diff options
author | DongHun Kwak <dh0128.kwak@samsung.com> | 2021-03-03 15:14:38 +0900 |
---|---|---|
committer | DongHun Kwak <dh0128.kwak@samsung.com> | 2021-03-03 15:14:38 +0900 |
commit | 5ff6a8462f7461075fb39cd0d6d76e6c51ffbc9f (patch) | |
tree | ae4581c010d54ff4c58c050bb38a34ed80b42eb1 | |
parent | 19729fb7029c2bb287c72c7dcc5e5a293a069d09 (diff) | |
download | git-5ff6a8462f7461075fb39cd0d6d76e6c51ffbc9f.tar.gz git-5ff6a8462f7461075fb39cd0d6d76e6c51ffbc9f.tar.bz2 git-5ff6a8462f7461075fb39cd0d6d76e6c51ffbc9f.zip |
Imported Upstream version 2.1.0upstream/2.1.0
386 files changed, 14238 insertions, 5152 deletions
@@ -165,6 +165,7 @@ /git-upload-archive /git-upload-pack /git-var +/git-verify-commit /git-verify-pack /git-verify-tag /git-web--browse @@ -180,6 +181,7 @@ /test-date /test-delta /test-dump-cache-tree +/test-dump-split-index /test-scrap-cache-tree /test-genrandom /test-hashmap @@ -226,6 +228,7 @@ /config.mak.autogen /config.mak.append /configure +/unicode /tags /TAGS /cscope* @@ -85,6 +85,7 @@ Jeff King <peff@peff.net> <peff@github.com> Jeff Muizelaar <jmuizelaar@mozilla.com> <jeff@infidigm.net> Jens Axboe <axboe@kernel.dk> <axboe@suse.de> Jens Axboe <axboe@kernel.dk> <jens.axboe@oracle.com> +Jens Lindström <jl@opera.com> Jens Lindstrom <jl@opera.com> Jim Meyering <jim@meyering.net> <meyering@redhat.com> Joachim Berdal Haga <cjhaga@fys.uio.no> Johannes Schindelin <Johannes.Schindelin@gmx.de> <johannes.schindelin@gmx.de> @@ -113,6 +114,7 @@ Karsten Blees <blees@dcon.de> <karsten.blees@dcon.de> Karsten Blees <blees@dcon.de> <karsten.blees@gmail.com> Kay Sievers <kay.sievers@vrfy.org> <kay.sievers@suse.de> Kay Sievers <kay.sievers@vrfy.org> <kay@mam.(none)> +Kazuki Saitoh <ksaitoh560@gmail.com> kazuki saitoh <ksaitoh560@gmail.com> Keith Cascio <keith@CS.UCLA.EDU> <keith@cs.ucla.edu> Kent Engstrom <kent@lysator.liu.se> Kevin Leung <kevinlsk@gmail.com> @@ -230,6 +232,7 @@ Tommi Virtanen <tv@debian.org> <tv@inoi.fi> Tommy Thorn <tommy-git@thorn.ws> <tt1729@yahoo.com> Tony Luck <tony.luck@intel.com> Tor Arne Vestbø <torarnv@gmail.com> <tavestbo@trolltech.com> +Trần Ngọc Quân <vnwildman@gmail.com> Tran Ngoc Quan <vnwildman@gmail.com> Trent Piepho <tpiepho@gmail.com> <tpiepho@freescale.com> Trent Piepho <tpiepho@gmail.com> <xyzzy@speakeasy.org> Uwe Kleine-König <u.kleine-koenig@pengutronix.de> <Uwe.Kleine-Koenig@digi.com> diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index f4137c68..894546dd 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -18,6 +18,14 @@ code. For Git in general, three rough rules are: judgement call, the decision based more on real world constraints people face than what the paper standard says. + - Fixing style violations while working on a real change as a + preparatory clean-up step is good, but otherwise avoid useless code + churn for the sake of conforming to the style. + + "Once it _is_ in the tree, it's not really worth the patch noise to + go and fix it up." + Cf. http://article.gmane.org/gmane.linux.kernel/943020 + Make your code readable and sensible, and don't try to be clever. As for more concrete guidelines, just imitate the existing code @@ -34,7 +42,17 @@ For shell scripts specifically (not exhaustive): - We use tabs for indentation. - - Case arms are indented at the same depth as case and esac lines. + - Case arms are indented at the same depth as case and esac lines, + like this: + + case "$variable" in + pattern1) + do this + ;; + pattern2) + do that + ;; + esac - Redirection operators should be written with space before, but no space after them. In other words, write 'echo test >"$file"' @@ -43,6 +61,14 @@ For shell scripts specifically (not exhaustive): redirection target in a variable (as shown above), our code does so because some versions of bash issue a warning without the quotes. + (incorrect) + cat hello > world < universe + echo hello >$world + + (correct) + cat hello >world <universe + echo hello >"$world" + - We prefer $( ... ) for command substitution; unlike ``, it properly nests. It should have been the way Bourne spelled it from day one, but unfortunately isn't. @@ -81,14 +107,33 @@ For shell scripts specifically (not exhaustive): "then" should be on the next line for if statements, and "do" should be on the next line for "while" and "for". + (incorrect) + if test -f hello; then + do this + fi + + (correct) + if test -f hello + then + do this + fi + - We prefer "test" over "[ ... ]". - We do not write the noiseword "function" in front of shell functions. - - We prefer a space between the function name and the parentheses. The - opening "{" should also be on the same line. - E.g.: my_function () { + - We prefer a space between the function name and the parentheses, + and no space inside the parentheses. The opening "{" should also + be on the same line. + + (incorrect) + my_function(){ + ... + + (correct) + my_function () { + ... - As to use of grep, stick to a subset of BRE (namely, no \{m,n\}, [::], [==], or [..]) for portability. @@ -106,6 +151,19 @@ For shell scripts specifically (not exhaustive): interface translatable. See "Marking strings for translation" in po/README. + - We do not write our "test" command with "-a" and "-o" and use "&&" + or "||" to concatenate multiple "test" commands instead, because + the use of "-a/-o" is often error-prone. E.g. + + test -n "$x" -a "$a" = "$b" + + is buggy and breaks when $x is "=", but + + test -n "$x" && test "$a" = "$b" + + does not have such a problem. + + For C programs: - We use tabs to indent, and interpret tabs as taking up to @@ -149,7 +207,7 @@ For C programs: of "else if" statements, it can make sense to add braces to single line blocks. - - We try to avoid assignments inside if(). + - We try to avoid assignments in the condition of an "if" statement. - Try to make your code understandable. You may put comments in, but comments invariably tend to stale out when the code @@ -177,6 +235,88 @@ For C programs: - Double negation is often harder to understand than no negation at all. + - There are two schools of thought when it comes to comparison, + especially inside a loop. Some people prefer to have the less stable + value on the left hand side and the more stable value on the right hand + side, e.g. if you have a loop that counts variable i down to the + lower bound, + + while (i > lower_bound) { + do something; + i--; + } + + Other people prefer to have the textual order of values match the + actual order of values in their comparison, so that they can + mentally draw a number line from left to right and place these + values in order, i.e. + + while (lower_bound < i) { + do something; + i--; + } + + Both are valid, and we use both. However, the more "stable" the + stable side becomes, the more we tend to prefer the former + (comparison with a constant, "i > 0", is an extreme example). + Just do not mix styles in the same part of the code and mimic + existing styles in the neighbourhood. + + - There are two schools of thought when it comes to splitting a long + logical line into multiple lines. Some people push the second and + subsequent lines far enough to the right with tabs and align them: + + if (the_beginning_of_a_very_long_expression_that_has_to || + span_more_than_a_single_line_of || + the_source_text) { + ... + + while other people prefer to align the second and the subsequent + lines with the column immediately inside the opening parenthesis, + with tabs and spaces, following our "tabstop is always a multiple + of 8" convention: + + if (the_beginning_of_a_very_long_expression_that_has_to || + span_more_than_a_single_line_of || + the_source_text) { + ... + + Both are valid, and we use both. Again, just do not mix styles in + the same part of the code and mimic existing styles in the + neighbourhood. + + - When splitting a long logical line, some people change line before + a binary operator, so that the result looks like a parse tree when + you turn your head 90-degrees counterclockwise: + + if (the_beginning_of_a_very_long_expression_that_has_to + || span_more_than_a_single_line_of_the_source_text) { + + while other people prefer to leave the operator at the end of the + line: + + if (the_beginning_of_a_very_long_expression_that_has_to || + span_more_than_a_single_line_of_the_source_text) { + + Both are valid, but we tend to use the latter more, unless the + expression gets fairly complex, in which case the former tends to + be easier to read. Again, just do not mix styles in the same part + of the code and mimic existing styles in the neighbourhood. + + - When splitting a long logical line, with everything else being + equal, it is preferable to split after the operator at higher + level in the parse tree. That is, this is more preferable: + + if (a_very_long_variable * that_is_used_in + + a_very_long_expression) { + ... + + than + + if (a_very_long_variable * + that_is_used_in + a_very_long_expression) { + ... + - Some clever tricks, like using the !! operator with arithmetic constructs, can be extremely confusing to others. Avoid them, unless there is a compelling reason to use them. diff --git a/Documentation/Makefile b/Documentation/Makefile index fc6b2cf9..cea0e7ae 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -59,6 +59,7 @@ SP_ARTICLES += howto/recover-corrupted-blob-object SP_ARTICLES += howto/recover-corrupted-object-harder SP_ARTICLES += howto/rebuild-from-update-hook SP_ARTICLES += howto/rebase-from-internal-branch +SP_ARTICLES += howto/keep-canonical-history-correct SP_ARTICLES += howto/maintain-git API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt))) SP_ARTICLES += $(API_DOCS) diff --git a/Documentation/RelNotes/1.8.5.6.txt b/Documentation/RelNotes/1.8.5.6.txt deleted file mode 100644 index 92ff92b1..00000000 --- a/Documentation/RelNotes/1.8.5.6.txt +++ /dev/null @@ -1,34 +0,0 @@ -Git v1.8.5.6 Release Notes -========================== - -Fixes since v1.8.5.5 --------------------- - - * We used to allow committing a path ".Git/config" with Git that is - running on a case sensitive filesystem, but an attempt to check out - such a path with Git that runs on a case insensitive filesystem - would have clobbered ".git/config", which is definitely not what - the user would have expected. Git now prevents you from tracking - a path with ".Git" (in any case combination) as a path component. - - * On Windows, certain path components that are different from ".git" - are mapped to ".git", e.g. "git~1/config" is treated as if it were - ".git/config". HFS+ has a similar issue, where certain unicode - codepoints are ignored, e.g. ".g\u200cit/config" is treated as if - it were ".git/config". Pathnames with these potential issues are - rejected on the affected systems. Git on systems that are not - affected by this issue (e.g. Linux) can also be configured to - reject them to ensure cross platform interoperability of the hosted - projects. - - * "git fsck" notices a tree object that records such a path that can - be confused with ".git", and with receive.fsckObjects configuration - set to true, an attempt to "git push" such a tree object will be - rejected. Such a path may not be a problem on a well behaving - filesystem but in order to protect those on HFS+ and on case - insensitive filesystems, this check is enabled on all platforms. - -A big "thanks!" for bringing this issue to us goes to our friends in -the Mercurial land, namely, Matt Mackall and Augie Fackler. - -Also contains typofixes, documentation updates and trivial code clean-ups. diff --git a/Documentation/RelNotes/1.9.5.txt b/Documentation/RelNotes/1.9.5.txt deleted file mode 100644 index 8d6ac0cf..00000000 --- a/Documentation/RelNotes/1.9.5.txt +++ /dev/null @@ -1,34 +0,0 @@ -Git v1.9.5 Release Notes -======================== - -Fixes since v1.9.4 ------------------- - - * We used to allow committing a path ".Git/config" with Git that is - running on a case sensitive filesystem, but an attempt to check out - such a path with Git that runs on a case insensitive filesystem - would have clobbered ".git/config", which is definitely not what - the user would have expected. Git now prevents you from tracking - a path with ".Git" (in any case combination) as a path component. - - * On Windows, certain path components that are different from ".git" - are mapped to ".git", e.g. "git~1/config" is treated as if it were - ".git/config". HFS+ has a similar issue, where certain unicode - codepoints are ignored, e.g. ".g\u200cit/config" is treated as if - it were ".git/config". Pathnames with these potential issues are - rejected on the affected systems. Git on systems that are not - affected by this issue (e.g. Linux) can also be configured to - reject them to ensure cross platform interoperability of the hosted - projects. - - * "git fsck" notices a tree object that records such a path that can - be confused with ".git", and with receive.fsckObjects configuration - set to true, an attempt to "git push" such a tree object will be - rejected. Such a path may not be a problem on a well behaving - filesystem but in order to protect those on HFS+ and on case - insensitive filesystems, this check is enabled on all platforms. - -A big "thanks!" for bringing this issue to us goes to our friends in -the Mercurial land, namely, Matt Mackall and Augie Fackler. - -Also contains typofixes, documentation updates and trivial code clean-ups. diff --git a/Documentation/RelNotes/2.0.5.txt b/Documentation/RelNotes/2.0.5.txt deleted file mode 100644 index 3a16f697..00000000 --- a/Documentation/RelNotes/2.0.5.txt +++ /dev/null @@ -1,34 +0,0 @@ -Git v2.0.5 Release Notes -======================== - -Fixes since v2.0.4 ------------------- - - * We used to allow committing a path ".Git/config" with Git that is - running on a case sensitive filesystem, but an attempt to check out - such a path with Git that runs on a case insensitive filesystem - would have clobbered ".git/config", which is definitely not what - the user would have expected. Git now prevents you from tracking - a path with ".Git" (in any case combination) as a path component. - - * On Windows, certain path components that are different from ".git" - are mapped to ".git", e.g. "git~1/config" is treated as if it were - ".git/config". HFS+ has a similar issue, where certain unicode - codepoints are ignored, e.g. ".g\u200cit/config" is treated as if - it were ".git/config". Pathnames with these potential issues are - rejected on the affected systems. Git on systems that are not - affected by this issue (e.g. Linux) can also be configured to - reject them to ensure cross platform interoperability of the hosted - projects. - - * "git fsck" notices a tree object that records such a path that can - be confused with ".git", and with receive.fsckObjects configuration - set to true, an attempt to "git push" such a tree object will be - rejected. Such a path may not be a problem on a well behaving - filesystem but in order to protect those on HFS+ and on case - insensitive filesystems, this check is enabled on all platforms. - -A big "thanks!" for bringing this issue to us goes to our friends in -the Mercurial land, namely, Matt Mackall and Augie Fackler. - -Also contains typofixes, documentation updates and trivial code clean-ups. diff --git a/Documentation/RelNotes/2.1.0.txt b/Documentation/RelNotes/2.1.0.txt new file mode 100644 index 00000000..ae475372 --- /dev/null +++ b/Documentation/RelNotes/2.1.0.txt @@ -0,0 +1,391 @@ +Git v2.1 Release Notes +====================== + +Backward compatibility notes +---------------------------- + + * The default value we give to the environment variable LESS has been + changed from "FRSX" to "FRX", losing "S" (chop long lines instead + of wrapping). Existing users who prefer not to see line-wrapped + output may want to set + + $ git config core.pager "less -S" + + to restore the traditional behaviour. It is expected that people + find output from most subcommands easier to read with the new + default, except for "blame" which tends to produce really long + lines. To override the new default only for "git blame", you can + do this: + + $ git config pager.blame "less -S" + + * A few disused directories in contrib/ have been retired. + + +Updates since v2.0 +------------------ + +UI, Workflows & Features + + * Since the very beginning of Git, we gave the LESS environment a + default value "FRSX" when we spawn "less" as the pager. "S" (chop + long lines instead of wrapping) has been removed from this default + set of options, because it is more or less a personal taste thing, + as opposed to the others that have good justifications (i.e. "R" is + very much justified because many kinds of output we produce are + colored and "FX" is justified because output we produce is often + shorter than a page). + + * The logic and data used to compute the display width needed for + UTF-8 strings have been updated to match Unicode 7.0 better. + + * HTTP-based transports learned to better propagate the error messages from + the webserver to the client coming over the HTTP transport. + + * The completion script for bash (in contrib/) has been updated to + better handle aliases that define a complex sequence of commands. + + * The "core.preloadindex" configuration variable is enabled by default, + allowing modern platforms to take advantage of their + multiple cores. + + * "git clone" applies the "if cloning from a local disk, physically + copy the repository using hardlinks, unless otherwise told not to with + --no-local" optimization when the url.*.insteadOf mechanism rewrites a + remote-repository "git clone $URL" into a + clone from a local disk. + + * "git commit --date=<date>" option learned more + timestamp formats, including "--date=now". + + * The `core.commentChar` configuration variable is used to specify a + custom comment character (other than the default "#") for + the commit message editor. This can be set to `auto` to attempt to + choose a different character that does not conflict with any that + already starts a line in the message being edited, for cases like + "git commit --amend". + + * "git format-patch" learned --signature-file=<file> to add the contents + of a file as a signature to the mail message it produces. + + * "git grep" learned the grep.fullname configuration variable to force + "--full-name" to be the default. This may cause regressions for + scripted users who do not expect this new behaviour. + + * "git imap-send" learned to ask the credential helper for auth + material. + + * "git log" and friends now understand the value "auto" for the + "log.decorate" configuration variable to enable the "--decorate" + option automatically when the output is sent to tty. + + * "git merge" without an argument, even when there is an upstream + defined for the current branch, refused to run until + merge.defaultToUpstream is set to true. Flip the default of that + configuration variable to true. + + * "git mergetool" learned to drive the vimdiff3 backend. + + * mergetool.prompt used to default to 'true', always asking "do you + really want to run the tool on this path?". The default has been + changed to 'false'. However, the prompt will still appear if + mergetool used its autodetection system to guess which tool to use. + Users who explicitly specify or configure a tool will no longer see + the prompt by default. + + Strictly speaking, this is a backward incompatible change and + users need to explicitly set the variable to 'true' if they want + to be prompted to confirm running the tool on each path. + + * "git replace" learned the "--edit" subcommand to create a + replacement by editing an existing object. + + * "git replace" learned a "--graft" option to rewrite the parents of a + commit. + + * "git send-email" learned "--to-cover" and "--cc-cover" options, to + tell it to copy To: and Cc: headers found in the first input file + when emitting later input files. + + * "git svn" learned to cope with malformed timestamps with only one + digit in the hour part, e.g. 2014-01-07T5:01:02.048176Z, emitted + by some broken subversion server implementations. + + * "git tag" when editing the tag message shows the name of the tag + being edited as a comment in the editor. + + * "git tag" learned to pay attention to "tag.sort" configuration, to + be used as the default sort order when no --sort=<value> option + is given. + + * A new "git verify-commit" command, to check GPG signatures in signed + commits, in a way similar to "git verify-tag" is used to check + signed tags, was added. + + +Performance, Internal Implementation, etc. + + * Build procedure for 'subtree' (in contrib/) has been cleaned up. + + * Support for the profile-feedback build, which has + bit-rotted for quite a while, has been updated. + + * An experimental format to use two files (the base file and + incremental changes relative to it) to represent the index has been + introduced; this may reduce I/O cost of rewriting a large index + when only small part of the working tree changes. + + * Effort to shrink the size of patches Windows folks maintain on top + by upstreaming them continues. More tests that are not applicable + to the Windows environment are identified and either skipped or + made more portable. + + * Eradication of "test $condition -a $condition" from our scripts + continues. + + * The `core.deltabasecachelimit` used to default to 16 MiB , but this + proved to be too small, and has been bumped to 96 MiB. + + * "git blame" has been optimized greatly by reorganising the data + structure that is used to keep track of the work to be done. + + * "git diff" that compares 3-or-more trees (e.g. parents and the + result of a merge) has been optimized. + + * The API to update/delete references are being converted to handle + updates to multiple references in a transactional way. As an + example, "update-ref --stdin [-z]" has been updated to use this + API. + + * skip_prefix() and strip_suffix() API functions are used a lot more + widely throughout the codebase now. + + * Parts of the test scripts can be skipped by using a range notation, + e.g. "sh t1234-test.sh --run='1-4 6 8-'" to omit test piece 5 and 7 + and run everything else. + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v2.0 +---------------- + +Unless otherwise noted, all the fixes since v2.0 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * We used to unconditionally disable the pager in the pager process + we spawn to feed out output, but that prevented people who want to + run "less" within "less" from doing so. + (merge c0459ca je/pager-do-not-recurse later to maint). + + * Tools that read diagnostic output in our standard error stream do + not want to see terminal control sequence (e.g. erase-to-eol). + Detect them by checking if the standard error stream is connected + to a tty. + (merge 38de156 mn/sideband-no-ansi later to maint). + + * Mishandling of patterns in .gitignore that have trailing SPs quoted + with backslashes (e.g. ones that end with "\ ") has been + corrected. + (merge 97c1364be6b pb/trim-trailing-spaces later to maint). + + * Reworded the error message given upon a failure to open an existing + loose object file due to e.g. permission issues; it was reported as + the object being corrupt, but that is not quite true. + (merge d6c8a05 jk/report-fail-to-read-objects-better later to maint). + + * "git log -2master" is a common typo that shows two commits starting + from whichever random branch that is not 'master' that happens to + be checked out currently. + (merge e3fa568 jc/revision-dash-count-parsing later to maint). + + * Code to avoid adding the same alternate object store twice was + subtly broken for a long time, but nobody seems to have noticed. + (merge 80b4785 rs/fix-alt-odb-path-comparison later to maint). + (merge 539e750 ek/alt-odb-entry-fix later to maint). + + * The "%<(10,trunc)%s" pretty format specifier in the log family of + commands is used to truncate the string to a given length (e.g. 10 + in the example) with padding to column-align the output, but did + not take into account that number of bytes and number of display + columns are different. + (merge 7d50987 as/pretty-truncate later to maint). + + * "%G" (nothing after G) is an invalid pretty format specifier, but + the parser did not notice it as garbage. + (merge 958b2eb jk/pretty-G-format-fixes later to maint). + + * A handful of code paths had to read the commit object more than + once when showing header fields that are usually not parsed. The + internal data structure to keep track of the contents of the commit + object has been updated to reduce the need for this double-reading, + and to allow the caller find the length of the object. + (merge 218aa3a jk/commit-buffer-length later to maint). + + * The "mailmap.file" configuration option did not support tilde + expansion (i.e. ~user/path and ~/path). + (merge 9352fd5 ow/config-mailmap-pathname later to maint). + + * The completion scripts (in contrib/) did not know about quite a few + options that are common between "git merge" and "git pull", and a + couple of options unique to "git merge". + (merge 8fee872 jk/complete-merge-pull later to maint). + + * The unix-domain socket used by the sample credential cache daemon + tried to unlink an existing stale one at a wrong path, if the path + to the socket was given as an overlong path that does not fit in + the sun_path member of the sockaddr_un structure. + (merge 2869b3e rs/fix-unlink-unix-socket later to maint). + + * An ancient rewrite passed a wrong pointer to a curl library + function in a rarely used code path. + (merge 479eaa8 ah/fix-http-push later to maint). + + * "--ignore-space-change" option of "git apply" ignored the spaces + at the beginning of lines too aggressively, which is inconsistent + with the option of the same name that "diff" and "git diff" have. + (merge 14d3bb4 jc/apply-ignore-whitespace later to maint). + + * "git blame" miscounted the number of columns needed to show localized + timestamps, resulting in a jaggy left-side-edge for the source code + lines in its output. + (merge dd75553 jx/blame-align-relative-time later to maint). + + * "git blame" assigned the blame to the copy in the working-tree if + the repository is set to core.autocrlf=input and the file used CRLF + line endings. + (merge 4d4813a bc/blame-crlf-test later to maint). + + * "git clone -b brefs/tags/bar" would have mistakenly thought we were + following a single tag, even though it was a name of the branch, + because it incorrectly used strstr(). + (merge 60a5f5f jc/fix-clone-single-starting-at-a-tag later to maint). + + * "git commit --allow-empty-message -C $commit" did not work when the + commit did not have any log message. + (merge 076cbd6 jk/commit-C-pick-empty later to maint). + + * "git diff --find-copies-harder" sometimes pretended as if the mode + bits have changed for paths that are marked with the assume-unchanged + bit. + (merge 5304810 jk/diff-files-assume-unchanged later to maint). + + * "filter-branch" left an empty single-parent commit that results when + all parents of a merge commit get mapped to the same commit, even + under "--prune-empty". + (merge 79bc4ef cb/filter-branch-prune-empty-degenerate-merges later to maint). + + * "git format-patch" did not enforce the rule that the "--follow" + option from the log/diff family of commands must be used with + exactly one pathspec. + (merge dd63f16 jk/diff-follow-must-take-one-pathspec later to maint). + + * "git gc --auto" was recently changed to run in the background to + give control back early to the end-user sitting in front of the + terminal, but it forgot that housekeeping involving reflogs should + be done without other processes competing for accesses to the refs. + (merge 62aad18 nd/daemonize-gc later to maint). + + * "git grep -O" to show the lines that hit in the pager did not work + well with case insensitive search. We now spawn "less" with its + "-I" option when it is used as the pager (which is the default). + (merge f7febbe sk/spawn-less-case-insensitively-from-grep-O-i later to maint). + + * We used to disable threaded "git index-pack" on platforms without + thread-safe pread(); use a different workaround for such + platforms to allow threaded "git index-pack". + (merge 3953949 nd/index-pack-one-fd-per-thread later to maint). + + * The error reporting from "git index-pack" has been improved to + distinguish missing objects from type errors. + (merge 77583e7 jk/index-pack-report-missing later to maint). + + * "log --show-signature" incorrectly decided the color to paint a + mergetag that was and was not correctly validated. + (merge 42c55ce mg/fix-log-mergetag-color later to maint). + + * "log --show-signature" did not pay attention to the "--graph" option. + (merge cf3983d zk/log-graph-showsig later to maint). + + * "git mailinfo" used to read beyond the ends of header strings while + parsing an incoming e-mail message to extract the patch. + (merge b1a013d rs/mailinfo-header-cmp later to maint). + + * On a case insensitive filesystem, merge-recursive incorrectly + deleted the file that is to be renamed to a name that is the same + except for case differences. + (merge baa37bf dt/merge-recursive-case-insensitive later to maint). + + * Merging changes into a file that ends in an incomplete line made the + last line into a complete one, even when the other branch did not + change anything around the end of file. + (merge ba31180 mk/merge-incomplete-files later to maint). + + * "git pack-objects" unnecessarily copied the previous contents when + extending the hashtable, even though it will populate the table + from scratch anyway. + (merge fb79947 rs/pack-objects-no-unnecessary-realloc later to maint). + + * Recent updates to "git repack" started to duplicate objects that + are in packfiles marked with the .keep flag into the new packfile by + mistake. + (merge d078d85 jk/repack-pack-keep-objects later to maint). + + * "git rerere forget" did not work well when merge.conflictstyle + was set to a non-default value. + (merge de3d8bb fc/rerere-conflict-style later to maint). + + * "git remote rm" and "git remote prune" can involve removing many + refs at once, which is not a very efficient thing to do when very + many refs exist in the packed-refs file. + (merge e6bea66 jl/remote-rm-prune later to maint). + + * "git log --exclude=<glob> --all | git shortlog" worked as expected, + but "git shortlog --exclude=<glob> --all", which is supposed to be + identical to the above pipeline, was not accepted at the command + line argument parser level. + (merge eb07774 jc/shortlog-ref-exclude later to maint). + + * The autostash mode of "git rebase -i" did not restore the dirty + working tree state if the user aborted the interactive rebase by + emptying the insn sheet. + (merge ddb5432 rr/rebase-autostash-fix later to maint). + + * "git rebase --fork-point" did not filter out patch-identical + commits correctly. + + * During "git rebase --merge", a conflicted patch could not be + skipped with "--skip" if the next one also conflicted. + (merge 95104c7 bc/fix-rebase-merge-skip later to maint). + + * "git show -s" (i.e. show log message only) used to incorrectly emit + an extra blank line after a merge commit. + (merge ad2f725 mk/show-s-no-extra-blank-line-for-merges later to maint). + + * "git status", even though it is a read-only operation, tries to + update the index with refreshed lstat(2) info to optimize future + accesses to the working tree opportunistically, but this could + race with a "read-write" operation that modifies the index while it + is running. Detect such a race and avoid overwriting the index. + (merge 426ddee ym/fix-opportunistic-index-update-race later to maint). + + * "git status" (and "git commit") behaved as if changes in a modified + submodule are not there if submodule.*.ignore configuration is set, + which was misleading. The configuration is only to unclutter diff + output during the course of development, and not to hide + changes in the "status" output to cause the users forget to commit + them. + (merge c215d3d jl/status-added-submodule-is-never-ignored later to maint). + + * Documentation for "git submodule sync" forgot to say that the subcommand + can take the "--recursive" option. + (merge 9393ae7 mc/doc-submodule-sync-recurse later to maint). + + * "git update-index --cacheinfo" in 2.0 release crashed on a + malformed command line. + (merge c8e1ee4 jc/rev-parse-argh-dashed-multi-words later to maint). + + * The mode to run tests with HTTP server tests disabled was broken. + (merge afa53fe na/no-http-test-in-the-middle later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 0c325976..c55c22ab 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -233,17 +233,6 @@ core.precomposeunicode:: When false, file names are handled fully transparent by Git, which is backward compatible with older versions of Git. -core.protectHFS:: - If set to true, do not allow checkout of paths that would - be considered equivalent to `.git` on an HFS+ filesystem. - Defaults to `true` on Mac OS, and `false` elsewhere. - -core.protectNTFS:: - If set to true, do not allow checkout of paths that would - cause problems with the NTFS filesystem, e.g. conflict with - 8.3 "short" names. - Defaults to `true` on Windows, and `false` elsewhere. - core.trustctime:: If false, the ctime differences between the index and the working tree are ignored; useful when the inode change time @@ -392,7 +381,7 @@ false), while all other repositories are assumed to be bare (bare core.worktree:: Set the path to the root of the working tree. This can be overridden by the GIT_WORK_TREE environment - variable and the '--work-tree' command line option. + variable and the '--work-tree' command-line option. The value can be an absolute path or relative to the path to the .git directory, which is either specified by --git-dir or GIT_DIR, or automatically discovered. @@ -500,7 +489,7 @@ core.deltaBaseCacheLimit:: to avoid unpacking and decompressing frequently used base objects multiple times. + -Default is 16 MiB on all platforms. This should be reasonable +Default is 96 MiB on all platforms. This should be reasonable for all users/operating systems, except on the largest projects. You probably do not need to adjust this value. + @@ -534,7 +523,7 @@ core.askpass:: environment variable. If not set, fall back to the value of the 'SSH_ASKPASS' environment variable or, failing that, a simple password prompt. The external program shall be given a suitable prompt as - command line argument and write the password on its STDOUT. + command-line argument and write the password on its STDOUT. core.attributesfile:: In addition to '.gitattributes' (per-directory) and @@ -555,6 +544,9 @@ core.commentchar:: messages consider a line that begins with this character commented, and removes them after the editor returns (default '#'). ++ +If set to "auto", `git-commit` would select a character that is not +the beginning character of any line in existing commit messages. sequence.editor:: Text editor used by `git rebase -i` for editing the rebase instruction file. @@ -569,14 +561,19 @@ core.pager:: configuration, then `$PAGER`, and then the default chosen at compile time (usually 'less'). + -When the `LESS` environment variable is unset, Git sets it to `FRSX` +When the `LESS` environment variable is unset, Git sets it to `FRX` (if `LESS` environment variable is set, Git does not change it at all). If you want to selectively override Git's default setting -for `LESS`, you can set `core.pager` to e.g. `less -+S`. This will +for `LESS`, you can set `core.pager` to e.g. `less -S`. This will be passed to the shell by Git, which will translate the final -command to `LESS=FRSX less -+S`. The environment tells the command -to set the `S` option to chop long lines but the command line -resets it to the default to fold long lines. +command to `LESS=FRX less -S`. The environment does not set the +`S` option but the command line does, instructing less to truncate +long lines. Similarly, setting `core.pager` to `less -+F` will +deactivate the `F` option specified by the environment from the +command-line, deactivating the "quit if one screen" behavior of +`less`. One can specifically activate some flags for particular +commands: for example, setting `pager.blame` to `less -S` enables +line truncation only for `git blame`. + Likewise, when the `LV` environment variable is unset, Git sets it to `-c`. You can override this setting by exporting `LV` with @@ -624,9 +621,9 @@ core.preloadindex:: + This can speed up operations like 'git diff' and 'git status' especially on filesystems like NFS that have weak caching semantics and thus -relatively high IO latencies. With this set to 'true', Git will do the +relatively high IO latencies. When enabled, Git will do the index comparison to the filesystem data in parallel, allowing -overlapping IO's. +overlapping IO's. Defaults to true. core.createObject:: You can set this to 'link', in which case a hardlink followed by @@ -1125,6 +1122,10 @@ format.signature:: Set this variable to the empty string ("") to suppress signature generation. +format.signaturefile:: + Works just like format.signature except the contents of the + file specified by this variable will be used as the signature. + format.suffix:: The default for format-patch is to output files with the suffix `.patch`. Use this variable to change that suffix (make sure to @@ -1335,7 +1336,7 @@ grep.extendedRegexp:: gpg.program:: Use this custom program instead of "gpg" found on $PATH when making or verifying a PGP signature. The program must support the - same command line interface as GPG, namely, to verify a detached + same command-line interface as GPG, namely, to verify a detached signature, "gpg --verify $file - <$signature" is run, and the program is expected to signal a good signature by exiting with code 0, and to generate an ascii-armored detached signature, the @@ -1904,12 +1905,7 @@ pack.useBitmaps:: you are debugging pack bitmaps. pack.writebitmaps:: - When true, git will write a bitmap index when packing all - objects to disk (e.g., when `git repack -a` is run). This - index can speed up the "counting objects" phase of subsequent - packs created for clones and fetches, at the cost of some disk - space and extra time spent on the initial repack. Defaults to - false. + This is a deprecated synonym for `repack.writeBitmaps`. pack.writeBitmapHashCache:: When true, git will include a "hash cache" section in the bitmap @@ -2186,7 +2182,15 @@ repack.packKeptObjects:: `--pack-kept-objects` was passed. See linkgit:git-repack[1] for details. Defaults to `false` normally, but `true` if a bitmap index is being written (either via `--write-bitmap-index` or - `pack.writeBitmaps`). + `repack.writeBitmaps`). + +repack.writeBitmaps:: + When true, git will write a bitmap index when packing all + objects to disk (e.g., when `git repack -a` is run). This + index can speed up the "counting objects" phase of subsequent + packs created for clones and fetches, at the cost of some disk + space and extra time spent on the initial repack. Defaults to + false. rerere.autoupdate:: When set to true, `git-rerere` updates the index with the @@ -2308,7 +2312,7 @@ status.submodulesummary:: exception to that rule is that status and commit will show staged submodule changes. To also view the summary for ignored submodules you can either use - the --ignore-submodules=dirty command line option or the 'git + the --ignore-submodules=dirty command-line option or the 'git submodule summary' command, which shows a similar output but does not honor these settings. @@ -2330,7 +2334,7 @@ submodule.<name>.branch:: submodule.<name>.fetchRecurseSubmodules:: This option can be used to control recursive fetching of this submodule. It can be overridden by using the --[no-]recurse-submodules - command line option to "git fetch" and "git pull". + command-line option to "git fetch" and "git pull". This setting will override that from in the linkgit:gitmodules[5] file. @@ -2350,6 +2354,11 @@ submodule.<name>.ignore:: "--ignore-submodules" option. The 'git submodule' commands are not affected by this setting. +tag.sort:: + This variable controls the sort ordering of tags when displayed by + linkgit:git-tag[1]. Without the "--sort=<value>" option provided, the + value of this variable will be used as the default. + tar.umask:: This variable can be used to restrict the permission bits of tar archive entries. The default is 0002, which turns off the diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index f07b4513..b0017795 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -76,7 +76,7 @@ diff.ignoreSubmodules:: this setting when reporting uncommitted changes. Setting it to 'all' disables the submodule summary normally shown by 'git commit' and 'git status' when 'status.submodulesummary' is set unless it is - overridden by using the --ignore-submodules command line option. + overridden by using the --ignore-submodules command-line option. The 'git submodule' commands are not affected by this setting. diff.mnemonicprefix:: diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 92c68c3f..b09a783e 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -72,6 +72,14 @@ endif::git-pull[] setting. See linkgit:git-config[1]. ifndef::git-pull[] +--refmap=<refspec>:: + When fetching refs listed on the command line, use the + specified refspec (can be given more than once) to map the + refs to remote-tracking branches, instead of the values of + `remote.*.fetch` configuration variables for the remote + repository. See section on "Configured Remote-tracking + Branches" for details. + -t:: --tags:: Fetch all tags from the remote (i.e., fetch remote tags diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index f986c5cb..4cb52a73 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -117,7 +117,7 @@ $ git bisect visualize `view` may also be used as a synonym for `visualize`. If the 'DISPLAY' environment variable is not set, 'git log' is used -instead. You can also give command line options such as `-p` and +instead. You can also give command-line options such as `-p` and `--stat`. ------------ diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index e9917b89..9dfa1a5c 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -256,7 +256,7 @@ All writing options will per default write to the repository specific configuration file. Note that this also affects options like '--replace-all' and '--unset'. *'git config' will only ever change one file at a time*. -You can override these rules either by command line options or by environment +You can override these rules either by command-line options or by environment variables. The '--global' and the '--system' options will limit the file used to the global or system-wide file respectively. The GIT_CONFIG environment variable has a similar effect, but you can specify any filename you want. diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 223f7315..a69b3616 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -169,7 +169,7 @@ Git configuration files in that directory are readable by `<user>`. --forbid-override=<service>:: Allow/forbid overriding the site-wide default with per repository configuration. By default, all the services - are overridable. + may be overridden. --[no-]informative-errors:: When informative errors are turned on, git-daemon will report @@ -184,7 +184,7 @@ Git configuration files in that directory are readable by `<user>`. Every time a client connects, first run an external command specified by the <path> with service name (e.g. "upload-pack"), path to the repository, hostname (%H), canonical hostname - (%CH), ip address (%IP), and tcp port (%P) as its command line + (%CH), IP address (%IP), and TCP port (%P) as its command-line arguments. The external command can decide to decline the service by exiting with a non-zero status (or to allow it by exiting with a zero status). It can also look at the $REMOTE_ADDR @@ -204,7 +204,7 @@ SERVICES -------- These services can be globally enabled/disabled using the -command line options of this command. If a finer-grained +command-line options of this command. If finer-grained control is desired (e.g. to allow 'git archive' to be run against only in a few selected repositories the daemon serves), the per-repository configuration file can be used to enable or diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index 85f1f30f..221506b0 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -105,6 +105,10 @@ marks the same across runs. in the commit (as opposed to just listing the files which are different from the commit's first parent). +--refspec:: + Apply the specified refspec to each ref exported. Multiple of them can + be specified. + [<git-rev-list-args>...]:: A list of arguments, acceptable to 'git rev-parse' and 'git rev-list', that specifies the specific objects and references diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index fd22a9a0..377eeaa3 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -231,7 +231,7 @@ Date Formats ~~~~~~~~~~~~ The following date formats are supported. A frontend should select the format it will use for this import by passing the format name -in the \--date-format=<fmt> command line option. +in the \--date-format=<fmt> command-line option. `raw`:: This is the Git native format and is `<time> SP <offutc>`. @@ -348,7 +348,7 @@ and control the current import process. More detailed discussion `done`:: Marks the end of the stream. This command is optional unless the `done` feature was requested using the - `--done` command line option or `feature done` command. + `--done` command-line option or `feature done` command. `cat-blob`:: Causes fast-import to print a blob in 'cat-file --batch' @@ -437,7 +437,7 @@ the email address from the other fields in the line. Note that of bytes, except `LT`, `GT` and `LF`. `<name>` is typically UTF-8 encoded. The time of the change is specified by `<when>` using the date format -that was selected by the \--date-format=<fmt> command line option. +that was selected by the \--date-format=<fmt> command-line option. See ``Date Formats'' above for the set of supported formats, and their syntax. @@ -483,6 +483,9 @@ Marks must be declared (via `mark`) before they can be used. * Any valid Git SHA-1 expression that resolves to a commit. See ``SPECIFYING REVISIONS'' in linkgit:gitrevisions[7] for details. +* The special null SHA-1 (40 zeros) specifies that the branch is to be + removed. + The special case of restarting an incremental import from the current branch value should be written as: ---- @@ -1085,7 +1088,7 @@ Option commands must be the first commands on the input (not counting feature commands), to give an option command after any non-option command is an error. -The following commandline options change import semantics and may therefore +The following command-line options change import semantics and may therefore not be passed as option: * date-format @@ -1099,7 +1102,7 @@ not be passed as option: If the `done` feature is not in use, treated as if EOF was read. This can be used to tell fast-import to finish early. -If the `--done` command line option or `feature done` command is +If the `--done` command-line option or `feature done` command is in use, the `done` command is mandatory and marks the end of the stream. diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index 5809aa4e..8deb6146 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -17,22 +17,20 @@ SYNOPSIS DESCRIPTION ----------- -Fetches named heads or tags from one or more other repositories, -along with the objects necessary to complete them. +Fetch branches and/or tags (collectively, "refs") from one or more +other repositories, along with the objects necessary to complete their +histories. Remote-tracking branches are updated (see the description +of <refspec> below for ways to control this behavior). -The ref names and their object names of fetched refs are stored -in `.git/FETCH_HEAD`. This information is left for a later merge -operation done by 'git merge'. - -By default, tags are auto-followed. This means that when fetching -from a remote, any tags on the remote that point to objects that exist -in the local repository are fetched. The effect is to fetch tags that +By default, any tag that points into the histories being fetched is +also fetched; the effect is to fetch tags that point at branches that you are interested in. This default behavior -can be changed by using the --tags or --no-tags options, by -configuring remote.<name>.tagopt, or by using a refspec that fetches -tags explicitly. +can be changed by using the --tags or --no-tags options or by +configuring remote.<name>.tagopt. By using a refspec that fetches tags +explicitly, you can fetch tags that do not point into branches you +are interested in as well. -'git fetch' can fetch from either a single named repository, +'git fetch' can fetch from either a single named repository or URL, or from several repositories at once if <group> is given and there is a remotes.<group> entry in the configuration file. (See linkgit:git-config[1]). @@ -40,6 +38,10 @@ there is a remotes.<group> entry in the configuration file. When no remote is specified, by default the `origin` remote will be used, unless there's an upstream branch configured for the current branch. +The names of refs that are fetched, together with the object names +they point at, are written to `.git/FETCH_HEAD`. This information +may be used by scripts or other git commands, such as linkgit:git-pull[1]. + OPTIONS ------- include::fetch-options.txt[] @@ -49,6 +51,55 @@ include::pull-fetch-param.txt[] include::urls-remotes.txt[] +CONFIGURED REMOTE-TRACKING BRANCHES[[CRTB]] +------------------------------------------- + +You often interact with the same remote repository by +regularly and repeatedly fetching from it. In order to keep track +of the progress of such a remote repository, `git fetch` allows you +to configure `remote.<repository>.fetch` configuration variables. + +Typically such a variable may look like this: + +------------------------------------------------ +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* +------------------------------------------------ + +This configuration is used in two ways: + +* When `git fetch` is run without specifying what branches + and/or tags to fetch on the command line, e.g. `git fetch origin` + or `git fetch`, `remote.<repository>.fetch` values are used as + the refspecs---they specify which refs to fetch and which local refs + to update. The example above will fetch + all branches that exist in the `origin` (i.e. any ref that matches + the left-hand side of the value, `refs/heads/*`) and update the + corresponding remote-tracking branches in the `refs/remotes/origin/*` + hierarchy. + +* When `git fetch` is run with explicit branches and/or tags + to fetch on the command line, e.g. `git fetch origin master`, the + <refspec>s given on the command line determine what are to be + fetched (e.g. `master` in the example, + which is a short-hand for `master:`, which in turn means + "fetch the 'master' branch but I do not explicitly say what + remote-tracking branch to update with it from the command line"), + and the example command will + fetch _only_ the 'master' branch. The `remote.<repository>.fetch` + values determine which + remote-tracking branch, if any, is updated. When used in this + way, the `remote.<repository>.fetch` values do not have any + effect in deciding _what_ gets fetched (i.e. the values are not + used as refspecs when the command-line lists refspecs); they are + only used to decide _where_ the refs that are fetched are stored + by acting as a mapping. + +The latter use of the `remote.<repository>.fetch` values can be +overridden by giving the `--refmap=<refspec>` parameter(s) on the +command line. + + EXAMPLES -------- @@ -76,6 +127,19 @@ the local repository by fetching from the branches (respectively) The `pu` branch will be updated even if it is does not fast-forward, because it is prefixed with a plus sign; `tmp` will not be. +* Peek at a remote's branch, without configuring the remote in your local +repository: ++ +------------------------------------------------ +$ git fetch git://git.kernel.org/pub/scm/git/git.git maint +$ git log FETCH_HEAD +------------------------------------------------ ++ +The first command fetches the `maint` branch from the repository at +`git://git.kernel.org/pub/scm/git/git.git` and the second command uses +`FETCH_HEAD` to examine the branch with linkgit:git-log[1]. The fetched +objects will eventually be removed by git's built-in housekeeping (see +linkgit:git-gc[1]). BUGS ---- diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 5c0a4ab2..c0fd470d 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -14,6 +14,7 @@ SYNOPSIS [(--attach|--inline)[=<boundary>] | --no-attach] [-s | --signoff] [--signature=<signature> | --no-signature] + [--signature-file=<file>] [-n | --numbered | -N | --no-numbered] [--start-number <n>] [--numbered-files] [--in-reply-to=Message-Id] [--suffix=.<sfx>] @@ -233,6 +234,9 @@ configuration options in linkgit:git-notes[1] to use this workflow). signature option is omitted the signature defaults to the Git version number. +--signature-file=<file>:: + Works just like --signature except the signature is read from a file. + --suffix=.<sfx>:: Instead of using `.patch` as the suffix for generated filenames, use specified suffix. A common alternative is diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index f8373349..31811f16 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -53,6 +53,9 @@ grep.extendedRegexp:: option is ignored when the 'grep.patternType' option is set to a value other than 'default'. +grep.fullName:: + If set to true, enable '--full-name' option by default. + OPTIONS ------- diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index b21e9d79..39565252 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -80,9 +80,9 @@ CONFIGURATION VARIABLES help.format ~~~~~~~~~~~ -If no command line option is passed, the 'help.format' configuration +If no command-line option is passed, the 'help.format' configuration variable will be checked. The following values are supported for this -variable; they make 'git help' behave as their corresponding command +variable; they make 'git help' behave as their corresponding command- line option: * "man" corresponds to '-m|--man', @@ -93,15 +93,15 @@ help.browser, web.browser and browser.<tool>.path ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The 'help.browser', 'web.browser' and 'browser.<tool>.path' will also -be checked if the 'web' format is chosen (either by command line +be checked if the 'web' format is chosen (either by command-line option or configuration variable). See '-w|--web' in the OPTIONS section above and linkgit:git-web{litdd}browse[1]. man.viewer ~~~~~~~~~~ -The 'man.viewer' config variable will be checked if the 'man' format -is chosen. The following values are currently supported: +The 'man.viewer' configuration variable will be checked if the 'man' +format is chosen. The following values are currently supported: * "man": use the 'man' program as usual, * "woman": use 'emacsclient' to launch the "woman" mode in emacs @@ -124,7 +124,7 @@ For example, this configuration: viewer = woman ------------------------------------------------ -will try to use konqueror first. But this may fail (for example if +will try to use konqueror first. But this may fail (for example, if DISPLAY is not set) and in that case emacs' woman mode will be tried. If everything fails, or if no viewer is configured, the viewer specified diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index c0856a6e..e26f01fb 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -185,15 +185,15 @@ specifies the format of exclude patterns. These exclude patterns come from these places, in order: - 1. The command line flag --exclude=<pattern> specifies a + 1. The command-line flag --exclude=<pattern> specifies a single pattern. Patterns are ordered in the same order they appear in the command line. - 2. The command line flag --exclude-from=<file> specifies a + 2. The command-line flag --exclude-from=<file> specifies a file containing a list of patterns. Patterns are ordered in the same order they appear in the file. - 3. The command line flag --exclude-per-directory=<name> specifies + 3. The command-line flag --exclude-per-directory=<name> specifies a name of the file in each directory 'git ls-files' examines, normally `.gitignore`. Files in deeper directories take precedence. Patterns are ordered in the diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index a3c1fa33..cf2c374b 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -101,9 +101,8 @@ commit or stash your changes before running 'git merge'. Specifying more than one commit will create a merge with more than two parents (affectionately called an Octopus merge). + -If no commit is given from the command line, and if `merge.defaultToUpstream` -configuration variable is set, merge the remote-tracking branches -that the current branch is configured to use as its upstream. +If no commit is given from the command line, merge the remote-tracking +branches that the current branch is configured to use as its upstream. See also the configuration section of this manual page. diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 07137f25..e846c2ed 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -71,11 +71,13 @@ success of the resolution after the custom tool has exited. --no-prompt:: Don't prompt before each invocation of the merge resolution program. + This is the default if the merge resolution program is + explicitly specified with the `--tool` option or with the + `merge.tool` configuration variable. --prompt:: - Prompt before each invocation of the merge resolution program. - This is the default behaviour; the option is provided to - override any configuration settings. + Prompt before each invocation of the merge resolution program + to give the user a chance to skip the path. TEMPORARY FILES --------------- diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt index 312c3b1f..31efc587 100644 --- a/Documentation/git-patch-id.txt +++ b/Documentation/git-patch-id.txt @@ -8,14 +8,14 @@ git-patch-id - Compute unique ID for a patch SYNOPSIS -------- [verse] -'git patch-id' < <patch> +'git patch-id' [--stable | --unstable] < <patch> DESCRIPTION ----------- -A "patch ID" is nothing but a SHA-1 of the diff associated with a patch, with -whitespace and line numbers ignored. As such, it's "reasonably stable", but at -the same time also reasonably unique, i.e., two patches that have the same "patch -ID" are almost guaranteed to be the same thing. +A "patch ID" is nothing but a sum of SHA-1 of the file diffs associated with a +patch, with whitespace and line numbers ignored. As such, it's "reasonably +stable", but at the same time also reasonably unique, i.e., two patches that +have the same "patch ID" are almost guaranteed to be the same thing. IOW, you can use this thing to look for likely duplicate commits. @@ -27,6 +27,33 @@ This can be used to make a mapping from patch ID to commit ID. OPTIONS ------- + +--stable:: + Use a "stable" sum of hashes as the patch ID. With this option: + - Reordering file diffs that make up a patch does not affect the ID. + In particular, two patches produced by comparing the same two trees + with two different settings for "-O<orderfile>" result in the same + patch ID signature, thereby allowing the computed result to be used + as a key to index some meta-information about the change between + the two trees; + + - Result is different from the value produced by git 1.9 and older + or produced when an "unstable" hash (see --unstable below) is + configured - even when used on a diff output taken without any use + of "-O<orderfile>", thereby making existing databases storing such + "unstable" or historical patch-ids unusable. + + This is the default if patchid.stable is set to true. + +--unstable:: + Use an "unstable" hash as the patch ID. With this option, + the result produced is compatible with the patch-id value produced + by git 1.9 and older. Users with pre-existing databases storing + patch-ids produced by git 1.9 and older (who do not deal with reordered + patches) may want to use this option. + + This is the default. + <patch>:: The diff to create the ID of. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 21cd4555..c0d7403b 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -33,7 +33,7 @@ When the command line does not specify what to push with `<refspec>...` arguments or `--all`, `--mirror`, `--tags` options, the command finds the default `<refspec>` by consulting `remote.*.push` configuration, and if it is not found, honors `push.default` configuration to decide -what to push (See gitlink:git-config[1] for the meaning of `push.default`). +what to push (See linkgit:git-config[1] for the meaning of `push.default`). OPTIONS[[OPTIONS]] diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 056c0dba..fa1d557e 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -283,7 +283,7 @@ merge. The different stages represent the "result tree" (stage 0, aka you are trying to merge (stage 2 and 3 respectively). The order of stages 1, 2 and 3 (hence the order of three -<tree-ish> command line arguments) are significant when you +<tree-ish> command-line arguments) are significant when you start a 3-way merge with an index file that is already populated. Here is an outline of how the algorithm works: diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index 0a02f706..8fff598f 100644 --- a/Documentation/git-replace.txt +++ b/Documentation/git-replace.txt @@ -9,6 +9,8 @@ SYNOPSIS -------- [verse] 'git replace' [-f] <object> <replacement> +'git replace' [-f] --edit <object> +'git replace' [-f] --graft <commit> [<parent>...] 'git replace' -d <object>... 'git replace' [--format=<format>] [-l [<pattern>]] @@ -63,6 +65,32 @@ OPTIONS --delete:: Delete existing replace refs for the given objects. +--edit <object>:: + Edit an object's content interactively. The existing content + for <object> is pretty-printed into a temporary file, an + editor is launched on the file, and the result is parsed to + create a new object of the same type as <object>. A + replacement ref is then created to replace <object> with the + newly created object. See linkgit:git-var[1] for details about + how the editor will be chosen. + +--raw:: + When editing, provide the raw object contents rather than + pretty-printed ones. Currently this only affects trees, which + will be shown in their binary form. This is harder to work with, + but can help when repairing a tree that is so corrupted it + cannot be pretty-printed. Note that you may need to configure + your editor to cleanly read and write binary data. + +--graft <commit> [<parent>...]:: + Create a graft commit. A new commit is created with the same + content as <commit> except that its parents will be + [<parent>...] instead of <commit>'s parents. A replacement ref + is then created to replace <commit> with the newly created + commit. See contrib/convert-grafts-to-replace-refs.sh for an + example script based on this option that can convert grafts to + replace refs. + -l <pattern>:: --list <pattern>:: List replace refs for objects that match the given pattern (or @@ -92,7 +120,9 @@ CREATING REPLACEMENT OBJECTS linkgit:git-filter-branch[1], linkgit:git-hash-object[1] and linkgit:git-rebase[1], among other git commands, can be used to create -replacement objects from existing objects. +replacement objects from existing objects. The `--edit` option can +also be used with 'git replace' to create a replacement object by +editing an existing object. If you want to replace many blobs, trees or commits that are part of a string of commits, you may just want to create a replacement string of @@ -117,6 +147,8 @@ linkgit:git-filter-branch[1] linkgit:git-rebase[1] linkgit:git-tag[1] linkgit:git-branch[1] +linkgit:git-commit[1] +linkgit:git-var[1] linkgit:git[1] GIT diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 54143a06..0b84769b 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -245,6 +245,10 @@ print a message to stderr and exit with nonzero status. --show-toplevel:: Show the absolute path of the top-level directory. +--shared-index-path:: + Show the path to the shared index file in split index mode, or + empty if not in split-index mode. + Other Options ~~~~~~~~~~~~~ diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index f0e57a59..a60776eb 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -20,7 +20,7 @@ files in the directory), or directly as a revision list. In the last case, any format accepted by linkgit:git-format-patch[1] can be passed to git send-email. -The header of the email is configurable by command line options. If not +The header of the email is configurable via command-line options. If not specified on the command line, the user will be prompted with a ReadLine enabled interface to provide the necessary information. @@ -68,7 +68,7 @@ The --cc option must be repeated for each user you want on the cc list. When '--compose' is used, git send-email will use the From, Subject, and In-Reply-To headers specified in the message. If the body of the message (what you type after the headers and a blank line) only contains blank -(or Git: prefixed) lines the summary won't be sent, but From, Subject, +(or Git: prefixed) lines, the summary won't be sent, but From, Subject, and In-Reply-To headers will be used unless they are removed. + Missing From or In-Reply-To headers will be prompted for. @@ -78,7 +78,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'. --from=<address>:: Specify the sender of the emails. If not specified on the command line, the value of the 'sendemail.from' configuration option is used. If - neither the command line option nor 'sendemail.from' are set, then the + neither the command-line option nor 'sendemail.from' are set, then the user will be prompted for the value. The default for the prompt will be the value of GIT_AUTHOR_IDENT, or GIT_COMMITTER_IDENT if that is not set, as returned by "git var -l". @@ -248,6 +248,18 @@ Automating cc list. Default is the value of 'sendemail.signedoffbycc' configuration value; if that is unspecified, default to --signed-off-by-cc. +--[no-]cc-cover:: + If this is set, emails found in Cc: headers in the first patch of + the series (typically the cover letter) are added to the cc list + for each email set. Default is the value of 'sendemail.cccover' + configuration value; if that is unspecified, default to --no-cc-cover. + +--[no-]to-cover:: + If this is set, emails found in To: headers in the first patch of + the series (typically the cover letter) are added to the to list + for each email set. Default is the value of 'sendemail.tocover' + configuration value; if that is unspecified, default to --no-to-cover. + --suppress-cc=<category>:: Specify an additional category of recipients to suppress the auto-cc of: diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index fce58538..44c970ce 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -148,8 +148,8 @@ the same local time zone. [verse] config key: svn-remote.<name>.ignore-paths + -If the ignore-paths config key is set and the command line option is -also given, both regular expressions will be used. +If the ignore-paths configuration key is set, and the command-line +option is also given, both regular expressions will be used. + Examples: + diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 303de8ee..32090836 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -42,7 +42,7 @@ committer identity for the current user is used to find the GnuPG key for signing. The configuration variable `gpg.program` is used to specify custom GnuPG binary. -Tag objects (created with `-a`, `-s`, or `-u`) are called "annotated" +Tag objects (created with `-a`, `s`, or `-u`) are called "annotated" tags; they contain a creation date, the tagger name and e-mail, a tagging message, and an optional GnuPG signature. Whereas a "lightweight" tag is simply a name for an object (usually a commit @@ -99,7 +99,9 @@ OPTIONS Sort in a specific order. Supported type is "refname" (lexicographic order), "version:refname" or "v:refname" (tag names are treated as versions). Prepend "-" to reverse sort - order. + order. When this option is not given, the sort order defaults to the + value configured for the 'tag.sort' variable if it exists, or + lexicographic order otherwise. See linkgit:git-config[1]. --column[=<options>]:: --no-column:: @@ -317,6 +319,7 @@ include::date-formats.txt[] SEE ALSO -------- linkgit:git-check-ref-format[1]. +linkgit:git-config[1]. GIT --- diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index d6de4a00..dfc09d93 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -161,6 +161,17 @@ may not support it yet. Only meaningful with `--stdin` or `--index-info`; paths are separated with NUL character instead of LF. +--split-index:: +--no-split-index:: + Enable or disable split index mode. If enabled, the index is + split into two files, $GIT_DIR/index and $GIT_DIR/sharedindex.<SHA-1>. + Changes are accumulated in $GIT_DIR/index while the shared + index file contains all index entries stays unchanged. If + split-index mode is already enabled and `--split-index` is + given again, all changes in $GIT_DIR/index are pushed back to + the shared index file. This mode is designed for very large + indexes that take a signficant amount of time to read or write. + \--:: Do not interpret any more arguments as options. diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index 0a0a5512..c8f5ae5c 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -68,7 +68,12 @@ performs all modifications together. Specify commands of the form: option SP <opt> LF Quote fields containing whitespace as if they were strings in C source -code. Alternatively, use `-z` to specify commands without quoting: +code; i.e., surrounded by double-quotes and with backslash escapes. +Use 40 "0" characters or the empty string to specify a zero value. To +specify a missing value, omit the value and its preceding SP entirely. + +Alternatively, use `-z` to specify in NUL-terminated format, without +quoting: update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL create SP <ref> NUL <newvalue> NUL @@ -76,8 +81,12 @@ code. Alternatively, use `-z` to specify commands without quoting: verify SP <ref> NUL [<oldvalue>] NUL option SP <opt> NUL -Lines of any other format or a repeated <ref> produce an error. -Command meanings are: +In this format, use 40 "0" to specify a zero value, and use the empty +string to specify a missing value. + +In either format, values can be specified in any form that Git +recognizes as an object name. Commands in any other format or a +repeated <ref> produce an error. Command meanings are: update:: Set <ref> to <newvalue> after verifying <oldvalue>, if given. @@ -102,9 +111,6 @@ option:: The only valid option is `no-deref` to avoid dereferencing a symbolic ref. -Use 40 "0" or the empty string to specify a zero value, except that -with `-z` an empty <oldvalue> is considered missing. - If all <ref>s can be locked with matching <oldvalue>s simultaneously, all modifications are performed. Otherwise, no modifications are performed. Note that while each individual diff --git a/Documentation/git-verify-commit.txt b/Documentation/git-verify-commit.txt new file mode 100644 index 00000000..9413e280 --- /dev/null +++ b/Documentation/git-verify-commit.txt @@ -0,0 +1,28 @@ +git-verify-commit(1) +==================== + +NAME +---- +git-verify-commit - Check the GPG signature of commits + +SYNOPSIS +-------- +[verse] +'git verify-commit' <commit>... + +DESCRIPTION +----------- +Validates the gpg signature created by 'git commit -S'. + +OPTIONS +------- +-v:: +--verbose:: + Print the contents of the commit object before validating it. + +<commit>...:: + SHA-1 identifiers of Git commit objects. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index 2de575f5..16ede5b4 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -62,7 +62,7 @@ CONF.VAR (from -c option) and web.browser ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The web browser can be specified using a configuration variable passed -with the -c (or --config) command line option, or the 'web.browser' +with the -c (or --config) command-line option, or the 'web.browser' configuration variable if the former is not used. browser.<tool>.path @@ -87,7 +87,7 @@ the URLs passed as arguments. Note about konqueror -------------------- -When 'konqueror' is specified by a command line option or a +When 'konqueror' is specified by a command-line option or a configuration variable, we launch 'kfmclient' to try to open the HTML man page on an already opened konqueror in a new tab if possible. diff --git a/Documentation/git.txt b/Documentation/git.txt index 674ecf9a..de7b870a 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -29,7 +29,7 @@ in-depth introduction. After you mastered the basic concepts, you can come back to this page to learn what commands Git offers. You can learn more about individual Git commands with "git help command". linkgit:gitcli[7] -manual page gives you an overview of the command line command syntax. +manual page gives you an overview of the command-line command syntax. Formatted and hyperlinked version of the latest Git documentation can be viewed at `http://git-htmldocs.googlecode.com/git/git.html`. @@ -39,34 +39,36 @@ ifdef::stalenotes[] ============ You are reading the documentation for the latest (possibly -unreleased) version of Git, that is available from 'master' +unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.0.5/git.html[documentation for release 2.0.5] +* link:v2.1.0/git.html[documentation for release 2.1] + +* release notes for + link:RelNotes/2.1.0.txt[2.1]. + +* link:v2.0.4/git.html[documentation for release 2.0.4] * release notes for - link:RelNotes/2.0.5.txt[2.0.5], link:RelNotes/2.0.4.txt[2.0.4], link:RelNotes/2.0.3.txt[2.0.3], link:RelNotes/2.0.2.txt[2.0.2], link:RelNotes/2.0.1.txt[2.0.1], link:RelNotes/2.0.0.txt[2.0.0]. -* link:v1.9.5/git.html[documentation for release 1.9.5] +* link:v1.9.4/git.html[documentation for release 1.9.4] * release notes for - link:RelNotes/1.9.5.txt[1.9.5], link:RelNotes/1.9.4.txt[1.9.4], link:RelNotes/1.9.3.txt[1.9.3], link:RelNotes/1.9.2.txt[1.9.2], link:RelNotes/1.9.1.txt[1.9.1], link:RelNotes/1.9.0.txt[1.9.0]. -* link:v1.8.5.6/git.html[documentation for release 1.8.5.6] +* link:v1.8.5.5/git.html[documentation for release 1.8.5.5] * release notes for - link:RelNotes/1.8.5.6.txt[1.8.5.6], link:RelNotes/1.8.5.5.txt[1.8.5.5], link:RelNotes/1.8.5.4.txt[1.8.5.4], link:RelNotes/1.8.5.3.txt[1.8.5.3], @@ -762,7 +764,7 @@ Git so take care if using Cogito etc. 'GIT_WORK_TREE':: Set the path to the root of the working tree. - This can also be controlled by the '--work-tree' command line + This can also be controlled by the '--work-tree' command-line option and the core.worktree configuration variable. 'GIT_NAMESPACE':: @@ -887,7 +889,7 @@ for further details. 'GIT_ASKPASS':: If this environment variable is set, then Git commands which need to acquire passwords or passphrases (e.g. for HTTP or IMAP authentication) - will call this program with a suitable prompt as command line argument + will call this program with a suitable prompt as command-line argument and read the password from its STDOUT. See also the 'core.askpass' option in linkgit:git-config[1]. @@ -911,31 +913,54 @@ for further details. based on whether stdout appears to be redirected to a file or not. 'GIT_TRACE':: - If this variable is set to "1", "2" or "true" (comparison - is case insensitive), Git will print `trace:` messages on - stderr telling about alias expansion, built-in command - execution and external command execution. - If this variable is set to an integer value greater than 1 - and lower than 10 (strictly) then Git will interpret this - value as an open file descriptor and will try to write the - trace messages into this file descriptor. - Alternatively, if this variable is set to an absolute path - (starting with a '/' character), Git will interpret this - as a file path and will try to write the trace messages - into it. + Enables general trace messages, e.g. alias expansion, built-in + command execution and external command execution. ++ +If this variable is set to "1", "2" or "true" (comparison +is case insensitive), trace messages will be printed to +stderr. ++ +If the variable is set to an integer value greater than 2 +and lower than 10 (strictly) then Git will interpret this +value as an open file descriptor and will try to write the +trace messages into this file descriptor. ++ +Alternatively, if the variable is set to an absolute path +(starting with a '/' character), Git will interpret this +as a file path and will try to write the trace messages +into it. ++ +Unsetting the variable, or setting it to empty, "0" or +"false" (case insensitive) disables trace messages. 'GIT_TRACE_PACK_ACCESS':: - If this variable is set to a path, a file will be created at - the given path logging all accesses to any packs. For each + Enables trace messages for all accesses to any packs. For each access, the pack file name and an offset in the pack is recorded. This may be helpful for troubleshooting some pack-related performance problems. + See 'GIT_TRACE' for available trace output options. 'GIT_TRACE_PACKET':: - If this variable is set, it shows a trace of all packets - coming in or out of a given program. This can help with - debugging object negotiation or other protocol issues. Tracing - is turned off at a packet starting with "PACK". + Enables trace messages for all packets coming in or out of a + given program. This can help with debugging object negotiation + or other protocol issues. Tracing is turned off at a packet + starting with "PACK". + See 'GIT_TRACE' for available trace output options. + +'GIT_TRACE_PERFORMANCE':: + Enables performance related trace messages, e.g. total execution + time of each Git command. + See 'GIT_TRACE' for available trace output options. + +'GIT_TRACE_SETUP':: + Enables trace messages printing the .git, working tree and current + working directory after Git has completed its setup phase. + See 'GIT_TRACE' for available trace output options. + +'GIT_TRACE_SHALLOW':: + Enables trace messages that can help debugging fetching / + cloning of shallow repositories. + See 'GIT_TRACE' for available trace output options. GIT_LITERAL_PATHSPECS:: Setting this variable to `1` will cause Git to treat all diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index 1c3e109c..dfe7d837 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -3,7 +3,7 @@ gitcli(7) NAME ---- -gitcli - Git command line interface and conventions +gitcli - Git command-line interface and conventions SYNOPSIS -------- @@ -66,13 +66,13 @@ you will. Here are the rules regarding the "flags" that you should follow when you are scripting Git: - * it's preferred to use the non dashed form of Git commands, which means that + * it's preferred to use the non-dashed form of Git commands, which means that you should prefer `git foo` to `git-foo`. * splitting short options to separate words (prefer `git foo -a -b` to `git foo -ab`, the latter may not even work). - * when a command line option takes an argument, use the 'stuck' form. In + * when a command-line option takes an argument, use the 'stuck' form. In other words, write `git foo -oArg` instead of `git foo -o Arg` for short options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg` for long options. An option that takes optional option-argument must be @@ -103,7 +103,7 @@ Here is a list of the facilities provided by this option parser. Magic Options ~~~~~~~~~~~~~ Commands which have the enhanced option parser activated all understand a -couple of magic command line options: +couple of magic command-line options: -h:: gives a pretty printed usage of the command. diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 7e03fcc6..7ae50aa2 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -27,7 +27,7 @@ gitk-specific options. gitk generally only understands options with arguments in the 'sticked' form (see linkgit:gitcli[7]) due to limitations in the -command line parser. +command-line parser. rev-list options and arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index 17d2ea6c..79653f31 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -155,6 +155,10 @@ index:: The current index file for the repository. It is usually not found in a bare repository. +sharedindex.<SHA-1>:: + The shared index part, to be referenced by $GIT_DIR/index and + other temporary index files. Only valid in split index mode. + info:: Additional information about the repository is recorded in this directory. diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt index 952f503a..ebe7a6c2 100644 --- a/Documentation/gitweb.conf.txt +++ b/Documentation/gitweb.conf.txt @@ -904,7 +904,7 @@ the following in your GITWEB_CONFIG file: $feature{'snapshot'}{'override'} = 1; If you allow overriding for the snapshot feature, you can specify which -snapshot formats are globally disabled. You can also add any command line +snapshot formats are globally disabled. You can also add any command-line options you want (such as setting the compression level). For instance, you can disable Zip compressed snapshots and set *gzip*(1) to run at level 6 by adding the following lines to your gitweb configuration file: diff --git a/Documentation/howto/keep-canonical-history-correct.txt b/Documentation/howto/keep-canonical-history-correct.txt new file mode 100644 index 00000000..35d48ef7 --- /dev/null +++ b/Documentation/howto/keep-canonical-history-correct.txt @@ -0,0 +1,216 @@ +From: Junio C Hamano <gitster@pobox.com> +Date: Wed, 07 May 2014 13:15:39 -0700 +Subject: Beginner question on "Pull is mostly evil" +Abstract: This how-to explains a method for keeping a + project's history correct when using git pull. +Content-type: text/asciidoc + +Keep authoritative canonical history correct with git pull +========================================================== + +Sometimes a new project integrator will end up with project history +that appears to be "backwards" from what other project developers +expect. This howto presents a suggested integration workflow for +maintaining a central repository. + +Suppose that that central repository has this history: + +------------ + ---o---o---A +------------ + +which ends at commit `A` (time flows from left to right and each node +in the graph is a commit, lines between them indicating parent-child +relationship). + +Then you clone it and work on your own commits, which leads you to +have this history in *your* repository: + +------------ + ---o---o---A---B---C +------------ + +Imagine your coworker did the same and built on top of `A` in *his* +repository in the meantime, and then pushed it to the +central repository: + +------------ + ---o---o---A---X---Y---Z +------------ + +Now, if you `git push` at this point, because your history that leads +to `C` lacks `X`, `Y` and `Z`, it will fail. You need to somehow make +the tip of your history a descendant of `Z`. + +One suggested way to solve the problem is "fetch and then merge", aka +`git pull`. When you fetch, your repository will have a history like +this: + +------------ + ---o---o---A---B---C + \ + X---Y---Z +------------ + +Once you run merge after that, while still on *your* branch, i.e. `C`, +you will create a merge `M` and make the history look like this: + +------------ + ---o---o---A---B---C---M + \ / + X---Y---Z +------------ + +`M` is a descendant of `Z`, so you can push to update the central +repository. Such a merge `M` does not lose any commit in both +histories, so in that sense it may not be wrong, but when people want +to talk about "the authoritative canonical history that is shared +among the project participants", i.e. "the trunk", they often view +it as "commits you see by following the first-parent chain", and use +this command to view it: + +------------ + $ git log --first-parent +------------ + +For all other people who observed the central repository after your +coworker pushed `Z` but before you pushed `M`, the commit on the trunk +used to be `o-o-A-X-Y-Z`. But because you made `M` while you were on +`C`, `M`'s first parent is `C`, so by pushing `M` to advance the +central repository, you made `X-Y-Z` a side branch, not on the trunk. + +You would rather want to have a history of this shape: + +------------ + ---o---o---A---X---Y---Z---M' + \ / + B-----------C +------------ + +so that in the first-parent chain, it is clear that the project first +did `X` and then `Y` and then `Z` and merged a change that consists of +two commits `B` and `C` that achieves a single goal. You may have +worked on fixing the bug #12345 with these two patches, and the merge +`M'` with swapped parents can say in its log message "Merge +fix-bug-12345". Having a way to tell `git pull` to create a merge +but record the parents in reverse order may be a way to do so. + +Note that I said "achieves a single goal" above, because this is +important. "Swapping the merge order" only covers a special case +where the project does not care too much about having unrelated +things done on a single merge but cares a lot about first-parent +chain. + +There are multiple schools of thought about the "trunk" management. + + 1. Some projects want to keep a completely linear history without any + merges. Obviously, swapping the merge order would not match their + taste. You would need to flatten your history on top of the + updated upstream to result in a history of this shape instead: ++ +------------ + ---o---o---A---X---Y---Z---B---C +------------ ++ +with `git pull --rebase` or something. + + 2. Some projects tolerate merges in their history, but do not worry + too much about the first-parent order, and allow fast-forward + merges. To them, swapping the merge order does not hurt, but + it is unnecessary. + + 3. Some projects want each commit on the "trunk" to do one single + thing. The output of `git log --first-parent` in such a project + would show either a merge of a side branch that completes a single + theme, or a single commit that completes a single theme by itself. + If your two commits `B` and `C` (or they may even be two groups of + commits) were solving two independent issues, then the merge `M'` + we made in the earlier example by swapping the merge order is + still not up to the project standard. It merges two unrelated + efforts `B` and `C` at the same time. + +For projects in the last category (Git itself is one of them), +individual developers would want to prepare a history more like +this: + +------------ + C0--C1--C2 topic-c + / + ---o---o---A master + \ + B0--B1--B2 topic-b +------------ + +That is, keeping separate topics on separate branches, perhaps like +so: + +------------ + $ git clone $URL work && cd work + $ git checkout -b topic-b master + $ ... work to create B0, B1 and B2 to complete one theme + $ git checkout -b topic-c master + $ ... same for the theme of topic-c +------------ + +And then + +------------ + $ git checkout master + $ git pull --ff-only +------------ + +would grab `X`, `Y` and `Z` from the upstream and advance your master +branch: + +------------ + C0--C1--C2 topic-c + / + ---o---o---A---X---Y---Z master + \ + B0--B1--B2 topic-b +------------ + +And then you would merge these two branches separately: + +------------ + $ git merge topic-b + $ git merge topic-c +------------ + +to result in + +------------ + C0--C1---------C2 + / \ + ---o---o---A---X---Y---Z---M---N + \ / + B0--B1-----B2 +------------ + +and push it back to the central repository. + +It is very much possible that while you are merging topic-b and +topic-c, somebody again advanced the history in the central repository +to put `W` on top of `Z`, and make your `git push` fail. + +In such a case, you would rewind to discard `M` and `N`, update the +tip of your 'master' again and redo the two merges: + +------------ + $ git reset --hard origin/master + $ git pull --ff-only + $ git merge topic-b + $ git merge topic-c +------------ + +The procedure will result in a history that looks like this: + +------------ + C0--C1--------------C2 + / \ + ---o---o---A---X---Y---Z---W---M'--N' + \ / + B0--B1---------B2 +------------ + +See also http://git-blame.blogspot.com/2013/09/fun-with-first-parent-history.html diff --git a/Documentation/howto/setup-git-server-over-http.txt b/Documentation/howto/setup-git-server-over-http.txt index 6de4f3c4..f44e5e94 100644 --- a/Documentation/howto/setup-git-server-over-http.txt +++ b/Documentation/howto/setup-git-server-over-http.txt @@ -181,7 +181,7 @@ On Debian: Most tests should pass. -A command line tool to test WebDAV is cadaver. If you prefer GUIs, for +A command-line tool to test WebDAV is cadaver. If you prefer GUIs, for example, konqueror can open WebDAV URLs as "webdav://..." or "webdavs://...". diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index 18cffc25..1ebbf1d7 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -12,9 +12,23 @@ ifndef::git-pull[] endif::git-pull[] <refspec>:: - The format of a <refspec> parameter is an optional plus - `+`, followed by the source ref <src>, followed - by a colon `:`, followed by the destination ref <dst>. + Specifies which refs to fetch and which local refs to update. + When no <refspec>s appear on the command line, the refs to fetch + are read from `remote.<repository>.fetch` variables instead +ifndef::git-pull[] + (see <<CRTB,CONFIGURED REMOTE-TRACKING BRANCHES>> below). +endif::git-pull[] +ifdef::git-pull[] + (see linkgit:git-fetch[1]). +endif::git-pull[] ++ +The format of a <refspec> parameter is an optional plus +`+`, followed by the source ref <src>, followed +by a colon `:`, followed by the destination ref <dst>. +The colon can be omitted when <dst> is empty. ++ +`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`; +it requests fetching everything up to the given tag. + The remote ref that matches <src> is fetched, and if <dst> is not empty string, the local @@ -24,55 +38,34 @@ is updated even if it does not result in a fast-forward update. + [NOTE] -If the remote branch from which you want to pull is -modified in non-linear ways such as being rewound and -rebased frequently, then a pull will attempt a merge with -an older version of itself, likely conflict, and fail. -It is under these conditions that you would want to use -the `+` sign to indicate non-fast-forward updates will -be needed. There is currently no easy way to determine -or declare that a branch will be made available in a -repository with this behavior; the pulling user simply +When the remote branch you want to fetch is known to +be rewound and rebased regularly, it is expected that +its new tip will not be descendant of its previous tip +(as stored in your remote-tracking branch the last time +you fetched). You would want +to use the `+` sign to indicate non-fast-forward updates +will be needed for such branches. There is no way to +determine or declare that a branch will be made available +in a repository with this behavior; the pulling user simply must know this is the expected usage pattern for a branch. -+ -[NOTE] -You never do your own development on branches that appear -on the right hand side of a <refspec> colon on `Pull:` lines; -they are to be updated by 'git fetch'. If you intend to do -development derived from a remote branch `B`, have a `Pull:` -line to track it (i.e. `Pull: B:remote-B`), and have a separate -branch `my-B` to do your development on top of it. The latter -is created by `git branch my-B remote-B` (or its equivalent `git -checkout -b my-B remote-B`). Run `git fetch` to keep track of -the progress of the remote side, and when you see something new -on the remote branch, merge it into your development branch with -`git pull . remote-B`, while you are on `my-B` branch. +ifdef::git-pull[] + [NOTE] There is a difference between listing multiple <refspec> directly on 'git pull' command line and having multiple -`Pull:` <refspec> lines for a <repository> and running +`remote.<repository>.fetch` entries in your configuration +for a <repository> and running a 'git pull' command without any explicit <refspec> parameters. -<refspec> listed explicitly on the command line are always +<refspec>s listed explicitly on the command line are always merged into the current branch after fetching. In other words, -if you list more than one remote refs, you would be making -an Octopus. While 'git pull' run without any explicit <refspec> -parameter takes default <refspec>s from `Pull:` lines, it -merges only the first <refspec> found into the current branch, -after fetching all the remote refs. This is because making an +if you list more than one remote ref, 'git pull' will create +an Octopus merge. On the other hand, if you do not list any +explicit <refspec> parameter on the command line, 'git pull' +will fetch all the <refspec>s it finds in the +`remote.<repository>.fetch` configuration and merge +only the first <refspec> found into the current branch. +This is because making an Octopus from remote refs is rarely done, while keeping track of multiple remote heads in one-go by fetching more than one is often useful. -+ -Some short-cut notations are also supported. -+ -* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`; - it requests fetching everything up to the given tag. -ifndef::git-pull[] -* A parameter <ref> without a colon fetches that ref into FETCH_HEAD, -endif::git-pull[] -ifdef::git-pull[] -* A parameter <ref> without a colon merges <ref> into the current - branch, endif::git-pull[] - and updates the remote-tracking branches (if any). diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index 5a286d0d..07961185 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -94,7 +94,9 @@ some output processing may assume ref names in UTF-8. '<branchname>@\{upstream\}', e.g. 'master@\{upstream\}', '@\{u\}':: The suffix '@\{upstream\}' to a branchname (short form '<branchname>@\{u\}') refers to the branch that the branch specified by branchname is set to build on - top of. A missing branchname defaults to the current one. + top of (configured with `branch.<name>.remote` and + `branch.<name>.merge`). A missing branchname defaults to the + current one. '<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0':: A suffix '{caret}' to a revision parameter means the first parent of diff --git a/Documentation/technical/api-argv-array.txt b/Documentation/technical/api-argv-array.txt index a6b7d83a..1a797812 100644 --- a/Documentation/technical/api-argv-array.txt +++ b/Documentation/technical/api-argv-array.txt @@ -53,11 +53,3 @@ Functions `argv_array_clear`:: Free all memory associated with the array and return it to the initial, empty state. - -`argv_array_detach`:: - Detach the argv array from the `struct argv_array`, transferring - ownership of the allocated array and strings. - -`argv_array_free_detached`:: - Free the memory allocated by a `struct argv_array` that was later - detached and is now no longer needed. diff --git a/Documentation/technical/api-builtin.txt b/Documentation/technical/api-builtin.txt index e3d6e7a7..22a39b92 100644 --- a/Documentation/technical/api-builtin.txt +++ b/Documentation/technical/api-builtin.txt @@ -22,11 +22,14 @@ Git: where options is the bitwise-or of: `RUN_SETUP`:: - - Make sure there is a Git directory to work on, and if there is a - work tree, chdir to the top of it if the command was invoked - in a subdirectory. If there is no work tree, no chdir() is - done. + If there is not a Git directory to work on, abort. If there + is a work tree, chdir to the top of it if the command was + invoked in a subdirectory. If there is no work tree, no + chdir() is done. + +`RUN_SETUP_GENTLY`:: + If there is a Git directory, chdir as per RUN_SETUP, otherwise, + don't chdir anywhere. `USE_PAGER`:: diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt index 230b3a0f..edd5018e 100644 --- a/Documentation/technical/api-config.txt +++ b/Documentation/technical/api-config.txt @@ -137,4 +137,33 @@ int read_file_with_include(const char *file, config_fn_t fn, void *data) Writing Config Files -------------------- -TODO +Git gives multiple entry points in the Config API to write config values to +files namely `git_config_set_in_file` and `git_config_set`, which write to +a specific config file or to `.git/config` respectively. They both take a +key/value pair as parameter. +In the end they both call `git_config_set_multivar_in_file` which takes four +parameters: + +- the name of the file, as a string, to which key/value pairs will be written. + +- the name of key, as a string. This is in canonical "flat" form: the section, + subsection, and variable segments will be separated by dots, and the section + and variable segments will be all lowercase. + E.g., `core.ignorecase`, `diff.SomeType.textconv`. + +- the value of the variable, as a string. If value is equal to NULL, it will + remove the matching key from the config file. + +- the value regex, as a string. It will disregard key/value pairs where value + does not match. + +- a multi_replace value, as an int. If value is equal to zero, nothing or only + one matching key/value is replaced, else all matching key/values (regardless + how many) are removed, before the new pair is written. + +It returns 0 on success. + +Also, there are functions `git_config_rename_section` and +`git_config_rename_section_in_file` with parameters `old_name` and `new_name` +for renaming or removing sections in the config files. If NULL is passed +through `new_name` parameter, the section will be removed from the config file. diff --git a/Documentation/technical/api-hashmap.txt b/Documentation/technical/api-hashmap.txt index b977ae8b..ad7a5bdd 100644 --- a/Documentation/technical/api-hashmap.txt +++ b/Documentation/technical/api-hashmap.txt @@ -8,11 +8,19 @@ Data Structures `struct hashmap`:: - The hash table structure. + The hash table structure. Members can be used as follows, but should + not be modified directly: + -The `size` member keeps track of the total number of entries. The `cmpfn` -member is a function used to compare two entries for equality. The `table` and -`tablesize` members store the hash table and its size, respectively. +The `size` member keeps track of the total number of entries (0 means the +hashmap is empty). ++ +`tablesize` is the allocated size of the hash table. A non-0 value indicates +that the hashmap is initialized. It may also be useful for statistical purposes +(i.e. `size / tablesize` is the current load factor). ++ +`cmpfn` stores the comparison function specified in `hashmap_init()`. In +advanced scenarios, it may be useful to change this, e.g. to switch between +case-sensitive and case-insensitive lookup. `struct hashmap_entry`:: @@ -58,6 +66,15 @@ Functions + `strihash` and `memihash` are case insensitive versions. +`unsigned int sha1hash(const unsigned char *sha1)`:: + + Converts a cryptographic hash (e.g. SHA-1) into an int-sized hash code + for use in hash tables. Cryptographic hashes are supposed to have + uniform distribution, so in contrast to `memhash()`, this just copies + the first `sizeof(int)` bytes without shuffling any bits. Note that + the results will be different on big-endian and little-endian + platforms, so they should not be stored or transferred over the net. + `void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, size_t initial_size)`:: Initializes a hashmap structure. @@ -101,6 +118,20 @@ hashmap_entry) that has at least been initialized with the proper hash code If an entry with matching hash code is found, `key` and `keydata` are passed to `hashmap_cmp_fn` to decide whether the entry matches the key. +`void *hashmap_get_from_hash(const struct hashmap *map, unsigned int hash, const void *keydata)`:: + + Returns the hashmap entry for the specified hash code and key data, + or NULL if not found. ++ +`map` is the hashmap structure. ++ +`hash` is the hash code of the entry to look up. ++ +If an entry with matching hash code is found, `keydata` is passed to +`hashmap_cmp_fn` to decide whether the entry matches the key. The +`entry_or_key` parameter points to a bogus hashmap_entry structure that +should not be used in the comparison. + `void *hashmap_get_next(const struct hashmap *map, const void *entry)`:: Returns the next equal hashmap entry, or NULL if not found. This can be @@ -162,6 +193,21 @@ more entries. `hashmap_iter_first` is a combination of both (i.e. initializes the iterator and returns the first entry, if any). +`const char *strintern(const char *string)`:: +`const void *memintern(const void *data, size_t len)`:: + + Returns the unique, interned version of the specified string or data, + similar to the `String.intern` API in Java and .NET, respectively. + Interned strings remain valid for the entire lifetime of the process. ++ +Can be used as `[x]strdup()` or `xmemdupz` replacement, except that interned +strings / data must not be modified or freed. ++ +Interned strings are best used for short strings with high probability of +duplicates. ++ +Uses a hashmap to store the pool of interned strings. + Usage example ------------- diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index 5d7d7f2d..69510ae5 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -109,6 +109,13 @@ terminated), of which .argv[0] is the program name to run (usually without a path). If the command to run is a git command, set argv[0] to the command name without the 'git-' prefix and set .git_cmd = 1. +Note that the ownership of the memory pointed to by .argv stays with the +caller, but it should survive until `finish_command` completes. If the +.argv member is NULL, `start_command` will point it at the .args +`argv_array` (so you may use one or the other, but you must use exactly +one). The memory in .args will be cleaned up automatically during +`finish_command` (or during `start_command` when it is unsuccessful). + The members .in, .out, .err are used to redirect stdin, stdout, stderr as follows: diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index 1d00e4d5..f9c06a75 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -121,10 +121,28 @@ Functions * Related to the contents of the buffer +`strbuf_trim`:: + + Strip whitespace from the beginning and end of a string. + Equivalent to performing `strbuf_rtrim()` followed by `strbuf_ltrim()`. + `strbuf_rtrim`:: Strip whitespace from the end of a string. +`strbuf_ltrim`:: + + Strip whitespace from the beginning of a string. + +`strbuf_reencode`:: + + Replace the contents of the strbuf with a reencoded form. Returns -1 + on error, 0 on success. + +`strbuf_tolower`:: + + Lowercase each character in the buffer using `tolower`. + `strbuf_cmp`:: Compare two buffers. Returns an integer less than, equal to, or greater diff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt index 20be3488..d51a6579 100644 --- a/Documentation/technical/api-string-list.txt +++ b/Documentation/technical/api-string-list.txt @@ -68,6 +68,11 @@ Functions * General ones (works with sorted and unsorted lists as well) +`string_list_init`:: + + Initialize the members of the string_list, set `strdup_strings` + member according to the value of the second parameter. + `filter_string_list`:: Apply a function to each item in a list, retaining only the @@ -200,3 +205,5 @@ Represents the list itself. You should not tamper with it. . Setting the `strdup_strings` member to 1 will strdup() the strings before adding them, see above. +. The `compare_strings_fn` member is used to specify a custom compare + function, otherwise `strcmp()` is used as the default function. diff --git a/Documentation/technical/api-trace.txt b/Documentation/technical/api-trace.txt new file mode 100644 index 00000000..097a651d --- /dev/null +++ b/Documentation/technical/api-trace.txt @@ -0,0 +1,97 @@ +trace API +========= + +The trace API can be used to print debug messages to stderr or a file. Trace +code is inactive unless explicitly enabled by setting `GIT_TRACE*` environment +variables. + +The trace implementation automatically adds `timestamp file:line ... \n` to +all trace messages. E.g.: + +------------ +23:59:59.123456 git.c:312 trace: built-in: git 'foo' +00:00:00.000001 builtin/foo.c:99 foo: some message +------------ + +Data Structures +--------------- + +`struct trace_key`:: + + Defines a trace key (or category). The default (for API functions that + don't take a key) is `GIT_TRACE`. ++ +E.g. to define a trace key controlled by environment variable `GIT_TRACE_FOO`: ++ +------------ +static struct trace_key trace_foo = TRACE_KEY_INIT(FOO); + +static void trace_print_foo(const char *message) +{ + trace_print_key(&trace_foo, message); +} +------------ ++ +Note: don't use `const` as the trace implementation stores internal state in +the `trace_key` structure. + +Functions +--------- + +`int trace_want(struct trace_key *key)`:: + + Checks whether the trace key is enabled. Used to prevent expensive + string formatting before calling one of the printing APIs. + +`void trace_disable(struct trace_key *key)`:: + + Disables tracing for the specified key, even if the environment + variable was set. + +`void trace_printf(const char *format, ...)`:: +`void trace_printf_key(struct trace_key *key, const char *format, ...)`:: + + Prints a formatted message, similar to printf. + +`void trace_argv_printf(const char **argv, const char *format, ...)``:: + + Prints a formatted message, followed by a quoted list of arguments. + +`void trace_strbuf(struct trace_key *key, const struct strbuf *data)`:: + + Prints the strbuf, without additional formatting (i.e. doesn't + choke on `%` or even `\0`). + +`uint64_t getnanotime(void)`:: + + Returns nanoseconds since the epoch (01/01/1970), typically used + for performance measurements. ++ +Currently there are high precision timer implementations for Linux (using +`clock_gettime(CLOCK_MONOTONIC)`) and Windows (`QueryPerformanceCounter`). +Other platforms use `gettimeofday` as time source. + +`void trace_performance(uint64_t nanos, const char *format, ...)`:: +`void trace_performance_since(uint64_t start, const char *format, ...)`:: + + Prints the elapsed time (in nanoseconds), or elapsed time since + `start`, followed by a formatted message. Enabled via environment + variable `GIT_TRACE_PERFORMANCE`. Used for manual profiling, e.g.: ++ +------------ +uint64_t start = getnanotime(); +/* code section to measure */ +trace_performance_since(start, "foobar"); +------------ ++ +------------ +uint64_t t = 0; +for (;;) { + /* ignore */ + t -= getnanotime(); + /* code section to measure */ + t += getnanotime(); + /* ignore */ +} +trace_performance(t, "frotz"); +------------ diff --git a/Documentation/technical/http-protocol.txt b/Documentation/technical/http-protocol.txt index 58c7e872..229f845d 100644 --- a/Documentation/technical/http-protocol.txt +++ b/Documentation/technical/http-protocol.txt @@ -374,7 +374,7 @@ C: Send one `$GIT_URL/git-upload-pack` request: C: 0000 The stream is organized into "commands", with each command -appearing by itself in a pkt-line. Within a command line +appearing by itself in a pkt-line. Within a command line, the text leading up to the first space is the command name, and the remainder of the line to the first LF is the value. Command lines are terminated with an LF as the last byte of diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt index f352a9b2..fe6f3166 100644 --- a/Documentation/technical/index-format.txt +++ b/Documentation/technical/index-format.txt @@ -129,6 +129,9 @@ Git index format (Version 4) In version 4, the padding after the pathname does not exist. + Interpretation of index entries in split index mode is completely + different. See below for details. + == Extensions === Cached tree @@ -198,3 +201,35 @@ Git index format - At most three 160-bit object names of the entry in stages from 1 to 3 (nothing is written for a missing stage). +=== Split index + + In split index mode, the majority of index entries could be stored + in a separate file. This extension records the changes to be made on + top of that to produce the final index. + + The signature for this extension is { 'l', 'i, 'n', 'k' }. + + The extension consists of: + + - 160-bit SHA-1 of the shared index file. The shared index file path + is $GIT_DIR/sharedindex.<SHA-1>. If all 160 bits are zero, the + index does not require a shared index file. + + - An ewah-encoded delete bitmap, each bit represents an entry in the + shared index. If a bit is set, its corresponding entry in the + shared index will be removed from the final index. Note, because + a delete operation changes index entry positions, but we do need + original positions in replace phase, it's best to just mark + entries for removal, then do a mass deletion after replacement. + + - An ewah-encoded replace bitmap, each bit represents an entry in + the shared index. If a bit is set, its corresponding entry in the + shared index will be replaced with an entry in this index + file. All replaced entries are stored in sorted order in this + index. The first "1" bit in the replace bitmap corresponds to the + first index entry, the second "1" bit to the second entry and so + on. Replaced entries may have empty path names to save space. + + The remaining index entries after replaced ones will be added to the + final index. These added entries are also sorted by entry namme then + stage. diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 46aa6bc1..7330d880 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -4230,9 +4230,9 @@ Most of what `git rev-list` did is contained in `revision.c` and controls how and what revisions are walked, and more. The original job of `git rev-parse` is now taken by the function -`setup_revisions()`, which parses the revisions and the common command line +`setup_revisions()`, which parses the revisions and the common command-line options for the revision walker. This information is stored in the struct -`rev_info` for later consumption. You can do your own command line option +`rev_info` for later consumption. You can do your own command-line option parsing after calling `setup_revisions()`. After that, you have to call `prepare_revision_walk()` for initialization, and then you can get the commits one by one with the function `get_revision()`. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 38c62140..a4cdfbf7 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.0.5 +DEF_VER=v2.1.0 LF=' ' @@ -28,7 +28,7 @@ set up install paths (via config.mak.autogen), so you can write instead If you're willing to trade off (much) longer build time for a later faster git you can also do a profile feedback build with - $ make prefix=/usr PROFILE=BUILD all + $ make prefix=/usr profile # make prefix=/usr PROFILE=BUILD install This will run the complete test suite as training workload and then @@ -36,10 +36,20 @@ rebuild git with the generated profile feedback. This results in a git which is a few percent faster on CPU intensive workloads. This may be a good tradeoff for distribution packagers. +Alternatively you can run profile feedback only with the git benchmark +suite. This runs significantly faster than the full test suite, but +has less coverage: + + $ make prefix=/usr profile-fast + # make prefix=/usr PROFILE=BUILD install + Or if you just want to install a profile-optimized version of git into your home directory, you could run: - $ make PROFILE=BUILD install + $ make profile-install + +or + $ make profile-fast-install As a caveat: a profile-optimized build takes a *lot* longer since the git tree must be built twice, and in order for the profiling @@ -30,6 +30,8 @@ all:: # Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in # /foo/bar/include and /foo/bar/lib directories. # +# Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header. +# # Define NO_CURL if you do not have libcurl installed. git-http-fetch and # git-http-push are not built, and you cannot use http:// and https:// # transports (neither smart nor dumb). @@ -338,6 +340,8 @@ all:: # # Define GMTIME_UNRELIABLE_ERRORS if your gmtime() function does not # return NULL when it receives a bogus time_t. +# +# Define HAVE_CLOCK_GETTIME if your platform has clock_gettime in librt. GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -550,6 +554,7 @@ TEST_PROGRAMS_NEED_X += test-ctype TEST_PROGRAMS_NEED_X += test-date TEST_PROGRAMS_NEED_X += test-delta TEST_PROGRAMS_NEED_X += test-dump-cache-tree +TEST_PROGRAMS_NEED_X += test-dump-split-index TEST_PROGRAMS_NEED_X += test-genrandom TEST_PROGRAMS_NEED_X += test-hashmap TEST_PROGRAMS_NEED_X += test-index-version @@ -727,6 +732,7 @@ LIB_H += transport.h LIB_H += tree-walk.h LIB_H += tree.h LIB_H += unpack-trees.h +LIB_H += unicode_width.h LIB_H += url.h LIB_H += urlmatch.h LIB_H += userdiff.h @@ -872,6 +878,7 @@ LIB_OBJS += sha1_name.o LIB_OBJS += shallow.o LIB_OBJS += sideband.o LIB_OBJS += sigchain.o +LIB_OBJS += split-index.o LIB_OBJS += strbuf.o LIB_OBJS += streaming.o LIB_OBJS += string-list.o @@ -996,6 +1003,7 @@ BUILTIN_OBJS += builtin/update-ref.o BUILTIN_OBJS += builtin/update-server-info.o BUILTIN_OBJS += builtin/upload-archive.o BUILTIN_OBJS += builtin/var.o +BUILTIN_OBJS += builtin/verify-commit.o BUILTIN_OBJS += builtin/verify-pack.o BUILTIN_OBJS += builtin/verify-tag.o BUILTIN_OBJS += builtin/write-tree.o @@ -1108,6 +1116,10 @@ ifdef USE_LIBPCRE EXTLIBS += -lpcre endif +ifdef HAVE_ALLOCA_H + BASIC_CFLAGS += -DHAVE_ALLOCA_H +endif + ifdef NO_CURL BASIC_CFLAGS += -DNO_CURL REMOTE_CURL_PRIMARY = @@ -1490,6 +1502,11 @@ ifdef GMTIME_UNRELIABLE_ERRORS BASIC_CFLAGS += -DGMTIME_UNRELIABLE_ERRORS endif +ifdef HAVE_CLOCK_GETTIME + BASIC_CFLAGS += -DHAVE_CLOCK_GETTIME + EXTLIBS += -lrt +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK = NoThanks endif @@ -1545,13 +1562,13 @@ endif PROFILE_DIR := $(CURDIR) ifeq ("$(PROFILE)","GEN") - CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1 + BASIC_CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1 EXTLIBS += -lgcov export CCACHE_DISABLE = t V = 1 else ifneq ("$(PROFILE)","") - CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1 + BASIC_CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1 export CCACHE_DISABLE = t V = 1 endif @@ -1636,12 +1653,20 @@ SHELL = $(SHELL_PATH) all:: shell_compatibility_test ifeq "$(PROFILE)" "BUILD" -ifeq ($(filter all,$(MAKECMDGOALS)),all) -all:: profile-clean +all:: profile +endif + +profile:: profile-clean $(MAKE) PROFILE=GEN all $(MAKE) PROFILE=GEN -j1 test -endif -endif + $(MAKE) PROFILE=GEN -j1 perf + $(MAKE) PROFILE=USE all + +profile-fast: profile-clean + $(MAKE) PROFILE=GEN all + $(MAKE) PROFILE=GEN -j1 perf + $(MAKE) PROFILE=USE all + all:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) @@ -2328,6 +2353,12 @@ mergetools_instdir_SQ = $(subst ','\'',$(mergetools_instdir)) install_bindir_programs := $(patsubst %,%$X,$(BINDIR_PROGRAMS_NEED_X)) $(BINDIR_PROGRAMS_NO_X) +profile-install: profile + $(MAKE) install + +profile-fast-install: profile-fast + $(MAKE) install + install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' @@ -1 +1 @@ -Documentation/RelNotes/2.0.5.txt
\ No newline at end of file +Documentation/RelNotes/2.1.0.txt
\ No newline at end of file @@ -61,9 +61,12 @@ void advise(const char *advice, ...) int git_default_advice_config(const char *var, const char *value) { - const char *k = skip_prefix(var, "advice."); + const char *k; int i; + if (!skip_prefix(var, "advice.", &k)) + return 0; + for (i = 0; i < ARRAY_SIZE(advice_config); i++) { if (strcmp(k, advice_config[i].name)) continue; @@ -76,16 +79,15 @@ int git_default_advice_config(const char *var, const char *value) int error_resolve_conflict(const char *me) { - error("'%s' is not possible because you have unmerged files.", me); + error("%s is not possible because you have unmerged files.", me); if (advice_resolve_conflict) /* * Message used both when 'git commit' fails and when * other commands doing a merge do. */ - advise(_("Fix them up in the work tree,\n" - "and then use 'git add/rm <file>' as\n" - "appropriate to mark resolution and make a commit,\n" - "or use 'git commit -a'.")); + advise(_("Fix them up in the work tree, and then use 'git add/rm <file>'\n" + "as appropriate to mark resolution and make a commit, or use\n" + "'git commit -a'.")); return -1; } @@ -5,7 +5,8 @@ static char *alias_val; static int alias_lookup_cb(const char *k, const char *v, void *cb) { - if (starts_with(k, "alias.") && !strcmp(k + 6, alias_key)) { + const char *name; + if (skip_prefix(k, "alias.", &name) && !strcmp(name, alias_key)) { if (!v) return config_error_nonbool(k); alias_val = xstrdup(v); diff --git a/argv-array.c b/argv-array.c index 9e960d54..256741d2 100644 --- a/argv-array.c +++ b/argv-array.c @@ -68,23 +68,3 @@ void argv_array_clear(struct argv_array *array) } argv_array_init(array); } - -const char **argv_array_detach(struct argv_array *array, int *argc) -{ - const char **argv = - array->argv == empty_argv || array->argc == 0 ? NULL : array->argv; - if (argc) - *argc = array->argc; - argv_array_init(array); - return argv; -} - -void argv_array_free_detached(const char **argv) -{ - if (argv) { - int i; - for (i = 0; argv[i]; i++) - free((char **)argv[i]); - free(argv); - } -} diff --git a/argv-array.h b/argv-array.h index 85ba438a..c65e6e82 100644 --- a/argv-array.h +++ b/argv-array.h @@ -19,7 +19,5 @@ LAST_ARG_MUST_BE_NULL void argv_array_pushl(struct argv_array *, ...); void argv_array_pop(struct argv_array *); void argv_array_clear(struct argv_array *); -const char **argv_array_detach(struct argv_array *array, int *argc); -void argv_array_free_detached(const char **argv); #endif /* ARGV_ARRAY_H */ @@ -50,11 +50,11 @@ static int should_setup_rebase(const char *origin) void install_branch_config(int flag, const char *local, const char *origin, const char *remote) { - const char *shortname = skip_prefix(remote, "refs/heads/"); + const char *shortname = NULL; struct strbuf key = STRBUF_INIT; int rebasing = should_setup_rebase(origin); - if (shortname + if (skip_prefix(remote, "refs/heads/", &shortname) && !strcmp(local, shortname) && !origin) { warning(_("Not setting branch %s as its own upstream."), @@ -128,6 +128,7 @@ extern int cmd_update_server_info(int argc, const char **argv, const char *prefi extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix); extern int cmd_var(int argc, const char **argv, const char *prefix); +extern int cmd_verify_commit(int argc, const char **argv, const char *prefix); extern int cmd_verify_tag(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index 459208a3..4baf3a56 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -299,7 +299,6 @@ static int add_files(struct dir_struct *dir, int flags) int cmd_add(int argc, const char **argv, const char *prefix) { int exit_status = 0; - int newfd; struct pathspec pathspec; struct dir_struct dir; int flags; @@ -345,7 +344,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; - newfd = hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, 1); flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | (show_only ? ADD_CACHE_PRETEND : 0) | @@ -443,8 +442,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) finish: if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } diff --git a/builtin/apply.c b/builtin/apply.c index 622ee167..be2b4ce2 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1075,7 +1075,7 @@ static int gitdiff_index(const char *line, struct patch *patch) line = ptr + 2; ptr = strchr(line, ' '); - eol = strchr(line, '\n'); + eol = strchrnul(line, '\n'); if (!ptr || eol < ptr) ptr = eol; @@ -1281,9 +1281,7 @@ static int parse_git_header(const char *line, int len, unsigned int size, struct */ patch->def_name = git_header_name(line, len); if (patch->def_name && root) { - char *s = xmalloc(root_len + strlen(patch->def_name) + 1); - strcpy(s, root); - strcpy(s + root_len, patch->def_name); + char *s = xstrfmt("%s%s", root, patch->def_name); free(patch->def_name); patch->def_name = s; } @@ -3084,13 +3082,15 @@ static void prepare_fn_table(struct patch *patch) } } -static int checkout_target(struct cache_entry *ce, struct stat *st) +static int checkout_target(struct index_state *istate, + struct cache_entry *ce, struct stat *st) { struct checkout costate; memset(&costate, 0, sizeof(costate)); costate.base_dir = ""; costate.refresh_cache = 1; + costate.istate = istate; if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st)) return error(_("cannot checkout %s"), ce->name); return 0; @@ -3257,7 +3257,7 @@ static int load_current(struct image *image, struct patch *patch) if (lstat(name, &st)) { if (errno != ENOENT) return error(_("%s: %s"), name, strerror(errno)); - if (checkout_target(ce, &st)) + if (checkout_target(&the_index, ce, &st)) return -1; } if (verify_index_match(ce, &st)) @@ -3411,7 +3411,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s } *ce = active_cache[pos]; if (stat_ret < 0) { - if (checkout_target(*ce, st)) + if (checkout_target(&the_index, *ce, st)) return -1; } if (!cached && verify_index_match(*ce, st)) @@ -3644,7 +3644,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename) { struct patch *patch; struct index_state result = { NULL }; - int fd; + static struct lock_file lock; /* Once we start supporting the reverse patch, it may be * worth showing the new sha1 prefix, but until then... @@ -3682,8 +3682,8 @@ static void build_fake_ancestor(struct patch *list, const char *filename) die ("Could not add %s to temporary index", name); } - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0 || write_index(&result, fd) || close(fd)) + hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); + if (write_locked_index(&result, &lock, COMMIT_LOCK)) die ("Could not write temporary index to %s", filename); discard_index(&result); @@ -3845,9 +3845,10 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned ce->ce_flags = create_ce_flags(0); ce->ce_namelen = namelen; if (S_ISGITLINK(mode)) { - const char *s = buf; + const char *s; - if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) + if (!skip_prefix(buf, "Subproject commit ", &s) || + get_sha1_hex(s, ce->sha1)) die(_("corrupt patch for submodule %s"), path); } else { if (!cached) { @@ -4501,8 +4502,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) } if (update_index) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } diff --git a/builtin/blame.c b/builtin/blame.c index eefd6bc2..17d30d00 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1,7 +1,8 @@ /* * Blame * - * Copyright (c) 2006, Junio C Hamano + * Copyright (c) 2006, 2014 by its authors + * See COPYING for licensing conditions */ #include "cache.h" @@ -18,7 +19,9 @@ #include "cache-tree.h" #include "string-list.h" #include "mailmap.h" +#include "mergesort.h" #include "parse-options.h" +#include "prio-queue.h" #include "utf8.h" #include "userdiff.h" #include "line-range.h" @@ -83,11 +86,42 @@ static unsigned blame_copy_score; */ struct origin { int refcnt; + /* Record preceding blame record for this blob */ struct origin *previous; + /* origins are put in a list linked via `next' hanging off the + * corresponding commit's util field in order to make finding + * them fast. The presence in this chain does not count + * towards the origin's reference count. It is tempting to + * let it count as long as the commit is pending examination, + * but even under circumstances where the commit will be + * present multiple times in the priority queue of unexamined + * commits, processing the first instance will not leave any + * work requiring the origin data for the second instance. An + * interspersed commit changing that would have to be + * preexisting with a different ancestry and with the same + * commit date in order to wedge itself between two instances + * of the same commit in the priority queue _and_ produce + * blame entries relevant for it. While we don't want to let + * us get tripped up by this case, it certainly does not seem + * worth optimizing for. + */ + struct origin *next; struct commit *commit; + /* `suspects' contains blame entries that may be attributed to + * this origin's commit or to parent commits. When a commit + * is being processed, all suspects will be moved, either by + * assigning them to an origin in a different commit, or by + * shipping them to the scoreboard's ent list because they + * cannot be attributed to a different commit. + */ + struct blame_entry *suspects; mmfile_t file; unsigned char blob_sha1[20]; unsigned mode; + /* guilty gets set when shipping any suspects to the final + * blame list instead of other commits + */ + char guilty; char path[FLEX_ARRAY]; }; @@ -176,10 +210,22 @@ static inline struct origin *origin_incref(struct origin *o) static void origin_decref(struct origin *o) { if (o && --o->refcnt <= 0) { + struct origin *p, *l = NULL; if (o->previous) origin_decref(o->previous); free(o->file.ptr); - free(o); + /* Should be present exactly once in commit chain */ + for (p = o->commit->util; p; l = p, p = p->next) { + if (p == o) { + if (l) + l->next = p->next; + else + o->commit->util = p->next; + free(o); + return; + } + } + die("internal error in blame::origin_decref"); } } @@ -193,8 +239,12 @@ static void drop_origin_blob(struct origin *o) /* * Each group of lines is described by a blame_entry; it can be split - * as we pass blame to the parents. They form a linked list in the - * scoreboard structure, sorted by the target line number. + * as we pass blame to the parents. They are arranged in linked lists + * kept as `suspects' of some unprocessed origin, or entered (when the + * blame origin has been finalized) into the scoreboard structure. + * While the scoreboard structure is only sorted at the end of + * processing (according to final image line number), the lists + * attached to an origin are sorted by the target line number. */ struct blame_entry { struct blame_entry *next; @@ -210,15 +260,6 @@ struct blame_entry { /* the commit that introduced this group into the final image */ struct origin *suspect; - /* true if the suspect is truly guilty; false while we have not - * checked if the group came from one of its parents. - */ - char guilty; - - /* true if the entry has been scanned for copies in the current parent - */ - char scanned; - /* the line number of the first line of this group in the * suspect's file; internally all line numbers are 0 based. */ @@ -231,11 +272,112 @@ struct blame_entry { }; /* + * Any merge of blames happens on lists of blames that arrived via + * different parents in a single suspect. In this case, we want to + * sort according to the suspect line numbers as opposed to the final + * image line numbers. The function body is somewhat longish because + * it avoids unnecessary writes. + */ + +static struct blame_entry *blame_merge(struct blame_entry *list1, + struct blame_entry *list2) +{ + struct blame_entry *p1 = list1, *p2 = list2, + **tail = &list1; + + if (!p1) + return p2; + if (!p2) + return p1; + + if (p1->s_lno <= p2->s_lno) { + do { + tail = &p1->next; + if ((p1 = *tail) == NULL) { + *tail = p2; + return list1; + } + } while (p1->s_lno <= p2->s_lno); + } + for (;;) { + *tail = p2; + do { + tail = &p2->next; + if ((p2 = *tail) == NULL) { + *tail = p1; + return list1; + } + } while (p1->s_lno > p2->s_lno); + *tail = p1; + do { + tail = &p1->next; + if ((p1 = *tail) == NULL) { + *tail = p2; + return list1; + } + } while (p1->s_lno <= p2->s_lno); + } +} + +static void *get_next_blame(const void *p) +{ + return ((struct blame_entry *)p)->next; +} + +static void set_next_blame(void *p1, void *p2) +{ + ((struct blame_entry *)p1)->next = p2; +} + +/* + * Final image line numbers are all different, so we don't need a + * three-way comparison here. + */ + +static int compare_blame_final(const void *p1, const void *p2) +{ + return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno + ? 1 : -1; +} + +static int compare_blame_suspect(const void *p1, const void *p2) +{ + const struct blame_entry *s1 = p1, *s2 = p2; + /* + * to allow for collating suspects, we sort according to the + * respective pointer value as the primary sorting criterion. + * The actual relation is pretty unimportant as long as it + * establishes a total order. Comparing as integers gives us + * that. + */ + if (s1->suspect != s2->suspect) + return (intptr_t)s1->suspect > (intptr_t)s2->suspect ? 1 : -1; + if (s1->s_lno == s2->s_lno) + return 0; + return s1->s_lno > s2->s_lno ? 1 : -1; +} + +static struct blame_entry *blame_sort(struct blame_entry *head, + int (*compare_fn)(const void *, const void *)) +{ + return llist_mergesort (head, get_next_blame, set_next_blame, compare_fn); +} + +static int compare_commits_by_reverse_commit_date(const void *a, + const void *b, + void *c) +{ + return -compare_commits_by_commit_date(a, b, c); +} + +/* * The current state of the blame assignment. */ struct scoreboard { /* the final commit (i.e. where we started digging from) */ struct commit *final; + /* Priority queue for commits with unassigned blame records */ + struct prio_queue commits; struct rev_info *revs; const char *path; @@ -268,7 +410,6 @@ static void coalesce(struct scoreboard *sb) for (ent = sb->ent; ent && (next = ent->next); ent = next) { if (ent->suspect == next->suspect && - ent->guilty == next->guilty && ent->s_lno + ent->num_lines == next->s_lno) { ent->num_lines += next->num_lines; ent->next = next->next; @@ -284,6 +425,30 @@ static void coalesce(struct scoreboard *sb) } /* + * Merge the given sorted list of blames into a preexisting origin. + * If there were no previous blames to that commit, it is entered into + * the commit priority queue of the score board. + */ + +static void queue_blames(struct scoreboard *sb, struct origin *porigin, + struct blame_entry *sorted) +{ + if (porigin->suspects) + porigin->suspects = blame_merge(porigin->suspects, sorted); + else { + struct origin *o; + for (o = porigin->commit->util; o; o = o->next) { + if (o->suspects) { + porigin->suspects = sorted; + return; + } + } + porigin->suspects = sorted; + prio_queue_put(&sb->commits, porigin->commit); + } +} + +/* * Given a commit and a path in it, create a new origin structure. * The callers that add blame to the scoreboard should use * get_origin() to obtain shared, refcounted copy instead of calling @@ -295,23 +460,32 @@ static struct origin *make_origin(struct commit *commit, const char *path) o = xcalloc(1, sizeof(*o) + strlen(path) + 1); o->commit = commit; o->refcnt = 1; + o->next = commit->util; + commit->util = o; strcpy(o->path, path); return o; } /* * Locate an existing origin or create a new one. + * This moves the origin to front position in the commit util list. */ static struct origin *get_origin(struct scoreboard *sb, struct commit *commit, const char *path) { - struct blame_entry *e; + struct origin *o, *l; - for (e = sb->ent; e; e = e->next) { - if (e->suspect->commit == commit && - !strcmp(e->suspect->path, path)) - return origin_incref(e->suspect); + for (o = commit->util, l = NULL; o; l = o, o = o->next) { + if (!strcmp(o->path, path)) { + /* bump to front */ + if (l) { + l->next = o->next; + o->next = commit->util; + commit->util = o; + } + return origin_incref(o); + } } return make_origin(commit, path); } @@ -350,41 +524,19 @@ static struct origin *find_origin(struct scoreboard *sb, struct commit *parent, struct origin *origin) { - struct origin *porigin = NULL; + struct origin *porigin; struct diff_options diff_opts; const char *paths[2]; - if (parent->util) { - /* - * Each commit object can cache one origin in that - * commit. This is a freestanding copy of origin and - * not refcounted. - */ - struct origin *cached = parent->util; - if (!strcmp(cached->path, origin->path)) { + /* First check any existing origins */ + for (porigin = parent->util; porigin; porigin = porigin->next) + if (!strcmp(porigin->path, origin->path)) { /* * The same path between origin and its parent * without renaming -- the most common case. */ - porigin = get_origin(sb, parent, cached->path); - - /* - * If the origin was newly created (i.e. get_origin - * would call make_origin if none is found in the - * scoreboard), it does not know the blob_sha1/mode, - * so copy it. Otherwise porigin was in the - * scoreboard and already knows blob_sha1/mode. - */ - if (porigin->refcnt == 1) { - hashcpy(porigin->blob_sha1, cached->blob_sha1); - porigin->mode = cached->mode; - } - return porigin; + return origin_incref (porigin); } - /* otherwise it was not very useful; free it */ - free(parent->util); - parent->util = NULL; - } /* See if the origin->path is different between parent * and origin first. Most of the time they are the @@ -450,19 +602,6 @@ static struct origin *find_origin(struct scoreboard *sb, } diff_flush(&diff_opts); free_pathspec(&diff_opts.pathspec); - if (porigin) { - /* - * Create a freestanding copy that is not part of - * the refcounted origin found in the scoreboard, and - * cache it in the commit. - */ - struct origin *cached; - - cached = make_origin(porigin->commit, porigin->path); - hashcpy(cached->blob_sha1, porigin->blob_sha1); - cached->mode = porigin->mode; - parent->util = cached; - } return porigin; } @@ -509,46 +648,31 @@ static struct origin *find_rename(struct scoreboard *sb, } /* - * Link in a new blame entry to the scoreboard. Entries that cover the - * same line range have been removed from the scoreboard previously. + * Append a new blame entry to a given output queue. */ -static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) +static void add_blame_entry(struct blame_entry ***queue, struct blame_entry *e) { - struct blame_entry *ent, *prev = NULL; - origin_incref(e->suspect); - for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next) - prev = ent; - - /* prev, if not NULL, is the last one that is below e */ - - if (prev) { - e->next = prev->next; - prev->next = e; - } - else { - e->next = sb->ent; - sb->ent = e; - } + e->next = **queue; + **queue = e; + *queue = &e->next; } /* * src typically is on-stack; we want to copy the information in it to - * a malloced blame_entry that is already on the linked list of the - * scoreboard. The origin of dst loses a refcnt while the origin of src - * gains one. + * a malloced blame_entry that gets added to the given queue. The + * origin of dst loses a refcnt. */ -static void dup_entry(struct blame_entry *dst, struct blame_entry *src) +static void dup_entry(struct blame_entry ***queue, + struct blame_entry *dst, struct blame_entry *src) { - struct blame_entry *n; - - n = dst->next; origin_incref(src->suspect); origin_decref(dst->suspect); memcpy(dst, src, sizeof(*src)); - dst->next = n; - dst->score = 0; + dst->next = **queue; + **queue = dst; + *queue = &dst->next; } static const char *nth_line(struct scoreboard *sb, long lno) @@ -620,10 +744,11 @@ static void split_overlap(struct blame_entry *split, /* * split_overlap() divided an existing blame e into up to three parts - * in split. Adjust the linked list of blames in the scoreboard to + * in split. Any assigned blame is moved to queue to * reflect the split. */ -static void split_blame(struct scoreboard *sb, +static void split_blame(struct blame_entry ***blamed, + struct blame_entry ***unblamed, struct blame_entry *split, struct blame_entry *e) { @@ -631,61 +756,39 @@ static void split_blame(struct scoreboard *sb, if (split[0].suspect && split[2].suspect) { /* The first part (reuse storage for the existing entry e) */ - dup_entry(e, &split[0]); + dup_entry(unblamed, e, &split[0]); /* The last part -- me */ new_entry = xmalloc(sizeof(*new_entry)); memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); + add_blame_entry(unblamed, new_entry); /* ... and the middle part -- parent */ new_entry = xmalloc(sizeof(*new_entry)); memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); + add_blame_entry(blamed, new_entry); } else if (!split[0].suspect && !split[2].suspect) /* * The parent covers the entire area; reuse storage for * e and replace it with the parent. */ - dup_entry(e, &split[1]); + dup_entry(blamed, e, &split[1]); else if (split[0].suspect) { /* me and then parent */ - dup_entry(e, &split[0]); + dup_entry(unblamed, e, &split[0]); new_entry = xmalloc(sizeof(*new_entry)); memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); + add_blame_entry(blamed, new_entry); } else { /* parent and then me */ - dup_entry(e, &split[1]); + dup_entry(blamed, e, &split[1]); new_entry = xmalloc(sizeof(*new_entry)); memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - - if (DEBUG) { /* sanity */ - struct blame_entry *ent; - int lno = sb->ent->lno, corrupt = 0; - - for (ent = sb->ent; ent; ent = ent->next) { - if (lno != ent->lno) - corrupt = 1; - if (ent->s_lno < 0) - corrupt = 1; - lno += ent->num_lines; - } - if (corrupt) { - lno = sb->ent->lno; - for (ent = sb->ent; ent; ent = ent->next) { - printf("L %8d l %8d n %8d\n", - lno, ent->lno, ent->num_lines); - lno = ent->lno + ent->num_lines; - } - die("oops"); - } + add_blame_entry(unblamed, new_entry); } } @@ -702,74 +805,146 @@ static void decref_split(struct blame_entry *split) } /* - * Helper for blame_chunk(). blame_entry e is known to overlap with - * the patch hunk; split it and pass blame to the parent. + * reverse_blame reverses the list given in head, appending tail. + * That allows us to build lists in reverse order, then reverse them + * afterwards. This can be faster than building the list in proper + * order right away. The reason is that building in proper order + * requires writing a link in the _previous_ element, while building + * in reverse order just requires placing the list head into the + * _current_ element. */ -static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - struct blame_entry split[3]; - split_overlap(split, e, tlno, plno, same, parent); - if (split[1].suspect) - split_blame(sb, split, e); - decref_split(split); -} - -/* - * Find the line number of the last line the target is suspected for. - */ -static int find_last_in_target(struct scoreboard *sb, struct origin *target) +static struct blame_entry *reverse_blame(struct blame_entry *head, + struct blame_entry *tail) { - struct blame_entry *e; - int last_in_target = -1; - - for (e = sb->ent; e; e = e->next) { - if (e->guilty || e->suspect != target) - continue; - if (last_in_target < e->s_lno + e->num_lines) - last_in_target = e->s_lno + e->num_lines; + while (head) { + struct blame_entry *next = head->next; + head->next = tail; + tail = head; + head = next; } - return last_in_target; + return tail; } /* * Process one hunk from the patch between the current suspect for - * blame_entry e and its parent. Find and split the overlap, and - * pass blame to the overlapping part to the parent. + * blame_entry e and its parent. This first blames any unfinished + * entries before the chunk (which is where target and parent start + * differing) on the parent, and then splits blame entries at the + * start and at the end of the difference region. Since use of -M and + * -C options may lead to overlapping/duplicate source line number + * ranges, all we can rely on from sorting/merging is the order of the + * first suspect line number. */ -static void blame_chunk(struct scoreboard *sb, - int tlno, int plno, int same, - struct origin *target, struct origin *parent) +static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq, + int tlno, int offset, int same, + struct origin *parent) { - struct blame_entry *e; + struct blame_entry *e = **srcq; + struct blame_entry *samep = NULL, *diffp = NULL; - for (e = sb->ent; e; e = e->next) { - if (e->guilty || e->suspect != target) - continue; - if (same <= e->s_lno) - continue; - if (tlno < e->s_lno + e->num_lines) - blame_overlap(sb, e, tlno, plno, same, parent); + while (e && e->s_lno < tlno) { + struct blame_entry *next = e->next; + /* + * current record starts before differing portion. If + * it reaches into it, we need to split it up and + * examine the second part separately. + */ + if (e->s_lno + e->num_lines > tlno) { + /* Move second half to a new record */ + int len = tlno - e->s_lno; + struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry)); + n->suspect = e->suspect; + n->lno = e->lno + len; + n->s_lno = e->s_lno + len; + n->num_lines = e->num_lines - len; + e->num_lines = len; + e->score = 0; + /* Push new record to diffp */ + n->next = diffp; + diffp = n; + } else + origin_decref(e->suspect); + /* Pass blame for everything before the differing + * chunk to the parent */ + e->suspect = origin_incref(parent); + e->s_lno += offset; + e->next = samep; + samep = e; + e = next; + } + /* + * As we don't know how much of a common stretch after this + * diff will occur, the currently blamed parts are all that we + * can assign to the parent for now. + */ + + if (samep) { + **dstq = reverse_blame(samep, **dstq); + *dstq = &samep->next; } + /* + * Prepend the split off portions: everything after e starts + * after the blameable portion. + */ + e = reverse_blame(diffp, e); + + /* + * Now retain records on the target while parts are different + * from the parent. + */ + samep = NULL; + diffp = NULL; + while (e && e->s_lno < same) { + struct blame_entry *next = e->next; + + /* + * If current record extends into sameness, need to split. + */ + if (e->s_lno + e->num_lines > same) { + /* + * Move second half to a new record to be + * processed by later chunks + */ + int len = same - e->s_lno; + struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry)); + n->suspect = origin_incref(e->suspect); + n->lno = e->lno + len; + n->s_lno = e->s_lno + len; + n->num_lines = e->num_lines - len; + e->num_lines = len; + e->score = 0; + /* Push new record to samep */ + n->next = samep; + samep = n; + } + e->next = diffp; + diffp = e; + e = next; + } + **srcq = reverse_blame(diffp, reverse_blame(samep, e)); + /* Move across elements that are in the unblamable portion */ + if (diffp) + *srcq = &diffp->next; } struct blame_chunk_cb_data { - struct scoreboard *sb; - struct origin *target; struct origin *parent; - long plno; - long tlno; + long offset; + struct blame_entry **dstq; + struct blame_entry **srcq; }; +/* diff chunks are from parent to target */ static int blame_chunk_cb(long start_a, long count_a, long start_b, long count_b, void *data) { struct blame_chunk_cb_data *d = data; - blame_chunk(d->sb, d->tlno, d->plno, start_b, d->target, d->parent); - d->plno = start_a + count_a; - d->tlno = start_b + count_b; + if (start_a - start_b != d->offset) + die("internal error in blame::blame_chunk_cb"); + blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b, + start_b + count_b, d->parent); + d->offset = start_a + count_a - (start_b + count_b); return 0; } @@ -778,29 +953,32 @@ static int blame_chunk_cb(long start_a, long count_a, * for the lines it is suspected to its parent. Run diff to find * which lines came from parent and pass blame for them. */ -static int pass_blame_to_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) +static void pass_blame_to_parent(struct scoreboard *sb, + struct origin *target, + struct origin *parent) { - int last_in_target; mmfile_t file_p, file_o; struct blame_chunk_cb_data d; + struct blame_entry *newdest = NULL; - memset(&d, 0, sizeof(d)); - d.sb = sb; d.target = target; d.parent = parent; - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ + if (!target->suspects) + return; /* nothing remains for this target */ + + d.parent = parent; + d.offset = 0; + d.dstq = &newdest; d.srcq = &target->suspects; fill_origin_blob(&sb->revs->diffopt, parent, &file_p); fill_origin_blob(&sb->revs->diffopt, target, &file_o); num_get_patch++; diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d); - /* The rest (i.e. anything after tlno) are the same as the parent */ - blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent); + /* The rest are the same as the parent */ + blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent); + *d.dstq = NULL; + queue_blames(sb, parent, newdest); - return 0; + return; } /* @@ -945,43 +1123,80 @@ static void find_copy_in_blob(struct scoreboard *sb, handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); } +/* Move all blame entries from list *source that have a score smaller + * than score_min to the front of list *small. + * Returns a pointer to the link pointing to the old head of the small list. + */ + +static struct blame_entry **filter_small(struct scoreboard *sb, + struct blame_entry **small, + struct blame_entry **source, + unsigned score_min) +{ + struct blame_entry *p = *source; + struct blame_entry *oldsmall = *small; + while (p) { + if (ent_score(sb, p) <= score_min) { + *small = p; + small = &p->next; + p = *small; + } else { + *source = p; + source = &p->next; + p = *source; + } + } + *small = oldsmall; + *source = NULL; + return small; +} + /* * See if lines currently target is suspected for can be attributed to * parent. */ -static int find_move_in_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) +static void find_move_in_parent(struct scoreboard *sb, + struct blame_entry ***blamed, + struct blame_entry **toosmall, + struct origin *target, + struct origin *parent) { - int last_in_target, made_progress; struct blame_entry *e, split[3]; + struct blame_entry *unblamed = target->suspects; + struct blame_entry *leftover = NULL; mmfile_t file_p; - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ + if (!unblamed) + return; /* nothing remains for this target */ fill_origin_blob(&sb->revs->diffopt, parent, &file_p); if (!file_p.ptr) - return 0; + return; - made_progress = 1; - while (made_progress) { - made_progress = 0; - for (e = sb->ent; e; e = e->next) { - if (e->guilty || e->suspect != target || - ent_score(sb, e) < blame_move_score) - continue; + /* At each iteration, unblamed has a NULL-terminated list of + * entries that have not yet been tested for blame. leftover + * contains the reversed list of entries that have been tested + * without being assignable to the parent. + */ + do { + struct blame_entry **unblamedtail = &unblamed; + struct blame_entry *next; + for (e = unblamed; e; e = next) { + next = e->next; find_copy_in_blob(sb, e, parent, split, &file_p); if (split[1].suspect && blame_move_score < ent_score(sb, &split[1])) { - split_blame(sb, split, e); - made_progress = 1; + split_blame(blamed, &unblamedtail, split, e); + } else { + e->next = leftover; + leftover = e; } decref_split(split); } - } - return 0; + *unblamedtail = NULL; + toosmall = filter_small(sb, toosmall, &unblamed, blame_move_score); + } while (unblamed); + target->suspects = reverse_blame(leftover, NULL); } struct blame_list { @@ -993,62 +1208,46 @@ struct blame_list { * Count the number of entries the target is suspected for, * and prepare a list of entry and the best split. */ -static struct blame_list *setup_blame_list(struct scoreboard *sb, - struct origin *target, - int min_score, +static struct blame_list *setup_blame_list(struct blame_entry *unblamed, int *num_ents_p) { struct blame_entry *e; int num_ents, i; struct blame_list *blame_list = NULL; - for (e = sb->ent, num_ents = 0; e; e = e->next) - if (!e->scanned && !e->guilty && - e->suspect == target && - min_score < ent_score(sb, e)) - num_ents++; + for (e = unblamed, num_ents = 0; e; e = e->next) + num_ents++; if (num_ents) { blame_list = xcalloc(num_ents, sizeof(struct blame_list)); - for (e = sb->ent, i = 0; e; e = e->next) - if (!e->scanned && !e->guilty && - e->suspect == target && - min_score < ent_score(sb, e)) - blame_list[i++].ent = e; + for (e = unblamed, i = 0; e; e = e->next) + blame_list[i++].ent = e; } *num_ents_p = num_ents; return blame_list; } /* - * Reset the scanned status on all entries. - */ -static void reset_scanned_flag(struct scoreboard *sb) -{ - struct blame_entry *e; - for (e = sb->ent; e; e = e->next) - e->scanned = 0; -} - -/* * For lines target is suspected for, see if we can find code movement * across file boundary from the parent commit. porigin is the path * in the parent we already tried. */ -static int find_copy_in_parent(struct scoreboard *sb, - struct origin *target, - struct commit *parent, - struct origin *porigin, - int opt) +static void find_copy_in_parent(struct scoreboard *sb, + struct blame_entry ***blamed, + struct blame_entry **toosmall, + struct origin *target, + struct commit *parent, + struct origin *porigin, + int opt) { struct diff_options diff_opts; int i, j; - int retval; struct blame_list *blame_list; int num_ents; + struct blame_entry *unblamed = target->suspects; + struct blame_entry *leftover = NULL; - blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents); - if (!blame_list) - return 1; /* nothing remains for this target */ + if (!unblamed) + return; /* nothing remains for this target */ diff_setup(&diff_opts); DIFF_OPT_SET(&diff_opts, RECURSIVE); @@ -1078,9 +1277,9 @@ static int find_copy_in_parent(struct scoreboard *sb, if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) diffcore_std(&diff_opts); - retval = 0; - while (1) { - int made_progress = 0; + do { + struct blame_entry **unblamedtail = &unblamed; + blame_list = setup_blame_list(unblamed, &num_ents); for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; @@ -1117,27 +1316,21 @@ static int find_copy_in_parent(struct scoreboard *sb, struct blame_entry *split = blame_list[j].split; if (split[1].suspect && blame_copy_score < ent_score(sb, &split[1])) { - split_blame(sb, split, blame_list[j].ent); - made_progress = 1; + split_blame(blamed, &unblamedtail, split, + blame_list[j].ent); + } else { + blame_list[j].ent->next = leftover; + leftover = blame_list[j].ent; } - else - blame_list[j].ent->scanned = 1; decref_split(split); } free(blame_list); - - if (!made_progress) - break; - blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents); - if (!blame_list) { - retval = 1; - break; - } - } - reset_scanned_flag(sb); + *unblamedtail = NULL; + toosmall = filter_small(sb, toosmall, &unblamed, blame_copy_score); + } while (unblamed); + target->suspects = reverse_blame(leftover, NULL); diff_flush(&diff_opts); free_pathspec(&diff_opts.pathspec); - return retval; } /* @@ -1147,20 +1340,21 @@ static int find_copy_in_parent(struct scoreboard *sb, static void pass_whole_blame(struct scoreboard *sb, struct origin *origin, struct origin *porigin) { - struct blame_entry *e; + struct blame_entry *e, *suspects; if (!porigin->file.ptr && origin->file.ptr) { /* Steal its file */ porigin->file = origin->file; origin->file.ptr = NULL; } - for (e = sb->ent; e; e = e->next) { - if (e->suspect != origin) - continue; + suspects = origin->suspects; + origin->suspects = NULL; + for (e = suspects; e; e = e->next) { origin_incref(porigin); origin_decref(e->suspect); e->suspect = porigin; } + queue_blames(sb, porigin, suspects); } /* @@ -1177,11 +1371,29 @@ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit static int num_scapegoats(struct rev_info *revs, struct commit *commit) { - int cnt; struct commit_list *l = first_scapegoat(revs, commit); - for (cnt = 0; l; l = l->next) - cnt++; - return cnt; + return commit_list_count(l); +} + +/* Distribute collected unsorted blames to the respected sorted lists + * in the various origins. + */ +static void distribute_blame(struct scoreboard *sb, struct blame_entry *blamed) +{ + blamed = blame_sort(blamed, compare_blame_suspect); + while (blamed) + { + struct origin *porigin = blamed->suspect; + struct blame_entry *suspects = NULL; + do { + struct blame_entry *next = blamed->next; + blamed->next = suspects; + suspects = blamed; + blamed = next; + } while (blamed && blamed->suspect == porigin); + suspects = reverse_blame(suspects, NULL); + queue_blames(sb, porigin, suspects); + } } #define MAXSG 16 @@ -1194,6 +1406,8 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) struct commit_list *sg; struct origin *sg_buf[MAXSG]; struct origin *porigin, **sg_origin = sg_buf; + struct blame_entry *toosmall = NULL; + struct blame_entry *blames, **blametail = &blames; num_sg = num_scapegoats(revs, commit); if (!num_sg) @@ -1255,38 +1469,71 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) origin_incref(porigin); origin->previous = porigin; } - if (pass_blame_to_parent(sb, origin, porigin)) + pass_blame_to_parent(sb, origin, porigin); + if (!origin->suspects) goto finish; } /* * Optionally find moves in parents' files. */ - if (opt & PICKAXE_BLAME_MOVE) - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (!porigin) - continue; - if (find_move_in_parent(sb, origin, porigin)) - goto finish; + if (opt & PICKAXE_BLAME_MOVE) { + filter_small(sb, &toosmall, &origin->suspects, blame_move_score); + if (origin->suspects) { + for (i = 0, sg = first_scapegoat(revs, commit); + i < num_sg && sg; + sg = sg->next, i++) { + struct origin *porigin = sg_origin[i]; + if (!porigin) + continue; + find_move_in_parent(sb, &blametail, &toosmall, origin, porigin); + if (!origin->suspects) + break; + } } + } /* * Optionally find copies from parents' files. */ - if (opt & PICKAXE_BLAME_COPY) + if (opt & PICKAXE_BLAME_COPY) { + if (blame_copy_score > blame_move_score) + filter_small(sb, &toosmall, &origin->suspects, blame_copy_score); + else if (blame_copy_score < blame_move_score) { + origin->suspects = blame_merge(origin->suspects, toosmall); + toosmall = NULL; + filter_small(sb, &toosmall, &origin->suspects, blame_copy_score); + } + if (!origin->suspects) + goto finish; + for (i = 0, sg = first_scapegoat(revs, commit); i < num_sg && sg; sg = sg->next, i++) { struct origin *porigin = sg_origin[i]; - if (find_copy_in_parent(sb, origin, sg->item, - porigin, opt)) + find_copy_in_parent(sb, &blametail, &toosmall, + origin, sg->item, porigin, opt); + if (!origin->suspects) goto finish; } + } - finish: +finish: + *blametail = NULL; + distribute_blame(sb, blames); + /* + * prepend toosmall to origin->suspects + * + * There is no point in sorting: this ends up on a big + * unsorted list in the caller anyway. + */ + if (toosmall) { + struct blame_entry **tail = &toosmall; + while (*tail) + tail = &(*tail)->next; + *tail = origin->suspects; + origin->suspects = toosmall; + } for (i = 0; i < num_sg; i++) { if (sg_origin[i]) { drop_origin_blob(sg_origin[i]); @@ -1481,14 +1728,11 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) } /* - * The blame_entry is found to be guilty for the range. Mark it - * as such, and show it in incremental output. + * The blame_entry is found to be guilty for the range. + * Show it in incremental output. */ static void found_guilty_entry(struct blame_entry *ent) { - if (ent->guilty) - return; - ent->guilty = 1; if (incremental) { struct origin *suspect = ent->suspect; @@ -1502,32 +1746,34 @@ static void found_guilty_entry(struct blame_entry *ent) } /* - * The main loop -- while the scoreboard has lines whose true origin - * is still unknown, pick one blame_entry, and allow its current - * suspect to pass blames to its parents. - */ + * The main loop -- while we have blobs with lines whose true origin + * is still unknown, pick one blob, and allow its lines to pass blames + * to its parents. */ static void assign_blame(struct scoreboard *sb, int opt) { struct rev_info *revs = sb->revs; + struct commit *commit = prio_queue_get(&sb->commits); - while (1) { + while (commit) { struct blame_entry *ent; - struct commit *commit; - struct origin *suspect = NULL; + struct origin *suspect = commit->util; /* find one suspect to break down */ - for (ent = sb->ent; !suspect && ent; ent = ent->next) - if (!ent->guilty) - suspect = ent->suspect; - if (!suspect) - return; /* all done */ + while (suspect && !suspect->suspects) + suspect = suspect->next; + + if (!suspect) { + commit = prio_queue_get(&sb->commits); + continue; + } + + assert(commit == suspect->commit); /* * We will use this suspect later in the loop, * so hold onto it in the meantime. */ origin_incref(suspect); - commit = suspect->commit; parse_commit(commit); if (reverse || (!(commit->object.flags & UNINTERESTING) && @@ -1543,9 +1789,22 @@ static void assign_blame(struct scoreboard *sb, int opt) commit->object.flags |= UNINTERESTING; /* Take responsibility for the remaining entries */ - for (ent = sb->ent; ent; ent = ent->next) - if (ent->suspect == suspect) + ent = suspect->suspects; + if (ent) { + suspect->guilty = 1; + for (;;) { + struct blame_entry *next = ent->next; found_guilty_entry(ent); + if (next) { + ent = next; + continue; + } + ent->next = sb->ent; + sb->ent = suspect->suspects; + suspect->suspects = NULL; + break; + } + } origin_decref(suspect); if (DEBUG) /* sanity */ @@ -1609,9 +1868,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, char hex[41]; strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); - printf("%s%c%d %d %d\n", + printf("%s %d %d %d\n", hex, - ent->guilty ? ' ' : '*', /* purely for debugging */ ent->s_lno + 1, ent->lno + 1, ent->num_lines); @@ -1724,17 +1982,16 @@ static void output(struct scoreboard *sb, int option) if (option & OUTPUT_PORCELAIN) { for (ent = sb->ent; ent; ent = ent->next) { - struct blame_entry *oth; - struct origin *suspect = ent->suspect; - struct commit *commit = suspect->commit; + int count = 0; + struct origin *suspect; + struct commit *commit = ent->suspect->commit; if (commit->object.flags & MORE_THAN_ONE_PATH) continue; - for (oth = ent->next; oth; oth = oth->next) { - if ((oth->suspect->commit != commit) || - !strcmp(oth->suspect->path, suspect->path)) - continue; - commit->object.flags |= MORE_THAN_ONE_PATH; - break; + for (suspect = commit->util; suspect; suspect = suspect->next) { + if (suspect->guilty && count++) { + commit->object.flags |= MORE_THAN_ONE_PATH; + break; + } } } } @@ -1748,6 +2005,12 @@ static void output(struct scoreboard *sb, int option) } } +static const char *get_next_line(const char *start, const char *end) +{ + const char *nl = memchr(start, '\n', end - start); + return nl ? nl + 1 : end; +} + /* * To allow quick access to the contents of nth line in the * final image, prepare an index in the scoreboard. @@ -1759,39 +2022,19 @@ static int prepare_lines(struct scoreboard *sb) const char *end = buf + len; const char *p; int *lineno; - int num = 0, incomplete = 0; - - for (p = buf;;) { - p = memchr(p, '\n', end - p); - if (p) { - p++; - num++; - continue; - } - break; - } + int num = 0; - if (len && end[-1] != '\n') - incomplete++; /* incomplete line at the end */ + for (p = buf; p < end; p = get_next_line(p, end)) + num++; - sb->lineno = xmalloc(sizeof(*sb->lineno) * (num + incomplete + 1)); - lineno = sb->lineno; + sb->lineno = lineno = xmalloc(sizeof(*sb->lineno) * (num + 1)); - *lineno++ = 0; - for (p = buf;;) { - p = memchr(p, '\n', end - p); - if (p) { - p++; - *lineno++ = p - buf; - continue; - } - break; - } + for (p = buf; p < end; p = get_next_line(p, end)) + *lineno++ = p - buf; - if (incomplete) - *lineno++ = len; + *lineno = len; - sb->num_lines = num + incomplete; + sb->num_lines = num; return sb->num_lines; } @@ -2109,7 +2352,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, origin->file.ptr = buf.buf; origin->file.size = buf.len; pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); - commit->util = origin; /* * Read the current index, replace the path entry with @@ -2143,7 +2385,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, * right now, but someday we might optimize diff-index --cached * with cache-tree information. */ - cache_tree_invalidate_path(active_cache_tree, path); + cache_tree_invalidate_path(&the_index, path); return commit; } @@ -2427,12 +2669,16 @@ parse_done: memset(&sb, 0, sizeof(sb)); sb.revs = &revs; - if (!reverse) + if (!reverse) { final_commit_name = prepare_final(&sb); + sb.commits.compare = compare_commits_by_commit_date; + } else if (contents_from) die("--contents and --children do not blend well."); - else + else { final_commit_name = prepare_initial(&sb); + sb.commits.compare = compare_commits_by_reverse_commit_date; + } if (!sb.final) { /* @@ -2518,12 +2764,16 @@ parse_done: ent->next = next; origin_incref(o); } + + o->suspects = ent; + prio_queue_put(&sb.commits, o->commit); + origin_decref(o); range_set_release(&ranges); string_list_clear(&range_list, 0); - sb.ent = ent; + sb.ent = NULL; sb.path = path; read_mailmap(&mailmap, NULL); @@ -2536,6 +2786,8 @@ parse_done: if (incremental) return 0; + sb.ent = blame_sort(sb.ent, compare_blame_final); + coalesce(&sb); if (!(output_option & OUTPUT_PORCELAIN)) diff --git a/builtin/branch.c b/builtin/branch.c index 652b1d2d..0591b22a 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -294,13 +294,13 @@ static char *resolve_symref(const char *src, const char *prefix) { unsigned char sha1[20]; int flag; - const char *dst, *cp; + const char *dst; dst = resolve_ref_unsafe(src, sha1, 0, &flag); if (!(dst && (flag & REF_ISSYMREF))) return NULL; - if (prefix && (cp = skip_prefix(dst, prefix))) - dst = cp; + if (prefix) + skip_prefix(dst, prefix, &dst); return xstrdup(dst); } diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 61e75eb6..05edd9e1 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -135,6 +135,7 @@ static int option_parse_u(const struct option *opt, int *newfd = opt->value; state.refresh_cache = 1; + state.istate = &the_index; if (*newfd < 0) *newfd = hold_locked_index(&lock_file, 1); return 0; @@ -279,8 +280,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) checkout_all(prefix, prefix_length); if (0 <= newfd && - (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file))) + write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die("Unable to write new index file"); return 0; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 07cf5553..f71e7453 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -225,7 +225,6 @@ static int checkout_paths(const struct checkout_opts *opts, int flag; struct commit *head; int errs = 0; - int newfd; struct lock_file *lock_file; if (opts->track != BRANCH_TRACK_UNSPECIFIED) @@ -256,7 +255,7 @@ static int checkout_paths(const struct checkout_opts *opts, lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); if (read_cache_preload(&opts->pathspec) < 0) return error(_("corrupt index file")); @@ -337,6 +336,7 @@ static int checkout_paths(const struct checkout_opts *opts, memset(&state, 0, sizeof(state)); state.force = 1; state.refresh_cache = 1; + state.istate = &the_index; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { @@ -352,8 +352,7 @@ static int checkout_paths(const struct checkout_opts *opts, } } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); read_ref_full("HEAD", rev, 0, &flag); @@ -444,8 +443,8 @@ static int merge_working_tree(const struct checkout_opts *opts, { int ret; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - int newfd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); if (read_cache_preload(NULL) < 0) return error(_("corrupt index file")); @@ -553,8 +552,7 @@ static int merge_working_tree(const struct checkout_opts *opts, } } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); if (!opts->force && !opts->quiet) @@ -624,7 +622,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, /* Nothing to do. */ } else if (opts->force_detach || !new->path) { /* No longer on any branch. */ update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, - REF_NODEREF, DIE_ON_ERR); + REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); if (!opts->quiet) { if (old->path && advice_detached_head) detach_advice(new->name); @@ -651,12 +649,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts, } } if (old->path && old->name) { - char log_file[PATH_MAX], ref_file[PATH_MAX]; - - git_snpath(log_file, sizeof(log_file), "logs/%s", old->path); - git_snpath(ref_file, sizeof(ref_file), "%s", old->path); - if (!file_exists(ref_file) && file_exists(log_file)) - remove_path(log_file); + if (!ref_exists(old->path) && reflog_exists(old->path)) + delete_reflog(old->path); } } remove_branch_state(); @@ -780,8 +774,8 @@ static int switch_branches(const struct checkout_opts *opts, if (!(flag & REF_ISSYMREF)) old.path = NULL; - if (old.path && starts_with(old.path, "refs/heads/")) - old.name = old.path + strlen("refs/heads/"); + if (old.path) + skip_prefix(old.path, "refs/heads/", &old.name); if (!new->name) { new->name = "HEAD"; diff --git a/builtin/clone.c b/builtin/clone.c index 545105a8..bbd169ce 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -521,7 +521,7 @@ static void write_followtags(const struct ref *refs, const char *msg) if (!has_sha1_file(ref->old_sha1)) continue; update_ref(msg, ref->name, ref->old_sha1, - NULL, 0, DIE_ON_ERR); + NULL, 0, UPDATE_REFS_DIE_ON_ERR); } } @@ -584,19 +584,20 @@ static void update_remote_refs(const struct ref *refs, static void update_head(const struct ref *our, const struct ref *remote, const char *msg) { - if (our && starts_with(our->name, "refs/heads/")) { + const char *head; + if (our && skip_prefix(our->name, "refs/heads/", &head)) { /* Local default branch link */ create_symref("HEAD", our->name, NULL); if (!option_bare) { - const char *head = skip_prefix(our->name, "refs/heads/"); - update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR); + update_ref(msg, "HEAD", our->old_sha1, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); install_branch_config(0, head, option_origin, our->name); } } else if (our) { struct commit *c = lookup_commit_reference(our->old_sha1); /* --branch specifies a non-branch (i.e. tags), detach HEAD */ update_ref(msg, "HEAD", c->object.sha1, - NULL, REF_NODEREF, DIE_ON_ERR); + NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); } else if (remote) { /* * We know remote HEAD points to a non-branch, or @@ -604,7 +605,7 @@ static void update_head(const struct ref *our, const struct ref *remote, * Detach HEAD in all these cases. */ update_ref(msg, "HEAD", remote->old_sha1, - NULL, REF_NODEREF, DIE_ON_ERR); + NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); } } @@ -616,7 +617,7 @@ static int checkout(void) struct unpack_trees_options opts; struct tree *tree; struct tree_desc t; - int err = 0, fd; + int err = 0; if (option_no_checkout) return 0; @@ -640,7 +641,7 @@ static int checkout(void) setup_work_tree(); lock_file = xcalloc(1, sizeof(struct lock_file)); - fd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); memset(&opts, 0, sizeof opts); opts.update = 1; @@ -656,8 +657,7 @@ static int checkout(void) if (unpack_trees(1, &t, &opts) < 0) die(_("unable to checkout working tree")); - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1), @@ -702,9 +702,12 @@ static void write_refspec_config(const char* src_ref_prefix, strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name, branch_top->buf, option_branch); } else if (remote_head_points_at) { + const char *head = remote_head_points_at->name; + if (!skip_prefix(head, "refs/heads/", &head)) + die("BUG: remote HEAD points at non-head?"); + strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name, - branch_top->buf, - skip_prefix(remote_head_points_at->name, "refs/heads/")); + branch_top->buf, head); } /* * otherwise, the next "git fetch" will @@ -796,18 +799,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die(_("repository '%s' does not exist"), repo_name); else repo = repo_name; - is_local = option_local != 0 && path && !is_bundle; - if (is_local) { - if (option_depth) - warning(_("--depth is ignored in local clones; use file:// instead.")); - if (!access(mkpath("%s/shallow", path), F_OK)) { - if (option_local > 0) - warning(_("source repository is shallow, ignoring --local")); - is_local = 0; - } - } - if (option_local > 0 && !is_local) - warning(_("--local is ignored")); /* no need to be strict, transport_set_option() will validate it again */ if (option_depth && atoi(option_depth) < 1) @@ -900,6 +891,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); + path = get_repo_path(remote->url[0], &is_bundle); + is_local = option_local != 0 && path && !is_bundle; + if (is_local) { + if (option_depth) + warning(_("--depth is ignored in local clones; use file:// instead.")); + if (!access(mkpath("%s/shallow", path), F_OK)) { + if (option_local > 0) + warning(_("source repository is shallow, ignoring --local")); + is_local = 0; + } + } + if (option_local > 0 && !is_local) + warning(_("--local is ignored")); transport->cloning = 1; if (!transport->get_refs_list || (!is_local && !transport->fetch)) diff --git a/builtin/commit.c b/builtin/commit.c index 39cf8976..5ed60364 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -305,7 +305,6 @@ static void refresh_cache_or_die(int refresh_flags) static char *prepare_index(int argc, const char **argv, const char *prefix, const struct commit *current_head, int is_status) { - int fd; struct string_list partial; struct pathspec pathspec; int refresh_flags = REFRESH_QUIET; @@ -321,12 +320,11 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, if (interactive) { char *old_index_env = NULL; - fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to create temporary index")); old_index_env = getenv(INDEX_ENVIRONMENT); @@ -360,12 +358,11 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, * (B) on failure, rollback the real index. */ if (all || (also && pathspec.nr)) { - fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, &pathspec, 0); refresh_cache_or_die(refresh_flags); update_main_cache_tree(WRITE_TREE_SILENT); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; return index_lock.filename; @@ -381,12 +378,12 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, * We still need to refresh the index here. */ if (!only && !pathspec.nr) { - fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); if (active_cache_changed) { update_main_cache_tree(WRITE_TREE_SILENT); - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(&index_lock)) + if (write_locked_index(&the_index, &index_lock, + COMMIT_LOCK)) die(_("unable to write new_index file")); } else { rollback_lock_file(&index_lock); @@ -423,8 +420,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, die(_("cannot do a partial commit during a cherry-pick.")); } - memset(&partial, 0, sizeof(partial)); - partial.strdup_strings = 1; + string_list_init(&partial, 1); if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec)) exit(1); @@ -432,24 +428,22 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, if (read_cache() < 0) die(_("cannot read the index")); - fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) + if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) die(_("unable to write new_index file")); - fd = hold_lock_file_for_update(&false_lock, - git_path("next-index-%"PRIuMAX, - (uintmax_t) getpid()), - LOCK_DIE_ON_ERROR); + hold_lock_file_for_update(&false_lock, + git_path("next-index-%"PRIuMAX, + (uintmax_t) getpid()), + LOCK_DIE_ON_ERROR); create_base_index(current_head); add_remove_files(&partial); refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&false_lock)) + if (write_locked_index(&the_index, &false_lock, CLOSE_LOCK)) die(_("unable to write temporary index file")); discard_cache(); @@ -526,10 +520,29 @@ static int sane_ident_split(struct ident_split *person) return 1; } +static int parse_force_date(const char *in, char *out, int len) +{ + if (len < 1) + return -1; + *out++ = '@'; + len--; + + if (parse_date(in, out, len) < 0) { + int errors = 0; + unsigned long t = approxidate_careful(in, &errors); + if (errors) + return -1; + snprintf(out, len, "%lu", t); + } + + return 0; +} + static void determine_author_info(struct strbuf *author_ident) { char *name, *email, *date; struct ident_split author; + char date_buf[64]; name = getenv("GIT_AUTHOR_NAME"); email = getenv("GIT_AUTHOR_EMAIL"); @@ -574,8 +587,12 @@ static void determine_author_info(struct strbuf *author_ident) email = xstrndup(lb + 2, rb - (lb + 2)); } - if (force_date) - date = force_date; + if (force_date) { + if (parse_force_date(force_date, date_buf, sizeof(date_buf))) + die(_("invalid date format: %s"), force_date); + date = date_buf; + } + strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); if (!split_ident_line(&author, author_ident->buf, author_ident->len) && sane_ident_split(&author)) { @@ -585,13 +602,46 @@ static void determine_author_info(struct strbuf *author_ident) } } -static char *cut_ident_timestamp_part(char *string) +static void split_ident_or_die(struct ident_split *id, const struct strbuf *buf) { - char *ket = strrchr(string, '>'); - if (!ket || ket[1] != ' ') - die(_("Malformed ident string: '%s'"), string); - *++ket = '\0'; - return ket; + if (split_ident_line(id, buf->buf, buf->len) || + !sane_ident_split(id)) + die(_("Malformed ident string: '%s'"), buf->buf); +} + +static int author_date_is_interesting(void) +{ + return author_message || force_date; +} + +static void adjust_comment_line_char(const struct strbuf *sb) +{ + char candidates[] = "#;@!$%^&|:"; + char *candidate; + const char *p; + + comment_line_char = candidates[0]; + if (!memchr(sb->buf, comment_line_char, sb->len)) + return; + + p = sb->buf; + candidate = strchr(candidates, *p); + if (candidate) + *candidate = ' '; + for (p = sb->buf; *p; p++) { + if ((p[0] == '\n' || p[0] == '\r') && p[1]) { + candidate = strchr(candidates, p[1]); + if (candidate) + *candidate = ' '; + } + } + + for (p = candidates; *p == ' '; p++) + ; + if (!*p) + die(_("unable to select a comment character that is not used\n" + "in the current commit message")); + comment_line_char = *p; } static int prepare_to_commit(const char *index_file, const char *prefix, @@ -651,7 +701,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, char *buffer; buffer = strstr(use_message_buffer, "\n\n"); if (buffer) - strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); + strbuf_addstr(&sb, buffer + 2); hook_arg1 = "commit"; hook_arg2 = use_message; } else if (fixup_message) { @@ -747,6 +797,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) die_errno(_("could not write commit template")); + if (auto_comment_line_char) + adjust_comment_line_char(&sb); strbuf_release(&sb); /* This checks if committer ident is explicitly given */ @@ -754,7 +806,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (use_editor && include_status) { int ident_shown = 0; int saved_color_setting; - char *ai_tmp, *ci_tmp; + struct ident_split ci, ai; + if (whence != FROM_COMMIT) { if (cleanup_mode == CLEANUP_SCISSORS) wt_status_add_cut_line(s->fp); @@ -794,32 +847,39 @@ static int prepare_to_commit(const char *index_file, const char *prefix, status_printf_ln(s, GIT_COLOR_NORMAL, "%s", only_include_assumed); - ai_tmp = cut_ident_timestamp_part(author_ident->buf); - ci_tmp = cut_ident_timestamp_part(committer_ident.buf); - if (strcmp(author_ident->buf, committer_ident.buf)) + split_ident_or_die(&ai, author_ident); + split_ident_or_die(&ci, &committer_ident); + + if (ident_cmp(&ai, &ci)) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" - "Author: %s"), + "Author: %.*s <%.*s>"), ident_shown++ ? "" : "\n", - author_ident->buf); + (int)(ai.name_end - ai.name_begin), ai.name_begin, + (int)(ai.mail_end - ai.mail_begin), ai.mail_begin); + + if (author_date_is_interesting()) + status_printf_ln(s, GIT_COLOR_NORMAL, + _("%s" + "Date: %s"), + ident_shown++ ? "" : "\n", + show_ident_date(&ai, DATE_NORMAL)); if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" - "Committer: %s"), + "Committer: %.*s <%.*s>"), ident_shown++ ? "" : "\n", - committer_ident.buf); + (int)(ci.name_end - ci.name_begin), ci.name_begin, + (int)(ci.mail_end - ci.mail_begin), ci.mail_begin); if (ident_shown) - status_printf_ln(s, GIT_COLOR_NORMAL, ""); + status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); saved_color_setting = s->use_color; s->use_color = 0; commitable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; - - *ai_tmp = ' '; - *ci_tmp = ' '; } else { unsigned char sha1[20]; const char *parent = "HEAD"; @@ -954,7 +1014,7 @@ static int message_is_empty(struct strbuf *sb) static int template_untouched(struct strbuf *sb) { struct strbuf tmpl = STRBUF_INIT; - char *start; + const char *start; if (cleanup_mode == CLEANUP_NONE && sb->len) return 0; @@ -963,8 +1023,7 @@ static int template_untouched(struct strbuf *sb) return 0; stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); - start = (char *)skip_prefix(sb->buf, tmpl.buf); - if (!start) + if (!skip_prefix(sb->buf, tmpl.buf, &start)) start = sb->buf; strbuf_release(&tmpl); return rest_is_empty(sb, start - sb->buf); @@ -1369,6 +1428,13 @@ static void print_summary(const char *prefix, const unsigned char *sha1, strbuf_addstr(&format, "\n Author: "); strbuf_addbuf_percentquote(&format, &author_ident); } + if (author_date_is_interesting()) { + struct strbuf date = STRBUF_INIT; + format_commit_message(commit, "%ad", &date, &pctx); + strbuf_addstr(&format, "\n Date: "); + strbuf_addbuf_percentquote(&format, &date); + strbuf_release(&date); + } if (!committer_ident_sufficiently_given()) { strbuf_addstr(&format, "\n Committer: "); strbuf_addbuf_percentquote(&format, &committer_ident); @@ -1685,6 +1751,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) ? NULL : current_head->object.sha1, 0, NULL); + if (!ref_lock) { + rollback_index_files(); + die(_("cannot lock HEAD ref")); + } nl = strchr(sb.buf, '\n'); if (nl) @@ -1694,10 +1764,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); - if (!ref_lock) { - rollback_index_files(); - die(_("cannot lock HEAD ref")); - } if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) { rollback_index_files(); die(_("cannot update HEAD ref")); diff --git a/builtin/config.c b/builtin/config.c index 5677c942..fcd84747 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -395,19 +395,6 @@ static int urlmatch_collect_fn(const char *var, const char *value, void *cb) return 0; } -static char *dup_downcase(const char *string) -{ - char *result; - size_t len, i; - - len = strlen(string); - result = xmalloc(len + 1); - for (i = 0; i < len; i++) - result[i] = tolower(string[i]); - result[i] = '\0'; - return result; -} - static int get_urlmatch(const char *var, const char *url) { char *section_tail; @@ -422,7 +409,7 @@ static int get_urlmatch(const char *var, const char *url) if (!url_normalize(url, &config.url)) die("%s", config.url.err); - config.section = dup_downcase(var); + config.section = xstrdup_tolower(var); section_tail = strchr(config.section, '.'); if (section_tail) { *section_tail = '\0'; diff --git a/builtin/describe.c b/builtin/describe.c index 24d740c8..ee6a3b99 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -56,18 +56,9 @@ static int commit_name_cmp(const struct commit_name *cn1, return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled); } -static inline unsigned int hash_sha1(const unsigned char *sha1) -{ - unsigned int hash; - memcpy(&hash, sha1, sizeof(hash)); - return hash; -} - static inline struct commit_name *find_commit_name(const unsigned char *peeled) { - struct commit_name key; - hashmap_entry_init(&key, hash_sha1(peeled)); - return hashmap_get(&names, &key, peeled); + return hashmap_get_from_hash(&names, sha1hash(peeled), peeled); } static int replace_name(struct commit_name *e, @@ -114,7 +105,7 @@ static void add_to_known_names(const char *path, if (!e) { e = xmalloc(sizeof(struct commit_name)); hashcpy(e->peeled, peeled); - hashmap_entry_init(e, hash_sha1(peeled)); + hashmap_entry_init(e, sha1hash(peeled)); hashmap_add(&names, e); e->path = NULL; } diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index dddd0f91..1c4ad622 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -22,14 +22,10 @@ static int stdin_diff_commit(struct commit *commit, char *line, int len) if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) { /* Graft the fake parents locally to the commit */ int pos = 41; - struct commit_list **pptr, *parents; + struct commit_list **pptr; /* Free the real parent list */ - for (parents = commit->parents; parents; ) { - struct commit_list *tmp = parents->next; - free(parents); - parents = tmp; - } + free_commit_list(commit->parents); commit->parents = NULL; pptr = &(commit->parents); while (line[pos] && !get_sha1_hex(line + pos, sha1)) { diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 05d161f1..92b4624a 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -17,6 +17,7 @@ #include "utf8.h" #include "parse-options.h" #include "quote.h" +#include "remote.h" static const char *fast_export_usage[] = { N_("git fast-export [rev-list-opts]"), @@ -31,6 +32,8 @@ static int use_done_feature; static int no_data; static int full_tree; static struct string_list extra_refs = STRING_LIST_INIT_NODUP; +static struct refspec *refspecs; +static int refspecs_nr; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@ -528,6 +531,15 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) continue; + if (refspecs) { + char *private; + private = apply_refspecs(refspecs, refspecs_nr, full_name); + if (private) { + free(full_name); + full_name = private; + } + } + commit = get_commit(e, full_name); if (!commit) { warning("%s: Unexpected object of type %s, skipping.", @@ -664,6 +676,19 @@ static void import_marks(char *input_file) fclose(f); } +static void handle_deletes(void) +{ + int i; + for (i = 0; i < refspecs_nr; i++) { + struct refspec *refspec = &refspecs[i]; + if (*refspec->src) + continue; + + printf("reset %s\nfrom %s\n\n", + refspec->dst, sha1_to_hex(null_sha1)); + } +} + int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -671,6 +696,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) struct commit *commit; char *export_filename = NULL, *import_filename = NULL; uint32_t lastimportid; + struct string_list refspecs_list = STRING_LIST_INIT_NODUP; struct option options[] = { OPT_INTEGER(0, "progress", &progress, N_("show progress after <n> objects")), @@ -691,6 +717,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "use-done-feature", &use_done_feature, N_("Use the done feature to terminate the stream")), OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")), + OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), + N_("Apply refspec to exported refs")), OPT_END() }; @@ -704,11 +732,27 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) revs.topo_order = 1; revs.show_source = 1; revs.rewrite_parents = 1; + argc = parse_options(argc, argv, prefix, options, fast_export_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); argc = setup_revisions(argc, argv, &revs, NULL); - argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0); if (argc > 1) usage_with_options (fast_export_usage, options); + if (refspecs_list.nr) { + const char **refspecs_str; + int i; + + refspecs_str = xmalloc(sizeof(*refspecs_str) * refspecs_list.nr); + for (i = 0; i < refspecs_list.nr; i++) + refspecs_str[i] = refspecs_list.items[i].string; + + refspecs_nr = refspecs_list.nr; + refspecs = parse_fetch_refspec(refspecs_nr, refspecs_str); + + string_list_clear(&refspecs_list, 1); + free(refspecs_str); + } + if (use_done_feature) printf("feature done\n"); @@ -736,6 +780,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) } handle_tags_and_duplicates(); + handle_deletes(); if (export_filename && lastimportid != last_idnum) export_marks(export_filename); @@ -743,5 +788,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (use_done_feature) printf("done\n"); + free_refspec(refspecs_nr, refspecs); + return 0; } diff --git a/builtin/fetch.c b/builtin/fetch.c index 55f457c0..e8d0cca3 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -45,6 +45,8 @@ static struct transport *gsecondary; static const char *submodule_prefix = ""; static const char *recurse_submodules_default; static int shown_url = 0; +static int refmap_alloc, refmap_nr; +static const char **refmap_array; static int option_parse_recurse_submodules(const struct option *opt, const char *arg, int unset) @@ -69,6 +71,19 @@ static int git_fetch_config(const char *k, const char *v, void *cb) return 0; } +static int parse_refmap_arg(const struct option *opt, const char *arg, int unset) +{ + ALLOC_GROW(refmap_array, refmap_nr + 1, refmap_alloc); + + /* + * "git fetch --refmap='' origin foo" + * can be used to tell the command not to store anywhere + */ + if (*arg) + refmap_array[refmap_nr++] = arg; + return 0; +} + static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "all", &all, @@ -107,6 +122,8 @@ static struct option builtin_fetch_options[] = { N_("default mode for recursion"), PARSE_OPT_HIDDEN }, OPT_BOOL(0, "update-shallow", &update_shallow, N_("accept refs that update .git/shallow")), + { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"), + N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg }, OPT_END() }; @@ -278,6 +295,9 @@ static struct ref *get_ref_map(struct transport *transport, const struct ref *remote_refs = transport_get_remote_refs(transport); if (refspec_count) { + struct refspec *fetch_refspec; + int fetch_refspec_nr; + for (i = 0; i < refspec_count; i++) { get_fetch_map(remote_refs, &refspecs[i], &tail, 0); if (refspecs[i].dst && refspecs[i].dst[0]) @@ -307,12 +327,21 @@ static struct ref *get_ref_map(struct transport *transport, * by ref_remove_duplicates() in favor of one of these * opportunistic entries with FETCH_HEAD_IGNORE. */ - for (i = 0; i < transport->remote->fetch_refspec_nr; i++) - get_fetch_map(ref_map, &transport->remote->fetch[i], - &oref_tail, 1); + if (refmap_array) { + fetch_refspec = parse_fetch_refspec(refmap_nr, refmap_array); + fetch_refspec_nr = refmap_nr; + } else { + fetch_refspec = transport->remote->fetch; + fetch_refspec_nr = transport->remote->fetch_refspec_nr; + } + + for (i = 0; i < fetch_refspec_nr; i++) + get_fetch_map(ref_map, &fetch_refspec[i], &oref_tail, 1); if (tags == TAGS_SET) get_fetch_map(remote_refs, tag_refspec, &tail, 0); + } else if (refmap_array) { + die("--refmap option is only meaningful with command-line refspec(s)."); } else { /* Use the defaults */ struct remote *remote = transport->remote; @@ -1053,16 +1082,11 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) refs = xcalloc(argc + 1, sizeof(const char *)); for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { - char *ref; i++; if (i >= argc) die(_("You need to specify a tag name.")); - ref = xmalloc(strlen(argv[i]) * 2 + 22); - strcpy(ref, "refs/tags/"); - strcat(ref, argv[i]); - strcat(ref, ":refs/tags/"); - strcat(ref, argv[i]); - refs[j++] = ref; + refs[j++] = xstrfmt("refs/tags/%s:refs/tags/%s", + argv[i], argv[i]); } else refs[j++] = argv[i]; } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index ef8b254e..79df05ef 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -100,7 +100,8 @@ static int handle_line(char *line, struct merge_parents *merge_parents) { int i, len = strlen(line); struct origin_data *origin_data; - char *src, *origin; + char *src; + const char *origin; struct src_data *src_data; struct string_list_item *item; int pulling_head = 0; @@ -164,8 +165,7 @@ static int handle_line(char *line, struct merge_parents *merge_parents) origin = line; string_list_append(&src_data->tag, origin + 4); src_data->head_status |= 2; - } else if (starts_with(line, "remote-tracking branch ")) { - origin = line + strlen("remote-tracking branch "); + } else if (skip_prefix(line, "remote-tracking branch ", &origin)) { string_list_append(&src_data->r_branch, origin); src_data->head_status |= 2; } else { @@ -178,11 +178,8 @@ static int handle_line(char *line, struct merge_parents *merge_parents) int len = strlen(origin); if (origin[0] == '\'' && origin[len - 1] == '\'') origin = xmemdupz(origin + 1, len - 2); - } else { - char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); - sprintf(new_origin, "%s of %s", origin, src); - origin = new_origin; - } + } else + origin = xstrfmt("%s of %s", origin, src); if (strcmp(".", src)) origin_data->is_local_branch = 0; string_list_append(&origins, origin)->util = origin_data; @@ -300,8 +297,8 @@ static void credit_people(struct strbuf *out, if (!them->nr || (them->nr == 1 && me && - (me = skip_prefix(me, them->items->string)) != NULL && - skip_prefix(me, " <"))) + skip_prefix(me, them->items->string, &me) && + starts_with(me, " <"))) return; strbuf_addf(out, "\n%c %s ", comment_line_char, label); add_people_count(out, them); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 3e1d5c33..47bd6246 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -193,7 +193,7 @@ static int verify_format(const char *format) at = parse_atom(sp + 2, ep); cp = ep + 1; - if (!memcmp(used_atom[at], "color:", 6)) + if (starts_with(used_atom[at], "color:")) need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset); } return 0; @@ -283,18 +283,6 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob } } -static int num_parents(struct commit *commit) -{ - struct commit_list *parents; - int i; - - for (i = 0, parents = commit->parents; - parents; - parents = parents->next) - i++; - return i; -} - /* See grab_values */ static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { @@ -315,12 +303,12 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object } if (!strcmp(name, "numparent")) { char *s = xmalloc(40); - v->ul = num_parents(commit); + v->ul = commit_list_count(commit->parents); sprintf(s, "%lu", v->ul); v->s = s; } else if (!strcmp(name, "parent")) { - int num = num_parents(commit); + int num = commit_list_count(commit->parents); int i; struct commit_list *parents; char *s = xmalloc(41 * num + 1); diff --git a/builtin/fsck.c b/builtin/fsck.c index 8aadca16..d42a27da 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -481,11 +481,6 @@ static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, in return 0; } -static int is_branch(const char *refname) -{ - return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"); -} - static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; diff --git a/builtin/grep.c b/builtin/grep.c index b8d440d0..c86a142f 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -361,9 +361,7 @@ static void run_pager(struct grep_opt *opt, const char *prefix) argv[i] = path_list->items[i].string; argv[path_list->nr] = NULL; - if (prefix && chdir(prefix)) - die(_("Failed to chdir: %s"), prefix); - status = run_command_v_opt(argv, RUN_USING_SHELL); + status = run_command_v_opt_cd_env(argv, RUN_USING_SHELL, prefix, NULL); if (status) exit(status); free(argv); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 9ca02039..5568a5bc 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1505,7 +1505,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) const char *curr_index; const char *index_name = NULL, *pack_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; - char *index_name_buf = NULL, *keep_name_buf = NULL; + struct strbuf index_name_buf = STRBUF_INIT, + keep_name_buf = STRBUF_INIT; struct pack_idx_entry **idx_objects; struct pack_idx_option opts; unsigned char pack_sha1[20]; @@ -1602,24 +1603,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (fix_thin_pack && !from_stdin) die(_("--fix-thin cannot be used without --stdin")); if (!index_name && pack_name) { - int len = strlen(pack_name); - if (!has_extension(pack_name, ".pack")) + size_t len; + if (!strip_suffix(pack_name, ".pack", &len)) die(_("packfile name '%s' does not end with '.pack'"), pack_name); - index_name_buf = xmalloc(len); - memcpy(index_name_buf, pack_name, len - 5); - strcpy(index_name_buf + len - 5, ".idx"); - index_name = index_name_buf; + strbuf_add(&index_name_buf, pack_name, len); + strbuf_addstr(&index_name_buf, ".idx"); + index_name = index_name_buf.buf; } if (keep_msg && !keep_name && pack_name) { - int len = strlen(pack_name); - if (!has_extension(pack_name, ".pack")) + size_t len; + if (!strip_suffix(pack_name, ".pack", &len)) die(_("packfile name '%s' does not end with '.pack'"), pack_name); - keep_name_buf = xmalloc(len); - memcpy(keep_name_buf, pack_name, len - 5); - strcpy(keep_name_buf + len - 5, ".keep"); - keep_name = keep_name_buf; + strbuf_add(&keep_name_buf, pack_name, len); + strbuf_addstr(&keep_name_buf, ".idx"); + keep_name = keep_name_buf.buf; } if (verify) { if (!index_name) @@ -1667,8 +1666,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) else close(input_fd); free(objects); - free(index_name_buf); - free(keep_name_buf); + strbuf_release(&index_name_buf); + strbuf_release(&keep_name_buf); if (pack_name == NULL) free((void *) curr_pack); if (index_name == NULL) diff --git a/builtin/log.c b/builtin/log.c index 4f678136..4389722b 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -63,6 +63,8 @@ static int parse_decoration_style(const char *var, const char *value) return DECORATE_FULL_REFS; else if (!strcmp(value, "short")) return DECORATE_SHORT_REFS; + else if (!strcmp(value, "auto")) + return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0; return -1; } @@ -668,6 +670,7 @@ static void add_header(const char *value) static int thread; static int do_signoff; static const char *signature = git_version_string; +static const char *signature_file; static int config_cover_letter; enum { @@ -737,6 +740,8 @@ static int git_format_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "format.signature")) return git_config_string(&signature, var, value); + if (!strcmp(var, "format.signaturefile")) + return git_config_pathname(&signature_file, var, value); if (!strcmp(var, "format.coverletter")) { if (value && !strcasecmp(value, "auto")) { config_cover_letter = COVER_AUTO; @@ -839,8 +844,13 @@ static void gen_message_id(struct rev_info *info, char *base) static void print_signature(void) { - if (signature && *signature) - printf("-- \n%s\n\n", signature); + if (!signature || !*signature) + return; + + printf("-- \n%s", signature); + if (signature[strlen(signature)-1] != '\n') + putchar('\n'); + putchar('\n'); } static void add_branch_description(struct strbuf *buf, const char *branch_name) @@ -851,7 +861,7 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name) read_branch_desc(&desc, branch_name); if (desc.len) { strbuf_addch(buf, '\n'); - strbuf_add(buf, desc.buf, desc.len); + strbuf_addbuf(buf, &desc); strbuf_addch(buf, '\n'); } } @@ -861,7 +871,7 @@ static char *find_branch_name(struct rev_info *rev) int i, positive = -1; unsigned char branch_sha1[20]; const unsigned char *tip_sha1; - const char *ref; + const char *ref, *v; char *full_ref, *branch = NULL; for (i = 0; i < rev->cmdline.nr; i++) { @@ -877,9 +887,9 @@ static char *find_branch_name(struct rev_info *rev) ref = rev->cmdline.rev[positive].name; tip_sha1 = rev->cmdline.rev[positive].item->sha1; if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && - starts_with(full_ref, "refs/heads/") && + skip_prefix(full_ref, "refs/heads/", &v) && !hashcmp(tip_sha1, branch_sha1)) - branch = xstrdup(full_ref + strlen("refs/heads/")); + branch = xstrdup(v); free(full_ref); return branch; } @@ -1228,6 +1238,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback }, OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), + OPT_FILENAME(0, "signature-file", &signature_file, + N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), OPT_END() }; @@ -1384,10 +1396,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (check_head) { unsigned char sha1[20]; - const char *ref; + const char *ref, *v; ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); - if (ref && starts_with(ref, "refs/heads/")) - branch_name = xstrdup(ref + strlen("refs/heads/")); + if (ref && skip_prefix(ref, "refs/heads/", &v)) + branch_name = xstrdup(v); else branch_name = xstrdup(""); /* no branch */ } @@ -1445,6 +1457,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) cover_letter = (config_cover_letter == COVER_ON); } + if (!signature) { + ; /* --no-signature inhibits all signatures */ + } else if (signature && signature != git_version_string) { + ; /* non-default signature already set */ + } else if (signature_file) { + struct strbuf buf = STRBUF_INIT; + + if (strbuf_read_file(&buf, signature_file, 128) < 0) + die_errno(_("unable to read signature file '%s'"), signature_file); + signature = strbuf_detach(&buf, NULL); + } + if (in_reply_to || thread || cover_letter) rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); if (in_reply_to) { diff --git a/builtin/merge.c b/builtin/merge.c index 8763b2ef..ce82eb29 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -63,7 +63,7 @@ static int verbosity; static int allow_rerere_auto; static int abort_current_merge; static int show_progress = -1; -static int default_to_upstream; +static int default_to_upstream = 1; static const char *sign_commit; static struct strategy all_strategy[] = { @@ -398,7 +398,7 @@ static void finish(struct commit *head_commit, const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", new_head, head, 0, - DIE_ON_ERR); + UPDATE_REFS_DIE_ON_ERR); /* * We ignore errors in 'gc --auto', since the * user should see them. @@ -657,14 +657,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *remoteheads, struct commit *head, const char *head_arg) { - int index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - index_fd = hold_locked_index(lock, 1); + hold_locked_index(lock, 1); refresh_cache(REFRESH_QUIET); if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) + write_locked_index(&the_index, lock, COMMIT_LOCK)) return error(_("Unable to write index.")); rollback_lock_file(lock); @@ -672,7 +670,6 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, int clean, x; struct commit *result; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int index_fd; struct commit_list *reversed = NULL; struct merge_options o; struct commit_list *j; @@ -700,12 +697,11 @@ 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); - index_fd = hold_locked_index(lock, 1); + hold_locked_index(lock, 1); clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) + write_locked_index(&the_index, lock, COMMIT_LOCK)) die (_("unable to write %s"), get_index_file()); rollback_lock_file(lock); return clean ? 0 : 1; @@ -843,16 +839,14 @@ static void prepare_to_commit(struct commit_list *remoteheads) static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { unsigned char result_tree[20], result_commit[20]; - struct commit_list *parent = xmalloc(sizeof(*parent)); + struct commit_list *parents, **pptr = &parents; write_tree_trivial(result_tree); printf(_("Wonderful.\n")); - parent->item = head; - parent->next = xmalloc(sizeof(*parent->next)); - parent->next->item = remoteheads->item; - parent->next->next = NULL; + pptr = commit_list_append(head, pptr); + pptr = commit_list_append(remoteheads->item, pptr); prepare_to_commit(remoteheads); - if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parent, + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, result_commit, "In-index merge"); @@ -1222,7 +1216,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("%s - not something we can merge"), argv[0]); read_empty(remote_head->object.sha1, 0); update_ref("initial pull", "HEAD", remote_head->object.sha1, - NULL, 0, DIE_ON_ERR); + NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; } else { struct strbuf merge_names = STRBUF_INIT; @@ -1282,10 +1276,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) printf(_("Commit %s has a good GPG signature by %s\n"), hex, signature_check.signer); - free(signature_check.gpg_output); - free(signature_check.gpg_status); - free(signature_check.signer); - free(signature_check.key); + signature_check_clear(&signature_check); } } @@ -1339,7 +1330,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, - NULL, 0, DIE_ON_ERR); + NULL, 0, UPDATE_REFS_DIE_ON_ERR); if (remoteheads && !common) ; /* No common ancestors found. We need a real merge. */ diff --git a/builtin/mv.c b/builtin/mv.c index 180ef991..6ffe540c 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -63,7 +63,7 @@ static struct lock_file lock_file; int cmd_mv(int argc, const char **argv, const char *prefix) { - int i, newfd, gitmodules_modified = 0; + int i, gitmodules_modified = 0; int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), @@ -85,7 +85,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (--argc < 1) usage_with_options(builtin_mv_usage, builtin_mv_options); - newfd = hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, 1); if (read_cache() < 0) die(_("index file corrupt")); @@ -276,8 +276,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) stage_updated_gitmodules(); if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index c824d4ec..3c8f319b 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -33,10 +33,7 @@ static void name_rev(struct commit *commit, return; if (deref) { - char *new_name = xmalloc(strlen(tip_name)+3); - strcpy(new_name, tip_name); - strcat(new_name, "^0"); - tip_name = new_name; + tip_name = xstrfmt("%s^0", tip_name); if (generation) die("generation: %d, but deref?", generation); diff --git a/builtin/notes.c b/builtin/notes.c index 39c8573c..820c3413 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -717,7 +717,7 @@ static int merge_commit(struct notes_merge_options *o) strbuf_insert(&msg, 0, "notes: ", 7); update_ref(msg.buf, o->local_ref, sha1, is_null_sha1(parent_sha1) ? NULL : parent_sha1, - 0, DIE_ON_ERR); + 0, UPDATE_REFS_DIE_ON_ERR); free_notes(t); strbuf_release(&msg); @@ -812,11 +812,11 @@ static int merge(int argc, const char **argv, const char *prefix) if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ /* Update default notes ref with new commit */ update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, - 0, DIE_ON_ERR); + 0, UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, - 0, DIE_ON_ERR); + 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die("Failed to store link to current notes ref (%s)", diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index de36c60c..238b5021 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2214,10 +2214,6 @@ static int git_pack_config(const char *k, const char *v, void *cb) cache_max_small_delta_size = git_config_int(k, v); return 0; } - if (!strcmp(k, "pack.writebitmaps")) { - write_bitmap_index = git_config_bool(k, v); - return 0; - } if (!strcmp(k, "pack.writebitmaphashcache")) { if (git_config_bool(k, v)) write_bitmap_options |= BITMAP_OPT_HASH_CACHE; diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 3cfe02d5..77db8739 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,17 +1,14 @@ #include "builtin.h" -static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) +static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result) { - unsigned char result[20]; char name[50]; if (!patchlen) return; - git_SHA1_Final(result, c); memcpy(name, sha1_to_hex(id), 41); printf("%s %s\n", sha1_to_hex(result), name); - git_SHA1_Init(c); } static int remove_space(char *line) @@ -56,10 +53,31 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf) +static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx) +{ + unsigned char hash[20]; + unsigned short carry = 0; + int i; + + git_SHA1_Final(hash, ctx); + git_SHA1_Init(ctx); + /* 20-byte sum, with carry */ + for (i = 0; i < 20; ++i) { + carry += result[i] + hash[i]; + result[i] = carry; + carry >>= 8; + } +} + +static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, + struct strbuf *line_buf, int stable) { int patchlen = 0, found_next = 0; int before = -1, after = -1; + git_SHA_CTX ctx; + + git_SHA1_Init(&ctx); + hashclr(result); while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { char *line = line_buf->buf; @@ -107,6 +125,8 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st break; /* Else we're parsing another header. */ + if (stable) + flush_one_hunk(result, &ctx); before = after = -1; } @@ -119,39 +139,63 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st /* Compute the sha without whitespace */ len = remove_space(line); patchlen += len; - git_SHA1_Update(ctx, line, len); + git_SHA1_Update(&ctx, line, len); } if (!found_next) hashclr(next_sha1); + flush_one_hunk(result, &ctx); + return patchlen; } -static void generate_id_list(void) +static void generate_id_list(int stable) { - unsigned char sha1[20], n[20]; - git_SHA_CTX ctx; + unsigned char sha1[20], n[20], result[20]; int patchlen; struct strbuf line_buf = STRBUF_INIT; - git_SHA1_Init(&ctx); hashclr(sha1); while (!feof(stdin)) { - patchlen = get_one_patchid(n, &ctx, &line_buf); - flush_current_id(patchlen, sha1, &ctx); + patchlen = get_one_patchid(n, result, &line_buf, stable); + flush_current_id(patchlen, sha1, result); hashcpy(sha1, n); } strbuf_release(&line_buf); } -static const char patch_id_usage[] = "git patch-id < patch"; +static const char patch_id_usage[] = "git patch-id [--stable | --unstable] < patch"; + +static int git_patch_id_config(const char *var, const char *value, void *cb) +{ + int *stable = cb; + + if (!strcmp(var, "patchid.stable")) { + *stable = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} int cmd_patch_id(int argc, const char **argv, const char *prefix) { - if (argc != 1) + int stable = -1; + + git_config(git_patch_id_config, &stable); + + /* If nothing is set, default to unstable. */ + if (stable < 0) + stable = 0; + + if (argc == 2 && !strcmp(argv[1], "--stable")) + stable = 1; + else if (argc == 2 && !strcmp(argv[1], "--unstable")) + stable = 0; + else if (argc != 1) usage(patch_id_usage); - generate_id_list(); + generate_id_list(stable); return 0; } diff --git a/builtin/push.c b/builtin/push.c index f8dfea41..f50e3d5e 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -127,11 +127,10 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote * them the big ugly fully qualified ref. */ const char *advice_maybe = ""; - const char *short_upstream = - skip_prefix(branch->merge[0]->src, "refs/heads/"); + const char *short_upstream = branch->merge[0]->src; + + skip_prefix(short_upstream, "refs/heads/", &short_upstream); - if (!short_upstream) - short_upstream = branch->merge[0]->src; /* * Don't show advice for people who explicitly set * push.default. diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 0d7ef847..e7e1c33a 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -99,7 +99,7 @@ static struct lock_file lock_file; int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { - int i, newfd, stage = 0; + int i, stage = 0; unsigned char sha1[20]; struct tree_desc t[MAX_UNPACK_TREES]; struct unpack_trees_options opts; @@ -149,12 +149,21 @@ 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); - newfd = hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, 1); prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); + /* + * NEEDSWORK + * + * The old index should be read anyway even if we're going to + * destroy all index entries because we still need to preserve + * certain information such as index version or split-index + * mode. + */ + if (opts.reset || opts.merge || opts.prefix) { if (read_cache_unmerged() && (opts.prefix || opts.merge)) die("You need to resolve your current index first"); @@ -231,10 +240,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * what came from the tree. */ if (nr_trees == 1 && !opts.prefix) - prime_cache_tree(&active_cache_tree, trees[0]); + prime_cache_tree(&the_index, trees[0]); - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die("unable to write new index file"); return 0; } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c3230817..f93ac454 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -438,7 +438,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) uint32_t mask = 1 << (cmd->index % 32); int i; - trace_printf_key("GIT_TRACE_SHALLOW", + trace_printf_key(&trace_shallow, "shallow: update_shallow_ref %s\n", cmd->ref_name); for (i = 0; i < si->shallow->nr; i++) if (si->used_shallow[i] && @@ -614,12 +614,9 @@ static void run_update_post_hook(struct command *commands) argv[0] = hook; for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { - char *p; if (cmd->error_string || cmd->did_not_exist) continue; - p = xmalloc(strlen(cmd->ref_name) + 1); - strcpy(p, cmd->ref_name); - argv[argc] = p; + argv[argc] = xstrdup(cmd->ref_name); argc++; } argv[argc] = NULL; @@ -1125,7 +1122,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) int advertise_refs = 0; int stateless_rpc = 0; int i; - char *dir = NULL; + const char *dir = NULL; struct command *commands; struct sha1_array shallow = SHA1_ARRAY_INIT; struct sha1_array ref = SHA1_ARRAY_INIT; @@ -1160,7 +1157,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) } if (dir) usage(receive_pack_usage); - dir = xstrdup(arg); + dir = arg; } if (!dir) usage(receive_pack_usage); diff --git a/builtin/reflog.c b/builtin/reflog.c index c12a9784..e8a8fb13 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -369,7 +369,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, if (!lock) return error("cannot lock ref '%s'", ref); log_file = git_pathdup("logs/%s", ref); - if (!file_exists(log_file)) + if (!reflog_exists(ref)) goto finish; if (!cmd->dry_run) { newlog_path = git_pathdup("logs/%s.lock", ref); diff --git a/builtin/remote.c b/builtin/remote.c index c9102e8f..9a4640db 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -250,9 +250,7 @@ static struct string_list branch_list; static const char *abbrev_ref(const char *name, const char *prefix) { - const char *abbrev = skip_prefix(name, prefix); - if (abbrev) - return abbrev; + skip_prefix(name, prefix, &name); return name; } #define abbrev_branch(name) abbrev_ref((name), "refs/heads/") @@ -265,16 +263,17 @@ static int config_read_branches(const char *key, const char *value, void *cb) struct string_list_item *item; struct branch_info *info; enum { REMOTE, MERGE, REBASE } type; + size_t key_len; key += 7; - if (ends_with(key, ".remote")) { - name = xstrndup(key, strlen(key) - 7); + if (strip_suffix(key, ".remote", &key_len)) { + name = xmemdupz(key, key_len); type = REMOTE; - } else if (ends_with(key, ".merge")) { - name = xstrndup(key, strlen(key) - 6); + } else if (strip_suffix(key, ".merge", &key_len)) { + name = xmemdupz(key, key_len); type = MERGE; - } else if (ends_with(key, ".rebase")) { - name = xstrndup(key, strlen(key) - 7); + } else if (strip_suffix(key, ".rebase", &key_len)) { + name = xmemdupz(key, key_len); type = REBASE; } else return 0; @@ -755,7 +754,7 @@ static int remove_branches(struct string_list *branches) branch_names = xmalloc(branches->nr * sizeof(*branch_names)); for (i = 0; i < branches->nr; i++) branch_names[i] = branches->items[i].string; - result |= repack_without_refs(branch_names, branches->nr); + result |= repack_without_refs(branch_names, branches->nr, NULL); free(branch_names); for (i = 0; i < branches->nr; i++) { @@ -1333,7 +1332,8 @@ static int prune_remote(const char *remote, int dry_run) for (i = 0; i < states.stale.nr; i++) delete_refs[i] = states.stale.items[i].util; if (!dry_run) - result |= repack_without_refs(delete_refs, states.stale.nr); + result |= repack_without_refs(delete_refs, + states.stale.nr, NULL); free(delete_refs); } diff --git a/builtin/repack.c b/builtin/repack.c index 36c1cf9c..a77e743b 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -10,7 +10,7 @@ static int delta_base_offset = 1; static int pack_kept_objects = -1; -static int write_bitmaps = -1; +static int write_bitmaps; static char *packdir, *packtmp; static const char *const git_repack_usage[] = { @@ -28,7 +28,8 @@ static int repack_config(const char *var, const char *value, void *cb) pack_kept_objects = git_config_bool(var, value); return 0; } - if (!strcmp(var, "pack.writebitmaps")) { + if (!strcmp(var, "repack.writebitmaps") || + !strcmp(var, "pack.writebitmaps")) { write_bitmaps = git_config_bool(var, value); return 0; } @@ -82,16 +83,15 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list) DIR *dir; struct dirent *e; char *fname; - size_t len; if (!(dir = opendir(packdir))) return; while ((e = readdir(dir)) != NULL) { - if (!ends_with(e->d_name, ".pack")) + size_t len; + if (!strip_suffix(e->d_name, ".pack", &len)) continue; - len = strlen(e->d_name) - strlen(".pack"); fname = xmemdupz(e->d_name, len); if (!file_exists(mkpath("%s/%s.keep", packdir, fname))) @@ -195,7 +195,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) git_repack_usage, 0); if (pack_kept_objects < 0) - pack_kept_objects = write_bitmaps > 0; + pack_kept_objects = write_bitmaps; packdir = mkpathdup("%s/pack", get_object_directory()); packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid()); @@ -221,9 +221,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argv_array_pushf(&cmd_args, "--no-reuse-delta"); if (no_reuse_object) argv_array_pushf(&cmd_args, "--no-reuse-object"); - if (write_bitmaps >= 0) - argv_array_pushf(&cmd_args, "--%swrite-bitmap-index", - write_bitmaps ? "" : "no-"); + if (write_bitmaps) + argv_array_push(&cmd_args, "--write-bitmap-index"); if (pack_everything & ALL_INTO_ONE) { get_non_kept_pack_filenames(&existing_packs); diff --git a/builtin/replace.c b/builtin/replace.c index b62420a0..294b61b9 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -12,18 +12,22 @@ #include "builtin.h" #include "refs.h" #include "parse-options.h" +#include "run-command.h" +#include "tag.h" static const char * const git_replace_usage[] = { N_("git replace [-f] <object> <replacement>"), + N_("git replace [-f] --edit <object>"), + N_("git replace [-f] --graft <commit> [<parent>...]"), N_("git replace -d <object>..."), N_("git replace [--format=<format>] [-l [<pattern>]]"), NULL }; enum replace_format { - REPLACE_FORMAT_SHORT, - REPLACE_FORMAT_MEDIUM, - REPLACE_FORMAT_LONG + REPLACE_FORMAT_SHORT, + REPLACE_FORMAT_MEDIUM, + REPLACE_FORMAT_LONG }; struct show_data { @@ -123,26 +127,36 @@ static int delete_replace_ref(const char *name, const char *ref, return 0; } -static int replace_object(const char *object_ref, const char *replace_ref, - int force) +static void check_ref_valid(unsigned char object[20], + unsigned char prev[20], + char *ref, + int ref_size, + int force) { - unsigned char object[20], prev[20], repl[20]; - enum object_type obj_type, repl_type; - char ref[PATH_MAX]; - struct ref_lock *lock; - - if (get_sha1(object_ref, object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); - if (get_sha1(replace_ref, repl)) - die("Failed to resolve '%s' as a valid ref.", replace_ref); - - if (snprintf(ref, sizeof(ref), + if (snprintf(ref, ref_size, "refs/replace/%s", - sha1_to_hex(object)) > sizeof(ref) - 1) + sha1_to_hex(object)) > ref_size - 1) die("replace ref name too long: %.*s...", 50, ref); if (check_refname_format(ref, 0)) die("'%s' is not a valid ref name.", ref); + if (read_ref(ref, prev)) + hashclr(prev); + else if (!force) + die("replace ref '%s' already exists", ref); +} + +static int replace_object_sha1(const char *object_ref, + unsigned char object[20], + const char *replace_ref, + unsigned char repl[20], + int force) +{ + unsigned char prev[20]; + enum object_type obj_type, repl_type; + char ref[PATH_MAX]; + struct ref_lock *lock; + obj_type = sha1_object_info(object, NULL); repl_type = sha1_object_info(repl, NULL); if (!force && obj_type != repl_type) @@ -152,10 +166,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, object_ref, typename(obj_type), replace_ref, typename(repl_type)); - if (read_ref(ref, prev)) - hashclr(prev); - else if (!force) - die("replace ref '%s' already exists", ref); + check_ref_valid(object, prev, ref, sizeof(ref), force); lock = lock_any_ref_for_update(ref, prev, 0, NULL); if (!lock) @@ -166,14 +177,261 @@ static int replace_object(const char *object_ref, const char *replace_ref, return 0; } +static int replace_object(const char *object_ref, const char *replace_ref, int force) +{ + unsigned char object[20], repl[20]; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + if (get_sha1(replace_ref, repl)) + die("Failed to resolve '%s' as a valid ref.", replace_ref); + + return replace_object_sha1(object_ref, object, replace_ref, repl, force); +} + +/* + * Write the contents of the object named by "sha1" to the file "filename". + * If "raw" is true, then the object's raw contents are printed according to + * "type". Otherwise, we pretty-print the contents for human editing. + */ +static void export_object(const unsigned char *sha1, enum object_type type, + int raw, const char *filename) +{ + struct child_process cmd = { NULL }; + int fd; + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + die_errno("unable to open %s for writing", filename); + + argv_array_push(&cmd.args, "--no-replace-objects"); + argv_array_push(&cmd.args, "cat-file"); + if (raw) + argv_array_push(&cmd.args, typename(type)); + else + argv_array_push(&cmd.args, "-p"); + argv_array_push(&cmd.args, sha1_to_hex(sha1)); + cmd.git_cmd = 1; + cmd.out = fd; + + if (run_command(&cmd)) + die("cat-file reported failure"); +} + +/* + * Read a previously-exported (and possibly edited) object back from "filename", + * interpreting it as "type", and writing the result to the object database. + * The sha1 of the written object is returned via sha1. + */ +static void import_object(unsigned char *sha1, enum object_type type, + int raw, const char *filename) +{ + int fd; + + fd = open(filename, O_RDONLY); + if (fd < 0) + die_errno("unable to open %s for reading", filename); + + if (!raw && type == OBJ_TREE) { + const char *argv[] = { "mktree", NULL }; + struct child_process cmd = { argv }; + struct strbuf result = STRBUF_INIT; + + cmd.argv = argv; + cmd.git_cmd = 1; + cmd.in = fd; + cmd.out = -1; + + if (start_command(&cmd)) + die("unable to spawn mktree"); + + if (strbuf_read(&result, cmd.out, 41) < 0) + die_errno("unable to read from mktree"); + close(cmd.out); + + if (finish_command(&cmd)) + die("mktree reported failure"); + if (get_sha1_hex(result.buf, sha1) < 0) + die("mktree did not return an object name"); + + strbuf_release(&result); + } else { + struct stat st; + int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT; + + if (fstat(fd, &st) < 0) + die_errno("unable to fstat %s", filename); + if (index_fd(sha1, fd, &st, type, NULL, flags) < 0) + die("unable to write object to database"); + /* index_fd close()s fd for us */ + } + + /* + * No need to close(fd) here; both run-command and index-fd + * will have done it for us. + */ +} + +static int edit_and_replace(const char *object_ref, int force, int raw) +{ + char *tmpfile = git_pathdup("REPLACE_EDITOBJ"); + enum object_type type; + unsigned char old[20], new[20], prev[20]; + char ref[PATH_MAX]; + + if (get_sha1(object_ref, old) < 0) + die("Not a valid object name: '%s'", object_ref); + + type = sha1_object_info(old, NULL); + if (type < 0) + die("unable to get object type for %s", sha1_to_hex(old)); + + check_ref_valid(old, prev, ref, sizeof(ref), force); + + export_object(old, type, raw, tmpfile); + if (launch_editor(tmpfile, NULL, NULL) < 0) + die("editing object file failed"); + import_object(new, type, raw, tmpfile); + + free(tmpfile); + + if (!hashcmp(old, new)) + return error("new object is the same as the old one: '%s'", sha1_to_hex(old)); + + return replace_object_sha1(object_ref, old, "replacement", new, force); +} + +static void replace_parents(struct strbuf *buf, int argc, const char **argv) +{ + struct strbuf new_parents = STRBUF_INIT; + const char *parent_start, *parent_end; + int i; + + /* find existing parents */ + parent_start = buf->buf; + parent_start += 46; /* "tree " + "hex sha1" + "\n" */ + parent_end = parent_start; + + while (starts_with(parent_end, "parent ")) + parent_end += 48; /* "parent " + "hex sha1" + "\n" */ + + /* prepare new parents */ + for (i = 0; i < argc; i++) { + unsigned char sha1[20]; + if (get_sha1(argv[i], sha1) < 0) + die(_("Not a valid object name: '%s'"), argv[i]); + lookup_commit_or_die(sha1, argv[i]); + strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1)); + } + + /* replace existing parents with new ones */ + strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start, + new_parents.buf, new_parents.len); + + strbuf_release(&new_parents); +} + +struct check_mergetag_data { + int argc; + const char **argv; +}; + +static void check_one_mergetag(struct commit *commit, + struct commit_extra_header *extra, + void *data) +{ + struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data; + const char *ref = mergetag_data->argv[0]; + unsigned char tag_sha1[20]; + struct tag *tag; + int i; + + hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1); + tag = lookup_tag(tag_sha1); + if (!tag) + die(_("bad mergetag in commit '%s'"), ref); + if (parse_tag_buffer(tag, extra->value, extra->len)) + die(_("malformed mergetag in commit '%s'"), ref); + + /* iterate over new parents */ + for (i = 1; i < mergetag_data->argc; i++) { + unsigned char sha1[20]; + if (get_sha1(mergetag_data->argv[i], sha1) < 0) + die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]); + if (!hashcmp(tag->tagged->sha1, sha1)) + return; /* found */ + } + + die(_("original commit '%s' contains mergetag '%s' that is discarded; " + "use --edit instead of --graft"), ref, sha1_to_hex(tag_sha1)); +} + +static void check_mergetags(struct commit *commit, int argc, const char **argv) +{ + struct check_mergetag_data mergetag_data; + + mergetag_data.argc = argc; + mergetag_data.argv = argv; + for_each_mergetag(check_one_mergetag, commit, &mergetag_data); +} + +static int create_graft(int argc, const char **argv, int force) +{ + unsigned char old[20], new[20]; + const char *old_ref = argv[0]; + struct commit *commit; + struct strbuf buf = STRBUF_INIT; + const char *buffer; + unsigned long size; + + if (get_sha1(old_ref, old) < 0) + die(_("Not a valid object name: '%s'"), old_ref); + commit = lookup_commit_or_die(old, old_ref); + + buffer = get_commit_buffer(commit, &size); + strbuf_add(&buf, buffer, size); + unuse_commit_buffer(commit, buffer); + + replace_parents(&buf, argc - 1, &argv[1]); + + if (remove_signature(&buf)) { + warning(_("the original commit '%s' has a gpg signature."), old_ref); + warning(_("the signature will be removed in the replacement commit!")); + } + + check_mergetags(commit, argc, argv); + + if (write_sha1_file(buf.buf, buf.len, commit_type, new)) + die(_("could not write replacement commit for: '%s'"), old_ref); + + strbuf_release(&buf); + + if (!hashcmp(old, new)) + return error("new commit is the same as the old one: '%s'", sha1_to_hex(old)); + + return replace_object_sha1(old_ref, old, "replacement", new, force); +} + int cmd_replace(int argc, const char **argv, const char *prefix) { - int list = 0, delete = 0, force = 0; + int force = 0; + int raw = 0; const char *format = NULL; + enum { + MODE_UNSPECIFIED = 0, + MODE_LIST, + MODE_DELETE, + MODE_EDIT, + MODE_GRAFT, + MODE_REPLACE + } cmdmode = MODE_UNSPECIFIED; struct option options[] = { - OPT_BOOL('l', "list", &list, N_("list replace refs")), - OPT_BOOL('d', "delete", &delete, N_("delete replace refs")), + OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST), + OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE), + OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT), + OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT), OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")), + OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")), OPT_STRING(0, "format", &format, N_("format"), N_("use this format")), OPT_END() }; @@ -182,44 +440,56 @@ int cmd_replace(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); - if (list && delete) - usage_msg_opt("-l and -d cannot be used together", + if (!cmdmode) + cmdmode = argc ? MODE_REPLACE : MODE_LIST; + + if (format && cmdmode != MODE_LIST) + usage_msg_opt("--format cannot be used when not listing", git_replace_usage, options); - if (format && delete) - usage_msg_opt("--format and -d cannot be used together", + if (force && + cmdmode != MODE_REPLACE && + cmdmode != MODE_EDIT && + cmdmode != MODE_GRAFT) + usage_msg_opt("-f only makes sense when writing a replacement", git_replace_usage, options); - if (force && (list || delete)) - usage_msg_opt("-f cannot be used with -d or -l", + if (raw && cmdmode != MODE_EDIT) + usage_msg_opt("--raw only makes sense with --edit", git_replace_usage, options); - /* Delete refs */ - if (delete) { + switch (cmdmode) { + case MODE_DELETE: if (argc < 1) usage_msg_opt("-d needs at least one argument", git_replace_usage, options); return for_each_replace_name(argv, delete_replace_ref); - } - /* Replace object */ - if (!list && argc) { + case MODE_REPLACE: if (argc != 2) usage_msg_opt("bad number of arguments", git_replace_usage, options); - if (format) - usage_msg_opt("--format cannot be used when not listing", - git_replace_usage, options); return replace_object(argv[0], argv[1], force); - } - /* List refs, even if "list" is not set */ - if (argc > 1) - usage_msg_opt("only one pattern can be given with -l", - git_replace_usage, options); - if (force) - usage_msg_opt("-f needs some arguments", - git_replace_usage, options); + case MODE_EDIT: + if (argc != 1) + usage_msg_opt("-e needs exactly one argument", + git_replace_usage, options); + return edit_and_replace(argv[0], force, raw); - return list_replace_refs(argv[0], format); + case MODE_GRAFT: + if (argc < 1) + usage_msg_opt("-g needs at least one argument", + git_replace_usage, options); + return create_graft(argc, argv, force); + + case MODE_LIST: + if (argc > 1) + usage_msg_opt("only one pattern can be given with -l", + git_replace_usage, options); + return list_replace_refs(argv[0], format); + + default: + die("BUG: invalid cmdmode %d", (int)cmdmode); + } } diff --git a/builtin/reset.c b/builtin/reset.c index 6bd62458..855d478e 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -84,7 +84,7 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet) if (reset_type == MIXED || reset_type == HARD) { tree = parse_tree_indirect(sha1); - prime_cache_tree(&active_cache_tree, tree); + prime_cache_tree(&the_index, tree); } return 0; @@ -252,11 +252,13 @@ static int reset_refs(const char *rev, const unsigned char *sha1) if (!get_sha1("HEAD", sha1_orig)) { orig = sha1_orig; set_reflog_message(&msg, "updating ORIG_HEAD", NULL); - update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) delete_ref("ORIG_HEAD", old_orig, 0); set_reflog_message(&msg, "updating HEAD", rev); - update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); + update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, + UPDATE_REFS_MSG_ON_ERR); strbuf_release(&msg); return update_ref_status; } @@ -351,7 +353,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type != SOFT) { struct lock_file *lock = xcalloc(1, sizeof(*lock)); - int newfd = hold_locked_index(lock, 1); + hold_locked_index(lock, 1); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, sha1, intent_to_add)) @@ -367,8 +369,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die(_("Could not reset index file to revision '%s'."), rev); } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock)) + if (write_locked_index(&the_index, lock, COMMIT_LOCK)) die(_("Could not write new index file.")); } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 1a6122d3..d85e08cc 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -11,6 +11,7 @@ #include "parse-options.h" #include "diff.h" #include "revision.h" +#include "split-index.h" #define DO_REVS 1 #define DO_NOREV 2 @@ -150,6 +151,7 @@ static void show_rev(int type, const unsigned char *sha1, const char *name) error("refname '%s' is ambiguous", name); break; } + free(full); } else { show_with_type(type, name); } @@ -775,6 +777,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) : "false"); continue; } + if (!strcmp(arg, "--shared-index-path")) { + if (read_cache() < 0) + die(_("Could not read the index")); + if (the_index.split_index) { + const unsigned char *sha1 = the_index.split_index->base_sha1; + puts(git_path("sharedindex.%s", sha1_to_hex(sha1))); + } + continue; + } if (starts_with(arg, "--since=")) { show_datestring("--max-age=", arg+8); continue; diff --git a/builtin/rm.c b/builtin/rm.c index 960634dd..bc6490b8 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -278,7 +278,7 @@ static struct option builtin_rm_options[] = { int cmd_rm(int argc, const char **argv, const char *prefix) { - int i, newfd; + int i; struct pathspec pathspec; char *seen; @@ -293,7 +293,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) setup_work_tree(); - newfd = hold_locked_index(&lock_file, 1); + hold_locked_index(&lock_file, 1); if (read_cache() < 0) die(_("index file corrupt")); @@ -427,8 +427,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) } if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("Unable to write new index file")); } diff --git a/builtin/show-branch.c b/builtin/show-branch.c index d8731729..298c95e3 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -755,7 +755,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } for (i = 0; i < reflog; i++) { - char *logmsg, *m; + char *logmsg; const char *msg; unsigned long timestamp; int tz; @@ -770,15 +770,14 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) msg = "(none)"; else msg++; - m = xmalloc(strlen(msg) + 200); - sprintf(m, "(%s) %s", - show_date(timestamp, tz, 1), - msg); - reflog_msg[i] = m; + reflog_msg[i] = xstrfmt("(%s) %s", + show_date(timestamp, tz, 1), + msg); free(logmsg); sprintf(nth_desc, "%s@{%d}", *av, base+i); append_ref(nth_desc, sha1, 1); } + free(ref); } else if (all_heads + all_remotes) snarf_refs(all_heads, all_remotes); diff --git a/builtin/tag.c b/builtin/tag.c index 6c7c6bde..19eb7478 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -32,6 +32,8 @@ static const char * const git_tag_usage[] = { #define SORT_MASK 0x7fff #define REVERSE_SORT 0x8000 +static int tag_sort; + struct tag_filter { const char **patterns; int lines; @@ -80,11 +82,19 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) return 0; } -static int contains_recurse(struct commit *candidate, +enum contains_result { + CONTAINS_UNKNOWN = -1, + CONTAINS_NO = 0, + CONTAINS_YES = 1 +}; + +/* + * Test whether the candidate or one of its parents is contained in the list. + * Do not recurse to find out, though, but return -1 if inconclusive. + */ +static enum contains_result contains_test(struct commit *candidate, const struct commit_list *want) { - struct commit_list *p; - /* was it previously marked as containing a want commit? */ if (candidate->object.flags & TMP_MARK) return 1; @@ -92,26 +102,78 @@ static int contains_recurse(struct commit *candidate, if (candidate->object.flags & UNINTERESTING) return 0; /* or are we it? */ - if (in_commit_list(want, candidate)) + if (in_commit_list(want, candidate)) { + candidate->object.flags |= TMP_MARK; return 1; + } if (parse_commit(candidate) < 0) return 0; - /* Otherwise recurse and mark ourselves for future traversals. */ - for (p = candidate->parents; p; p = p->next) { - if (contains_recurse(p->item, want)) { - candidate->object.flags |= TMP_MARK; - return 1; - } - } - candidate->object.flags |= UNINTERESTING; - return 0; + return -1; } -static int contains(struct commit *candidate, const struct commit_list *want) +/* + * Mimicking the real stack, this stack lives on the heap, avoiding stack + * overflows. + * + * At each recursion step, the stack items points to the commits whose + * ancestors are to be inspected. + */ +struct stack { + int nr, alloc; + struct stack_entry { + struct commit *commit; + struct commit_list *parents; + } *stack; +}; + +static void push_to_stack(struct commit *candidate, struct stack *stack) { - return contains_recurse(candidate, want); + int index = stack->nr++; + ALLOC_GROW(stack->stack, stack->nr, stack->alloc); + stack->stack[index].commit = candidate; + stack->stack[index].parents = candidate->parents; +} + +static enum contains_result contains(struct commit *candidate, + const struct commit_list *want) +{ + struct stack stack = { 0, 0, NULL }; + int result = contains_test(candidate, want); + + if (result != CONTAINS_UNKNOWN) + return result; + + push_to_stack(candidate, &stack); + while (stack.nr) { + struct stack_entry *entry = &stack.stack[stack.nr - 1]; + struct commit *commit = entry->commit; + struct commit_list *parents = entry->parents; + + if (!parents) { + commit->object.flags |= UNINTERESTING; + stack.nr--; + } + /* + * If we just popped the stack, parents->item has been marked, + * therefore contains_test will return a meaningful 0 or 1. + */ + else switch (contains_test(parents->item, want)) { + case CONTAINS_YES: + commit->object.flags |= TMP_MARK; + stack.nr--; + break; + case CONTAINS_NO: + entry->parents = parents->next; + break; + case CONTAINS_UNKNOWN: + push_to_stack(parents->item, &stack); + break; + } + } + free(stack.stack); + return contains_test(candidate, want); } static void show_tag_lines(const unsigned char *sha1, int lines) @@ -278,17 +340,59 @@ static int do_sign(struct strbuf *buffer) } static const char tag_template[] = - N_("\nWrite a tag message\n" + N_("\nWrite a message for tag:\n %s\n" "Lines starting with '%c' will be ignored.\n"); static const char tag_template_nocleanup[] = - N_("\nWrite a tag message\n" + N_("\nWrite a message for tag:\n %s\n" "Lines starting with '%c' will be kept; you may remove them" " yourself if you want to.\n"); +/* + * Parse a sort string, and return 0 if parsed successfully. Will return + * non-zero when the sort string does not parse into a known type. If var is + * given, the error message becomes a warning and includes information about + * the configuration value. + */ +static int parse_sort_string(const char *var, const char *arg, int *sort) +{ + int type = 0, flags = 0; + + if (skip_prefix(arg, "-", &arg)) + flags |= REVERSE_SORT; + + if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg)) + type = VERCMP_SORT; + else + type = STRCMP_SORT; + + if (strcmp(arg, "refname")) { + if (!var) + return error(_("unsupported sort specification '%s'"), arg); + else { + warning(_("unsupported sort specification '%s' in variable '%s'"), + var, arg); + return -1; + } + } + + *sort = (type | flags); + + return 0; +} + static int git_tag_config(const char *var, const char *value, void *cb) { - int status = git_gpg_config(var, value, cb); + int status; + + if (!strcmp(var, "tag.sort")) { + if (!value) + return config_error_nonbool(var); + parse_sort_string(var, value, &tag_sort); + return 0; + } + + status = git_gpg_config(var, value, cb); if (status) return status; if (starts_with(var, "column.")) @@ -378,9 +482,9 @@ static void create_tag(const unsigned char *object, const char *tag, struct strbuf buf = STRBUF_INIT; strbuf_addch(&buf, '\n'); if (opt->cleanup_mode == CLEANUP_ALL) - strbuf_commented_addf(&buf, _(tag_template), comment_line_char); + strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char); else - strbuf_commented_addf(&buf, _(tag_template_nocleanup), comment_line_char); + strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char); write_or_die(fd, buf.buf, buf.len); strbuf_release(&buf); } @@ -462,24 +566,8 @@ static int parse_opt_points_at(const struct option *opt __attribute__((unused)), static int parse_opt_sort(const struct option *opt, const char *arg, int unset) { int *sort = opt->value; - int flags = 0; - if (*arg == '-') { - flags |= REVERSE_SORT; - arg++; - } - if (starts_with(arg, "version:")) { - *sort = VERCMP_SORT; - arg += 8; - } else if (starts_with(arg, "v:")) { - *sort = VERCMP_SORT; - arg += 2; - } else - *sort = STRCMP_SORT; - if (strcmp(arg, "refname")) - die(_("unsupported sort specification %s"), arg); - *sort |= flags; - return 0; + return parse_sort_string(NULL, arg, sort); } int cmd_tag(int argc, const char **argv, const char *prefix) @@ -492,7 +580,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct create_tag_options opt; char *cleanup_arg = NULL; int annotate = 0, force = 0, lines = -1; - int cmdmode = 0, sort = 0; + int cmdmode = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct commit_list *with_commit = NULL; @@ -518,7 +606,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT__FORCE(&force, N_("replace the tag if exists")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), { - OPTION_CALLBACK, 0, "sort", &sort, N_("type"), N_("sort tags"), + OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), PARSE_OPT_NONEG, parse_opt_sort }, @@ -574,9 +662,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) copts.padding = 2; run_column_filter(colopts, &copts); } - if (lines != -1 && sort) + if (lines != -1 && tag_sort) die(_("--sort and -n are incompatible")); - ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort); + ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort); if (column_active(colopts)) stop_column_filter(); return ret; diff --git a/builtin/update-index.c b/builtin/update-index.c index ebea285e..e8c7fd4d 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -13,6 +13,7 @@ #include "parse-options.h" #include "pathspec.h" #include "dir.h" +#include "split-index.h" /* * Default to not allowing changes to the list of files. The @@ -55,8 +56,9 @@ static int mark_ce_flags(const char *path, int flag, int mark) active_cache[pos]->ce_flags |= flag; else active_cache[pos]->ce_flags &= ~flag; - cache_tree_invalidate_path(active_cache_tree, path); - active_cache_changed = 1; + active_cache[pos]->ce_flags |= CE_UPDATE_IN_BASE; + cache_tree_invalidate_path(&the_index, path); + active_cache_changed |= CE_ENTRY_CHANGED; return 0; } return -1; @@ -267,8 +269,9 @@ static void chmod_path(int flip, const char *path) default: goto fail; } - cache_tree_invalidate_path(active_cache_tree, path); - active_cache_changed = 1; + cache_tree_invalidate_path(&the_index, path); + ce->ce_flags |= CE_UPDATE_IN_BASE; + active_cache_changed |= CE_ENTRY_CHANGED; report("chmod %cx '%s'", flip, path); return; fail: @@ -743,6 +746,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) char set_executable_bit = 0; struct refresh_params refresh_args = {0, &has_errors}; int lock_error = 0; + int split_index = -1; struct lock_file *lock_file; struct parse_opt_ctx_t ctx; int parseopt_state = PARSE_OPT_UNKNOWN; @@ -825,6 +829,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) resolve_undo_clear_callback}, OPT_INTEGER(0, "index-version", &preferred_index_format, N_("write index in this format")), + OPT_BOOL(0, "split-index", &split_index, + N_("enable or disable split index")), OPT_END() }; @@ -892,7 +898,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) INDEX_FORMAT_LB, INDEX_FORMAT_UB); if (the_index.version != preferred_index_format) - active_cache_changed = 1; + active_cache_changed |= SOMETHING_CHANGED; the_index.version = preferred_index_format; } @@ -918,14 +924,27 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) strbuf_release(&buf); } + if (split_index > 0) { + init_split_index(&the_index); + the_index.cache_changed |= SPLIT_INDEX_ORDERED; + } else if (!split_index && the_index.split_index) { + /* + * can't discard_split_index(&the_index); because that + * will destroy split_index->base->cache[], which may + * be shared with the_index.cache[]. So yeah we're + * leaking a bit here. + */ + the_index.split_index = NULL; + the_index.cache_changed |= SOMETHING_CHANGED; + } + if (active_cache_changed) { if (newfd < 0) { if (refresh_args.flags & REFRESH_QUIET) exit(128); unable_to_lock_index_die(get_index_file(), lock_error); } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die("Unable to write new index file"); } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 5c208bb1..3067b113 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -12,238 +12,332 @@ static const char * const git_update_ref_usage[] = { NULL }; -static int updates_alloc; -static int updates_count; -static const struct ref_update **updates; +static struct ref_transaction *transaction; static char line_termination = '\n'; static int update_flags; - -static struct ref_update *update_alloc(void) -{ - struct ref_update *update; - - /* Allocate and zero-init a struct ref_update */ - update = xcalloc(1, sizeof(*update)); - ALLOC_GROW(updates, updates_count + 1, updates_alloc); - updates[updates_count++] = update; - - /* Store and reset accumulated options */ - update->flags = update_flags; - update_flags = 0; - - return update; -} - -static void update_store_ref_name(struct ref_update *update, - const char *ref_name) -{ - if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL)) - die("invalid ref format: %s", ref_name); - update->ref_name = xstrdup(ref_name); -} - -static void update_store_new_sha1(struct ref_update *update, - const char *newvalue) -{ - if (*newvalue && get_sha1(newvalue, update->new_sha1)) - die("invalid new value for ref %s: %s", - update->ref_name, newvalue); -} - -static void update_store_old_sha1(struct ref_update *update, - const char *oldvalue) -{ - if (*oldvalue && get_sha1(oldvalue, update->old_sha1)) - die("invalid old value for ref %s: %s", - update->ref_name, oldvalue); - - /* We have an old value if non-empty, or if empty without -z */ - update->have_old = *oldvalue || line_termination; -} - +static struct strbuf err = STRBUF_INIT; + +/* + * Parse one whitespace- or NUL-terminated, possibly C-quoted argument + * and append the result to arg. Return a pointer to the terminator. + * Die if there is an error in how the argument is C-quoted. This + * function is only used if not -z. + */ static const char *parse_arg(const char *next, struct strbuf *arg) { - /* Parse SP-terminated, possibly C-quoted argument */ - if (*next != '"') + if (*next == '"') { + const char *orig = next; + + if (unquote_c_style(arg, next, &next)) + die("badly quoted argument: %s", orig); + if (*next && !isspace(*next)) + die("unexpected character after quoted argument: %s", orig); + } else { while (*next && !isspace(*next)) strbuf_addch(arg, *next++); - else if (unquote_c_style(arg, next, &next)) - die("badly quoted argument: %s", next); + } - /* Return position after the argument */ return next; } -static const char *parse_first_arg(const char *next, struct strbuf *arg) +/* + * Parse the reference name immediately after "command SP". If not + * -z, then handle C-quoting. Return a pointer to a newly allocated + * string containing the name of the reference, or NULL if there was + * an error. Update *next to point at the character that terminates + * the argument. Die if C-quoting is malformed or the reference name + * is invalid. + */ +static char *parse_refname(struct strbuf *input, const char **next) { - /* Parse argument immediately after "command SP" */ - strbuf_reset(arg); + struct strbuf ref = STRBUF_INIT; + if (line_termination) { /* Without -z, use the next argument */ - next = parse_arg(next, arg); + *next = parse_arg(*next, &ref); } else { - /* With -z, use rest of first NUL-terminated line */ - strbuf_addstr(arg, next); - next = next + arg->len; + /* With -z, use everything up to the next NUL */ + strbuf_addstr(&ref, *next); + *next += ref.len; } - return next; + + if (!ref.len) { + strbuf_release(&ref); + return NULL; + } + + if (check_refname_format(ref.buf, REFNAME_ALLOW_ONELEVEL)) + die("invalid ref format: %s", ref.buf); + + return strbuf_detach(&ref, NULL); } -static const char *parse_next_arg(const char *next, struct strbuf *arg) +/* + * The value being parsed is <oldvalue> (as opposed to <newvalue>; the + * difference affects which error messages are generated): + */ +#define PARSE_SHA1_OLD 0x01 + +/* + * For backwards compatibility, accept an empty string for update's + * <newvalue> in binary mode to be equivalent to specifying zeros. + */ +#define PARSE_SHA1_ALLOW_EMPTY 0x02 + +/* + * Parse an argument separator followed by the next argument, if any. + * If there is an argument, convert it to a SHA-1, write it to sha1, + * set *next to point at the character terminating the argument, and + * return 0. If there is no argument at all (not even the empty + * string), return 1 and leave *next unchanged. If the value is + * provided but cannot be converted to a SHA-1, die. flags can + * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY. + */ +static int parse_next_sha1(struct strbuf *input, const char **next, + unsigned char *sha1, + const char *command, const char *refname, + int flags) { - /* Parse next SP-terminated or NUL-terminated argument, if any */ - strbuf_reset(arg); + struct strbuf arg = STRBUF_INIT; + int ret = 0; + + if (*next == input->buf + input->len) + goto eof; + if (line_termination) { /* Without -z, consume SP and use next argument */ - if (!*next) - return NULL; - if (*next != ' ') - die("expected SP but got: %s", next); - next = parse_arg(next + 1, arg); + if (!**next || **next == line_termination) + return 1; + if (**next != ' ') + die("%s %s: expected SP but got: %s", + command, refname, *next); + (*next)++; + *next = parse_arg(*next, &arg); + if (arg.len) { + if (get_sha1(arg.buf, sha1)) + goto invalid; + } else { + /* Without -z, an empty value means all zeros: */ + hashclr(sha1); + } } else { /* With -z, read the next NUL-terminated line */ - if (*next) - die("expected NUL but got: %s", next); - if (strbuf_getline(arg, stdin, '\0') == EOF) - return NULL; - next = arg->buf + arg->len; + if (**next) + die("%s %s: expected NUL but got: %s", + command, refname, *next); + (*next)++; + if (*next == input->buf + input->len) + goto eof; + strbuf_addstr(&arg, *next); + *next += arg.len; + + if (arg.len) { + if (get_sha1(arg.buf, sha1)) + goto invalid; + } else if (flags & PARSE_SHA1_ALLOW_EMPTY) { + /* With -z, treat an empty value as all zeros: */ + warning("%s %s: missing <newvalue>, treating as zero", + command, refname); + hashclr(sha1); + } else { + /* + * With -z, an empty non-required value means + * unspecified: + */ + ret = 1; + } } - return next; + + strbuf_release(&arg); + + return ret; + + invalid: + die(flags & PARSE_SHA1_OLD ? + "%s %s: invalid <oldvalue>: %s" : + "%s %s: invalid <newvalue>: %s", + command, refname, arg.buf); + + eof: + die(flags & PARSE_SHA1_OLD ? + "%s %s: unexpected end of input when reading <oldvalue>" : + "%s %s: unexpected end of input when reading <newvalue>", + command, refname); } -static void parse_cmd_update(const char *next) + +/* + * The following five parse_cmd_*() functions parse the corresponding + * command. In each case, next points at the character following the + * command name and the following space. They each return a pointer + * to the character terminating the command, and die with an + * explanatory message if there are any parsing problems. All of + * these functions handle either text or binary format input, + * depending on how line_termination is set. + */ + +static const char *parse_cmd_update(struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf newvalue = STRBUF_INIT; - struct strbuf oldvalue = STRBUF_INIT; - struct ref_update *update; + char *refname; + unsigned char new_sha1[20]; + unsigned char old_sha1[20]; + int have_old; - update = update_alloc(); + refname = parse_refname(input, &next); + if (!refname) + die("update: missing <ref>"); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("update line missing <ref>"); + if (parse_next_sha1(input, &next, new_sha1, "update", refname, + PARSE_SHA1_ALLOW_EMPTY)) + die("update %s: missing <newvalue>", refname); - if ((next = parse_next_arg(next, &newvalue)) != NULL) - update_store_new_sha1(update, newvalue.buf); - else - die("update %s missing <newvalue>", ref.buf); + have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname, + PARSE_SHA1_OLD); + + if (*next != line_termination) + die("update %s: extra input: %s", refname, next); - if ((next = parse_next_arg(next, &oldvalue)) != NULL) - update_store_old_sha1(update, oldvalue.buf); - else if(!line_termination) - die("update %s missing [<oldvalue>] NUL", ref.buf); + if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, + update_flags, have_old, &err)) + die("%s", err.buf); + + update_flags = 0; + free(refname); - if (next && *next) - die("update %s has extra input: %s", ref.buf, next); + return next; } -static void parse_cmd_create(const char *next) +static const char *parse_cmd_create(struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf newvalue = STRBUF_INIT; - struct ref_update *update; + char *refname; + unsigned char new_sha1[20]; - update = update_alloc(); - update->have_old = 1; + refname = parse_refname(input, &next); + if (!refname) + die("create: missing <ref>"); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("create line missing <ref>"); + if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0)) + die("create %s: missing <newvalue>", refname); - if ((next = parse_next_arg(next, &newvalue)) != NULL) - update_store_new_sha1(update, newvalue.buf); - else - die("create %s missing <newvalue>", ref.buf); - if (is_null_sha1(update->new_sha1)) - die("create %s given zero new value", ref.buf); + if (is_null_sha1(new_sha1)) + die("create %s: zero <newvalue>", refname); + + if (*next != line_termination) + die("create %s: extra input: %s", refname, next); + + ref_transaction_create(transaction, refname, new_sha1, update_flags); + + update_flags = 0; + free(refname); - if (next && *next) - die("create %s has extra input: %s", ref.buf, next); + return next; } -static void parse_cmd_delete(const char *next) +static const char *parse_cmd_delete(struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf oldvalue = STRBUF_INIT; - struct ref_update *update; + char *refname; + unsigned char old_sha1[20]; + int have_old; - update = update_alloc(); + refname = parse_refname(input, &next); + if (!refname) + die("delete: missing <ref>"); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("delete line missing <ref>"); + if (parse_next_sha1(input, &next, old_sha1, "delete", refname, + PARSE_SHA1_OLD)) { + have_old = 0; + } else { + if (is_null_sha1(old_sha1)) + die("delete %s: zero <oldvalue>", refname); + have_old = 1; + } - if ((next = parse_next_arg(next, &oldvalue)) != NULL) - update_store_old_sha1(update, oldvalue.buf); - else if(!line_termination) - die("delete %s missing [<oldvalue>] NUL", ref.buf); - if (update->have_old && is_null_sha1(update->old_sha1)) - die("delete %s given zero old value", ref.buf); + if (*next != line_termination) + die("delete %s: extra input: %s", refname, next); - if (next && *next) - die("delete %s has extra input: %s", ref.buf, next); + ref_transaction_delete(transaction, refname, old_sha1, + update_flags, have_old); + + update_flags = 0; + free(refname); + + return next; } -static void parse_cmd_verify(const char *next) +static const char *parse_cmd_verify(struct strbuf *input, const char *next) { - struct strbuf ref = STRBUF_INIT; - struct strbuf value = STRBUF_INIT; - struct ref_update *update; + char *refname; + unsigned char new_sha1[20]; + unsigned char old_sha1[20]; + int have_old; + + refname = parse_refname(input, &next); + if (!refname) + die("verify: missing <ref>"); + + if (parse_next_sha1(input, &next, old_sha1, "verify", refname, + PARSE_SHA1_OLD)) { + hashclr(new_sha1); + have_old = 0; + } else { + hashcpy(new_sha1, old_sha1); + have_old = 1; + } - update = update_alloc(); + if (*next != line_termination) + die("verify %s: extra input: %s", refname, next); - if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) - update_store_ref_name(update, ref.buf); - else - die("verify line missing <ref>"); + if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, + update_flags, have_old, &err)) + die("%s", err.buf); - if ((next = parse_next_arg(next, &value)) != NULL) { - update_store_old_sha1(update, value.buf); - update_store_new_sha1(update, value.buf); - } else if(!line_termination) - die("verify %s missing [<oldvalue>] NUL", ref.buf); + update_flags = 0; + free(refname); - if (next && *next) - die("verify %s has extra input: %s", ref.buf, next); + return next; } -static void parse_cmd_option(const char *next) +static const char *parse_cmd_option(struct strbuf *input, const char *next) { - if (!strcmp(next, "no-deref")) + if (!strncmp(next, "no-deref", 8) && next[8] == line_termination) update_flags |= REF_NODEREF; else die("option unknown: %s", next); + return next + 8; } static void update_refs_stdin(void) { - struct strbuf cmd = STRBUF_INIT; + struct strbuf input = STRBUF_INIT; + const char *next; + if (strbuf_read(&input, 0, 1000) < 0) + die_errno("could not read from stdin"); + next = input.buf; /* Read each line dispatch its command */ - while (strbuf_getline(&cmd, stdin, line_termination) != EOF) - if (!cmd.buf[0]) + while (next < input.buf + input.len) { + if (*next == line_termination) die("empty command in input"); - else if (isspace(*cmd.buf)) - die("whitespace before command: %s", cmd.buf); - else if (starts_with(cmd.buf, "update ")) - parse_cmd_update(cmd.buf + 7); - else if (starts_with(cmd.buf, "create ")) - parse_cmd_create(cmd.buf + 7); - else if (starts_with(cmd.buf, "delete ")) - parse_cmd_delete(cmd.buf + 7); - else if (starts_with(cmd.buf, "verify ")) - parse_cmd_verify(cmd.buf + 7); - else if (starts_with(cmd.buf, "option ")) - parse_cmd_option(cmd.buf + 7); + else if (isspace(*next)) + die("whitespace before command: %s", next); + else if (starts_with(next, "update ")) + next = parse_cmd_update(&input, next + 7); + else if (starts_with(next, "create ")) + next = parse_cmd_create(&input, next + 7); + else if (starts_with(next, "delete ")) + next = parse_cmd_delete(&input, next + 7); + else if (starts_with(next, "verify ")) + next = parse_cmd_verify(&input, next + 7); + else if (starts_with(next, "option ")) + next = parse_cmd_option(&input, next + 7); else - die("unknown command: %s", cmd.buf); + die("unknown command: %s", next); + + next++; + } - strbuf_release(&cmd); + strbuf_release(&input); } int cmd_update_ref(int argc, const char **argv, const char *prefix) @@ -268,12 +362,16 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("Refusing to perform update with empty message."); if (read_stdin) { + transaction = ref_transaction_begin(); if (delete || no_deref || argc > 0) usage_with_options(git_update_ref_usage, options); if (end_null) line_termination = '\0'; update_refs_stdin(); - return update_refs(msg, updates, updates_count, DIE_ON_ERR); + if (ref_transaction_commit(transaction, msg, &err)) + die("%s", err.buf); + ref_transaction_free(transaction); + return 0; } if (end_null) @@ -305,5 +403,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) return delete_ref(refname, oldval ? oldsha1 : NULL, flags); else return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, - flags, DIE_ON_ERR); + flags, UPDATE_REFS_DIE_ON_ERR); } diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c new file mode 100644 index 00000000..b0f85042 --- /dev/null +++ b/builtin/verify-commit.c @@ -0,0 +1,93 @@ +/* + * Builtin "git commit-commit" + * + * Copyright (c) 2014 Michael J Gruber <git@drmicha.warpmail.net> + * + * Based on git-verify-tag + */ +#include "cache.h" +#include "builtin.h" +#include "commit.h" +#include "run-command.h" +#include <signal.h> +#include "parse-options.h" +#include "gpg-interface.h" + +static const char * const verify_commit_usage[] = { + N_("git verify-commit [-v|--verbose] <commit>..."), + NULL +}; + +static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose) +{ + struct signature_check signature_check; + + memset(&signature_check, 0, sizeof(signature_check)); + + check_commit_signature(lookup_commit(sha1), &signature_check); + + if (verbose && signature_check.payload) + fputs(signature_check.payload, stdout); + + if (signature_check.gpg_output) + fputs(signature_check.gpg_output, stderr); + + signature_check_clear(&signature_check); + return signature_check.result != 'G'; +} + +static int verify_commit(const char *name, int verbose) +{ + enum object_type type; + unsigned char sha1[20]; + char *buf; + unsigned long size; + int ret; + + if (get_sha1(name, sha1)) + return error("commit '%s' not found.", name); + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return error("%s: unable to read file.", name); + if (type != OBJ_COMMIT) + return error("%s: cannot verify a non-commit object of type %s.", + name, typename(type)); + + ret = run_gpg_verify(sha1, buf, size, verbose); + + free(buf); + return ret; +} + +static int git_verify_commit_config(const char *var, const char *value, void *cb) +{ + int status = git_gpg_config(var, value, cb); + if (status) + return status; + return git_default_config(var, value, cb); +} + +int cmd_verify_commit(int argc, const char **argv, const char *prefix) +{ + int i = 1, verbose = 0, had_error = 0; + const struct option verify_commit_options[] = { + OPT__VERBOSE(&verbose, N_("print commit contents")), + OPT_END() + }; + + git_config(git_verify_commit_config, NULL); + + argc = parse_options(argc, argv, prefix, verify_commit_options, + verify_commit_usage, PARSE_OPT_KEEP_ARGV0); + if (argc <= i) + usage_with_options(verify_commit_usage, verify_commit_options); + + /* sometimes the program was terminated because this signal + * was received in the process of writing the gpg input: */ + signal(SIGPIPE, SIG_IGN); + while (i < argc) + if (verify_commit(argv[i++], verbose)) + had_error = 1; + return had_error; +} diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index 66cd2df0..972579f3 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -27,10 +27,9 @@ static int verify_one_pack(const char *path, unsigned int flags) * normalize these forms to "foo.pack" for "index-pack --verify". */ strbuf_addstr(&arg, path); - if (has_extension(arg.buf, ".idx")) - strbuf_splice(&arg, arg.len - 3, 3, "pack", 4); - else if (!has_extension(arg.buf, ".pack")) - strbuf_add(&arg, ".pack", 5); + if (strbuf_strip_suffix(&arg, ".idx") || + !ends_with(arg.buf, ".pack")) + strbuf_addstr(&arg, ".pack"); argv[2] = arg.buf; memset(&index_pack, 0, sizeof(index_pack)); @@ -237,8 +237,6 @@ int create_bundle(struct bundle_header *header, const char *path, static struct lock_file lock; int bundle_fd = -1; int bundle_to_stdout; - struct argv_array argv_boundary = ARGV_ARRAY_INIT; - struct argv_array argv_pack = ARGV_ARRAY_INIT; int i, ref_count = 0; struct strbuf buf = STRBUF_INIT; struct rev_info revs; @@ -260,14 +258,12 @@ int create_bundle(struct bundle_header *header, const char *path, init_revisions(&revs, NULL); /* write prerequisites */ - argv_array_pushl(&argv_boundary, + memset(&rls, 0, sizeof(rls)); + argv_array_pushl(&rls.args, "rev-list", "--boundary", "--pretty=oneline", NULL); for (i = 1; i < argc; i++) - argv_array_push(&argv_boundary, argv[i]); - - memset(&rls, 0, sizeof(rls)); - rls.argv = argv_boundary.argv; + argv_array_push(&rls.args, argv[i]); rls.out = -1; rls.git_cmd = 1; if (start_command(&rls)) @@ -382,12 +378,11 @@ int create_bundle(struct bundle_header *header, const char *path, write_or_die(bundle_fd, "\n", 1); /* write pack */ - argv_array_pushl(&argv_pack, + memset(&rls, 0, sizeof(rls)); + argv_array_pushl(&rls.args, "pack-objects", "--all-progress-implied", "--stdout", "--thin", "--delta-base-offset", NULL); - memset(&rls, 0, sizeof(rls)); - rls.argv = argv_pack.argv; rls.in = -1; rls.out = bundle_fd; rls.git_cmd = 1; diff --git a/cache-tree.c b/cache-tree.c index 7fa524a1..c53f7de2 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -98,7 +98,7 @@ struct cache_tree_sub *cache_tree_sub(struct cache_tree *it, const char *path) return find_subtree(it, path, pathlen, 1); } -void cache_tree_invalidate_path(struct cache_tree *it, const char *path) +static int do_invalidate_path(struct cache_tree *it, const char *path) { /* a/b/c * ==> invalidate self @@ -116,7 +116,7 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path) #endif if (!it) - return; + return 0; slash = strchrnul(path, '/'); namelen = slash - path; it->entry_count = -1; @@ -137,14 +137,21 @@ void cache_tree_invalidate_path(struct cache_tree *it, const char *path) (it->subtree_nr - pos - 1)); it->subtree_nr--; } - return; + return 1; } down = find_subtree(it, path, namelen, 0); if (down) - cache_tree_invalidate_path(down->cache_tree, slash + 1); + do_invalidate_path(down->cache_tree, slash + 1); + return 1; } -static int verify_cache(const struct cache_entry * const *cache, +void cache_tree_invalidate_path(struct index_state *istate, const char *path) +{ + if (do_invalidate_path(istate->cache_tree, path)) + istate->cache_changed |= CACHE_TREE_CHANGED; +} + +static int verify_cache(struct cache_entry **cache, int entries, int flags) { int i, funny; @@ -229,7 +236,7 @@ int cache_tree_fully_valid(struct cache_tree *it) } static int update_one(struct cache_tree *it, - const struct cache_entry * const *cache, + struct cache_entry **cache, int entries, const char *base, int baselen, @@ -391,18 +398,19 @@ static int update_one(struct cache_tree *it, return i; } -int cache_tree_update(struct cache_tree *it, - const struct cache_entry * const *cache, - int entries, - int flags) +int cache_tree_update(struct index_state *istate, int flags) { - int i, skip; - i = verify_cache(cache, entries, flags); + struct cache_tree *it = istate->cache_tree; + struct cache_entry **cache = istate->cache; + int entries = istate->cache_nr; + int skip, i = verify_cache(cache, entries, flags); + if (i) return i; i = update_one(it, cache, entries, "", 0, &skip, flags); if (i < 0) return i; + istate->cache_changed |= CACHE_TREE_CHANGED; return 0; } @@ -590,13 +598,10 @@ int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix) was_valid = cache_tree_fully_valid(active_cache_tree); if (!was_valid) { - if (cache_tree_update(active_cache_tree, - (const struct cache_entry * const *)active_cache, - active_nr, flags) < 0) + if (cache_tree_update(&the_index, flags) < 0) return WRITE_TREE_UNMERGED_INDEX; if (0 <= newfd) { - if (!write_cache(newfd, active_cache, active_nr) && - !commit_lock_file(lock_file)) + if (!write_locked_index(&the_index, lock_file, COMMIT_LOCK)) newfd = -1; } /* Not being able to write is fine -- we are only interested @@ -649,11 +654,12 @@ static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) it->entry_count = cnt; } -void prime_cache_tree(struct cache_tree **it, struct tree *tree) +void prime_cache_tree(struct index_state *istate, struct tree *tree) { - cache_tree_free(it); - *it = cache_tree(); - prime_cache_tree_rec(*it, tree); + cache_tree_free(&istate->cache_tree); + istate->cache_tree = cache_tree(); + prime_cache_tree_rec(istate->cache_tree, tree); + istate->cache_changed |= CACHE_TREE_CHANGED; } /* @@ -692,7 +698,5 @@ int update_main_cache_tree(int flags) { if (!the_index.cache_tree) the_index.cache_tree = cache_tree(); - return cache_tree_update(the_index.cache_tree, - (const struct cache_entry * const *)the_index.cache, - the_index.cache_nr, flags); + return cache_tree_update(&the_index, flags); } diff --git a/cache-tree.h b/cache-tree.h index f1923ad1..b47ccec7 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -23,14 +23,14 @@ struct cache_tree { struct cache_tree *cache_tree(void); void cache_tree_free(struct cache_tree **); -void cache_tree_invalidate_path(struct cache_tree *, const char *); +void cache_tree_invalidate_path(struct index_state *, const char *); struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *); void cache_tree_write(struct strbuf *, struct cache_tree *root); struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); int cache_tree_fully_valid(struct cache_tree *); -int cache_tree_update(struct cache_tree *, const struct cache_entry * const *, int, int); +int cache_tree_update(struct index_state *, int); int update_main_cache_tree(int); @@ -46,7 +46,7 @@ int update_main_cache_tree(int); #define WRITE_TREE_PREFIX_ERROR (-3) int write_cache_as_tree(unsigned char *sha1, int flags, const char *prefix); -void prime_cache_tree(struct cache_tree **, struct tree *); +void prime_cache_tree(struct index_state *, struct tree *); extern int cache_tree_matches_traversal(struct cache_tree *, struct name_entry *ent, struct traverse_info *info); @@ -7,6 +7,7 @@ #include "advice.h" #include "gettext.h" #include "convert.h" +#include "trace.h" #include SHA1_HEADER #ifndef git_SHA_CTX @@ -75,6 +76,21 @@ unsigned long git_deflate_bound(git_zstream *, unsigned long); #define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) /* + * Some mode bits are also used internally for computations. + * + * They *must* not overlap with any valid modes, and they *must* not be emitted + * to outside world - i.e. appear on disk or network. In other words, it's just + * temporary fields, which we internally use, but they have to stay in-house. + * + * ( such approach is valid, as standard S_IF* fits into 16 bits, and in Git + * codebase mode is `unsigned int` which is assumed to be at least 32 bits ) + */ + +/* used internally in tree-diff */ +#define S_DIFFTREE_IFXMIN_NEQ 0x80000000 + + +/* * Intensive research over the course of many years has shown that * port 9418 is totally unused by anything else. Or * @@ -135,6 +151,7 @@ struct cache_entry { unsigned int ce_mode; unsigned int ce_flags; unsigned int ce_namelen; + unsigned int index; /* for link extension */ unsigned char sha1[20]; char name[FLEX_ARRAY]; /* more */ }; @@ -145,7 +162,7 @@ struct cache_entry { #define CE_STAGESHIFT 12 /* - * Range 0xFFFF0000 in ce_flags is divided into + * Range 0xFFFF0FFF in ce_flags is divided into * two parts: in-memory flags and on-disk ones. * Flags in CE_EXTENDED_FLAGS will get saved on-disk * if you want to save a new flag, add it in @@ -168,6 +185,9 @@ struct cache_entry { /* used to temporarily mark paths matched by pathspecs */ #define CE_MATCHED (1 << 26) +#define CE_UPDATE_IN_BASE (1 << 27) +#define CE_STRIP_NAME (1 << 28) + /* * Extended on-disk flags */ @@ -268,12 +288,22 @@ static inline unsigned int canon_mode(unsigned int mode) #define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1) +#define SOMETHING_CHANGED (1 << 0) /* unclassified changes go here */ +#define CE_ENTRY_CHANGED (1 << 1) +#define CE_ENTRY_REMOVED (1 << 2) +#define CE_ENTRY_ADDED (1 << 3) +#define RESOLVE_UNDO_CHANGED (1 << 4) +#define CACHE_TREE_CHANGED (1 << 5) +#define SPLIT_INDEX_ORDERED (1 << 6) + +struct split_index; struct index_state { struct cache_entry **cache; unsigned int version; unsigned int cache_nr, cache_alloc, cache_changed; struct string_list *resolve_undo; struct cache_tree *cache_tree; + struct split_index *split_index; struct cache_time timestamp; unsigned name_hash_initialized : 1, initialized : 1; @@ -302,7 +332,6 @@ extern void free_name_hash(struct index_state *istate); #define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec)) #define is_cache_unborn() is_index_unborn(&the_index) #define read_cache_unmerged() read_index_unmerged(&the_index) -#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd)) #define discard_cache() discard_index(&the_index) #define unmerged_cache() unmerged_index(&the_index) #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen)) @@ -457,12 +486,17 @@ extern int daemonize(void); } while (0) /* Initialize and use the cache information */ +struct lock_file; extern int read_index(struct index_state *); extern int read_index_preload(struct index_state *, const struct pathspec *pathspec); +extern int do_read_index(struct index_state *istate, const char *path, + int must_exist); /* for testting only! */ extern int read_index_from(struct index_state *, const char *path); extern int is_index_unborn(struct index_state *); extern int read_index_unmerged(struct index_state *); -extern int write_index(struct index_state *, int newfd); +#define COMMIT_LOCK (1 << 0) +#define CLOSE_LOCK (1 << 1) +extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags); extern int discard_index(struct index_state *); extern int unmerged_index(const struct index_state *); extern int verify_path(const char *path); @@ -474,6 +508,7 @@ extern int index_name_pos(const struct index_state *, const char *name, int name #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ #define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ #define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */ +#define ADD_CACHE_KEEP_CACHE_TREE 32 /* Do not invalidate cache-tree */ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option); extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); extern int remove_index_entry_at(struct index_state *, int pos); @@ -544,6 +579,8 @@ struct lock_file { #define LOCK_DIE_ON_ERROR 1 #define LOCK_NODEREF 2 extern int unable_to_lock_error(const char *path, int err); +extern void unable_to_lock_message(const char *path, int err, + struct strbuf *buf); extern NORETURN void unable_to_lock_index_die(const char *path, int err); extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); @@ -551,7 +588,6 @@ extern int commit_lock_file(struct lock_file *); extern void update_index_if_able(struct index_state *, struct lock_file *); extern int hold_locked_index(struct lock_file *, int); -extern int commit_locked_index(struct lock_file *); extern void set_alternate_index_output(const char *); extern int close_lock_file(struct lock_file *); extern void rollback_lock_file(struct lock_file *); @@ -597,14 +633,13 @@ extern int fsync_object_files; extern int core_preload_index; extern int core_apply_sparse_checkout; extern int precomposed_unicode; -extern int protect_hfs; -extern int protect_ntfs; /* * The character that begins a commented line in user-editable file * that is subject to stripspace. */ extern char comment_line_char; +extern int auto_comment_line_char; enum branch_track { BRANCH_TRACK_UNSPECIFIED = -1, @@ -812,8 +847,6 @@ int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, struct string_list *prefixes); char *strip_path_suffix(const char *path, const char *suffix); int daemon_avoid_alias(const char *path); -int offset_1st_component(const char *path); -extern int is_ntfs_dotgit(const char *name); /* object replacement */ #define LOOKUP_REPLACE_OBJECT 1 @@ -965,7 +998,7 @@ extern int read_ref(const char *refname, unsigned char *sha1); * NULL. If more than MAXDEPTH recursive symbolic lookups are needed, * give up and return NULL. * - * errno is sometimes set on errors, but not always. + * errno is set to something meaningful on error. */ extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag); extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag); @@ -987,7 +1020,7 @@ extern int validate_headref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); -extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); +extern int name_compare(const char *name1, size_t len1, const char *name2, size_t len2); extern int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2); extern void *read_object_with_reference(const unsigned char *sha1, @@ -1050,6 +1083,13 @@ struct ident_split { extern int split_ident_line(struct ident_split *, const char *, int); /* + * Like show_date, but pull the timestamp and tz parameters from + * the ident_split. It will also sanity-check the values and produce + * a well-known sentinel date if they appear bogus. + */ +const char *show_ident_date(const struct ident_split *id, enum date_mode mode); + +/* * Compare split idents for equality or strict ordering. Note that we * compare only the ident part of the line, ignoring any timestamp. * @@ -1059,6 +1099,7 @@ extern int split_ident_line(struct ident_split *, const char *, int); extern int ident_cmp(const struct ident_split *, const struct ident_split *); struct checkout { + struct index_state *istate; const char *base_dir; int base_dir_len; unsigned force:1, @@ -1071,12 +1112,16 @@ struct checkout { extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); struct cache_def { - char path[PATH_MAX + 1]; - int len; + struct strbuf path; int flags; int track_flags; int prefix_len_stat_func; }; +#define CACHE_DEF_INIT { STRBUF_INIT, 0, 0, 0 } +static inline void cache_def_clear(struct cache_def *cache) +{ + strbuf_release(&cache->path); +} extern int has_symlink_leading_path(const char *name, int len); extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); @@ -1275,8 +1320,8 @@ extern int check_repository_format_version(const char *var, const char *value, v extern int git_env_bool(const char *, int); extern int git_config_system(void); extern int config_error_nonbool(const char *); -#if defined(__GNUC__) && ! defined(__clang__) -#define config_error_nonbool(s) (config_error_nonbool(s), -1) +#if defined(__GNUC__) +#define config_error_nonbool(s) (config_error_nonbool(s), const_error()) #endif extern const char *get_log_output_encoding(void); extern const char *get_commit_output_encoding(void); @@ -1359,17 +1404,7 @@ extern void *alloc_object_node(void); extern void alloc_report(void); extern unsigned int alloc_commit_index(void); -/* trace.c */ -__attribute__((format (printf, 1, 2))) -extern void trace_printf(const char *format, ...); -__attribute__((format (printf, 2, 3))) -extern void trace_argv_printf(const char **argv, const char *format, ...); -extern void trace_repo_setup(const char *prefix); -extern int trace_want(const char *key); -__attribute__((format (printf, 2, 3))) -extern void trace_printf_key(const char *key, const char *fmt, ...); -extern void trace_strbuf(const char *key, const struct strbuf *buf); - +/* pkt-line.c */ void packet_trace_identity(const char *prog); /* add */ diff --git a/check_bindir b/check_bindir index a1c4c3e8..623eadcb 100755 --- a/check_bindir +++ b/check_bindir @@ -2,7 +2,7 @@ bindir="$1" gitexecdir="$2" gitcmd="$3" -if test "$bindir" != "$gitexecdir" -a -x "$gitcmd" +if test "$bindir" != "$gitexecdir" && test -x "$gitcmd" then echo echo "!! You have installed git-* commands to new gitexecdir." @@ -336,8 +336,9 @@ static int column_config(const char *var, const char *value, int git_column_config(const char *var, const char *value, const char *command, unsigned int *colopts) { - const char *it = skip_prefix(var, "column."); - if (!it) + const char *it; + + if (!skip_prefix(var, "column.", &it)) return 0; if (!strcmp(it, "ui")) @@ -370,46 +371,29 @@ static struct child_process column_process; int run_column_filter(int colopts, const struct column_options *opts) { - const char *av[10]; - int ret, ac = 0; - struct strbuf sb_colopt = STRBUF_INIT; - struct strbuf sb_width = STRBUF_INIT; - struct strbuf sb_padding = STRBUF_INIT; + struct argv_array *argv; if (fd_out != -1) return -1; - av[ac++] = "column"; - strbuf_addf(&sb_colopt, "--raw-mode=%d", colopts); - av[ac++] = sb_colopt.buf; - if (opts && opts->width) { - strbuf_addf(&sb_width, "--width=%d", opts->width); - av[ac++] = sb_width.buf; - } - if (opts && opts->indent) { - av[ac++] = "--indent"; - av[ac++] = opts->indent; - } - if (opts && opts->padding) { - strbuf_addf(&sb_padding, "--padding=%d", opts->padding); - av[ac++] = sb_padding.buf; - } - av[ac] = NULL; + memset(&column_process, 0, sizeof(column_process)); + argv = &column_process.args; + + argv_array_push(argv, "column"); + argv_array_pushf(argv, "--raw-mode=%d", colopts); + if (opts && opts->width) + argv_array_pushf(argv, "--width=%d", opts->width); + if (opts && opts->indent) + argv_array_pushf(argv, "--indent=%s", opts->indent); + if (opts && opts->padding) + argv_array_pushf(argv, "--padding=%d", opts->padding); fflush(stdout); - memset(&column_process, 0, sizeof(column_process)); column_process.in = -1; column_process.out = dup(1); column_process.git_cmd = 1; - column_process.argv = av; - - ret = start_command(&column_process); - - strbuf_release(&sb_colopt); - strbuf_release(&sb_width); - strbuf_release(&sb_padding); - if (ret) + if (start_command(&column_process)) return -2; fd_out = dup(1); diff --git a/combine-diff.c b/combine-diff.c index fd6d63c7..f9975d2c 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -1301,6 +1301,81 @@ static const char *path_path(void *obj) return path->path; } + +/* find set of paths that every parent touches */ +static struct combine_diff_path *find_paths_generic(const unsigned char *sha1, + const struct sha1_array *parents, struct diff_options *opt) +{ + struct combine_diff_path *paths = NULL; + int i, num_parent = parents->nr; + + int output_format = opt->output_format; + const char *orderfile = opt->orderfile; + + opt->output_format = DIFF_FORMAT_NO_OUTPUT; + /* tell diff_tree to emit paths in sorted (=tree) order */ + opt->orderfile = NULL; + + /* D(A,P1...Pn) = D(A,P1) ^ ... ^ D(A,Pn) (wrt paths) */ + for (i = 0; i < num_parent; i++) { + /* + * show stat against the first parent even when doing + * combined diff. + */ + int stat_opt = (output_format & + (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT)); + if (i == 0 && stat_opt) + opt->output_format = stat_opt; + else + opt->output_format = DIFF_FORMAT_NO_OUTPUT; + diff_tree_sha1(parents->sha1[i], sha1, "", opt); + diffcore_std(opt); + paths = intersect_paths(paths, i, num_parent); + + /* if showing diff, show it in requested order */ + if (opt->output_format != DIFF_FORMAT_NO_OUTPUT && + orderfile) { + diffcore_order(orderfile); + } + + diff_flush(opt); + } + + opt->output_format = output_format; + opt->orderfile = orderfile; + return paths; +} + + +/* + * find set of paths that everybody touches, assuming diff is run without + * rename/copy detection, etc, comparing all trees simultaneously (= faster). + */ +static struct combine_diff_path *find_paths_multitree( + const unsigned char *sha1, const struct sha1_array *parents, + struct diff_options *opt) +{ + int i, nparent = parents->nr; + const unsigned char **parents_sha1; + struct combine_diff_path paths_head; + struct strbuf base; + + parents_sha1 = xmalloc(nparent * sizeof(parents_sha1[0])); + for (i = 0; i < nparent; i++) + parents_sha1[i] = parents->sha1[i]; + + /* fake list head, so worker can assume it is non-NULL */ + paths_head.next = NULL; + + strbuf_init(&base, PATH_MAX); + diff_tree_paths(&paths_head, sha1, parents_sha1, nparent, &base, opt); + + strbuf_release(&base); + free(parents_sha1); + return paths_head.next; +} + + void diff_tree_combined(const unsigned char *sha1, const struct sha1_array *parents, int dense, @@ -1308,50 +1383,83 @@ void diff_tree_combined(const unsigned char *sha1, { struct diff_options *opt = &rev->diffopt; struct diff_options diffopts; - struct combine_diff_path *p, *paths = NULL; + struct combine_diff_path *p, *paths; int i, num_paths, needsep, show_log_first, num_parent = parents->nr; + int need_generic_pathscan; + + /* nothing to do, if no parents */ + if (!num_parent) + return; + + show_log_first = !!rev->loginfo && !rev->no_commit_id; + needsep = 0; + if (show_log_first) { + show_log(rev); + + if (rev->verbose_header && opt->output_format && + opt->output_format != DIFF_FORMAT_NO_OUTPUT) + printf("%s%c", diff_line_prefix(opt), + opt->line_termination); + } diffopts = *opt; copy_pathspec(&diffopts.pathspec, &opt->pathspec); - diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; DIFF_OPT_SET(&diffopts, RECURSIVE); DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL); - /* tell diff_tree to emit paths in sorted (=tree) order */ - diffopts.orderfile = NULL; - show_log_first = !!rev->loginfo && !rev->no_commit_id; - needsep = 0; - /* find set of paths that everybody touches */ - for (i = 0; i < num_parent; i++) { - /* show stat against the first parent even + /* find set of paths that everybody touches + * + * NOTE + * + * Diffcore transformations are bound to diff_filespec and logic + * comparing two entries - i.e. they do not apply directly to combine + * diff. + * + * If some of such transformations is requested - we launch generic + * path scanning, which works significantly slower compared to + * simultaneous all-trees-in-one-go scan in find_paths_multitree(). + * + * TODO some of the filters could be ported to work on + * combine_diff_paths - i.e. all functionality that skips paths, so in + * theory, we could end up having only multitree path scanning. + * + * NOTE please keep this semantically in sync with diffcore_std() + */ + need_generic_pathscan = opt->skip_stat_unmatch || + DIFF_OPT_TST(opt, FOLLOW_RENAMES) || + opt->break_opt != -1 || + opt->detect_rename || + opt->pickaxe || + opt->filter; + + + if (need_generic_pathscan) { + /* + * NOTE generic case also handles --stat, as it computes + * diff(sha1,parent_i) for all i to do the job, specifically + * for parent0. + */ + paths = find_paths_generic(sha1, parents, &diffopts); + } + else { + int stat_opt; + paths = find_paths_multitree(sha1, parents, &diffopts); + + /* + * show stat against the first parent even * when doing combined diff. */ - int stat_opt = (opt->output_format & + stat_opt = (opt->output_format & (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT)); - if (i == 0 && stat_opt) + if (stat_opt) { diffopts.output_format = stat_opt; - else - diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_tree_sha1(parents->sha1[i], sha1, "", &diffopts); - diffcore_std(&diffopts); - paths = intersect_paths(paths, i, num_parent); - if (show_log_first && i == 0) { - show_log(rev); - - if (rev->verbose_header && opt->output_format && - opt->output_format != DIFF_FORMAT_NO_OUTPUT) - printf("%s%c", diff_line_prefix(opt), - opt->line_termination); + diff_tree_sha1(parents->sha1[0], sha1, "", &diffopts); + diffcore_std(&diffopts); + if (opt->orderfile) + diffcore_order(opt->orderfile); + diff_flush(&diffopts); } - - /* if showing diff, show it in requested order */ - if (diffopts.output_format != DIFF_FORMAT_NO_OUTPUT && - opt->orderfile) { - diffcore_order(opt->orderfile); - } - - diff_flush(&diffopts); } /* find out number of surviving paths */ diff --git a/command-list.txt b/command-list.txt index cf36c3d7..a3ff0c9e 100644 --- a/command-list.txt +++ b/command-list.txt @@ -132,6 +132,7 @@ git-update-server-info synchingrepositories git-upload-archive synchelpers git-upload-pack synchelpers git-var plumbinginterrogators +git-verify-commit ancillaryinterrogators git-verify-pack plumbinginterrogators git-verify-tag ancillaryinterrogators gitweb ancillaryinterrogators @@ -430,12 +430,7 @@ struct commit_list *copy_commit_list(struct commit_list *list) struct commit_list *head = NULL; struct commit_list **pp = &head; while (list) { - struct commit_list *new; - new = xmalloc(sizeof(struct commit_list)); - new->item = list->item; - new->next = NULL; - *pp = new; - pp = &new->next; + pp = commit_list_append(list->item, pp); list = list->next; } return head; @@ -597,8 +592,7 @@ static void record_author_date(struct author_date_slab *author_date, for (buf = buffer; buf; buf = line_end + 1) { line_end = strchrnul(buf, '\n'); - ident_line = skip_prefix(buf, "author "); - if (!ident_line) { + if (!skip_prefix(buf, "author ", &ident_line)) { if (!line_end[0] || line_end[1] == '\n') return; /* end of header */ continue; @@ -770,45 +764,41 @@ void sort_in_topological_order(struct commit_list **list, enum rev_sort_order so static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT); -static struct commit *interesting(struct commit_list *list) +static int queue_has_nonstale(struct prio_queue *queue) { - while (list) { - struct commit *commit = list->item; - list = list->next; - if (commit->object.flags & STALE) - continue; - return commit; + int i; + for (i = 0; i < queue->nr; i++) { + struct commit *commit = queue->array[i].data; + if (!(commit->object.flags & STALE)) + return 1; } - return NULL; + return 0; } /* all input commits in one and twos[] must have been parsed! */ static struct commit_list *paint_down_to_common(struct commit *one, int n, struct commit **twos) { - struct commit_list *list = NULL; + struct prio_queue queue = { compare_commits_by_commit_date }; struct commit_list *result = NULL; int i; one->object.flags |= PARENT1; - commit_list_insert_by_date(one, &list); - if (!n) - return list; + if (!n) { + commit_list_append(one, &result); + return result; + } + prio_queue_put(&queue, one); + for (i = 0; i < n; i++) { twos[i]->object.flags |= PARENT2; - commit_list_insert_by_date(twos[i], &list); + prio_queue_put(&queue, twos[i]); } - while (interesting(list)) { - struct commit *commit; + while (queue_has_nonstale(&queue)) { + struct commit *commit = prio_queue_get(&queue); struct commit_list *parents; - struct commit_list *next; int flags; - commit = list->item; - next = list->next; - free(list); - list = next; - flags = commit->object.flags & (PARENT1 | PARENT2 | STALE); if (flags == (PARENT1 | PARENT2)) { if (!(commit->object.flags & RESULT)) { @@ -827,11 +817,11 @@ static struct commit_list *paint_down_to_common(struct commit *one, int n, struc if (parse_commit(p)) return NULL; p->object.flags |= flags; - commit_list_insert_by_date(p, &list); + prio_queue_put(&queue, p); } } - free_commit_list(list); + clear_prio_queue(&queue); return result; } @@ -976,12 +966,7 @@ struct commit_list *get_merge_bases_many(struct commit *one, } /* There are more than one */ - cnt = 0; - list = result; - while (list) { - list = list->next; - cnt++; - } + cnt = commit_list_count(result); rslt = xcalloc(cnt, sizeof(*rslt)); for (list = result, i = 0; list; list = list->next) rslt[i++] = list->item; @@ -1161,6 +1146,40 @@ int parse_signed_commit(const struct commit *commit, return saw_signature; } +int remove_signature(struct strbuf *buf) +{ + const char *line = buf->buf; + const char *tail = buf->buf + buf->len; + int in_signature = 0; + const char *sig_start = NULL; + const char *sig_end = NULL; + + while (line < tail) { + const char *next = memchr(line, '\n', tail - line); + next = next ? next + 1 : tail; + + if (in_signature && line[0] == ' ') + sig_end = next; + else if (starts_with(line, gpg_sig_header) && + line[gpg_sig_header_len] == ' ') { + sig_start = line; + sig_end = next; + in_signature = 1; + } else { + if (*line == '\n') + /* dump the whole remainder of the buffer */ + next = tail; + in_signature = 0; + } + line = next; + } + + if (sig_start) + strbuf_remove(buf, sig_start - buf->buf, sig_end - sig_start); + + return sig_start != NULL; +} + static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail) { struct merge_remote_desc *desc; @@ -1220,8 +1239,7 @@ static void parse_gpg_output(struct signature_check *sigc) for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) { const char *found, *next; - found = skip_prefix(buf, sigcheck_gpg_status[i].check + 1); - if (!found) { + if (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) { found = strstr(buf, sigcheck_gpg_status[i].check); if (!found) continue; @@ -1255,6 +1273,7 @@ void check_commit_signature(const struct commit* commit, struct signature_check &gpg_output, &gpg_status); if (status && !gpg_output.len) goto out; + sigc->payload = strbuf_detach(&payload, NULL); sigc->gpg_output = strbuf_detach(&gpg_output, NULL); sigc->gpg_status = strbuf_detach(&gpg_status, NULL); parse_gpg_output(sigc); @@ -1299,6 +1318,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit, return extra; } +void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data) +{ + struct commit_extra_header *extra, *to_free; + + to_free = read_commit_extra_headers(commit, NULL); + for (extra = to_free; extra; extra = extra->next) { + if (strcmp(extra->key, "mergetag")) + continue; /* not a merge tag */ + fn(commit, extra, data); + } + free_commit_extra_headers(to_free); +} + static inline int standard_header_field(const char *field, size_t len) { return ((len == 4 && !memcmp(field, "tree ", 5)) || @@ -271,6 +271,7 @@ extern void assign_shallow_commits_to_refs(struct shallow_info *info, int *ref_status); extern int delayed_reachability_test(struct shallow_info *si, int c); extern void prune_shallow(int show_only); +extern struct trace_key trace_shallow; int is_descendant_of(struct commit *, struct commit_list *); int in_merge_bases(struct commit *, struct commit *); @@ -312,6 +313,11 @@ extern struct commit_extra_header *read_commit_extra_headers(struct commit *, co extern void free_commit_extra_headers(struct commit_extra_header *extra); +typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra, + void *cb_data); + +extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data); + struct merge_remote_desc { struct object *obj; /* the named object, could be a tag */ const char *name; @@ -327,6 +333,8 @@ struct commit *get_merge_parent(const char *name); extern int parse_signed_commit(const struct commit *commit, struct strbuf *message, struct strbuf *signature); +extern int remove_signature(struct strbuf *buf); + extern void print_commit_list(struct commit_list *list, const char *format_cur, const char *format_last); diff --git a/compat/mingw.c b/compat/mingw.c index 363a957a..c5c37e53 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1,8 +1,10 @@ #include "../git-compat-util.h" #include "win32.h" #include <conio.h> +#include <wchar.h> #include "../strbuf.h" #include "../run-command.h" +#include "../cache.h" static const int delay[] = { 0, 1, 10, 20, 40 }; @@ -198,14 +200,16 @@ static int ask_yes_no_if_possible(const char *format, ...) } } -#undef unlink int mingw_unlink(const char *pathname) { int ret, tries = 0; + wchar_t wpathname[MAX_PATH]; + if (xutftowcs_path(wpathname, pathname) < 0) + return -1; /* read-only files cannot be removed */ - chmod(pathname, 0666); - while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) { + _wchmod(wpathname, 0666); + while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) { if (!is_file_in_use_error(GetLastError())) break; /* @@ -221,45 +225,45 @@ int mingw_unlink(const char *pathname) while (ret == -1 && is_file_in_use_error(GetLastError()) && ask_yes_no_if_possible("Unlink of file '%s' failed. " "Should I try again?", pathname)) - ret = unlink(pathname); + ret = _wunlink(wpathname); return ret; } -static int is_dir_empty(const char *path) +static int is_dir_empty(const wchar_t *wpath) { - struct strbuf buf = STRBUF_INIT; - WIN32_FIND_DATAA findbuf; + WIN32_FIND_DATAW findbuf; HANDLE handle; - - strbuf_addf(&buf, "%s\\*", path); - handle = FindFirstFileA(buf.buf, &findbuf); - if (handle == INVALID_HANDLE_VALUE) { - strbuf_release(&buf); + wchar_t wbuf[MAX_PATH + 2]; + wcscpy(wbuf, wpath); + wcscat(wbuf, L"\\*"); + handle = FindFirstFileW(wbuf, &findbuf); + if (handle == INVALID_HANDLE_VALUE) return GetLastError() == ERROR_NO_MORE_FILES; - } - while (!strcmp(findbuf.cFileName, ".") || - !strcmp(findbuf.cFileName, "..")) - if (!FindNextFile(handle, &findbuf)) { - strbuf_release(&buf); - return GetLastError() == ERROR_NO_MORE_FILES; + while (!wcscmp(findbuf.cFileName, L".") || + !wcscmp(findbuf.cFileName, L"..")) + if (!FindNextFileW(handle, &findbuf)) { + DWORD err = GetLastError(); + FindClose(handle); + return err == ERROR_NO_MORE_FILES; } FindClose(handle); - strbuf_release(&buf); return 0; } -#undef rmdir int mingw_rmdir(const char *pathname) { int ret, tries = 0; + wchar_t wpathname[MAX_PATH]; + if (xutftowcs_path(wpathname, pathname) < 0) + return -1; - while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) { + while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) { if (!is_file_in_use_error(GetLastError())) errno = err_win_to_posix(GetLastError()); if (errno != EACCES) break; - if (!is_dir_empty(pathname)) { + if (!is_dir_empty(wpathname)) { errno = ENOTEMPTY; break; } @@ -276,16 +280,26 @@ int mingw_rmdir(const char *pathname) while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) && ask_yes_no_if_possible("Deletion of directory '%s' failed. " "Should I try again?", pathname)) - ret = rmdir(pathname); + ret = _wrmdir(wpathname); + return ret; +} + +int mingw_mkdir(const char *path, int mode) +{ + int ret; + wchar_t wpath[MAX_PATH]; + if (xutftowcs_path(wpath, path) < 0) + return -1; + ret = _wmkdir(wpath); return ret; } -#undef open int mingw_open (const char *filename, int oflags, ...) { va_list args; unsigned mode; int fd; + wchar_t wfilename[MAX_PATH]; va_start(args, oflags); mode = va_arg(args, int); @@ -294,10 +308,12 @@ int mingw_open (const char *filename, int oflags, ...) if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; - fd = open(filename, oflags, mode); + if (xutftowcs_path(wfilename, filename) < 0) + return -1; + fd = _wopen(wfilename, oflags, mode); if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) { - DWORD attrs = GetFileAttributes(filename); + DWORD attrs = GetFileAttributesW(wfilename); if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) errno = EISDIR; } @@ -332,17 +348,28 @@ int mingw_fgetc(FILE *stream) #undef fopen FILE *mingw_fopen (const char *filename, const char *otype) { + FILE *file; + wchar_t wfilename[MAX_PATH], wotype[4]; if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; - return fopen(filename, otype); + if (xutftowcs_path(wfilename, filename) < 0 || + xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) + return NULL; + file = _wfopen(wfilename, wotype); + return file; } -#undef freopen FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) { + FILE *file; + wchar_t wfilename[MAX_PATH], wotype[4]; if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; - return freopen(filename, otype, stream); + if (xutftowcs_path(wfilename, filename) < 0 || + xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) + return NULL; + file = _wfreopen(wfilename, wotype, stream); + return file; } #undef fflush @@ -367,6 +394,31 @@ int mingw_fflush(FILE *stream) return ret; } +int mingw_access(const char *filename, int mode) +{ + wchar_t wfilename[MAX_PATH]; + if (xutftowcs_path(wfilename, filename) < 0) + return -1; + /* X_OK is not supported by the MSVCRT version */ + return _waccess(wfilename, mode & ~X_OK); +} + +int mingw_chdir(const char *dirname) +{ + wchar_t wdirname[MAX_PATH]; + if (xutftowcs_path(wdirname, dirname) < 0) + return -1; + return _wchdir(wdirname); +} + +int mingw_chmod(const char *filename, int mode) +{ + wchar_t wfilename[MAX_PATH]; + if (xutftowcs_path(wfilename, filename) < 0) + return -1; + return _wchmod(wfilename, mode); +} + /* * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. @@ -392,10 +444,12 @@ static inline time_t filetime_to_time_t(const FILETIME *ft) */ static int do_lstat(int follow, const char *file_name, struct stat *buf) { - int err; WIN32_FILE_ATTRIBUTE_DATA fdata; + wchar_t wfilename[MAX_PATH]; + if (xutftowcs_path(wfilename, file_name) < 0) + return -1; - if (!(err = get_file_attr(file_name, &fdata))) { + if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) { buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; @@ -408,8 +462,8 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - WIN32_FIND_DATAA findbuf; - HANDLE handle = FindFirstFileA(file_name, &findbuf); + WIN32_FIND_DATAW findbuf; + HANDLE handle = FindFirstFileW(wfilename, &findbuf); if (handle != INVALID_HANDLE_VALUE) { if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { @@ -428,7 +482,23 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) } return 0; } - errno = err; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_SHARING_BUFFER_EXCEEDED: + errno = EACCES; + break; + case ERROR_BUFFER_OVERFLOW: + errno = ENAMETOOLONG; + break; + case ERROR_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + break; + default: + errno = ENOENT; + break; + } return -1; } @@ -441,7 +511,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) static int do_stat_internal(int follow, const char *file_name, struct stat *buf) { int namelen; - static char alt_name[PATH_MAX]; + char alt_name[PATH_MAX]; if (!do_lstat(follow, file_name, buf)) return 0; @@ -516,16 +586,20 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) { FILETIME mft, aft; int fh, rc; + DWORD attrs; + wchar_t wfilename[MAX_PATH]; + if (xutftowcs_path(wfilename, file_name) < 0) + return -1; /* must have write permission */ - DWORD attrs = GetFileAttributes(file_name); + attrs = GetFileAttributesW(wfilename); if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_READONLY)) { /* ignore errors here; open() will report them */ - SetFileAttributes(file_name, attrs & ~FILE_ATTRIBUTE_READONLY); + SetFileAttributesW(wfilename, attrs & ~FILE_ATTRIBUTE_READONLY); } - if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0) { + if ((fh = _wopen(wfilename, O_RDWR | O_BINARY)) < 0) { rc = -1; goto revert_attrs; } @@ -548,7 +622,7 @@ revert_attrs: if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_READONLY)) { /* ignore errors again */ - SetFileAttributes(file_name, attrs); + SetFileAttributesW(wfilename, attrs); } return rc; } @@ -559,6 +633,18 @@ unsigned int sleep (unsigned int seconds) return 0; } +char *mingw_mktemp(char *template) +{ + wchar_t wtemplate[MAX_PATH]; + if (xutftowcs_path(wtemplate, template) < 0) + return NULL; + if (!_wmktemp(wtemplate)) + return NULL; + if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0) + return NULL; + return template; +} + int mkstemp(char *template) { char *filename = mktemp(template); @@ -617,17 +703,18 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) return result; } -#undef getcwd char *mingw_getcwd(char *pointer, int len) { int i; - char *ret = getcwd(pointer, len); - if (!ret) - return ret; + wchar_t wpointer[MAX_PATH]; + if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer))) + return NULL; + if (xwcstoutf(pointer, wpointer, len) < 0) + return NULL; for (i = 0; pointer[i]; i++) if (pointer[i] == '\\') pointer[i] = '/'; - return ret; + return pointer; } /* @@ -812,11 +899,44 @@ static char *path_lookup(const char *cmd, char **path, int exe_only) return prog; } -static int env_compare(const void *a, const void *b) +static int do_putenv(char **env, const char *name, int size, int free_old); + +/* used number of elements of environ array, including terminating NULL */ +static int environ_size = 0; +/* allocated size of environ array, in bytes */ +static int environ_alloc = 0; + +/* + * Create environment block suitable for CreateProcess. Merges current + * process environment and the supplied environment changes. + */ +static wchar_t *make_environment_block(char **deltaenv) { - char *const *ea = a; - char *const *eb = b; - return strcasecmp(*ea, *eb); + wchar_t *wenvblk = NULL; + char **tmpenv; + int i = 0, size = environ_size, wenvsz = 0, wenvpos = 0; + + while (deltaenv && deltaenv[i]) + i++; + + /* copy the environment, leaving space for changes */ + tmpenv = xmalloc((size + i) * sizeof(char*)); + memcpy(tmpenv, environ, size * sizeof(char*)); + + /* merge supplied environment changes into the temporary environment */ + for (i = 0; deltaenv && deltaenv[i]; i++) + size = do_putenv(tmpenv, deltaenv[i], size, 0); + + /* create environment block from temporary environment */ + for (i = 0; tmpenv[i]; i++) { + size = 2 * strlen(tmpenv[i]) + 2; /* +2 for final \0 */ + ALLOC_GROW(wenvblk, (wenvpos + size) * sizeof(wchar_t), wenvsz); + wenvpos += xutftowcs(&wenvblk[wenvpos], tmpenv[i], size) + 1; + } + /* add final \0 terminator */ + wenvblk[wenvpos] = 0; + free(tmpenv); + return wenvblk; } struct pinfo_t { @@ -827,14 +947,15 @@ struct pinfo_t { static struct pinfo_t *pinfo = NULL; CRITICAL_SECTION pinfo_cs; -static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, +static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv, const char *dir, int prepend_cmd, int fhin, int fhout, int fherr) { - STARTUPINFO si; + STARTUPINFOW si; PROCESS_INFORMATION pi; - struct strbuf envblk, args; - unsigned flags; + struct strbuf args; + wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL; + unsigned flags = CREATE_UNICODE_ENVIRONMENT; BOOL ret; /* Determine whether or not we are associated to a console */ @@ -851,7 +972,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, * instead of CREATE_NO_WINDOW to make ssh * recognize that it has no console. */ - flags = DETACHED_PROCESS; + flags |= DETACHED_PROCESS; } else { /* There is already a console. If we specified * DETACHED_PROCESS here, too, Windows would @@ -859,15 +980,19 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, * The same is true for CREATE_NO_WINDOW. * Go figure! */ - flags = 0; CloseHandle(cons); } memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = (HANDLE) _get_osfhandle(fhin); - si.hStdOutput = (HANDLE) _get_osfhandle(fhout); - si.hStdError = (HANDLE) _get_osfhandle(fherr); + si.hStdInput = winansi_get_osfhandle(fhin); + si.hStdOutput = winansi_get_osfhandle(fhout); + si.hStdError = winansi_get_osfhandle(fherr); + + if (xutftowcs_path(wcmd, cmd) < 0) + return -1; + if (dir && xutftowcs_path(wdir, dir) < 0) + return -1; /* concatenate argv, quoting args as we go */ strbuf_init(&args, 0); @@ -886,33 +1011,18 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, free(quoted); } - if (env) { - int count = 0; - char **e, **sorted_env; - - for (e = env; *e; e++) - count++; - - /* environment must be sorted */ - sorted_env = xmalloc(sizeof(*sorted_env) * (count + 1)); - memcpy(sorted_env, env, sizeof(*sorted_env) * (count + 1)); - qsort(sorted_env, count, sizeof(*sorted_env), env_compare); + wargs = xmalloc((2 * args.len + 1) * sizeof(wchar_t)); + xutftowcs(wargs, args.buf, 2 * args.len + 1); + strbuf_release(&args); - strbuf_init(&envblk, 0); - for (e = sorted_env; *e; e++) { - strbuf_addstr(&envblk, *e); - strbuf_addch(&envblk, '\0'); - } - free(sorted_env); - } + wenvblk = make_environment_block(deltaenv); memset(&pi, 0, sizeof(pi)); - ret = CreateProcess(cmd, args.buf, NULL, NULL, TRUE, flags, - env ? envblk.buf : NULL, dir, &si, &pi); + ret = CreateProcessW(wcmd, wargs, NULL, NULL, TRUE, flags, + wenvblk, dir ? wdir : NULL, &si, &pi); - if (env) - strbuf_release(&envblk); - strbuf_release(&args); + free(wenvblk); + free(wargs); if (!ret) { errno = ENOENT; @@ -941,13 +1051,12 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, return (pid_t)pi.dwProcessId; } -static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, - int prepend_cmd) +static pid_t mingw_spawnv(const char *cmd, const char **argv, int prepend_cmd) { - return mingw_spawnve_fd(cmd, argv, env, NULL, prepend_cmd, 0, 1, 2); + return mingw_spawnve_fd(cmd, argv, NULL, NULL, prepend_cmd, 0, 1, 2); } -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **deltaenv, const char *dir, int fhin, int fhout, int fherr) { @@ -971,14 +1080,14 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, pid = -1; } else { - pid = mingw_spawnve_fd(iprog, argv, env, dir, 1, + pid = mingw_spawnve_fd(iprog, argv, deltaenv, dir, 1, fhin, fhout, fherr); free(iprog); } argv[0] = argv0; } else - pid = mingw_spawnve_fd(prog, argv, env, dir, 0, + pid = mingw_spawnve_fd(prog, argv, deltaenv, dir, 0, fhin, fhout, fherr); free(prog); } @@ -986,7 +1095,7 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, return pid; } -static int try_shell_exec(const char *cmd, char *const *argv, char **env) +static int try_shell_exec(const char *cmd, char *const *argv) { const char *interpr = parse_interpreter(cmd); char **path; @@ -1004,7 +1113,7 @@ static int try_shell_exec(const char *cmd, char *const *argv, char **env) argv2 = xmalloc(sizeof(*argv) * (argc+1)); argv2[0] = (char *)cmd; /* full path to the script file */ memcpy(&argv2[1], &argv[1], sizeof(*argv) * argc); - pid = mingw_spawnve(prog, argv2, env, 1); + pid = mingw_spawnv(prog, argv2, 1); if (pid >= 0) { int status; if (waitpid(pid, &status, 0) < 0) @@ -1019,19 +1128,20 @@ static int try_shell_exec(const char *cmd, char *const *argv, char **env) return pid; } -static void mingw_execve(const char *cmd, char *const *argv, char *const *env) +int mingw_execv(const char *cmd, char *const *argv) { /* check if git_command is a shell script */ - if (!try_shell_exec(cmd, argv, (char **)env)) { + if (!try_shell_exec(cmd, argv)) { int pid, status; - pid = mingw_spawnve(cmd, (const char **)argv, (char **)env, 0); + pid = mingw_spawnv(cmd, (const char **)argv, 0); if (pid < 0) - return; + return -1; if (waitpid(pid, &status, 0) < 0) status = 255; exit(status); } + return -1; } int mingw_execvp(const char *cmd, char *const *argv) @@ -1040,7 +1150,7 @@ int mingw_execvp(const char *cmd, char *const *argv) char *prog = path_lookup(cmd, path, 0); if (prog) { - mingw_execve(prog, argv, environ); + mingw_execv(prog, argv); free(prog); } else errno = ENOENT; @@ -1049,12 +1159,6 @@ int mingw_execvp(const char *cmd, char *const *argv) return -1; } -int mingw_execv(const char *cmd, char *const *argv) -{ - mingw_execve(cmd, argv, environ); - return -1; -} - int mingw_kill(pid_t pid, int sig) { if (pid > 0 && sig == SIGTERM) { @@ -1080,108 +1184,88 @@ int mingw_kill(pid_t pid, int sig) return -1; } -static char **copy_environ(void) -{ - char **env; - int i = 0; - while (environ[i]) - i++; - env = xmalloc((i+1)*sizeof(*env)); - for (i = 0; environ[i]; i++) - env[i] = xstrdup(environ[i]); - env[i] = NULL; - return env; -} - -void free_environ(char **env) -{ - int i; - for (i = 0; env[i]; i++) - free(env[i]); - free(env); +/* + * Compare environment entries by key (i.e. stopping at '=' or '\0'). + */ +static int compareenv(const void *v1, const void *v2) +{ + const char *e1 = *(const char**)v1; + const char *e2 = *(const char**)v2; + + for (;;) { + int c1 = *e1++; + int c2 = *e2++; + c1 = (c1 == '=') ? 0 : tolower(c1); + c2 = (c2 == '=') ? 0 : tolower(c2); + if (c1 > c2) + return 1; + if (c1 < c2) + return -1; + if (c1 == 0) + return 0; + } } -static int lookup_env(char **env, const char *name, size_t nmln) +static int bsearchenv(char **env, const char *name, size_t size) { - int i; - - for (i = 0; env[i]; i++) { - if (0 == strncmp(env[i], name, nmln) - && '=' == env[i][nmln]) - /* matches */ - return i; + unsigned low = 0, high = size; + while (low < high) { + unsigned mid = low + ((high - low) >> 1); + int cmp = compareenv(&env[mid], &name); + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid; + else + return mid; } - return -1; + return ~low; /* not found, return 1's complement of insert position */ } /* * If name contains '=', then sets the variable, otherwise it unsets it + * Size includes the terminating NULL. Env must have room for size + 1 entries + * (in case of insert). Returns the new size. Optionally frees removed entries. */ -static char **env_setenv(char **env, const char *name) +static int do_putenv(char **env, const char *name, int size, int free_old) { - char *eq = strchrnul(name, '='); - int i = lookup_env(env, name, eq-name); + int i = bsearchenv(env, name, size - 1); - if (i < 0) { - if (*eq) { - for (i = 0; env[i]; i++) - ; - env = xrealloc(env, (i+2)*sizeof(*env)); - env[i] = xstrdup(name); - env[i+1] = NULL; - } - } - else { + /* optionally free removed / replaced entry */ + if (i >= 0 && free_old) free(env[i]); - if (*eq) - env[i] = xstrdup(name); - else - for (; env[i]; i++) - env[i] = env[i+1]; - } - return env; -} -/* - * Copies global environ and adjusts variables as specified by vars. - */ -char **make_augmented_environ(const char *const *vars) -{ - char **env = copy_environ(); - - while (*vars) - env = env_setenv(env, *vars++); - return env; + if (strchr(name, '=')) { + /* if new value ('key=value') is specified, insert or replace entry */ + if (i < 0) { + i = ~i; + memmove(&env[i + 1], &env[i], (size - i) * sizeof(char*)); + size++; + } + env[i] = (char*) name; + } else if (i >= 0) { + /* otherwise ('key') remove existing entry */ + size--; + memmove(&env[i], &env[i + 1], (size - i) * sizeof(char*)); + } + return size; } -#undef getenv - -/* - * The system's getenv looks up the name in a case-insensitive manner. - * This version tries a case-sensitive lookup and falls back to - * case-insensitive if nothing was found. This is necessary because, - * as a prominent example, CMD sets 'Path', but not 'PATH'. - * Warning: not thread-safe. - */ -static char *getenv_cs(const char *name) +char *mingw_getenv(const char *name) { - size_t len = strlen(name); - int i = lookup_env(environ, name, len); - if (i >= 0) - return environ[i] + len + 1; /* skip past name and '=' */ - return getenv(name); + char *value; + int pos = bsearchenv(environ, name, environ_size - 1); + if (pos < 0) + return NULL; + value = strchr(environ[pos], '='); + return value ? &value[1] : NULL; } -char *mingw_getenv(const char *name) +int mingw_putenv(const char *namevalue) { - char *result = getenv_cs(name); - if (!result && !strcmp(name, "TMPDIR")) { - /* on Windows it is TMP and TEMP */ - result = getenv_cs("TMP"); - if (!result) - result = getenv_cs("TEMP"); - } - return result; + ALLOC_GROW(environ, (environ_size + 1) * sizeof(char*), environ_alloc); + environ_size = do_putenv(environ, namevalue, environ_size, 1); + return 0; } /* @@ -1480,33 +1564,36 @@ int mingw_rename(const char *pold, const char *pnew) { DWORD attrs, gle; int tries = 0; + wchar_t wpold[MAX_PATH], wpnew[MAX_PATH]; + if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0) + return -1; /* * Try native rename() first to get errno right. * It is based on MoveFile(), which cannot overwrite existing files. */ - if (!rename(pold, pnew)) + if (!_wrename(wpold, wpnew)) return 0; if (errno != EEXIST) return -1; repeat: - if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING)) + if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING)) return 0; /* TODO: translate more errors */ gle = GetLastError(); if (gle == ERROR_ACCESS_DENIED && - (attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) { + (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) { if (attrs & FILE_ATTRIBUTE_DIRECTORY) { errno = EISDIR; return -1; } if ((attrs & FILE_ATTRIBUTE_READONLY) && - SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) { - if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING)) + SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) { + if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING)) return 0; gle = GetLastError(); /* revert file attributes on failure */ - SetFileAttributes(pnew, attrs); + SetFileAttributesW(wpnew, attrs); } } if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) { @@ -1752,11 +1839,16 @@ void mingw_open_html(const char *unixpath) int link(const char *oldpath, const char *newpath) { - typedef BOOL (WINAPI *T)(const char*, const char*, LPSECURITY_ATTRIBUTES); + typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); static T create_hard_link = NULL; + wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH]; + if (xutftowcs_path(woldpath, oldpath) < 0 || + xutftowcs_path(wnewpath, newpath) < 0) + return -1; + if (!create_hard_link) { create_hard_link = (T) GetProcAddress( - GetModuleHandle("kernel32.dll"), "CreateHardLinkA"); + GetModuleHandle("kernel32.dll"), "CreateHardLinkW"); if (!create_hard_link) create_hard_link = (T)-1; } @@ -1764,7 +1856,7 @@ int link(const char *oldpath, const char *newpath) errno = ENOSYS; return -1; } - if (!create_hard_link(newpath, oldpath, NULL)) { + if (!create_hard_link(wnewpath, woldpath, NULL)) { errno = err_win_to_posix(GetLastError()); return -1; } @@ -1822,3 +1914,217 @@ pid_t waitpid(pid_t pid, int *status, int options) errno = EINVAL; return -1; } + +int mingw_offset_1st_component(const char *path) +{ + int offset = 0; + if (has_dos_drive_prefix(path)) + offset = 2; + + /* unc paths */ + else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) { + + /* skip server name */ + char *pos = strpbrk(path + 2, "\\/"); + if (!pos) + return 0; /* Error: malformed unc path */ + + do { + pos++; + } while (*pos && !is_dir_sep(*pos)); + + offset = pos - path; + } + + return offset + is_dir_sep(path[offset]); +} + +int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen) +{ + int upos = 0, wpos = 0; + const unsigned char *utf = (const unsigned char*) utfs; + if (!utf || !wcs || wcslen < 1) { + errno = EINVAL; + return -1; + } + /* reserve space for \0 */ + wcslen--; + if (utflen < 0) + utflen = INT_MAX; + + while (upos < utflen) { + int c = utf[upos++] & 0xff; + if (utflen == INT_MAX && c == 0) + break; + + if (wpos >= wcslen) { + wcs[wpos] = 0; + errno = ERANGE; + return -1; + } + + if (c < 0x80) { + /* ASCII */ + wcs[wpos++] = c; + } else if (c >= 0xc2 && c < 0xe0 && upos < utflen && + (utf[upos] & 0xc0) == 0x80) { + /* 2-byte utf-8 */ + c = ((c & 0x1f) << 6); + c |= (utf[upos++] & 0x3f); + wcs[wpos++] = c; + } else if (c >= 0xe0 && c < 0xf0 && upos + 1 < utflen && + !(c == 0xe0 && utf[upos] < 0xa0) && /* over-long encoding */ + (utf[upos] & 0xc0) == 0x80 && + (utf[upos + 1] & 0xc0) == 0x80) { + /* 3-byte utf-8 */ + c = ((c & 0x0f) << 12); + c |= ((utf[upos++] & 0x3f) << 6); + c |= (utf[upos++] & 0x3f); + wcs[wpos++] = c; + } else if (c >= 0xf0 && c < 0xf5 && upos + 2 < utflen && + wpos + 1 < wcslen && + !(c == 0xf0 && utf[upos] < 0x90) && /* over-long encoding */ + !(c == 0xf4 && utf[upos] >= 0x90) && /* > \u10ffff */ + (utf[upos] & 0xc0) == 0x80 && + (utf[upos + 1] & 0xc0) == 0x80 && + (utf[upos + 2] & 0xc0) == 0x80) { + /* 4-byte utf-8: convert to \ud8xx \udcxx surrogate pair */ + c = ((c & 0x07) << 18); + c |= ((utf[upos++] & 0x3f) << 12); + c |= ((utf[upos++] & 0x3f) << 6); + c |= (utf[upos++] & 0x3f); + c -= 0x10000; + wcs[wpos++] = 0xd800 | (c >> 10); + wcs[wpos++] = 0xdc00 | (c & 0x3ff); + } else if (c >= 0xa0) { + /* invalid utf-8 byte, printable unicode char: convert 1:1 */ + wcs[wpos++] = c; + } else { + /* invalid utf-8 byte, non-printable unicode: convert to hex */ + static const char *hex = "0123456789abcdef"; + wcs[wpos++] = hex[c >> 4]; + if (wpos < wcslen) + wcs[wpos++] = hex[c & 0x0f]; + } + } + wcs[wpos] = 0; + return wpos; +} + +int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen) +{ + if (!wcs || !utf || utflen < 1) { + errno = EINVAL; + return -1; + } + utflen = WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf, utflen, NULL, NULL); + if (utflen) + return utflen - 1; + errno = ERANGE; + return -1; +} + +/* + * Disable MSVCRT command line wildcard expansion (__getmainargs called from + * mingw startup code, see init.c in mingw runtime). + */ +int _CRT_glob = 0; + +typedef struct { + int newmode; +} _startupinfo; + +extern int __wgetmainargs(int *argc, wchar_t ***argv, wchar_t ***env, int glob, + _startupinfo *si); + +static NORETURN void die_startup() +{ + fputs("fatal: not enough memory for initialization", stderr); + exit(128); +} + +static void *malloc_startup(size_t size) +{ + void *result = malloc(size); + if (!result) + die_startup(); + return result; +} + +static char *wcstoutfdup_startup(char *buffer, const wchar_t *wcs, size_t len) +{ + len = xwcstoutf(buffer, wcs, len) + 1; + return memcpy(malloc_startup(len), buffer, len); +} + +void mingw_startup() +{ + int i, maxlen, argc; + char *buffer; + wchar_t **wenv, **wargv; + _startupinfo si; + + /* get wide char arguments and environment */ + si.newmode = 0; + if (__wgetmainargs(&argc, &wargv, &wenv, _CRT_glob, &si) < 0) + die_startup(); + + /* determine size of argv and environ conversion buffer */ + maxlen = wcslen(_wpgmptr); + for (i = 1; i < argc; i++) + maxlen = max(maxlen, wcslen(wargv[i])); + for (i = 0; wenv[i]; i++) + maxlen = max(maxlen, wcslen(wenv[i])); + + /* + * nedmalloc can't free CRT memory, allocate resizable environment + * list. Note that xmalloc / xmemdupz etc. call getenv, so we cannot + * use it while initializing the environment itself. + */ + environ_size = i + 1; + environ_alloc = alloc_nr(environ_size * sizeof(char*)); + environ = malloc_startup(environ_alloc); + + /* allocate buffer (wchar_t encodes to max 3 UTF-8 bytes) */ + maxlen = 3 * maxlen + 1; + buffer = malloc_startup(maxlen); + + /* convert command line arguments and environment to UTF-8 */ + __argv[0] = wcstoutfdup_startup(buffer, _wpgmptr, maxlen); + for (i = 1; i < argc; i++) + __argv[i] = wcstoutfdup_startup(buffer, wargv[i], maxlen); + for (i = 0; wenv[i]; i++) + environ[i] = wcstoutfdup_startup(buffer, wenv[i], maxlen); + environ[i] = NULL; + free(buffer); + + /* sort environment for O(log n) getenv / putenv */ + qsort(environ, i, sizeof(char*), compareenv); + + /* fix Windows specific environment settings */ + + /* on Windows it is TMP and TEMP */ + if (!mingw_getenv("TMPDIR")) { + const char *tmp = mingw_getenv("TMP"); + if (!tmp) + tmp = mingw_getenv("TEMP"); + if (tmp) + setenv("TMPDIR", tmp, 1); + } + + /* simulate TERM to enable auto-color (see color.c) */ + if (!getenv("TERM")) + setenv("TERM", "cygwin", 1); + + /* initialize critical section for waitpid pinfo_t list */ + InitializeCriticalSection(&pinfo_cs); + + /* set up default file mode and file modes for stdin/out/err */ + _fmode = _O_BINARY; + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); + _setmode(_fileno(stderr), _O_BINARY); + + /* initialize Unicode console */ + winansi_init(); +} diff --git a/compat/mingw.h b/compat/mingw.h index e033e720..df0e3203 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -35,6 +35,9 @@ typedef int socklen_t; #ifndef EWOULDBLOCK #define EWOULDBLOCK EAGAIN #endif +#ifndef ELOOP +#define ELOOP EMLINK +#endif #define SHUT_WR SD_SEND #define SIGHUP 1 @@ -118,10 +121,7 @@ static inline int fcntl(int fd, int cmd, ...) * simple adaptors */ -static inline int mingw_mkdir(const char *path, int mode) -{ - return mkdir(path); -} +int mingw_mkdir(const char *path, int mode); #define mkdir mingw_mkdir #define WNOHANG 1 @@ -192,11 +192,27 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream); int mingw_fflush(FILE *stream); #define fflush mingw_fflush +int mingw_access(const char *filename, int mode); +#undef access +#define access mingw_access + +int mingw_chdir(const char *dirname); +#define chdir mingw_chdir + +int mingw_chmod(const char *filename, int mode); +#define chmod mingw_chmod + +char *mingw_mktemp(char *template); +#define mktemp mingw_mktemp + char *mingw_getcwd(char *pointer, int len); #define getcwd mingw_getcwd char *mingw_getenv(const char *name); #define getenv mingw_getenv +int mingw_putenv(const char *namevalue); +#define putenv mingw_putenv +#define unsetenv mingw_putenv int mingw_gethostname(char *host, int namelen); #define gethostname mingw_gethostname @@ -317,12 +333,8 @@ int mingw_raise(int sig); * ANSI emulation wrappers */ -int winansi_fputs(const char *str, FILE *stream); -int winansi_printf(const char *format, ...) __attribute__((format (printf, 1, 2))); -int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format (printf, 2, 3))); -#define fputs winansi_fputs -#define printf(...) winansi_printf(__VA_ARGS__) -#define fprintf(...) winansi_fprintf(__VA_ARGS__) +void winansi_init(void); +HANDLE winansi_get_osfhandle(int fd); /* * git specific compatibility @@ -339,6 +351,8 @@ static inline char *mingw_find_last_dir_sep(const char *path) return ret; } #define find_last_dir_sep mingw_find_last_dir_sep +int mingw_offset_1st_component(const char *path); +#define offset_1st_component mingw_offset_1st_component #define PATH_SEP ';' #define PRIuMAX "I64u" #define PRId64 "I64d" @@ -346,12 +360,112 @@ static inline char *mingw_find_last_dir_sep(const char *path) void mingw_open_html(const char *path); #define open_html mingw_open_html -/* - * helpers +void mingw_mark_as_git_dir(const char *dir); +#define mark_as_git_dir mingw_mark_as_git_dir + +/** + * Converts UTF-8 encoded string to UTF-16LE. + * + * To support repositories with legacy-encoded file names, invalid UTF-8 bytes + * 0xa0 - 0xff are converted to corresponding printable Unicode chars \u00a0 - + * \u00ff, and invalid UTF-8 bytes 0x80 - 0x9f (which would make non-printable + * Unicode) are converted to hex-code. + * + * Lead-bytes not followed by an appropriate number of trail-bytes, over-long + * encodings and 4-byte encodings > \u10ffff are detected as invalid UTF-8. + * + * Maximum space requirement for the target buffer is two wide chars per UTF-8 + * char (((strlen(utf) * 2) + 1) [* sizeof(wchar_t)]). + * + * The maximum space is needed only if the entire input string consists of + * invalid UTF-8 bytes in range 0x80-0x9f, as per the following table: + * + * | | UTF-8 | UTF-16 | + * Code point | UTF-8 sequence | bytes | words | ratio + * --------------+-------------------+-------+--------+------- + * 000000-00007f | 0-7f | 1 | 1 | 1 + * 000080-0007ff | c2-df + 80-bf | 2 | 1 | 0.5 + * 000800-00ffff | e0-ef + 2 * 80-bf | 3 | 1 | 0.33 + * 010000-10ffff | f0-f4 + 3 * 80-bf | 4 | 2 (a) | 0.5 + * invalid | 80-9f | 1 | 2 (b) | 2 + * invalid | a0-ff | 1 | 1 | 1 + * + * (a) encoded as UTF-16 surrogate pair + * (b) encoded as two hex digits + * + * Note that, while the UTF-8 encoding scheme can be extended to 5-byte, 6-byte + * or even indefinite-byte sequences, the largest valid code point \u10ffff + * encodes as only 4 UTF-8 bytes. + * + * Parameters: + * wcs: wide char target buffer + * utf: string to convert + * wcslen: size of target buffer (in wchar_t's) + * utflen: size of string to convert, or -1 if 0-terminated + * + * Returns: + * length of converted string (_wcslen(wcs)), or -1 on failure + * + * Errors: + * EINVAL: one of the input parameters is invalid (e.g. NULL) + * ERANGE: the output buffer is too small */ +int xutftowcsn(wchar_t *wcs, const char *utf, size_t wcslen, int utflen); -char **make_augmented_environ(const char *const *vars); -void free_environ(char **env); +/** + * Simplified variant of xutftowcsn, assumes input string is \0-terminated. + */ +static inline int xutftowcs(wchar_t *wcs, const char *utf, size_t wcslen) +{ + return xutftowcsn(wcs, utf, wcslen, -1); +} + +/** + * Simplified file system specific variant of xutftowcsn, assumes output + * buffer size is MAX_PATH wide chars and input string is \0-terminated, + * fails with ENAMETOOLONG if input string is too long. + */ +static inline int xutftowcs_path(wchar_t *wcs, const char *utf) +{ + int result = xutftowcsn(wcs, utf, MAX_PATH, -1); + if (result < 0 && errno == ERANGE) + errno = ENAMETOOLONG; + return result; +} + +/** + * Converts UTF-16LE encoded string to UTF-8. + * + * Maximum space requirement for the target buffer is three UTF-8 chars per + * wide char ((_wcslen(wcs) * 3) + 1). + * + * The maximum space is needed only if the entire input string consists of + * UTF-16 words in range 0x0800-0xd7ff or 0xe000-0xffff (i.e. \u0800-\uffff + * modulo surrogate pairs), as per the following table: + * + * | | UTF-16 | UTF-8 | + * Code point | UTF-16 sequence | words | bytes | ratio + * --------------+-----------------------+--------+-------+------- + * 000000-00007f | 0000-007f | 1 | 1 | 1 + * 000080-0007ff | 0080-07ff | 1 | 2 | 2 + * 000800-00ffff | 0800-d7ff / e000-ffff | 1 | 3 | 3 + * 010000-10ffff | d800-dbff + dc00-dfff | 2 | 4 | 2 + * + * Note that invalid code points > 10ffff cannot be represented in UTF-16. + * + * Parameters: + * utf: target buffer + * wcs: wide string to convert + * utflen: size of target buffer + * + * Returns: + * length of converted string, or -1 on failure + * + * Errors: + * EINVAL: one of the input parameters is invalid (e.g. NULL) + * ERANGE: the output buffer is too small + */ +int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen); /* * A critical section used in the implementation of the spawn @@ -361,22 +475,16 @@ void free_environ(char **env); extern CRITICAL_SECTION pinfo_cs; /* - * A replacement of main() that ensures that argv[0] has a path - * and that default fmode and std(in|out|err) are in binary mode + * A replacement of main() that adds win32 specific initialization. */ +void mingw_startup(); #define main(c,v) dummy_decl_mingw_main(); \ static int mingw_main(c,v); \ int main(int argc, char **argv) \ { \ - extern CRITICAL_SECTION pinfo_cs; \ - _fmode = _O_BINARY; \ - _setmode(_fileno(stdin), _O_BINARY); \ - _setmode(_fileno(stdout), _O_BINARY); \ - _setmode(_fileno(stderr), _O_BINARY); \ - argv[0] = xstrdup(_pgmptr); \ - InitializeCriticalSection(&pinfo_cs); \ - return mingw_main(argc, argv); \ + mingw_startup(); \ + return mingw_main(__argc, (void *)__argv); \ } \ static int mingw_main(c,v) diff --git a/compat/poll/poll.c b/compat/poll/poll.c index 31163f2a..a9b41d89 100644 --- a/compat/poll/poll.c +++ b/compat/poll/poll.c @@ -605,7 +605,7 @@ restart: if (!rc && timeout == INFTIM) { - SwitchToThread(); + SleepEx (1, TRUE); goto restart; } diff --git a/compat/vcbuild/include/alloca.h b/compat/win32/alloca.h index c0d7985b..c0d7985b 100644 --- a/compat/vcbuild/include/alloca.h +++ b/compat/win32/alloca.h diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c index 7a0debe5..52420ec7 100644 --- a/compat/win32/dirent.c +++ b/compat/win32/dirent.c @@ -1,96 +1,81 @@ -#include "../git-compat-util.h" -#include "dirent.h" +#include "../../git-compat-util.h" struct DIR { struct dirent dd_dir; /* includes d_type */ HANDLE dd_handle; /* FindFirstFile handle */ int dd_stat; /* 0-based index */ - char dd_name[1]; /* extend struct */ }; +static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata) +{ + /* convert UTF-16 name to UTF-8 */ + xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name)); + + /* Set file type, based on WIN32_FIND_DATA */ + if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + ent->d_type = DT_DIR; + else + ent->d_type = DT_REG; +} + DIR *opendir(const char *name) { - DWORD attrs = GetFileAttributesA(name); + wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */ + WIN32_FIND_DATAW fdata; + HANDLE h; int len; - DIR *p; + DIR *dir; - /* check for valid path */ - if (attrs == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; + /* convert name to UTF-16 and check length < MAX_PATH */ + if ((len = xutftowcs_path(pattern, name)) < 0) return NULL; - } - /* check if it's a directory */ - if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) { - errno = ENOTDIR; + /* append optional '/' and wildcard '*' */ + if (len && !is_dir_sep(pattern[len - 1])) + pattern[len++] = '/'; + pattern[len++] = '*'; + pattern[len] = 0; + + /* open find handle */ + h = FindFirstFileW(pattern, &fdata); + if (h == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err); return NULL; } - /* check that the pattern won't be too long for FindFirstFileA */ - len = strlen(name); - if (is_dir_sep(name[len - 1])) - len--; - if (len + 2 >= MAX_PATH) { - errno = ENAMETOOLONG; - return NULL; - } - - p = malloc(sizeof(DIR) + len + 2); - if (!p) - return NULL; - - memset(p, 0, sizeof(DIR) + len + 2); - strcpy(p->dd_name, name); - p->dd_name[len] = '/'; - p->dd_name[len+1] = '*'; - - p->dd_handle = INVALID_HANDLE_VALUE; - return p; + /* initialize DIR structure and copy first dir entry */ + dir = xmalloc(sizeof(DIR)); + dir->dd_handle = h; + dir->dd_stat = 0; + finddata2dirent(&dir->dd_dir, &fdata); + return dir; } struct dirent *readdir(DIR *dir) { - WIN32_FIND_DATAA buf; - HANDLE handle; - - if (!dir || !dir->dd_handle) { + if (!dir) { errno = EBADF; /* No set_errno for mingw */ return NULL; } - if (dir->dd_handle == INVALID_HANDLE_VALUE && dir->dd_stat == 0) { - DWORD lasterr; - handle = FindFirstFileA(dir->dd_name, &buf); - lasterr = GetLastError(); - dir->dd_handle = handle; - if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) { - errno = err_win_to_posix(lasterr); + /* if first entry, dirent has already been set up by opendir */ + if (dir->dd_stat) { + /* get next entry and convert from WIN32_FIND_DATA to dirent */ + WIN32_FIND_DATAW fdata; + if (FindNextFileW(dir->dd_handle, &fdata)) { + finddata2dirent(&dir->dd_dir, &fdata); + } else { + DWORD lasterr = GetLastError(); + /* POSIX says you shouldn't set errno when readdir can't + find any more files; so, if another error we leave it set. */ + if (lasterr != ERROR_NO_MORE_FILES) + errno = err_win_to_posix(lasterr); return NULL; } - } else if (dir->dd_handle == INVALID_HANDLE_VALUE) { - return NULL; - } else if (!FindNextFileA(dir->dd_handle, &buf)) { - DWORD lasterr = GetLastError(); - FindClose(dir->dd_handle); - dir->dd_handle = INVALID_HANDLE_VALUE; - /* POSIX says you shouldn't set errno when readdir can't - find any more files; so, if another error we leave it set. */ - if (lasterr != ERROR_NO_MORE_FILES) - errno = err_win_to_posix(lasterr); - return NULL; } - /* We get here if `buf' contains valid data. */ - strcpy(dir->dd_dir.d_name, buf.cFileName); ++dir->dd_stat; - - /* Set file type, based on WIN32_FIND_DATA */ - dir->dd_dir.d_type = 0; - if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - dir->dd_dir.d_type |= DT_DIR; - else - dir->dd_dir.d_type |= DT_REG; - return &dir->dd_dir; } @@ -101,8 +86,7 @@ int closedir(DIR *dir) return -1; } - if (dir->dd_handle != INVALID_HANDLE_VALUE) - FindClose(dir->dd_handle); + FindClose(dir->dd_handle); free(dir); return 0; } diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h index 927a25ca..058207e4 100644 --- a/compat/win32/dirent.h +++ b/compat/win32/dirent.h @@ -9,12 +9,8 @@ typedef struct DIR DIR; #define DT_LNK 3 struct dirent { - long d_ino; /* Always zero. */ - char d_name[FILENAME_MAX]; /* File name. */ - union { - unsigned short d_reclen; /* Always zero. */ - unsigned char d_type; /* Reimplementation adds this */ - }; + unsigned char d_type; /* file type to prevent lstat after readdir */ + char d_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */ }; DIR *opendir(const char *dirname); diff --git a/compat/winansi.c b/compat/winansi.c index dedce210..efc5bb3a 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -2,15 +2,10 @@ * Copyright 2008 Peter Harris <git@peter.is-a-geek.org> */ +#undef NOGDI #include "../git-compat-util.h" - -/* - Functions to be wrapped: -*/ -#undef printf -#undef fprintf -#undef fputs -/* TODO: write */ +#include <wingdi.h> +#include <winreg.h> /* ANSI codes used by git: m, K @@ -23,29 +18,114 @@ static HANDLE console; static WORD plain_attr; static WORD attr; static int negative; +static int non_ascii_used = 0; +static HANDLE hthread, hread, hwrite; +static HANDLE hconsole1, hconsole2; + +#ifdef __MINGW32__ +typedef struct _CONSOLE_FONT_INFOEX { + ULONG cbSize; + DWORD nFont; + COORD dwFontSize; + UINT FontFamily; + UINT FontWeight; + WCHAR FaceName[LF_FACESIZE]; +} CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX; +#endif + +typedef BOOL (WINAPI *PGETCURRENTCONSOLEFONTEX)(HANDLE, BOOL, + PCONSOLE_FONT_INFOEX); + +static void warn_if_raster_font(void) +{ + DWORD fontFamily = 0; + PGETCURRENTCONSOLEFONTEX pGetCurrentConsoleFontEx; + + /* don't bother if output was ascii only */ + if (!non_ascii_used) + return; + + /* GetCurrentConsoleFontEx is available since Vista */ + pGetCurrentConsoleFontEx = (PGETCURRENTCONSOLEFONTEX) GetProcAddress( + GetModuleHandle("kernel32.dll"), + "GetCurrentConsoleFontEx"); + if (pGetCurrentConsoleFontEx) { + CONSOLE_FONT_INFOEX cfi; + cfi.cbSize = sizeof(cfi); + if (pGetCurrentConsoleFontEx(console, 0, &cfi)) + fontFamily = cfi.FontFamily; + } else { + /* pre-Vista: check default console font in registry */ + HKEY hkey; + if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console", + 0, KEY_READ, &hkey)) { + DWORD size = sizeof(fontFamily); + RegQueryValueExA(hkey, "FontFamily", NULL, NULL, + (LPVOID) &fontFamily, &size); + RegCloseKey(hkey); + } + } + + if (!(fontFamily & TMPF_TRUETYPE)) { + const wchar_t *msg = L"\nWarning: Your console font probably " + L"doesn\'t support Unicode. If you experience strange " + L"characters in the output, consider switching to a " + L"TrueType font such as Consolas!\n"; + DWORD dummy; + WriteConsoleW(console, msg, wcslen(msg), &dummy, NULL); + } +} -static void init(void) +static int is_console(int fd) { CONSOLE_SCREEN_BUFFER_INFO sbi; + HANDLE hcon; static int initialized = 0; - if (initialized) - return; - console = GetStdHandle(STD_OUTPUT_HANDLE); - if (console == INVALID_HANDLE_VALUE) - console = NULL; + /* get OS handle of the file descriptor */ + hcon = (HANDLE) _get_osfhandle(fd); + if (hcon == INVALID_HANDLE_VALUE) + return 0; - if (!console) - return; + /* check if its a device (i.e. console, printer, serial port) */ + if (GetFileType(hcon) != FILE_TYPE_CHAR) + return 0; - GetConsoleScreenBufferInfo(console, &sbi); - attr = plain_attr = sbi.wAttributes; - negative = 0; + /* check if its a handle to a console output screen buffer */ + if (!GetConsoleScreenBufferInfo(hcon, &sbi)) + return 0; + + /* initialize attributes */ + if (!initialized) { + console = hcon; + attr = plain_attr = sbi.wAttributes; + negative = 0; + initialized = 1; + } - initialized = 1; + return 1; } +#define BUFFER_SIZE 4096 +#define MAX_PARAMS 16 + +static void write_console(unsigned char *str, size_t len) +{ + /* only called from console_thread, so a static buffer will do */ + static wchar_t wbuf[2 * BUFFER_SIZE + 1]; + DWORD dummy; + + /* convert utf-8 to utf-16 */ + int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len); + + /* write directly to console */ + WriteConsoleW(console, wbuf, wlen, &dummy, NULL); + + /* remember if non-ascii characters are printed */ + if (wlen != len) + non_ascii_used = 1; +} #define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) #define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) @@ -90,18 +170,13 @@ static void erase_in_line(void) &dummy); } - -static const char *set_attr(const char *str) +static void set_attr(char func, const int *params, int paramlen) { - const char *func; - size_t len = strspn(str, "0123456789;"); - func = str + len; - - switch (*func) { + int i; + switch (func) { case 'm': - do { - long val = strtol(str, (char **)&str, 10); - switch (val) { + for (i = 0; i < paramlen; i++) { + switch (params[i]) { case 0: /* reset */ attr = plain_attr; negative = 0; @@ -224,9 +299,7 @@ static const char *set_attr(const char *str) /* Unsupported code */ break; } - str++; - } while (*(str-1) == ';'); - + } set_console_attr(); break; case 'K': @@ -236,122 +309,271 @@ static const char *set_attr(const char *str) /* Unsupported code */ break; } - - return func + 1; } -static int ansi_emulate(const char *str, FILE *stream) +enum { + TEXT = 0, ESCAPE = 033, BRACKET = '[' +}; + +static DWORD WINAPI console_thread(LPVOID unused) { - int rv = 0; - const char *pos = str; - - while (*pos) { - pos = strstr(str, "\033["); - if (pos) { - size_t len = pos - str; - - if (len) { - size_t out_len = fwrite(str, 1, len, stream); - rv += out_len; - if (out_len < len) - return rv; + unsigned char buffer[BUFFER_SIZE]; + DWORD bytes; + int start, end = 0, c, parampos = 0, state = TEXT; + int params[MAX_PARAMS]; + + while (1) { + /* read next chunk of bytes from the pipe */ + if (!ReadFile(hread, buffer + end, BUFFER_SIZE - end, &bytes, + NULL)) { + /* exit if pipe has been closed or disconnected */ + if (GetLastError() == ERROR_PIPE_NOT_CONNECTED || + GetLastError() == ERROR_BROKEN_PIPE) + break; + /* ignore other errors */ + continue; + } + + /* scan the bytes and handle ANSI control codes */ + bytes += end; + start = end = 0; + while (end < bytes) { + c = buffer[end++]; + switch (state) { + case TEXT: + if (c == ESCAPE) { + /* print text seen so far */ + if (end - 1 > start) + write_console(buffer + start, + end - 1 - start); + + /* then start parsing escape sequence */ + start = end - 1; + memset(params, 0, sizeof(params)); + parampos = 0; + state = ESCAPE; + } + break; + + case ESCAPE: + /* continue if "\033[", otherwise bail out */ + state = (c == BRACKET) ? BRACKET : TEXT; + break; + + case BRACKET: + /* parse [0-9;]* into array of parameters */ + if (c >= '0' && c <= '9') { + params[parampos] *= 10; + params[parampos] += c - '0'; + } else if (c == ';') { + /* + * next parameter, bail out if out of + * bounds + */ + parampos++; + if (parampos >= MAX_PARAMS) + state = TEXT; + } else { + /* + * end of escape sequence, change + * console attributes + */ + set_attr(c, params, parampos + 1); + start = end; + state = TEXT; + } + break; } + } - str = pos + 2; - rv += 2; + /* print remaining text unless parsing an escape sequence */ + if (state == TEXT && end > start) { + /* check for incomplete UTF-8 sequences and fix end */ + if (buffer[end - 1] >= 0x80) { + if (buffer[end -1] >= 0xc0) + end--; + else if (end - 1 > start && + buffer[end - 2] >= 0xe0) + end -= 2; + else if (end - 2 > start && + buffer[end - 3] >= 0xf0) + end -= 3; + } - fflush(stream); + /* print remaining complete UTF-8 sequences */ + if (end > start) + write_console(buffer + start, end - start); - pos = set_attr(str); - rv += pos - str; - str = pos; + /* move remaining bytes to the front */ + if (end < bytes) + memmove(buffer, buffer + end, bytes - end); + end = bytes - end; } else { - rv += strlen(str); - fputs(str, stream); - return rv; + /* all data has been consumed, mark buffer empty */ + end = 0; } } - return rv; + + /* check if the console font supports unicode */ + warn_if_raster_font(); + + CloseHandle(hread); + return 0; } -int winansi_fputs(const char *str, FILE *stream) +static void winansi_exit(void) { - int rv; - - if (!isatty(fileno(stream))) - return fputs(str, stream); + /* flush all streams */ + _flushall(); - init(); + /* signal console thread to exit */ + FlushFileBuffers(hwrite); + DisconnectNamedPipe(hwrite); - if (!console) - return fputs(str, stream); + /* wait for console thread to copy remaining data */ + WaitForSingleObject(hthread, INFINITE); - rv = ansi_emulate(str, stream); + /* cleanup handles... */ + CloseHandle(hwrite); + CloseHandle(hthread); +} - if (rv >= 0) - return 0; - else - return EOF; +static void die_lasterr(const char *fmt, ...) +{ + va_list params; + va_start(params, fmt); + errno = err_win_to_posix(GetLastError()); + die_errno(fmt, params); + va_end(params); } -static int winansi_vfprintf(FILE *stream, const char *format, va_list list) +static HANDLE duplicate_handle(HANDLE hnd) { - int len, rv; - char small_buf[256]; - char *buf = small_buf; - va_list cp; + HANDLE hresult, hproc = GetCurrentProcess(); + if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE, + DUPLICATE_SAME_ACCESS)) + die_lasterr("DuplicateHandle(%li) failed", (long) hnd); + return hresult; +} - if (!isatty(fileno(stream))) - goto abort; - init(); +/* + * Make MSVCRT's internal file descriptor control structure accessible + * so that we can tweak OS handles and flags directly (we need MSVCRT + * to treat our pipe handle as if it were a console). + * + * We assume that the ioinfo structure (exposed by MSVCRT.dll via + * __pioinfo) starts with the OS handle and the flags. The exact size + * varies between MSVCRT versions, so we try different sizes until + * toggling the FDEV bit of _pioinfo(1)->osflags is reflected in + * isatty(1). + */ +typedef struct { + HANDLE osfhnd; + char osflags; +} ioinfo; - if (!console) - goto abort; +extern __declspec(dllimport) ioinfo *__pioinfo[]; - va_copy(cp, list); - len = vsnprintf(small_buf, sizeof(small_buf), format, cp); - va_end(cp); +static size_t sizeof_ioinfo = 0; - if (len > sizeof(small_buf) - 1) { - buf = malloc(len + 1); - if (!buf) - goto abort; +#define IOINFO_L2E 5 +#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E) - len = vsnprintf(buf, len + 1, format, list); - } +#define FDEV 0x40 - rv = ansi_emulate(buf, stream); +static inline ioinfo* _pioinfo(int fd) +{ + return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] + + (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo); +} - if (buf != small_buf) - free(buf); - return rv; +static int init_sizeof_ioinfo() +{ + int istty, wastty; + /* don't init twice */ + if (sizeof_ioinfo) + return sizeof_ioinfo >= 256; + + sizeof_ioinfo = sizeof(ioinfo); + wastty = isatty(1); + while (sizeof_ioinfo < 256) { + /* toggle FDEV flag, check isatty, then toggle back */ + _pioinfo(1)->osflags ^= FDEV; + istty = isatty(1); + _pioinfo(1)->osflags ^= FDEV; + /* return if we found the correct size */ + if (istty != wastty) + return 0; + sizeof_ioinfo += sizeof(void*); + } + error("Tweaking file descriptors doesn't work with this MSVCRT.dll"); + return 1; +} -abort: - rv = vfprintf(stream, format, list); - return rv; +static HANDLE swap_osfhnd(int fd, HANDLE new_handle) +{ + ioinfo *pioinfo; + HANDLE old_handle; + + /* init ioinfo size if we haven't done so */ + if (init_sizeof_ioinfo()) + return INVALID_HANDLE_VALUE; + + /* get ioinfo pointer and change the handles */ + pioinfo = _pioinfo(fd); + old_handle = pioinfo->osfhnd; + pioinfo->osfhnd = new_handle; + return old_handle; } -int winansi_fprintf(FILE *stream, const char *format, ...) +void winansi_init(void) { - va_list list; - int rv; + int con1, con2; + char name[32]; - va_start(list, format); - rv = winansi_vfprintf(stream, format, list); - va_end(list); + /* check if either stdout or stderr is a console output screen buffer */ + con1 = is_console(1); + con2 = is_console(2); + if (!con1 && !con2) + return; - return rv; + /* create a named pipe to communicate with the console thread */ + sprintf(name, "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId()); + hwrite = CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND, + PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL); + if (hwrite == INVALID_HANDLE_VALUE) + die_lasterr("CreateNamedPipe failed"); + + hread = CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + if (hread == INVALID_HANDLE_VALUE) + die_lasterr("CreateFile for named pipe failed"); + + /* start console spool thread on the pipe's read end */ + hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL); + if (hthread == INVALID_HANDLE_VALUE) + die_lasterr("CreateThread(console_thread) failed"); + + /* schedule cleanup routine */ + if (atexit(winansi_exit)) + die_errno("atexit(winansi_exit) failed"); + + /* redirect stdout / stderr to the pipe */ + if (con1) + hconsole1 = swap_osfhnd(1, duplicate_handle(hwrite)); + if (con2) + hconsole2 = swap_osfhnd(2, duplicate_handle(hwrite)); } -int winansi_printf(const char *format, ...) +/* + * Returns the real console handle if stdout / stderr is a pipe redirecting + * to the console. Allows spawn / exec to pass the console to the next process. + */ +HANDLE winansi_get_osfhandle(int fd) { - va_list list; - int rv; - - va_start(list, format); - rv = winansi_vfprintf(stdout, format, list); - va_end(list); - - return rv; + HANDLE hnd = (HANDLE) _get_osfhandle(fd); + if ((fd == 1 || fd == 2) && isatty(fd) + && GetFileType(hnd) == FILE_TYPE_PIPE) + return (fd == 1) ? hconsole1 : hconsole2; + return hnd; } @@ -138,8 +138,7 @@ int git_config_include(const char *var, const char *value, void *data) if (ret < 0) return ret; - type = skip_prefix(var, "include."); - if (!type) + if (!skip_prefix(var, "include.", &type)) return ret; if (!strcmp(type, "path")) @@ -147,12 +146,6 @@ int git_config_include(const char *var, const char *value, void *data) return ret; } -static void lowercase(char *p) -{ - for (; *p; p++) - *p = tolower(*p); -} - void git_config_push_parameter(const char *text) { struct strbuf env = STRBUF_INIT; @@ -180,7 +173,7 @@ int git_config_parse_parameter(const char *text, strbuf_list_free(pair); return error("bogus config parameter: %s", text); } - lowercase(pair[0]->buf); + strbuf_tolower(pair[0]); if (fn(pair[0]->buf, pair[1] ? pair[1]->buf : NULL, data) < 0) { strbuf_list_free(pair); return -1; @@ -824,11 +817,16 @@ static int git_default_core_config(const char *var, const char *value) return git_config_string(&editor_program, var, value); if (!strcmp(var, "core.commentchar")) { - const char *comment; - int ret = git_config_string(&comment, var, value); - if (!ret) - comment_line_char = comment[0]; - return ret; + if (!value) + return config_error_nonbool(var); + else if (!strcasecmp(value, "auto")) + auto_comment_line_char = 1; + else if (value[0] && !value[1]) { + comment_line_char = value[0]; + auto_comment_line_char = 0; + } else + return error("core.commentChar should only be one character"); + return 0; } if (!strcmp(var, "core.askpass")) @@ -874,16 +872,6 @@ static int git_default_core_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "core.protecthfs")) { - protect_hfs = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.protectntfs")) { - protect_ntfs = git_config_bool(var, value); - return 0; - } - /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@ -1646,6 +1634,13 @@ int git_config_set_multivar_in_file(const char *config_filename, MAP_PRIVATE, in_fd, 0); close(in_fd); + if (chmod(lock->filename, st.st_mode & 07777) < 0) { + error("chmod on %s failed: %s", + lock->filename, strerror(errno)); + ret = CONFIG_NO_WRITE; + goto out_free; + } + if (store.seen == 0) store.seen = 1; @@ -1794,6 +1789,7 @@ int git_config_rename_section_in_file(const char *config_filename, int out_fd; char buf[1024]; FILE *config_file; + struct stat st; if (new_name && !section_name_is_ok(new_name)) { ret = error("invalid section name: %s", new_name); @@ -1815,6 +1811,14 @@ int git_config_rename_section_in_file(const char *config_filename, goto unlock_and_out; } + fstat(fileno(config_file), &st); + + if (chmod(lock->filename, st.st_mode & 07777) < 0) { + ret = error("chmod on %s failed: %s", + lock->filename, strerror(errno)); + goto out; + } + while (fgets(buf, sizeof(buf), config_file)) { int i; int length; diff --git a/config.mak.uname b/config.mak.uname index aba56dcf..15ee15e9 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -28,13 +28,16 @@ ifeq ($(uname_S),OSF1) NO_NSEC = YesPlease endif ifeq ($(uname_S),Linux) + HAVE_ALLOCA_H = YesPlease NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease LIBC_CONTAINS_LIBINTL = YesPlease HAVE_DEV_TTY = YesPlease + HAVE_CLOCK_GETTIME = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) + HAVE_ALLOCA_H = YesPlease NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease @@ -97,13 +100,13 @@ ifeq ($(uname_S),Darwin) HAVE_DEV_TTY = YesPlease COMPAT_OBJS += compat/precompose_utf8.o BASIC_CFLAGS += -DPRECOMPOSE_UNICODE - BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1 endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin + HAVE_ALLOCA_H = YesPlease NO_STRCASESTR = YesPlease NO_MEMMEM = YesPlease NO_MKDTEMP = YesPlease @@ -146,7 +149,7 @@ ifeq ($(uname_S),SunOS) endif INSTALL = /usr/ucb/install TAR = gtar - BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H + BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ endif ifeq ($(uname_O),Cygwin) ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4) @@ -165,6 +168,7 @@ ifeq ($(uname_O),Cygwin) else NO_REGEX = UnfortunatelyYes endif + HAVE_ALLOCA_H = YesPlease NEEDS_LIBICONV = YesPlease NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease @@ -239,6 +243,7 @@ ifeq ($(uname_S),AIX) endif ifeq ($(uname_S),GNU) # GNU/Hurd + HAVE_ALLOCA_H = YesPlease NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease @@ -313,6 +318,7 @@ endif ifeq ($(uname_S),Windows) GIT_VERSION := $(GIT_VERSION).MSVC pathsep = ; + HAVE_ALLOCA_H = YesPlease NO_PREAD = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease @@ -321,7 +327,6 @@ ifeq ($(uname_S),Windows) NO_IPV6 = YesPlease NO_UNIX_SOCKETS = YesPlease NO_SETENV = YesPlease - NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease NO_MEMMEM = YesPlease @@ -349,6 +354,7 @@ ifeq ($(uname_S),Windows) NO_POSIX_GOODIES = UnfortunatelyYes NATIVE_CRLF = YesPlease DEFAULT_HELP_FORMAT = html + NO_D_INO_IN_DIRENT = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl @@ -357,18 +363,17 @@ ifeq ($(uname_S),Windows) COMPAT_OBJS = compat/msvc.o compat/winansi.o \ compat/win32/pthread.o compat/win32/syslog.o \ compat/win32/dirent.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" - BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" + BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj PTHREAD_LIBS = lib = - BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 ifndef DEBUG - BASIC_CFLAGS += -GL -Os -MT + BASIC_CFLAGS += -GL -Os -MD BASIC_LDFLAGS += -LTCG AR += -LTCG else - BASIC_CFLAGS += -Zi -MTd + BASIC_CFLAGS += -Zi -MDd endif X = .exe endif @@ -466,6 +471,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL) endif ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; + HAVE_ALLOCA_H = YesPlease NO_PREAD = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease @@ -473,7 +479,6 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_SYMLINK_HEAD = YesPlease NO_UNIX_SOCKETS = YesPlease NO_SETENV = YesPlease - NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease NO_MEMMEM = YesPlease @@ -498,12 +503,12 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_INET_NTOP = YesPlease NO_POSIX_GOODIES = UnfortunatelyYes DEFAULT_HELP_FORMAT = html + NO_D_INO_IN_DIRENT = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -D_USE_32BIT_TIME_T -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/winansi.o \ compat/win32/pthread.o compat/win32/syslog.o \ compat/win32/dirent.o - BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 BASIC_LDFLAGS += -Wl,--large-address-aware EXTLIBS += -lws2_32 GITLIBS += git.res diff --git a/configure.ac b/configure.ac index b7112542..4b1ae7c3 100644 --- a/configure.ac +++ b/configure.ac @@ -272,6 +272,14 @@ AS_HELP_STRING([], [ARG can be also prefix for libpcre library and hea GIT_CONF_SUBST([LIBPCREDIR]) fi) # +# Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header. +AC_FUNC_ALLOCA +case $ac_cv_working_alloca_h in + yes) HAVE_ALLOCA_H=YesPlease;; + *) HAVE_ALLOCA_H='';; +esac +GIT_CONF_SUBST([HAVE_ALLOCA_H]) +# # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. @@ -127,6 +127,7 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, char *name; int len, name_len; char *buffer = packet_buffer; + const char *arg; len = packet_read(in, &src_buf, &src_len, packet_buffer, sizeof(packet_buffer), @@ -138,12 +139,12 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, if (!len) break; - if (len > 4 && starts_with(buffer, "ERR ")) - die("remote error: %s", buffer + 4); + if (len > 4 && skip_prefix(buffer, "ERR ", &arg)) + die("remote error: %s", arg); - if (len == 48 && starts_with(buffer, "shallow ")) { - if (get_sha1_hex(buffer + 8, old_sha1)) - die("protocol error: expected shallow sha-1, got '%s'", buffer + 8); + if (len == 48 && skip_prefix(buffer, "shallow ", &arg)) { + if (get_sha1_hex(arg, old_sha1)) + die("protocol error: expected shallow sha-1, got '%s'", arg); if (!shallow_points) die("repository on the other end cannot be shallow"); sha1_array_append(shallow_points, old_sha1); @@ -532,22 +533,18 @@ static int git_use_proxy(const char *host) static struct child_process *git_proxy_connect(int fd[2], char *host) { const char *port = STR(DEFAULT_GIT_PORT); - const char **argv; struct child_process *proxy; get_host_and_port(&host, &port); - argv = xmalloc(sizeof(*argv) * 4); - argv[0] = git_proxy_command; - argv[1] = host; - argv[2] = port; - argv[3] = NULL; proxy = xcalloc(1, sizeof(*proxy)); - proxy->argv = argv; + argv_array_push(&proxy->args, git_proxy_command); + argv_array_push(&proxy->args, host); + argv_array_push(&proxy->args, port); proxy->in = -1; proxy->out = -1; if (start_command(proxy)) - die("cannot start proxy %s", argv[0]); + die("cannot start proxy %s", git_proxy_command); fd[0] = proxy->out; /* read from proxy stdout */ fd[1] = proxy->in; /* write to proxy stdin */ return proxy; @@ -661,7 +658,6 @@ struct child_process *git_connect(int fd[2], const char *url, char *hostandport, *path; struct child_process *conn = &no_fork; enum protocol protocol; - const char **arg; struct strbuf cmd = STRBUF_INIT; /* Without this we cannot rely on waitpid() to tell @@ -705,7 +701,6 @@ struct child_process *git_connect(int fd[2], const char *url, sq_quote_buf(&cmd, path); conn->in = conn->out = -1; - conn->argv = arg = xcalloc(7, sizeof(*arg)); if (protocol == PROTO_SSH) { const char *ssh = getenv("GIT_SSH"); int putty = ssh && strcasestr(ssh, "plink"); @@ -716,22 +711,21 @@ struct child_process *git_connect(int fd[2], const char *url, if (!ssh) ssh = "ssh"; - *arg++ = ssh; + argv_array_push(&conn->args, ssh); if (putty && !strcasestr(ssh, "tortoiseplink")) - *arg++ = "-batch"; + argv_array_push(&conn->args, "-batch"); if (port) { /* P is for PuTTY, p is for OpenSSH */ - *arg++ = putty ? "-P" : "-p"; - *arg++ = port; + argv_array_push(&conn->args, putty ? "-P" : "-p"); + argv_array_push(&conn->args, port); } - *arg++ = ssh_host; + argv_array_push(&conn->args, ssh_host); } else { /* remove repo-local variables from the environment */ conn->env = local_repo_env; conn->use_shell = 1; } - *arg++ = cmd.buf; - *arg = NULL; + argv_array_push(&conn->args, cmd.buf); if (start_command(conn)) die("unable to fork"); @@ -757,7 +751,6 @@ int finish_connect(struct child_process *conn) return 0; code = finish_command(conn); - free(conn->argv); free(conn); return code; } diff --git a/connected.c b/connected.c index be0253e2..dae9c997 100644 --- a/connected.c +++ b/connected.c @@ -31,6 +31,7 @@ static int check_everything_connected_real(sha1_iterate_fn fn, unsigned char sha1[20]; int err = 0, ac = 0; struct packed_git *new_pack = NULL; + size_t base_len; if (fn(cb_data, sha1)) return err; @@ -38,10 +39,9 @@ static int check_everything_connected_real(sha1_iterate_fn fn, if (transport && transport->smart_options && transport->smart_options->self_contained_and_connected && transport->pack_lockfile && - ends_with(transport->pack_lockfile, ".keep")) { + strip_suffix(transport->pack_lockfile, ".keep", &base_len)) { struct strbuf idx_file = STRBUF_INIT; - strbuf_addstr(&idx_file, transport->pack_lockfile); - strbuf_setlen(&idx_file, idx_file.len - 5); /* ".keep" */ + strbuf_add(&idx_file, transport->pack_lockfile, base_len); strbuf_addstr(&idx_file, ".idx"); new_pack = add_packed_git(idx_file.buf, idx_file.len, 1); strbuf_release(&idx_file); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 019026ef..06bf2620 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -21,6 +21,12 @@ # source ~/.git-completion.sh # 3) Consider changing your PS1 to also show the current branch, # see git-prompt.sh for details. +# +# If you use complex aliases of form '!f() { ... }; f', you can use the null +# command ':' as the first command in the function body to declare the desired +# completion style. For example '!f() { : git commit ; ... }; f' will +# tell the completion to use commit completion. This also works with aliases +# of form "!sh -c '...'". For example, "!sh -c ': git commit ; ... '". case "$COMP_WORDBREAKS" in *:*) : great ;; @@ -781,6 +787,10 @@ __git_aliased_command () -*) : option ;; *=*) : setting env ;; git) : git itself ;; + \(\)) : skip parens of shell function definition ;; + {) : skip start of shell helper function ;; + :) : skip null command ;; + \'*) : skip opening quote after sh -c ;; *) echo "$word" return @@ -1611,12 +1621,33 @@ _git_pull () __git_push_recurse_submodules="check on-demand" +__git_complete_force_with_lease () +{ + local cur_=$1 + + case "$cur_" in + --*=) + ;; + *:*) + __gitcomp_nl "$(__git_refs)" "" "${cur_#*:}" + ;; + *) + __gitcomp_nl "$(__git_refs)" "" "$cur_" + ;; + esac +} + _git_push () { case "$prev" in --repo) __gitcomp_nl "$(__git_remotes)" return + ;; + --recurse-submodules) + __gitcomp "$__git_push_recurse_submodules" + return + ;; esac case "$cur" in --repo=*) @@ -1627,11 +1658,16 @@ _git_push () __gitcomp "$__git_push_recurse_submodules" "" "${cur##--recurse-submodules=}" return ;; + --force-with-lease=*) + __git_complete_force_with_lease "${cur##--force-with-lease=}" + return + ;; --*) __gitcomp " --all --mirror --tags --dry-run --force --verbose + --quiet --prune --delete --follow-tags --receive-pack= --repo= --set-upstream - --recurse-submodules= + --force-with-lease --force-with-lease= --recurse-submodules= " return ;; diff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh new file mode 100755 index 00000000..0cbc917b --- /dev/null +++ b/contrib/convert-grafts-to-replace-refs.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# You should execute this script in the repository where you +# want to convert grafts to replace refs. + +GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts" + +. $(git --exec-path)/git-sh-setup + +test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'" + +grep '^[^# ]' "$GRAFTS_FILE" | +while read definition +do + if test -n "$definition" + then + echo "Converting: $definition" + git replace --graft $definition || + die "Conversion failed for: $definition" + fi +done + +mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" || + die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'" + +echo "Success!" +echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!" +echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'" diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt index 0565d83f..f871880c 100644 --- a/contrib/convert-objects/git-convert-objects.txt +++ b/contrib/convert-objects/git-convert-objects.txt @@ -26,4 +26,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel GIT --- -Part of the gitlink:git[7] suite +Part of the linkgit:git[7] suite diff --git a/contrib/credential/wincred/Makefile b/contrib/credential/wincred/Makefile index bad45ca4..6e992c08 100644 --- a/contrib/credential/wincred/Makefile +++ b/contrib/credential/wincred/Makefile @@ -1,14 +1,22 @@ all: git-credential-wincred.exe -CC = gcc -RM = rm -f -CFLAGS = -O2 -Wall - -include ../../../config.mak.autogen -include ../../../config.mak +CC ?= gcc +RM ?= rm -f +CFLAGS ?= -O2 -Wall + +prefix ?= /usr/local +libexecdir ?= $(prefix)/libexec/git-core + +INSTALL ?= install + git-credential-wincred.exe : git-credential-wincred.c $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@ +install: git-credential-wincred.exe + $(INSTALL) -m 755 $^ $(libexecdir) + clean: $(RM) git-credential-wincred.exe diff --git a/contrib/diffall/README b/contrib/diffall/README deleted file mode 100644 index 507f17dc..00000000 --- a/contrib/diffall/README +++ /dev/null @@ -1,31 +0,0 @@ -The git-diffall script provides a directory based diff mechanism -for git. - -To determine what diff viewer is used, the script requires either -the 'diff.tool' or 'merge.tool' configuration option to be set. - -This script is compatible with most common forms used to specify a -range of revisions to diff: - - 1. git diffall: shows diff between working tree and staged changes - 2. git diffall --cached [<commit>]: shows diff between staged - changes and HEAD (or other named commit) - 3. git diffall <commit>: shows diff between working tree and named - commit - 4. git diffall <commit> <commit>: show diff between two named commits - 5. git diffall <commit>..<commit>: same as above - 6. git diffall <commit>...<commit>: show the changes on the branch - containing and up to the second, starting at a common ancestor - of both <commit> - -Note: all forms take an optional path limiter [-- <path>*] - -The '--extcmd=<command>' option allows the user to specify a custom -command for viewing diffs. When given, configured defaults are -ignored and the script runs $command $LOCAL $REMOTE. Additionally, -$BASE is set in the environment. - -This script is based on an example provided by Thomas Rast on the -Git list [1]: - -[1] http://thread.gmane.org/gmane.comp.version-control.git/124807 diff --git a/contrib/diffall/git-diffall b/contrib/diffall/git-diffall deleted file mode 100755 index 84f2b654..00000000 --- a/contrib/diffall/git-diffall +++ /dev/null @@ -1,257 +0,0 @@ -#!/bin/sh -# Copyright 2010 - 2012, Tim Henigan <tim.henigan@gmail.com> -# -# Perform a directory diff between commits in the repository using -# the external diff or merge tool specified in the user's config. - -USAGE='[--cached] [--copy-back] [-x|--extcmd=<command>] <commit>{0,2} [-- <path>*] - - --cached Compare to the index rather than the working tree. - - --copy-back Copy files back to the working tree when the diff - tool exits (in case they were modified by the - user). This option is only valid if the diff - compared with the working tree. - - -x=<command> - --extcmd=<command> Specify a custom command for viewing diffs. - git-diffall ignores the configured defaults and - runs $command $LOCAL $REMOTE when this option is - specified. Additionally, $BASE is set in the - environment. -' - -SUBDIRECTORY_OK=1 -. "$(git --exec-path)/git-sh-setup" - -TOOL_MODE=diff -. "$(git --exec-path)/git-mergetool--lib" - -merge_tool="$(get_merge_tool)" -if test -z "$merge_tool" -then - echo "Error: Either the 'diff.tool' or 'merge.tool' option must be set." - usage -fi - -start_dir=$(pwd) - -# All the file paths returned by the diff command are relative to the root -# of the working copy. So if the script is called from a subdirectory, it -# must switch to the root of working copy before trying to use those paths. -cdup=$(git rev-parse --show-cdup) && -cd "$cdup" || { - echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" - exit 1 -} - -# set up temp dir -tmp=$(perl -e 'use File::Temp qw(tempdir); - $t=tempdir("/tmp/git-diffall.XXXXX") or exit(1); - print $t') || exit 1 -trap 'rm -rf "$tmp"' EXIT - -left= -right= -paths= -dashdash_seen= -compare_staged= -merge_base= -left_dir= -right_dir= -diff_tool= -copy_back= - -while test $# != 0 -do - case "$1" in - -h|--h|--he|--hel|--help) - usage - ;; - --cached) - compare_staged=1 - ;; - --copy-back) - copy_back=1 - ;; - -x|--e|--ex|--ext|--extc|--extcm|--extcmd) - if test $# = 1 - then - echo You must specify the tool for use with --extcmd - usage - else - diff_tool=$2 - shift - fi - ;; - --) - dashdash_seen=1 - ;; - -*) - echo Invalid option: "$1" - usage - ;; - *) - # could be commit, commit range or path limiter - case "$1" in - *...*) - left=${1%...*} - right=${1#*...} - merge_base=1 - ;; - *..*) - left=${1%..*} - right=${1#*..} - ;; - *) - if test -n "$dashdash_seen" - then - paths="$paths$1 " - elif test -z "$left" - then - left=$1 - elif test -z "$right" - then - right=$1 - else - paths="$paths$1 " - fi - ;; - esac - ;; - esac - shift -done - -# Determine the set of files which changed -if test -n "$left" && test -n "$right" -then - left_dir="cmt-$(git rev-parse --short $left)" - right_dir="cmt-$(git rev-parse --short $right)" - - if test -n "$compare_staged" - then - usage - elif test -n "$merge_base" - then - git diff --name-only "$left"..."$right" -- $paths >"$tmp/filelist" - else - git diff --name-only "$left" "$right" -- $paths >"$tmp/filelist" - fi -elif test -n "$left" -then - left_dir="cmt-$(git rev-parse --short $left)" - - if test -n "$compare_staged" - then - right_dir="staged" - git diff --name-only --cached "$left" -- $paths >"$tmp/filelist" - else - right_dir="working_tree" - git diff --name-only "$left" -- $paths >"$tmp/filelist" - fi -else - left_dir="HEAD" - - if test -n "$compare_staged" - then - right_dir="staged" - git diff --name-only --cached -- $paths >"$tmp/filelist" - else - right_dir="working_tree" - git diff --name-only -- $paths >"$tmp/filelist" - fi -fi - -# Exit immediately if there are no diffs -if test ! -s "$tmp/filelist" -then - exit 0 -fi - -if test -n "$copy_back" && test "$right_dir" != "working_tree" -then - echo "--copy-back is only valid when diff includes the working tree." - exit 1 -fi - -# Create the named tmp directories that will hold the files to be compared -mkdir -p "$tmp/$left_dir" "$tmp/$right_dir" - -# Populate the tmp/right_dir directory with the files to be compared -while read name -do - if test -n "$right" - then - ls_list=$(git ls-tree $right "$name") - if test -n "$ls_list" - then - mkdir -p "$tmp/$right_dir/$(dirname "$name")" - git show "$right":"$name" >"$tmp/$right_dir/$name" || true - fi - elif test -n "$compare_staged" - then - ls_list=$(git ls-files -- "$name") - if test -n "$ls_list" - then - mkdir -p "$tmp/$right_dir/$(dirname "$name")" - git show :"$name" >"$tmp/$right_dir/$name" - fi - else - if test -e "$name" - then - mkdir -p "$tmp/$right_dir/$(dirname "$name")" - cp "$name" "$tmp/$right_dir/$name" - fi - fi -done < "$tmp/filelist" - -# Populate the tmp/left_dir directory with the files to be compared -while read name -do - if test -n "$left" - then - ls_list=$(git ls-tree $left "$name") - if test -n "$ls_list" - then - mkdir -p "$tmp/$left_dir/$(dirname "$name")" - git show "$left":"$name" >"$tmp/$left_dir/$name" || true - fi - else - if test -n "$compare_staged" - then - ls_list=$(git ls-tree HEAD "$name") - if test -n "$ls_list" - then - mkdir -p "$tmp/$left_dir/$(dirname "$name")" - git show HEAD:"$name" >"$tmp/$left_dir/$name" - fi - else - mkdir -p "$tmp/$left_dir/$(dirname "$name")" - git show :"$name" >"$tmp/$left_dir/$name" - fi - fi -done < "$tmp/filelist" - -LOCAL="$tmp/$left_dir" -REMOTE="$tmp/$right_dir" - -if test -n "$diff_tool" -then - export BASE - eval $diff_tool '"$LOCAL"' '"$REMOTE"' -else - run_merge_tool "$merge_tool" false -fi - -# Copy files back to the working dir, if requested -if test -n "$copy_back" && test "$right_dir" = "working_tree" -then - cd "$start_dir" - git_top_dir=$(git rev-parse --show-toplevel) - find "$tmp/$right_dir" -type f | - while read file - do - cp "$file" "$git_top_dir/${file#$tmp/$right_dir/}" - done -fi diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c index 8bc8c753..ee191664 100644 --- a/contrib/examples/builtin-fetch--tool.c +++ b/contrib/examples/builtin-fetch--tool.c @@ -31,7 +31,8 @@ static int update_ref_env(const char *action, rla = "(reflog update)"; if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) warning("reflog message too long: %.*s...", 50, msg); - return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR); + return update_ref(msg, refname, sha1, oldval, 0, + UPDATE_REFS_QUIET_ON_ERR); } static int update_local_ref(const char *name, diff --git a/contrib/examples/git-clone.sh b/contrib/examples/git-clone.sh index b4c9376a..08cf246b 100755 --- a/contrib/examples/git-clone.sh +++ b/contrib/examples/git-clone.sh @@ -516,7 +516,7 @@ then case "$no_checkout" in '') - test "z$quiet" = z -a "z$no_progress" = z && v=-v || v= + test "z$quiet" = z && test "z$no_progress" = z && v=-v || v= git read-tree -m -u $v HEAD HEAD esac fi diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh index 5cafe2eb..934505ba 100755 --- a/contrib/examples/git-commit.sh +++ b/contrib/examples/git-commit.sh @@ -51,7 +51,7 @@ run_status () { export GIT_INDEX_FILE fi - if test "$status_only" = "t" -o "$use_status_color" = "t"; then + if test "$status_only" = "t" || test "$use_status_color" = "t"; then color= else color=--nocolor @@ -296,7 +296,7 @@ t,,,[1-9]*) die "No paths with -i does not make sense." ;; esac -if test ! -z "$templatefile" -a -z "$log_given" +if test ! -z "$templatefile" && test -z "$log_given" then if test ! -f "$templatefile" then diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh index 7e40f40c..52f2aafb 100755 --- a/contrib/examples/git-merge.sh +++ b/contrib/examples/git-merge.sh @@ -161,7 +161,7 @@ merge_name () { return fi fi - if test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD" + if test "$remote" = "FETCH_HEAD" && test -r "$GIT_DIR/FETCH_HEAD" then sed -e 's/ not-for-merge / /' -e 1q \ "$GIT_DIR/FETCH_HEAD" @@ -527,7 +527,7 @@ do git diff-files --name-only git ls-files --unmerged } | wc -l` - if test $best_cnt -le 0 -o $cnt -le $best_cnt + if test $best_cnt -le 0 || test $cnt -le $best_cnt then best_strategy=$strategy best_cnt=$cnt diff --git a/contrib/examples/git-repack.sh b/contrib/examples/git-repack.sh index f312405a..96e3fed3 100755 --- a/contrib/examples/git-repack.sh +++ b/contrib/examples/git-repack.sh @@ -76,8 +76,8 @@ case ",$all_into_one," in existing="$existing $e" fi done - if test -n "$existing" -a -n "$unpack_unreachable" -a \ - -n "$remove_redundant" + if test -n "$existing" && test -n "$unpack_unreachable" && \ + test -n "$remove_redundant" then # This may have arbitrary user arguments, so we # have to protect it against whitespace splitting diff --git a/contrib/examples/git-resolve.sh b/contrib/examples/git-resolve.sh index 48d0fc97..70fdc27b 100755 --- a/contrib/examples/git-resolve.sh +++ b/contrib/examples/git-resolve.sh @@ -76,7 +76,7 @@ case "$common" in 2>/dev/null || continue # Count the paths that are unmerged. cnt=$(GIT_INDEX_FILE=$G git ls-files --unmerged | wc -l) - if test $best_cnt -le 0 -o $cnt -le $best_cnt + if test $best_cnt -le 0 || test $cnt -le $best_cnt then best=$c best_cnt=$cnt diff --git a/contrib/examples/git-svnimport.txt b/contrib/examples/git-svnimport.txt index 3bb871e4..3f0a9c33 100644 --- a/contrib/examples/git-svnimport.txt +++ b/contrib/examples/git-svnimport.txt @@ -176,4 +176,4 @@ Documentation by Matthias Urlichs <smurf@smurf.noris.de>. GIT --- -Part of the gitlink:git[7] suite +Part of the linkgit:git[7] suite diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt index 9e12f978..7b5f9002 100644 --- a/contrib/gitview/gitview.txt +++ b/contrib/gitview/gitview.txt @@ -28,7 +28,7 @@ OPTIONS <args>:: - All the valid option for gitlink:git-rev-list[1]. + All the valid option for linkgit:git-rev-list[1]. Key Bindings ------------ diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl index 3f8d993a..8dd74a9a 100755 --- a/contrib/mw-to-git/git-remote-mediawiki.perl +++ b/contrib/mw-to-git/git-remote-mediawiki.perl @@ -461,7 +461,12 @@ sub download_mw_mediafile { my $response = $mediawiki->{ua}->get($download_url); if ($response->code == HTTP_CODE_OK) { - return $response->decoded_content; + # It is tempting to return + # $response->decoded_content({charset => "none"}), but + # when doing so, utf8::downgrade($content) fails with + # "Wide character in subroutine entry". + $response->decode(); + return $response->content(); } else { print {*STDERR} "Error downloading mediafile from :\n"; print {*STDERR} "URL: ${download_url}\n"; diff --git a/contrib/mw-to-git/t/install-wiki.sh b/contrib/mw-to-git/t/install-wiki.sh index 70a53f67..c215213c 100755 --- a/contrib/mw-to-git/t/install-wiki.sh +++ b/contrib/mw-to-git/t/install-wiki.sh @@ -20,6 +20,8 @@ usage () { echo " install | -i : Install a wiki on your computer." echo " delete | -d : Delete the wiki and all its pages and " echo " content." + echo " start | -s : Start the previously configured lighttpd daemon" + echo " stop : Stop lighttpd daemon." } @@ -33,6 +35,14 @@ case "$1" in wiki_delete exit 0 ;; + "start" | "-s") + start_lighttpd + exit + ;; + "stop") + stop_lighttpd + exit + ;; "--help" | "-h") usage exit 0 diff --git a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh index 5a037393..3ff3a095 100755 --- a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh +++ b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh @@ -58,6 +58,25 @@ test_expect_success 'git clone works on previously created wiki with media files test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt ' +test_expect_success 'git push can upload media (File:) files containing valid UTF-8' ' + wiki_reset && + git clone mediawiki::'"$WIKI_URL"' mw_dir && + ( + cd mw_dir && + "$PERL_PATH" -e "print STDOUT \"UTF-8 content: éèàéê€.\";" >Bar.txt && + git add Bar.txt && + git commit -m "add a text file with UTF-8 content" && + git push + ) +' + +test_expect_success 'git clone works on previously created wiki with media files containing valid UTF-8' ' + test_when_finished "rm -rf mw_dir mw_dir_clone" && + git clone -c remote.origin.mediaimport=true \ + mediawiki::'"$WIKI_URL"' mw_dir_clone && + test_cmp mw_dir_clone/Bar.txt mw_dir/Bar.txt +' + test_expect_success 'git push & pull work with locally renamed media files' ' wiki_reset && git clone mediawiki::'"$WIKI_URL"' mw_dir && diff --git a/contrib/mw-to-git/t/t9365-continuing-queries.sh b/contrib/mw-to-git/t/t9365-continuing-queries.sh index 27e267f5..01645474 100755 --- a/contrib/mw-to-git/t/t9365-continuing-queries.sh +++ b/contrib/mw-to-git/t/t9365-continuing-queries.sh @@ -9,7 +9,7 @@ test_check_precond test_expect_success 'creating page w/ >500 revisions' ' wiki_reset && - for i in `test_seq 501` + for i in $(test_seq 501) do echo "creating revision $i" && wiki_editpage foo "revision $i<br/>" true diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh index 3372b2af..6546294f 100755 --- a/contrib/mw-to-git/t/test-gitmw-lib.sh +++ b/contrib/mw-to-git/t/test-gitmw-lib.sh @@ -90,7 +90,7 @@ test_diff_directories () { # # Check that <dir> contains exactly <N> files test_contains_N_files () { - if test `ls -- "$1" | wc -l` -ne "$2"; then + if test $(ls -- "$1" | wc -l) -ne "$2"; then echo "directory $1 should contain $2 files" echo "it contains these files:" ls "$1" @@ -289,7 +289,6 @@ start_lighttpd () { # Kill daemon lighttpd and removes files and folders associated. stop_lighttpd () { test -f "$WEB_TMP/pid" && kill $(cat "$WEB_TMP/pid") - rm -rf "$WEB" } # Create the SQLite database of the MediaWiki. If the database file already @@ -341,10 +340,10 @@ wiki_install () { "http://download.wikimedia.org/mediawiki/$MW_VERSION_MAJOR/"\ "$MW_FILENAME. "\ "Please fix your connection and launch the script again." - echo "$MW_FILENAME downloaded in `pwd`. "\ + echo "$MW_FILENAME downloaded in $(pwd). "\ "You can delete it later if you want." else - echo "Reusing existing $MW_FILENAME downloaded in `pwd`." + echo "Reusing existing $MW_FILENAME downloaded in $(pwd)." fi archive_abs_path=$(pwd)/$MW_FILENAME cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" || @@ -415,6 +414,7 @@ wiki_reset () { wiki_delete () { if test $LIGHTTPD = "true"; then stop_lighttpd + rm -fr "$WEB" else # Delete the wiki's directory. rm -rf "$WIKI_DIR_INST/$WIKI_DIR_NAME" || diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile index 4030a168..d9a0ce2c 100644 --- a/contrib/subtree/Makefile +++ b/contrib/subtree/Makefile @@ -3,17 +3,28 @@ prefix ?= /usr/local mandir ?= $(prefix)/share/man -libexecdir ?= $(prefix)/libexec/git-core -gitdir ?= $(shell git --exec-path) +gitexecdir ?= $(prefix)/libexec/git-core man1dir ?= $(mandir)/man1 -gitver ?= $(word 3,$(shell git --version)) +../../GIT-VERSION-FILE: FORCE + $(MAKE) -C ../../ GIT-VERSION-FILE + +-include ../../GIT-VERSION-FILE # this should be set to a 'standard' bsd-type install program -INSTALL ?= install +INSTALL ?= install +RM ?= rm -f + +ASCIIDOC = asciidoc +XMLTO = xmlto -ASCIIDOC_CONF = ../../Documentation/asciidoc.conf -MANPAGE_NORMAL_XSL = ../../Documentation/manpage-normal.xsl +ifndef SHELL_PATH + SHELL_PATH = /bin/sh +endif +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) + +ASCIIDOC_CONF = ../../Documentation/asciidoc.conf +MANPAGE_XSL = ../../Documentation/manpage-normal.xsl GIT_SUBTREE_SH := git-subtree.sh GIT_SUBTREE := git-subtree @@ -26,13 +37,14 @@ GIT_SUBTREE_HTML := git-subtree.html all: $(GIT_SUBTREE) $(GIT_SUBTREE): $(GIT_SUBTREE_SH) - cp $< $@ && chmod +x $@ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' $< >$@ + chmod +x $@ doc: $(GIT_SUBTREE_DOC) $(GIT_SUBTREE_HTML) install: $(GIT_SUBTREE) - $(INSTALL) -d -m 755 $(DESTDIR)$(libexecdir) - $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(libexecdir) + $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir) + $(INSTALL) -m 755 $(GIT_SUBTREE) $(DESTDIR)$(gitexecdir) install-doc: install-man @@ -41,19 +53,21 @@ install-man: $(GIT_SUBTREE_DOC) $(INSTALL) -m 644 $^ $(DESTDIR)$(man1dir) $(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML) - xmlto -m $(MANPAGE_NORMAL_XSL) man $^ + $(XMLTO) -m $(MANPAGE_XSL) man $^ $(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT) - asciidoc -b docbook -d manpage -f $(ASCIIDOC_CONF) \ - -agit_version=$(gitver) $^ + $(ASCIIDOC) -b docbook -d manpage -f $(ASCIIDOC_CONF) \ + -agit_version=$(GIT_VERSION) $^ $(GIT_SUBTREE_HTML): $(GIT_SUBTREE_TXT) - asciidoc -b xhtml11 -d manpage -f $(ASCIIDOC_CONF) \ - -agit_version=$(gitver) $^ + $(ASCIIDOC) -b xhtml11 -d manpage -f $(ASCIIDOC_CONF) \ + -agit_version=$(GIT_VERSION) $^ test: $(MAKE) -C t/ test clean: - rm -f *~ *.xml *.html *.1 - rm -rf subproj mainline + $(RM) $(GIT_SUBTREE) + $(RM) *.xml *.html *.1 + +.PHONY: FORCE diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index db925ca7..fa1a5839 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -558,8 +558,9 @@ cmd_add_commit() commit=$(add_squashed_msg "$rev" "$dir" | git commit-tree $tree $headp -p "$rev") || exit $? else + revp=$(peel_committish "$rev") && commit=$(add_msg "$dir" "$headrev" "$rev" | - git commit-tree $tree $headp -p "$rev") || exit $? + git commit-tree $tree $headp -p "$revp") || exit $? fi git reset "$commit" || exit $? diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 8dc68403..6309d124 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -77,7 +77,7 @@ test_expect_success 'add sub1' ' # Save this hash for testing later. -subdir_hash=`git rev-parse HEAD` +subdir_hash=$(git rev-parse HEAD) test_expect_success 'add sub2' ' create sub2 && diff --git a/contrib/svn-fe/svnrdump_sim.py b/contrib/svn-fe/svnrdump_sim.py index 4e78a1c3..11ac6f69 100755 --- a/contrib/svn-fe/svnrdump_sim.py +++ b/contrib/svn-fe/svnrdump_sim.py @@ -5,53 +5,64 @@ of the specified revision range. To simulate incremental imports the environment variable SVNRMAX can be set to the highest revision that should be available. """ -import sys, os +import sys +import os if sys.hexversion < 0x02040000: - # The limiter is the ValueError() calls. This may be too conservative - sys.stderr.write("svnrdump-sim.py: requires Python 2.4 or later.\n") - sys.exit(1) + # The limiter is the ValueError() calls. This may be too conservative + sys.stderr.write("svnrdump-sim.py: requires Python 2.4 or later.\n") + sys.exit(1) + def getrevlimit(): - var = 'SVNRMAX' - if var in os.environ: - return os.environ[var] - return None + var = 'SVNRMAX' + if var in os.environ: + return os.environ[var] + return None + def writedump(url, lower, upper): - if url.startswith('sim://'): - filename = url[6:] - if filename[-1] == '/': filename = filename[:-1] #remove terminating slash - else: - raise ValueError('sim:// url required') - f = open(filename, 'r'); - state = 'header' - wroterev = False - while(True): - l = f.readline() - if l == '': break - if state == 'header' and l.startswith('Revision-number: '): - state = 'prefix' - if state == 'prefix' and l == 'Revision-number: %s\n' % lower: - state = 'selection' - if not upper == 'HEAD' and state == 'selection' and l == 'Revision-number: %s\n' % upper: - break; + if url.startswith('sim://'): + filename = url[6:] + if filename[-1] == '/': + filename = filename[:-1] # remove terminating slash + else: + raise ValueError('sim:// url required') + f = open(filename, 'r') + state = 'header' + wroterev = False + while(True): + l = f.readline() + if l == '': + break + if state == 'header' and l.startswith('Revision-number: '): + state = 'prefix' + if state == 'prefix' and l == 'Revision-number: %s\n' % lower: + state = 'selection' + if not upper == 'HEAD' and state == 'selection' and \ + l == 'Revision-number: %s\n' % upper: + break - if state == 'header' or state == 'selection': - if state == 'selection': wroterev = True - sys.stdout.write(l) - return wroterev + if state == 'header' or state == 'selection': + if state == 'selection': + wroterev = True + sys.stdout.write(l) + return wroterev if __name__ == "__main__": - if not (len(sys.argv) in (3, 4, 5)): - print("usage: %s dump URL -rLOWER:UPPER") - sys.exit(1) - if not sys.argv[1] == 'dump': raise NotImplementedError('only "dump" is suppported.') - url = sys.argv[2] - r = ('0', 'HEAD') - if len(sys.argv) == 4 and sys.argv[3][0:2] == '-r': - r = sys.argv[3][2:].lstrip().split(':') - if not getrevlimit() is None: r[1] = getrevlimit() - if writedump(url, r[0], r[1]): ret = 0 - else: ret = 1 - sys.exit(ret) + if not (len(sys.argv) in (3, 4, 5)): + print("usage: %s dump URL -rLOWER:UPPER") + sys.exit(1) + if not sys.argv[1] == 'dump': + raise NotImplementedError('only "dump" is suppported.') + url = sys.argv[2] + r = ('0', 'HEAD') + if len(sys.argv) == 4 and sys.argv[3][0:2] == '-r': + r = sys.argv[3][2:].lstrip().split(':') + if not getrevlimit() is None: + r[1] = getrevlimit() + if writedump(url, r[0], r[1]): + ret = 0 + else: + ret = 1 + sys.exit(ret) diff --git a/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh index 5eb4a516..8dc73ece 100755 --- a/contrib/thunderbird-patch-inline/appp.sh +++ b/contrib/thunderbird-patch-inline/appp.sh @@ -10,7 +10,7 @@ CONFFILE=~/.appprc SEP="-=-=-=-=-=-=-=-=-=# Don't remove this line #=-=-=-=-=-=-=-=-=-" if [ -e "$CONFFILE" ] ; then - LAST_DIR=`grep -m 1 "^LAST_DIR=" "${CONFFILE}"|sed -e 's/^LAST_DIR=//'` + LAST_DIR=$(grep -m 1 "^LAST_DIR=" "${CONFFILE}"|sed -e 's/^LAST_DIR=//') cd "${LAST_DIR}" else cd > /dev/null @@ -25,11 +25,11 @@ fi cd - > /dev/null -SUBJECT=`sed -n -e '/^Subject: /p' "${PATCH}"` -HEADERS=`sed -e '/^'"${SEP}"'$/,$d' $1` -BODY=`sed -e "1,/${SEP}/d" $1` -CMT_MSG=`sed -e '1,/^$/d' -e '/^---$/,$d' "${PATCH}"` -DIFF=`sed -e '1,/^---$/d' "${PATCH}"` +SUBJECT=$(sed -n -e '/^Subject: /p' "${PATCH}") +HEADERS=$(sed -e '/^'"${SEP}"'$/,$d' $1) +BODY=$(sed -e "1,/${SEP}/d" $1) +CMT_MSG=$(sed -e '1,/^$/d' -e '/^---$/,$d' "${PATCH}") +DIFF=$(sed -e '1,/^---$/d' "${PATCH}") CCS=`echo -e "$CMT_MSG\n$HEADERS" | sed -n -e 's/^Cc: \(.*\)$/\1,/gp' \ -e 's/^Signed-off-by: \(.*\)/\1,/gp'` @@ -48,7 +48,7 @@ if [ "x${BODY}x" != "xx" ] ; then fi echo "$DIFF" >> $1 -LAST_DIR=`dirname "${PATCH}"` +LAST_DIR=$(dirname "${PATCH}") grep -v "^LAST_DIR=" "${CONFFILE}" > "${CONFFILE}_" echo "LAST_DIR=${LAST_DIR}" >> "${CONFFILE}_" diff --git a/contrib/vim/README b/contrib/vim/README deleted file mode 100644 index 8f16d069..00000000 --- a/contrib/vim/README +++ /dev/null @@ -1,22 +0,0 @@ -Syntax highlighting for git commit messages, config files, etc. is -included with the vim distribution as of vim 7.2, and should work -automatically. - -If you have an older version of vim, you can get the latest syntax -files from the vim project: - - http://ftp.vim.org/pub/vim/runtime/syntax/git.vim - http://ftp.vim.org/pub/vim/runtime/syntax/gitcommit.vim - http://ftp.vim.org/pub/vim/runtime/syntax/gitconfig.vim - http://ftp.vim.org/pub/vim/runtime/syntax/gitrebase.vim - http://ftp.vim.org/pub/vim/runtime/syntax/gitsendemail.vim - -These files are also available via FTP at the same location. - -To install: - - 1. Copy these files to vim's syntax directory $HOME/.vim/syntax - 2. To auto-detect the editing of various git-related filetypes: - - $ curl http://ftp.vim.org/pub/vim/runtime/filetype.vim | - sed -ne '/^" Git$/, /^$/ p' >>$HOME/.vim/filetype.vim @@ -1121,9 +1121,9 @@ static int is_foreign_ident(const char *str) { int i; - if (!starts_with(str, "$Id: ")) + if (!skip_prefix(str, "$Id: ", &str)) return 0; - for (i = 5; str[i]; i++) { + for (i = 0; str[i]; i++) { if (isspace(str[i]) && str[i+1] != '$') return 1; } diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index 390f1942..3b370ca5 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -109,14 +109,12 @@ static int read_request(FILE *fh, struct credential *c, const char *p; strbuf_getline(&item, fh, '\n'); - p = skip_prefix(item.buf, "action="); - if (!p) + if (!skip_prefix(item.buf, "action=", &p)) return error("client sent bogus action line: %s", item.buf); strbuf_addstr(action, p); strbuf_getline(&item, fh, '\n'); - p = skip_prefix(item.buf, "timeout="); - if (!p) + if (!skip_prefix(item.buf, "timeout=", &p)) return error("client sent bogus timeout line: %s", item.buf); *timeout = atoi(p); diff --git a/credential.c b/credential.c index e54753c7..4d79d320 100644 --- a/credential.c +++ b/credential.c @@ -40,8 +40,7 @@ static int credential_config_callback(const char *var, const char *value, struct credential *c = data; const char *key, *dot; - key = skip_prefix(var, "credential."); - if (!key) + if (!skip_prefix(var, "credential.", &key)) return 0; if (!value) @@ -39,8 +39,8 @@ static int strict_paths; static int export_all_trees; /* Take all paths relative to this one if non-NULL */ -static char *base_path; -static char *interpolated_path; +static const char *base_path; +static const char *interpolated_path; static int base_path_relaxed; /* Flag indicating client sent extra args. */ @@ -106,12 +106,12 @@ static void NORETURN daemon_die(const char *err, va_list params) exit(1); } -static const char *path_ok(char *directory) +static const char *path_ok(const char *directory) { static char rpath[PATH_MAX]; static char interp_path[PATH_MAX]; const char *path; - char *dir; + const char *dir; dir = directory; @@ -131,7 +131,7 @@ static const char *path_ok(char *directory) * "~alice/%s/foo". */ int namlen, restlen = strlen(dir); - char *slash = strchr(dir, '/'); + const char *slash = strchr(dir, '/'); if (!slash) slash = dir + restlen; namlen = slash - dir; @@ -235,8 +235,10 @@ static int service_enabled; static int git_daemon_config(const char *var, const char *value, void *cb) { - if (starts_with(var, "daemon.") && - !strcmp(var + 7, service_looking_at->config_name)) { + const char *service; + + if (skip_prefix(var, "daemon.", &service) && + !strcmp(service, service_looking_at->config_name)) { service_enabled = git_config_bool(var, value); return 0; } @@ -253,7 +255,7 @@ static int daemon_error(const char *dir, const char *msg) return -1; } -static char *access_hook; +static const char *access_hook; static int run_access_hook(struct daemon_service *service, const char *dir, const char *path) { @@ -318,7 +320,7 @@ error_return: return -1; } -static int run_service(char *dir, struct daemon_service *service) +static int run_service(const char *dir, struct daemon_service *service) { const char *path; int enabled = service->enabled; @@ -475,14 +477,6 @@ static void make_service_overridable(const char *name, int ena) die("No such service %s", name); } -static char *xstrdup_tolower(const char *str) -{ - char *p, *dup = xstrdup(str); - for (p = dup; *p; p++) - *p = tolower(*p); - return dup; -} - static void parse_host_and_port(char *hostport, char **host, char **port) { @@ -632,15 +626,16 @@ static int execute(void) for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); - int namelen = strlen(s->name); - if (starts_with(line, "git-") && - !strncmp(s->name, line + 4, namelen) && - line[namelen + 4] == ' ') { + const char *arg; + + if (skip_prefix(line, "git-", &arg) && + skip_prefix(arg, s->name, &arg) && + *arg++ == ' ') { /* * Note: The directory here is probably context sensitive, * and might depend on the actual service being performed. */ - return run_service(line + namelen + 5, s); + return run_service(arg, s); } } @@ -783,7 +778,6 @@ static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen) logerror("unable to fork"); else add_child(&cld, addr, addrlen); - close(incoming); } static void child_handler(int signo) @@ -1141,16 +1135,17 @@ int main(int argc, char **argv) for (i = 1; i < argc; i++) { char *arg = argv[i]; + const char *v; - if (starts_with(arg, "--listen=")) { - string_list_append(&listen_addr, xstrdup_tolower(arg + 9)); + if (skip_prefix(arg, "--listen=", &v)) { + string_list_append(&listen_addr, xstrdup_tolower(v)); continue; } - if (starts_with(arg, "--port=")) { + if (skip_prefix(arg, "--port=", &v)) { char *end; unsigned long n; - n = strtoul(arg+7, &end, 0); - if (arg[7] && !*end) { + n = strtoul(v, &end, 0); + if (*v && !*end) { listen_port = n; continue; } @@ -1176,20 +1171,20 @@ int main(int argc, char **argv) export_all_trees = 1; continue; } - if (starts_with(arg, "--access-hook=")) { - access_hook = arg + 14; + if (skip_prefix(arg, "--access-hook=", &v)) { + access_hook = v; continue; } - if (starts_with(arg, "--timeout=")) { - timeout = atoi(arg+10); + if (skip_prefix(arg, "--timeout=", &v)) { + timeout = atoi(v); continue; } - if (starts_with(arg, "--init-timeout=")) { - init_timeout = atoi(arg+15); + if (skip_prefix(arg, "--init-timeout=", &v)) { + init_timeout = atoi(v); continue; } - if (starts_with(arg, "--max-connections=")) { - max_connections = atoi(arg+18); + if (skip_prefix(arg, "--max-connections=", &v)) { + max_connections = atoi(v); if (max_connections < 0) max_connections = 0; /* unlimited */ continue; @@ -1198,16 +1193,16 @@ int main(int argc, char **argv) strict_paths = 1; continue; } - if (starts_with(arg, "--base-path=")) { - base_path = arg+12; + if (skip_prefix(arg, "--base-path=", &v)) { + base_path = v; continue; } if (!strcmp(arg, "--base-path-relaxed")) { base_path_relaxed = 1; continue; } - if (starts_with(arg, "--interpolated-path=")) { - interpolated_path = arg+20; + if (skip_prefix(arg, "--interpolated-path=", &v)) { + interpolated_path = v; continue; } if (!strcmp(arg, "--reuseaddr")) { @@ -1218,12 +1213,12 @@ int main(int argc, char **argv) user_path = ""; continue; } - if (starts_with(arg, "--user-path=")) { - user_path = arg + 12; + if (skip_prefix(arg, "--user-path=", &v)) { + user_path = v; continue; } - if (starts_with(arg, "--pid-file=")) { - pid_file = arg + 11; + if (skip_prefix(arg, "--pid-file=", &v)) { + pid_file = v; continue; } if (!strcmp(arg, "--detach")) { @@ -1231,28 +1226,28 @@ int main(int argc, char **argv) log_syslog = 1; continue; } - if (starts_with(arg, "--user=")) { - user_name = arg + 7; + if (skip_prefix(arg, "--user=", &v)) { + user_name = v; continue; } - if (starts_with(arg, "--group=")) { - group_name = arg + 8; + if (skip_prefix(arg, "--group=", &v)) { + group_name = v; continue; } - if (starts_with(arg, "--enable=")) { - enable_service(arg + 9, 1); + if (skip_prefix(arg, "--enable=", &v)) { + enable_service(v, 1); continue; } - if (starts_with(arg, "--disable=")) { - enable_service(arg + 10, 0); + if (skip_prefix(arg, "--disable=", &v)) { + enable_service(v, 0); continue; } - if (starts_with(arg, "--allow-override=")) { - make_service_overridable(arg + 17, 1); + if (skip_prefix(arg, "--allow-override=", &v)) { + make_service_overridable(v, 1); continue; } - if (starts_with(arg, "--forbid-override=")) { - make_service_overridable(arg + 18, 0); + if (skip_prefix(arg, "--forbid-override=", &v)) { + make_service_overridable(v, 0); continue; } if (!strcmp(arg, "--informative-errors")) { @@ -8,10 +8,7 @@ static unsigned int hash_obj(const struct object *obj, unsigned int n) { - unsigned int hash; - - memcpy(&hash, obj->sha1, sizeof(unsigned int)); - return hash % n; + return sha1hash(obj->sha1) % n; } static void *insert_decoration(struct decoration *n, const struct object *base, void *decoration) @@ -52,23 +52,23 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* FUNCINFO */ }; -static int parse_diff_color_slot(const char *var, int ofs) +static int parse_diff_color_slot(const char *var) { - if (!strcasecmp(var+ofs, "plain")) + if (!strcasecmp(var, "plain")) return DIFF_PLAIN; - if (!strcasecmp(var+ofs, "meta")) + if (!strcasecmp(var, "meta")) return DIFF_METAINFO; - if (!strcasecmp(var+ofs, "frag")) + if (!strcasecmp(var, "frag")) return DIFF_FRAGINFO; - if (!strcasecmp(var+ofs, "old")) + if (!strcasecmp(var, "old")) return DIFF_FILE_OLD; - if (!strcasecmp(var+ofs, "new")) + if (!strcasecmp(var, "new")) return DIFF_FILE_NEW; - if (!strcasecmp(var+ofs, "commit")) + if (!strcasecmp(var, "commit")) return DIFF_COMMIT; - if (!strcasecmp(var+ofs, "whitespace")) + if (!strcasecmp(var, "whitespace")) return DIFF_WHITESPACE; - if (!strcasecmp(var+ofs, "func")) + if (!strcasecmp(var, "func")) return DIFF_FUNCINFO; return -1; } @@ -231,6 +231,8 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) int git_diff_basic_config(const char *var, const char *value, void *cb) { + const char *name; + if (!strcmp(var, "diff.renamelimit")) { diff_rename_limit_default = git_config_int(var, value); return 0; @@ -239,8 +241,9 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) if (userdiff_config(var, value) < 0) return -1; - if (starts_with(var, "diff.color.") || starts_with(var, "color.diff.")) { - int slot = parse_diff_color_slot(var, 11); + if (skip_prefix(var, "diff.color.", &name) || + skip_prefix(var, "color.diff.", &name)) { + int slot = parse_diff_color_slot(name); if (slot < 0) return 0; if (!value) @@ -522,9 +525,9 @@ static void emit_hunk_header(struct emit_callback *ecbdata, ep += 2; /* skip over @@ */ /* The hunk header in fraginfo color */ - strbuf_add(&msgbuf, frag, strlen(frag)); + strbuf_addstr(&msgbuf, frag); strbuf_add(&msgbuf, line, ep - line); - strbuf_add(&msgbuf, reset, strlen(reset)); + strbuf_addstr(&msgbuf, reset); /* * trailing "\r\n" @@ -538,15 +541,15 @@ static void emit_hunk_header(struct emit_callback *ecbdata, if (*ep != ' ' && *ep != '\t') break; if (ep != cp) { - strbuf_add(&msgbuf, plain, strlen(plain)); + strbuf_addstr(&msgbuf, plain); strbuf_add(&msgbuf, cp, ep - cp); - strbuf_add(&msgbuf, reset, strlen(reset)); + strbuf_addstr(&msgbuf, reset); } if (ep < line + len) { - strbuf_add(&msgbuf, func, strlen(func)); + strbuf_addstr(&msgbuf, func); strbuf_add(&msgbuf, ep, line + len - ep); - strbuf_add(&msgbuf, reset, strlen(reset)); + strbuf_addstr(&msgbuf, reset); } strbuf_add(&msgbuf, line + len, org_len - len); @@ -2341,6 +2344,7 @@ static void builtin_diff(const char *name_a, } else { /* Crazy xdl interfaces.. */ const char *diffopts = getenv("GIT_DIFF_OPTS"); + const char *v; xpparam_t xpp; xdemitconf_t xecfg; struct emit_callback ecbdata; @@ -2379,10 +2383,10 @@ static void builtin_diff(const char *name_a, xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags); if (!diffopts) ; - else if (starts_with(diffopts, "--unified=")) - xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); - else if (starts_with(diffopts, "-u")) - xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); + else if (skip_prefix(diffopts, "--unified=", &v)) + xecfg.ctxlen = strtoul(v, NULL, 10); + else if (skip_prefix(diffopts, "-u", &v)) + xecfg.ctxlen = strtoul(v, NULL, 10); if (o->word_diff) init_diff_words_data(&ecbdata, o, one, two); xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, @@ -2880,6 +2884,16 @@ static struct diff_tempfile *prepare_temp_file(const char *name, return temp; } +static void add_external_diff_name(struct argv_array *argv, + const char *name, + struct diff_filespec *df) +{ + struct diff_tempfile *temp = prepare_temp_file(name, df); + argv_array_push(argv, temp->name); + argv_array_push(argv, temp->hex); + argv_array_push(argv, temp->mode); +} + /* An external diff command takes: * * diff-cmd name infile1 infile1-sha1 infile1-mode \ @@ -2896,48 +2910,32 @@ static void run_external_diff(const char *pgm, struct diff_options *o) { struct argv_array argv = ARGV_ARRAY_INIT; - int retval; + struct argv_array env = ARGV_ARRAY_INIT; struct diff_queue_struct *q = &diff_queued_diff; - const char *env[3] = { NULL }; - char env_counter[50]; - char env_total[50]; + + argv_array_push(&argv, pgm); + argv_array_push(&argv, name); if (one && two) { - struct diff_tempfile *temp_one, *temp_two; - const char *othername = (other ? other : name); - temp_one = prepare_temp_file(name, one); - temp_two = prepare_temp_file(othername, two); - argv_array_push(&argv, pgm); - argv_array_push(&argv, name); - argv_array_push(&argv, temp_one->name); - argv_array_push(&argv, temp_one->hex); - argv_array_push(&argv, temp_one->mode); - argv_array_push(&argv, temp_two->name); - argv_array_push(&argv, temp_two->hex); - argv_array_push(&argv, temp_two->mode); - if (other) { + add_external_diff_name(&argv, name, one); + if (!other) + add_external_diff_name(&argv, name, two); + else { + add_external_diff_name(&argv, other, two); argv_array_push(&argv, other); argv_array_push(&argv, xfrm_msg); } - } else { - argv_array_push(&argv, pgm); - argv_array_push(&argv, name); } - fflush(NULL); - env[0] = env_counter; - snprintf(env_counter, sizeof(env_counter), "GIT_DIFF_PATH_COUNTER=%d", - ++o->diff_path_counter); - env[1] = env_total; - snprintf(env_total, sizeof(env_total), "GIT_DIFF_PATH_TOTAL=%d", q->nr); + argv_array_pushf(&env, "GIT_DIFF_PATH_COUNTER=%d", ++o->diff_path_counter); + argv_array_pushf(&env, "GIT_DIFF_PATH_TOTAL=%d", q->nr); + + if (run_command_v_opt_cd_env(argv.argv, RUN_USING_SHELL, NULL, env.argv)) + die(_("external diff died, stopping at %s"), name); - retval = run_command_v_opt_cd_env(argv.argv, RUN_USING_SHELL, NULL, env); remove_tempfile(); argv_array_clear(&argv); - if (retval) { - fprintf(stderr, "external diff died, stopping at %s.\n", name); - exit(1); - } + argv_array_clear(&env); } static int similarity_index(struct diff_filepair *p) @@ -3205,6 +3203,7 @@ void diff_setup(struct diff_options *options) options->context = diff_context_default; DIFF_OPT_SET(options, RENAME_EMPTY); + /* pathchange left =NULL by default */ options->change = diff_change; options->add_remove = diff_addremove; options->use_color = diff_use_color_default; @@ -3396,12 +3395,10 @@ int parse_long_opt(const char *opt, const char **argv, const char **optarg) { const char *arg = argv[0]; - if (arg[0] != '-' || arg[1] != '-') + if (!skip_prefix(arg, "--", &arg)) return 0; - arg += strlen("--"); - if (!starts_with(arg, opt)) + if (!skip_prefix(arg, opt, &arg)) return 0; - arg += strlen(opt); if (*arg == '=') { /* stuck form: --option=value */ *optarg = arg + 1; return 1; @@ -3425,13 +3422,13 @@ static int stat_opt(struct diff_options *options, const char **av) int count = options->stat_count; int argcount = 1; - arg += strlen("--stat"); + if (!skip_prefix(arg, "--stat", &arg)) + die("BUG: stat option does not begin with --stat: %s", arg); end = (char *)arg; switch (*arg) { case '-': - if (starts_with(arg, "-width")) { - arg += strlen("-width"); + if (skip_prefix(arg, "-width", &arg)) { if (*arg == '=') width = strtoul(arg + 1, &end, 10); else if (!*arg && !av[1]) @@ -3440,8 +3437,7 @@ static int stat_opt(struct diff_options *options, const char **av) width = strtoul(av[1], &end, 10); argcount = 2; } - } else if (starts_with(arg, "-name-width")) { - arg += strlen("-name-width"); + } else if (skip_prefix(arg, "-name-width", &arg)) { if (*arg == '=') name_width = strtoul(arg + 1, &end, 10); else if (!*arg && !av[1]) @@ -3450,8 +3446,7 @@ static int stat_opt(struct diff_options *options, const char **av) name_width = strtoul(av[1], &end, 10); argcount = 2; } - } else if (starts_with(arg, "-graph-width")) { - arg += strlen("-graph-width"); + } else if (skip_prefix(arg, "-graph-width", &arg)) { if (*arg == '=') graph_width = strtoul(arg + 1, &end, 10); else if (!*arg && !av[1]) @@ -3460,8 +3455,7 @@ static int stat_opt(struct diff_options *options, const char **av) graph_width = strtoul(av[1], &end, 10); argcount = 2; } - } else if (starts_with(arg, "-count")) { - arg += strlen("-count"); + } else if (skip_prefix(arg, "-count", &arg)) { if (*arg == '=') count = strtoul(arg + 1, &end, 10); else if (!*arg && !av[1]) @@ -3614,17 +3608,17 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_SHORTSTAT; else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat")) return parse_dirstat_opt(options, ""); - else if (starts_with(arg, "-X")) - return parse_dirstat_opt(options, arg + 2); - else if (starts_with(arg, "--dirstat=")) - return parse_dirstat_opt(options, arg + 10); + else if (skip_prefix(arg, "-X", &arg)) + return parse_dirstat_opt(options, arg); + else if (skip_prefix(arg, "--dirstat=", &arg)) + return parse_dirstat_opt(options, arg); else if (!strcmp(arg, "--cumulative")) return parse_dirstat_opt(options, "cumulative"); else if (!strcmp(arg, "--dirstat-by-file")) return parse_dirstat_opt(options, "files"); - else if (starts_with(arg, "--dirstat-by-file=")) { + else if (skip_prefix(arg, "--dirstat-by-file=", &arg)) { parse_dirstat_opt(options, "files"); - return parse_dirstat_opt(options, arg + 18); + return parse_dirstat_opt(options, arg); } else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; @@ -3674,9 +3668,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, RENAME_EMPTY); else if (!strcmp(arg, "--relative")) DIFF_OPT_SET(options, RELATIVE_NAME); - else if (starts_with(arg, "--relative=")) { + else if (skip_prefix(arg, "--relative=", &arg)) { DIFF_OPT_SET(options, RELATIVE_NAME); - options->prefix = arg + 11; + options->prefix = arg; } /* xdiff options */ @@ -3727,8 +3721,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, FOLLOW_RENAMES); else if (!strcmp(arg, "--color")) options->use_color = 1; - else if (starts_with(arg, "--color=")) { - int value = git_config_colorbool(NULL, arg+8); + else if (skip_prefix(arg, "--color=", &arg)) { + int value = git_config_colorbool(NULL, arg); if (value < 0) return error("option `color' expects \"always\", \"auto\", or \"never\""); options->use_color = value; @@ -3739,29 +3733,28 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } - else if (starts_with(arg, "--color-words=")) { + else if (skip_prefix(arg, "--color-words=", &arg)) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; - options->word_regex = arg + 14; + options->word_regex = arg; } else if (!strcmp(arg, "--word-diff")) { if (options->word_diff == DIFF_WORDS_NONE) options->word_diff = DIFF_WORDS_PLAIN; } - else if (starts_with(arg, "--word-diff=")) { - const char *type = arg + 12; - if (!strcmp(type, "plain")) + else if (skip_prefix(arg, "--word-diff=", &arg)) { + if (!strcmp(arg, "plain")) options->word_diff = DIFF_WORDS_PLAIN; - else if (!strcmp(type, "color")) { + else if (!strcmp(arg, "color")) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } - else if (!strcmp(type, "porcelain")) + else if (!strcmp(arg, "porcelain")) options->word_diff = DIFF_WORDS_PORCELAIN; - else if (!strcmp(type, "none")) + else if (!strcmp(arg, "none")) options->word_diff = DIFF_WORDS_NONE; else - die("bad --word-diff argument: %s", type); + die("bad --word-diff argument: %s", arg); } else if ((argcount = parse_long_opt("word-diff-regex", av, &optarg))) { if (options->word_diff == DIFF_WORDS_NONE) @@ -3784,13 +3777,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--ignore-submodules")) { DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(options, "all"); - } else if (starts_with(arg, "--ignore-submodules=")) { + } else if (skip_prefix(arg, "--ignore-submodules=", &arg)) { DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); - handle_ignore_submodules_arg(options, arg + 20); + handle_ignore_submodules_arg(options, arg); } else if (!strcmp(arg, "--submodule")) DIFF_OPT_SET(options, SUBMODULE_LOG); - else if (starts_with(arg, "--submodule=")) - return parse_submodule_opt(options, arg + 12); + else if (skip_prefix(arg, "--submodule=", &arg)) + return parse_submodule_opt(options, arg); /* misc options */ else if (!strcmp(arg, "-z")) @@ -3825,8 +3818,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if (!strcmp(arg, "--abbrev")) options->abbrev = DEFAULT_ABBREV; - else if (starts_with(arg, "--abbrev=")) { - options->abbrev = strtoul(arg + 9, NULL, 10); + else if (skip_prefix(arg, "--abbrev=", &arg)) { + options->abbrev = strtoul(arg, NULL, 10); if (options->abbrev < MINIMUM_ABBREV) options->abbrev = MINIMUM_ABBREV; else if (40 < options->abbrev) @@ -3907,16 +3900,13 @@ static int diff_scoreopt_parse(const char *opt) cmd = *opt++; if (cmd == '-') { /* convert the long-form arguments into short-form versions */ - if (starts_with(opt, "break-rewrites")) { - opt += strlen("break-rewrites"); + if (skip_prefix(opt, "break-rewrites", &opt)) { if (*opt == 0 || *opt++ == '=') cmd = 'B'; - } else if (starts_with(opt, "find-copies")) { - opt += strlen("find-copies"); + } else if (skip_prefix(opt, "find-copies", &opt)) { if (*opt == 0 || *opt++ == '=') cmd = 'C'; - } else if (starts_with(opt, "find-renames")) { - opt += strlen("find-renames"); + } else if (skip_prefix(opt, "find-renames", &opt)) { if (*opt == 0 || *opt++ == '=') cmd = 'M'; } @@ -4752,6 +4742,7 @@ void diffcore_fix_diff_index(struct diff_options *options) void diffcore_std(struct diff_options *options) { + /* NOTE please keep the following in sync with diff_tree_combined() */ if (options->skip_stat_unmatch) diffcore_skip_stat_unmatch(options); if (!options->found_follow) { @@ -15,6 +15,10 @@ struct diff_filespec; struct userdiff_driver; struct sha1_array; struct commit; +struct combine_diff_path; + +typedef int (*pathchange_fn_t)(struct diff_options *options, + struct combine_diff_path *path); typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, @@ -157,6 +161,7 @@ struct diff_options { int close_file; struct pathspec pathspec; + pathchange_fn_t pathchange; change_fn_t change; add_remove_fn_t add_remove; diff_format_fn_t format_callback; @@ -189,8 +194,10 @@ const char *diff_line_prefix(struct diff_options *); extern const char mime_boundary_leader[]; -extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2, - const char *base, struct diff_options *opt); +extern struct combine_diff_path *diff_tree_paths( + struct combine_diff_path *p, const unsigned char *sha1, + const unsigned char **parent_sha1, int nparent, + struct strbuf *base, struct diff_options *opt); extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt); extern int diff_root_tree_sha1(const unsigned char *new, const char *base, diff --git a/diffcore-rename.c b/diffcore-rename.c index 749a35d2..2e44a374 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -242,14 +242,12 @@ struct file_similarity { static unsigned int hash_filespec(struct diff_filespec *filespec) { - unsigned int hash; if (!filespec->sha1_valid) { if (diff_populate_filespec(filespec, 0)) return 0; hash_sha1_file(filespec->data, filespec->size, "blob", filespec->sha1); } - memcpy(&hash, filespec->sha1, sizeof(hash)); - return hash; + return sha1hash(filespec->sha1); } static int find_identical_files(struct hashmap *srcs, @@ -259,15 +257,14 @@ static int find_identical_files(struct hashmap *srcs, int renames = 0; struct diff_filespec *target = rename_dst[dst_index].two; - struct file_similarity *p, *best, dst; + struct file_similarity *p, *best = NULL; int i = 100, best_score = -1; /* * Find the best source match for specified destination. */ - best = NULL; - hashmap_entry_init(&dst, hash_filespec(target)); - for (p = hashmap_get(srcs, &dst, NULL); p; p = hashmap_get_next(srcs, p)) { + p = hashmap_get_from_hash(srcs, hash_filespec(target), NULL); + for (; p; p = hashmap_get_next(srcs, p)) { int score; struct diff_filespec *source = p->filespec; @@ -557,8 +557,7 @@ int add_excludes_from_file_to_list(const char *fname, buf = xrealloc(buf, size+1); buf[size++] = '\n'; } - } - else { + } else { size = xsize_t(st.st_size); if (size == 0) { close(fd); @@ -793,17 +792,19 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) group = &dir->exclude_list_group[EXC_DIRS]; - /* Pop the exclude lists from the EXCL_DIRS exclude_list_group + /* + * Pop the exclude lists from the EXCL_DIRS exclude_list_group * which originate from directories not in the prefix of the - * path being checked. */ + * path being checked. + */ while ((stk = dir->exclude_stack) != NULL) { if (stk->baselen <= baselen && - !strncmp(dir->basebuf, base, stk->baselen)) + !strncmp(dir->basebuf.buf, base, stk->baselen)) break; el = &group->el[dir->exclude_stack->exclude_ix]; dir->exclude_stack = stk->prev; dir->exclude = NULL; - free((char *)el->src); /* see strdup() below */ + free((char *)el->src); /* see strbuf_detach() below */ clear_exclude_list(el); free(stk); group->nr--; @@ -813,8 +814,17 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) if (dir->exclude) return; + /* + * Lazy initialization. All call sites currently just + * memset(dir, 0, sizeof(*dir)) before use. Changing all of + * them seems lots of work for little benefit. + */ + if (!dir->basebuf.buf) + strbuf_init(&dir->basebuf, PATH_MAX); + /* Read from the parent directories and push them down. */ current = stk ? stk->baselen : -1; + strbuf_setlen(&dir->basebuf, current < 0 ? 0 : current); while (current < baselen) { struct exclude_stack *stk = xcalloc(1, sizeof(*stk)); const char *cp; @@ -822,8 +832,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) if (current < 0) { cp = base; current = 0; - } - else { + } else { cp = strchr(base + current + 1, '/'); if (!cp) die("oops in prep_exclude"); @@ -833,48 +842,47 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) stk->baselen = cp - base; stk->exclude_ix = group->nr; el = add_exclude_list(dir, EXC_DIRS, NULL); - memcpy(dir->basebuf + current, base + current, - stk->baselen - current); + strbuf_add(&dir->basebuf, base + current, stk->baselen - current); + assert(stk->baselen == dir->basebuf.len); /* Abort if the directory is excluded */ if (stk->baselen) { int dt = DT_DIR; - dir->basebuf[stk->baselen - 1] = 0; + dir->basebuf.buf[stk->baselen - 1] = 0; dir->exclude = last_exclude_matching_from_lists(dir, - dir->basebuf, stk->baselen - 1, - dir->basebuf + current, &dt); - dir->basebuf[stk->baselen - 1] = '/'; + dir->basebuf.buf, stk->baselen - 1, + dir->basebuf.buf + current, &dt); + dir->basebuf.buf[stk->baselen - 1] = '/'; if (dir->exclude && dir->exclude->flags & EXC_FLAG_NEGATIVE) dir->exclude = NULL; if (dir->exclude) { - dir->basebuf[stk->baselen] = 0; dir->exclude_stack = stk; return; } } - /* Try to read per-directory file unless path is too long */ - if (dir->exclude_per_dir && - stk->baselen + strlen(dir->exclude_per_dir) < PATH_MAX) { - strcpy(dir->basebuf + stk->baselen, - dir->exclude_per_dir); + /* Try to read per-directory file */ + if (dir->exclude_per_dir) { /* * dir->basebuf gets reused by the traversal, but we * need fname to remain unchanged to ensure the src * member of each struct exclude correctly * back-references its source file. Other invocations * of add_exclude_list provide stable strings, so we - * strdup() and free() here in the caller. + * strbuf_detach() and free() here in the caller. */ - el->src = strdup(dir->basebuf); - add_excludes_from_file_to_list(dir->basebuf, - dir->basebuf, stk->baselen, el, 1); + struct strbuf sb = STRBUF_INIT; + strbuf_addbuf(&sb, &dir->basebuf); + strbuf_addstr(&sb, dir->exclude_per_dir); + el->src = strbuf_detach(&sb, NULL); + add_excludes_from_file_to_list(el->src, el->src, + stk->baselen, el, 1); } dir->exclude_stack = stk; current = stk->baselen; } - dir->basebuf[baselen] = '\0'; + strbuf_setlen(&dir->basebuf, baselen); } /* @@ -1354,8 +1362,7 @@ static int cmp_name(const void *p1, const void *p2) const struct dir_entry *e1 = *(const struct dir_entry **)p1; const struct dir_entry *e2 = *(const struct dir_entry **)p2; - return cache_name_compare(e1->name, e1->len, - e2->name, e2->len); + return name_compare(e1->name, e1->len, e2->name, e2->len); } static struct path_simplify *create_simplify(const char **pathspec) @@ -1672,4 +1679,5 @@ void clear_directory(struct dir_struct *dir) free(stk); stk = prev; } + strbuf_release(&dir->basebuf); } @@ -15,6 +15,27 @@ struct dir_entry { #define EXC_FLAG_MUSTBEDIR 8 #define EXC_FLAG_NEGATIVE 16 +struct exclude { + /* + * This allows callers of last_exclude_matching() etc. + * to determine the origin of the matching pattern. + */ + struct exclude_list *el; + + const char *pattern; + int patternlen; + int nowildcardlen; + const char *base; + int baselen; + int flags; + + /* + * Counting starts from 1 for line numbers in ignore files, + * and from -1 decrementing for patterns from CLI args. + */ + int srcpos; +}; + /* * Each excludes file will be parsed into a fresh exclude_list which * is appended to the relevant exclude_list_group (either EXC_DIRS or @@ -32,26 +53,7 @@ struct exclude_list { /* origin of list, e.g. path to filename, or descriptive string */ const char *src; - struct exclude { - /* - * This allows callers of last_exclude_matching() etc. - * to determine the origin of the matching pattern. - */ - struct exclude_list *el; - - const char *pattern; - int patternlen; - int nowildcardlen; - const char *base; - int baselen; - int flags; - - /* - * Counting starts from 1 for line numbers in ignore files, - * and from -1 decrementing for patterns from CLI args. - */ - int srcpos; - } **excludes; + struct exclude **excludes; }; /* @@ -117,7 +119,7 @@ struct dir_struct { */ struct exclude_stack *exclude_stack; struct exclude *exclude; - char basebuf[PATH_MAX]; + struct strbuf basebuf; }; /* @@ -210,9 +210,12 @@ static int write_entry(struct cache_entry *ce, finish: if (state->refresh_cache) { + assert(state->istate); if (!fstat_done) lstat(ce->name, &st); fill_stat_cache_info(ce, &st); + ce->ce_flags |= CE_UPDATE_IN_BASE; + state->istate->cache_changed |= CE_ENTRY_CHANGED; } return 0; } diff --git a/environment.c b/environment.c index 9daa0ba4..565f6529 100644 --- a/environment.c +++ b/environment.c @@ -37,7 +37,7 @@ int core_compression_seen; int fsync_object_files; size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; -size_t delta_base_cache_limit = 16 * 1024 * 1024; +size_t delta_base_cache_limit = 96 * 1024 * 1024; unsigned long big_file_threshold = 512 * 1024 * 1024; const char *pager_program; int pager_use_color = 1; @@ -64,24 +64,15 @@ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ struct startup_info *startup_info; unsigned long pack_size_limit_cfg; -#ifndef PROTECT_HFS_DEFAULT -#define PROTECT_HFS_DEFAULT 0 -#endif -int protect_hfs = PROTECT_HFS_DEFAULT; - -#ifndef PROTECT_NTFS_DEFAULT -#define PROTECT_NTFS_DEFAULT 0 -#endif -int protect_ntfs = PROTECT_NTFS_DEFAULT; - /* * The character that begins a commented line in user-editable file * that is subject to stripspace. */ char comment_line_char = '#'; +int auto_comment_line_char; /* Parallel index stat data preload? */ -int core_preload_index = 0; +int core_preload_index = 1; /* This is set by setup_git_dir_gently() and/or git_default_config() */ char *git_work_tree_cfg; @@ -133,6 +124,12 @@ static char *expand_namespace(const char *raw_namespace) return strbuf_detach(&buf, NULL); } +static char *git_path_from_env(const char *envvar, const char *path) +{ + const char *value = getenv(envvar); + return value ? xstrdup(value) : git_pathdup("%s", path); +} + static void setup_git_env(void) { const char *gitfile; @@ -143,19 +140,9 @@ static void setup_git_env(void) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; gitfile = read_gitfile(git_dir); git_dir = xstrdup(gitfile ? gitfile : git_dir); - git_object_dir = getenv(DB_ENVIRONMENT); - if (!git_object_dir) { - git_object_dir = xmalloc(strlen(git_dir) + 9); - sprintf(git_object_dir, "%s/objects", git_dir); - } - git_index_file = getenv(INDEX_ENVIRONMENT); - if (!git_index_file) { - git_index_file = xmalloc(strlen(git_dir) + 7); - sprintf(git_index_file, "%s/index", git_dir); - } - git_graft_file = getenv(GRAFT_ENVIRONMENT); - if (!git_graft_file) - git_graft_file = git_pathdup("info/grafts"); + git_object_dir = git_path_from_env(DB_ENVIRONMENT, "objects"); + git_index_file = git_path_from_env(INDEX_ENVIRONMENT, "index"); + git_graft_file = git_path_from_env(GRAFT_ENVIRONMENT, "info/grafts"); if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT)) check_replace_refs = 0; namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT)); diff --git a/ewah/ewah_io.c b/ewah/ewah_io.c index f7f700ef..1c2d7afd 100644 --- a/ewah/ewah_io.c +++ b/ewah/ewah_io.c @@ -110,9 +110,9 @@ int ewah_serialize(struct ewah_bitmap *self, int fd) return ewah_serialize_to(self, write_helper, (void *)(intptr_t)fd); } -int ewah_read_mmap(struct ewah_bitmap *self, void *map, size_t len) +int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len) { - uint8_t *ptr = map; + const uint8_t *ptr = map; size_t i; self->bit_size = get_be32(ptr); diff --git a/ewah/ewok.h b/ewah/ewok.h index 43adeb5c..f6ad190a 100644 --- a/ewah/ewok.h +++ b/ewah/ewok.h @@ -99,8 +99,7 @@ int ewah_serialize(struct ewah_bitmap *self, int fd); int ewah_serialize_native(struct ewah_bitmap *self, int fd); int ewah_deserialize(struct ewah_bitmap *self, int fd); -int ewah_read_mmap(struct ewah_bitmap *self, void *map, size_t len); -int ewah_read_mmap_native(struct ewah_bitmap *self, void *map, size_t len); +int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len); uint32_t ewah_checksum(struct ewah_bitmap *self); diff --git a/fast-import.c b/fast-import.c index fb4738d3..d73f58cb 100644 --- a/fast-import.c +++ b/fast-import.c @@ -248,6 +248,7 @@ struct branch { uintmax_t last_commit; uintmax_t num_notes; unsigned active : 1; + unsigned delete : 1; unsigned pack_id : PACK_ID_BITS; unsigned char sha1[20]; }; @@ -370,8 +371,8 @@ static volatile sig_atomic_t checkpoint_requested; static int cat_blob_fd = STDOUT_FILENO; static void parse_argv(void); -static void parse_cat_blob(void); -static void parse_ls(struct branch *b); +static void parse_cat_blob(const char *p); +static void parse_ls(const char *p, struct branch *b); static void write_branch_report(FILE *rpt, struct branch *b) { @@ -1681,10 +1682,13 @@ static int update_branch(struct branch *b) struct ref_lock *lock; unsigned char old_sha1[20]; - if (is_null_sha1(b->sha1)) - return 0; if (read_ref(b->name, old_sha1)) hashclr(old_sha1); + if (is_null_sha1(b->sha1)) { + if (b->delete) + delete_ref(b->name, old_sha1, 0); + return 0; + } lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL); if (!lock) return error("Unable to lock %s", b->name); @@ -1857,6 +1861,8 @@ static int read_next_command(void) } for (;;) { + const char *p; + if (unread_command_buf) { unread_command_buf = 0; } else { @@ -1889,8 +1895,8 @@ static int read_next_command(void) rc->prev->next = rc; cmd_tail = rc; } - if (starts_with(command_buf.buf, "cat-blob ")) { - parse_cat_blob(); + if (skip_prefix(command_buf.buf, "cat-blob ", &p)) { + parse_cat_blob(p); continue; } if (command_buf.buf[0] == '#') @@ -1908,8 +1914,9 @@ static void skip_optional_lf(void) static void parse_mark(void) { - if (starts_with(command_buf.buf, "mark :")) { - next_mark = strtoumax(command_buf.buf + 6, NULL, 10); + const char *v; + if (skip_prefix(command_buf.buf, "mark :", &v)) { + next_mark = strtoumax(v, NULL, 10); read_next_command(); } else @@ -1918,14 +1925,15 @@ static void parse_mark(void) static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res) { + const char *data; strbuf_reset(sb); - if (!starts_with(command_buf.buf, "data ")) + if (!skip_prefix(command_buf.buf, "data ", &data)) die("Expected 'data n' command, found: %s", command_buf.buf); - if (starts_with(command_buf.buf + 5, "<<")) { - char *term = xstrdup(command_buf.buf + 5 + 2); - size_t term_len = command_buf.len - 5 - 2; + if (skip_prefix(data, "<<", &data)) { + char *term = xstrdup(data); + size_t term_len = command_buf.len - (data - command_buf.buf); strbuf_detach(&command_buf, NULL); for (;;) { @@ -1940,7 +1948,7 @@ static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res) free(term); } else { - uintmax_t len = strtoumax(command_buf.buf + 5, NULL, 10); + uintmax_t len = strtoumax(data, NULL, 10); size_t n = 0, length = (size_t)len; if (limit && limit < len) { @@ -2261,15 +2269,14 @@ static uintmax_t parse_mark_ref_space(const char **p) char *end; mark = parse_mark_ref(*p, &end); - if (*end != ' ') + if (*end++ != ' ') die("Missing space after mark: %s", command_buf.buf); *p = end; return mark; } -static void file_change_m(struct branch *b) +static void file_change_m(const char *p, struct branch *b) { - const char *p = command_buf.buf + 2; static struct strbuf uq = STRBUF_INIT; const char *endp; struct object_entry *oe; @@ -2297,20 +2304,17 @@ static void file_change_m(struct branch *b) if (*p == ':') { oe = find_mark(parse_mark_ref_space(&p)); hashcpy(sha1, oe->idx.sha1); - } else if (starts_with(p, "inline ")) { + } else if (skip_prefix(p, "inline ", &p)) { inline_data = 1; oe = NULL; /* not used with inline_data, but makes gcc happy */ - p += strlen("inline"); /* advance to space */ } else { if (get_sha1_hex(p, sha1)) die("Invalid dataref: %s", command_buf.buf); oe = find_object(sha1); p += 40; - if (*p != ' ') + if (*p++ != ' ') die("Missing space after SHA1: %s", command_buf.buf); } - assert(*p == ' '); - p++; /* skip space */ strbuf_reset(&uq); if (!unquote_c_style(&uq, p, &endp)) { @@ -2320,7 +2324,7 @@ static void file_change_m(struct branch *b) } /* Git does not track empty, non-toplevel directories. */ - if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) { + if (S_ISDIR(mode) && !hashcmp(sha1, EMPTY_TREE_SHA1_BIN) && *p) { tree_content_remove(&b->branch_tree, p, NULL, 0); return; } @@ -2370,9 +2374,8 @@ static void file_change_m(struct branch *b) tree_content_set(&b->branch_tree, p, sha1, mode, NULL); } -static void file_change_d(struct branch *b) +static void file_change_d(const char *p, struct branch *b) { - const char *p = command_buf.buf + 2; static struct strbuf uq = STRBUF_INIT; const char *endp; @@ -2385,15 +2388,14 @@ static void file_change_d(struct branch *b) tree_content_remove(&b->branch_tree, p, NULL, 1); } -static void file_change_cr(struct branch *b, int rename) +static void file_change_cr(const char *s, struct branch *b, int rename) { - const char *s, *d; + const char *d; static struct strbuf s_uq = STRBUF_INIT; static struct strbuf d_uq = STRBUF_INIT; const char *endp; struct tree_entry leaf; - s = command_buf.buf + 2; strbuf_reset(&s_uq); if (!unquote_c_style(&s_uq, s, &endp)) { if (*endp != ' ') @@ -2438,9 +2440,8 @@ static void file_change_cr(struct branch *b, int rename) leaf.tree); } -static void note_change_n(struct branch *b, unsigned char *old_fanout) +static void note_change_n(const char *p, struct branch *b, unsigned char *old_fanout) { - const char *p = command_buf.buf + 2; static struct strbuf uq = STRBUF_INIT; struct object_entry *oe; struct branch *s; @@ -2470,20 +2471,17 @@ static void note_change_n(struct branch *b, unsigned char *old_fanout) if (*p == ':') { oe = find_mark(parse_mark_ref_space(&p)); hashcpy(sha1, oe->idx.sha1); - } else if (starts_with(p, "inline ")) { + } else if (skip_prefix(p, "inline ", &p)) { inline_data = 1; oe = NULL; /* not used with inline_data, but makes gcc happy */ - p += strlen("inline"); /* advance to space */ } else { if (get_sha1_hex(p, sha1)) die("Invalid dataref: %s", command_buf.buf); oe = find_object(sha1); p += 40; - if (*p != ' ') + if (*p++ != ' ') die("Missing space after SHA1: %s", command_buf.buf); } - assert(*p == ' '); - p++; /* skip space */ /* <commit-ish> */ s = lookup_branch(p); @@ -2581,7 +2579,7 @@ static int parse_from(struct branch *b) const char *from; struct branch *s; - if (!starts_with(command_buf.buf, "from ")) + if (!skip_prefix(command_buf.buf, "from ", &from)) return 0; if (b->branch_tree.tree) { @@ -2589,7 +2587,6 @@ static int parse_from(struct branch *b) b->branch_tree.tree = NULL; } - from = strchr(command_buf.buf, ' ') + 1; s = lookup_branch(from); if (b == s) die("Can't create a branch from itself: %s", b->name); @@ -2611,8 +2608,11 @@ static int parse_from(struct branch *b) free(buf); } else parse_from_existing(b); - } else if (!get_sha1(from, b->sha1)) + } else if (!get_sha1(from, b->sha1)) { parse_from_existing(b); + if (is_null_sha1(b->sha1)) + b->delete = 1; + } else die("Invalid ref name or SHA1 expression: %s", from); @@ -2627,8 +2627,7 @@ static struct hash_list *parse_merge(unsigned int *count) struct branch *s; *count = 0; - while (starts_with(command_buf.buf, "merge ")) { - from = strchr(command_buf.buf, ' ') + 1; + while (skip_prefix(command_buf.buf, "merge ", &from)) { n = xmalloc(sizeof(*n)); s = lookup_branch(from); if (s) @@ -2659,31 +2658,29 @@ static struct hash_list *parse_merge(unsigned int *count) return list; } -static void parse_new_commit(void) +static void parse_new_commit(const char *arg) { static struct strbuf msg = STRBUF_INIT; struct branch *b; - char *sp; char *author = NULL; char *committer = NULL; struct hash_list *merge_list = NULL; unsigned int merge_count; unsigned char prev_fanout, new_fanout; + const char *v; - /* Obtain the branch name from the rest of our command */ - sp = strchr(command_buf.buf, ' ') + 1; - b = lookup_branch(sp); + b = lookup_branch(arg); if (!b) - b = new_branch(sp); + b = new_branch(arg); read_next_command(); parse_mark(); - if (starts_with(command_buf.buf, "author ")) { - author = parse_ident(command_buf.buf + 7); + if (skip_prefix(command_buf.buf, "author ", &v)) { + author = parse_ident(v); read_next_command(); } - if (starts_with(command_buf.buf, "committer ")) { - committer = parse_ident(command_buf.buf + 10); + if (skip_prefix(command_buf.buf, "committer ", &v)) { + committer = parse_ident(v); read_next_command(); } if (!committer) @@ -2703,20 +2700,20 @@ static void parse_new_commit(void) /* file_change* */ while (command_buf.len > 0) { - if (starts_with(command_buf.buf, "M ")) - file_change_m(b); - else if (starts_with(command_buf.buf, "D ")) - file_change_d(b); - else if (starts_with(command_buf.buf, "R ")) - file_change_cr(b, 1); - else if (starts_with(command_buf.buf, "C ")) - file_change_cr(b, 0); - else if (starts_with(command_buf.buf, "N ")) - note_change_n(b, &prev_fanout); + if (skip_prefix(command_buf.buf, "M ", &v)) + file_change_m(v, b); + else if (skip_prefix(command_buf.buf, "D ", &v)) + file_change_d(v, b); + else if (skip_prefix(command_buf.buf, "R ", &v)) + file_change_cr(v, b, 1); + else if (skip_prefix(command_buf.buf, "C ", &v)) + file_change_cr(v, b, 0); + else if (skip_prefix(command_buf.buf, "N ", &v)) + note_change_n(v, b, &prev_fanout); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); - else if (starts_with(command_buf.buf, "ls ")) - parse_ls(b); + else if (skip_prefix(command_buf.buf, "ls ", &v)) + parse_ls(v, b); else { unread_command_buf = 1; break; @@ -2759,10 +2756,9 @@ static void parse_new_commit(void) b->last_commit = object_count_by_type[OBJ_COMMIT]; } -static void parse_new_tag(void) +static void parse_new_tag(const char *arg) { static struct strbuf msg = STRBUF_INIT; - char *sp; const char *from; char *tagger; struct branch *s; @@ -2770,12 +2766,11 @@ static void parse_new_tag(void) uintmax_t from_mark = 0; unsigned char sha1[20]; enum object_type type; + const char *v; - /* Obtain the new tag name from the rest of our command */ - sp = strchr(command_buf.buf, ' ') + 1; t = pool_alloc(sizeof(struct tag)); memset(t, 0, sizeof(struct tag)); - t->name = pool_strdup(sp); + t->name = pool_strdup(arg); if (last_tag) last_tag->next_tag = t; else @@ -2784,9 +2779,8 @@ static void parse_new_tag(void) read_next_command(); /* from ... */ - if (!starts_with(command_buf.buf, "from ")) + if (!skip_prefix(command_buf.buf, "from ", &from)) die("Expected from command, got %s", command_buf.buf); - from = strchr(command_buf.buf, ' ') + 1; s = lookup_branch(from); if (s) { if (is_null_sha1(s->sha1)) @@ -2812,8 +2806,8 @@ static void parse_new_tag(void) read_next_command(); /* tagger ... */ - if (starts_with(command_buf.buf, "tagger ")) { - tagger = parse_ident(command_buf.buf + 7); + if (skip_prefix(command_buf.buf, "tagger ", &v)) { + tagger = parse_ident(v); read_next_command(); } else tagger = NULL; @@ -2842,14 +2836,11 @@ static void parse_new_tag(void) t->pack_id = pack_id; } -static void parse_reset_branch(void) +static void parse_reset_branch(const char *arg) { struct branch *b; - char *sp; - /* Obtain the branch name from the rest of our command */ - sp = strchr(command_buf.buf, ' ') + 1; - b = lookup_branch(sp); + b = lookup_branch(arg); if (b) { hashclr(b->sha1); hashclr(b->branch_tree.versions[0].sha1); @@ -2860,7 +2851,7 @@ static void parse_reset_branch(void) } } else - b = new_branch(sp); + b = new_branch(arg); read_next_command(); parse_from(b); if (command_buf.len > 0) @@ -2918,14 +2909,12 @@ static void cat_blob(struct object_entry *oe, unsigned char sha1[20]) free(buf); } -static void parse_cat_blob(void) +static void parse_cat_blob(const char *p) { - const char *p; struct object_entry *oe = oe; unsigned char sha1[20]; /* cat-blob SP <object> LF */ - p = command_buf.buf + strlen("cat-blob "); if (*p == ':') { oe = find_mark(parse_mark_ref_eol(p)); if (!oe) @@ -3008,6 +2997,8 @@ static struct object_entry *parse_treeish_dataref(const char **p) die("Invalid dataref: %s", command_buf.buf); e = find_object(sha1); *p += 40; + if (*(*p)++ != ' ') + die("Missing space after tree-ish: %s", command_buf.buf); } while (!e || e->type != OBJ_TREE) @@ -3042,14 +3033,12 @@ static void print_ls(int mode, const unsigned char *sha1, const char *path) cat_blob_write(line.buf, line.len); } -static void parse_ls(struct branch *b) +static void parse_ls(const char *p, struct branch *b) { - const char *p; struct tree_entry *root = NULL; struct tree_entry leaf = {NULL}; /* ls SP (<tree-ish> SP)? <path> */ - p = command_buf.buf + strlen("ls "); if (*p == '"') { if (!b) die("Not in a commit: %s", command_buf.buf); @@ -3061,8 +3050,6 @@ static void parse_ls(struct branch *b) if (!is_null_sha1(root->versions[1].sha1)) root->versions[1].mode = S_IFDIR; load_tree(root); - if (*p++ != ' ') - die("Missing space after tree-ish: %s", command_buf.buf); } if (*p == '"') { static struct strbuf uq = STRBUF_INIT; @@ -3200,9 +3187,9 @@ static void option_export_pack_edges(const char *edges) static int parse_one_option(const char *option) { - if (starts_with(option, "max-pack-size=")) { + if (skip_prefix(option, "max-pack-size=", &option)) { unsigned long v; - if (!git_parse_ulong(option + 14, &v)) + if (!git_parse_ulong(option, &v)) return 0; if (v < 8192) { warning("max-pack-size is now in bytes, assuming --max-pack-size=%lum", v); @@ -3212,17 +3199,17 @@ static int parse_one_option(const char *option) v = 1024 * 1024; } max_packsize = v; - } else if (starts_with(option, "big-file-threshold=")) { + } else if (skip_prefix(option, "big-file-threshold=", &option)) { unsigned long v; - if (!git_parse_ulong(option + 19, &v)) + if (!git_parse_ulong(option, &v)) return 0; big_file_threshold = v; - } else if (starts_with(option, "depth=")) { - option_depth(option + 6); - } else if (starts_with(option, "active-branches=")) { - option_active_branches(option + 16); - } else if (starts_with(option, "export-pack-edges=")) { - option_export_pack_edges(option + 18); + } else if (skip_prefix(option, "depth=", &option)) { + option_depth(option); + } else if (skip_prefix(option, "active-branches=", &option)) { + option_active_branches(option); + } else if (skip_prefix(option, "export-pack-edges=", &option)) { + option_export_pack_edges(option); } else if (starts_with(option, "quiet")) { show_stats = 0; } else if (starts_with(option, "stats")) { @@ -3236,15 +3223,16 @@ static int parse_one_option(const char *option) static int parse_one_feature(const char *feature, int from_stream) { - if (starts_with(feature, "date-format=")) { - option_date_format(feature + 12); - } else if (starts_with(feature, "import-marks=")) { - option_import_marks(feature + 13, from_stream, 0); - } else if (starts_with(feature, "import-marks-if-exists=")) { - option_import_marks(feature + strlen("import-marks-if-exists="), - from_stream, 1); - } else if (starts_with(feature, "export-marks=")) { - option_export_marks(feature + 13); + const char *arg; + + if (skip_prefix(feature, "date-format=", &arg)) { + option_date_format(arg); + } else if (skip_prefix(feature, "import-marks=", &arg)) { + option_import_marks(arg, from_stream, 0); + } else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) { + option_import_marks(arg, from_stream, 1); + } else if (skip_prefix(feature, "export-marks=", &arg)) { + option_export_marks(arg); } else if (!strcmp(feature, "cat-blob")) { ; /* Don't die - this feature is supported */ } else if (!strcmp(feature, "relative-marks")) { @@ -3264,10 +3252,8 @@ static int parse_one_feature(const char *feature, int from_stream) return 1; } -static void parse_feature(void) +static void parse_feature(const char *feature) { - char *feature = command_buf.buf + 8; - if (seen_data_command) die("Got feature command '%s' after data command", feature); @@ -3277,10 +3263,8 @@ static void parse_feature(void) die("This version of fast-import does not support feature %s.", feature); } -static void parse_option(void) +static void parse_option(const char *option) { - char *option = command_buf.buf + 11; - if (seen_data_command) die("Got option command '%s' after data command", option); @@ -3335,18 +3319,21 @@ static void parse_argv(void) if (*a != '-' || !strcmp(a, "--")) break; - if (parse_one_option(a + 2)) + if (!skip_prefix(a, "--", &a)) + die("unknown option %s", a); + + if (parse_one_option(a)) continue; - if (parse_one_feature(a + 2, 0)) + if (parse_one_feature(a, 0)) continue; - if (starts_with(a + 2, "cat-blob-fd=")) { - option_cat_blob_fd(a + 2 + strlen("cat-blob-fd=")); + if (skip_prefix(a, "cat-blob-fd=", &a)) { + option_cat_blob_fd(a); continue; } - die("unknown option %s", a); + die("unknown option --%s", a); } if (i != global_argc) usage(fast_import_usage); @@ -3393,26 +3380,27 @@ int main(int argc, char **argv) set_die_routine(die_nicely); set_checkpoint_signal(); while (read_next_command() != EOF) { + const char *v; if (!strcmp("blob", command_buf.buf)) parse_new_blob(); - else if (starts_with(command_buf.buf, "ls ")) - parse_ls(NULL); - else if (starts_with(command_buf.buf, "commit ")) - parse_new_commit(); - else if (starts_with(command_buf.buf, "tag ")) - parse_new_tag(); - else if (starts_with(command_buf.buf, "reset ")) - parse_reset_branch(); + else if (skip_prefix(command_buf.buf, "ls ", &v)) + parse_ls(v, NULL); + else if (skip_prefix(command_buf.buf, "commit ", &v)) + parse_new_commit(v); + else if (skip_prefix(command_buf.buf, "tag ", &v)) + parse_new_tag(v); + else if (skip_prefix(command_buf.buf, "reset ", &v)) + parse_reset_branch(v); else if (!strcmp("checkpoint", command_buf.buf)) parse_checkpoint(); else if (!strcmp("done", command_buf.buf)) break; else if (starts_with(command_buf.buf, "progress ")) parse_progress(); - else if (starts_with(command_buf.buf, "feature ")) - parse_feature(); - else if (starts_with(command_buf.buf, "option git ")) - parse_option(); + else if (skip_prefix(command_buf.buf, "feature ", &v)) + parse_feature(v); + else if (skip_prefix(command_buf.buf, "option git ", &v)) + parse_option(v); else if (starts_with(command_buf.buf, "option ")) /* ignore non-git options*/; else diff --git a/fetch-pack.c b/fetch-pack.c index eeee2bb7..b8a58fa7 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -189,20 +189,23 @@ static enum ack_type get_ack(int fd, unsigned char *result_sha1) { int len; char *line = packet_read_line(fd, &len); + const char *arg; if (!len) die("git fetch-pack: expected ACK/NAK, got EOF"); if (!strcmp(line, "NAK")) return NAK; - if (starts_with(line, "ACK ")) { - if (!get_sha1_hex(line+4, result_sha1)) { - if (len < 45) + if (skip_prefix(line, "ACK ", &arg)) { + if (!get_sha1_hex(arg, result_sha1)) { + arg += 40; + len -= arg - line; + if (len < 1) return ACK; - if (strstr(line+45, "continue")) + if (strstr(arg, "continue")) return ACK_continue; - if (strstr(line+45, "common")) + if (strstr(arg, "common")) return ACK_common; - if (strstr(line+45, "ready")) + if (strstr(arg, "ready")) return ACK_ready; return ACK; } @@ -319,18 +322,19 @@ static int find_common(struct fetch_pack_args *args, if (args->depth > 0) { char *line; + const char *arg; unsigned char sha1[20]; send_request(args, fd[1], &req_buf); while ((line = packet_read_line(fd[0], NULL))) { - if (starts_with(line, "shallow ")) { - if (get_sha1_hex(line + 8, sha1)) + if (skip_prefix(line, "shallow ", &arg)) { + if (get_sha1_hex(arg, sha1)) die("invalid shallow line: %s", line); register_shallow(sha1); continue; } - if (starts_with(line, "unshallow ")) { - if (get_sha1_hex(line + 10, sha1)) + if (skip_prefix(line, "unshallow ", &arg)) { + if (get_sha1_hex(arg, sha1)) die("invalid unshallow line: %s", line); if (!lookup_object(sha1)) die("object not found: %s", line); @@ -507,7 +511,7 @@ static void filter_refs(struct fetch_pack_args *args, int keep = 0; next = ref->next; - if (!memcmp(ref->name, "refs/", 5) && + if (starts_with(ref->name, "refs/") && check_refname_format(ref->name, 0)) ; /* trash */ else { @@ -6,7 +6,6 @@ #include "commit.h" #include "tag.h" #include "fsck.h" -#include "utf8.h" static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data) { @@ -171,9 +170,7 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) has_empty_name |= !*name; has_dot |= !strcmp(name, "."); has_dotdot |= !strcmp(name, ".."); - has_dotgit |= (!strcmp(name, ".git") || - is_hfs_dotgit(name) || - is_ntfs_dotgit(name)); + has_dotgit |= !strcmp(name, ".git"); has_zero_pad |= *(char *)desc.buffer == '0'; update_tree_entry(&desc); @@ -282,54 +279,39 @@ static int fsck_ident(const char **ident, struct object *obj, fsck_error error_f static int fsck_commit_buffer(struct commit *commit, const char *buffer, fsck_error error_func) { - const char *tmp; unsigned char tree_sha1[20], sha1[20]; struct commit_graft *graft; - int parents = 0; + unsigned parent_count, parent_line_count = 0; int err; - buffer = skip_prefix(buffer, "tree "); - if (!buffer) + if (!skip_prefix(buffer, "tree ", &buffer)) return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line"); if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n') return error_func(&commit->object, FSCK_ERROR, "invalid 'tree' line format - bad sha1"); buffer += 41; - while ((tmp = skip_prefix(buffer, "parent "))) { - buffer = tmp; + while (skip_prefix(buffer, "parent ", &buffer)) { if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') return error_func(&commit->object, FSCK_ERROR, "invalid 'parent' line format - bad sha1"); buffer += 41; - parents++; + parent_line_count++; } graft = lookup_commit_graft(commit->object.sha1); + parent_count = commit_list_count(commit->parents); if (graft) { - struct commit_list *p = commit->parents; - parents = 0; - while (p) { - p = p->next; - parents++; - } - if (graft->nr_parent == -1 && !parents) + if (graft->nr_parent == -1 && !parent_count) ; /* shallow commit */ - else if (graft->nr_parent != parents) + else if (graft->nr_parent != parent_count) return error_func(&commit->object, FSCK_ERROR, "graft objects missing"); } else { - struct commit_list *p = commit->parents; - while (p && parents) { - p = p->next; - parents--; - } - if (p || parents) + if (parent_count != parent_line_count) return error_func(&commit->object, FSCK_ERROR, "parent objects missing"); } - buffer = skip_prefix(buffer, "author "); - if (!buffer) + if (!skip_prefix(buffer, "author ", &buffer)) return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'author' line"); err = fsck_ident(&buffer, &commit->object, error_func); if (err) return err; - buffer = skip_prefix(buffer, "committer "); - if (!buffer) + if (!skip_prefix(buffer, "committer ", &buffer)) return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'committer' line"); err = fsck_ident(&buffer, &commit->object, error_func); if (err) diff --git a/git-bisect.sh b/git-bisect.sh index af4d04c3..1e0d602f 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -408,7 +408,7 @@ bisect_replay () { bisect_reset while read git bisect command rev do - test "$git $bisect" = "git bisect" -o "$git" = "git-bisect" || continue + test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue if test "$git" = "git-bisect" then rev="$command" diff --git a/git-compat-util.h b/git-compat-util.h index e6a4159a..f587749b 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -267,6 +267,10 @@ extern char *gitbasename(char *); #define has_dos_drive_prefix(path) 0 #endif +#ifndef offset_1st_component +#define offset_1st_component(path) (is_dir_sep((path)[0])) +#endif + #ifndef is_dir_sep #define is_dir_sep(c) ((c) == '/') #endif @@ -287,10 +291,12 @@ extern char *gitbasename(char *); #else #define NORETURN #define NORETURN_PTR +#ifndef __GNUC__ #ifndef __attribute__ #define __attribute__(x) #endif #endif +#endif /* The sentinel attribute is valid from gcc version 4.0 */ #if defined(__GNUC__) && (__GNUC__ >= 4) @@ -330,8 +336,12 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))) * trying to help gcc, anyway, it's OK; other compilers will fall back to * using the function as usual. */ -#if defined(__GNUC__) && ! defined(__clang__) -#define error(...) (error(__VA_ARGS__), -1) +#if defined(__GNUC__) +static inline int const_error(void) +{ + return -1; +} +#define error(...) (error(__VA_ARGS__), const_error()) #endif extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); @@ -339,15 +349,66 @@ extern void set_error_routine(void (*routine)(const char *err, va_list params)); extern void set_die_is_recursing_routine(int (*routine)(void)); extern int starts_with(const char *str, const char *prefix); -extern int ends_with(const char *str, const char *suffix); -static inline const char *skip_prefix(const char *str, const char *prefix) +/* + * If the string "str" begins with the string found in "prefix", return 1. + * The "out" parameter is set to "str + strlen(prefix)" (i.e., to the point in + * the string right after the prefix). + * + * Otherwise, return 0 and leave "out" untouched. + * + * Examples: + * + * [extract branch name, fail if not a branch] + * if (!skip_prefix(ref, "refs/heads/", &branch) + * return -1; + * + * [skip prefix if present, otherwise use whole string] + * skip_prefix(name, "refs/heads/", &name); + */ +static inline int skip_prefix(const char *str, const char *prefix, + const char **out) { do { - if (!*prefix) - return str; + if (!*prefix) { + *out = str; + return 1; + } } while (*str++ == *prefix++); - return NULL; + return 0; +} + +/* + * If buf ends with suffix, return 1 and subtract the length of the suffix + * from *len. Otherwise, return 0 and leave *len untouched. + */ +static inline int strip_suffix_mem(const char *buf, size_t *len, + const char *suffix) +{ + size_t suflen = strlen(suffix); + if (*len < suflen || memcmp(buf + (*len - suflen), suffix, suflen)) + return 0; + *len -= suflen; + return 1; +} + +/* + * If str ends with suffix, return 1 and set *len to the size of the string + * without the suffix. Otherwise, return 0 and set *len to the size of the + * string. + * + * Note that we do _not_ NUL-terminate str to the new length. + */ +static inline int strip_suffix(const char *str, const char *suffix, size_t *len) +{ + *len = strlen(str); + return strip_suffix_mem(str, len, suffix); +} + +static inline int ends_with(const char *str, const char *suffix) +{ + size_t len; + return strip_suffix(str, suffix, &len); } #if defined(NO_MMAP) || defined(USE_WIN32_MMAP) @@ -521,6 +582,14 @@ extern void release_pack_memory(size_t); typedef void (*try_to_free_t)(size_t); extern try_to_free_t set_try_to_free_routine(try_to_free_t); +#ifdef HAVE_ALLOCA_H +# include <alloca.h> +# define xalloca(size) (alloca(size)) +# define xalloca_free(p) do {} while (0) +#else +# define xalloca(size) (xmalloc(size)) +# define xalloca_free(p) (free(p)) +#endif extern char *xstrdup(const char *str); extern void *xmalloc(size_t size); extern void *xmallocz(size_t size); @@ -546,13 +615,6 @@ static inline size_t xsize_t(off_t len) return (size_t)len; } -static inline int has_extension(const char *filename, const char *ext) -{ - size_t len = strlen(filename); - size_t extlen = strlen(ext); - return len > extlen && !memcmp(filename + len - extlen, ext, extlen); -} - /* in ctype.c, for kwset users */ extern const char tolower_trans_tbl[256]; @@ -688,6 +750,10 @@ void git_qsort(void *base, size_t nmemb, size_t size, #endif #endif +#if defined(__GNUC__) || (_MSC_VER >= 1400) +#define HAVE_VARIADIC_MACROS 1 +#endif + /* * Preserves errno, prints a message, but gives no warning for ENOENT. * Always returns the return value of unlink(2). diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 6a8907e7..b186329d 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -1558,7 +1558,11 @@ proc rescan_stage2 {fd after} { set rescan_active 2 ui_status [mc "Scanning for modified files ..."] - set fd_di [git_read diff-index --cached -z [PARENT]] + if {[git-version >= "1.7.2"]} { + set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]] + } else { + set fd_di [git_read diff-index --cached -z [PARENT]] + } set fd_df [git_read diff-files -z] fconfigure $fd_di -blocking 0 -translation binary -encoding binary diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index 30d9a797..b0a5180a 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -287,6 +287,9 @@ proc start_show_diff {cont_info {add_opts {}}} { if {$w eq $ui_index} { lappend cmd diff-index lappend cmd --cached + if {[git-version >= "1.7.2"]} { + lappend cmd --ignore-submodules=dirty + } } elseif {$w eq $ui_workdir} { if {[string first {U} $m] >= 0} { lappend cmd diff diff --git a/git-mergetool.sh b/git-mergetool.sh index 332528ff..9a046b75 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -205,7 +205,7 @@ checkout_staged_file () { "$(git checkout-index --temp --stage="$1" "$2" 2>/dev/null)" \ : '\([^ ]*\) ') - if test $? -eq 0 -a -n "$tmpfile" + if test $? -eq 0 && test -n "$tmpfile" then mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3" else @@ -256,7 +256,7 @@ merge_file () { checkout_staged_file 2 "$MERGED" "$LOCAL" checkout_staged_file 3 "$MERGED" "$REMOTE" - if test -z "$local_mode" -o -z "$remote_mode" + if test -z "$local_mode" || test -z "$remote_mode" then echo "Deleted merge conflict for '$MERGED':" describe_file "$local_mode" "local" "$LOCAL" @@ -277,7 +277,7 @@ merge_file () { echo "Normal merge conflict for '$MERGED':" describe_file "$local_mode" "local" "$LOCAL" describe_file "$remote_mode" "remote" "$REMOTE" - if "$prompt" = true + if test "$guessed_merge_tool" = true || test "$prompt" = true then printf "Hit return to start merge resolution tool (%s): " "$merge_tool" read ans || return 1 @@ -315,7 +315,8 @@ merge_file () { return 0 } -prompt=$(git config --bool mergetool.prompt || echo true) +prompt=$(git config --bool mergetool.prompt) +guessed_merge_tool=false while test $# != 0 do @@ -373,7 +374,14 @@ prompt_after_failed_merge () { if test -z "$merge_tool" then - merge_tool=$(get_merge_tool "$merge_tool") || exit + # Check if a merge tool has been configured + merge_tool=$(get_configured_merge_tool) + # Try to guess an appropriate merge tool if no tool has been set. + if test -z "$merge_tool" + then + merge_tool=$(guess_merge_tool) || exit + guessed_merge_tool=true + fi fi merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)" merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)" @@ -1238,6 +1238,28 @@ class P4Submit(Command, P4UserMap): if response == 'n': return False + def get_diff_description(self, editedFiles, filesToAdd): + # diff + if os.environ.has_key("P4DIFF"): + del(os.environ["P4DIFF"]) + diff = "" + for editedFile in editedFiles: + diff += p4_read_pipe(['diff', '-du', + wildcard_encode(editedFile)]) + + # new file diff + newdiff = "" + for newFile in filesToAdd: + newdiff += "==== new file ====\n" + newdiff += "--- /dev/null\n" + newdiff += "+++ %s\n" % newFile + f = open(newFile, "r") + for line in f.readlines(): + newdiff += "+" + line + f.close() + + return (diff + newdiff).replace('\r\n', '\n') + def applyCommit(self, id): """Apply one commit, return True if it succeeded.""" @@ -1398,34 +1420,15 @@ class P4Submit(Command, P4UserMap): submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n" separatorLine = "######## everything below this line is just the diff #######\n" + if not self.prepare_p4_only: + submitTemplate += separatorLine + submitTemplate += self.get_diff_description(editedFiles, filesToAdd) - # diff - if os.environ.has_key("P4DIFF"): - del(os.environ["P4DIFF"]) - diff = "" - for editedFile in editedFiles: - diff += p4_read_pipe(['diff', '-du', - wildcard_encode(editedFile)]) - - # new file diff - newdiff = "" - for newFile in filesToAdd: - newdiff += "==== new file ====\n" - newdiff += "--- /dev/null\n" - newdiff += "+++ %s\n" % newFile - f = open(newFile, "r") - for line in f.readlines(): - newdiff += "+" + line - f.close() - - # change description file: submitTemplate, separatorLine, diff, newdiff (handle, fileName) = tempfile.mkstemp() - tmpFile = os.fdopen(handle, "w+") + tmpFile = os.fdopen(handle, "w+b") if self.isWindows: submitTemplate = submitTemplate.replace("\n", "\r\n") - separatorLine = separatorLine.replace("\n", "\r\n") - newdiff = newdiff.replace("\n", "\r\n") - tmpFile.write(submitTemplate + separatorLine + diff + newdiff) + tmpFile.write(submitTemplate) tmpFile.close() if self.prepare_p4_only: @@ -1472,9 +1475,9 @@ class P4Submit(Command, P4UserMap): tmpFile = open(fileName, "rb") message = tmpFile.read() tmpFile.close() - submitTemplate = message[:message.index(separatorLine)] if self.isWindows: - submitTemplate = submitTemplate.replace("\r\n", "\n") + message = message.replace("\r\n", "\n") + submitTemplate = message[:message.index(separatorLine)] p4_write_pipe(['submit', '-i'], submitTemplate) if self.preserveUser: diff --git a/git-pull.sh b/git-pull.sh index 7358fac8..18a394fc 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -106,7 +106,7 @@ do -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) case "$#,$1" in *,*=*) - strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; + strategy=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;; 1,*) usage ;; *) diff --git a/git-rebase--am.sh b/git-rebase--am.sh index ca20e1e6..f9237323 100644 --- a/git-rebase--am.sh +++ b/git-rebase--am.sh @@ -29,7 +29,13 @@ skip) ;; esac -test -n "$rebase_root" && root_flag=--root +if test -z "$rebase_root" + # this is now equivalent to ! -z "$upstream" +then + revisions=$upstream...$orig_head +else + revisions=$onto...$orig_head +fi ret=0 if test -n "$keep_empty" @@ -38,14 +44,17 @@ then # empty commits and even if it didn't the format doesn't really lend # itself well to recording empty patches. fortunately, cherry-pick # makes this easy - git cherry-pick ${gpg_sign_opt:+"$gpg_sign_opt"} --allow-empty "$revisions" + git cherry-pick ${gpg_sign_opt:+"$gpg_sign_opt"} --allow-empty \ + --right-only "$revisions" \ + ${restrict_revision+^$restrict_revision} ret=$? else rm -f "$GIT_DIR/rebased-patches" - git format-patch -k --stdout --full-index --ignore-if-in-upstream \ + git format-patch -k --stdout --full-index --cherry-pick --right-only \ --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \ - $root_flag "$revisions" >"$GIT_DIR/rebased-patches" + "$revisions" ${restrict_revision+^$restrict_revision} \ + >"$GIT_DIR/rebased-patches" ret=$? if test 0 != $ret diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index f267d8b6..b64dd28a 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -963,7 +963,7 @@ else fi git rev-list $merges_option --pretty=oneline --abbrev-commit \ --abbrev=7 --reverse --left-right --topo-order \ - $revisions | \ + $revisions ${restrict_revision+^$restrict_revision} | \ sed -n "s/^>//p" | while read -r shortsha1 rest do @@ -1013,7 +1013,7 @@ then git rev-list $revisions | while read rev do - if test -f "$rewritten"/$rev -a "$(sane_grep "$rev" "$state_dir"/not-cherry-picks)" = "" + if test -f "$rewritten"/$rev && test "$(sane_grep "$rev" "$state_dir"/not-cherry-picks)" = "" then # Use -f2 because if rev-list is telling us this commit is # not worthwhile, we don't want to track its multiple heads, diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh index 66c64d7e..d3fb67d7 100644 --- a/git-rebase--merge.sh +++ b/git-rebase--merge.sh @@ -24,7 +24,7 @@ continue_merge () { die "$resolvemsg" fi - cmt=`cat "$state_dir/current"` + cmt=$(cat "$state_dir/current") if ! git diff-index --quiet --ignore-submodules HEAD -- then if ! git commit ${gpg_sign_opt:+"$gpg_sign_opt"} --no-verify -C "$cmt" @@ -144,7 +144,7 @@ echo "$onto_name" > "$state_dir/onto_name" write_basic_state msgnum=0 -for cmt in `git rev-list --reverse --no-merges "$revisions"` +for cmt in $(git rev-list --reverse --no-merges "$revisions") do msgnum=$(($msgnum + 1)) echo "$cmt" > "$state_dir/cmt.$msgnum" diff --git a/git-rebase.sh b/git-rebase.sh index 47ca3b99..55da9db8 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -59,6 +59,7 @@ If you prefer to skip this patch, run "git rebase --skip" instead. To check out the original branch and stop rebasing, run "git rebase --abort".') " unset onto +unset restrict_revision cmd= strategy= strategy_opts= @@ -466,8 +467,8 @@ then else if test -z "$onto" then - empty_tree=`git hash-object -t tree /dev/null` - onto=`git commit-tree $empty_tree </dev/null` + empty_tree=$(git hash-object -t tree /dev/null) + onto=$(git commit-tree $empty_tree </dev/null) squash_onto="$onto" fi unset upstream_name @@ -525,10 +526,10 @@ case "$#" in ;; 0) # Do not need to switch branches, we are already on it. - if branch_name=`git symbolic-ref -q HEAD` + if branch_name=$(git symbolic-ref -q HEAD) then head_name=$branch_name - branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'` + branch_name=$(expr "z$branch_name" : 'zrefs/heads/\(.*\)') else head_name="detached HEAD" branch_name=HEAD ;# detached @@ -546,7 +547,7 @@ then "${switch_to:-HEAD}") if test -n "$new_upstream" then - upstream=$new_upstream + restrict_revision=$new_upstream fi fi @@ -572,7 +573,7 @@ require_clean_work_tree "rebase" "$(gettext "Please commit or stash them.")" # and if this is not an interactive rebase. mb=$(git merge-base "$onto" "$orig_head") if test "$type" != interactive && test "$upstream" = "$onto" && - test "$mb" = "$onto" && + test "$mb" = "$onto" && test -z "$restrict_revision" && # linear history? ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null then @@ -626,7 +627,7 @@ if test -n "$rebase_root" then revisions="$onto..$orig_head" else - revisions="$upstream..$orig_head" + revisions="${restrict_revision-$upstream}..$orig_head" fi run_specific_rebase diff --git a/git-send-email.perl b/git-send-email.perl index fdb0029b..9949db01 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -80,6 +80,8 @@ git send-email [options] <file | directory | rev-list options > --to-cmd <str> * Email To: via `<str> \$patch_path` --cc-cmd <str> * Email Cc: via `<str> \$patch_path` --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all. + --[no-]cc-cover * Email Cc: addresses in the cover letter. + --[no-]to-cover * Email To: addresses in the cover letter. --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. --[no-]suppress-from * Send to self. Default off. --[no-]chain-reply-to * Chain In-Reply-To: fields. Default off. @@ -195,6 +197,7 @@ sub do_edit { # Variables with corresponding config settings my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc); +my ($cover_cc, $cover_to); my ($to_cmd, $cc_cmd); my ($smtp_server, $smtp_server_port, @smtp_server_options); my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path); @@ -211,6 +214,8 @@ my %config_bool_settings = ( "chainreplyto" => [\$chain_reply_to, 0], "suppressfrom" => [\$suppress_from, undef], "signedoffbycc" => [\$signed_off_by_cc, undef], + "cccover" => [\$cover_cc, undef], + "tocover" => [\$cover_to, undef], "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated "validate" => [\$validate, 1], "multiedit" => [\$multiedit, undef], @@ -302,6 +307,8 @@ my $rc = GetOptions("h" => \$help, "suppress-from!" => \$suppress_from, "suppress-cc=s" => \@suppress_cc, "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc, + "cc-cover|cc-cover!" => \$cover_cc, + "to-cover|to-cover!" => \$cover_to, "confirm=s" => \$confirm, "dry-run" => \$dry_run, "envelope-sender=s" => \$envelope_sender, @@ -1113,6 +1120,18 @@ sub ssl_verify_params { } } +sub file_name_is_absolute { + my ($path) = @_; + + # msys does not grok DOS drive-prefixes + if ($^O eq 'msys') { + return ($path =~ m#^/# || $path =~ m#^[a-zA-Z]\:#) + } + + require File::Spec::Functions; + return File::Spec::Functions::file_name_is_absolute($path); +} + # Returns 1 if the message was sent, and 0 otherwise. # In actuality, the whole program dies when there # is an error sending a message. @@ -1197,7 +1216,7 @@ X-Mailer: git-send-email $gitversion if ($dry_run) { # We don't want to send the email. - } elsif ($smtp_server =~ m#^/#) { + } elsif (file_name_is_absolute($smtp_server)) { my $pid = open my $sm, '|-'; defined $pid or die $!; if (!$pid) { @@ -1271,7 +1290,7 @@ X-Mailer: git-send-email $gitversion printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject); } else { print (($dry_run ? "Dry-" : "")."OK. Log says:\n"); - if ($smtp_server !~ m#^/#) { + if (!file_name_is_absolute($smtp_server)) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; foreach my $entry (@recipients) { @@ -1469,6 +1488,15 @@ foreach my $t (@files) { @to = (@initial_to, @to); @cc = (@initial_cc, @cc); + if ($message_num == 1) { + if (defined $cover_cc and $cover_cc) { + @initial_cc = @cc; + } + if (defined $cover_to and $cover_to) { + @initial_to = @to; + } + } + my $message_was_sent = send_message(); # set up for the next message diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 5f28b32d..94479803 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -160,7 +160,7 @@ git_pager() { else GIT_PAGER=cat fi - : ${LESS=-FRSX} + : ${LESS=-FRX} : ${LV=-c} export LESS LV diff --git a/git-stash.sh b/git-stash.sh index 4621d81f..bcc757b3 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -13,7 +13,7 @@ USAGE="list [<options>] SUBDIRECTORY_OK=Yes OPTIONS_SPEC= -START_DIR=`pwd` +START_DIR=$(pwd) . git-sh-setup . git-sh-i18n require_work_tree diff --git a/git-submodule.sh b/git-submodule.sh index b55d83ac..9245abfd 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -235,7 +235,7 @@ module_name() sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) test -z "$name" && die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")" - echo "$name" + printf '%s\n' "$name" } # @@ -291,9 +291,6 @@ module_clone() # resolve any symlinks that might be present in $PWD a=$(cd_to_toplevel && cd "$gitdir" && pwd)/ b=$(cd_to_toplevel && cd "$sm_path" && pwd)/ - # normalize Windows-style absolute paths to POSIX-style absolute paths - case $a in [a-zA-Z]:/*) a=/${a%%:*}${a#*:} ;; esac - case $b in [a-zA-Z]:/*) b=/${b%%:*}${b#*:} ;; esac # Remove all common leading directories after a sanity check if test "${a#$b}" != "$a" || test "${b#$a}" != "$b"; then die "$(eval_gettext "Gitdir '\$a' is part of the submodule path '\$b' or vice versa")" @@ -308,10 +305,10 @@ module_clone() b=${b%/} # Turn each leading "*/" component into "../" - rel=$(echo $b | sed -e 's|[^/][^/]*|..|g') - echo "gitdir: $rel/$a" >"$sm_path/.git" + rel=$(printf '%s\n' "$b" | sed -e 's|[^/][^/]*|..|g') + printf '%s\n' "gitdir: $rel/$a" >"$sm_path/.git" - rel=$(echo $a | sed -e 's|[^/][^/]*|..|g') + rel=$(printf '%s\n' "$a" | sed -e 's|[^/][^/]*|..|g') (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b") } @@ -392,11 +389,11 @@ cmd_add() sm_path=$2 if test -z "$sm_path"; then - sm_path=$(echo "$repo" | + sm_path=$(printf '%s\n' "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') fi - if test -z "$repo" -o -z "$sm_path"; then + if test -z "$repo" || test -z "$sm_path"; then usage fi @@ -453,7 +450,7 @@ Use -f if you really want to add it." >&2 # perhaps the path exists and is already a git repo, else clone it if test -e "$sm_path" then - if test -d "$sm_path"/.git -o -f "$sm_path"/.git + if test -d "$sm_path"/.git || test -f "$sm_path"/.git then eval_gettextln "Adding existing repo at '\$sm_path' to the index" else @@ -835,7 +832,7 @@ Maybe you want to use 'update --init'?")" continue fi - if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git + if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git then module_clone "$sm_path" "$name" "$url" "$reference" "$depth" || exit cloned_modules="$cloned_modules;$name" @@ -860,11 +857,11 @@ Maybe you want to use 'update --init'?")" die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")" fi - if test "$subsha1" != "$sha1" -o -n "$force" + if test "$subsha1" != "$sha1" || test -n "$force" then subforce=$force # If we don't already have a -f flag and the submodule has never been checked out - if test -z "$subsha1" -a -z "$force" + if test -z "$subsha1" && test -z "$force" then subforce="-f" fi @@ -1034,7 +1031,7 @@ cmd_summary() { then head=$rev test $# = 0 || shift - elif test -z "$1" -o "$1" = "HEAD" + elif test -z "$1" || test "$1" = "HEAD" then # before the first commit: compare with an empty tree head=$(git hash-object -w -t tree --stdin </dev/null) @@ -1059,17 +1056,21 @@ cmd_summary() { while read mod_src mod_dst sha1_src sha1_dst status sm_path do # Always show modules deleted or type-changed (blob<->module) - test $status = D -o $status = T && echo "$sm_path" && continue + if test "$status" = D || test "$status" = T + then + printf '%s\n' "$sm_path" + continue + fi # Respect the ignore setting for --for-status. if test -n "$for_status" then name=$(module_name "$sm_path") ignore_config=$(get_submodule_config "$name" ignore none) - test $status != A -a $ignore_config = all && continue + test $status != A && test $ignore_config = all && continue fi # Also show added or modified modules which are checked out GIT_DIR="$sm_path/.git" git-rev-parse --git-dir >/dev/null 2>&1 && - echo "$sm_path" + printf '%s\n' "$sm_path" done ) @@ -1125,7 +1126,7 @@ cmd_summary() { *) errmsg= total_commits=$( - if test $mod_src = 160000 -a $mod_dst = 160000 + if test $mod_src = 160000 && test $mod_dst = 160000 then range="$sha1_src...$sha1_dst" elif test $mod_src = 160000 @@ -1162,7 +1163,7 @@ cmd_summary() { # i.e. deleted or changed to blob test $mod_dst = 160000 && echo "$errmsg" else - if test $mod_src = 160000 -a $mod_dst = 160000 + if test $mod_src = 160000 && test $mod_dst = 160000 then limit= test $summary_limit -gt 0 && limit="-$summary_limit" @@ -1233,7 +1234,11 @@ cmd_status() say "U$sha1 $displaypath" continue fi - if test -z "$url" || ! test -d "$sm_path"/.git -o -f "$sm_path"/.git + if test -z "$url" || + { + ! test -d "$sm_path"/.git && + ! test -f "$sm_path"/.git + } then say "-$sha1 $displaypath" continue; @@ -1306,7 +1311,7 @@ cmd_sync() ./*|../*) # rewrite foo/bar as ../.. to find path from # submodule work tree to superproject work tree - up_path="$(echo "$sm_path" | sed "s/[^/][^/]*/../g")" && + up_path="$(printf '%s\n' "$sm_path" | sed "s/[^/][^/]*/../g")" && # guarantee a trailing / up_path=${up_path%/}/ && # path from submodule work tree to submodule origin repo @@ -1402,7 +1407,7 @@ then fi # "--cached" is accepted only by "status" and "summary" -if test -n "$cached" && test "$command" != status -a "$command" != summary +if test -n "$cached" && test "$command" != status && test "$command" != summary then usage fi diff --git a/git-web--browse.sh b/git-web--browse.sh index ebdfba6c..ae152534 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -59,7 +59,7 @@ do -b|--browser*|-t|--tool*) case "$#,$1" in *,*=*) - browser=`expr "z$1" : 'z-[^=]*=\(.*\)'` + browser=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;; 1,*) usage ;; @@ -71,7 +71,7 @@ do -c|--config*) case "$#,$1" in *,*=*) - conf=`expr "z$1" : 'z-[^=]*=\(.*\)'` + conf=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;; 1,*) usage ;; @@ -100,7 +100,7 @@ then for opt in "$conf" "web.browser" do test -z "$opt" && continue - browser="`git config $opt`" + browser="$(git config $opt)" test -z "$browser" || break done if test -n "$browser" && ! valid_tool "$browser"; then @@ -20,6 +20,43 @@ const char git_more_info_string[] = static struct startup_info git_startup_info; static int use_pager = -1; +static char orig_cwd[PATH_MAX]; +static const char *env_names[] = { + GIT_DIR_ENVIRONMENT, + GIT_WORK_TREE_ENVIRONMENT, + GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, + GIT_PREFIX_ENVIRONMENT +}; +static char *orig_env[4]; +static int saved_environment; + +static void save_env(void) +{ + int i; + if (saved_environment) + return; + saved_environment = 1; + if (!getcwd(orig_cwd, sizeof(orig_cwd))) + die_errno("cannot getcwd"); + for (i = 0; i < ARRAY_SIZE(env_names); i++) { + orig_env[i] = getenv(env_names[i]); + if (orig_env[i]) + orig_env[i] = xstrdup(orig_env[i]); + } +} + +static void restore_env(void) +{ + int i; + if (*orig_cwd && chdir(orig_cwd)) + die_errno("could not move to %s", orig_cwd); + for (i = 0; i < ARRAY_SIZE(env_names); i++) { + if (orig_env[i]) + setenv(env_names[i], orig_env[i], 1); + else + unsetenv(env_names[i]); + } +} static void commit_pager_choice(void) { switch (use_pager) { @@ -54,8 +91,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) /* * Check remaining flags. */ - if (starts_with(cmd, "--exec-path")) { - cmd += 11; + if (skip_prefix(cmd, "--exec-path", &cmd)) { if (*cmd == '=') git_set_argv_exec_path(cmd + 1); else { @@ -92,8 +128,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) *envchanged = 1; (*argv)++; (*argc)--; - } else if (starts_with(cmd, "--git-dir=")) { - setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1); + } else if (skip_prefix(cmd, "--git-dir=", &cmd)) { + setenv(GIT_DIR_ENVIRONMENT, cmd, 1); if (envchanged) *envchanged = 1; } else if (!strcmp(cmd, "--namespace")) { @@ -106,8 +142,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) *envchanged = 1; (*argv)++; (*argc)--; - } else if (starts_with(cmd, "--namespace=")) { - setenv(GIT_NAMESPACE_ENVIRONMENT, cmd + 12, 1); + } else if (skip_prefix(cmd, "--namespace=", &cmd)) { + setenv(GIT_NAMESPACE_ENVIRONMENT, cmd, 1); if (envchanged) *envchanged = 1; } else if (!strcmp(cmd, "--work-tree")) { @@ -120,8 +156,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) *envchanged = 1; (*argv)++; (*argc)--; - } else if (starts_with(cmd, "--work-tree=")) { - setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1); + } else if (skip_prefix(cmd, "--work-tree=", &cmd)) { + setenv(GIT_WORK_TREE_ENVIRONMENT, cmd, 1); if (envchanged) *envchanged = 1; } else if (!strcmp(cmd, "--bare")) { @@ -272,6 +308,7 @@ static int handle_alias(int *argcp, const char ***argv) * RUN_SETUP for reading from the configuration file. */ #define NEED_WORK_TREE (1<<3) +#define NO_SETUP (1<<4) struct cmd_struct { const char *cmd; @@ -290,7 +327,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) if (!help) { if (p->option & RUN_SETUP) prefix = setup_git_directory(); - if (p->option & RUN_SETUP_GENTLY) { + else if (p->option & RUN_SETUP_GENTLY) { int nongit_ok; prefix = setup_git_directory_gently(&nongit_ok); } @@ -352,7 +389,7 @@ static struct cmd_struct commands[] = { { "cherry", cmd_cherry, RUN_SETUP }, { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, - { "clone", cmd_clone }, + { "clone", cmd_clone, NO_SETUP }, { "column", cmd_column, RUN_SETUP_GENTLY }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, @@ -378,8 +415,8 @@ static struct cmd_struct commands[] = { { "hash-object", cmd_hash_object }, { "help", cmd_help }, { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY }, - { "init", cmd_init_db }, - { "init-db", cmd_init_db }, + { "init", cmd_init_db, NO_SETUP }, + { "init-db", cmd_init_db, NO_SETUP }, { "log", cmd_log, RUN_SETUP }, { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, @@ -441,6 +478,7 @@ static struct cmd_struct commands[] = { { "upload-archive", cmd_upload_archive }, { "upload-archive--writer", cmd_upload_archive_writer }, { "var", cmd_var, RUN_SETUP_GENTLY }, + { "verify-commit", cmd_verify_commit, RUN_SETUP }, { "verify-pack", cmd_verify_pack }, { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, @@ -484,6 +522,10 @@ static void handle_builtin(int argc, const char **argv) struct cmd_struct *p = commands+i; if (strcmp(p->cmd, cmd)) continue; + if (saved_environment && (p->option & NO_SETUP)) { + restore_env(); + break; + } exit(run_builtin(p, argc, argv)); } } @@ -539,7 +581,10 @@ static int run_argv(int *argcp, const char ***argv) * of overriding "git log" with "git show" by having * alias.log = show */ - if (done_alias || !handle_alias(argcp, argv)) + if (done_alias) + break; + save_env(); + if (!handle_alias(argcp, argv)) break; done_alias = 1; } @@ -568,6 +613,8 @@ int main(int argc, char **av) git_setup_gettext(); + trace_command_performance(argv); + /* * "git-xxxx" is the same as "git xxxx", but we obviously: * @@ -578,8 +625,7 @@ int main(int argc, char **av) * So we just directly call the builtin handler, and die if * that one cannot handle it. */ - if (starts_with(cmd, "git-")) { - cmd += 4; + if (skip_prefix(cmd, "git-", &cmd)) { argv[0] = cmd; handle_builtin(argc, argv); die("cannot handle %s as a builtin", cmd); @@ -590,8 +636,8 @@ int main(int argc, char **av) argc--; handle_options(&argv, &argc, NULL); if (argc > 0) { - if (starts_with(argv[0], "--")) - argv[0] += 2; + /* translate --help and --version into commands */ + skip_prefix(argv[0], "--", &argv[0]); } else { /* The user didn't specify a command; give them help */ commit_pager_choice(); diff --git a/gitk-git/gitk b/gitk-git/gitk index 90764e89..3520bdae 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -2585,6 +2585,7 @@ proc makewindow {} { bind $fstring <Key-Return> {dofind 1 1} bind $sha1entry <Key-Return> {gotocommit; break} bind $sha1entry <<PasteSelection>> clearsha1 + bind $sha1entry <<Paste>> clearsha1 bind $cflist <1> {sel_flist %W %x %y; break} bind $cflist <B1-Motion> {sel_flist %W %x %y; break} bind $cflist <ButtonRelease-1> {treeclick %W %x %y} @@ -2593,6 +2594,9 @@ proc makewindow {} { bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y} bind $ctext <Button-1> {focus %W} bind $ctext <<Selection>> rehighlight_search_results + for {set i 1} {$i < 10} {incr i} { + bind . <$M1B-Key-$i> [list go_to_parent $i] + } set maincursor [. cget -cursor] set textcursor [$ctext cget -cursor] @@ -2786,7 +2790,7 @@ proc savestuff {w} { global mainheadcirclecolor workingfilescirclecolor indexcirclecolor global linkfgcolor circleoutlinecolor global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk - global hideremotes want_ttk maxrefs + global hideremotes want_ttk maxrefs visiblerefs global config_file config_file_tmp if {$stuffsaved} return @@ -2812,6 +2816,7 @@ proc savestuff {w} { puts $f [list set autosellen $autosellen] puts $f [list set showneartags $showneartags] puts $f [list set maxrefs $maxrefs] + puts $f [list set visiblerefs $visiblerefs] puts $f [list set hideremotes $hideremotes] puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] @@ -3015,6 +3020,7 @@ proc keys {} { [mc "<Down>, n, j Move down one commit"] [mc "<Left>, z, h Go back in history list"] [mc "<Right>, x, l Go forward in history list"] +[mc "<%s-n> Go to n-th parent of current commit in history list" $M1T] [mc "<PageUp> Move up one page in commit list"] [mc "<PageDown> Move down one page in commit list"] [mc "<%s-Home> Scroll to top of commit list" $M1T] @@ -3492,10 +3498,20 @@ proc flist_hl {only} { } proc gitknewtmpdir {} { - global diffnum gitktmpdir gitdir + global diffnum gitktmpdir gitdir env if {![info exists gitktmpdir]} { - set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]] + if {[info exists env(GITK_TMPDIR)]} { + set tmpdir $env(GITK_TMPDIR) + } elseif {[info exists env(TMPDIR)]} { + set tmpdir $env(TMPDIR) + } else { + set tmpdir $gitdir + } + set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"] + if {[catch {set gitktmpdir [exec mktemp -d $gitktmpformat]}]} { + set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]] + } if {[catch {file mkdir $gitktmpdir} err]} { error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err" unset gitktmpdir @@ -3870,7 +3886,7 @@ proc read_line_source {fd inst} { set id $nullid2 } if {[commitinview $id $curview]} { - selectline [rowofcommit $id] 1 [list $fname $lnum] + selectline [rowofcommit $id] 1 [list $fname $lnum] 1 } else { error_popup [mc "That line comes from commit %s, \ which is not in this view" [shortids $id]] @@ -5205,11 +5221,15 @@ proc dohidelocalchanges {} { # spawn off a process to do git diff-index --cached HEAD proc dodiffindex {} { global lserial showlocalchanges vfilelimit curview - global hasworktree + global hasworktree git_version if {!$showlocalchanges || !$hasworktree} return incr lserial - set cmd "|git diff-index --cached HEAD" + if {[package vcompare $git_version "1.7.2"] >= 0} { + set cmd "|git diff-index --cached --ignore-submodules=dirty HEAD" + } else { + set cmd "|git diff-index --cached HEAD" + } if {$vfilelimit($curview) ne {}} { set cmd [concat $cmd -- $vfilelimit($curview)] } @@ -7020,7 +7040,7 @@ proc viewnextline {dir} { # add a list of tag or branch names at position pos # returns the number of names inserted proc appendrefs {pos ids var} { - global ctext linknum curview $var maxrefs mainheadid + global ctext linknum curview $var maxrefs visiblerefs mainheadid if {[catch {$ctext index $pos}]} { return 0 @@ -7041,14 +7061,14 @@ proc appendrefs {pos ids var} { if {[llength $tags] > $maxrefs} { # If we are displaying heads, and there are too many, # see if there are some important heads to display. - # Currently this means "master" and the current head. + # Currently that are the current head and heads listed in $visiblerefs option set itags {} if {$var eq "idheads"} { set utags {} foreach ti $tags { set hname [lindex $ti 0] set id [lindex $ti 1] - if {($hname eq "master" || $id eq $mainheadid) && + if {([lsearch -exact $visiblerefs $hname] != -1 || $id eq $mainheadid) && [llength $itags] < $maxrefs} { lappend itags $ti } else { @@ -7161,7 +7181,7 @@ proc make_idmark {id} { $canv raise $t } -proc selectline {l isnew {desired_loc {}}} { +proc selectline {l isnew {desired_loc {}} {switch_to_patch 0}} { global canv ctext commitinfo selectedline global canvy0 linespc parents children curview global currentid sha1entry @@ -7187,6 +7207,10 @@ proc selectline {l isnew {desired_loc {}}} { setcanvscroll } + if {$cmitmode ne "patch" && $switch_to_patch} { + set cmitmode "patch" + } + set y [expr {$canvy0 + $l * $linespc}] set ymax [lindex [$canv cget -scrollregion] 3] set ytop [expr {$y - $linespc - 1}] @@ -7477,6 +7501,14 @@ proc goforw {} { } } +proc go_to_parent {i} { + global parents curview targetid + set ps $parents($curview,$targetid) + if {[llength $ps] >= $i} { + selbyid [lindex $ps [expr $i - 1]] + } +} + proc gettree {id} { global treefilelist treeidlist diffids diffmergeid treepending global nullid nullid2 @@ -7705,7 +7737,7 @@ proc addtocflist {ids} { } proc diffcmd {ids flags} { - global log_showroot nullid nullid2 + global log_showroot nullid nullid2 git_version set i [lsearch -exact $ids $nullid] set j [lsearch -exact $ids $nullid2] @@ -7726,6 +7758,9 @@ proc diffcmd {ids flags} { } } } elseif {$j >= 0} { + if {[package vcompare $git_version "1.7.2"] >= 0} { + set flags "$flags --ignore-submodules=dirty" + } set cmd [concat | git diff-index --cached $flags] if {[llength $ids] > 1} { # comparing index with specific revision @@ -11575,7 +11610,29 @@ proc prefsok {} { proc formatdate {d} { global datetimeformat if {$d ne {}} { - set d [clock format [lindex $d 0] -format $datetimeformat] + # If $datetimeformat includes a timezone, display in the + # timezone of the argument. Otherwise, display in local time. + if {[string match {*%[zZ]*} $datetimeformat]} { + if {[catch {set d [clock format [lindex $d 0] -timezone [lindex $d 1] -format $datetimeformat]}]} { + # Tcl < 8.5 does not support -timezone. Emulate it by + # setting TZ (e.g. TZ=<-0430>+04:30). + global env + if {[info exists env(TZ)]} { + set savedTZ $env(TZ) + } + set zone [lindex $d 1] + set sign [string map {+ - - +} [string index $zone 0]] + set env(TZ) <$zone>$sign[string range $zone 1 2]:[string range $zone 3 4] + set d [clock format [lindex $d 0] -format $datetimeformat] + if {[info exists savedTZ]} { + set env(TZ) $savedTZ + } else { + unset env(TZ) + } + } + } else { + set d [clock format [lindex $d 0] -format $datetimeformat] + } } return $d } @@ -12001,6 +12058,7 @@ set wrapcomment "none" set showneartags 1 set hideremotes 0 set maxrefs 20 +set visiblerefs {"master"} set maxlinelen 200 set showlocalchanges 1 set limitdiffs 1 diff --git a/gitk-git/po/bg.po b/gitk-git/po/bg.po index 782397e6..f86a1611 100644 --- a/gitk-git/po/bg.po +++ b/gitk-git/po/bg.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitk master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-01-26 15:47-0800\n" -"PO-Revision-Date: 2014-01-08 08:03+0200\n" +"POT-Creation-Date: 2014-08-03 11:17+0300\n" +"PO-Revision-Date: 2014-07-28 07:32+0300\n" "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n" "Language-Team: Bulgarian <dict@fsa-bg.org>\n" "Language: bg\n" @@ -22,11 +22,11 @@ msgstr "" msgid "Couldn't get list of unmerged files:" msgstr "Списъкът с неслети файлове не може да бъде получен:" -#: gitk:212 gitk:2353 +#: gitk:212 gitk:2379 msgid "Color words" msgstr "Оцветяване на думите" -#: gitk:217 gitk:2353 gitk:8103 gitk:8136 +#: gitk:217 gitk:2379 gitk:8155 gitk:8188 msgid "Markup words" msgstr "Отбелязване на думите" @@ -40,13 +40,16 @@ msgstr "Грешка при изпълнение на командата с „- #: gitk:391 msgid "No files selected: --merge specified but no files are unmerged." -msgstr "Не са избрани файлове — указана е опцията „--merge“, но няма неслети файлове." +msgstr "" +"Не са избрани файлове — указана е опцията „--merge“, но няма неслети файлове." #: gitk:394 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." -msgstr "Не са избрани файлове — указана е опцията „--merge“, но няма неслети файлове в ограниченията." +msgstr "" +"Не са избрани файлове — указана е опцията „--merge“, но няма неслети файлове " +"в ограниченията." #: gitk:416 gitk:564 msgid "Error executing git log:" @@ -56,11 +59,11 @@ msgstr "Грешка при изпълнение на „git log“:" msgid "Reading" msgstr "Прочитане" -#: gitk:494 gitk:4429 +#: gitk:494 gitk:4470 msgid "Reading commits..." msgstr "Прочитане на подаванията…" -#: gitk:497 gitk:1635 gitk:4432 +#: gitk:497 gitk:1635 gitk:4473 msgid "No commits selected" msgstr "Не са избрани подавания" @@ -76,12 +79,12 @@ msgstr "Липсва информация за подавания" msgid "mc" msgstr "mc" -#: gitk:1930 gitk:4222 gitk:9552 gitk:11122 gitk:11401 +#: gitk:1930 gitk:4263 gitk:9604 gitk:11174 gitk:11453 msgid "OK" msgstr "Добре" -#: gitk:1932 gitk:4224 gitk:9079 gitk:9158 gitk:9274 gitk:9323 gitk:9554 -#: gitk:11123 gitk:11402 +#: gitk:1932 gitk:4265 gitk:9131 gitk:9210 gitk:9326 gitk:9375 gitk:9606 +#: gitk:11175 gitk:11454 msgid "Cancel" msgstr "Отказ" @@ -103,7 +106,7 @@ msgstr "Изброяване на указателите" #: gitk:2072 msgid "Start git gui" -msgstr "Стартиране на git gui" +msgstr "Стартиране на „git gui“" #: gitk:2074 msgid "Quit" @@ -137,11 +140,11 @@ msgstr "Изтриване на изгледа" msgid "All files" msgstr "Всички файлове" -#: gitk:2081 gitk:3975 +#: gitk:2081 gitk:4016 msgid "View" msgstr "Изглед" -#: gitk:2091 gitk:2101 gitk:2945 +#: gitk:2091 gitk:2101 gitk:2976 msgid "About gitk" msgstr "Относно gitk" @@ -153,7 +156,7 @@ msgstr "Клавишни комбинации" msgid "Help" msgstr "Помощ" -#: gitk:2183 gitk:8535 +#: gitk:2183 gitk:8587 msgid "SHA1 ID:" msgstr "SHA1:" @@ -165,708 +168,706 @@ msgstr "Ред" msgid "Find" msgstr "Търсене" -#: gitk:2266 -msgid "next" -msgstr "следващо" - -#: gitk:2267 -msgid "prev" -msgstr "предишно" - -#: gitk:2268 +#: gitk:2293 msgid "commit" msgstr "подаване" -#: gitk:2271 gitk:2273 gitk:4590 gitk:4613 gitk:4637 gitk:6653 gitk:6725 -#: gitk:6810 +#: gitk:2297 gitk:2299 gitk:4631 gitk:4654 gitk:4678 gitk:6698 gitk:6770 +#: gitk:6855 msgid "containing:" msgstr "съдържащо:" -#: gitk:2274 gitk:3457 gitk:3462 gitk:4666 +#: gitk:2300 gitk:3488 gitk:3493 gitk:4707 msgid "touching paths:" msgstr "засягащо пътищата:" -#: gitk:2275 gitk:4680 +#: gitk:2301 gitk:4721 msgid "adding/removing string:" msgstr "добавящо/премахващо низ" -#: gitk:2276 gitk:4682 +#: gitk:2302 gitk:4723 msgid "changing lines matching:" msgstr "променящо редове напасващи:" -#: gitk:2285 gitk:2287 gitk:4669 +#: gitk:2311 gitk:2313 gitk:4710 msgid "Exact" msgstr "Точно" -#: gitk:2287 gitk:4757 gitk:6621 +#: gitk:2313 gitk:4798 gitk:6666 msgid "IgnCase" msgstr "Без регистър" -#: gitk:2287 gitk:4639 gitk:4755 gitk:6617 +#: gitk:2313 gitk:4680 gitk:4796 gitk:6662 msgid "Regexp" -msgstr "Рег. изр." +msgstr "Рег. израз" -#: gitk:2289 gitk:2290 gitk:4777 gitk:4807 gitk:4814 gitk:6746 gitk:6814 +#: gitk:2315 gitk:2316 gitk:4818 gitk:4848 gitk:4855 gitk:6791 gitk:6859 msgid "All fields" msgstr "Всички полета" -#: gitk:2290 gitk:4774 gitk:4807 gitk:6684 +#: gitk:2316 gitk:4815 gitk:4848 gitk:6729 msgid "Headline" msgstr "Първи ред" -#: gitk:2291 gitk:4774 gitk:6684 gitk:6814 gitk:7283 +#: gitk:2317 gitk:4815 gitk:6729 gitk:6859 gitk:7332 msgid "Comments" msgstr "Коментари" -#: gitk:2291 gitk:4774 gitk:4779 gitk:4814 gitk:6684 gitk:7218 gitk:8713 -#: gitk:8728 +#: gitk:2317 gitk:4815 gitk:4820 gitk:4855 gitk:6729 gitk:7267 gitk:8765 +#: gitk:8780 msgid "Author" msgstr "Автор" -#: gitk:2291 gitk:4774 gitk:6684 gitk:7220 +#: gitk:2317 gitk:4815 gitk:6729 gitk:7269 msgid "Committer" msgstr "Подаващ" -#: gitk:2322 +#: gitk:2348 msgid "Search" msgstr "Търсене" -#: gitk:2330 +#: gitk:2356 msgid "Diff" msgstr "Разлики" -#: gitk:2332 +#: gitk:2358 msgid "Old version" msgstr "Стара версия" -#: gitk:2334 +#: gitk:2360 msgid "New version" msgstr "Нова версия" -#: gitk:2336 +#: gitk:2362 msgid "Lines of context" msgstr "Контекст в редове" -#: gitk:2346 +#: gitk:2372 msgid "Ignore space change" msgstr "Празните знаци без значение" -#: gitk:2350 gitk:2352 gitk:7842 gitk:8089 +#: gitk:2376 gitk:2378 gitk:7894 gitk:8141 msgid "Line diff" msgstr "Поредови разлики" -#: gitk:2417 +#: gitk:2443 msgid "Patch" msgstr "Кръпка" -#: gitk:2419 +#: gitk:2445 msgid "Tree" msgstr "Дърво" -#: gitk:2577 gitk:2597 +#: gitk:2604 gitk:2624 msgid "Diff this -> selected" msgstr "Разлики между това и избраното" -#: gitk:2578 gitk:2598 +#: gitk:2605 gitk:2625 msgid "Diff selected -> this" msgstr "Разлики между избраното и това" -#: gitk:2579 gitk:2599 +#: gitk:2606 gitk:2626 msgid "Make patch" msgstr "Създаване на кръпка" -#: gitk:2580 gitk:9137 +#: gitk:2607 gitk:9189 msgid "Create tag" msgstr "Създаване на етикет" -#: gitk:2581 gitk:9254 +#: gitk:2608 gitk:9306 msgid "Write commit to file" msgstr "Запазване на подаването във файл" -#: gitk:2582 gitk:9311 +#: gitk:2609 gitk:9363 msgid "Create new branch" msgstr "Създаване на нов клон" -#: gitk:2583 +#: gitk:2610 msgid "Cherry-pick this commit" msgstr "Отбиране на това подаване" -#: gitk:2584 +#: gitk:2611 msgid "Reset HEAD branch to here" msgstr "Привеждане на върха на клона към текущото подаване" -#: gitk:2585 +#: gitk:2612 msgid "Mark this commit" msgstr "Отбелязване на това подаване" -#: gitk:2586 +#: gitk:2613 msgid "Return to mark" msgstr "Връщане към отбелязаното подаване" -#: gitk:2587 +#: gitk:2614 msgid "Find descendant of this and mark" msgstr "Откриване и отбелязване на наследниците" -#: gitk:2588 +#: gitk:2615 msgid "Compare with marked commit" msgstr "Сравнение с отбелязаното подаване" -#: gitk:2589 gitk:2600 +#: gitk:2616 gitk:2627 msgid "Diff this -> marked commit" msgstr "Разлики между това и отбелязаното" -#: gitk:2590 gitk:2601 +#: gitk:2617 gitk:2628 msgid "Diff marked commit -> this" msgstr "Разлики между отбелязаното и това" -#: gitk:2591 +#: gitk:2618 msgid "Revert this commit" msgstr "Отмяна на това подаване" -#: gitk:2607 +#: gitk:2634 msgid "Check out this branch" msgstr "Изтегляне на този клон" -#: gitk:2608 +#: gitk:2635 msgid "Remove this branch" msgstr "Изтриване на този клон" -#: gitk:2615 +#: gitk:2642 msgid "Highlight this too" msgstr "Отбелязване и на това" -#: gitk:2616 +#: gitk:2643 msgid "Highlight this only" msgstr "Отбелязване само на това" -#: gitk:2617 +#: gitk:2644 msgid "External diff" msgstr "Външна програма за разлики" -#: gitk:2618 +#: gitk:2645 msgid "Blame parent commit" msgstr "Анотиране на родителското подаване" -#: gitk:2625 +#: gitk:2652 msgid "Show origin of this line" msgstr "Показване на произхода на този ред" -#: gitk:2626 +#: gitk:2653 msgid "Run git gui blame on this line" msgstr "Изпълнение на „git gui blame“ върху този ред" -#: gitk:2947 +#: gitk:2978 msgid "" "\n" "Gitk - a commit viewer for git\n" "\n" -"Copyright © 2005-2011 Paul Mackerras\n" +"Copyright © 2005-2014 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" "Gitk — визуализация на подаванията в Git\n" "\n" -"Авторски права: © 2005-2011 Paul Mackerras\n" +"Авторски права: © 2005-2014 Paul Mackerras\n" "\n" "Използвайте и разпространявайте при условията на ОПЛ на ГНУ" -#: gitk:2955 gitk:3020 gitk:9738 +#: gitk:2986 gitk:3051 gitk:9790 msgid "Close" msgstr "Затваряне" -#: gitk:2976 +#: gitk:3007 msgid "Gitk key bindings" msgstr "Клавишни комбинации" -#: gitk:2979 +#: gitk:3010 msgid "Gitk key bindings:" msgstr "Клавишни комбинации:" -#: gitk:2981 +#: gitk:3012 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tСпиране на програмата" -#: gitk:2982 +#: gitk:3013 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tЗатваряне на прозореца" -#: gitk:2983 +#: gitk:3014 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tКъм първото подаване" -#: gitk:2984 +#: gitk:3015 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tКъм последното подаване" -#: gitk:2985 +#: gitk:3016 msgid "<Up>, p, k\tMove up one commit" msgstr "<Up>, p, k\tЕдно подаване нагоре" -#: gitk:2986 +#: gitk:3017 msgid "<Down>, n, j\tMove down one commit" msgstr "<Down>, n, j\tЕдно подаване надолу" -#: gitk:2987 +#: gitk:3018 msgid "<Left>, z, h\tGo back in history list" msgstr "<Left>, z, h\tНазад в историята" -#: gitk:2988 +#: gitk:3019 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Right>, x, l\tНапред в историята" -#: gitk:2989 +#: gitk:3020 msgid "<PageUp>\tMove up one page in commit list" -msgstr "<PageUp>\tЕдна страница нагоре в списъка с подаванията" +msgstr "<PageUp>\tСтраница нагоре в списъка с подаванията" -#: gitk:2990 +#: gitk:3021 msgid "<PageDown>\tMove down one page in commit list" -msgstr "<PageDown>\tЕдна страница надолу в списъка с подаванията" +msgstr "<PageDown>\tСтраница надолу в списъка с подаванията" -#: gitk:2991 +#: gitk:3022 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tКъм началото на списъка с подаванията" -#: gitk:2992 +#: gitk:3023 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tКъм края на списъка с подаванията" -#: gitk:2993 +#: gitk:3024 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" -msgstr "<%s-Up>\tПридвижване на списъка с подавания с един ред нагоре" +msgstr "<%s-Up>\tРед нагоре в списъка с подавания" -#: gitk:2994 +#: gitk:3025 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" -msgstr "<%s-Down>\tПридвижване на списъка с подавания с един ред надолу" +msgstr "<%s-Down>\tРед надолу в списъка с подавания" -#: gitk:2995 +#: gitk:3026 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" -msgstr "<%s-PageUp>\tПридвижване на списъка с подавания с една страница нагоре" +msgstr "<%s-PageUp>\tСтраница нагоре в списъка с подавания" -#: gitk:2996 +#: gitk:3027 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" -msgstr "<%s-PageDown>\tПридвижване на списъка с подавания с една страница надолу" +msgstr "<%s-PageDown>\tСтраница надолу в списъка с подавания" -#: gitk:2997 +#: gitk:3028 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Up>\tТърсене назад (визуално нагоре, исторически — последващи)" -#: gitk:2998 +#: gitk:3029 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" -msgstr "<Shift-Down>\tТърсене напред (визуално надолу, исторически — предхождащи)" +msgstr "" +"<Shift-Down>\tТърсене напред (визуално надолу, исторически — предхождащи)" -#: gitk:2999 +#: gitk:3030 msgid "<Delete>, b\tScroll diff view up one page" -msgstr "<Delete>, b\tПридвижване на изгледа за разлики една страница нагоре" +msgstr "<Delete>, b\tСтраница нагоре в изгледа за разлики" -#: gitk:3000 +#: gitk:3031 msgid "<Backspace>\tScroll diff view up one page" -msgstr "<Backspace>\tПридвижване на изгледа за разлики една страница нагоре" +msgstr "<Backspace>\tСтраница надолу в изгледа за разлики" -#: gitk:3001 +#: gitk:3032 msgid "<Space>\t\tScroll diff view down one page" -msgstr "<Space>\t\tПридвижване на изгледа за разлики една страница надолу" +msgstr "<Space>\t\tСтраница надолу в изгледа за разлики" -#: gitk:3002 +#: gitk:3033 msgid "u\t\tScroll diff view up 18 lines" -msgstr "u\t\tПридвижване на изгледа за разлики 18 реда нагоре" +msgstr "u\t\t18 реда нагоре в изгледа за разлики" -#: gitk:3003 +#: gitk:3034 msgid "d\t\tScroll diff view down 18 lines" -msgstr "d\t\tПридвижване на изгледа за разлики 18 реда надолу" +msgstr "d\t\t18 реда надолу в изгледа за разлики" -#: gitk:3004 +#: gitk:3035 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tТърсене" -#: gitk:3005 +#: gitk:3036 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tКъм следващата поява" -#: gitk:3006 +#: gitk:3037 msgid "<Return>\tMove to next find hit" msgstr "<Return>\tКъм следващата поява" -#: gitk:3007 +#: gitk:3038 msgid "/\t\tFocus the search box" msgstr "/\t\tФокус върху полето за търсене" -#: gitk:3008 +#: gitk:3039 msgid "?\t\tMove to previous find hit" msgstr "?\t\tКъм предишната поява" -#: gitk:3009 +#: gitk:3040 msgid "f\t\tScroll diff view to next file" -msgstr "f\t\tПридвижване на изгледа за разлики към следващия ред" +msgstr "f\t\tСледващ файл в изгледа за разлики" -#: gitk:3010 +#: gitk:3041 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tТърсене на следващата поява в изгледа за разлики" -#: gitk:3011 +#: gitk:3042 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tТърсене на предишната поява в изгледа за разлики" -#: gitk:3012 +#: gitk:3043 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tПо-голям размер на шрифта" -#: gitk:3013 +#: gitk:3044 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tПо-голям размер на шрифта" -#: gitk:3014 +#: gitk:3045 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tПо-малък размер на шрифта" -#: gitk:3015 +#: gitk:3046 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tПо-малък размер на шрифта" -#: gitk:3016 +#: gitk:3047 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tОбновяване" -#: gitk:3471 gitk:3480 +#: gitk:3512 gitk:3521 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Грешка при създаването на временната директория „%s“:" -#: gitk:3493 +#: gitk:3534 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Грешка при получаването на „%s“ от %s:" -#: gitk:3556 +#: gitk:3597 msgid "command failed:" msgstr "неуспешно изпълнение на команда:" -#: gitk:3705 +#: gitk:3746 msgid "No such commit" msgstr "Такова подаване няма" -#: gitk:3719 +#: gitk:3760 msgid "git gui blame: command failed:" -msgstr "git gui blame: неуспешно изпълнение на команда:" +msgstr "„git gui blame“: неуспешно изпълнение на команда:" -#: gitk:3750 +#: gitk:3791 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Върхът за сливане не може да бъде прочетен: %s" -#: gitk:3758 +#: gitk:3799 #, tcl-format msgid "Error reading index: %s" msgstr "Грешка при прочитане на индекса: %s" -#: gitk:3783 +#: gitk:3824 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Командата „git blame“ не може да бъде стартирана: %s" -#: gitk:3786 gitk:6652 +#: gitk:3827 gitk:6697 msgid "Searching" msgstr "Търсене" -#: gitk:3818 +#: gitk:3859 #, tcl-format msgid "Error running git blame: %s" msgstr "Грешка при изпълнението на „git blame“: %s" -#: gitk:3846 +#: gitk:3887 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Този ред идва от подаването %s, което не е в изгледа" -#: gitk:3860 +#: gitk:3901 msgid "External diff viewer failed:" msgstr "Неуспешно изпълнение на външната програма за разлики:" -#: gitk:3978 +#: gitk:4019 msgid "Gitk view definition" msgstr "Дефиниция на изглед в Gitk" -#: gitk:3982 +#: gitk:4023 msgid "Remember this view" msgstr "Запазване на този изглед" -#: gitk:3983 +#: gitk:4024 msgid "References (space separated list):" msgstr "Указатели (списък с разделител интервал):" -#: gitk:3984 +#: gitk:4025 msgid "Branches & tags:" msgstr "Клони и етикети:" -#: gitk:3985 +#: gitk:4026 msgid "All refs" msgstr "Всички указатели" -#: gitk:3986 +#: gitk:4027 msgid "All (local) branches" msgstr "Всички (локални) клони" -#: gitk:3987 +#: gitk:4028 msgid "All tags" msgstr "Всички етикети" -#: gitk:3988 +#: gitk:4029 msgid "All remote-tracking branches" msgstr "Всички следящи клони" -#: gitk:3989 +#: gitk:4030 msgid "Commit Info (regular expressions):" msgstr "Информация за подаване (рег. изр.):" -#: gitk:3990 +#: gitk:4031 msgid "Author:" msgstr "Автор:" -#: gitk:3991 +#: gitk:4032 msgid "Committer:" msgstr "Подал:" -#: gitk:3992 +#: gitk:4033 msgid "Commit Message:" msgstr "Съобщение при подаване:" -#: gitk:3993 +#: gitk:4034 msgid "Matches all Commit Info criteria" msgstr "Съвпадение по коя да е информация за подаването" -#: gitk:3994 +#: gitk:4035 msgid "Changes to Files:" msgstr "Промени по файловете:" -#: gitk:3995 +#: gitk:4036 msgid "Fixed String" msgstr "Дословен низ" -#: gitk:3996 +#: gitk:4037 msgid "Regular Expression" msgstr "Регулярен израз" -#: gitk:3997 +#: gitk:4038 msgid "Search string:" msgstr "Низ за търсене:" -#: gitk:3998 +#: gitk:4039 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -msgstr "Дата на подаване („2 weeks ago“ (преди 2 седмици), „2009-03-17 15:27:38“, „March 17, 2009 15:27:38“):" +msgstr "" +"Дата на подаване („2 weeks ago“ (преди 2 седмици), „2009-03-17 15:27:38“, " +"„March 17, 2009 15:27:38“):" -#: gitk:3999 +#: gitk:4040 msgid "Since:" msgstr "От:" -#: gitk:4000 +#: gitk:4041 msgid "Until:" msgstr "До:" -#: gitk:4001 +#: gitk:4042 msgid "Limit and/or skip a number of revisions (positive integer):" -msgstr "Ограничаване и/или прескачане на определен брой версии (неотрицателно цяло число):" +msgstr "" +"Ограничаване и/или прескачане на определен брой версии (неотрицателно цяло " +"число):" -#: gitk:4002 +#: gitk:4043 msgid "Number to show:" msgstr "Брой показани:" -#: gitk:4003 +#: gitk:4044 msgid "Number to skip:" msgstr "Брой прескочени:" -#: gitk:4004 +#: gitk:4045 msgid "Miscellaneous options:" msgstr "Разни:" -#: gitk:4005 +#: gitk:4046 msgid "Strictly sort by date" msgstr "Подреждане по дата" -#: gitk:4006 +#: gitk:4047 msgid "Mark branch sides" msgstr "Отбелязване на страните по клона" -#: gitk:4007 +#: gitk:4048 msgid "Limit to first parent" -msgstr "Само първият родител" +msgstr "Само първия родител" -#: gitk:4008 +#: gitk:4049 msgid "Simple history" msgstr "Опростена история" -#: gitk:4009 +#: gitk:4050 msgid "Additional arguments to git log:" msgstr "Допълнителни аргументи към „git log“:" -#: gitk:4010 +#: gitk:4051 msgid "Enter files and directories to include, one per line:" msgstr "Въведете файловете и директориите за включване, по елемент на ред" -#: gitk:4011 +#: gitk:4052 msgid "Command to generate more commits to include:" -msgstr "Команда за генерирането на допълнителни подавания, които да бъдат включени:" +msgstr "" +"Команда за генерирането на допълнителни подавания, които да бъдат включени:" -#: gitk:4135 +#: gitk:4176 msgid "Gitk: edit view" msgstr "Gitk: редактиране на изглед" -#: gitk:4143 +#: gitk:4184 msgid "-- criteria for selecting revisions" msgstr "— критерии за избор на версии" -#: gitk:4148 +#: gitk:4189 msgid "View Name" msgstr "Име на изглед" -#: gitk:4223 +#: gitk:4264 msgid "Apply (F5)" msgstr "Прилагане (F5)" -#: gitk:4261 +#: gitk:4302 msgid "Error in commit selection arguments:" msgstr "Грешка в аргументите за избор на подавания:" -#: gitk:4314 gitk:4366 gitk:4827 gitk:4841 gitk:6107 gitk:12184 gitk:12185 +#: gitk:4355 gitk:4407 gitk:4868 gitk:4882 gitk:6152 gitk:12281 gitk:12282 msgid "None" msgstr "Няма" -#: gitk:4924 gitk:4929 +#: gitk:4965 gitk:4970 msgid "Descendant" msgstr "Наследник" -#: gitk:4925 +#: gitk:4966 msgid "Not descendant" msgstr "Не е наследник" -#: gitk:4932 gitk:4937 +#: gitk:4973 gitk:4978 msgid "Ancestor" msgstr "Предшественик" -#: gitk:4933 +#: gitk:4974 msgid "Not ancestor" msgstr "Не е предшественик" -#: gitk:5223 +#: gitk:5268 msgid "Local changes checked in to index but not committed" msgstr "Локални промени добавени към индекса, но неподадени" -#: gitk:5259 +#: gitk:5304 msgid "Local uncommitted changes, not checked in to index" msgstr "Локални промени извън индекса" -#: gitk:7032 +#: gitk:7077 msgid "and many more" msgstr "и още много" -#: gitk:7035 +#: gitk:7080 msgid "many" msgstr "много" -#: gitk:7222 +#: gitk:7271 msgid "Tags:" msgstr "Етикети:" -#: gitk:7239 gitk:7245 gitk:8708 +#: gitk:7288 gitk:7294 gitk:8760 msgid "Parent" msgstr "Родител" -#: gitk:7250 +#: gitk:7299 msgid "Child" msgstr "Дете" -#: gitk:7259 +#: gitk:7308 msgid "Branch" msgstr "Клон" -#: gitk:7262 +#: gitk:7311 msgid "Follows" msgstr "Следва" -#: gitk:7265 +#: gitk:7314 msgid "Precedes" msgstr "Предшества" -#: gitk:7849 +#: gitk:7901 #, tcl-format msgid "Error getting diffs: %s" msgstr "Грешка при получаването на разликите: %s" -#: gitk:8533 +#: gitk:8585 msgid "Goto:" msgstr "Към ред:" -#: gitk:8554 +#: gitk:8606 #, tcl-format msgid "Short SHA1 id %s is ambiguous" -msgstr "Съкратената SHA1 %s не е еднозначна" +msgstr "Съкратената сума по SHA1 %s не е еднозначна" -#: gitk:8561 +#: gitk:8613 #, tcl-format msgid "Revision %s is not known" msgstr "Непозната версия %s" -#: gitk:8571 +#: gitk:8623 #, tcl-format msgid "SHA1 id %s is not known" -msgstr "Непозната SHA1 %s" +msgstr "Непозната сума по SHA1 %s" -#: gitk:8573 +#: gitk:8625 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Версия %s не е в текущия изглед" -#: gitk:8715 gitk:8730 +#: gitk:8767 gitk:8782 msgid "Date" msgstr "Дата" -#: gitk:8718 +#: gitk:8770 msgid "Children" msgstr "Деца" -#: gitk:8781 +#: gitk:8833 #, tcl-format msgid "Reset %s branch to here" msgstr "Зануляване на клона „%s“ към текущото подаване" -#: gitk:8783 +#: gitk:8835 msgid "Detached head: can't reset" msgstr "Несвързан връх: невъзможно зануляване" -#: gitk:8888 gitk:8894 +#: gitk:8940 gitk:8946 msgid "Skipping merge commit " msgstr "Пропускане на подаването на сливането" -#: gitk:8903 gitk:8908 +#: gitk:8955 gitk:8960 msgid "Error getting patch ID for " msgstr "Грешка при получаването на идентификатора на " -#: gitk:8904 gitk:8909 +#: gitk:8956 gitk:8961 msgid " - stopping\n" msgstr " — спиране\n" -#: gitk:8914 gitk:8917 gitk:8925 gitk:8939 gitk:8948 +#: gitk:8966 gitk:8969 gitk:8977 gitk:8991 gitk:9000 msgid "Commit " msgstr "Подаване" -#: gitk:8918 +#: gitk:8970 msgid "" " is the same patch as\n" " " @@ -874,7 +875,7 @@ msgstr "" " е същата кръпка като\n" " " -#: gitk:8926 +#: gitk:8978 msgid "" " differs from\n" " " @@ -882,135 +883,138 @@ msgstr "" " се различава от\n" " " -#: gitk:8928 +#: gitk:8980 msgid "" "Diff of commits:\n" "\n" -msgstr "Разлика между подаванията:\n\n" +msgstr "" +"Разлика между подаванията:\n" +"\n" -#: gitk:8940 gitk:8949 +#: gitk:8992 gitk:9001 #, tcl-format msgid " has %s children - stopping\n" msgstr " има %s деца — спиране\n" -#: gitk:8968 +#: gitk:9020 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Грешка при запазването на подаването във файл: %s" -#: gitk:8974 +#: gitk:9026 #, tcl-format msgid "Error diffing commits: %s" msgstr "Грешка при изчисляването на разликите между подаванията: %s" -#: gitk:9020 +#: gitk:9072 msgid "Top" msgstr "Най-горе" -#: gitk:9021 +#: gitk:9073 msgid "From" msgstr "От" -#: gitk:9026 +#: gitk:9078 msgid "To" msgstr "До" -#: gitk:9050 +#: gitk:9102 msgid "Generate patch" msgstr "Генериране на кръпка" -#: gitk:9052 +#: gitk:9104 msgid "From:" msgstr "От:" -#: gitk:9061 +#: gitk:9113 msgid "To:" msgstr "До:" -#: gitk:9070 +#: gitk:9122 msgid "Reverse" msgstr "Обръщане" -#: gitk:9072 gitk:9268 +#: gitk:9124 gitk:9320 msgid "Output file:" msgstr "Запазване във файла:" -#: gitk:9078 +#: gitk:9130 msgid "Generate" msgstr "Генериране" -#: gitk:9116 +#: gitk:9168 msgid "Error creating patch:" msgstr "Грешка при създаването на кръпка:" -#: gitk:9139 gitk:9256 gitk:9313 +#: gitk:9191 gitk:9308 gitk:9365 msgid "ID:" msgstr "Идентификатор:" -#: gitk:9148 +#: gitk:9200 msgid "Tag name:" msgstr "Име на етикет:" -#: gitk:9151 +#: gitk:9203 msgid "Tag message is optional" msgstr "Съобщението за етикет е незадължително" -#: gitk:9153 +#: gitk:9205 msgid "Tag message:" msgstr "Съобщение за етикет:" -#: gitk:9157 gitk:9322 +#: gitk:9209 gitk:9374 msgid "Create" msgstr "Създаване" -#: gitk:9175 +#: gitk:9227 msgid "No tag name specified" msgstr "Липсва име на етикет" -#: gitk:9179 +#: gitk:9231 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Етикетът „%s“ вече съществува" -#: gitk:9189 +#: gitk:9241 msgid "Error creating tag:" msgstr "Грешка при създаването на етикет:" -#: gitk:9265 +#: gitk:9317 msgid "Command:" msgstr "Команда:" -#: gitk:9273 +#: gitk:9325 msgid "Write" -msgstr "Pdmdpldke" +msgstr "Запазване" -#: gitk:9291 +#: gitk:9343 msgid "Error writing commit:" msgstr "Грешка при запазването на подаването:" -#: gitk:9318 +#: gitk:9370 msgid "Name:" msgstr "Име:" -#: gitk:9341 +#: gitk:9393 msgid "Please specify a name for the new branch" msgstr "Укажете име за новия клон" -#: gitk:9346 +#: gitk:9398 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Клонът „%s“ вече съществува. Да бъде ли презаписан?" -#: gitk:9413 +#: gitk:9465 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" -msgstr "Подаването „%s“ вече е включено в клона „%s“ — да бъде ли приложено отново?" +msgstr "" +"Подаването „%s“ вече е включено в клона „%s“ — да бъде ли приложено отново?" -#: gitk:9418 +#: gitk:9470 msgid "Cherry-picking" msgstr "Отбиране" -#: gitk:9427 +#: gitk:9479 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1019,7 +1023,7 @@ msgstr "" "Неуспешно отбиране, защото във файла „%s“ има локални промени.\n" "Подайте, занулете или ги скатайте и пробвайте отново." -#: gitk:9433 +#: gitk:9485 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1027,20 +1031,20 @@ msgstr "" "Неуспешно отбиране поради конфликти при сливане.\n" "Искате ли да ги коригирате чрез „git citool“?" -#: gitk:9449 gitk:9507 +#: gitk:9501 gitk:9559 msgid "No changes committed" msgstr "Не са подадени промени" -#: gitk:9476 +#: gitk:9528 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "Подаването „%s“ не е включено в клона „%s“. Да бъде ли отменено?" -#: gitk:9481 +#: gitk:9533 msgid "Reverting" msgstr "Отмяна" -#: gitk:9489 +#: gitk:9541 #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1049,7 +1053,7 @@ msgstr "" "Неуспешна отмяна, защото във файла „%s“ има локални промени.\n" "Подайте, занулете или ги скатайте и пробвайте отново.<" -#: gitk:9493 +#: gitk:9545 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1057,28 +1061,28 @@ msgstr "" "Неуспешно отмяна поради конфликти при сливане.\n" "Искате ли да ги коригирате чрез „git citool“?" -#: gitk:9536 +#: gitk:9588 msgid "Confirm reset" msgstr "Потвърждаване на зануляването" -#: gitk:9538 +#: gitk:9590 #, tcl-format msgid "Reset branch %s to %s?" -msgstr "Да се занули ли клона „%s“ към „%s“?" +msgstr "Да се занули ли клонът „%s“ към „%s“?" -#: gitk:9540 +#: gitk:9592 msgid "Reset type:" msgstr "Вид зануляване:" -#: gitk:9543 +#: gitk:9595 msgid "Soft: Leave working tree and index untouched" msgstr "Слабо: работното дърво и индекса остават същите" -#: gitk:9546 +#: gitk:9598 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Смесено: работното дърво остава същото, индексът се занулява" -#: gitk:9549 +#: gitk:9601 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1086,249 +1090,251 @@ msgstr "" "Силно: зануляване и на работното дърво, и на индекса\n" "(*ВСИЧКИ* локални промени ще бъдат безвъзвратно загубени)" -#: gitk:9566 +#: gitk:9618 msgid "Resetting" msgstr "Зануляване" -#: gitk:9626 +#: gitk:9678 msgid "Checking out" msgstr "Изтегляне" -#: gitk:9679 +#: gitk:9731 msgid "Cannot delete the currently checked-out branch" msgstr "Текущо изтегленият клон не може да бъде изтрит" -#: gitk:9685 +#: gitk:9737 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" "Really delete branch %s?" msgstr "" "Подаванията на клона „%s“ не са на никой друг клон.\n" -"Наистина ли да се изтрие клона „%s“?" +"Наистина ли да се изтрие клонът „%s“?" -#: gitk:9716 +#: gitk:9768 #, tcl-format msgid "Tags and heads: %s" msgstr "Етикети и върхове: %s" -#: gitk:9731 +#: gitk:9783 msgid "Filter" msgstr "Филтриране" -#: gitk:10027 +#: gitk:10079 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." -msgstr "Грешка при прочитането на топологията на подаванията. Информацията за клона и предшестващите/следващите етикети ще е непълна." +msgstr "" +"Грешка при прочитането на топологията на подаванията. Информацията за клона " +"и предшестващите/следващите етикети ще е непълна." -#: gitk:11004 +#: gitk:11056 msgid "Tag" msgstr "Етикет" -#: gitk:11008 +#: gitk:11060 msgid "Id" msgstr "Идентификатор" -#: gitk:11091 +#: gitk:11143 msgid "Gitk font chooser" msgstr "Избор на шрифт за Gitk" -#: gitk:11108 +#: gitk:11160 msgid "B" msgstr "Ч" -#: gitk:11111 +#: gitk:11163 msgid "I" msgstr "К" -#: gitk:11229 +#: gitk:11281 msgid "Commit list display options" msgstr "Настройки на списъка с подавания" -#: gitk:11232 +#: gitk:11284 msgid "Maximum graph width (lines)" msgstr "Максимална широчина на графа (в редове)" -#: gitk:11235 +#: gitk:11287 #, tcl-format msgid "Maximum graph width (% of pane)" msgstr "Максимална широчина на графа (% от панела)" -#: gitk:11238 +#: gitk:11290 msgid "Show local changes" msgstr "Показване на локалните промени" -#: gitk:11241 +#: gitk:11293 msgid "Auto-select SHA1 (length)" msgstr "Автоматично избиране на SHA1 (дължина)" -#: gitk:11245 +#: gitk:11297 msgid "Hide remote refs" msgstr "Скриване на отдалечените указатели" -#: gitk:11249 +#: gitk:11301 msgid "Diff display options" msgstr "Настройки на показването на разликите" -#: gitk:11251 +#: gitk:11303 msgid "Tab spacing" msgstr "Широчина на табулатора" -#: gitk:11254 +#: gitk:11306 msgid "Display nearby tags/heads" msgstr "Извеждане на близките етикети и върхове" -#: gitk:11257 +#: gitk:11309 msgid "Maximum # tags/heads to show" msgstr "Максимален брой етикети/върхове за показване" -#: gitk:11260 +#: gitk:11312 msgid "Limit diffs to listed paths" msgstr "Разлика само в избраните пътища" -#: gitk:11263 +#: gitk:11315 msgid "Support per-file encodings" msgstr "Поддръжка на различни кодирания за всеки файл" -#: gitk:11269 gitk:11416 +#: gitk:11321 gitk:11468 msgid "External diff tool" msgstr "Външен инструмент за разлики" -#: gitk:11270 +#: gitk:11322 msgid "Choose..." msgstr "Избор…" -#: gitk:11275 +#: gitk:11327 msgid "General options" msgstr "Общи настройки" -#: gitk:11278 +#: gitk:11330 msgid "Use themed widgets" msgstr "Използване на тема за графичните обекти" -#: gitk:11280 +#: gitk:11332 msgid "(change requires restart)" msgstr "(промяната изисква рестартиране на Gitk)" -#: gitk:11282 +#: gitk:11334 msgid "(currently unavailable)" msgstr "(в момента недостъпно)" -#: gitk:11293 +#: gitk:11345 msgid "Colors: press to choose" msgstr "Цветове: избира се с натискане" -#: gitk:11296 +#: gitk:11348 msgid "Interface" msgstr "Интерфейс" -#: gitk:11297 +#: gitk:11349 msgid "interface" msgstr "интерфейс" -#: gitk:11300 +#: gitk:11352 msgid "Background" msgstr "Фон" -#: gitk:11301 gitk:11331 +#: gitk:11353 gitk:11383 msgid "background" -msgstr "Фон" +msgstr "фон" -#: gitk:11304 +#: gitk:11356 msgid "Foreground" msgstr "Знаци" -#: gitk:11305 +#: gitk:11357 msgid "foreground" msgstr "знаци" -#: gitk:11308 +#: gitk:11360 msgid "Diff: old lines" msgstr "Разлика: стари редове" -#: gitk:11309 +#: gitk:11361 msgid "diff old lines" msgstr "разлика, стари редове" -#: gitk:11313 +#: gitk:11365 msgid "Diff: new lines" msgstr "Разлика: нови редове" -#: gitk:11314 +#: gitk:11366 msgid "diff new lines" msgstr "разлика, нови редове" -#: gitk:11318 +#: gitk:11370 msgid "Diff: hunk header" msgstr "Разлика: начало на парче" -#: gitk:11320 +#: gitk:11372 msgid "diff hunk header" msgstr "разлика, начало на парче" -#: gitk:11324 +#: gitk:11376 msgid "Marked line bg" msgstr "Фон на отбелязан ред" -#: gitk:11326 +#: gitk:11378 msgid "marked line background" -msgstr "Фон на отбелязан ред" +msgstr "фон на отбелязан ред" -#: gitk:11330 +#: gitk:11382 msgid "Select bg" msgstr "Избор на фон" -#: gitk:11339 +#: gitk:11391 msgid "Fonts: press to choose" msgstr "Шрифтове: избира се с натискане" -#: gitk:11341 +#: gitk:11393 msgid "Main font" msgstr "Основен шрифт" -#: gitk:11342 +#: gitk:11394 msgid "Diff display font" msgstr "Шрифт за разликите" -#: gitk:11343 +#: gitk:11395 msgid "User interface font" msgstr "Шрифт на интерфейса" -#: gitk:11365 +#: gitk:11417 msgid "Gitk preferences" msgstr "Настройки на Gitk" -#: gitk:11374 +#: gitk:11426 msgid "General" msgstr "Общи" -#: gitk:11375 +#: gitk:11427 msgid "Colors" msgstr "Цветове" -#: gitk:11376 +#: gitk:11428 msgid "Fonts" msgstr "Шрифтове" -#: gitk:11426 +#: gitk:11478 #, tcl-format msgid "Gitk: choose color for %s" -msgstr "Gitk: избор на цвят на %s" +msgstr "Gitk: избор на цвят на „%s“" -#: gitk:12080 +#: gitk:12177 msgid "Cannot find a git repository here." msgstr "Тук липсва хранилище на Git." -#: gitk:12127 +#: gitk:12224 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Нееднозначен аргумент „%s“: има и такава версия, и такъв файл" -#: gitk:12139 +#: gitk:12236 msgid "Bad arguments to gitk:" msgstr "Неправилни аргументи на gitk:" -#: gitk:12242 +#: gitk:12339 msgid "Command line" msgstr "Команден ред" diff --git a/gitk-git/po/vi.po b/gitk-git/po/vi.po new file mode 100644 index 00000000..4dfe125a --- /dev/null +++ b/gitk-git/po/vi.po @@ -0,0 +1,1351 @@ +# Vietnamese translations for gitk package. +# Bản dịch tiếng Việt cho gói gitk. +# This file is distributed under the same license as the gitk package. +# Trần Ngọc Quân <vnwildman@gmail.com>, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: gitk @@GIT_VERSION@@\n" +"Report-Msgid-Bugs-To: Paul Mackerras <paulus@samba.org>\n" +"POT-Creation-Date: 2013-12-14 09:24+0700\n" +"PO-Revision-Date: 2013-12-14 14:40+0700\n" +"Last-Translator: Trần Ngọc Quân <vnwildman@gmail.com>\n" +"Language-Team: Vietnamese <translation-team-vi@lists.sourceforge.net>\n" +"Language: vi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: gitk:140 +msgid "Couldn't get list of unmerged files:" +msgstr "Không thể lấy danh sách các tập-tin chưa được hòa trộn:" + +#: gitk:212 gitk:2353 +msgid "Color words" +msgstr "Tô màu chữ" + +#: gitk:217 gitk:2353 gitk:8103 gitk:8136 +msgid "Markup words" +msgstr "Đánh dấu chữ" + +#: gitk:322 +msgid "Error parsing revisions:" +msgstr "Gặp lỗi khi phân tích điểm xét duyệt:" + +#: gitk:378 +msgid "Error executing --argscmd command:" +msgstr "Gặp lỗi khi thực hiện lệnh --argscmd:" + +#: gitk:391 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"Chưa chọn tập tin: --merge đã chỉ định nhưng không có tập tin chưa hòa trộn." + +#: gitk:394 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"Chưa chọn tập tin: --merge đã chỉ định nhưng không có tập tin chưa hòa trộn " +"trong giới hạn tập tin." + +#: gitk:416 gitk:564 +msgid "Error executing git log:" +msgstr "Gặp lỗi khi thực hiện lệnh git log:" + +#: gitk:434 gitk:580 +msgid "Reading" +msgstr "Đang đọc" + +#: gitk:494 gitk:4429 +msgid "Reading commits..." +msgstr "Đang đọc các lần chuyển giao..." + +#: gitk:497 gitk:1635 gitk:4432 +msgid "No commits selected" +msgstr "Chưa chọn các lần chuyển giao" + +#: gitk:1509 +msgid "Can't parse git log output:" +msgstr "Không thể phân tích kết xuất từ lệnh git log:" + +#: gitk:1738 +msgid "No commit information available" +msgstr "Không có thông tin về lần chuyển giao nào" + +#: gitk:1895 +msgid "mc" +msgstr "mc" + +#: gitk:1930 gitk:4222 gitk:9552 gitk:11122 gitk:11401 +msgid "OK" +msgstr "Đồng ý" + +#: gitk:1932 gitk:4224 gitk:9079 gitk:9158 gitk:9274 gitk:9323 gitk:9554 +#: gitk:11123 gitk:11402 +msgid "Cancel" +msgstr "Thôi" + +#: gitk:2067 +msgid "Update" +msgstr "Cập nhật" + +#: gitk:2068 +msgid "Reload" +msgstr "Tải lại" + +#: gitk:2069 +msgid "Reread references" +msgstr "Đọc lại tham chiếu" + +#: gitk:2070 +msgid "List references" +msgstr "Liệt kê các tham chiếu" + +#: gitk:2072 +msgid "Start git gui" +msgstr "Khởi chạy git gui" + +#: gitk:2074 +msgid "Quit" +msgstr "Thoát" + +#: gitk:2066 +msgid "File" +msgstr "Chính" + +#: gitk:2078 +msgid "Preferences" +msgstr "Cá nhân hóa" + +#: gitk:2077 +msgid "Edit" +msgstr "Chỉnh sửa" + +#: gitk:2082 +msgid "New view..." +msgstr "Thêm trình bày mới..." + +#: gitk:2083 +msgid "Edit view..." +msgstr "Sửa cách trình bày..." + +#: gitk:2084 +msgid "Delete view" +msgstr "Xóa cách trình bày" + +#: gitk:2086 +msgid "All files" +msgstr "Mọi tập tin" + +#: gitk:2081 gitk:3975 +msgid "View" +msgstr "Trình bày" + +#: gitk:2091 gitk:2101 gitk:2945 +msgid "About gitk" +msgstr "Giới thiệu về gitk" + +#: gitk:2092 gitk:2106 +msgid "Key bindings" +msgstr "Tổ hợp phím" + +#: gitk:2090 gitk:2105 +msgid "Help" +msgstr "Trợ giúp" + +#: gitk:2183 gitk:8535 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" + +#: gitk:2227 +msgid "Row" +msgstr "Hàng" + +#: gitk:2265 +msgid "Find" +msgstr "Tìm" + +#: gitk:2266 +msgid "next" +msgstr "tiếp" + +#: gitk:2267 +msgid "prev" +msgstr "trước" + +#: gitk:2268 +msgid "commit" +msgstr "lần chuyển giao" + +#: gitk:2271 gitk:2273 gitk:4590 gitk:4613 gitk:4637 gitk:6653 gitk:6725 +#: gitk:6810 +msgid "containing:" +msgstr "có chứa:" + +#: gitk:2274 gitk:3457 gitk:3462 gitk:4666 +msgid "touching paths:" +msgstr "đang chạm đường dẫn:" + +#: gitk:2275 gitk:4680 +msgid "adding/removing string:" +msgstr "thêm/gỡ bỏ chuỗi:" + +#: gitk:2276 gitk:4682 +msgid "changing lines matching:" +msgstr "những dòng thay đổi khớp mẫu:" + +#: gitk:2285 gitk:2287 gitk:4669 +msgid "Exact" +msgstr "Chính xác" + +#: gitk:2287 gitk:4757 gitk:6621 +msgid "IgnCase" +msgstr "BquaHt" + +#: gitk:2287 gitk:4639 gitk:4755 gitk:6617 +msgid "Regexp" +msgstr "BTCQ" + +#: gitk:2289 gitk:2290 gitk:4777 gitk:4807 gitk:4814 gitk:6746 gitk:6814 +msgid "All fields" +msgstr "Mọi trường" + +#: gitk:2290 gitk:4774 gitk:4807 gitk:6684 +msgid "Headline" +msgstr "Nội dung chính" + +#: gitk:2291 gitk:4774 gitk:6684 gitk:6814 gitk:7283 +msgid "Comments" +msgstr "Ghi chú" + +#: gitk:2291 gitk:4774 gitk:4779 gitk:4814 gitk:6684 gitk:7218 gitk:8713 +#: gitk:8728 +msgid "Author" +msgstr "Tác giả" + +#: gitk:2291 gitk:4774 gitk:6684 gitk:7220 +msgid "Committer" +msgstr "Người chuyển giao" + +#: gitk:2322 +msgid "Search" +msgstr "Tìm kiếm" + +#: gitk:2330 +msgid "Diff" +msgstr "So sánh" + +#: gitk:2332 +msgid "Old version" +msgstr "Phiên bản cũ" + +#: gitk:2334 +msgid "New version" +msgstr "Phiên bản mới" + +#: gitk:2336 +msgid "Lines of context" +msgstr "Các dòng của nội dung" + +#: gitk:2346 +msgid "Ignore space change" +msgstr "Không xét đến thay đổi do khoảng trắng" + +#: gitk:2350 gitk:2352 gitk:7842 gitk:8089 +msgid "Line diff" +msgstr "Khác biệt theo dòng" + +#: gitk:2417 +msgid "Patch" +msgstr "Vá" + +#: gitk:2419 +msgid "Tree" +msgstr "Cây" + +#: gitk:2577 gitk:2597 +msgid "Diff this -> selected" +msgstr "So sánh cái này -> cái đã chọn" + +#: gitk:2578 gitk:2598 +msgid "Diff selected -> this" +msgstr "So sánh cái đã chọn -> cái này" + +#: gitk:2579 gitk:2599 +msgid "Make patch" +msgstr "Tạo miếng vá" + +#: gitk:2580 gitk:9137 +msgid "Create tag" +msgstr "Tạo thẻ" + +#: gitk:2581 gitk:9254 +msgid "Write commit to file" +msgstr "Ghi lần chuyển giao ra tập tin" + +#: gitk:2582 gitk:9311 +msgid "Create new branch" +msgstr "Tạo nhánh mới" + +#: gitk:2583 +msgid "Cherry-pick this commit" +msgstr "Cherry-pick lần chuyển giao này" + +#: gitk:2584 +msgid "Reset HEAD branch to here" +msgstr "Đặt lại HEAD của nhánh vào đây" + +#: gitk:2585 +msgid "Mark this commit" +msgstr "Đánh dấu lần chuyển giao này" + +#: gitk:2586 +msgid "Return to mark" +msgstr "Quay lại vị trí dấu" + +#: gitk:2587 +msgid "Find descendant of this and mark" +msgstr "Tìm con cháu của cái này và cái đã đánh dấu" + +#: gitk:2588 +msgid "Compare with marked commit" +msgstr "So sánh với lần chuyển giao đã đánh dấu" + +#: gitk:2589 gitk:2600 +msgid "Diff this -> marked commit" +msgstr "So sánh cái này -> lần chuyển giao đã đánh dấu" + +#: gitk:2590 gitk:2601 +msgid "Diff marked commit -> this" +msgstr "So sánh lần chuyển giao đã đánh dấu -> cái này" + +#: gitk:2591 +msgid "Revert this commit" +msgstr "Hoàn lại lần chuyển giao này" + +#: gitk:2607 +msgid "Check out this branch" +msgstr "Checkout nhánh này" + +#: gitk:2608 +msgid "Remove this branch" +msgstr "Gỡ bỏ nhánh này" + +#: gitk:2615 +msgid "Highlight this too" +msgstr "Cũng tô sáng nó" + +#: gitk:2616 +msgid "Highlight this only" +msgstr "Chỉ tô sáng cái này" + +#: gitk:2617 +msgid "External diff" +msgstr "diff từ bên ngoài" + +#: gitk:2618 +msgid "Blame parent commit" +msgstr "Xem công trạng lần chuyển giao cha mẹ" + +#: gitk:2625 +msgid "Show origin of this line" +msgstr "Hiển thị nguyên gốc của dòng này" + +#: gitk:2626 +msgid "Run git gui blame on this line" +msgstr "Chạy lệnh git gui blame cho dòng này" + +#: gitk:2947 +msgid "" +"\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright © 2005-2011 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "" +"\n" +"Gitk - phần mềm xem các lần chuyển giao dành cho git\n" +"\n" +"Bản quyền © 2005-2011 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" + +#: gitk:2955 gitk:3020 gitk:9738 +msgid "Close" +msgstr "Đóng" + +#: gitk:2976 +msgid "Gitk key bindings" +msgstr "Tổ hợp phím gitk" + +#: gitk:2979 +msgid "Gitk key bindings:" +msgstr "Tổ hợp phím gitk:" + +#: gitk:2981 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\tThoát" + +#: gitk:2982 +#, tcl-format +msgid "<%s-W>\t\tClose window" +msgstr "<%s-W>\t\tĐóng cửa sổ" + +#: gitk:2983 +msgid "<Home>\t\tMove to first commit" +msgstr "<Home>\t\tChuyển đến lần chuyển giao đầu tiên" + +#: gitk:2984 +msgid "<End>\t\tMove to last commit" +msgstr "<End>\t\tChuyển đến lần chuyển giao cuối" + +#: gitk:2985 +msgid "<Up>, p, k\tMove up one commit" +msgstr "<Up>, p, k\tDi chuyển lên một lần chuyển giao" + +#: gitk:2986 +msgid "<Down>, n, j\tMove down one commit" +msgstr "<Down>, n, j\tDi chuyển xuống một lần chuyển giao" + +#: gitk:2987 +msgid "<Left>, z, h\tGo back in history list" +msgstr "<Left>, z, h\tQuay trở lại danh sách lịch sử" + +#: gitk:2988 +msgid "<Right>, x, l\tGo forward in history list" +msgstr "<Right>, x, l\tDi chuyển tiếp trong danh sách lịch sử" + +#: gitk:2989 +msgid "<PageUp>\tMove up one page in commit list" +msgstr "<PageUp>\tDi chuyển lên một trang trong danh sách lần chuyển giao" + +#: gitk:2990 +msgid "<PageDown>\tMove down one page in commit list" +msgstr "<PageDown>\tDi chuyển xuống một trang trong danh sách lần chuyển giao" + +#: gitk:2991 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Home>\tCuộn lên trên cùng của danh sách lần chuyển giao" + +#: gitk:2992 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tCuộn xuống dưới cùng của danh sách lần chuyển giao" + +#: gitk:2993 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Up>\tCuộn danh sách lần chuyển giao lên một dòng" + +#: gitk:2994 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Down>\tCuộn danh sách lần chuyển giao xuống một dòng" + +#: gitk:2995 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tCuộn danh sách lần chuyển giao lên một trang" + +#: gitk:2996 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tCuộn danh sách lần chuyển giao xuống một trang" + +#: gitk:2997 +msgid "<Shift-Up>\tFind backwards (upwards, later commits)" +msgstr "<Shift-Up>\tTìm về phía sau (hướng lên trên, lần chuyển giao sau này)" + +#: gitk:2998 +msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" +msgstr "" +"<Shift-Down>\tTìm về phía trước (hướng xuống dưới, lần chuyển giao trước đây)" + +#: gitk:2999 +msgid "<Delete>, b\tScroll diff view up one page" +msgstr "<Delete>, b\tCuộn phần trình bày diff lên một trang" + +#: gitk:3000 +msgid "<Backspace>\tScroll diff view up one page" +msgstr "<Backspace>\tCuộn phần trình bày diff lên một trang" + +#: gitk:3001 +msgid "<Space>\t\tScroll diff view down one page" +msgstr "<Space>\t\tCuộn phần trình bày diff xuống một trang" + +#: gitk:3002 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tCuộn phần trình bày diff lên 18 dòng" + +#: gitk:3003 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tCuộn phần trình bày diff xuống 18 dòng" + +#: gitk:3004 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\tTìm kiếm" + +#: gitk:3005 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\tDi chuyển đến chỗ gặp kế tiếp" + +#: gitk:3006 +msgid "<Return>\tMove to next find hit" +msgstr "<Return>\t\tDi chuyển đến chỗ gặp kế tiếp" + +#: gitk:3007 +msgid "/\t\tFocus the search box" +msgstr "/\t\tĐưa con trỏ chuột vào ô tìm kiếm" + +#: gitk:3008 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\tDi chuyển đến chỗ gặp kế trước" + +#: gitk:3009 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\tCuộn phần trình bày diff sang tập-tin kế" + +#: gitk:3010 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tTìm đến chỗ khác biệt kế tiếp" + +#: gitk:3011 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tTìm đến chỗ khác biệt kế trước" + +#: gitk:3012 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\tTăng cỡ chữ" + +#: gitk:3013 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-plus>\tTăng cỡ chữ" + +#: gitk:3014 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\tGiảm cỡ chữ" + +#: gitk:3015 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-minus>\tGiảm cỡ chữ" + +#: gitk:3016 +msgid "<F5>\t\tUpdate" +msgstr "<F5>\t\tCập nhật" + +#: gitk:3471 gitk:3480 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Gặp lỗi khi tạo thư mục tạm %s:" + +#: gitk:3493 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Lỗi chào hỏi \"%s\" từ %s:" + +#: gitk:3556 +msgid "command failed:" +msgstr "lệnh gặp lỗi:" + +#: gitk:3705 +msgid "No such commit" +msgstr "Không có lần chuyển giao như vậy" + +#: gitk:3719 +msgid "git gui blame: command failed:" +msgstr "git gui blame: lệnh gặp lỗi:" + +#: gitk:3750 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Không thể độc đầu của hòa trộn: %s" + +# tcl-format +#: gitk:3758 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Gặp lỗi khi đọc chỉ mục: %s" + +#: gitk:3783 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Không thể khởi chạy git blame: %s" + +#: gitk:3786 gitk:6652 +msgid "Searching" +msgstr "Đang tìm kiếm" + +#: gitk:3818 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Gặp lỗi khi chạy git blame: %s" + +#: gitk:3846 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "Dòng đến từ lần chuyển giao %s, cái mà không trong trình bày này" + +#: gitk:3860 +msgid "External diff viewer failed:" +msgstr "Bộ trình bày diff từ bên ngoài gặp lỗi:" + +#: gitk:3978 +msgid "Gitk view definition" +msgstr "Định nghĩa cách trình bày gitk" + +#: gitk:3982 +msgid "Remember this view" +msgstr "Nhớ cách trình bày này" + +#: gitk:3983 +msgid "References (space separated list):" +msgstr "Tham chiếu (danh sách ngăn cách bằng dấu cách):" + +#: gitk:3984 +msgid "Branches & tags:" +msgstr "Nhánh & thẻ:" + +#: gitk:3985 +msgid "All refs" +msgstr "Mọi tham chiếu" + +#: gitk:3986 +msgid "All (local) branches" +msgstr "Mọi nhánh (nội bộ)" + +#: gitk:3987 +msgid "All tags" +msgstr "Mọi thẻ" + +#: gitk:3988 +msgid "All remote-tracking branches" +msgstr "Mọi nhánh remote-tracking" + +#: gitk:3989 +msgid "Commit Info (regular expressions):" +msgstr "Thông tin chuyển giao (biểu thức chính quy):" + +#: gitk:3990 +msgid "Author:" +msgstr "Tác giả:" + +#: gitk:3991 +msgid "Committer:" +msgstr "Người chuyển giao:" + +#: gitk:3992 +msgid "Commit Message:" +msgstr "Chú thích của lần chuyển giao:" + +#: gitk:3993 +msgid "Matches all Commit Info criteria" +msgstr "Khớp mọi điều kiện Thông tin Chuyển giao" + +#: gitk:3994 +msgid "Changes to Files:" +msgstr "Đổi thành Tập tin:" + +#: gitk:3995 +msgid "Fixed String" +msgstr "Chuỗi cố định" + +#: gitk:3996 +msgid "Regular Expression" +msgstr "Biểu thức chính quy" + +#: gitk:3997 +msgid "Search string:" +msgstr "Chuỗi tìm kiếm:" + +#: gitk:3998 +msgid "" +"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" +msgstr "" +"Ngày chuyển giao (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " +"15:27:38\"):" + +#: gitk:3999 +msgid "Since:" +msgstr "Kể từ:" + +#: gitk:4000 +msgid "Until:" +msgstr "Đến:" + +#: gitk:4001 +msgid "Limit and/or skip a number of revisions (positive integer):" +msgstr "Giới hạn và/hoặc bỏ số của điểm xét (số nguyên âm):" + +#: gitk:4002 +msgid "Number to show:" +msgstr "Số lượng hiển thị:" + +#: gitk:4003 +msgid "Number to skip:" +msgstr "Số lượng sẽ bỏ qua:" + +#: gitk:4004 +msgid "Miscellaneous options:" +msgstr "Tuỳ chọn hỗn hợp:" + +#: gitk:4005 +msgid "Strictly sort by date" +msgstr "Sắp xếp chặt chẽ theo ngày" + +#: gitk:4006 +msgid "Mark branch sides" +msgstr "Đánh dấu các cạnh nhánh" + +#: gitk:4007 +msgid "Limit to first parent" +msgstr "Giới hạn thành cha mẹ đầu tiên" + +#: gitk:4008 +msgid "Simple history" +msgstr "Lịch sử dạng đơn giản" + +#: gitk:4009 +msgid "Additional arguments to git log:" +msgstr "Đối số bổ xung cho lệnh git log:" + +#: gitk:4010 +msgid "Enter files and directories to include, one per line:" +msgstr "Nhập vào các tập tin và thư mục bao gồm, mỗi dòng một cái:" + +#: gitk:4011 +msgid "Command to generate more commits to include:" +msgstr "Lệnh tạo ra nhiều lần chuyển giao hơn bao gồm:" + +#: gitk:4135 +msgid "Gitk: edit view" +msgstr "Gitk: sửa cách trình bày" + +#: gitk:4143 +msgid "-- criteria for selecting revisions" +msgstr "-- tiêu chuẩn chọn điểm xét duyệt" + +#: gitk:4148 +msgid "View Name" +msgstr "Tên cách trình bày" + +#: gitk:4223 +msgid "Apply (F5)" +msgstr "Áp dụng (F5)" + +#: gitk:4261 +msgid "Error in commit selection arguments:" +msgstr "Lỗi trong các đối số chọn chuyển giao:" + +#: gitk:4314 gitk:4366 gitk:4827 gitk:4841 gitk:6107 gitk:12184 gitk:12185 +msgid "None" +msgstr "Không" + +#: gitk:4924 gitk:4929 +msgid "Descendant" +msgstr "Con cháu" + +#: gitk:4925 +msgid "Not descendant" +msgstr "Không có con cháu" + +#: gitk:4932 gitk:4937 +msgid "Ancestor" +msgstr "Tổ tiên chung" + +#: gitk:4933 +msgid "Not ancestor" +msgstr "Không có chung tổ tiên" + +#: gitk:5223 +msgid "Local changes checked in to index but not committed" +msgstr "" +"Có thay đổi nội bộ đã được đưa vào bảng mục lục, nhưng chưa được chuyển giao" + +#: gitk:5259 +msgid "Local uncommitted changes, not checked in to index" +msgstr "Có thay đổi nội bộ, nhưng chưa được đưa vào bảng mục lục" + +#: gitk:7032 +msgid "and many more" +msgstr "và nhiều nữa" + +#: gitk:7035 +msgid "many" +msgstr "nhiều" + +#: gitk:7222 +msgid "Tags:" +msgstr "Thẻ:" + +#: gitk:7239 gitk:7245 gitk:8708 +msgid "Parent" +msgstr "Cha" + +#: gitk:7250 +msgid "Child" +msgstr "Con" + +#: gitk:7259 +msgid "Branch" +msgstr "Nhánh" + +#: gitk:7262 +msgid "Follows" +msgstr "Đứng sau" + +#: gitk:7265 +msgid "Precedes" +msgstr "Đứng trước" + +# tcl-format +#: gitk:7849 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Lỗi lấy diff: %s" + +#: gitk:8533 +msgid "Goto:" +msgstr "Nhảy tới:" + +#: gitk:8554 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "Định danh SHA1 dạng ngắn %s là chưa đủ rõ ràng" + +#: gitk:8561 +#, tcl-format +msgid "Revision %s is not known" +msgstr "Không hiểu điểm xét duyệt %s" + +#: gitk:8571 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "Không hiểu định danh SHA1 %s" + +#: gitk:8573 +#, tcl-format +msgid "Revision %s is not in the current view" +msgstr "Điểm %s không ở trong phần hiển thị hiện tại" + +#: gitk:8715 gitk:8730 +msgid "Date" +msgstr "Ngày" + +#: gitk:8718 +msgid "Children" +msgstr "Con cháu" + +#: gitk:8781 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "Đặt lại nhánh %s tại đây" + +#: gitk:8783 +msgid "Detached head: can't reset" +msgstr "Head đã bị tách rời: không thể đặt lại" + +#: gitk:8888 gitk:8894 +msgid "Skipping merge commit " +msgstr "Bỏ qua lần chuyển giao hòa trộn " + +#: gitk:8903 gitk:8908 +msgid "Error getting patch ID for " +msgstr "Gặp lỗi khi lấy ID miếng vá cho " + +#: gitk:8904 gitk:8909 +msgid " - stopping\n" +msgstr " - dừng\n" + +#: gitk:8914 gitk:8917 gitk:8925 gitk:8939 gitk:8948 +msgid "Commit " +msgstr "Commit " + +#: gitk:8918 +msgid "" +" is the same patch as\n" +" " +msgstr "" +" là cùng một miếng vá với\n" +" " + +#: gitk:8926 +msgid "" +" differs from\n" +" " +msgstr "" +" khác biệt từ\n" +" " + +#: gitk:8928 +msgid "" +"Diff of commits:\n" +"\n" +msgstr "" +"Khác biệt của lần chuyển giao (commit):\n" +"\n" + +#: gitk:8940 gitk:8949 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " có %s con - dừng\n" + +#: gitk:8968 +#, tcl-format +msgid "Error writing commit to file: %s" +msgstr "Gặp lỗi trong quá trình ghi lần chuyển giao vào tập tin: %s" + +#: gitk:8974 +#, tcl-format +msgid "Error diffing commits: %s" +msgstr "Gặp lỗi khi so sánh sự khác biệt giữa các lần chuyển giao: %s" + +#: gitk:9020 +msgid "Top" +msgstr "Đỉnh" + +#: gitk:9021 +msgid "From" +msgstr "Từ" + +#: gitk:9026 +msgid "To" +msgstr "Đến" + +#: gitk:9050 +msgid "Generate patch" +msgstr "Tạo miếng vá" + +#: gitk:9052 +msgid "From:" +msgstr "Từ:" + +#: gitk:9061 +msgid "To:" +msgstr "Đến:" + +#: gitk:9070 +msgid "Reverse" +msgstr "Đảo ngược" + +#: gitk:9072 gitk:9268 +msgid "Output file:" +msgstr "Tập tin kết xuất:" + +#: gitk:9078 +msgid "Generate" +msgstr "Tạo" + +#: gitk:9116 +msgid "Error creating patch:" +msgstr "Gặp lỗi khi tạo miếng vá:" + +#: gitk:9139 gitk:9256 gitk:9313 +msgid "ID:" +msgstr "ID:" + +#: gitk:9148 +msgid "Tag name:" +msgstr "Tên thẻ:" + +#: gitk:9151 +msgid "Tag message is optional" +msgstr "Ghi chú thẻ chỉ là tùy chọn" + +#: gitk:9153 +msgid "Tag message:" +msgstr "Ghi chú cho thẻ:" + +#: gitk:9157 gitk:9322 +msgid "Create" +msgstr "Tạo" + +#: gitk:9175 +msgid "No tag name specified" +msgstr "Chưa chỉ ra tên của thẻ" + +#: gitk:9179 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "Thẻ “%s” đã có sẵn rồi" + +#: gitk:9189 +msgid "Error creating tag:" +msgstr "Gặp lỗi khi tạo thẻ:" + +#: gitk:9265 +msgid "Command:" +msgstr "Lệnh:" + +#: gitk:9273 +msgid "Write" +msgstr "Ghi" + +#: gitk:9291 +msgid "Error writing commit:" +msgstr "Gặp lỗi trong quá trình ghi chuyển giao:" + +#: gitk:9318 +msgid "Name:" +msgstr "Tên:" + +#: gitk:9341 +msgid "Please specify a name for the new branch" +msgstr "Vui lòng chỉ định tên cho nhánh mới" + +#: gitk:9346 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "Nhánh “%s” đã có từ trước rồi. Ghi đè?" + +#: gitk:9413 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "" +"Lần chuyển giao %s đã sẵn được bao gồm trong nhánh %s -- bạn có thực sự muốn " +"áp dụng lại nó không?" + +#: gitk:9418 +msgid "Cherry-picking" +msgstr "Đang cherry-pick" + +#: gitk:9427 +#, 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 "" +"Cherry-pick gặp lỗi bởi vì các thay đổi nội bộ tập tin “%s”.\n" +"Xin hãy chuyển giao, reset hay stash các thay đổi của bạn sau đó thử lại." + +#: gitk:9433 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"Cherry-pick gặp lỗi bởi vì xung đột trong hòa trộn.\n" +"Bạn có muốn chạy lệnh “git citool” để giải quyết vấn đề này không?" + +#: gitk:9449 gitk:9507 +msgid "No changes committed" +msgstr "Không có thay đổi nào cần chuyển giao" + +#: gitk:9476 +#, tcl-format +msgid "Commit %s is not included in branch %s -- really revert it?" +msgstr "" +"Lần chuyển giao %s không được bao gồm trong nhánh %s -- bạn có thực sự muốn " +"“revert” nó không?" + +#: gitk:9481 +msgid "Reverting" +msgstr "Đang hoàn tác" + +#: gitk:9489 +#, 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 "" +"Revert gặp lỗi bởi vì tập tin sau đã được thay đổi nội bộ:%s\n" +"Xin hãy chạy lệnh “commit”, “reset” hoặc “stash” rồi thử lại." + +#: gitk:9493 +msgid "" +"Revert failed because of merge conflict.\n" +" Do you wish to run git citool to resolve it?" +msgstr "" +"Revert gặp lỗi bởi vì xung đột hòa trộn.\n" +" Bạn có muốn chạy lệnh “git citool” để phân giải nó không?" + +#: gitk:9536 +msgid "Confirm reset" +msgstr "Xác nhật đặt lại" + +#: gitk:9538 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "Đặt lại nhánh “%s” thành “%s”?" + +#: gitk:9540 +msgid "Reset type:" +msgstr "Kiểu đặt lại:" + +#: gitk:9543 +msgid "Soft: Leave working tree and index untouched" +msgstr "Mềm: Không động đến thư mục làm việc và bảng mục lục" + +#: gitk:9546 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "" +"Pha trộn: Không động chạm đến thư mục làm việc nhưng đặt lại bảng mục lục" + +#: gitk:9549 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"Hard: Đặt lại cây làm việc và mục lục\n" +"(hủy bỏ MỌI thay đổi nội bộ)" + +#: gitk:9566 +msgid "Resetting" +msgstr "Đang đặt lại" + +#: gitk:9626 +msgid "Checking out" +msgstr "Đang checkout" + +#: gitk:9679 +msgid "Cannot delete the currently checked-out branch" +msgstr "Không thể xóa nhánh hiện tại đang được lấy ra" + +#: gitk:9685 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"Các lần chuyển giao trên nhánh %s không ở trên nhánh khác.\n" +"Thực sự muốn xóa nhánh %s?" + +#: gitk:9716 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "Thẻ và Đầu: %s" + +#: gitk:9731 +msgid "Filter" +msgstr "Bộ lọc" + +#: gitk:10027 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"Gặp lỗi khi đọc thông tin hình học lần chuyển giao; thông tin nhánh và thẻ " +"trước/sau sẽ không hoàn thiện." + +#: gitk:11004 +msgid "Tag" +msgstr "Thẻ" + +#: gitk:11008 +msgid "Id" +msgstr "Id" + +#: gitk:11091 +msgid "Gitk font chooser" +msgstr "Hộp thoại chọn phông Gitk" + +#: gitk:11108 +msgid "B" +msgstr "B" + +#: gitk:11111 +msgid "I" +msgstr "I" + +#: gitk:11229 +msgid "Commit list display options" +msgstr "Các tùy chọn về hiển thị danh sách lần chuyển giao" + +#: gitk:11232 +msgid "Maximum graph width (lines)" +msgstr "Độ rộng biểu đồ tối đa (dòng)" + +#: gitk:11235 +#, tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "Độ rộng biểu đồ tối đa (% của bảng)" + +#: gitk:11238 +msgid "Show local changes" +msgstr "Hiển thị các thay đổi nội bộ" + +#: gitk:11241 +msgid "Auto-select SHA1 (length)" +msgstr "Tự chọn SHA1 (độ dài)" + +#: gitk:11245 +msgid "Hide remote refs" +msgstr "Ẩn tham chiếu đến máy chủ" + +#: gitk:11249 +msgid "Diff display options" +msgstr "Các tùy chọn trình bày các khác biệt" + +#: gitk:11251 +msgid "Tab spacing" +msgstr "Khoảng cách tab" + +#: gitk:11254 +msgid "Display nearby tags/heads" +msgstr "Hiển thị các thẻ/đầu xung quanh" + +#: gitk:11257 +msgid "Maximum # tags/heads to show" +msgstr "Số lượng thẻ/đầu tối đa sẽ hiển thị" + +#: gitk:11260 +msgid "Limit diffs to listed paths" +msgstr "Giới hạn các khác biệt cho đường dẫn đã liệt kê" + +#: gitk:11263 +msgid "Support per-file encodings" +msgstr "Hỗ trợ mã hóa mỗi-dòng" + +#: gitk:11269 gitk:11416 +msgid "External diff tool" +msgstr "Công cụ so sánh từ bên ngoài" + +#: gitk:11270 +msgid "Choose..." +msgstr "Chọn..." + +#: gitk:11275 +msgid "General options" +msgstr "Các tùy chọn chung" + +#: gitk:11278 +msgid "Use themed widgets" +msgstr "Dùng các widget chủ đề" + +#: gitk:11280 +msgid "(change requires restart)" +msgstr "(để thay đổi cần khởi động lại)" + +#: gitk:11282 +msgid "(currently unavailable)" +msgstr "(hiện tại không sẵn sàng)" + +#: gitk:11293 +msgid "Colors: press to choose" +msgstr "Màu sắc: bấm vào nút phía dưới để chọn màu" + +#: gitk:11296 +msgid "Interface" +msgstr "Giao diện" + +#: gitk:11297 +msgid "interface" +msgstr "giao diện" + +#: gitk:11300 +msgid "Background" +msgstr "Nền" + +#: gitk:11301 gitk:11331 +msgid "background" +msgstr "nền" + +#: gitk:11304 +msgid "Foreground" +msgstr "Tiền cảnh" + +#: gitk:11305 +msgid "foreground" +msgstr "tiền cảnh" + +#: gitk:11308 +msgid "Diff: old lines" +msgstr "So sánh: dòng cũ" + +#: gitk:11309 +msgid "diff old lines" +msgstr "diff dòng cũ" + +#: gitk:11313 +msgid "Diff: new lines" +msgstr "So sánh: dòng mới" + +#: gitk:11314 +msgid "diff new lines" +msgstr "màu dòng mới" + +#: gitk:11318 +msgid "Diff: hunk header" +msgstr "So sánh: phần đầu của đoạn" + +#: gitk:11320 +msgid "diff hunk header" +msgstr "màu của phần đầu của đoạn khi so sánh" + +#: gitk:11324 +msgid "Marked line bg" +msgstr "Nền dòng đánh dấu" + +#: gitk:11326 +msgid "marked line background" +msgstr "nền dòng được đánh dấu" + +#: gitk:11330 +msgid "Select bg" +msgstr "Màu nền" + +#: gitk:11339 +msgid "Fonts: press to choose" +msgstr "Phông chữ: bấm vào các nút ở dưới để chọn" + +#: gitk:11341 +msgid "Main font" +msgstr "Phông chữ chính" + +#: gitk:11342 +msgid "Diff display font" +msgstr "Phông chữ dùng khi so sánh" + +#: gitk:11343 +msgid "User interface font" +msgstr "Phông chữ giao diện" + +#: gitk:11365 +msgid "Gitk preferences" +msgstr "Cá nhân hóa các cài đặt cho Gitk" + +#: gitk:11374 +msgid "General" +msgstr "Chung" + +#: gitk:11375 +msgid "Colors" +msgstr "Màu sắc" + +#: gitk:11376 +msgid "Fonts" +msgstr "Phông chữ" + +#: gitk:11426 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: chọn màu cho %s" + +#: gitk:12080 +msgid "Cannot find a git repository here." +msgstr "Không thể tìm thấy kho git ở đây." + +#: gitk:12127 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "Đối số “%s” chưa rõ ràng: vừa là điểm xét duyệt vừa là tên tập tin" + +#: gitk:12139 +msgid "Bad arguments to gitk:" +msgstr "Đối số không hợp lệ cho gitk:" + +#: gitk:12242 +msgid "Command line" +msgstr "Dòng lệnh" diff --git a/gpg-interface.c b/gpg-interface.c index 8b0e8743..ff070127 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -7,6 +7,20 @@ static char *configured_signing_key; static const char *gpg_program = "gpg"; +void signature_check_clear(struct signature_check *sigc) +{ + free(sigc->payload); + free(sigc->gpg_output); + free(sigc->gpg_status); + free(sigc->signer); + free(sigc->key); + sigc->payload = NULL; + sigc->gpg_output = NULL; + sigc->gpg_status = NULL; + sigc->signer = NULL; + sigc->key = NULL; +} + void set_signing_key(const char *key) { free(configured_signing_key); diff --git a/gpg-interface.h b/gpg-interface.h index a85cb5bc..37c23daf 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -2,6 +2,7 @@ #define GPG_INTERFACE_H struct signature_check { + char *payload; char *gpg_output; char *gpg_status; char result; /* 0 (not checked), @@ -13,6 +14,7 @@ struct signature_check { char *key; }; +extern void signature_check_clear(struct signature_check *sigc); extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status); extern int git_gpg_config(const char *, const char *, void *); @@ -86,6 +86,11 @@ int grep_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "grep.fullname")) { + opt->relative = !git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "color.grep")) opt->color = git_config_colorbool(var, value); else if (!strcmp(var, "color.grep.context")) @@ -226,3 +226,41 @@ void *hashmap_iter_next(struct hashmap_iter *iter) current = iter->map->table[iter->tablepos++]; } } + +struct pool_entry { + struct hashmap_entry ent; + size_t len; + unsigned char data[FLEX_ARRAY]; +}; + +static int pool_entry_cmp(const struct pool_entry *e1, + const struct pool_entry *e2, + const unsigned char *keydata) +{ + return e1->data != keydata && + (e1->len != e2->len || memcmp(e1->data, keydata, e1->len)); +} + +const void *memintern(const void *data, size_t len) +{ + static struct hashmap map; + struct pool_entry key, *e; + + /* initialize string pool hashmap */ + if (!map.tablesize) + hashmap_init(&map, (hashmap_cmp_fn) pool_entry_cmp, 0); + + /* lookup interned string in pool */ + hashmap_entry_init(&key, memhash(data, len)); + key.len = len; + e = hashmap_get(&map, &key, data); + if (!e) { + /* not found: create it */ + e = xmallocz(sizeof(struct pool_entry) + len); + hashmap_entry_init(e, key.ent.hash); + e->len = len; + memcpy(e->data, data, len); + hashmap_add(&map, e); + } + return e->data; +} @@ -13,6 +13,17 @@ extern unsigned int strihash(const char *buf); extern unsigned int memhash(const void *buf, size_t len); extern unsigned int memihash(const void *buf, size_t len); +static inline unsigned int sha1hash(const unsigned char *sha1) +{ + /* + * Equivalent to 'return *(unsigned int *)sha1;', but safe on + * platforms that don't support unaligned reads. + */ + unsigned int hash; + memcpy(&hash, sha1, sizeof(hash)); + return hash; +} + /* data structures */ struct hashmap_entry { @@ -57,6 +68,14 @@ extern void *hashmap_put(struct hashmap *map, void *entry); extern void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata); +static inline void *hashmap_get_from_hash(const struct hashmap *map, + unsigned int hash, const void *keydata) +{ + struct hashmap_entry key; + hashmap_entry_init(&key, hash); + return hashmap_get(map, &key, keydata); +} + /* hashmap_iter functions */ extern void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter); @@ -68,4 +87,12 @@ static inline void *hashmap_iter_first(struct hashmap *map, return hashmap_iter_next(iter); } +/* string interning */ + +extern const void *memintern(const void *data, size_t len); +static inline const char *strintern(const char *string) +{ + return memintern(string, strlen(string)); +} + #endif @@ -129,7 +129,6 @@ static void list_commands_in_dir(struct cmdnames *cmds, const char *path, const char *prefix) { - int prefix_len; DIR *dir = opendir(path); struct dirent *de; struct strbuf buf = STRBUF_INIT; @@ -139,15 +138,15 @@ static void list_commands_in_dir(struct cmdnames *cmds, return; if (!prefix) prefix = "git-"; - prefix_len = strlen(prefix); strbuf_addf(&buf, "%s/", path); len = buf.len; while ((de = readdir(dir)) != NULL) { - int entlen; + const char *ent; + size_t entlen; - if (!starts_with(de->d_name, prefix)) + if (!skip_prefix(de->d_name, prefix, &ent)) continue; strbuf_setlen(&buf, len); @@ -155,11 +154,10 @@ static void list_commands_in_dir(struct cmdnames *cmds, if (!is_executable(buf.buf)) continue; - entlen = strlen(de->d_name) - prefix_len; - if (has_extension(de->d_name, ".exe")) - entlen -= 4; + entlen = strlen(ent); + strip_suffix(ent, ".exe", &entlen); - add_cmdname(cmds, de->d_name + prefix_len, entlen); + add_cmdname(cmds, ent, entlen); } closedir(dir); strbuf_release(&buf); @@ -251,11 +249,13 @@ static struct cmdnames aliases; static int git_unknown_cmd_config(const char *var, const char *value, void *cb) { + const char *p; + if (!strcmp(var, "help.autocorrect")) autocorrect = git_config_int(var,value); /* Also use aliases for command lookup */ - if (starts_with(var, "alias.")) - add_cmdname(&aliases, var + 6, strlen(var + 6)); + if (skip_prefix(var, "alias.", &p)) + add_cmdname(&aliases, p, strlen(p)); return git_default_config(var, value, cb); } @@ -412,11 +412,12 @@ static int append_similar_ref(const char *refname, const unsigned char *sha1, { struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data); char *branch = strrchr(refname, '/') + 1; + const char *remote; + /* A remote branch of the same name is deemed similar */ - if (starts_with(refname, "refs/remotes/") && + if (skip_prefix(refname, "refs/remotes/", &remote) && !strcmp(branch, cb->base_ref)) - string_list_append(cb->similar_refs, - refname + strlen("refs/remotes/")); + string_list_append(cb->similar_refs, remote); return 0; } diff --git a/http-backend.c b/http-backend.c index f6b7a5ba..80790bba 100644 --- a/http-backend.c +++ b/http-backend.c @@ -221,17 +221,19 @@ static void get_idx_file(char *name) static int http_config(const char *var, const char *value, void *cb) { + const char *p; + if (!strcmp(var, "http.getanyfile")) { getanyfile = git_config_bool(var, value); return 0; } - if (starts_with(var, "http.")) { + if (skip_prefix(var, "http.", &p)) { int i; for (i = 0; i < ARRAY_SIZE(rpc_service); i++) { struct rpc_service *svc = &rpc_service[i]; - if (!strcmp(var + 5, svc->config_name)) { + if (!strcmp(p, svc->config_name)) { svc->enabled = git_config_bool(var, value); return 0; } @@ -244,15 +246,16 @@ static int http_config(const char *var, const char *value, void *cb) static struct rpc_service *select_service(const char *name) { + const char *svc_name; struct rpc_service *svc = NULL; int i; - if (!starts_with(name, "git-")) + if (!skip_prefix(name, "git-", &svc_name)) forbidden("Unsupported service: '%s'", name); for (i = 0; i < ARRAY_SIZE(rpc_service); i++) { struct rpc_service *s = &rpc_service[i]; - if (!strcmp(s->name, name + 4)) { + if (!strcmp(s->name, svc_name)) { svc = s; break; } diff --git a/http-push.c b/http-push.c index bc94a3f6..952f8ede 100644 --- a/http-push.c +++ b/http-push.c @@ -719,14 +719,10 @@ static int fetch_indices(void) return ret; } -static void one_remote_object(const char *hex) +static void one_remote_object(const unsigned char *sha1) { - unsigned char sha1[20]; struct object *obj; - if (get_sha1_hex(hex, sha1) != 0) - return; - obj = lookup_object(sha1); if (!obj) obj = parse_object(sha1); @@ -767,15 +763,13 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) if (tag_closed && ctx->cdata) { if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) { - lock->owner = xmalloc(strlen(ctx->cdata) + 1); - strcpy(lock->owner, ctx->cdata); + lock->owner = xstrdup(ctx->cdata); } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TIMEOUT)) { - if (starts_with(ctx->cdata, "Second-")) - lock->timeout = - strtol(ctx->cdata + 7, NULL, 10); + const char *arg; + if (skip_prefix(ctx->cdata, "Second-", &arg)) + lock->timeout = strtol(arg, NULL, 10); } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) { - lock->token = xmalloc(strlen(ctx->cdata) + 1); - strcpy(lock->token, ctx->cdata); + lock->token = xstrdup(ctx->cdata); git_SHA1_Init(&sha_ctx); git_SHA1_Update(&sha_ctx, lock->token, strlen(lock->token)); @@ -856,8 +850,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) struct xml_ctx ctx; char *escaped; - url = xmalloc(strlen(repo->url) + strlen(path) + 1); - sprintf(url, "%s%s", repo->url, path); + url = xstrfmt("%s%s", repo->url, path); /* Make sure leading directories exist for the remote ref */ ep = strchr(url + strlen(repo->url) + 1, '/'); @@ -1020,26 +1013,38 @@ static void remote_ls(const char *path, int flags, void (*userFunc)(struct remote_ls_ctx *ls), void *userData); +/* extract hex from sharded "xx/x{40}" filename */ +static int get_sha1_hex_from_objpath(const char *path, unsigned char *sha1) +{ + char hex[40]; + + if (strlen(path) != 41) + return -1; + + memcpy(hex, path, 2); + path += 2; + path++; /* skip '/' */ + memcpy(hex, path, 38); + + return get_sha1_hex(hex, sha1); +} + static void process_ls_object(struct remote_ls_ctx *ls) { unsigned int *parent = (unsigned int *)ls->userData; - char *path = ls->dentry_name; - char *obj_hex; + const char *path = ls->dentry_name; + unsigned char sha1[20]; if (!strcmp(ls->path, ls->dentry_name) && (ls->flags & IS_DIR)) { remote_dir_exists[*parent] = 1; return; } - if (strlen(path) != 49) + if (!skip_prefix(path, "objects/", &path) || + get_sha1_hex_from_objpath(path, sha1)) return; - path += 8; - obj_hex = xmalloc(strlen(path)); - /* NB: path is not null-terminated, can not use strlcpy here */ - memcpy(obj_hex, path, 2); - strcpy(obj_hex + 2, path + 3); - one_remote_object(obj_hex); - free(obj_hex); + + one_remote_object(sha1); } static void process_ls_ref(struct remote_ls_ctx *ls) @@ -1117,7 +1122,7 @@ static void remote_ls(const char *path, int flags, void (*userFunc)(struct remote_ls_ctx *ls), void *userData) { - char *url = xmalloc(strlen(repo->url) + strlen(path) + 1); + char *url = xstrfmt("%s%s", repo->url, path); struct active_request_slot *slot; struct slot_results results; struct strbuf in_buffer = STRBUF_INIT; @@ -1133,8 +1138,6 @@ static void remote_ls(const char *path, int flags, ls.userData = userData; ls.userFunc = userFunc; - sprintf(url, "%s%s", repo->url, path); - strbuf_addf(&out_buffer.buf, PROPFIND_ALL_REQUEST); dav_headers = curl_slist_append(dav_headers, "Depth: 1"); @@ -1536,10 +1539,9 @@ static void update_remote_info_refs(struct remote_lock *lock) static int remote_exists(const char *path) { - char *url = xmalloc(strlen(repo->url) + strlen(path) + 1); + char *url = xstrfmt("%s%s", repo->url, path); int ret; - sprintf(url, "%s%s", repo->url, path); switch (http_get_strbuf(url, NULL, NULL)) { case HTTP_OK: @@ -1559,11 +1561,9 @@ static int remote_exists(const char *path) static void fetch_symref(const char *path, char **symref, unsigned char *sha1) { - char *url; + char *url = xstrfmt("%s%s", repo->url, path); struct strbuf buffer = STRBUF_INIT; - - url = xmalloc(strlen(repo->url) + strlen(path) + 1); - sprintf(url, "%s%s", repo->url, path); + const char *name; if (http_get_strbuf(url, &buffer, NULL) != HTTP_OK) die("Couldn't get %s for remote symref\n%s", url, @@ -1578,8 +1578,8 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) return; /* If it's a symref, set the refname; otherwise try for a sha1 */ - if (starts_with((char *)buffer.buf, "ref: ")) { - *symref = xmemdupz((char *)buffer.buf + 5, buffer.len - 6); + if (skip_prefix(buffer.buf, "ref: ", &name)) { + *symref = xmemdupz(name, buffer.len - (name - buffer.buf)); } else { get_sha1_hex(buffer.buf, sha1); } @@ -1673,8 +1673,7 @@ static int delete_remote_branch(const char *pattern, int force) fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name); if (dry_run) return 0; - url = xmalloc(strlen(repo->url) + strlen(remote_ref->name) + 1); - sprintf(url, "%s%s", repo->url, remote_ref->name); + url = xstrfmt("%s%s", repo->url, remote_ref->name); slot = get_active_slot(); slot->results = &results; curl_setup_http_get(slot->curl, url, DAV_DELETE); diff --git a/http-walker.c b/http-walker.c index 1516c5eb..dbddfaa1 100644 --- a/http-walker.c +++ b/http-walker.c @@ -341,8 +341,7 @@ static void fetch_alternates(struct walker *walker, const char *base) if (walker->get_verbosely) fprintf(stderr, "Getting alternates list for %s\n", base); - url = xmalloc(strlen(base) + 31); - sprintf(url, "%s/objects/info/http-alternates", base); + url = xstrfmt("%s/objects/info/http-alternates", base); /* * Use a callback to process the result, since another request @@ -566,8 +565,7 @@ struct walker *get_http_walker(const char *url) struct walker *walker = xmalloc(sizeof(struct walker)); data->alt = xmalloc(sizeof(*data->alt)); - data->alt->base = xmalloc(strlen(url) + 1); - strcpy(data->alt->base, url); + data->alt->base = xstrdup(url); for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s) *s = 0; @@ -906,6 +906,83 @@ static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf) return ret; } +/* + * Check for and extract a content-type parameter. "raw" + * should be positioned at the start of the potential + * parameter, with any whitespace already removed. + * + * "name" is the name of the parameter. The value is appended + * to "out". + */ +static int extract_param(const char *raw, const char *name, + struct strbuf *out) +{ + size_t len = strlen(name); + + if (strncasecmp(raw, name, len)) + return -1; + raw += len; + + if (*raw != '=') + return -1; + raw++; + + while (*raw && !isspace(*raw) && *raw != ';') + strbuf_addch(out, *raw++); + return 0; +} + +/* + * Extract a normalized version of the content type, with any + * spaces suppressed, all letters lowercased, and no trailing ";" + * or parameters. + * + * Note that we will silently remove even invalid whitespace. For + * example, "text / plain" is specifically forbidden by RFC 2616, + * but "text/plain" is the only reasonable output, and this keeps + * our code simple. + * + * If the "charset" argument is not NULL, store the value of any + * charset parameter there. + * + * Example: + * "TEXT/PLAIN; charset=utf-8" -> "text/plain", "utf-8" + * "text / plain" -> "text/plain" + */ +static void extract_content_type(struct strbuf *raw, struct strbuf *type, + struct strbuf *charset) +{ + const char *p; + + strbuf_reset(type); + strbuf_grow(type, raw->len); + for (p = raw->buf; *p; p++) { + if (isspace(*p)) + continue; + if (*p == ';') { + p++; + break; + } + strbuf_addch(type, tolower(*p)); + } + + if (!charset) + return; + + strbuf_reset(charset); + while (*p) { + while (isspace(*p) || *p == ';') + p++; + if (!extract_param(p, "charset", charset)) + return; + while (*p && !isspace(*p)) + p++; + } + + if (!charset->len && starts_with(type->buf, "text/")) + strbuf_addstr(charset, "ISO-8859-1"); +} + /* http_request() targets */ #define HTTP_REQUEST_STRBUF 0 #define HTTP_REQUEST_FILE 1 @@ -957,9 +1034,13 @@ static int http_request(const char *url, ret = run_one_slot(slot, &results); - if (options && options->content_type) - curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE, - options->content_type); + if (options && options->content_type) { + struct strbuf raw = STRBUF_INIT; + curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE, &raw); + extract_content_type(&raw, options->content_type, + options->charset); + strbuf_release(&raw); + } if (options && options->effective_url) curlinfo_strbuf(slot->curl, CURLINFO_EFFECTIVE_URL, @@ -1006,11 +1087,10 @@ static int update_url_from_redirect(struct strbuf *base, if (!strcmp(asked, got->buf)) return 0; - if (!starts_with(asked, base->buf)) + if (!skip_prefix(asked, base->buf, &tail)) die("BUG: update_url_from_redirect: %s is not a superset of %s", asked, base->buf); - tail = asked + base->len; tail_len = strlen(tail); if (got->len < tail_len || @@ -144,6 +144,13 @@ struct http_get_options { struct strbuf *content_type; /* + * If non-NULL, and content_type above is non-NULL, returns + * the charset parameter from the content-type. If none is + * present, returns an empty string. + */ + struct strbuf *charset; + + /* * If non-NULL, returns the URL we ended up at, including any * redirects we followed. */ diff --git a/imap-send.c b/imap-send.c index 45230e19..524fbabc 100644 --- a/imap-send.c +++ b/imap-send.c @@ -23,9 +23,9 @@ */ #include "cache.h" +#include "credential.h" #include "exec_cmd.h" #include "run-command.h" -#include "prompt.h" #ifdef NO_OPENSSL typedef void *SSL; #endif @@ -946,6 +946,7 @@ static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const cha static struct imap_store *imap_open_store(struct imap_server_conf *srvc) { + struct credential cred = CREDENTIAL_INIT; struct imap_store *ctx; struct imap *imap; char *arg, *rsp; @@ -1096,25 +1097,23 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc) } #endif imap_info("Logging in...\n"); - if (!srvc->user) { - fprintf(stderr, "Skipping server %s, no user\n", srvc->host); - goto bail; - } - if (!srvc->pass) { - struct strbuf prompt = STRBUF_INIT; - strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host); - arg = git_getpass(prompt.buf); - strbuf_release(&prompt); - if (!*arg) { - fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host); - goto bail; - } - /* - * getpass() returns a pointer to a static buffer. make a copy - * for long term storage. - */ - srvc->pass = xstrdup(arg); + if (!srvc->user || !srvc->pass) { + cred.protocol = xstrdup(srvc->use_ssl ? "imaps" : "imap"); + cred.host = xstrdup(srvc->host); + + if (srvc->user) + cred.username = xstrdup(srvc->user); + if (srvc->pass) + cred.password = xstrdup(srvc->pass); + + credential_fill(&cred); + + if (!srvc->user) + srvc->user = xstrdup(cred.username); + if (!srvc->pass) + srvc->pass = xstrdup(cred.password); } + if (CAP(NOLOGIN)) { fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host); goto bail; @@ -1153,10 +1152,18 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc) } } /* !preauth */ + if (cred.username) + credential_approve(&cred); + credential_clear(&cred); + ctx->prefix = ""; return ctx; bail: + if (cred.username) + credential_reject(&cred); + credential_clear(&cred); + imap_close_store(ctx); return NULL; } @@ -1321,13 +1328,9 @@ static char *imap_folder; static int git_imap_config(const char *key, const char *val, void *cb) { - char imap_key[] = "imap."; - - if (strncmp(key, imap_key, sizeof imap_key - 1)) + if (!skip_prefix(key, "imap.", &key)) return 0; - key += sizeof imap_key - 1; - /* check booleans first, and barf on others */ if (!strcmp("sslverify", key)) server.ssl_verify = git_config_bool(key, val); @@ -320,19 +320,12 @@ static const double __ac_HASH_UPPER = 0.77; code; \ } } -static inline khint_t __kh_oid_hash(const unsigned char *oid) -{ - khint_t hash; - memcpy(&hash, oid, sizeof(hash)); - return hash; -} - #define __kh_oid_cmp(a, b) (hashcmp(a, b) == 0) -KHASH_INIT(sha1, const unsigned char *, void *, 1, __kh_oid_hash, __kh_oid_cmp) +KHASH_INIT(sha1, const unsigned char *, void *, 1, sha1hash, __kh_oid_cmp) typedef kh_sha1_t khash_sha1; -KHASH_INIT(sha1_pos, const unsigned char *, int, 1, __kh_oid_hash, __kh_oid_cmp) +KHASH_INIT(sha1_pos, const unsigned char *, int, 1, sha1hash, __kh_oid_cmp) typedef kh_sha1_pos_t khash_sha1_pos; #endif /* __AC_KHASH_H */ @@ -766,17 +766,6 @@ void line_log_init(struct rev_info *rev, const char *prefix, struct string_list } } -static int count_parents(struct commit *commit) -{ - struct commit_list *parents = commit->parents; - int count = 0; - while (parents) { - count++; - parents = parents->next; - } - return count; -} - static void move_diff_queue(struct diff_queue_struct *dst, struct diff_queue_struct *src) { @@ -1150,7 +1139,7 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm struct commit **parents; struct commit_list *p; int i; - int nparents = count_parents(commit); + int nparents = commit_list_count(commit->parents); diffqueues = xmalloc(nparents * sizeof(*diffqueues)); cand = xmalloc(nparents * sizeof(*cand)); @@ -1174,9 +1163,7 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm */ add_line_range(rev, parents[i], cand[i]); clear_commit_line_range(rev, commit); - commit->parents = xmalloc(sizeof(struct commit_list)); - commit->parents->item = parents[i]; - commit->parents->next = NULL; + commit_list_append(parents[i], &commit->parents); free(parents); free(cand); free_diffqueues(nparents, diffqueues); @@ -5,7 +5,6 @@ #include "sigchain.h" static struct lock_file *lock_file_list; -static const char *alternate_index_output; static void remove_lock_file(void) { @@ -121,7 +120,7 @@ static char *resolve_symlink(char *p, size_t s) return p; } - +/* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags) { /* @@ -130,8 +129,10 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) */ static const size_t max_path_len = sizeof(lk->filename) - 5; - if (strlen(path) >= max_path_len) + if (strlen(path) >= max_path_len) { + errno = ENAMETOOLONG; return -1; + } strcpy(lk->filename, path); if (!(flags & LOCK_NODEREF)) resolve_symlink(lk->filename, max_path_len); @@ -148,44 +149,51 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) lock_file_list = lk; lk->on_list = 1; } - if (adjust_shared_perm(lk->filename)) - return error("cannot fix permission bits on %s", - lk->filename); + if (adjust_shared_perm(lk->filename)) { + int save_errno = errno; + error("cannot fix permission bits on %s", + lk->filename); + errno = save_errno; + return -1; + } } else lk->filename[0] = 0; return lk->fd; } -static char *unable_to_lock_message(const char *path, int err) +void unable_to_lock_message(const char *path, int err, struct strbuf *buf) { - struct strbuf buf = STRBUF_INIT; - if (err == EEXIST) { - strbuf_addf(&buf, "Unable to create '%s.lock': %s.\n\n" + strbuf_addf(buf, "Unable to create '%s.lock': %s.\n\n" "If no other git process is currently running, this probably means a\n" "git process crashed in this repository earlier. Make sure no other git\n" "process is running and remove the file manually to continue.", absolute_path(path), strerror(err)); } else - strbuf_addf(&buf, "Unable to create '%s.lock': %s", + strbuf_addf(buf, "Unable to create '%s.lock': %s", absolute_path(path), strerror(err)); - return strbuf_detach(&buf, NULL); } int unable_to_lock_error(const char *path, int err) { - char *msg = unable_to_lock_message(path, err); - error("%s", msg); - free(msg); + struct strbuf buf = STRBUF_INIT; + + unable_to_lock_message(path, err, &buf); + error("%s", buf.buf); + strbuf_release(&buf); return -1; } NORETURN void unable_to_lock_index_die(const char *path, int err) { - die("%s", unable_to_lock_message(path, err)); + struct strbuf buf = STRBUF_INIT; + + unable_to_lock_message(path, err, &buf); + die("%s", buf.buf); } +/* This should return a meaningful errno on failure */ int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags) { int fd = lock_file(lk, path, flags); @@ -252,25 +260,6 @@ int hold_locked_index(struct lock_file *lk, int die_on_error) : 0); } -void set_alternate_index_output(const char *name) -{ - alternate_index_output = name; -} - -int commit_locked_index(struct lock_file *lk) -{ - if (alternate_index_output) { - if (lk->fd >= 0 && close_lock_file(lk)) - return -1; - if (rename(lk->filename, alternate_index_output)) - return -1; - lk->filename[0] = 0; - return 0; - } - else - return commit_lock_file(lk); -} - void rollback_lock_file(struct lock_file *lk) { if (lk->filename[0]) { @@ -414,10 +414,11 @@ static int is_common_merge(const struct commit *commit) && !commit->parents->next->next); } -static void show_one_mergetag(struct rev_info *opt, +static void show_one_mergetag(struct commit *commit, struct commit_extra_header *extra, - struct commit *commit) + void *data) { + struct rev_info *opt = (struct rev_info *)data; unsigned char sha1[20]; struct tag *tag; struct strbuf verify_message; @@ -465,15 +466,7 @@ static void show_one_mergetag(struct rev_info *opt, static void show_mergetag(struct rev_info *opt, struct commit *commit) { - struct commit_extra_header *extra, *to_free; - - to_free = read_commit_extra_headers(commit, NULL); - for (extra = to_free; extra; extra = extra->next) { - if (strcmp(extra->key, "mergetag")) - continue; /* not a merge tag */ - show_one_mergetag(opt, extra, commit); - } - free_commit_extra_headers(to_free); + for_each_mergetag(show_one_mergetag, commit, opt); } void show_log(struct rev_info *opt) diff --git a/match-trees.c b/match-trees.c index e80b4af3..1ce0954a 100644 --- a/match-trees.c +++ b/match-trees.c @@ -140,17 +140,12 @@ static void match_trees(const unsigned char *hash1, goto next; score = score_trees(elem, hash2); if (*best_score < score) { - char *newpath; - newpath = xmalloc(strlen(base) + strlen(path) + 1); - sprintf(newpath, "%s%s", base, path); free(*best_match); - *best_match = newpath; + *best_match = xstrfmt("%s%s", base, path); *best_score = score; } if (recurse_limit) { - char *newbase; - newbase = xmalloc(strlen(base) + strlen(path) + 2); - sprintf(newbase, "%s%s/", base, path); + char *newbase = xstrfmt("%s%s/", base, path); match_trees(elem, hash2, best_score, best_match, newbase, recurse_limit - 1); free(newbase); diff --git a/merge-recursive.c b/merge-recursive.c index d38a3b2e..1d332b8b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -171,7 +171,7 @@ static void output(struct merge_options *o, int v, const char *fmt, ...) strbuf_vaddf(&o->obuf, fmt, ap); va_end(ap); - strbuf_add(&o->obuf, "\n", 1); + strbuf_addch(&o->obuf, '\n'); if (!o->buffer_output) flush_output(o); } @@ -267,9 +267,7 @@ struct tree *write_tree_from_memory(struct merge_options *o) active_cache_tree = cache_tree(); if (!cache_tree_fully_valid(active_cache_tree) && - cache_tree_update(active_cache_tree, - (const struct cache_entry * const *)active_cache, - active_nr, 0) < 0) + cache_tree_update(&the_index, 0) < 0) die(_("error building trees")); result = lookup_tree(active_cache_tree->sha1); @@ -603,25 +601,36 @@ static int remove_file(struct merge_options *o, int clean, return 0; } +/* add a string to a strbuf, but converting "/" to "_" */ +static void add_flattened_path(struct strbuf *out, const char *s) +{ + size_t i = out->len; + strbuf_addstr(out, s); + for (; i < out->len; i++) + if (out->buf[i] == '/') + out->buf[i] = '_'; +} + static char *unique_path(struct merge_options *o, const char *path, const char *branch) { - char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); + struct strbuf newpath = STRBUF_INIT; int suffix = 0; struct stat st; - char *p = newpath + strlen(path); - strcpy(newpath, path); - *(p++) = '~'; - strcpy(p, branch); - for (; *p; ++p) - if ('/' == *p) - *p = '_'; - while (string_list_has_string(&o->current_file_set, newpath) || - string_list_has_string(&o->current_directory_set, newpath) || - lstat(newpath, &st) == 0) - sprintf(p, "_%d", suffix++); - - string_list_insert(&o->current_file_set, newpath); - return newpath; + size_t base_len; + + strbuf_addf(&newpath, "%s~", path); + add_flattened_path(&newpath, branch); + + base_len = newpath.len; + while (string_list_has_string(&o->current_file_set, newpath.buf) || + string_list_has_string(&o->current_directory_set, newpath.buf) || + lstat(newpath.buf, &st) == 0) { + strbuf_setlen(&newpath, base_len); + strbuf_addf(&newpath, "_%d", suffix++); + } + + string_list_insert(&o->current_file_set, newpath.buf); + return strbuf_detach(&newpath, NULL); } static int dir_in_way(const char *path, int check_working_copy) @@ -971,14 +980,10 @@ merge_file_special_markers(struct merge_options *o, char *side2 = NULL; struct merge_file_info mfi; - if (filename1) { - side1 = xmalloc(strlen(branch1) + strlen(filename1) + 2); - sprintf(side1, "%s:%s", branch1, filename1); - } - if (filename2) { - side2 = xmalloc(strlen(branch2) + strlen(filename2) + 2); - sprintf(side2, "%s:%s", branch2, filename2); - } + if (filename1) + side1 = xstrfmt("%s:%s", branch1, filename1); + if (filename2) + side2 = xstrfmt("%s:%s", branch2, filename2); mfi = merge_file_1(o, one, a, b, side1 ? side1 : branch1, side2 ? side2 : branch2); @@ -1994,7 +1999,7 @@ int merge_recursive_generic(struct merge_options *o, const unsigned char **base_list, struct commit **result) { - int clean, index_fd; + int clean; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); struct commit *head_commit = get_ref(head, o->branch1); struct commit *next_commit = get_ref(merge, o->branch2); @@ -2011,12 +2016,11 @@ int merge_recursive_generic(struct merge_options *o, } } - index_fd = hold_locked_index(lock, 1); + hold_locked_index(lock, 1); clean = merge_recursive(o, head_commit, next_commit, ca, result); if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) + write_locked_index(&the_index, lock, COMMIT_LOCK)) return error(_("Unable to write index.")); return clean ? 0 : 1; @@ -2055,16 +2059,15 @@ void init_merge_options(struct merge_options *o) if (o->verbosity >= 5) o->buffer_output = 0; strbuf_init(&o->obuf, 0); - memset(&o->current_file_set, 0, sizeof(struct string_list)); - o->current_file_set.strdup_strings = 1; - memset(&o->current_directory_set, 0, sizeof(struct string_list)); - o->current_directory_set.strdup_strings = 1; - memset(&o->df_conflict_file_set, 0, sizeof(struct string_list)); - o->df_conflict_file_set.strdup_strings = 1; + string_list_init(&o->current_file_set, 1); + string_list_init(&o->current_directory_set, 1); + string_list_init(&o->df_conflict_file_set, 1); } int parse_merge_opt(struct merge_options *o, const char *s) { + const char *arg; + if (!s || !*s) return -1; if (!strcmp(s, "ours")) @@ -2073,14 +2076,14 @@ int parse_merge_opt(struct merge_options *o, const char *s) o->recursive_variant = MERGE_RECURSIVE_THEIRS; else if (!strcmp(s, "subtree")) o->subtree_shift = ""; - else if (starts_with(s, "subtree=")) - o->subtree_shift = s + strlen("subtree="); + else if (skip_prefix(s, "subtree=", &arg)) + o->subtree_shift = arg; else if (!strcmp(s, "patience")) o->xdl_opts = DIFF_WITH_ALG(o, PATIENCE_DIFF); else if (!strcmp(s, "histogram")) o->xdl_opts = DIFF_WITH_ALG(o, HISTOGRAM_DIFF); - else if (starts_with(s, "diff-algorithm=")) { - long value = parse_algorithm_value(s + strlen("diff-algorithm=")); + else if (skip_prefix(s, "diff-algorithm=", &arg)) { + long value = parse_algorithm_value(arg); if (value < 0) return -1; /* clear out previous settings */ @@ -2098,9 +2101,8 @@ int parse_merge_opt(struct merge_options *o, const char *s) o->renormalize = 1; else if (!strcmp(s, "no-renormalize")) o->renormalize = 0; - else if (starts_with(s, "rename-threshold=")) { - const char *score = s + strlen("rename-threshold="); - if ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0) + else if (skip_prefix(s, "rename-threshold=", &arg)) { + if ((o->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0) return -1; } else @@ -18,39 +18,23 @@ int try_merge_command(const char *strategy, size_t xopts_nr, const char **xopts, struct commit_list *common, const char *head_arg, struct commit_list *remotes) { - const char **args; - int i = 0, x = 0, ret; + struct argv_array args = ARGV_ARRAY_INIT; + int i, ret; struct commit_list *j; - struct strbuf buf = STRBUF_INIT; - args = xmalloc((4 + xopts_nr + commit_list_count(common) + - commit_list_count(remotes)) * sizeof(char *)); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (x = 0; x < xopts_nr; x++) { - char *s = xmalloc(strlen(xopts[x])+2+1); - strcpy(s, "--"); - strcpy(s+2, xopts[x]); - args[i++] = s; - } - for (j = common; j; j = j->next) - args[i++] = xstrdup(merge_argument(j->item)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remotes; j; j = j->next) - args[i++] = xstrdup(merge_argument(j->item)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (x = 0; x < xopts_nr; x++) - free((void *)args[i++]); + argv_array_pushf(&args, "merge-%s", strategy); + for (i = 0; i < xopts_nr; i++) + argv_array_pushf(&args, "--%s", xopts[i]); for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; + argv_array_push(&args, merge_argument(j->item)); + argv_array_push(&args, "--"); + argv_array_push(&args, head_arg); for (j = remotes; j; j = j->next) - free((void *)args[i++]); - free(args); + argv_array_push(&args, merge_argument(j->item)); + + ret = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + discard_cache(); if (read_cache() < 0) die(_("failed to read the cache")); @@ -66,13 +50,13 @@ int checkout_fast_forward(const unsigned char *head, struct tree *trees[MAX_UNPACK_TREES]; struct unpack_trees_options opts; struct tree_desc t[MAX_UNPACK_TREES]; - int i, fd, nr_trees = 0; + int i, nr_trees = 0; struct dir_struct dir; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); refresh_cache(REFRESH_QUIET); - fd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); memset(&trees, 0, sizeof(trees)); memset(&opts, 0, sizeof(opts)); @@ -105,8 +89,7 @@ int checkout_fast_forward(const unsigned char *head, } if (unpack_trees(nr_trees, t, &opts)) return -1; - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); return 0; } diff --git a/mergetools/gvimdiff3 b/mergetools/gvimdiff3 new file mode 100644 index 00000000..04a5bb0e --- /dev/null +++ b/mergetools/gvimdiff3 @@ -0,0 +1 @@ +. "$MERGE_TOOLS_DIR/vimdiff" diff --git a/mergetools/vimdiff b/mergetools/vimdiff index 39d03277..1ddfbfcd 100644 --- a/mergetools/vimdiff +++ b/mergetools/vimdiff @@ -20,16 +20,26 @@ merge_cmd () { "$merge_tool_path" -f -d -c 'wincmd l' \ "$LOCAL" "$MERGED" "$REMOTE" ;; + gvimdiff3|vimdiff3) + if $base_present + then + "$merge_tool_path" -f -d -c 'hid | hid | hid' \ + "$LOCAL" "$REMOTE" "$BASE" "$MERGED" + else + "$merge_tool_path" -f -d -c 'hid | hid' \ + "$LOCAL" "$REMOTE" "$MERGED" + fi + ;; esac check_unchanged } translate_merge_tool_path() { case "$1" in - gvimdiff|gvimdiff2) + gvimdiff|gvimdiff2|gvimdiff3) echo gvim ;; - vimdiff|vimdiff2) + vimdiff|vimdiff2|vimdiff3) echo vim ;; esac diff --git a/mergetools/vimdiff3 b/mergetools/vimdiff3 new file mode 100644 index 00000000..04a5bb0e --- /dev/null +++ b/mergetools/vimdiff3 @@ -0,0 +1 @@ +. "$MERGE_TOOLS_DIR/vimdiff" diff --git a/name-hash.c b/name-hash.c index 97444d02..702cd051 100644 --- a/name-hash.c +++ b/name-hash.c @@ -179,7 +179,7 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen * Always do exact compare, even if we want a case-ignoring comparison; * we do the quick exact one first, because it will be the common case. */ - if (len == namelen && !cache_name_compare(name, namelen, ce->name, len)) + if (len == namelen && !memcmp(name, ce->name, len)) return 1; if (!icase) @@ -213,12 +213,11 @@ struct cache_entry *index_dir_exists(struct index_state *istate, const char *nam struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int icase) { struct cache_entry *ce; - struct hashmap_entry key; lazy_init_name_hash(istate); - hashmap_entry_init(&key, memihash(name, namelen)); - ce = hashmap_get(&istate->name_hash, &key, NULL); + ce = hashmap_get_from_hash(&istate->name_hash, + memihash(name, namelen), NULL); while (ce) { if (same_name(ce, name, namelen, icase)) return ce; diff --git a/notes-cache.c b/notes-cache.c index 25b20aa2..c4e9bb7f 100644 --- a/notes-cache.c +++ b/notes-cache.c @@ -60,7 +60,7 @@ int notes_cache_write(struct notes_cache *c) commit_sha1, NULL, NULL) < 0) return -1; if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL, - 0, QUIET_ON_ERR) < 0) + 0, UPDATE_REFS_QUIET_ON_ERR) < 0) return -1; return 0; diff --git a/notes-utils.c b/notes-utils.c index f6891f72..b64dc1b0 100644 --- a/notes-utils.c +++ b/notes-utils.c @@ -49,7 +49,8 @@ void commit_notes(struct notes_tree *t, const char *msg) create_notes_commit(t, NULL, buf.buf, buf.len, commit_sha1); strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ - update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); + update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); strbuf_release(&buf); } @@ -50,18 +50,7 @@ int type_from_string(const char *str) */ static unsigned int hash_obj(const unsigned char *sha1, unsigned int n) { - unsigned int hash; - - /* - * Since the sha1 is essentially random, we just take the - * required number of bits directly from the first - * sizeof(unsigned int) bytes of sha1. First we have to copy - * the bytes into a properly aligned integer. If we cared - * about getting consistent results across architectures, we - * would have to call ntohl() here, too. - */ - memcpy(&hash, sha1, sizeof(unsigned int)); - return hash & (n - 1); + return sha1hash(sha1) & (n - 1); } /* diff --git a/pack-objects.c b/pack-objects.c index 4f36c320..9992f3ec 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -7,10 +7,9 @@ static uint32_t locate_object_entry_hash(struct packing_data *pdata, const unsigned char *sha1, int *found) { - uint32_t i, hash, mask = (pdata->index_size - 1); + uint32_t i, mask = (pdata->index_size - 1); - memcpy(&hash, sha1, sizeof(uint32_t)); - i = hash & mask; + i = sha1hash(sha1) & mask; while (pdata->index[i] > 0) { uint32_t pos = pdata->index[i] - 1; @@ -85,7 +85,7 @@ void setup_pager(void) int i = 0; if (!getenv("LESS")) - env[i++] = "LESS=FRSX"; + env[i++] = "LESS=FRX"; if (!getenv("LV")) env[i++] = "LV=-c"; env[i] = NULL; diff --git a/parse-options.c b/parse-options.c index b536896f..e7dafa80 100644 --- a/parse-options.c +++ b/parse-options.c @@ -231,7 +231,8 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg, continue; again: - rest = skip_prefix(arg, long_name); + if (!skip_prefix(arg, long_name, &rest)) + rest = NULL; if (options->type == OPTION_ARGUMENT) { if (!rest) continue; @@ -280,12 +281,13 @@ is_abbreviated: continue; } flags |= OPT_UNSET; - rest = skip_prefix(arg + 3, long_name); - /* abbreviated and negated? */ - if (!rest && starts_with(long_name, arg + 3)) - goto is_abbreviated; - if (!rest) - continue; + if (!skip_prefix(arg + 3, long_name, &rest)) { + /* abbreviated and negated? */ + if (starts_with(long_name, arg + 3)) + goto is_abbreviated; + else + continue; + } } if (*rest) { if (*rest != '=') diff --git a/parse-options.h b/parse-options.h index 31896766..7940bc71 100644 --- a/parse-options.h +++ b/parse-options.h @@ -176,8 +176,8 @@ extern NORETURN void usage_msg_opt(const char *msg, extern int optbug(const struct option *opt, const char *reason); extern int opterror(const struct option *opt, const char *reason, int flags); -#if defined(__GNUC__) && ! defined(__clang__) -#define opterror(o,r,f) (opterror((o),(r),(f)), -1) +#if defined(__GNUC__) +#define opterror(o,r,f) (opterror((o),(r),(f)), const_error()) #endif /*----- incremental advanced APIs -----*/ @@ -275,16 +275,16 @@ char *expand_user_path(const char *path) const char *home = getenv("HOME"); if (!home) goto return_null; - strbuf_add(&user_path, home, strlen(home)); + strbuf_addstr(&user_path, home); } else { struct passwd *pw = getpw_str(username, username_len); if (!pw) goto return_null; - strbuf_add(&user_path, pw->pw_dir, strlen(pw->pw_dir)); + strbuf_addstr(&user_path, pw->pw_dir); } to_copy = first_slash; } - strbuf_add(&user_path, to_copy, strlen(to_copy)); + strbuf_addstr(&user_path, to_copy); return strbuf_detach(&user_path, NULL); return_null: strbuf_release(&user_path); @@ -821,43 +821,3 @@ int daemon_avoid_alias(const char *p) } } } - -int offset_1st_component(const char *path) -{ - if (has_dos_drive_prefix(path)) - return 2 + is_dir_sep(path[2]); - return is_dir_sep(path[0]); -} - -static int only_spaces_and_periods(const char *path, size_t len, size_t skip) -{ - if (len < skip) - return 0; - len -= skip; - path += skip; - while (len-- > 0) { - char c = *(path++); - if (c != ' ' && c != '.') - return 0; - } - return 1; -} - -int is_ntfs_dotgit(const char *name) -{ - int len; - - for (len = 0; ; len++) - if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) { - if (only_spaces_and_periods(name, len, 4) && - !strncasecmp(name, ".git", 4)) - return 1; - if (only_spaces_and_periods(name, len, 5) && - !strncasecmp(name, "git~1", 5)) - return 1; - if (name[len] != '\\') - return 0; - name += len + 1; - len = -1; - } -} @@ -338,7 +338,7 @@ static void NORETURN unsupported_magic(const char *pattern, if (!(magic & m->bit)) continue; if (sb.len) - strbuf_addstr(&sb, " "); + strbuf_addch(&sb, ' '); if (short_magic & m->bit) strbuf_addf(&sb, "'%c'", m->mnemonic); else diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm index a59564fb..09cff135 100644 --- a/perl/Git/SVN.pm +++ b/perl/Git/SVN.pm @@ -1321,7 +1321,7 @@ sub get_untracked { sub parse_svn_date { my $date = shift || return '+0000 1970-01-01 00:00:00'; my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T - (\d\d)\:(\d\d)\:(\d\d)\.\d*Z$/x) or + (\d\d?)\:(\d\d)\:(\d\d)\.\d*Z$/x) or croak "Unable to parse date: $date\n"; my $parsed_date; # Set next. diff --git a/perl/Git/SVN/Log.pm b/perl/Git/SVN/Log.pm index 34f2869a..66410535 100644 --- a/perl/Git/SVN/Log.pm +++ b/perl/Git/SVN/Log.pm @@ -116,7 +116,7 @@ sub run_pager { return; } open STDIN, '<&', $rfd or fatal "Can't redirect stdin: $!"; - $ENV{LESS} ||= 'FRSX'; + $ENV{LESS} ||= 'FRX'; $ENV{LV} ||= '-c'; exec $pager or fatal "Can't run pager: $! ($pager)"; } @@ -3,7 +3,7 @@ char packet_buffer[LARGE_PACKET_MAX]; static const char *packet_trace_prefix = "git"; -static const char trace_key[] = "GIT_TRACE_PACKET"; +static struct trace_key trace_packet = TRACE_KEY_INIT(PACKET); void packet_trace_identity(const char *prog) { @@ -15,7 +15,7 @@ static void packet_trace(const char *buf, unsigned int len, int write) int i; struct strbuf out; - if (!trace_want(trace_key)) + if (!trace_want(&trace_packet)) return; /* +32 is just a guess for header + quoting */ @@ -27,7 +27,7 @@ static void packet_trace(const char *buf, unsigned int len, int write) if ((len >= 4 && starts_with(buf, "PACK")) || (len >= 5 && starts_with(buf+1, "PACK"))) { strbuf_addstr(&out, "PACK ..."); - unsetenv(trace_key); + trace_disable(&trace_packet); } else { /* XXX we should really handle printable utf8 */ @@ -43,7 +43,7 @@ static void packet_trace(const char *buf, unsigned int len, int write) } strbuf_addch(&out, '\n'); - trace_strbuf(trace_key, &out); + trace_strbuf(&trace_packet, &out); strbuf_release(&out); } diff --git a/preload-index.c b/preload-index.c index 968ee25e..c1fe3a3e 100644 --- a/preload-index.c +++ b/preload-index.c @@ -37,9 +37,8 @@ static void *preload_thread(void *_data) struct thread_data *p = _data; struct index_state *index = p->index; struct cache_entry **cep = index->cache + p->offset; - struct cache_def cache; + struct cache_def cache = CACHE_DEF_INIT; - memset(&cache, 0, sizeof(cache)); nr = p->nr; if (nr + p->offset > index->cache_nr) nr = index->cache_nr - p->offset; @@ -64,6 +63,7 @@ static void *preload_thread(void *_data) continue; ce_mark_uptodate(ce); } while (--nr > 0); + cache_def_clear(&cache); return NULL; } @@ -40,10 +40,9 @@ static int git_pretty_formats_config(const char *var, const char *value, void *c const char *fmt; int i; - if (!starts_with(var, "pretty.")) + if (!skip_prefix(var, "pretty.", &name)) return 0; - name = var + strlen("pretty."); for (i = 0; i < builtin_formats_len; i++) { if (!strcmp(commit_formats[i].name, name)) return 0; @@ -393,8 +392,8 @@ static void add_rfc2047(struct strbuf *sb, const char *line, size_t len, strbuf_addstr(sb, "?="); } -static const char *show_ident_date(const struct ident_split *ident, - enum date_mode mode) +const char *show_ident_date(const struct ident_split *ident, + enum date_mode mode) { unsigned long date = 0; long tz = 0; @@ -1377,7 +1376,7 @@ static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */ case trunc_none: break; } - strbuf_addstr(sb, local_sb.buf); + strbuf_addbuf(sb, &local_sb); } else { int sb_len = sb->len, offset = 0; if (c->flush_type == flush_left) @@ -1521,8 +1520,6 @@ void format_commit_message(const struct commit *commit, free(context.commit_encoding); unuse_commit_buffer(commit, context.message); - free(context.signature_check.gpg_output); - free(context.signature_check.signer); } static void pp_header(struct pretty_print_context *pp, @@ -1557,12 +1554,7 @@ static void pp_header(struct pretty_print_context *pp, } if (!parents_shown) { - struct commit_list *parent; - int num; - for (parent = commit->parents, num = 0; - parent; - parent = parent->next, num++) - ; + unsigned num = commit_list_count(commit->parents); /* with enough slop */ strbuf_grow(sb, num * 50 + 20); add_merge_info(pp, sb, commit); diff --git a/prio-queue.c b/prio-queue.c index c9f8c6d2..e4365b00 100644 --- a/prio-queue.c +++ b/prio-queue.c @@ -1,18 +1,30 @@ #include "cache.h" -#include "commit.h" #include "prio-queue.h" +static inline int compare(struct prio_queue *queue, int i, int j) +{ + int cmp = queue->compare(queue->array[i].data, queue->array[j].data, + queue->cb_data); + if (!cmp) + cmp = queue->array[i].ctr - queue->array[j].ctr; + return cmp; +} + +static inline void swap(struct prio_queue *queue, int i, int j) +{ + struct prio_queue_entry tmp = queue->array[i]; + queue->array[i] = queue->array[j]; + queue->array[j] = tmp; +} + void prio_queue_reverse(struct prio_queue *queue) { int i, j; if (queue->compare != NULL) die("BUG: prio_queue_reverse() on non-LIFO queue"); - for (i = 0; i <= (j = (queue->nr - 1) - i); i++) { - struct commit *swap = queue->array[i]; - queue->array[i] = queue->array[j]; - queue->array[j] = swap; - } + for (i = 0; i <= (j = (queue->nr - 1) - i); i++) + swap(queue, i, j); } void clear_prio_queue(struct prio_queue *queue) @@ -21,44 +33,42 @@ void clear_prio_queue(struct prio_queue *queue) queue->nr = 0; queue->alloc = 0; queue->array = NULL; + queue->insertion_ctr = 0; } void prio_queue_put(struct prio_queue *queue, void *thing) { - prio_queue_compare_fn compare = queue->compare; int ix, parent; /* Append at the end */ ALLOC_GROW(queue->array, queue->nr + 1, queue->alloc); - queue->array[queue->nr++] = thing; - if (!compare) + queue->array[queue->nr].ctr = queue->insertion_ctr++; + queue->array[queue->nr].data = thing; + queue->nr++; + if (!queue->compare) return; /* LIFO */ /* Bubble up the new one */ for (ix = queue->nr - 1; ix; ix = parent) { parent = (ix - 1) / 2; - if (compare(queue->array[parent], queue->array[ix], - queue->cb_data) <= 0) + if (compare(queue, parent, ix) <= 0) break; - thing = queue->array[parent]; - queue->array[parent] = queue->array[ix]; - queue->array[ix] = thing; + swap(queue, parent, ix); } } void *prio_queue_get(struct prio_queue *queue) { - void *result, *swap; + void *result; int ix, child; - prio_queue_compare_fn compare = queue->compare; if (!queue->nr) return NULL; - if (!compare) - return queue->array[--queue->nr]; /* LIFO */ + if (!queue->compare) + return queue->array[--queue->nr].data; /* LIFO */ - result = queue->array[0]; + result = queue->array[0].data; if (!--queue->nr) return result; @@ -67,18 +77,14 @@ void *prio_queue_get(struct prio_queue *queue) /* Push down the one at the root */ for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) { child = ix * 2 + 1; /* left */ - if ((child + 1 < queue->nr) && - (compare(queue->array[child], queue->array[child + 1], - queue->cb_data) >= 0)) + if (child + 1 < queue->nr && + compare(queue, child, child + 1) >= 0) child++; /* use right child */ - if (compare(queue->array[ix], queue->array[child], - queue->cb_data) <= 0) + if (compare(queue, ix, child) <= 0) break; - swap = queue->array[child]; - queue->array[child] = queue->array[ix]; - queue->array[ix] = swap; + swap(queue, child, ix); } return result; } diff --git a/prio-queue.h b/prio-queue.h index 9c3cd1f8..d030ec9d 100644 --- a/prio-queue.h +++ b/prio-queue.h @@ -21,11 +21,17 @@ */ typedef int (*prio_queue_compare_fn)(const void *one, const void *two, void *cb_data); +struct prio_queue_entry { + unsigned ctr; + void *data; +}; + struct prio_queue { prio_queue_compare_fn compare; + unsigned insertion_ctr; void *cb_data; int alloc, nr; - void **array; + struct prio_queue_entry *array; }; /* @@ -12,13 +12,14 @@ #include "gettext.h" #include "progress.h" #include "strbuf.h" +#include "trace.h" #define TP_IDX_MAX 8 struct throughput { off_t curr_total; off_t prev_total; - struct timeval prev_tv; + uint64_t prev_ns; unsigned int avg_bytes; unsigned int avg_misecs; unsigned int last_bytes[TP_IDX_MAX]; @@ -127,65 +128,65 @@ static void throughput_string(struct strbuf *buf, off_t total, void display_throughput(struct progress *progress, off_t total) { struct throughput *tp; - struct timeval tv; - unsigned int misecs; + uint64_t now_ns; + unsigned int misecs, count, rate; + struct strbuf buf = STRBUF_INIT; if (!progress) return; tp = progress->throughput; - gettimeofday(&tv, NULL); + now_ns = getnanotime(); if (!tp) { progress->throughput = tp = calloc(1, sizeof(*tp)); if (tp) { tp->prev_total = tp->curr_total = total; - tp->prev_tv = tv; + tp->prev_ns = now_ns; } return; } tp->curr_total = total; + /* only update throughput every 0.5 s */ + if (now_ns - tp->prev_ns <= 500000000) + return; + /* - * We have x = bytes and y = microsecs. We want z = KiB/s: + * We have x = bytes and y = nanosecs. We want z = KiB/s: * - * z = (x / 1024) / (y / 1000000) - * z = x / y * 1000000 / 1024 - * z = x / (y * 1024 / 1000000) + * z = (x / 1024) / (y / 1000000000) + * z = x / y * 1000000000 / 1024 + * z = x / (y * 1024 / 1000000000) * z = x / y' * * To simplify things we'll keep track of misecs, or 1024th of a sec * obtained with: * - * y' = y * 1024 / 1000000 - * y' = y / (1000000 / 1024) - * y' = y / 977 + * y' = y * 1024 / 1000000000 + * y' = y * (2^10 / 2^42) * (2^42 / 1000000000) + * y' = y / 2^32 * 4398 + * y' = (y * 4398) >> 32 */ - misecs = (tv.tv_sec - tp->prev_tv.tv_sec) * 1024; - misecs += (int)(tv.tv_usec - tp->prev_tv.tv_usec) / 977; + misecs = ((now_ns - tp->prev_ns) * 4398) >> 32; - if (misecs > 512) { - struct strbuf buf = STRBUF_INIT; - unsigned int count, rate; + count = total - tp->prev_total; + tp->prev_total = total; + tp->prev_ns = now_ns; + tp->avg_bytes += count; + tp->avg_misecs += misecs; + rate = tp->avg_bytes / tp->avg_misecs; + tp->avg_bytes -= tp->last_bytes[tp->idx]; + tp->avg_misecs -= tp->last_misecs[tp->idx]; + tp->last_bytes[tp->idx] = count; + tp->last_misecs[tp->idx] = misecs; + tp->idx = (tp->idx + 1) % TP_IDX_MAX; - count = total - tp->prev_total; - tp->prev_total = total; - tp->prev_tv = tv; - tp->avg_bytes += count; - tp->avg_misecs += misecs; - rate = tp->avg_bytes / tp->avg_misecs; - tp->avg_bytes -= tp->last_bytes[tp->idx]; - tp->avg_misecs -= tp->last_misecs[tp->idx]; - tp->last_bytes[tp->idx] = count; - tp->last_misecs[tp->idx] = misecs; - tp->idx = (tp->idx + 1) % TP_IDX_MAX; - - throughput_string(&buf, total, rate); - strncpy(tp->display, buf.buf, sizeof(tp->display)); - strbuf_release(&buf); - if (progress->last_value != -1 && progress_update) - display(progress, progress->last_value, NULL); - } + throughput_string(&buf, total, rate); + strncpy(tp->display, buf.buf, sizeof(tp->display)); + strbuf_release(&buf); + if (progress->last_value != -1 && progress_update) + display(progress, progress->last_value, NULL); } int display_progress(struct progress *progress, unsigned n) diff --git a/read-cache.c b/read-cache.c index 36a6f73f..5d3c8bd4 100644 --- a/read-cache.c +++ b/read-cache.c @@ -14,7 +14,8 @@ #include "resolve-undo.h" #include "strbuf.h" #include "varint.h" -#include "utf8.h" +#include "split-index.h" +#include "sigchain.h" static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, unsigned int options); @@ -35,8 +36,15 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) #define CACHE_EXT_TREE 0x54524545 /* "TREE" */ #define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */ +#define CACHE_EXT_LINK 0x6c696e6b /* "link" */ + +/* changes that can be kept in $GIT_DIR/index (basically all extensions) */ +#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \ + CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \ + SPLIT_INDEX_ORDERED) struct index_state the_index; +static const char *alternate_index_output; static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce) { @@ -48,10 +56,12 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache { struct cache_entry *old = istate->cache[nr]; + replace_index_entry_in_base(istate, old, ce); remove_name_hash(istate, old); free(old); set_index_entry(istate, nr, ce); - istate->cache_changed = 1; + ce->ce_flags |= CE_UPDATE_IN_BASE; + istate->cache_changed |= CE_ENTRY_CHANGED; } void rename_index_entry_at(struct index_state *istate, int nr, const char *new_name) @@ -63,9 +73,10 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n copy_cache_entry(new, old); new->ce_flags &= ~CE_HASHED; new->ce_namelen = namelen; + new->index = 0; memcpy(new->name, new_name, namelen + 1); - cache_tree_invalidate_path(istate->cache_tree, old->name); + cache_tree_invalidate_path(istate, old->name); remove_index_entry_at(istate, nr); add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); } @@ -423,18 +434,26 @@ int df_name_compare(const char *name1, int len1, int mode1, return c1 - c2; } -int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2) +int name_compare(const char *name1, size_t len1, const char *name2, size_t len2) { - int len = len1 < len2 ? len1 : len2; - int cmp; - - cmp = memcmp(name1, name2, len); + size_t min_len = (len1 < len2) ? len1 : len2; + int cmp = memcmp(name1, name2, min_len); if (cmp) return cmp; if (len1 < len2) return -1; if (len1 > len2) return 1; + return 0; +} + +int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2) +{ + int cmp; + + cmp = name_compare(name1, len1, name2, len2); + if (cmp) + return cmp; if (stage1 < stage2) return -1; @@ -443,11 +462,6 @@ int cache_name_stage_compare(const char *name1, int len1, int stage1, const char return 0; } -int cache_name_compare(const char *name1, int len1, const char *name2, int len2) -{ - return cache_name_stage_compare(name1, len1, 0, name2, len2, 0); -} - static int index_name_stage_pos(const struct index_state *istate, const char *name, int namelen, int stage) { int first, last; @@ -481,8 +495,8 @@ int remove_index_entry_at(struct index_state *istate, int pos) record_resolve_undo(istate, ce); remove_name_hash(istate, ce); - free(ce); - istate->cache_changed = 1; + save_or_free_index_entry(istate, ce); + istate->cache_changed |= CE_ENTRY_REMOVED; istate->cache_nr--; if (pos >= istate->cache_nr) return 0; @@ -505,12 +519,14 @@ void remove_marked_cache_entries(struct index_state *istate) for (i = j = 0; i < istate->cache_nr; i++) { if (ce_array[i]->ce_flags & CE_REMOVE) { remove_name_hash(istate, ce_array[i]); - free(ce_array[i]); + save_or_free_index_entry(istate, ce_array[i]); } else ce_array[j++] = ce_array[i]; } - istate->cache_changed = 1; + if (j == istate->cache_nr) + return; + istate->cache_changed |= CE_ENTRY_REMOVED; istate->cache_nr = j; } @@ -519,7 +535,7 @@ int remove_file_from_index(struct index_state *istate, const char *path) int pos = index_name_pos(istate, path, strlen(path)); if (pos < 0) pos = -pos-1; - cache_tree_invalidate_path(istate->cache_tree, path); + cache_tree_invalidate_path(istate, path); while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path)) remove_index_entry_at(istate, pos); return 0; @@ -568,7 +584,9 @@ static int different_name(struct cache_entry *ce, struct cache_entry *alias) * So we use the CE_ADDED flag to verify that the alias was an old * one before we accept it as */ -static struct cache_entry *create_alias_ce(struct cache_entry *ce, struct cache_entry *alias) +static struct cache_entry *create_alias_ce(struct index_state *istate, + struct cache_entry *ce, + struct cache_entry *alias) { int len; struct cache_entry *new; @@ -581,7 +599,7 @@ static struct cache_entry *create_alias_ce(struct cache_entry *ce, struct cache_ new = xcalloc(1, cache_entry_size(len)); memcpy(new->name, alias->name, len); copy_cache_entry(new, ce); - free(ce); + save_or_free_index_entry(istate, ce); return new; } @@ -674,7 +692,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, set_object_name_for_intent_to_add_entry(ce); if (ignore_case && alias && different_name(ce, alias)) - ce = create_alias_ce(ce, alias); + ce = create_alias_ce(istate, ce, alias); ce->ce_flags |= CE_ADDED; /* It was suspected to be racily clean, but it turns out to be Ok */ @@ -757,10 +775,9 @@ static int verify_dotfile(const char *rest) * shares the path end test with the ".." case. */ case 'g': - case 'G': - if (rest[1] != 'i' && rest[1] != 'I') + if (rest[1] != 'i') break; - if (rest[2] != 't' && rest[2] != 'T') + if (rest[2] != 't') break; rest += 2; /* fallthrough */ @@ -784,10 +801,6 @@ int verify_path(const char *path) return 1; if (is_dir_sep(c)) { inside: - if (protect_hfs && is_hfs_dotgit(path)) - return 0; - if (protect_ntfs && is_ntfs_dotgit(path)) - return 0; c = *path++; if ((c == '.' && !verify_dotfile(path)) || is_dir_sep(c) || c == '\0') @@ -942,7 +955,8 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; int new_only = option & ADD_CACHE_NEW_ONLY; - cache_tree_invalidate_path(istate->cache_tree, ce->name); + if (!(option & ADD_CACHE_KEEP_CACHE_TREE)) + cache_tree_invalidate_path(istate, ce->name); pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce)); /* existing match? Just replace it. */ @@ -1005,7 +1019,7 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti istate->cache + pos, (istate->cache_nr - pos - 1) * sizeof(ce)); set_index_entry(istate, pos, ce); - istate->cache_changed = 1; + istate->cache_changed |= CE_ENTRY_ADDED; return 0; } @@ -1104,6 +1118,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, !(ce->ce_flags & CE_VALID)) updated->ce_flags &= ~CE_VALID; + /* istate->cache_changed is updated in the caller */ return updated; } @@ -1185,7 +1200,8 @@ int refresh_index(struct index_state *istate, unsigned int flags, * means the index is not valid anymore. */ ce->ce_flags &= ~CE_VALID; - istate->cache_changed = 1; + ce->ce_flags |= CE_UPDATE_IN_BASE; + istate->cache_changed |= CE_ENTRY_CHANGED; } if (quiet) continue; @@ -1337,6 +1353,10 @@ static int read_index_extension(struct index_state *istate, case CACHE_EXT_RESOLVE_UNDO: istate->resolve_undo = resolve_undo_read(data, sz); break; + case CACHE_EXT_LINK: + if (read_link_extension(istate, data, sz)) + return -1; + break; default: if (*ext < 'A' || 'Z' < *ext) return error("index uses %.4s extension, which we do not understand", @@ -1371,6 +1391,7 @@ static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *on ce->ce_stat_data.sd_size = get_be32(&ondisk->size); ce->ce_flags = flags & ~CE_NAMEMASK; ce->ce_namelen = len; + ce->index = 0; hashcpy(ce->sha1, ondisk->sha1); memcpy(ce->name, name, len); ce->name[len] = '\0'; @@ -1445,7 +1466,7 @@ static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk, } /* remember to discard_cache() before reading a different cache! */ -int read_index_from(struct index_state *istate, const char *path) +int do_read_index(struct index_state *istate, const char *path, int must_exist) { int fd, i; struct stat st; @@ -1462,9 +1483,9 @@ int read_index_from(struct index_state *istate, const char *path) istate->timestamp.nsec = 0; fd = open(path, O_RDONLY); if (fd < 0) { - if (errno == ENOENT) + if (!must_exist && errno == ENOENT) return 0; - die_errno("index file open failed"); + die_errno("%s: index file open failed", path); } if (fstat(fd, &st)) @@ -1483,7 +1504,7 @@ int read_index_from(struct index_state *istate, const char *path) if (verify_hdr(hdr, mmap_size) < 0) goto unmap; - hashcpy(istate->sha1, (unsigned char *)hdr + mmap_size - 20); + hashcpy(istate->sha1, (const unsigned char *)hdr + mmap_size - 20); istate->version = ntohl(hdr->hdr_version); istate->cache_nr = ntohl(hdr->hdr_entries); istate->cache_alloc = alloc_nr(istate->cache_nr); @@ -1537,6 +1558,40 @@ unmap: die("index file corrupt"); } +int read_index_from(struct index_state *istate, const char *path) +{ + struct split_index *split_index; + int ret; + + /* istate->initialized covers both .git/index and .git/sharedindex.xxx */ + if (istate->initialized) + return istate->cache_nr; + + ret = do_read_index(istate, path, 0); + split_index = istate->split_index; + if (!split_index) + return ret; + + if (is_null_sha1(split_index->base_sha1)) + return ret; + + if (split_index->base) + discard_index(split_index->base); + else + split_index->base = xcalloc(1, sizeof(*split_index->base)); + ret = do_read_index(split_index->base, + git_path("sharedindex.%s", + sha1_to_hex(split_index->base_sha1)), 1); + if (hashcmp(split_index->base_sha1, split_index->base->sha1)) + die("broken index, expect %s in %s, got %s", + sha1_to_hex(split_index->base_sha1), + git_path("sharedindex.%s", + sha1_to_hex(split_index->base_sha1)), + sha1_to_hex(split_index->base->sha1)); + merge_base_index(istate); + return ret; +} + int is_index_unborn(struct index_state *istate) { return (!istate->cache_nr && !istate->timestamp.sec); @@ -1546,8 +1601,15 @@ int discard_index(struct index_state *istate) { int i; - for (i = 0; i < istate->cache_nr; i++) + for (i = 0; i < istate->cache_nr; i++) { + if (istate->cache[i]->index && + istate->split_index && + istate->split_index->base && + istate->cache[i]->index <= istate->split_index->base->cache_nr && + istate->cache[i] == istate->split_index->base->cache[istate->cache[i]->index - 1]) + continue; free(istate->cache[i]); + } resolve_undo_clear_index(istate); istate->cache_nr = 0; istate->cache_changed = 0; @@ -1559,6 +1621,7 @@ int discard_index(struct index_state *istate) free(istate->cache); istate->cache = NULL; istate->cache_alloc = 0; + discard_split_index(istate); return 0; } @@ -1619,7 +1682,7 @@ static int write_index_ext_header(git_SHA_CTX *context, int fd, (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0; } -static int ce_flush(git_SHA_CTX *context, int fd) +static int ce_flush(git_SHA_CTX *context, int fd, unsigned char *sha1) { unsigned int left = write_buffer_len; @@ -1637,6 +1700,7 @@ static int ce_flush(git_SHA_CTX *context, int fd) /* Append the SHA1 signature at the end */ git_SHA1_Final(write_buffer + left, context); + hashcpy(sha1, write_buffer + left); left += 20; return (write_in_full(fd, write_buffer, left) != left) ? -1 : 0; } @@ -1708,7 +1772,7 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk, ondisk->size = htonl(ce->ce_stat_data.sd_size); hashcpy(ondisk->sha1, ce->sha1); - flags = ce->ce_flags; + flags = ce->ce_flags & ~CE_NAMEMASK; flags |= (ce_namelen(ce) >= CE_NAMEMASK ? CE_NAMEMASK : ce_namelen(ce)); ondisk->flags = htons(flags); if (ce->ce_flags & CE_EXTENDED) { @@ -1727,9 +1791,15 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce, { int size; struct ondisk_cache_entry *ondisk; + int saved_namelen = saved_namelen; /* compiler workaround */ char *name; int result; + if (ce->ce_flags & CE_STRIP_NAME) { + saved_namelen = ce_namelen(ce); + ce->ce_namelen = 0; + } + if (!previous_name) { size = ondisk_ce_size(ce); ondisk = xcalloc(1, size); @@ -1761,6 +1831,10 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce, strbuf_splice(previous_name, common, to_remove, ce->name + common, ce_namelen(ce) - common); } + if (ce->ce_flags & CE_STRIP_NAME) { + ce->ce_namelen = saved_namelen; + ce->ce_flags &= ~CE_STRIP_NAME; + } result = ce_write(c, fd, ondisk, size); free(ondisk); @@ -1830,13 +1904,13 @@ static int has_racy_timestamp(struct index_state *istate) void update_index_if_able(struct index_state *istate, struct lock_file *lockfile) { if ((istate->cache_changed || has_racy_timestamp(istate)) && - verify_index(istate) && !write_index(istate, lockfile->fd)) - commit_locked_index(lockfile); - else + verify_index(istate) && + write_locked_index(istate, lockfile, COMMIT_LOCK)) rollback_lock_file(lockfile); } -int write_index(struct index_state *istate, int newfd) +static int do_write_index(struct index_state *istate, int newfd, + int strip_extensions) { git_SHA_CTX c; struct cache_header hdr; @@ -1858,8 +1932,11 @@ int write_index(struct index_state *istate, int newfd) } } - if (!istate->version) + if (!istate->version) { istate->version = get_index_format_default(); + if (getenv("GIT_TEST_SPLIT_INDEX")) + init_split_index(istate); + } /* demote version 3 to version 2 when the latter suffices */ if (istate->version == 3 || istate->version == 2) @@ -1899,7 +1976,18 @@ int write_index(struct index_state *istate, int newfd) strbuf_release(&previous_name_buf); /* Write extension data here */ - if (istate->cache_tree) { + if (!strip_extensions && istate->split_index) { + struct strbuf sb = STRBUF_INIT; + + err = write_link_extension(&sb, istate) < 0 || + write_index_ext_header(&c, newfd, CACHE_EXT_LINK, + sb.len) < 0 || + ce_write(&c, newfd, sb.buf, sb.len) < 0; + strbuf_release(&sb); + if (err) + return -1; + } + if (!strip_extensions && istate->cache_tree) { struct strbuf sb = STRBUF_INIT; cache_tree_write(&sb, istate->cache_tree); @@ -1909,7 +1997,7 @@ int write_index(struct index_state *istate, int newfd) if (err) return -1; } - if (istate->resolve_undo) { + if (!strip_extensions && istate->resolve_undo) { struct strbuf sb = STRBUF_INIT; resolve_undo_write(&sb, istate->resolve_undo); @@ -1921,13 +2009,138 @@ int write_index(struct index_state *istate, int newfd) return -1; } - if (ce_flush(&c, newfd) || fstat(newfd, &st)) + if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st)) return -1; istate->timestamp.sec = (unsigned int)st.st_mtime; istate->timestamp.nsec = ST_MTIME_NSEC(st); return 0; } +void set_alternate_index_output(const char *name) +{ + alternate_index_output = name; +} + +static int commit_locked_index(struct lock_file *lk) +{ + if (alternate_index_output) { + if (lk->fd >= 0 && close_lock_file(lk)) + return -1; + if (rename(lk->filename, alternate_index_output)) + return -1; + lk->filename[0] = 0; + return 0; + } else { + return commit_lock_file(lk); + } +} + +static int do_write_locked_index(struct index_state *istate, struct lock_file *lock, + unsigned flags) +{ + int ret = do_write_index(istate, lock->fd, 0); + if (ret) + return ret; + assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) != + (COMMIT_LOCK | CLOSE_LOCK)); + if (flags & COMMIT_LOCK) + return commit_locked_index(lock); + else if (flags & CLOSE_LOCK) + return close_lock_file(lock); + else + return ret; +} + +static int write_split_index(struct index_state *istate, + struct lock_file *lock, + unsigned flags) +{ + int ret; + prepare_to_write_split_index(istate); + ret = do_write_locked_index(istate, lock, flags); + finish_writing_split_index(istate); + return ret; +} + +static char *temporary_sharedindex; + +static void remove_temporary_sharedindex(void) +{ + if (temporary_sharedindex) { + unlink_or_warn(temporary_sharedindex); + free(temporary_sharedindex); + temporary_sharedindex = NULL; + } +} + +static void remove_temporary_sharedindex_on_signal(int signo) +{ + remove_temporary_sharedindex(); + sigchain_pop(signo); + raise(signo); +} + +static int write_shared_index(struct index_state *istate, + struct lock_file *lock, unsigned flags) +{ + struct split_index *si = istate->split_index; + static int installed_handler; + int fd, ret; + + temporary_sharedindex = git_pathdup("sharedindex_XXXXXX"); + fd = mkstemp(temporary_sharedindex); + if (fd < 0) { + free(temporary_sharedindex); + temporary_sharedindex = NULL; + hashclr(si->base_sha1); + return do_write_locked_index(istate, lock, flags); + } + if (!installed_handler) { + atexit(remove_temporary_sharedindex); + sigchain_push_common(remove_temporary_sharedindex_on_signal); + } + move_cache_to_base_index(istate); + ret = do_write_index(si->base, fd, 1); + close(fd); + if (ret) { + remove_temporary_sharedindex(); + return ret; + } + ret = rename(temporary_sharedindex, + git_path("sharedindex.%s", sha1_to_hex(si->base->sha1))); + free(temporary_sharedindex); + temporary_sharedindex = NULL; + if (!ret) + hashcpy(si->base_sha1, si->base->sha1); + return ret; +} + +int write_locked_index(struct index_state *istate, struct lock_file *lock, + unsigned flags) +{ + struct split_index *si = istate->split_index; + + if (!si || alternate_index_output || + (istate->cache_changed & ~EXTMASK)) { + if (si) + hashclr(si->base_sha1); + return do_write_locked_index(istate, lock, flags); + } + + if (getenv("GIT_TEST_SPLIT_INDEX")) { + int v = si->base_sha1[0]; + if ((v & 15) < 6) + istate->cache_changed |= SPLIT_INDEX_ORDERED; + } + if (istate->cache_changed & SPLIT_INDEX_ORDERED) { + int ret = write_shared_index(istate, lock, flags); + if (ret) + return ret; + } + + return write_split_index(istate, lock, flags); +} + /* * Read the index file that is potentially unmerged into given * index_state, dropping any unmerged entries. Returns true if @@ -6,8 +6,29 @@ #include "string-list.h" /* - * Make sure "ref" is something reasonable to have under ".git/refs/"; - * We do not like it if: + * How to handle various characters in refnames: + * 0: An acceptable character for refs + * 1: End-of-component + * 2: ., look for a preceding . to reject .. in refs + * 3: {, look for a preceding @ to reject @{ in refs + * 4: A bad character: ASCII control characters, "~", "^", ":" or SP + */ +static unsigned char refname_disposition[256] = { + 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4 +}; + +/* + * Try to read one refname component from the front of refname. + * Return the length of the component found, or -1 if the component is + * not legal. It is legal if it is something reasonable to have under + * ".git/refs/"; We do not like it if: * * - any path component of it begins with ".", or * - it has double dots "..", or @@ -16,41 +37,31 @@ * - it ends with ".lock" * - it contains a "\" (backslash) */ - -/* Return true iff ch is not allowed in reference names. */ -static inline int bad_ref_char(int ch) -{ - if (((unsigned) ch) <= ' ' || ch == 0x7f || - ch == '~' || ch == '^' || ch == ':' || ch == '\\') - return 1; - /* 2.13 Pattern Matching Notation */ - if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */ - return 1; - return 0; -} - -/* - * Try to read one refname component from the front of refname. Return - * the length of the component found, or -1 if the component is not - * legal. - */ static int check_refname_component(const char *refname, int flags) { const char *cp; char last = '\0'; for (cp = refname; ; cp++) { - char ch = *cp; - if (ch == '\0' || ch == '/') + int ch = *cp & 255; + unsigned char disp = refname_disposition[ch]; + switch (disp) { + case 1: + goto out; + case 2: + if (last == '.') + return -1; /* Refname contains "..". */ break; - if (bad_ref_char(ch)) - return -1; /* Illegal character in refname. */ - if (last == '.' && ch == '.') - return -1; /* Refname contains "..". */ - if (last == '@' && ch == '{') - return -1; /* Refname contains "@{". */ + case 3: + if (last == '@') + return -1; /* Refname contains "@{". */ + break; + case 4: + return -1; + } last = ch; } +out: if (cp == refname) return 0; /* Component has zero length. */ if (refname[0] == '.') { @@ -1151,7 +1162,7 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) if (de->d_name[0] == '.') continue; - if (has_extension(de->d_name, ".lock")) + if (ends_with(de->d_name, ".lock")) continue; strbuf_addstr(&refname, de->d_name); refdir = *refs->name @@ -1323,6 +1334,7 @@ static const char *handle_missing_loose_ref(const char *refname, } } +/* This function needs to return a meaningful errno on failure */ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag) { int depth = MAXDEPTH; @@ -1333,8 +1345,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea if (flag) *flag = 0; - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + errno = EINVAL; return NULL; + } for (;;) { char path[PATH_MAX]; @@ -1342,8 +1356,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea char *buf; int fd; - if (--depth < 0) + if (--depth < 0) { + errno = ELOOP; return NULL; + } git_snpath(path, sizeof(path), "%s", refname); @@ -1405,9 +1421,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea return NULL; } len = read_in_full(fd, buffer, sizeof(buffer)-1); - close(fd); - if (len < 0) + if (len < 0) { + int save_errno = errno; + close(fd); + errno = save_errno; return NULL; + } + close(fd); while (len && isspace(buffer[len-1])) len--; buffer[len] = '\0'; @@ -1424,6 +1444,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea (buffer[40] != '\0' && !isspace(buffer[40]))) { if (flag) *flag |= REF_ISBROKEN; + errno = EINVAL; return NULL; } return refname; @@ -1436,6 +1457,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { if (flag) *flag |= REF_ISBROKEN; + errno = EINVAL; return NULL; } refname = strcpy(refname_buffer, buf); @@ -1920,18 +1942,22 @@ int refname_match(const char *abbrev_name, const char *full_name) return 0; } +/* This function should make sure errno is meaningful on error */ static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) { + int save_errno = errno; error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); + errno = save_errno; return NULL; } if (hashcmp(lock->old_sha1, old_sha1)) { error("Ref %s is at %s but expected %s", lock->ref_name, sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); unlock_ref(lock); + errno = EBUSY; return NULL; } return lock; @@ -1944,14 +1970,16 @@ static int remove_empty_directories(const char *file) * only empty directories), remove them. */ struct strbuf path; - int result; + int result, save_errno; strbuf_init(&path, 20); strbuf_addstr(&path, file); result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY); + save_errno = errno; strbuf_release(&path); + errno = save_errno; return result; } @@ -2015,7 +2043,6 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) *log = NULL; for (p = ref_rev_parse_rules; *p; p++) { - struct stat st; unsigned char hash[20]; char path[PATH_MAX]; const char *ref, *it; @@ -2024,12 +2051,9 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) ref = resolve_ref_unsafe(path, hash, 1, NULL); if (!ref) continue; - if (!stat(git_path("logs/%s", path), &st) && - S_ISREG(st.st_mode)) + if (reflog_exists(path)) it = path; - else if (strcmp(ref, path) && - !stat(git_path("logs/%s", ref), &st) && - S_ISREG(st.st_mode)) + else if (strcmp(ref, path) && reflog_exists(ref)) it = ref; else continue; @@ -2044,6 +2068,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) return logs_found; } +/* This function should make sure errno is meaningful on error */ static struct ref_lock *lock_ref_sha1_basic(const char *refname, const unsigned char *old_sha1, int flags, int *type_p) @@ -2204,6 +2229,7 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data) return 0; } +/* This should return a meaningful errno on failure */ int lock_packed_refs(int flags) { struct packed_ref_cache *packed_ref_cache; @@ -2223,11 +2249,16 @@ int lock_packed_refs(int flags) return 0; } +/* + * Commit the packed refs changes. + * On error we must make sure that errno contains a meaningful value. + */ int commit_packed_refs(void) { struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(&ref_cache); int error = 0; + int save_errno = 0; if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); @@ -2237,10 +2268,13 @@ int commit_packed_refs(void) do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache), 0, write_packed_entry_fn, &packed_ref_cache->lock->fd); - if (commit_lock_file(packed_ref_cache->lock)) + if (commit_lock_file(packed_ref_cache->lock)) { + save_errno = errno; error = -1; + } packed_ref_cache->lock = NULL; release_packed_ref_cache(packed_ref_cache); + errno = save_errno; return error; } @@ -2447,12 +2481,12 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data) return 0; } -int repack_without_refs(const char **refnames, int n) +int repack_without_refs(const char **refnames, int n, struct strbuf *err) { struct ref_dir *packed; struct string_list refs_to_delete = STRING_LIST_INIT_DUP; struct string_list_item *ref_to_delete; - int i, removed = 0; + int i, ret, removed = 0; /* Look for a packed ref */ for (i = 0; i < n; i++) @@ -2464,6 +2498,11 @@ int repack_without_refs(const char **refnames, int n) return 0; /* no refname exists in packed refs */ if (lock_packed_refs(0)) { + if (err) { + unable_to_lock_message(git_path("packed-refs"), errno, + err); + return -1; + } unable_to_lock_error(git_path("packed-refs"), errno); return error("cannot delete '%s' from packed refs", refnames[i]); } @@ -2490,12 +2529,16 @@ int repack_without_refs(const char **refnames, int n) } /* Write what remains */ - return commit_packed_refs(); + ret = commit_packed_refs(); + if (ret && err) + strbuf_addf(err, "unable to overwrite old ref-pack file: %s", + strerror(errno)); + return ret; } static int repack_without_ref(const char *refname) { - return repack_without_refs(&refname, 1); + return repack_without_refs(&refname, 1, NULL); } static int delete_ref_loose(struct ref_lock *lock, int flag) @@ -2733,6 +2776,7 @@ static int copy_msg(char *buf, const char *msg) return cp - buf; } +/* This function must set a meaningful errno on failure */ int log_ref_setup(const char *refname, char *logfile, int bufsize) { int logfd, oflags = O_APPEND | O_WRONLY; @@ -2743,9 +2787,12 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) starts_with(refname, "refs/remotes/") || starts_with(refname, "refs/notes/") || !strcmp(refname, "HEAD"))) { - if (safe_create_leading_directories(logfile) < 0) - return error("unable to create directory for %s", - logfile); + if (safe_create_leading_directories(logfile) < 0) { + int save_errno = errno; + error("unable to create directory for %s", logfile); + errno = save_errno; + return -1; + } oflags |= O_CREAT; } @@ -2756,15 +2803,22 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) if ((oflags & O_CREAT) && errno == EISDIR) { if (remove_empty_directories(logfile)) { - return error("There are still logs under '%s'", - logfile); + int save_errno = errno; + error("There are still logs under '%s'", + logfile); + errno = save_errno; + return -1; } logfd = open(logfile, oflags, 0666); } - if (logfd < 0) - return error("Unable to append to %s: %s", - logfile, strerror(errno)); + if (logfd < 0) { + int save_errno = errno; + error("Unable to append to %s: %s", logfile, + strerror(errno)); + errno = save_errno; + return -1; + } } adjust_shared_perm(logfile); @@ -2804,24 +2858,38 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1, len += copy_msg(logrec + len - 1, msg) - 1; written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1; free(logrec); - if (close(logfd) != 0 || written != len) - return error("Unable to append to %s", log_file); + if (written != len) { + int save_errno = errno; + close(logfd); + error("Unable to append to %s", log_file); + errno = save_errno; + return -1; + } + if (close(logfd)) { + int save_errno = errno; + error("Unable to append to %s", log_file); + errno = save_errno; + return -1; + } return 0; } -static int is_branch(const char *refname) +int is_branch(const char *refname) { return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"); } +/* This function must return a meaningful errno */ int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *logmsg) { static char term = '\n'; struct object *o; - if (!lock) + if (!lock) { + errno = EINVAL; return -1; + } if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) { unlock_ref(lock); return 0; @@ -2831,19 +2899,23 @@ int write_ref_sha1(struct ref_lock *lock, error("Trying to write ref %s with nonexistent object %s", lock->ref_name, sha1_to_hex(sha1)); unlock_ref(lock); + errno = EINVAL; return -1; } if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { error("Trying to write non-commit object %s to branch %s", sha1_to_hex(sha1), lock->ref_name); unlock_ref(lock); + errno = EINVAL; return -1; } if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 || - write_in_full(lock->lock_fd, &term, 1) != 1 - || close_ref(lock) < 0) { + write_in_full(lock->lock_fd, &term, 1) != 1 || + close_ref(lock) < 0) { + int save_errno = errno; error("Couldn't write %s", lock->lk->filename); unlock_ref(lock); + errno = save_errno; return -1; } clear_loose_ref_cache(&ref_cache); @@ -2946,122 +3018,133 @@ int create_symref(const char *ref_target, const char *refs_heads_master, return 0; } -static char *ref_msg(const char *line, const char *endp) -{ - const char *ep; - line += 82; - ep = memchr(line, '\n', endp - line); - if (!ep) - ep = endp; - return xmemdupz(line, ep - line); +struct read_ref_at_cb { + const char *refname; + unsigned long at_time; + int cnt; + int reccnt; + unsigned char *sha1; + int found_it; + + unsigned char osha1[20]; + unsigned char nsha1[20]; + int tz; + unsigned long date; + char **msg; + unsigned long *cutoff_time; + int *cutoff_tz; + int *cutoff_cnt; +}; + +static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + struct read_ref_at_cb *cb = cb_data; + + cb->reccnt++; + cb->tz = tz; + cb->date = timestamp; + + if (timestamp <= cb->at_time || cb->cnt == 0) { + if (cb->msg) + *cb->msg = xstrdup(message); + if (cb->cutoff_time) + *cb->cutoff_time = timestamp; + if (cb->cutoff_tz) + *cb->cutoff_tz = tz; + if (cb->cutoff_cnt) + *cb->cutoff_cnt = cb->reccnt - 1; + /* + * we have not yet updated cb->[n|o]sha1 so they still + * hold the values for the previous record. + */ + if (!is_null_sha1(cb->osha1)) { + hashcpy(cb->sha1, nsha1); + if (hashcmp(cb->osha1, nsha1)) + warning("Log for ref %s has gap after %s.", + cb->refname, show_date(cb->date, cb->tz, DATE_RFC2822)); + } + else if (cb->date == cb->at_time) + hashcpy(cb->sha1, nsha1); + else if (hashcmp(nsha1, cb->sha1)) + warning("Log for ref %s unexpectedly ended on %s.", + cb->refname, show_date(cb->date, cb->tz, + DATE_RFC2822)); + hashcpy(cb->osha1, osha1); + hashcpy(cb->nsha1, nsha1); + cb->found_it = 1; + return 1; + } + hashcpy(cb->osha1, osha1); + hashcpy(cb->nsha1, nsha1); + if (cb->cnt > 0) + cb->cnt--; + return 0; +} + +static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, + int tz, const char *message, void *cb_data) +{ + struct read_ref_at_cb *cb = cb_data; + + if (cb->msg) + *cb->msg = xstrdup(message); + if (cb->cutoff_time) + *cb->cutoff_time = timestamp; + if (cb->cutoff_tz) + *cb->cutoff_tz = tz; + if (cb->cutoff_cnt) + *cb->cutoff_cnt = cb->reccnt; + hashcpy(cb->sha1, osha1); + if (is_null_sha1(cb->sha1)) + hashcpy(cb->sha1, nsha1); + /* We just want the first entry */ + return 1; } int read_ref_at(const char *refname, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt) { - const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; - char *tz_c; - int logfd, tz, reccnt = 0; - struct stat st; - unsigned long date; - unsigned char logged_sha1[20]; - void *log_mapped; - size_t mapsz; + struct read_ref_at_cb cb; - logfile = git_path("logs/%s", refname); - logfd = open(logfile, O_RDONLY, 0); - if (logfd < 0) - die_errno("Unable to read log '%s'", logfile); - fstat(logfd, &st); - if (!st.st_size) - die("Log %s is empty.", logfile); - mapsz = xsize_t(st.st_size); - log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0); - logdata = log_mapped; - close(logfd); + memset(&cb, 0, sizeof(cb)); + cb.refname = refname; + cb.at_time = at_time; + cb.cnt = cnt; + cb.msg = msg; + cb.cutoff_time = cutoff_time; + cb.cutoff_tz = cutoff_tz; + cb.cutoff_cnt = cutoff_cnt; + cb.sha1 = sha1; + + for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb); + + if (!cb.reccnt) + die("Log for %s is empty.", refname); + if (cb.found_it) + return 0; + + for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb); - lastrec = NULL; - rec = logend = logdata + st.st_size; - while (logdata < rec) { - reccnt++; - if (logdata < rec && *(rec-1) == '\n') - rec--; - lastgt = NULL; - while (logdata < rec && *(rec-1) != '\n') { - rec--; - if (*rec == '>') - lastgt = rec; - } - if (!lastgt) - die("Log %s is corrupt.", logfile); - date = strtoul(lastgt + 1, &tz_c, 10); - if (date <= at_time || cnt == 0) { - tz = strtoul(tz_c, NULL, 10); - if (msg) - *msg = ref_msg(rec, logend); - if (cutoff_time) - *cutoff_time = date; - if (cutoff_tz) - *cutoff_tz = tz; - if (cutoff_cnt) - *cutoff_cnt = reccnt - 1; - if (lastrec) { - if (get_sha1_hex(lastrec, logged_sha1)) - die("Log %s is corrupt.", logfile); - if (get_sha1_hex(rec + 41, sha1)) - die("Log %s is corrupt.", logfile); - if (hashcmp(logged_sha1, sha1)) { - warning("Log %s has gap after %s.", - logfile, show_date(date, tz, DATE_RFC2822)); - } - } - else if (date == at_time) { - if (get_sha1_hex(rec + 41, sha1)) - die("Log %s is corrupt.", logfile); - } - else { - if (get_sha1_hex(rec + 41, logged_sha1)) - die("Log %s is corrupt.", logfile); - if (hashcmp(logged_sha1, sha1)) { - warning("Log %s unexpectedly ended on %s.", - logfile, show_date(date, tz, DATE_RFC2822)); - } - } - munmap(log_mapped, mapsz); - return 0; - } - lastrec = rec; - if (cnt > 0) - cnt--; - } - - rec = logdata; - while (rec < logend && *rec != '>' && *rec != '\n') - rec++; - if (rec == logend || *rec == '\n') - die("Log %s is corrupt.", logfile); - date = strtoul(rec + 1, &tz_c, 10); - tz = strtoul(tz_c, NULL, 10); - if (get_sha1_hex(logdata, sha1)) - die("Log %s is corrupt.", logfile); - if (is_null_sha1(sha1)) { - if (get_sha1_hex(logdata + 41, sha1)) - die("Log %s is corrupt.", logfile); - } - if (msg) - *msg = ref_msg(logdata, logend); - munmap(log_mapped, mapsz); - - if (cutoff_time) - *cutoff_time = date; - if (cutoff_tz) - *cutoff_tz = tz; - if (cutoff_cnt) - *cutoff_cnt = reccnt; return 1; } +int reflog_exists(const char *refname) +{ + struct stat st; + + return !lstat(git_path("logs/%s", refname), &st) && + S_ISREG(st.st_mode); +} + +int delete_reflog(const char *refname) +{ + return remove_path(git_path("logs/%s", refname)); +} + static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) { unsigned char osha1[20], nsha1[20]; @@ -3214,7 +3297,7 @@ static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data if (de->d_name[0] == '.') continue; - if (has_extension(de->d_name, ".lock")) + if (ends_with(de->d_name, ".lock")) continue; strbuf_addstr(name, de->d_name); if (stat(git_path("logs/%s", name->buf), &st) < 0) { @@ -3259,9 +3342,9 @@ static struct ref_lock *update_ref_lock(const char *refname, if (!lock) { const char *str = "Cannot lock the ref '%s'."; switch (onerr) { - case MSG_ON_ERR: error(str, refname); break; - case DIE_ON_ERR: die(str, refname); break; - case QUIET_ON_ERR: break; + case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break; + case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break; + case UPDATE_REFS_QUIET_ON_ERR: break; } } return lock; @@ -3269,20 +3352,131 @@ static struct ref_lock *update_ref_lock(const char *refname, static int update_ref_write(const char *action, const char *refname, const unsigned char *sha1, struct ref_lock *lock, - enum action_on_err onerr) + struct strbuf *err, enum action_on_err onerr) { if (write_ref_sha1(lock, sha1, action) < 0) { const char *str = "Cannot update the ref '%s'."; + if (err) + strbuf_addf(err, str, refname); + switch (onerr) { - case MSG_ON_ERR: error(str, refname); break; - case DIE_ON_ERR: die(str, refname); break; - case QUIET_ON_ERR: break; + case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break; + case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break; + case UPDATE_REFS_QUIET_ON_ERR: break; } return 1; } return 0; } +/** + * Information needed for a single ref update. Set new_sha1 to the + * new value or to zero to delete the ref. To check the old value + * while locking the ref, set have_old to 1 and set old_sha1 to the + * value or to zero to ensure the ref does not exist before update. + */ +struct ref_update { + unsigned char new_sha1[20]; + unsigned char old_sha1[20]; + int flags; /* REF_NODEREF? */ + int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ + struct ref_lock *lock; + int type; + const char refname[FLEX_ARRAY]; +}; + +/* + * Data structure for holding a reference transaction, which can + * consist of checks and updates to multiple references, carried out + * as atomically as possible. This structure is opaque to callers. + */ +struct ref_transaction { + struct ref_update **updates; + size_t alloc; + size_t nr; +}; + +struct ref_transaction *ref_transaction_begin(void) +{ + return xcalloc(1, sizeof(struct ref_transaction)); +} + +void ref_transaction_free(struct ref_transaction *transaction) +{ + int i; + + if (!transaction) + return; + + for (i = 0; i < transaction->nr; i++) + free(transaction->updates[i]); + + free(transaction->updates); + free(transaction); +} + +static struct ref_update *add_update(struct ref_transaction *transaction, + const char *refname) +{ + size_t len = strlen(refname); + struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1); + + strcpy((char *)update->refname, refname); + ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc); + transaction->updates[transaction->nr++] = update; + return update; +} + +int ref_transaction_update(struct ref_transaction *transaction, + const char *refname, + const unsigned char *new_sha1, + const unsigned char *old_sha1, + int flags, int have_old, + struct strbuf *err) +{ + struct ref_update *update; + + if (have_old && !old_sha1) + die("BUG: have_old is true but old_sha1 is NULL"); + + update = add_update(transaction, refname); + hashcpy(update->new_sha1, new_sha1); + update->flags = flags; + update->have_old = have_old; + if (have_old) + hashcpy(update->old_sha1, old_sha1); + return 0; +} + +void ref_transaction_create(struct ref_transaction *transaction, + const char *refname, + const unsigned char *new_sha1, + int flags) +{ + struct ref_update *update = add_update(transaction, refname); + + assert(!is_null_sha1(new_sha1)); + hashcpy(update->new_sha1, new_sha1); + hashclr(update->old_sha1); + update->flags = flags; + update->have_old = 1; +} + +void ref_transaction_delete(struct ref_transaction *transaction, + const char *refname, + const unsigned char *old_sha1, + int flags, int have_old) +{ + struct ref_update *update = add_update(transaction, refname); + + update->flags = flags; + update->have_old = have_old; + if (have_old) { + assert(!is_null_sha1(old_sha1)); + hashcpy(update->old_sha1, old_sha1); + } +} + int update_ref(const char *action, const char *refname, const unsigned char *sha1, const unsigned char *oldval, int flags, enum action_on_err onerr) @@ -3291,105 +3485,106 @@ int update_ref(const char *action, const char *refname, lock = update_ref_lock(refname, oldval, flags, NULL, onerr); if (!lock) return 1; - return update_ref_write(action, refname, sha1, lock, onerr); + return update_ref_write(action, refname, sha1, lock, NULL, onerr); } static int ref_update_compare(const void *r1, const void *r2) { const struct ref_update * const *u1 = r1; const struct ref_update * const *u2 = r2; - return strcmp((*u1)->ref_name, (*u2)->ref_name); + return strcmp((*u1)->refname, (*u2)->refname); } static int ref_update_reject_duplicates(struct ref_update **updates, int n, - enum action_on_err onerr) + struct strbuf *err) { int i; for (i = 1; i < n; i++) - if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) { + if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) { const char *str = "Multiple updates for ref '%s' not allowed."; - switch (onerr) { - case MSG_ON_ERR: - error(str, updates[i]->ref_name); break; - case DIE_ON_ERR: - die(str, updates[i]->ref_name); break; - case QUIET_ON_ERR: - break; - } + if (err) + strbuf_addf(err, str, updates[i]->refname); + return 1; } return 0; } -int update_refs(const char *action, const struct ref_update **updates_orig, - int n, enum action_on_err onerr) +int ref_transaction_commit(struct ref_transaction *transaction, + const char *msg, struct strbuf *err) { int ret = 0, delnum = 0, i; - struct ref_update **updates; - int *types; - struct ref_lock **locks; const char **delnames; + int n = transaction->nr; + struct ref_update **updates = transaction->updates; - if (!updates_orig || !n) + if (!n) return 0; /* Allocate work space */ - updates = xmalloc(sizeof(*updates) * n); - types = xmalloc(sizeof(*types) * n); - locks = xcalloc(n, sizeof(*locks)); delnames = xmalloc(sizeof(*delnames) * n); /* Copy, sort, and reject duplicate refs */ - memcpy(updates, updates_orig, sizeof(*updates) * n); qsort(updates, n, sizeof(*updates), ref_update_compare); - ret = ref_update_reject_duplicates(updates, n, onerr); + ret = ref_update_reject_duplicates(updates, n, err); if (ret) goto cleanup; /* Acquire all locks while verifying old values */ for (i = 0; i < n; i++) { - locks[i] = update_ref_lock(updates[i]->ref_name, - (updates[i]->have_old ? - updates[i]->old_sha1 : NULL), - updates[i]->flags, - &types[i], onerr); - if (!locks[i]) { + struct ref_update *update = updates[i]; + + update->lock = update_ref_lock(update->refname, + (update->have_old ? + update->old_sha1 : NULL), + update->flags, + &update->type, + UPDATE_REFS_QUIET_ON_ERR); + if (!update->lock) { + if (err) + strbuf_addf(err, "Cannot lock the ref '%s'.", + update->refname); ret = 1; goto cleanup; } } /* Perform updates first so live commits remain referenced */ - for (i = 0; i < n; i++) - if (!is_null_sha1(updates[i]->new_sha1)) { - ret = update_ref_write(action, - updates[i]->ref_name, - updates[i]->new_sha1, - locks[i], onerr); - locks[i] = NULL; /* freed by update_ref_write */ + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if (!is_null_sha1(update->new_sha1)) { + ret = update_ref_write(msg, + update->refname, + update->new_sha1, + update->lock, err, + UPDATE_REFS_QUIET_ON_ERR); + update->lock = NULL; /* freed by update_ref_write */ if (ret) goto cleanup; } + } /* Perform deletes now that updates are safely completed */ - for (i = 0; i < n; i++) - if (locks[i]) { - delnames[delnum++] = locks[i]->ref_name; - ret |= delete_ref_loose(locks[i], types[i]); + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if (update->lock) { + delnames[delnum++] = update->lock->ref_name; + ret |= delete_ref_loose(update->lock, update->type); } - ret |= repack_without_refs(delnames, delnum); + } + + ret |= repack_without_refs(delnames, delnum, err); for (i = 0; i < delnum; i++) unlink_or_warn(git_path("logs/%s", delnames[i])); clear_loose_ref_cache(&ref_cache); cleanup: for (i = 0; i < n; i++) - if (locks[i]) - unlock_ref(locks[i]); - free(updates); - free(types); - free(locks); + if (updates[i]->lock) + unlock_ref(updates[i]->lock); free(delnames); return ret; } @@ -10,19 +10,7 @@ struct ref_lock { int force_write; }; -/** - * Information needed for a single ref update. Set new_sha1 to the - * new value or to zero to delete the ref. To check the old value - * while locking the ref, set have_old to 1 and set old_sha1 to the - * value or to zero to ensure the ref does not exist before update. - */ -struct ref_update { - const char *ref_name; - unsigned char new_sha1[20]; - unsigned char old_sha1[20]; - int flags; /* REF_NODEREF? */ - int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ -}; +struct ref_transaction; /* * Bit values set in the flags argument passed to each_ref_fn(): @@ -94,6 +82,7 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st /* * Lock the packed-refs file for writing. Flags is passed to * hold_lock_file_for_update(). Return 0 on success. + * Errno is set to something meaningful on error. */ extern int lock_packed_refs(int flags); @@ -109,6 +98,7 @@ extern void add_packed_ref(const char *refname, const unsigned char *sha1); * Write the current version of the packed refs cache from memory to * disk. The packed-refs file must already be locked for writing (see * lock_packed_refs()). Return zero on success. + * Sets errno to something meaningful on error. */ extern int commit_packed_refs(void); @@ -133,10 +123,13 @@ extern void rollback_packed_refs(void); */ int pack_refs(unsigned int flags); -extern int repack_without_refs(const char **refnames, int n); +extern int repack_without_refs(const char **refnames, int n, + struct strbuf *err); extern int ref_exists(const char *); +extern int is_branch(const char *refname); + /* * If refname is a non-symbolic reference that refers to a tag object, * and the tag can be (recursively) dereferenced to a non-tag object, @@ -147,11 +140,15 @@ extern int ref_exists(const char *); */ extern int peel_ref(const char *refname, unsigned char *sha1); -/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ +/* + * Locks a "refs/" ref returning the lock on success and NULL on failure. + * On failure errno is set to something meaningful. + */ extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1); /** Locks any ref (for 'HEAD' type refs). */ #define REF_NODEREF 0x01 +/* errno is set to something meaningful on failure */ extern struct ref_lock *lock_any_ref_for_update(const char *refname, const unsigned char *old_sha1, int flags, int *type_p); @@ -168,14 +165,22 @@ extern void unlock_ref(struct ref_lock *lock); /** Writes sha1 into the ref specified by the lock. **/ extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); -/** Setup reflog before using. **/ -int log_ref_setup(const char *ref_name, char *logfile, int bufsize); +/* + * Setup reflog before using. Set errno to something meaningful on failure. + */ +int log_ref_setup(const char *refname, char *logfile, int bufsize); /** Reads log for the value of ref during at_time. **/ extern int read_ref_at(const char *refname, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt); +/** Check if a particular reflog exists */ +extern int reflog_exists(const char *refname); + +/** Delete a reflog */ +extern int delete_reflog(const char *refname); + /* iterate over reflog entries */ typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data); @@ -217,18 +222,84 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg */ extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1); -/** lock a ref and then write its file */ -enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR }; +enum action_on_err { + UPDATE_REFS_MSG_ON_ERR, + UPDATE_REFS_DIE_ON_ERR, + UPDATE_REFS_QUIET_ON_ERR +}; + +/* + * Begin a reference transaction. The reference transaction must + * be freed by calling ref_transaction_free(). + */ +struct ref_transaction *ref_transaction_begin(void); + +/* + * The following functions add a reference check or update to a + * ref_transaction. In all of them, refname is the name of the + * reference to be affected. The functions make internal copies of + * refname, so the caller retains ownership of the parameter. flags + * can be REF_NODEREF; it is passed to update_ref_lock(). + */ + +/* + * Add a reference update to transaction. new_sha1 is the value that + * the reference should have after the update, or zeros if it should + * be deleted. If have_old is true, then old_sha1 holds the value + * that the reference should have had before the update, or zeros if + * it must not have existed beforehand. + * Function returns 0 on success and non-zero on failure. A failure to update + * means that the transaction as a whole has failed and will need to be + * rolled back. On failure the err buffer will be updated. + */ +int ref_transaction_update(struct ref_transaction *transaction, + const char *refname, + const unsigned char *new_sha1, + const unsigned char *old_sha1, + int flags, int have_old, + struct strbuf *err); + +/* + * Add a reference creation to transaction. new_sha1 is the value + * that the reference should have after the update; it must not be the + * null SHA-1. It is verified that the reference does not exist + * already. + */ +void ref_transaction_create(struct ref_transaction *transaction, + const char *refname, + const unsigned char *new_sha1, + int flags); + +/* + * Add a reference deletion to transaction. If have_old is true, then + * old_sha1 holds the value that the reference should have had before + * the update (which must not be the null SHA-1). + */ +void ref_transaction_delete(struct ref_transaction *transaction, + const char *refname, + const unsigned char *old_sha1, + int flags, int have_old); + +/* + * Commit all of the changes that have been queued in transaction, as + * atomically as possible. Return a nonzero value if there is a + * problem. + * If err is non-NULL we will add an error string to it to explain why + * the transaction failed. The string does not end in newline. + */ +int ref_transaction_commit(struct ref_transaction *transaction, + const char *msg, struct strbuf *err); + +/* + * Free an existing transaction and all associated data. + */ +void ref_transaction_free(struct ref_transaction *transaction); + +/** Lock a ref and then write its file */ int update_ref(const char *action, const char *refname, const unsigned char *sha1, const unsigned char *oldval, int flags, enum action_on_err onerr); -/** - * Lock all refs and then perform all modifications. - */ -int update_refs(const char *action, const struct ref_update **updates, - int n, enum action_on_err onerr); - extern int parse_hide_refs_config(const char *var, const char *value, const char *); extern int ref_is_hidden(const char *); diff --git a/remote-curl.c b/remote-curl.c index 52c2d96c..0fcf2ce5 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -194,19 +194,19 @@ static void free_discovery(struct discovery *d) } } -static int show_http_message(struct strbuf *type, struct strbuf *msg) +static int show_http_message(struct strbuf *type, struct strbuf *charset, + struct strbuf *msg) { const char *p, *eol; /* * We only show text/plain parts, as other types are likely * to be ugly to look at on the user's terminal. - * - * TODO should handle "; charset=XXX", and re-encode into - * logoutputencoding */ - if (strcasecmp(type->buf, "text/plain")) + if (strcmp(type->buf, "text/plain")) return -1; + if (charset->len) + strbuf_reencode(msg, charset->buf, get_log_output_encoding()); strbuf_trim(msg); if (!msg->len) @@ -225,6 +225,7 @@ static struct discovery* discover_refs(const char *service, int for_push) { struct strbuf exp = STRBUF_INIT; struct strbuf type = STRBUF_INIT; + struct strbuf charset = STRBUF_INIT; struct strbuf buffer = STRBUF_INIT; struct strbuf refs_url = STRBUF_INIT; struct strbuf effective_url = STRBUF_INIT; @@ -249,6 +250,7 @@ static struct discovery* discover_refs(const char *service, int for_push) memset(&options, 0, sizeof(options)); options.content_type = &type; + options.charset = &charset; options.effective_url = &effective_url; options.base_url = &url; options.no_cache = 1; @@ -259,13 +261,13 @@ static struct discovery* discover_refs(const char *service, int for_push) case HTTP_OK: break; case HTTP_MISSING_TARGET: - show_http_message(&type, &buffer); + show_http_message(&type, &charset, &buffer); die("repository '%s' not found", url.buf); case HTTP_NOAUTH: - show_http_message(&type, &buffer); + show_http_message(&type, &charset, &buffer); die("Authentication failed for '%s'", url.buf); default: - show_http_message(&type, &buffer); + show_http_message(&type, &charset, &buffer); die("unable to access '%s': %s", url.buf, curl_errorstr); } @@ -310,6 +312,7 @@ static struct discovery* discover_refs(const char *service, int for_push) strbuf_release(&refs_url); strbuf_release(&exp); strbuf_release(&type); + strbuf_release(&charset); strbuf_release(&effective_url); strbuf_release(&buffer); last_discovery = last; @@ -396,7 +399,7 @@ static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) rpc->pos = 0; return CURLIOE_OK; } - fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n"); + error("unable to rewind rpc post data - try increasing http.postBuffer"); return CURLIOE_FAILRESTART; default: @@ -706,7 +709,7 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) free(targets[i]); free(targets); - return ret ? error("Fetch failed.") : 0; + return ret ? error("fetch failed.") : 0; } static int fetch_git(struct discovery *heads, @@ -788,9 +791,9 @@ static void parse_fetch(struct strbuf *buf) int alloc_heads = 0, nr_heads = 0; do { - if (starts_with(buf->buf, "fetch ")) { - char *p = buf->buf + strlen("fetch "); - char *name; + const char *p; + if (skip_prefix(buf->buf, "fetch ", &p)) { + const char *name; struct ref *ref; unsigned char old_sha1[20]; @@ -946,7 +949,7 @@ int main(int argc, const char **argv) git_extract_argv0_path(argv[0]); setup_git_directory_gently(&nongit); if (argc < 2) { - fprintf(stderr, "Remote needed\n"); + error("remote-curl: usage: git remote-curl <remote> [<url>]"); return 1; } @@ -965,18 +968,18 @@ int main(int argc, const char **argv) http_init(remote, url.buf, 0); do { + const char *arg; + if (strbuf_getline(&buf, stdin, '\n') == EOF) { if (ferror(stdin)) - fprintf(stderr, "Error reading command stream\n"); - else - fprintf(stderr, "Unexpected end of command stream\n"); + error("remote-curl: error reading command stream from git"); return 1; } if (buf.len == 0) break; if (starts_with(buf.buf, "fetch ")) { if (nongit) - die("Fetch attempted without a local repo"); + die("remote-curl: fetch attempted without a local repo"); parse_fetch(&buf); } else if (!strcmp(buf.buf, "list") || starts_with(buf.buf, "list ")) { @@ -986,9 +989,8 @@ int main(int argc, const char **argv) } else if (starts_with(buf.buf, "push ")) { parse_push(&buf); - } else if (starts_with(buf.buf, "option ")) { - char *name = buf.buf + strlen("option "); - char *value = strchr(name, ' '); + } else if (skip_prefix(buf.buf, "option ", &arg)) { + char *value = strchr(arg, ' '); int result; if (value) @@ -996,7 +998,7 @@ int main(int argc, const char **argv) else value = "true"; - result = set_option(name, value); + result = set_option(arg, value); if (!result) printf("ok\n"); else if (result < 0) @@ -1013,7 +1015,7 @@ int main(int argc, const char **argv) printf("\n"); fflush(stdout); } else { - fprintf(stderr, "Unknown command '%s'\n", buf.buf); + error("remote-curl: unknown command '%s' from git", buf.buf); return 1; } strbuf_reset(&buf); diff --git a/remote-testsvn.c b/remote-testsvn.c index 6be55cbe..686e07d3 100644 --- a/remote-testsvn.c +++ b/remote-testsvn.c @@ -175,8 +175,8 @@ static int cmd_import(const char *line) char *note_msg; unsigned char head_sha1[20]; unsigned int startrev; - struct argv_array svndump_argv = ARGV_ARRAY_INIT; struct child_process svndump_proc; + const char *command = "svnrdump"; if (read_ref(private_ref, head_sha1)) startrev = 0; @@ -202,15 +202,14 @@ static int cmd_import(const char *line) } else { memset(&svndump_proc, 0, sizeof(struct child_process)); svndump_proc.out = -1; - argv_array_push(&svndump_argv, "svnrdump"); - argv_array_push(&svndump_argv, "dump"); - argv_array_push(&svndump_argv, url); - argv_array_pushf(&svndump_argv, "-r%u:HEAD", startrev); - svndump_proc.argv = svndump_argv.argv; + argv_array_push(&svndump_proc.args, command); + argv_array_push(&svndump_proc.args, "dump"); + argv_array_push(&svndump_proc.args, url); + argv_array_pushf(&svndump_proc.args, "-r%u:HEAD", startrev); code = start_command(&svndump_proc); if (code) - die("Unable to start %s, code %d", svndump_proc.argv[0], code); + die("Unable to start %s, code %d", command, code); dumpin_fd = svndump_proc.out; } /* setup marks file import/export */ @@ -226,8 +225,7 @@ static int cmd_import(const char *line) if (!dump_from_file) { code = finish_command(&svndump_proc); if (code) - warning("%s, returned %d", svndump_proc.argv[0], code); - argv_array_clear(&svndump_argv); + warning("%s, returned %d", command, code); } return 0; @@ -170,7 +170,6 @@ static struct branch *make_branch(const char *name, int len) { struct branch *ret; int i; - char *refname; for (i = 0; i < branches_nr; i++) { if (len ? (!strncmp(name, branches[i]->name, len) && @@ -186,10 +185,7 @@ static struct branch *make_branch(const char *name, int len) ret->name = xstrndup(name, len); else ret->name = xstrdup(name); - refname = xmalloc(strlen(name) + strlen("refs/heads/") + 1); - strcpy(refname, "refs/heads/"); - strcpy(refname + strlen("refs/heads/"), ret->name); - ret->refname = refname; + ret->refname = xstrfmt("refs/heads/%s", ret->name); return ret; } @@ -488,9 +484,8 @@ static void read_config(void) current_branch = NULL; head_ref = resolve_ref_unsafe("HEAD", sha1, 0, &flag); if (head_ref && (flag & REF_ISSYMREF) && - starts_with(head_ref, "refs/heads/")) { - current_branch = - make_branch(head_ref + strlen("refs/heads/"), 0); + skip_prefix(head_ref, "refs/heads/", &head_ref)) { + current_branch = make_branch(head_ref, 0); } git_config(handle_config, NULL); if (branch_pushremote_name) { @@ -1194,7 +1189,7 @@ static int match_explicit(struct ref *src, struct ref *dst, case 1: break; case 0: - if (!memcmp(dst_value, "refs/", 5)) + if (starts_with(dst_value, "refs/")) matched_dst = make_linked_ref(dst_value, dst_tail); else if (is_null_sha1(matched_src->new_sha1)) error("unable to delete '%s': remote ref does not exist", @@ -207,11 +207,11 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz strbuf_reset(&one); strbuf_reset(&two); } else if (hunk == RR_SIDE_1) - strbuf_addstr(&one, buf.buf); + strbuf_addbuf(&one, &buf); else if (hunk == RR_ORIGINAL) ; /* discard */ else if (hunk == RR_SIDE_2) - strbuf_addstr(&two, buf.buf); + strbuf_addbuf(&two, &buf); else rerere_io_putstr(buf.buf, io); continue; @@ -492,8 +492,7 @@ static int update_paths(struct string_list *update) } if (!status && active_cache_changed) { - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(&index_lock)) + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) die("Unable to write new index file"); } else if (fd >= 0) rollback_lock_file(&index_lock); diff --git a/resolve-undo.c b/resolve-undo.c index 44c697c3..468a2eb9 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -110,7 +110,7 @@ void resolve_undo_clear_index(struct index_state *istate) string_list_clear(resolve_undo, 1); free(resolve_undo); istate->resolve_undo = NULL; - istate->cache_changed = 1; + istate->cache_changed |= RESOLVE_UNDO_CHANGED; } int unmerge_index_entry_at(struct index_state *istate, int pos) diff --git a/run-command.c b/run-command.c index 75abc478..35a3ebf0 100644 --- a/run-command.c +++ b/run-command.c @@ -279,6 +279,9 @@ int start_command(struct child_process *cmd) int failed_errno; char *str; + if (!cmd->argv) + cmd->argv = cmd->args.argv; + /* * In case of errors we must keep the promise to close FDs * that have been passed in via ->in and ->out. @@ -328,6 +331,7 @@ int start_command(struct child_process *cmd) fail_pipe: error("cannot create %s pipe for %s: %s", str, cmd->argv[0], strerror(failed_errno)); + argv_array_clear(&cmd->args); errno = failed_errno; return -1; } @@ -450,7 +454,6 @@ fail_pipe: { int fhin = 0, fhout = 1, fherr = 2; const char **sargv = cmd->argv; - char **env = environ; if (cmd->no_stdin) fhin = open("/dev/null", O_RDWR); @@ -475,24 +478,19 @@ fail_pipe: else if (cmd->out > 1) fhout = dup(cmd->out); - if (cmd->env) - env = make_augmented_environ(cmd->env); - if (cmd->git_cmd) cmd->argv = prepare_git_cmd(cmd->argv); else if (cmd->use_shell) cmd->argv = prepare_shell_cmd(cmd->argv); - cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, cmd->dir, - fhin, fhout, fherr); + cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env, + cmd->dir, fhin, fhout, fherr); failed_errno = errno; if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) error("cannot spawn %s: %s", cmd->argv[0], strerror(errno)); if (cmd->clean_on_exit && cmd->pid >= 0) mark_child_for_cleanup(cmd->pid); - if (cmd->env) - free_environ(env); if (cmd->git_cmd) free(cmd->argv); @@ -519,6 +517,7 @@ fail_pipe: close_pair(fderr); else if (cmd->err) close(cmd->err); + argv_array_clear(&cmd->args); errno = failed_errno; return -1; } @@ -543,7 +542,9 @@ fail_pipe: int finish_command(struct child_process *cmd) { - return wait_or_whine(cmd->pid, cmd->argv[0]); + int ret = wait_or_whine(cmd->pid, cmd->argv[0]); + argv_array_clear(&cmd->args); + return ret; } int run_command(struct child_process *cmd) @@ -763,28 +764,21 @@ char *find_hook(const char *name) int run_hook_ve(const char *const *env, const char *name, va_list args) { struct child_process hook; - struct argv_array argv = ARGV_ARRAY_INIT; const char *p; - int ret; p = find_hook(name); if (!p) return 0; - argv_array_push(&argv, p); - - while ((p = va_arg(args, const char *))) - argv_array_push(&argv, p); - memset(&hook, 0, sizeof(hook)); - hook.argv = argv.argv; + argv_array_push(&hook.args, p); + while ((p = va_arg(args, const char *))) + argv_array_push(&hook.args, p); hook.env = env; hook.no_stdin = 1; hook.stdout_to_stderr = 1; - ret = run_command(&hook); - argv_array_clear(&argv); - return ret; + return run_command(&hook); } int run_hook_le(const char *const *env, const char *name, ...) diff --git a/run-command.h b/run-command.h index 3653bfa6..ea73de30 100644 --- a/run-command.h +++ b/run-command.h @@ -5,8 +5,11 @@ #include <pthread.h> #endif +#include "argv-array.h" + struct child_process { const char **argv; + struct argv_array args; pid_t pid; /* * Using .in, .out, .err: diff --git a/sequencer.c b/sequencer.c index bbaddcb0..3c060e05 100644 --- a/sequencer.c +++ b/sequencer.c @@ -243,11 +243,15 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, read_cache(); if (checkout_fast_forward(from, to, 1)) - exit(1); /* the callee should have complained already */ + exit(128); /* the callee should have complained already */ ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0, NULL); + if (!ref_lock) + return error(_("Failed to lock HEAD during fast_forward_to")); + strbuf_addf(&sb, "%s: fast-forward", action_name(opts)); ret = write_ref_sha1(ref_lock, to, sb.buf); + strbuf_release(&sb); return ret; } @@ -259,11 +263,11 @@ static int do_recursive_merge(struct commit *base, struct commit *next, { struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; - int clean, index_fd; + int clean; const char **xopt; static struct lock_file index_lock; - index_fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); read_cache(); @@ -284,8 +288,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, next_tree, base_tree, &result); if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(&index_lock))) + write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ die(_("%s: Unable to write new index file"), action_name(opts)); rollback_lock_file(&index_lock); @@ -337,9 +340,7 @@ static int is_index_unchanged(void) active_cache_tree = cache_tree(); if (!cache_tree_fully_valid(active_cache_tree)) - if (cache_tree_update(active_cache_tree, - (const struct cache_entry * const *)active_cache, - active_nr, 0)) + if (cache_tree_update(&the_index, 0)) return error(_("Unable to update cache tree\n")); return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1); @@ -357,18 +358,13 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, { struct argv_array array; int rc; - char *gpg_sign; argv_array_init(&array); argv_array_push(&array, "commit"); argv_array_push(&array, "-n"); - if (opts->gpg_sign) { - gpg_sign = xmalloc(3 + strlen(opts->gpg_sign)); - sprintf(gpg_sign, "-S%s", opts->gpg_sign); - argv_array_push(&array, gpg_sign); - free(gpg_sign); - } + if (opts->gpg_sign) + argv_array_pushf(&array, "-S%s", opts->gpg_sign); if (opts->signoff) argv_array_push(&array, "-s"); if (!opts->edit) { @@ -644,9 +640,8 @@ static void read_and_refresh_cache(struct replay_opts *opts) if (read_index_preload(&the_index, NULL) < 0) die(_("git %s: failed to read the index"), action_name(opts)); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); - if (the_index.cache_changed) { - if (write_index(&the_index, index_fd) || - commit_locked_index(&index_lock)) + if (the_index.cache_changed && index_fd >= 0) { + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) die(_("git %s: failed to refresh the index"), action_name(opts)); } rollback_lock_file(&index_lock); diff --git a/sha1_file.c b/sha1_file.c index a38854ce..3f70b1d8 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -36,9 +36,6 @@ static inline uintmax_t sz_fmt(size_t s) { return s; } const unsigned char null_sha1[20]; -static const char *no_log_pack_access = "no_log_pack_access"; -static const char *log_pack_access; - /* * This is meant to hold a *small* number of objects that you would * want read_sha1_file() to be able to return, but yet you do not want @@ -268,9 +265,9 @@ static struct alternate_object_database **alt_odb_tail; * SHA1, an extra slash for the first level indirection, and the * terminating NUL. */ -static int link_alt_odb_entry(const char *entry, const char *relative_base, int depth) +static int link_alt_odb_entry(const char *entry, const char *relative_base, + int depth, const char *normalized_objdir) { - const char *objdir = get_object_directory(); struct alternate_object_database *ent; struct alternate_object_database *alt; int pfxlen, entlen; @@ -321,7 +318,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base, int return -1; } } - if (!strcmp(ent->base, objdir)) { + if (!strcmp_icase(ent->base, normalized_objdir)) { free(ent); return -1; } @@ -345,6 +342,7 @@ static void link_alt_odb_entries(const char *alt, int len, int sep, struct string_list entries = STRING_LIST_INIT_NODUP; char *alt_copy; int i; + struct strbuf objdirbuf = STRBUF_INIT; if (depth > 5) { error("%s: ignoring alternate object stores, nesting too deep.", @@ -352,6 +350,9 @@ static void link_alt_odb_entries(const char *alt, int len, int sep, return; } + strbuf_addstr(&objdirbuf, absolute_path(get_object_directory())); + normalize_path_copy(objdirbuf.buf, objdirbuf.buf); + alt_copy = xmemdupz(alt, len); string_list_split_in_place(&entries, alt_copy, sep, -1); for (i = 0; i < entries.nr; i++) { @@ -362,11 +363,12 @@ static void link_alt_odb_entries(const char *alt, int len, int sep, error("%s: ignoring relative alternate object store %s", relative_base, entry); } else { - link_alt_odb_entry(entry, relative_base, depth); + link_alt_odb_entry(entry, relative_base, depth, objdirbuf.buf); } } string_list_clear(&entries, 0); free(alt_copy); + strbuf_release(&objdirbuf); } void read_info_alternates(const char * relative_base, int depth) @@ -1178,48 +1180,42 @@ static void report_pack_garbage(struct string_list *list) static void prepare_packed_git_one(char *objdir, int local) { - /* Ensure that this buffer is large enough so that we can - append "/pack/" without clobbering the stack even if - strlen(objdir) were PATH_MAX. */ - char path[PATH_MAX + 1 + 4 + 1 + 1]; - int len; + struct strbuf path = STRBUF_INIT; + size_t dirnamelen; DIR *dir; struct dirent *de; struct string_list garbage = STRING_LIST_INIT_DUP; - sprintf(path, "%s/pack", objdir); - len = strlen(path); - dir = opendir(path); + strbuf_addstr(&path, objdir); + strbuf_addstr(&path, "/pack"); + dir = opendir(path.buf); if (!dir) { if (errno != ENOENT) error("unable to open object pack directory: %s: %s", - path, strerror(errno)); + path.buf, strerror(errno)); + strbuf_release(&path); return; } - path[len++] = '/'; + strbuf_addch(&path, '/'); + dirnamelen = path.len; while ((de = readdir(dir)) != NULL) { - int namelen = strlen(de->d_name); struct packed_git *p; - - if (len + namelen + 1 > sizeof(path)) { - if (report_garbage) { - struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "%.*s/%s", len - 1, path, de->d_name); - report_garbage("path too long", sb.buf); - strbuf_release(&sb); - } - continue; - } + size_t base_len; if (is_dot_or_dotdot(de->d_name)) continue; - strcpy(path + len, de->d_name); + strbuf_setlen(&path, dirnamelen); + strbuf_addstr(&path, de->d_name); - if (has_extension(de->d_name, ".idx")) { + base_len = path.len; + if (strip_suffix_mem(path.buf, &base_len, ".idx")) { /* Don't reopen a pack we already have. */ for (p = packed_git; p; p = p->next) { - if (!memcmp(path, p->pack_name, len + namelen - 4)) + size_t len; + if (strip_suffix(p->pack_name, ".pack", &len) && + len == base_len && + !memcmp(p->pack_name, path.buf, len)) break; } if (p == NULL && @@ -1227,24 +1223,25 @@ static void prepare_packed_git_one(char *objdir, int local) * See if it really is a valid .idx file with * corresponding .pack file that we can map. */ - (p = add_packed_git(path, len + namelen, local)) != NULL) + (p = add_packed_git(path.buf, path.len, local)) != NULL) install_packed_git(p); } if (!report_garbage) continue; - if (has_extension(de->d_name, ".idx") || - has_extension(de->d_name, ".pack") || - has_extension(de->d_name, ".bitmap") || - has_extension(de->d_name, ".keep")) - string_list_append(&garbage, path); + if (ends_with(de->d_name, ".idx") || + ends_with(de->d_name, ".pack") || + ends_with(de->d_name, ".bitmap") || + ends_with(de->d_name, ".keep")) + string_list_append(&garbage, path.buf); else - report_garbage("garbage found", path); + report_garbage("garbage found", path.buf); } closedir(dir); report_pack_garbage(&garbage); string_list_clear(&garbage, 0); + strbuf_release(&path); } static int sort_pack(const void *a_, const void *b_) @@ -2086,27 +2083,9 @@ static void *read_object(const unsigned char *sha1, enum object_type *type, static void write_pack_access_log(struct packed_git *p, off_t obj_offset) { - static FILE *log_file; - - if (!log_pack_access) - log_pack_access = getenv("GIT_TRACE_PACK_ACCESS"); - if (!log_pack_access) - log_pack_access = no_log_pack_access; - if (log_pack_access == no_log_pack_access) - return; - - if (!log_file) { - log_file = fopen(log_pack_access, "w"); - if (!log_file) { - error("cannot open pack access log '%s' for writing: %s", - log_pack_access, strerror(errno)); - log_pack_access = no_log_pack_access; - return; - } - } - fprintf(log_file, "%s %"PRIuMAX"\n", - p->pack_name, (uintmax_t)obj_offset); - fflush(log_file); + static struct trace_key pack_access = TRACE_KEY_INIT(PACK_ACCESS); + trace_printf_key(&pack_access, "%s %"PRIuMAX"\n", + p->pack_name, (uintmax_t)obj_offset); } int do_check_packed_object_crc; @@ -2131,8 +2110,7 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, int delta_stack_nr = 0, delta_stack_alloc = UNPACK_ENTRY_STACK_PREALLOC; int base_from_cache = 0; - if (log_pack_access != no_log_pack_access) - write_pack_access_log(p, obj_offset); + write_pack_access_log(p, obj_offset); /* PHASE 1: drill down to the innermost base object */ for (;;) { diff --git a/sha1_name.c b/sha1_name.c index c2c938c4..63ee66fe 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -540,8 +540,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) char *tmp = xstrndup(str + at + 2, reflog_len); at_time = approxidate_careful(tmp, &errors); free(tmp); - if (errors) + if (errors) { + free(real_ref); return -1; + } } if (read_ref_at(real_ref, at_time, nth, sha1, NULL, &co_time, &co_tz, &co_cnt)) { @@ -901,10 +903,8 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, const char *match = NULL, *target = NULL; size_t len; - if (starts_with(message, "checkout: moving from ")) { - match = message + strlen("checkout: moving from "); + if (skip_prefix(message, "checkout: moving from ", &match)) target = strstr(match, " to "); - } if (!match || !target) return 0; @@ -948,7 +948,7 @@ static int interpret_nth_prior_checkout(const char *name, int namelen, retval = 0; if (0 < for_each_reflog_ent_reverse("HEAD", grab_nth_branch_switch, &cb)) { strbuf_reset(buf); - strbuf_add(buf, cb.buf.buf, cb.buf.len); + strbuf_addbuf(buf, &cb.buf); retval = brace - name + 1; } @@ -1242,10 +1242,7 @@ static void diagnose_invalid_sha1_path(const char *prefix, die("Path '%s' exists on disk, but not in '%.*s'.", filename, object_name_len, object_name); if (errno == ENOENT || errno == ENOTDIR) { - char *fullname = xmalloc(strlen(filename) - + strlen(prefix) + 1); - strcpy(fullname, prefix); - strcat(fullname, filename); + char *fullname = xstrfmt("%s%s", prefix, filename); if (!get_tree_entry(tree_sha1, fullname, sha1, &mode)) { @@ -325,7 +325,7 @@ void prune_shallow(int show_only) strbuf_release(&sb); } -#define TRACE_KEY "GIT_TRACE_SHALLOW" +struct trace_key trace_shallow = TRACE_KEY_INIT(SHALLOW); /* * Step 1, split sender shallow commits into "ours" and "theirs" @@ -334,7 +334,7 @@ void prune_shallow(int show_only) void prepare_shallow_info(struct shallow_info *info, struct sha1_array *sa) { int i; - trace_printf_key(TRACE_KEY, "shallow: prepare_shallow_info\n"); + trace_printf_key(&trace_shallow, "shallow: prepare_shallow_info\n"); memset(info, 0, sizeof(*info)); info->shallow = sa; if (!sa) @@ -365,7 +365,7 @@ void remove_nonexistent_theirs_shallow(struct shallow_info *info) { unsigned char (*sha1)[20] = info->shallow->sha1; int i, dst; - trace_printf_key(TRACE_KEY, "shallow: remove_nonexistent_theirs_shallow\n"); + trace_printf_key(&trace_shallow, "shallow: remove_nonexistent_theirs_shallow\n"); for (i = dst = 0; i < info->nr_theirs; i++) { if (i != dst) info->theirs[dst] = info->theirs[i]; @@ -516,7 +516,7 @@ void assign_shallow_commits_to_refs(struct shallow_info *info, int *shallow, nr_shallow = 0; struct paint_info pi; - trace_printf_key(TRACE_KEY, "shallow: assign_shallow_commits_to_refs\n"); + trace_printf_key(&trace_shallow, "shallow: assign_shallow_commits_to_refs\n"); shallow = xmalloc(sizeof(*shallow) * (info->nr_ours + info->nr_theirs)); for (i = 0; i < info->nr_ours; i++) shallow[nr_shallow++] = info->ours[i]; @@ -622,7 +622,7 @@ static void post_assign_shallow(struct shallow_info *info, int bitmap_nr = (info->ref->nr + 31) / 32; struct commit_array ca; - trace_printf_key(TRACE_KEY, "shallow: post_assign_shallow\n"); + trace_printf_key(&trace_shallow, "shallow: post_assign_shallow\n"); if (ref_status) memset(ref_status, 0, sizeof(*ref_status) * info->ref->nr); @@ -46,11 +46,7 @@ static int is_valid_cmd_name(const char *cmd) static char *make_cmd(const char *prog) { - char *prefix = xmalloc((strlen(prog) + strlen(COMMAND_DIR) + 2)); - strcpy(prefix, COMMAND_DIR); - strcat(prefix, "/"); - strcat(prefix, prog); - return prefix; + return xstrfmt("%s/%s", COMMAND_DIR, prog); } static void cd_to_homedir(void) diff --git a/split-index.c b/split-index.c new file mode 100644 index 00000000..21485e20 --- /dev/null +++ b/split-index.c @@ -0,0 +1,328 @@ +#include "cache.h" +#include "split-index.h" +#include "ewah/ewok.h" + +struct split_index *init_split_index(struct index_state *istate) +{ + if (!istate->split_index) { + istate->split_index = xcalloc(1, sizeof(*istate->split_index)); + istate->split_index->refcount = 1; + } + return istate->split_index; +} + +int read_link_extension(struct index_state *istate, + const void *data_, unsigned long sz) +{ + const unsigned char *data = data_; + struct split_index *si; + int ret; + + if (sz < 20) + return error("corrupt link extension (too short)"); + si = init_split_index(istate); + hashcpy(si->base_sha1, data); + data += 20; + sz -= 20; + if (!sz) + return 0; + si->delete_bitmap = ewah_new(); + ret = ewah_read_mmap(si->delete_bitmap, data, sz); + if (ret < 0) + return error("corrupt delete bitmap in link extension"); + data += ret; + sz -= ret; + si->replace_bitmap = ewah_new(); + ret = ewah_read_mmap(si->replace_bitmap, data, sz); + if (ret < 0) + return error("corrupt replace bitmap in link extension"); + if (ret != sz) + return error("garbage at the end of link extension"); + return 0; +} + +static int write_strbuf(void *user_data, const void *data, size_t len) +{ + struct strbuf *sb = user_data; + strbuf_add(sb, data, len); + return len; +} + +int write_link_extension(struct strbuf *sb, + struct index_state *istate) +{ + struct split_index *si = istate->split_index; + strbuf_add(sb, si->base_sha1, 20); + if (!si->delete_bitmap && !si->replace_bitmap) + return 0; + ewah_serialize_to(si->delete_bitmap, write_strbuf, sb); + ewah_serialize_to(si->replace_bitmap, write_strbuf, sb); + return 0; +} + +static void mark_base_index_entries(struct index_state *base) +{ + int i; + /* + * To keep track of the shared entries between + * istate->base->cache[] and istate->cache[], base entry + * position is stored in each base entry. All positions start + * from 1 instead of 0, which is resrved to say "this is a new + * entry". + */ + for (i = 0; i < base->cache_nr; i++) + base->cache[i]->index = i + 1; +} + +void move_cache_to_base_index(struct index_state *istate) +{ + struct split_index *si = istate->split_index; + int i; + + /* + * do not delete old si->base, its index entries may be shared + * with istate->cache[]. Accept a bit of leaking here because + * this code is only used by short-lived update-index. + */ + si->base = xcalloc(1, sizeof(*si->base)); + si->base->version = istate->version; + /* zero timestamp disables racy test in ce_write_index() */ + si->base->timestamp = istate->timestamp; + ALLOC_GROW(si->base->cache, istate->cache_nr, si->base->cache_alloc); + si->base->cache_nr = istate->cache_nr; + memcpy(si->base->cache, istate->cache, + sizeof(*istate->cache) * istate->cache_nr); + mark_base_index_entries(si->base); + for (i = 0; i < si->base->cache_nr; i++) + si->base->cache[i]->ce_flags &= ~CE_UPDATE_IN_BASE; +} + +static void mark_entry_for_delete(size_t pos, void *data) +{ + struct index_state *istate = data; + if (pos >= istate->cache_nr) + die("position for delete %d exceeds base index size %d", + (int)pos, istate->cache_nr); + istate->cache[pos]->ce_flags |= CE_REMOVE; + istate->split_index->nr_deletions = 1; +} + +static void replace_entry(size_t pos, void *data) +{ + struct index_state *istate = data; + struct split_index *si = istate->split_index; + struct cache_entry *dst, *src; + + if (pos >= istate->cache_nr) + die("position for replacement %d exceeds base index size %d", + (int)pos, istate->cache_nr); + if (si->nr_replacements >= si->saved_cache_nr) + die("too many replacements (%d vs %d)", + si->nr_replacements, si->saved_cache_nr); + dst = istate->cache[pos]; + if (dst->ce_flags & CE_REMOVE) + die("entry %d is marked as both replaced and deleted", + (int)pos); + src = si->saved_cache[si->nr_replacements]; + if (ce_namelen(src)) + die("corrupt link extension, entry %d should have " + "zero length name", (int)pos); + src->index = pos + 1; + src->ce_flags |= CE_UPDATE_IN_BASE; + src->ce_namelen = dst->ce_namelen; + copy_cache_entry(dst, src); + free(src); + si->nr_replacements++; +} + +void merge_base_index(struct index_state *istate) +{ + struct split_index *si = istate->split_index; + unsigned int i; + + mark_base_index_entries(si->base); + + si->saved_cache = istate->cache; + si->saved_cache_nr = istate->cache_nr; + istate->cache_nr = si->base->cache_nr; + istate->cache = NULL; + istate->cache_alloc = 0; + ALLOC_GROW(istate->cache, istate->cache_nr, istate->cache_alloc); + memcpy(istate->cache, si->base->cache, + sizeof(*istate->cache) * istate->cache_nr); + + si->nr_deletions = 0; + si->nr_replacements = 0; + ewah_each_bit(si->replace_bitmap, replace_entry, istate); + ewah_each_bit(si->delete_bitmap, mark_entry_for_delete, istate); + if (si->nr_deletions) + remove_marked_cache_entries(istate); + + for (i = si->nr_replacements; i < si->saved_cache_nr; i++) { + if (!ce_namelen(si->saved_cache[i])) + die("corrupt link extension, entry %d should " + "have non-zero length name", i); + add_index_entry(istate, si->saved_cache[i], + ADD_CACHE_OK_TO_ADD | + ADD_CACHE_KEEP_CACHE_TREE | + /* + * we may have to replay what + * merge-recursive.c:update_stages() + * does, which has this flag on + */ + ADD_CACHE_SKIP_DFCHECK); + si->saved_cache[i] = NULL; + } + + ewah_free(si->delete_bitmap); + ewah_free(si->replace_bitmap); + free(si->saved_cache); + si->delete_bitmap = NULL; + si->replace_bitmap = NULL; + si->saved_cache = NULL; + si->saved_cache_nr = 0; +} + +void prepare_to_write_split_index(struct index_state *istate) +{ + struct split_index *si = init_split_index(istate); + struct cache_entry **entries = NULL, *ce; + int i, nr_entries = 0, nr_alloc = 0; + + si->delete_bitmap = ewah_new(); + si->replace_bitmap = ewah_new(); + + if (si->base) { + /* Go through istate->cache[] and mark CE_MATCHED to + * entry with positive index. We'll go through + * base->cache[] later to delete all entries in base + * that are not marked eith either CE_MATCHED or + * CE_UPDATE_IN_BASE. If istate->cache[i] is a + * duplicate, deduplicate it. + */ + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *base; + /* namelen is checked separately */ + const unsigned int ondisk_flags = + CE_STAGEMASK | CE_VALID | CE_EXTENDED_FLAGS; + unsigned int ce_flags, base_flags, ret; + ce = istate->cache[i]; + if (!ce->index) + continue; + if (ce->index > si->base->cache_nr) { + ce->index = 0; + continue; + } + ce->ce_flags |= CE_MATCHED; /* or "shared" */ + base = si->base->cache[ce->index - 1]; + if (ce == base) + continue; + if (ce->ce_namelen != base->ce_namelen || + strcmp(ce->name, base->name)) { + ce->index = 0; + continue; + } + ce_flags = ce->ce_flags; + base_flags = base->ce_flags; + /* only on-disk flags matter */ + ce->ce_flags &= ondisk_flags; + base->ce_flags &= ondisk_flags; + ret = memcmp(&ce->ce_stat_data, &base->ce_stat_data, + offsetof(struct cache_entry, name) - + offsetof(struct cache_entry, ce_stat_data)); + ce->ce_flags = ce_flags; + base->ce_flags = base_flags; + if (ret) + ce->ce_flags |= CE_UPDATE_IN_BASE; + free(base); + si->base->cache[ce->index - 1] = ce; + } + for (i = 0; i < si->base->cache_nr; i++) { + ce = si->base->cache[i]; + if ((ce->ce_flags & CE_REMOVE) || + !(ce->ce_flags & CE_MATCHED)) + ewah_set(si->delete_bitmap, i); + else if (ce->ce_flags & CE_UPDATE_IN_BASE) { + ewah_set(si->replace_bitmap, i); + ce->ce_flags |= CE_STRIP_NAME; + ALLOC_GROW(entries, nr_entries+1, nr_alloc); + entries[nr_entries++] = ce; + } + } + } + + for (i = 0; i < istate->cache_nr; i++) { + ce = istate->cache[i]; + if ((!si->base || !ce->index) && !(ce->ce_flags & CE_REMOVE)) { + assert(!(ce->ce_flags & CE_STRIP_NAME)); + ALLOC_GROW(entries, nr_entries+1, nr_alloc); + entries[nr_entries++] = ce; + } + ce->ce_flags &= ~CE_MATCHED; + } + + /* + * take cache[] out temporarily, put entries[] in its place + * for writing + */ + si->saved_cache = istate->cache; + si->saved_cache_nr = istate->cache_nr; + istate->cache = entries; + istate->cache_nr = nr_entries; +} + +void finish_writing_split_index(struct index_state *istate) +{ + struct split_index *si = init_split_index(istate); + + ewah_free(si->delete_bitmap); + ewah_free(si->replace_bitmap); + si->delete_bitmap = NULL; + si->replace_bitmap = NULL; + free(istate->cache); + istate->cache = si->saved_cache; + istate->cache_nr = si->saved_cache_nr; +} + +void discard_split_index(struct index_state *istate) +{ + struct split_index *si = istate->split_index; + if (!si) + return; + istate->split_index = NULL; + si->refcount--; + if (si->refcount) + return; + if (si->base) { + discard_index(si->base); + free(si->base); + } + free(si); +} + +void save_or_free_index_entry(struct index_state *istate, struct cache_entry *ce) +{ + if (ce->index && + istate->split_index && + istate->split_index->base && + ce->index <= istate->split_index->base->cache_nr && + ce == istate->split_index->base->cache[ce->index - 1]) + ce->ce_flags |= CE_REMOVE; + else + free(ce); +} + +void replace_index_entry_in_base(struct index_state *istate, + struct cache_entry *old, + struct cache_entry *new) +{ + if (old->index && + istate->split_index && + istate->split_index->base && + old->index <= istate->split_index->base->cache_nr) { + new->index = old->index; + if (old != istate->split_index->base->cache[new->index - 1]) + free(istate->split_index->base->cache[new->index - 1]); + istate->split_index->base->cache[new->index - 1] = new; + } +} diff --git a/split-index.h b/split-index.h new file mode 100644 index 00000000..c1324f52 --- /dev/null +++ b/split-index.h @@ -0,0 +1,35 @@ +#ifndef SPLIT_INDEX_H +#define SPLIT_INDEX_H + +struct index_state; +struct strbuf; +struct ewah_bitmap; + +struct split_index { + unsigned char base_sha1[20]; + struct index_state *base; + struct ewah_bitmap *delete_bitmap; + struct ewah_bitmap *replace_bitmap; + struct cache_entry **saved_cache; + unsigned int saved_cache_nr; + unsigned int nr_deletions; + unsigned int nr_replacements; + int refcount; +}; + +struct split_index *init_split_index(struct index_state *istate); +void save_or_free_index_entry(struct index_state *istate, struct cache_entry *ce); +void replace_index_entry_in_base(struct index_state *istate, + struct cache_entry *old, + struct cache_entry *new); +int read_link_extension(struct index_state *istate, + const void *data, unsigned long sz); +int write_link_extension(struct strbuf *sb, + struct index_state *istate); +void move_cache_to_base_index(struct index_state *istate); +void merge_base_index(struct index_state *istate); +void prepare_to_write_split_index(struct index_state *istate); +void finish_writing_split_index(struct index_state *istate); +void discard_split_index(struct index_state *istate); + +#endif @@ -1,5 +1,6 @@ #include "cache.h" #include "refs.h" +#include "utf8.h" int starts_with(const char *str, const char *prefix) { @@ -10,15 +11,6 @@ int starts_with(const char *str, const char *prefix) return 0; } -int ends_with(const char *str, const char *suffix) -{ - int len = strlen(str), suflen = strlen(suffix); - if (len < suflen) - return 0; - else - return !strcmp(str + len - suflen, suffix); -} - /* * Used as the default ->buf value, so that people can always assume * buf is non NULL and ->buf is NUL terminated even for a freshly @@ -78,15 +70,8 @@ void strbuf_grow(struct strbuf *sb, size_t extra) void strbuf_trim(struct strbuf *sb) { - char *b = sb->buf; - while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1])) - sb->len--; - while (sb->len > 0 && isspace(*b)) { - b++; - sb->len--; - } - memmove(sb->buf, b, sb->len); - sb->buf[sb->len] = '\0'; + strbuf_rtrim(sb); + strbuf_ltrim(sb); } void strbuf_rtrim(struct strbuf *sb) { @@ -106,6 +91,29 @@ void strbuf_ltrim(struct strbuf *sb) sb->buf[sb->len] = '\0'; } +int strbuf_reencode(struct strbuf *sb, const char *from, const char *to) +{ + char *out; + int len; + + if (same_encoding(from, to)) + return 0; + + out = reencode_string_len(sb->buf, sb->len, to, from, &len); + if (!out) + return -1; + + strbuf_attach(sb, out, len, len); + return 0; +} + +void strbuf_tolower(struct strbuf *sb) +{ + char *p = sb->buf, *end = sb->buf + sb->len; + for (; p < end; p++) + *p = tolower(*p); +} + struct strbuf **strbuf_split_buf(const char *str, size_t slen, int terminator, int max) { @@ -570,3 +578,35 @@ int fprintf_ln(FILE *fp, const char *fmt, ...) return -1; return ret + 1; } + +char *xstrdup_tolower(const char *string) +{ + char *result; + size_t len, i; + + len = strlen(string); + result = xmalloc(len + 1); + for (i = 0; i < len; i++) + result[i] = tolower(string[i]); + result[i] = '\0'; + return result; +} + +char *xstrvfmt(const char *fmt, va_list ap) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_vaddf(&buf, fmt, ap); + return strbuf_detach(&buf, NULL); +} + +char *xstrfmt(const char *fmt, ...) +{ + va_list ap; + char *ret; + + va_start(ap, fmt); + ret = xstrvfmt(fmt, ap); + va_end(ap); + + return ret; +} @@ -45,8 +45,19 @@ static inline void strbuf_setlen(struct strbuf *sb, size_t len) extern void strbuf_trim(struct strbuf *); extern void strbuf_rtrim(struct strbuf *); extern void strbuf_ltrim(struct strbuf *); +extern int strbuf_reencode(struct strbuf *sb, const char *from, const char *to); +extern void strbuf_tolower(struct strbuf *sb); extern int strbuf_cmp(const struct strbuf *, const struct strbuf *); +static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix) +{ + if (strip_suffix_mem(sb->buf, &sb->len, suffix)) { + strbuf_setlen(sb, sb->len); + return 1; + } else + return 0; +} + /* * Split str (of length slen) at the specified terminator character. * Return a null-terminated array of pointers to strbuf objects @@ -183,4 +194,15 @@ extern int printf_ln(const char *fmt, ...); __attribute__((format (printf,2,3))) extern int fprintf_ln(FILE *fp, const char *fmt, ...); +char *xstrdup_tolower(const char *); + +/* + * Create a newly allocated string using printf format. You can do this easily + * with a strbuf, but this provides a shortcut to save a few lines. + */ +__attribute__((format (printf, 1, 0))) +char *xstrvfmt(const char *fmt, va_list ap); +__attribute__((format (printf, 1, 2))) +char *xstrfmt(const char *fmt, ...); + #endif /* STRBUF_H */ diff --git a/string-list.c b/string-list.c index aabb25ef..db38b62b 100644 --- a/string-list.c +++ b/string-list.c @@ -1,6 +1,12 @@ #include "cache.h" #include "string-list.h" +void string_list_init(struct string_list *list, int strdup_strings) +{ + memset(list, 0, sizeof(*list)); + list->strdup_strings = strdup_strings; +} + /* if there is no exact match, point to the index where the entry could be * inserted */ static int get_entry_index(const struct string_list *list, const char *string, diff --git a/string-list.h b/string-list.h index de6769c9..494eb5d9 100644 --- a/string-list.h +++ b/string-list.h @@ -15,8 +15,10 @@ struct string_list { compare_strings_fn cmp; /* NULL uses strcmp() */ }; -#define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0 } -#define STRING_LIST_INIT_DUP { NULL, 0, 0, 1 } +#define STRING_LIST_INIT_NODUP { NULL, 0, 0, 0, NULL } +#define STRING_LIST_INIT_DUP { NULL, 0, 0, 1, NULL } + +void string_list_init(struct string_list *list, int strdup_strings); void print_string_list(const struct string_list *p, const char *text); void string_list_clear(struct string_list *list, int free_util); diff --git a/submodule.c b/submodule.c index b80ecacf..c3a61e70 100644 --- a/submodule.c +++ b/submodule.c @@ -544,10 +544,7 @@ static int push_submodule(const char *path) int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name) { int i, ret = 1; - struct string_list needs_pushing; - - memset(&needs_pushing, 0, sizeof(struct string_list)); - needs_pushing.strdup_strings = 1; + struct string_list needs_pushing = STRING_LIST_INIT_DUP; if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing)) return 1; @@ -965,7 +962,7 @@ static int find_first_merges(struct object_array *result, const char *path, sha1_to_hex(a->object.sha1)); init_revisions(&revs, NULL); rev_opts.submodule = path; - setup_revisions(sizeof(rev_args)/sizeof(char *)-1, rev_args, &revs, &rev_opts); + setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts); /* save all revisions from the above list that contain b */ if (prepare_revision_walk(&revs)) @@ -35,12 +35,11 @@ static int longest_path_match(const char *name_a, int len_a, return match_len; } -static struct cache_def default_cache; +static struct cache_def default_cache = CACHE_DEF_INIT; static inline void reset_lstat_cache(struct cache_def *cache) { - cache->path[0] = '\0'; - cache->len = 0; + strbuf_reset(&cache->path); cache->flags = 0; /* * The track_flags and prefix_len_stat_func members is only @@ -73,7 +72,7 @@ static int lstat_cache_matchlen(struct cache_def *cache, int prefix_len_stat_func) { int match_len, last_slash, last_slash_dir, previous_slash; - int save_flags, max_len, ret; + int save_flags, ret; struct stat st; if (cache->track_flags != track_flags || @@ -93,14 +92,14 @@ static int lstat_cache_matchlen(struct cache_def *cache, * the 2 "excluding" path types. */ match_len = last_slash = - longest_path_match(name, len, cache->path, cache->len, - &previous_slash); + longest_path_match(name, len, cache->path.buf, + cache->path.len, &previous_slash); *ret_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK); if (!(track_flags & FL_FULLPATH) && match_len == len) match_len = last_slash = previous_slash; - if (*ret_flags && match_len == cache->len) + if (*ret_flags && match_len == cache->path.len) return match_len; /* * If we now have match_len > 0, we would know that @@ -121,21 +120,22 @@ static int lstat_cache_matchlen(struct cache_def *cache, */ *ret_flags = FL_DIR; last_slash_dir = last_slash; - max_len = len < PATH_MAX ? len : PATH_MAX; - while (match_len < max_len) { + if (len > cache->path.len) + strbuf_grow(&cache->path, len - cache->path.len); + while (match_len < len) { do { - cache->path[match_len] = name[match_len]; + cache->path.buf[match_len] = name[match_len]; match_len++; - } while (match_len < max_len && name[match_len] != '/'); - if (match_len >= max_len && !(track_flags & FL_FULLPATH)) + } while (match_len < len && name[match_len] != '/'); + if (match_len >= len && !(track_flags & FL_FULLPATH)) break; last_slash = match_len; - cache->path[last_slash] = '\0'; + cache->path.buf[last_slash] = '\0'; if (last_slash <= prefix_len_stat_func) - ret = stat(cache->path, &st); + ret = stat(cache->path.buf, &st); else - ret = lstat(cache->path, &st); + ret = lstat(cache->path.buf, &st); if (ret) { *ret_flags = FL_LSTATERR; @@ -158,12 +158,11 @@ static int lstat_cache_matchlen(struct cache_def *cache, * for the moment! */ save_flags = *ret_flags & track_flags & (FL_NOENT|FL_SYMLINK); - if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) { - cache->path[last_slash] = '\0'; - cache->len = last_slash; + if (save_flags && last_slash > 0) { + cache->path.buf[last_slash] = '\0'; + cache->path.len = last_slash; cache->flags = save_flags; - } else if ((track_flags & FL_DIR) && - last_slash_dir > 0 && last_slash_dir <= PATH_MAX) { + } else if ((track_flags & FL_DIR) && last_slash_dir > 0) { /* * We have a separate test for the directory case, * since it could be that we have found a symlink or a @@ -175,8 +174,8 @@ static int lstat_cache_matchlen(struct cache_def *cache, * can still cache the path components before the last * one (the found symlink or non-existing component). */ - cache->path[last_slash_dir] = '\0'; - cache->len = last_slash_dir; + cache->path.buf[last_slash_dir] = '\0'; + cache->path.len = last_slash_dir; cache->flags = FL_DIR; } else { reset_lstat_cache(cache); @@ -273,21 +272,18 @@ static int threaded_has_dirs_only_path(struct cache_def *cache, const char *name FL_DIR; } -static struct removal_def { - char path[PATH_MAX]; - int len; -} removal; +static struct strbuf removal = STRBUF_INIT; static void do_remove_scheduled_dirs(int new_len) { while (removal.len > new_len) { - removal.path[removal.len] = '\0'; - if (rmdir(removal.path)) + removal.buf[removal.len] = '\0'; + if (rmdir(removal.buf)) break; do { removal.len--; } while (removal.len > new_len && - removal.path[removal.len] != '/'); + removal.buf[removal.len] != '/'); } removal.len = new_len; } @@ -297,7 +293,7 @@ void schedule_dir_for_removal(const char *name, int len) int match_len, last_slash, i, previous_slash; match_len = last_slash = i = - longest_path_match(name, len, removal.path, removal.len, + longest_path_match(name, len, removal.buf, removal.len, &previous_slash); /* Find last slash inside 'name' */ while (i < len) { @@ -317,11 +313,8 @@ void schedule_dir_for_removal(const char *name, int len) * If we go deeper down the directory tree, we only need to * save the new path components as we go down. */ - if (match_len < last_slash) { - memcpy(&removal.path[match_len], &name[match_len], - last_slash - match_len); - removal.len = last_slash; - } + if (match_len < last_slash) + strbuf_add(&removal, &name[match_len], last_slash - match_len); } void remove_scheduled_dirs(void) @@ -13,7 +13,7 @@ TAR ?= $(TAR) RM ?= rm -f PROVE ?= prove DEFAULT_TEST_TARGET ?= test -TEST_LINT ?= test-lint-duplicates test-lint-executable +TEST_LINT ?= test-lint ifdef TEST_OUTPUT_DIRECTORY TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results @@ -29,6 +29,7 @@ TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh)) TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh)) +THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh))) all: $(DEFAULT_TEST_TARGET) @@ -65,7 +66,7 @@ test-lint-executable: echo >&2 "non-executable tests:" $$bad; exit 1; } test-lint-shell-syntax: - @'$(PERL_PATH_SQ)' check-non-portable-shell.pl $(T) + @'$(PERL_PATH_SQ)' check-non-portable-shell.pl $(T) $(THELPERS) aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results @@ -71,6 +71,7 @@ You can pass --verbose (or -v), --debug (or -d), and --immediate (or -i) command line argument to the test, or by setting GIT_TEST_OPTS appropriately before running "make". +-v:: --verbose:: This makes the test more verbose. Specifically, the command being run and their output if any are also @@ -81,6 +82,7 @@ appropriately before running "make". numbers matching <pattern>. The number matched against is simply the running count of the test within the file. +-d:: --debug:: This may help the person who is developing a new test. It causes the command defined with test_debug to run. @@ -89,6 +91,7 @@ appropriately before running "make". failed tests so that you can inspect its contents after the test finished. +-i:: --immediate:: This causes the test to immediately exit upon the first failed test. Cleanup commands requested with @@ -96,10 +99,17 @@ appropriately before running "make". in order to keep the state for inspection by the tester to diagnose the bug. +-l:: --long-tests:: This causes additional long-running tests to be run (where available), for more exhaustive testing. +-r:: +--run=<test-selector>:: + Run only the subset of tests indicated by + <test-selector>. See section "Skipping Tests" below for + <test-selector> syntax. + --valgrind=<tool>:: Execute all Git binaries under valgrind tool <tool> and exit with status 126 on errors (just like regular tests, this will @@ -187,10 +197,77 @@ and either can match the "t[0-9]{4}" part to skip the whole test, or t[0-9]{4} followed by ".$number" to say which particular test to skip. -Note that some tests in the existing test suite rely on previous -test item, so you cannot arbitrarily disable one and expect the -remainder of test to check what the test originally was intended -to check. +For an individual test suite --run could be used to specify that +only some tests should be run or that some tests should be +excluded from a run. + +The argument for --run is a list of individual test numbers or +ranges with an optional negation prefix that define what tests in +a test suite to include in the run. A range is two numbers +separated with a dash and matches a range of tests with both ends +been included. You may omit the first or the second number to +mean "from the first test" or "up to the very last test" +respectively. + +Optional prefix of '!' means that the test or a range of tests +should be excluded from the run. + +If --run starts with an unprefixed number or range the initial +set of tests to run is empty. If the first item starts with '!' +all the tests are added to the initial set. After initial set is +determined every test number or range is added or excluded from +the set one by one, from left to right. + +Individual numbers or ranges could be separated either by a space +or a comma. + +For example, to run only tests up to a specific test (21), one +could do this: + + $ sh ./t9200-git-cvsexport-commit.sh --run='1-21' + +or this: + + $ sh ./t9200-git-cvsexport-commit.sh --run='-21' + +Common case is to run several setup tests (1, 2, 3) and then a +specific test (21) that relies on that setup: + + $ sh ./t9200-git-cvsexport-commit.sh --run='1 2 3 21' + +or: + + $ sh ./t9200-git-cvsexport-commit.sh --run=1,2,3,21 + +or: + + $ sh ./t9200-git-cvsexport-commit.sh --run='-3 21' + +As noted above, the test set is built going though items left to +right, so this: + + $ sh ./t9200-git-cvsexport-commit.sh --run='1-4 !3' + +will run tests 1, 2, and 4. Items that comes later have higher +precendence. It means that this: + + $ sh ./t9200-git-cvsexport-commit.sh --run='!3 1-4' + +would just run tests from 1 to 4, including 3. + +You may use negation with ranges. The following will run all +test in the test suite except from 7 up to 11: + + $ sh ./t9200-git-cvsexport-commit.sh --run='!7-11' + +Some tests in a test suite rely on the previous tests performing +certain actions, specifically some tests are designated as +"setup" test, so you cannot _arbitrarily_ disable one test and +expect the rest to function correctly. + +--run is mostly useful when you want to focus on a specific test +and know what setup is needed for it. Or when you want to run +everything up to a certain test. Naming Tests @@ -596,6 +673,27 @@ library for your script to use. ... ' + - test_write_lines <lines> + + Write <lines> on standard output, one line per argument. + Useful to prepare multi-line files in a compact form. + + Example: + + test_write_lines a b c d e f g >foo + + Is a more compact equivalent of: + cat >foo <<-EOF + a + b + c + d + e + f + g + EOF + + - test_pause This command is useful for writing and debugging tests and must be diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index 304c7b7d..071e4d7d 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -1,6 +1,17 @@ # This file isn't used as a test script directly, instead it is # sourced from t8001-annotate.sh and t8002-blame.sh. +if test_have_prereq MINGW +then + sanitize_L () { + echo "$1" | sed 'sX\(^-L\|,\)\^\?/X&\\;*Xg' + } +else + sanitize_L () { + echo "$1" + } +fi + check_count () { head= && file='file' && @@ -10,6 +21,7 @@ check_count () { case "$1" in -h) head="$2"; shift; shift ;; -f) file="$2"; shift; shift ;; + -L*) options="$options $(sanitize_L "$1")"; shift ;; -*) options="$options $1"; shift ;; *) break ;; esac diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index 45971f43..b170cbc0 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -16,12 +16,12 @@ sub err { while (<>) { chomp; - /^\s*sed\s+-i/ and err 'sed -i is not portable'; - /^\s*echo\s+-n/ and err 'echo -n is not portable (please use printf)'; + /\bsed\s+-i/ and err 'sed -i is not portable'; + /\becho\s+-n/ and err 'echo -n is not portable (please use printf)'; /^\s*declare\s+/ and err 'arrays/declare not portable'; /^\s*[^#]\s*which\s/ and err 'which is not portable (please use type)'; - /test\s+[^=]*==/ and err '"test a == b" is not portable (please use =)'; - /^\s*export\s+[^=]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)'; + /\btest\s+[^=]*==/ and err '"test a == b" is not portable (please use =)'; + /\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)'; # this resets our $. for each file close ARGV if eof; } diff --git a/t/lib-credential.sh b/t/lib-credential.sh index 957ae936..9e7d7962 100755 --- a/t/lib-credential.sh +++ b/t/lib-credential.sh @@ -281,7 +281,7 @@ helper_test_timeout() { cat >askpass <<\EOF #!/bin/sh echo >&2 askpass: $* -what=`echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z` +what=$(echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z) echo "askpass-$what" EOF chmod +x askpass diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh index 50767189..9b2bcfb1 100644 --- a/t/lib-cvs.sh +++ b/t/lib-cvs.sh @@ -13,7 +13,7 @@ fi CVS="cvs -f" export CVS -cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'` +cvsps_version=$(cvsps -h 2>&1 | sed -ne 's/cvsps version //p') case "$cvsps_version" in 2.1 | 2.2*) ;; diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 05824fa8..fd499e7c 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -1,6 +1,6 @@ #!/bin/sh -gpg_version=`gpg --version 2>&1` +gpg_version=$(gpg --version 2>&1) if test $? = 127; then say "You do not seem to have gpg installed" else diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 252cbf16..fd53b571 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -37,6 +37,11 @@ then test_done fi +if ! test_have_prereq SANITY; then + test_skip_or_die $GIT_TEST_HTTPD \ + "Cannot run httpd tests as root" +fi + HTTPD_PARA="" for DEFAULT_HTTPD_PATH in '/usr/sbin/httpd' '/usr/sbin/apache2' @@ -105,10 +110,15 @@ else "Could not identify web server at '$LIB_HTTPD_PATH'" fi +install_script () { + write_script "$HTTPD_ROOT_PATH/$1" <"$TEST_PATH/$1" +} + prepare_httpd() { mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH" cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH" - cp "$TEST_PATH"/broken-smart-http.sh "$HTTPD_ROOT_PATH" + install_script broken-smart-http.sh + install_script error.sh ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" @@ -132,7 +142,7 @@ prepare_httpd() { HTTPD_URL_USER=$HTTPD_PROTO://user%40host@$HTTPD_DEST HTTPD_URL_USER_PASS=$HTTPD_PROTO://user%40host:pass%40host@$HTTPD_DEST - if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN" + if test -n "$LIB_HTTPD_DAV" || test -n "$LIB_HTTPD_SVN" then HTTPD_PARA="$HTTPD_PARA -DDAV" diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 3a03e826..b384d799 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -97,12 +97,16 @@ Alias /auth/dumb/ www/auth/dumb/ </LocationMatch> ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ +ScriptAlias /error/ error.sh/ <Directory ${GIT_EXEC_PATH}> Options FollowSymlinks </Directory> <Files broken-smart-http.sh> Options ExecCGI </Files> +<Files error.sh> + Options ExecCGI +</Files> <Files ${GIT_EXEC_PATH}/git-http-backend> Options ExecCGI </Files> diff --git a/t/lib-httpd/broken-smart-http.sh b/t/lib-httpd/broken-smart-http.sh index f7ebfffa..82cc610b 100755..100644 --- a/t/lib-httpd/broken-smart-http.sh +++ b/t/lib-httpd/broken-smart-http.sh @@ -1,4 +1,3 @@ -#!/bin/sh printf "Content-Type: text/%s\n" "html" echo printf "%s\n" "001e# service=git-upload-pack" diff --git a/t/lib-httpd/error.sh b/t/lib-httpd/error.sh new file mode 100755 index 00000000..a77b8e54 --- /dev/null +++ b/t/lib-httpd/error.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +printf "Status: 500 Intentional Breakage\n" + +printf "Content-Type: " +charset=iso-8859-1 +case "$PATH_INFO" in +*html*) + printf "text/html" + ;; +*text*) + printf "text/plain" + ;; +*charset*) + printf "text/plain; charset=utf-8" + charset=utf-8 + ;; +*utf16*) + printf "text/plain; charset=utf-16" + charset=utf-16 + ;; +*odd-spacing*) + printf "text/plain; foo=bar ;charset=utf-16; other=nonsense" + charset=utf-16 + ;; +esac +printf "\n" + +printf "\n" +printf "this is the error message\n" | +iconv -f us-ascii -t $charset diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh new file mode 100755 index 00000000..79cdd34a --- /dev/null +++ b/t/lib-submodule-update.sh @@ -0,0 +1,680 @@ +# Create a submodule layout used for all tests below. +# +# The following use cases are covered: +# - New submodule (no_submodule => add_sub1) +# - Removed submodule (add_sub1 => remove_sub1) +# - Updated submodule (add_sub1 => modify_sub1) +# - Submodule updated to invalid commit (add_sub1 => invalid_sub1) +# - Submodule updated from invalid commit (invalid_sub1 => valid_sub1) +# - Submodule replaced by tracked files in directory (add_sub1 => +# replace_sub1_with_directory) +# - Directory containing tracked files replaced by submodule +# (replace_sub1_with_directory => replace_directory_with_sub1) +# - Submodule replaced by tracked file with the same name (add_sub1 => +# replace_sub1_with_file) +# - Tracked file replaced by submodule (replace_sub1_with_file => +# replace_file_with_sub1) +# +# --O-----O +# / ^ replace_directory_with_sub1 +# / replace_sub1_with_directory +# /----O +# / ^ +# / modify_sub1 +# O------O-------O +# ^ ^\ ^ +# | | \ remove_sub1 +# | | -----O-----O +# | | \ ^ replace_file_with_sub1 +# | | \ replace_sub1_with_file +# | add_sub1 --O-----O +# no_submodule ^ valid_sub1 +# invalid_sub1 +# +create_lib_submodule_repo () { + git init submodule_update_repo && + ( + cd submodule_update_repo && + echo "expect" >>.gitignore && + echo "actual" >>.gitignore && + echo "x" >file1 && + echo "y" >file2 && + git add .gitignore file1 file2 && + git commit -m "Base" && + git branch "no_submodule" && + + git checkout -b "add_sub1" && + git submodule add ./. sub1 && + git config -f .gitmodules submodule.sub1.ignore all && + git config submodule.sub1.ignore all && + git add .gitmodules && + git commit -m "Add sub1" && + git checkout -b remove_sub1 && + git revert HEAD && + + git checkout -b "modify_sub1" "add_sub1" && + git submodule update && + ( + cd sub1 && + git fetch && + git checkout -b "modifications" && + echo "z" >file2 && + echo "x" >file3 && + git add file2 file3 && + git commit -m "modified file2 and added file3" && + git push origin modifications + ) && + git add sub1 && + git commit -m "Modify sub1" && + + git checkout -b "replace_sub1_with_directory" "add_sub1" && + git submodule update && + ( + cd sub1 && + git checkout modifications + ) && + git rm --cached sub1 && + rm sub1/.git* && + git config -f .gitmodules --remove-section "submodule.sub1" && + git add .gitmodules sub1/* && + git commit -m "Replace sub1 with directory" && + git checkout -b replace_directory_with_sub1 && + git revert HEAD && + + git checkout -b "replace_sub1_with_file" "add_sub1" && + git rm sub1 && + echo "content" >sub1 && + git add sub1 && + git commit -m "Replace sub1 with file" && + git checkout -b replace_file_with_sub1 && + git revert HEAD && + + git checkout -b "invalid_sub1" "add_sub1" && + git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 sub1 && + git commit -m "Invalid sub1 commit" && + git checkout -b valid_sub1 && + git revert HEAD && + git checkout master + ) +} + +# Helper function to replace gitfile with .git directory +replace_gitfile_with_git_dir () { + ( + cd "$1" && + git_dir="$(git rev-parse --git-dir)" && + rm -f .git && + cp -R "$git_dir" .git && + GIT_WORK_TREE=. git config --unset core.worktree + ) +} + +# Test that the .git directory in the submodule is unchanged (except for the +# core.worktree setting, which appears only in $GIT_DIR/modules/$1/config). +# Call this function before test_submodule_content as the latter might +# write the index file leading to false positive index differences. +# +# Note that this only supports submodules at the root level of the +# superproject, with the default name, i.e. same as its path. +test_git_directory_is_unchanged () { + ( + cd ".git/modules/$1" && + # does core.worktree point at the right place? + test "$(git config core.worktree)" = "../../../$1" && + # remove it temporarily before comparing, as + # "$1/.git/config" lacks it... + git config --unset core.worktree + ) && + diff -r ".git/modules/$1" "$1/.git" && + ( + # ... and then restore. + cd ".git/modules/$1" && + git config core.worktree "../../../$1" + ) +} + +# Helper function to be executed at the start of every test below, it sets up +# the submodule repo if it doesn't exist and configures the most problematic +# settings for diff.ignoreSubmodules. +prolog () { + (test -d submodule_update_repo || create_lib_submodule_repo) && + test_config_global diff.ignoreSubmodules all && + test_config diff.ignoreSubmodules all +} + +# Helper function to bring work tree back into the state given by the +# commit. This includes trying to populate sub1 accordingly if it exists and +# should be updated to an existing commit. +reset_work_tree_to () { + rm -rf submodule_update && + git clone submodule_update_repo submodule_update && + ( + cd submodule_update && + rm -rf sub1 && + git checkout -f "$1" && + git status -u -s >actual && + test_must_be_empty actual && + sha1=$(git rev-parse --revs-only HEAD:sub1) && + if test -n "$sha1" && + test $(cd "sub1" && git rev-parse --verify "$sha1^{commit}") + then + git submodule update --init --recursive "sub1" + fi + ) +} + +# Test that the superproject contains the content according to commit "$1" +# (the work tree must match the index for everything but submodules but the +# index must exactly match the given commit including any submodule SHA-1s). +test_superproject_content () { + git diff-index --cached "$1" >actual && + test_must_be_empty actual && + git diff-files --ignore-submodules >actual && + test_must_be_empty actual +} + +# Test that the given submodule at path "$1" contains the content according +# to the submodule commit recorded in the superproject's commit "$2" +test_submodule_content () { + if test $# != 2 + then + echo "test_submodule_content needs two arguments" + return 1 + fi && + submodule="$1" && + commit="$2" && + test -d "$submodule"/ && + if ! test -f "$submodule"/.git && ! test -d "$submodule"/.git + then + echo "Submodule $submodule is not populated" + return 1 + fi && + sha1=$(git rev-parse --verify "$commit:$submodule") && + if test -z "$sha1" + then + echo "Couldn't retrieve SHA-1 of $submodule for $commit" + return 1 + fi && + ( + cd "$submodule" && + git status -u -s >actual && + test_must_be_empty actual && + git diff "$sha1" >actual && + test_must_be_empty actual + ) +} + +# Test that the following transitions are correctly handled: +# - Updated submodule +# - New submodule +# - Removed submodule +# - Directory containing tracked files replaced by submodule +# - Submodule replaced by tracked files in directory +# - Submodule replaced by tracked file with the same name +# - tracked file replaced by submodule +# +# The default is that submodule contents aren't changed until "git submodule +# update" is run. And even then that command doesn't delete the work tree of +# a removed submodule. +# +# Removing a submodule containing a .git directory must fail even when forced +# to protect the history! +# + +# Test that submodule contents are currently not updated when switching +# between commits that change a submodule. +test_submodule_switch () { + command="$1" + ######################### Appearing submodule ######################### + # Switching to a commit letting a submodule appear creates empty dir ... + if test "$KNOWN_FAILURE_STASH_DOES_IGNORE_SUBMODULE_CHANGES" = 1 + then + # Restoring stash fails to restore submodule index entry + RESULT="failure" + else + RESULT="success" + fi + test_expect_$RESULT "$command: added submodule creates empty directory" ' + prolog && + reset_work_tree_to no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... and doesn't care if it already exists ... + test_expect_$RESULT "$command: added submodule leaves existing empty directory alone" ' + prolog && + reset_work_tree_to no_submodule && + ( + cd submodule_update && + mkdir sub1 && + git branch -t add_sub1 origin/add_sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... unless there is an untracked file in its place. + test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" ' + prolog && + reset_work_tree_to no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + >sub1 && + test_must_fail $command add_sub1 && + test_superproject_content origin/no_submodule && + test_must_be_empty sub1 + ) + ' + # Replacing a tracked file with a submodule produces an empty + # directory ... + test_expect_$RESULT "$command: replace tracked file with submodule creates empty directory" ' + prolog && + reset_work_tree_to replace_sub1_with_file && + ( + cd submodule_update && + git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 && + $command replace_file_with_sub1 && + test_superproject_content origin/replace_file_with_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/replace_file_with_sub1 + ) + ' + # ... as does removing a directory with tracked files with a + # submodule. + if test "$KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR" = 1 + then + # Non fast-forward merges fail with "Directory sub1 doesn't + # exist. sub1" because the empty submodule directory is not + # created + RESULT="failure" + else + RESULT="success" + fi + test_expect_$RESULT "$command: replace directory with submodule" ' + prolog && + reset_work_tree_to replace_sub1_with_directory && + ( + cd submodule_update && + git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 && + $command replace_directory_with_sub1 && + test_superproject_content origin/replace_directory_with_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/replace_directory_with_sub1 + ) + ' + + ######################## Disappearing submodule ####################### + # Removing a submodule doesn't remove its work tree ... + if test "$KNOWN_FAILURE_STASH_DOES_IGNORE_SUBMODULE_CHANGES" = 1 + then + RESULT="failure" + else + RESULT="success" + fi + test_expect_$RESULT "$command: removed submodule leaves submodule directory and its contents in place" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t remove_sub1 origin/remove_sub1 && + $command remove_sub1 && + test_superproject_content origin/remove_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... especially when it contains a .git directory. + test_expect_$RESULT "$command: removed submodule leaves submodule containing a .git directory alone" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t remove_sub1 origin/remove_sub1 && + replace_gitfile_with_git_dir sub1 && + $command remove_sub1 && + test_superproject_content origin/remove_sub1 && + test_git_directory_is_unchanged sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Replacing a submodule with files in a directory must fail as the + # submodule work tree isn't removed ... + if test "$KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES" = 1 + then + # Non fast-forward merges attempt to merge the former + # submodule files with the newly checked out ones in the + # directory of the same name while it shouldn't. + RESULT="failure" + else + RESULT="success" + fi + test_expect_$RESULT "$command: replace submodule with a directory must fail" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + test_must_fail $command replace_sub1_with_directory && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... especially when it contains a .git directory. + test_expect_$RESULT "$command: replace submodule containing a .git directory with a directory must fail" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + replace_gitfile_with_git_dir sub1 && + test_must_fail $command replace_sub1_with_directory && + test_superproject_content origin/add_sub1 && + test_git_directory_is_unchanged sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Replacing it with a file must fail as it could throw away any local + # work tree changes ... + test_expect_failure "$command: replace submodule with a file must fail" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + test_must_fail $command replace_sub1_with_file && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... or even destroy unpushed parts of submodule history if that + # still uses a .git directory. + test_expect_failure "$command: replace submodule containing a .git directory with a file must fail" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + replace_gitfile_with_git_dir sub1 && + test_must_fail $command replace_sub1_with_file && + test_superproject_content origin/add_sub1 && + test_git_directory_is_unchanged sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + + ########################## Modified submodule ######################### + # Updating a submodule sha1 doesn't update the submodule's work tree + if test "$KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT" = 1 + then + # When cherry picking a SHA-1 update for an ignored submodule + # the commit incorrectly fails with "The previous cherry-pick + # is now empty, possibly due to conflict resolution." + RESULT="failure" + else + RESULT="success" + fi + test_expect_$RESULT "$command: modified submodule does not update submodule work tree" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t modify_sub1 origin/modify_sub1 && + $command modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/add_sub1 && + git submodule update && + test_submodule_content sub1 origin/modify_sub1 + ) + ' + + # Updating a submodule to an invalid sha1 doesn't update the + # submodule's work tree, subsequent update will fail + test_expect_$RESULT "$command: modified submodule does not update submodule work tree to invalid commit" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t invalid_sub1 origin/invalid_sub1 && + $command invalid_sub1 && + test_superproject_content origin/invalid_sub1 && + test_submodule_content sub1 origin/add_sub1 && + test_must_fail git submodule update && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Updating a submodule from an invalid sha1 doesn't update the + # submodule's work tree, subsequent update will succeed + test_expect_$RESULT "$command: modified submodule does not update submodule work tree from invalid commit" ' + prolog && + reset_work_tree_to invalid_sub1 && + ( + cd submodule_update && + git branch -t valid_sub1 origin/valid_sub1 && + $command valid_sub1 && + test_superproject_content origin/valid_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/valid_sub1 + ) + ' +} + +# Test that submodule contents are currently not updated when switching +# between commits that change a submodule, but throwing away local changes in +# the superproject is allowed. +test_submodule_forced_switch () { + command="$1" + ######################### Appearing submodule ######################### + # Switching to a commit letting a submodule appear creates empty dir ... + test_expect_success "$command: added submodule creates empty directory" ' + prolog && + reset_work_tree_to no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... and doesn't care if it already exists ... + test_expect_success "$command: added submodule leaves existing empty directory alone" ' + prolog && + reset_work_tree_to no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + mkdir sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... unless there is an untracked file in its place. + test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" ' + prolog && + reset_work_tree_to no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + >sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_dir_is_empty sub1 + ) + ' + # Replacing a tracked file with a submodule produces an empty + # directory ... + test_expect_success "$command: replace tracked file with submodule creates empty directory" ' + prolog && + reset_work_tree_to replace_sub1_with_file && + ( + cd submodule_update && + git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 && + $command replace_file_with_sub1 && + test_superproject_content origin/replace_file_with_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/replace_file_with_sub1 + ) + ' + # ... as does removing a directory with tracked files with a + # submodule. + test_expect_success "$command: replace directory with submodule" ' + prolog && + reset_work_tree_to replace_sub1_with_directory && + ( + cd submodule_update && + git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 && + $command replace_directory_with_sub1 && + test_superproject_content origin/replace_directory_with_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/replace_directory_with_sub1 + ) + ' + + ######################## Disappearing submodule ####################### + # Removing a submodule doesn't remove its work tree ... + test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t remove_sub1 origin/remove_sub1 && + $command remove_sub1 && + test_superproject_content origin/remove_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... especially when it contains a .git directory. + test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t remove_sub1 origin/remove_sub1 && + replace_gitfile_with_git_dir sub1 && + $command remove_sub1 && + test_superproject_content origin/remove_sub1 && + test_git_directory_is_unchanged sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Replacing a submodule with files in a directory must fail as the + # submodule work tree isn't removed ... + test_expect_failure "$command: replace submodule with a directory must fail" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + test_must_fail $command replace_sub1_with_directory && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... especially when it contains a .git directory. + test_expect_failure "$command: replace submodule containing a .git directory with a directory must fail" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + replace_gitfile_with_git_dir sub1 && + test_must_fail $command replace_sub1_with_directory && + test_superproject_content origin/add_sub1 && + test_git_directory_is_unchanged sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Replacing it with a file must fail as it could throw away any local + # work tree changes ... + test_expect_failure "$command: replace submodule with a file must fail" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + test_must_fail $command replace_sub1_with_file && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # ... or even destroy unpushed parts of submodule history if that + # still uses a .git directory. + test_expect_failure "$command: replace submodule containing a .git directory with a file must fail" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + replace_gitfile_with_git_dir sub1 && + test_must_fail $command replace_sub1_with_file && + test_superproject_content origin/add_sub1 && + test_git_directory_is_unchanged sub1 && + test_submodule_content sub1 origin/add_sub1 + ) + ' + + ########################## Modified submodule ######################### + # Updating a submodule sha1 doesn't update the submodule's work tree + test_expect_success "$command: modified submodule does not update submodule work tree" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t modify_sub1 origin/modify_sub1 && + $command modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/add_sub1 && + git submodule update && + test_submodule_content sub1 origin/modify_sub1 + ) + ' + # Updating a submodule to an invalid sha1 doesn't update the + # submodule's work tree, subsequent update will fail + test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" ' + prolog && + reset_work_tree_to add_sub1 && + ( + cd submodule_update && + git branch -t invalid_sub1 origin/invalid_sub1 && + $command invalid_sub1 && + test_superproject_content origin/invalid_sub1 && + test_submodule_content sub1 origin/add_sub1 && + test_must_fail git submodule update && + test_submodule_content sub1 origin/add_sub1 + ) + ' + # Updating a submodule from an invalid sha1 doesn't update the + # submodule's work tree, subsequent update will succeed + test_expect_success "$command: modified submodule does not update submodule work tree from invalid commit" ' + prolog && + reset_work_tree_to invalid_sub1 && + ( + cd submodule_update && + git branch -t valid_sub1 origin/valid_sub1 && + $command valid_sub1 && + test_superproject_content origin/valid_sub1 && + test_dir_is_empty sub1 && + git submodule update --init --recursive && + test_submodule_content sub1 origin/valid_sub1 + ) + ' +} diff --git a/t/perf/p5302-pack-index.sh b/t/perf/p5302-pack-index.sh index 6cb5b0d5..5ee9211f 100755 --- a/t/perf/p5302-pack-index.sh +++ b/t/perf/p5302-pack-index.sh @@ -8,7 +8,7 @@ test_perf_large_repo test_expect_success 'repack' ' git repack -ad && - PACK=`ls .git/objects/pack/*.pack | head -n1` && + PACK=$(ls .git/objects/pack/*.pack | head -n1) && test -f "$PACK" && export PACK ' diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index 685d46f8..f8ed8573 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -8,6 +8,9 @@ test_perf_large_repo # note that we do everything through config, # since we want to be able to compare bitmap-aware # git versus non-bitmap git +# +# We intentionally use the deprecated pack.writebitmaps +# config so that we can test against older versions of git. test_expect_success 'setup bitmap config' ' git config pack.writebitmaps true && git config pack.writebitmaphashcache true diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index a2bb63ce..f10ba4a0 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -42,9 +42,9 @@ test_expect_success 'success is reported like this' ' : ' -run_sub_test_lib_test () { - name="$1" descr="$2" # stdin is the body of the test code - shift 2 +_run_sub_test_lib_test_common () { + neg="$1" name="$2" descr="$3" # stdin is the body of the test code + shift 3 mkdir "$name" && ( # Pretend we're not running under a test harness, whether we @@ -70,10 +70,23 @@ run_sub_test_lib_test () { export TEST_DIRECTORY && TEST_OUTPUT_DIRECTORY=$(pwd) && export TEST_OUTPUT_DIRECTORY && - ./"$name.sh" "$@" >out 2>err + if test -z "$neg" + then + ./"$name.sh" "$@" >out 2>err + else + ! ./"$name.sh" "$@" >out 2>err + fi ) } +run_sub_test_lib_test () { + _run_sub_test_lib_test_common '' "$@" +} + +run_sub_test_lib_test_err () { + _run_sub_test_lib_test_common '!' "$@" +} + check_sub_test_lib_test () { name="$1" # stdin is the expected output from the test ( @@ -84,6 +97,18 @@ check_sub_test_lib_test () { ) } +check_sub_test_lib_test_err () { + name="$1" # stdin is the expected output output from the test + # expected error output is in descriptior 3 + ( + cd "$name" && + sed -e 's/^> //' -e 's/Z$//' >expect.out && + test_cmp expect.out out && + sed -e 's/^> //' -e 's/Z$//' <&3 >expect.err && + test_cmp expect.err err + ) +} + test_expect_success 'pretend we have a fully passing test suite' " run_sub_test_lib_test full-pass '3 passing tests' <<-\\EOF && for i in 1 2 3 @@ -270,6 +295,398 @@ test_expect_success 'test --verbose-only' ' EOF ' +test_expect_success 'GIT_SKIP_TESTS' " + ( + GIT_SKIP_TESTS='git.2' && export GIT_SKIP_TESTS && + run_sub_test_lib_test git-skip-tests-basic \ + 'GIT_SKIP_TESTS' <<-\\EOF && + for i in 1 2 3 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test git-skip-tests-basic <<-\\EOF + > ok 1 - passing test #1 + > ok 2 # skip passing test #2 (GIT_SKIP_TESTS) + > ok 3 - passing test #3 + > # passed all 3 test(s) + > 1..3 + EOF + ) +" + +test_expect_success 'GIT_SKIP_TESTS several tests' " + ( + GIT_SKIP_TESTS='git.2 git.5' && export GIT_SKIP_TESTS && + run_sub_test_lib_test git-skip-tests-several \ + 'GIT_SKIP_TESTS several tests' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test git-skip-tests-several <<-\\EOF + > ok 1 - passing test #1 + > ok 2 # skip passing test #2 (GIT_SKIP_TESTS) + > ok 3 - passing test #3 + > ok 4 - passing test #4 + > ok 5 # skip passing test #5 (GIT_SKIP_TESTS) + > ok 6 - passing test #6 + > # passed all 6 test(s) + > 1..6 + EOF + ) +" + +test_expect_success 'GIT_SKIP_TESTS sh pattern' " + ( + GIT_SKIP_TESTS='git.[2-5]' && export GIT_SKIP_TESTS && + run_sub_test_lib_test git-skip-tests-sh-pattern \ + 'GIT_SKIP_TESTS sh pattern' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test git-skip-tests-sh-pattern <<-\\EOF + > ok 1 - passing test #1 + > ok 2 # skip passing test #2 (GIT_SKIP_TESTS) + > ok 3 # skip passing test #3 (GIT_SKIP_TESTS) + > ok 4 # skip passing test #4 (GIT_SKIP_TESTS) + > ok 5 # skip passing test #5 (GIT_SKIP_TESTS) + > ok 6 - passing test #6 + > # passed all 6 test(s) + > 1..6 + EOF + ) +" + +test_expect_success '--run basic' " + run_sub_test_lib_test run-basic \ + '--run basic' --run='1 3 5' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-basic <<-\\EOF + > ok 1 - passing test #1 + > ok 2 # skip passing test #2 (--run) + > ok 3 - passing test #3 + > ok 4 # skip passing test #4 (--run) + > ok 5 - passing test #5 + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run with a range' " + run_sub_test_lib_test run-range \ + '--run with a range' --run='1-3' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-range <<-\\EOF + > ok 1 - passing test #1 + > ok 2 - passing test #2 + > ok 3 - passing test #3 + > ok 4 # skip passing test #4 (--run) + > ok 5 # skip passing test #5 (--run) + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run with two ranges' " + run_sub_test_lib_test run-two-ranges \ + '--run with two ranges' --run='1-2 5-6' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-two-ranges <<-\\EOF + > ok 1 - passing test #1 + > ok 2 - passing test #2 + > ok 3 # skip passing test #3 (--run) + > ok 4 # skip passing test #4 (--run) + > ok 5 - passing test #5 + > ok 6 - passing test #6 + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run with a left open range' " + run_sub_test_lib_test run-left-open-range \ + '--run with a left open range' --run='-3' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-left-open-range <<-\\EOF + > ok 1 - passing test #1 + > ok 2 - passing test #2 + > ok 3 - passing test #3 + > ok 4 # skip passing test #4 (--run) + > ok 5 # skip passing test #5 (--run) + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run with a right open range' " + run_sub_test_lib_test run-right-open-range \ + '--run with a right open range' --run='4-' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-right-open-range <<-\\EOF + > ok 1 # skip passing test #1 (--run) + > ok 2 # skip passing test #2 (--run) + > ok 3 # skip passing test #3 (--run) + > ok 4 - passing test #4 + > ok 5 - passing test #5 + > ok 6 - passing test #6 + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run with basic negation' " + run_sub_test_lib_test run-basic-neg \ + '--run with basic negation' --run='"'!3'"' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-basic-neg <<-\\EOF + > ok 1 - passing test #1 + > ok 2 - passing test #2 + > ok 3 # skip passing test #3 (--run) + > ok 4 - passing test #4 + > ok 5 - passing test #5 + > ok 6 - passing test #6 + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run with two negations' " + run_sub_test_lib_test run-two-neg \ + '--run with two negations' --run='"'!3 !6'"' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-two-neg <<-\\EOF + > ok 1 - passing test #1 + > ok 2 - passing test #2 + > ok 3 # skip passing test #3 (--run) + > ok 4 - passing test #4 + > ok 5 - passing test #5 + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run a range and negation' " + run_sub_test_lib_test run-range-and-neg \ + '--run a range and negation' --run='"'-4 !2'"' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-range-and-neg <<-\\EOF + > ok 1 - passing test #1 + > ok 2 # skip passing test #2 (--run) + > ok 3 - passing test #3 + > ok 4 - passing test #4 + > ok 5 # skip passing test #5 (--run) + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run range negation' " + run_sub_test_lib_test run-range-neg \ + '--run range negation' --run='"'!1-3'"' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-range-neg <<-\\EOF + > ok 1 # skip passing test #1 (--run) + > ok 2 # skip passing test #2 (--run) + > ok 3 # skip passing test #3 (--run) + > ok 4 - passing test #4 + > ok 5 - passing test #5 + > ok 6 - passing test #6 + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run include, exclude and include' " + run_sub_test_lib_test run-inc-neg-inc \ + '--run include, exclude and include' \ + --run='"'1-5 !1-3 2'"' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-inc-neg-inc <<-\\EOF + > ok 1 # skip passing test #1 (--run) + > ok 2 - passing test #2 + > ok 3 # skip passing test #3 (--run) + > ok 4 - passing test #4 + > ok 5 - passing test #5 + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run include, exclude and include, comma separated' " + run_sub_test_lib_test run-inc-neg-inc-comma \ + '--run include, exclude and include, comma separated' \ + --run=1-5,\!1-3,2 <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-inc-neg-inc-comma <<-\\EOF + > ok 1 # skip passing test #1 (--run) + > ok 2 - passing test #2 + > ok 3 # skip passing test #3 (--run) + > ok 4 - passing test #4 + > ok 5 - passing test #5 + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run exclude and include' " + run_sub_test_lib_test run-neg-inc \ + '--run exclude and include' \ + --run='"'!3- 5'"' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-neg-inc <<-\\EOF + > ok 1 - passing test #1 + > ok 2 - passing test #2 + > ok 3 # skip passing test #3 (--run) + > ok 4 # skip passing test #4 (--run) + > ok 5 - passing test #5 + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run empty selectors' " + run_sub_test_lib_test run-empty-sel \ + '--run empty selectors' \ + --run='1,,3,,,5' <<-\\EOF && + for i in 1 2 3 4 5 6 + do + test_expect_success \"passing test #\$i\" 'true' + done + test_done + EOF + check_sub_test_lib_test run-empty-sel <<-\\EOF + > ok 1 - passing test #1 + > ok 2 # skip passing test #2 (--run) + > ok 3 - passing test #3 + > ok 4 # skip passing test #4 (--run) + > ok 5 - passing test #5 + > ok 6 # skip passing test #6 (--run) + > # passed all 6 test(s) + > 1..6 + EOF +" + +test_expect_success '--run invalid range start' " + run_sub_test_lib_test_err run-inv-range-start \ + '--run invalid range start' \ + --run='a-5' <<-\\EOF && + test_expect_success \"passing test #1\" 'true' + test_done + EOF + check_sub_test_lib_test_err run-inv-range-start \ + <<-\\EOF_OUT 3<<-\\EOF_ERR + > FATAL: Unexpected exit with code 1 + EOF_OUT + > error: --run: invalid non-numeric in range start: 'a-5' + EOF_ERR +" + +test_expect_success '--run invalid range end' " + run_sub_test_lib_test_err run-inv-range-end \ + '--run invalid range end' \ + --run='1-z' <<-\\EOF && + test_expect_success \"passing test #1\" 'true' + test_done + EOF + check_sub_test_lib_test_err run-inv-range-end \ + <<-\\EOF_OUT 3<<-\\EOF_ERR + > FATAL: Unexpected exit with code 1 + EOF_OUT + > error: --run: invalid non-numeric in range end: '1-z' + EOF_ERR +" + +test_expect_success '--run invalid selector' " + run_sub_test_lib_test_err run-inv-selector \ + '--run invalid selector' \ + --run='1?' <<-\\EOF && + test_expect_success \"passing test #1\" 'true' + test_done + EOF + check_sub_test_lib_test_err run-inv-selector \ + <<-\\EOF_OUT 3<<-\\EOF_ERR + > FATAL: Unexpected exit with code 1 + EOF_OUT + > error: --run: invalid non-numeric in test selector: '1?' + EOF_ERR +" + + test_set_prereq HAVEIT haveit=no test_expect_success HAVEIT 'test runs if prerequisite is satisfied' ' diff --git a/t/t0001-init.sh b/t/t0001-init.sh index bbc9cb60..e62c0ffb 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -56,7 +56,7 @@ test_expect_success 'plain through aliased command, outside any git repo' ' check_config plain-aliased/.git false unset ' -test_expect_failure 'plain nested through aliased command' ' +test_expect_success 'plain nested through aliased command' ' ( git init plain-ancestor-aliased && cd plain-ancestor-aliased && @@ -68,7 +68,7 @@ test_expect_failure 'plain nested through aliased command' ' check_config plain-ancestor-aliased/plain-nested/.git false unset ' -test_expect_failure 'plain nested in bare through aliased command' ' +test_expect_success 'plain nested in bare through aliased command' ' ( git init --bare bare-ancestor-aliased.git && cd bare-ancestor-aliased.git && @@ -185,14 +185,14 @@ test_expect_success 'init --bare/--shared overrides system/global config' ' git init --bare --shared=0666 init-bare-shared-override && check_config init-bare-shared-override true unset && test x0666 = \ - x`git config -f init-bare-shared-override/config core.sharedRepository` + x$(git config -f init-bare-shared-override/config core.sharedRepository) ' test_expect_success 'init honors global core.sharedRepository' ' test_config_global core.sharedRepository 0666 && git init shared-honor-global && test x0666 = \ - x`git config -f shared-honor-global/.git/config core.sharedRepository` + x$(git config -f shared-honor-global/.git/config core.sharedRepository) ' test_expect_success 'init rejects insanely long --template' ' @@ -285,7 +285,7 @@ test_expect_success 'init prefers command line to GIT_DIR' ' test_expect_success 'init with separate gitdir' ' rm -rf newdir && git init --separate-git-dir realgitdir newdir && - echo "gitdir: `pwd`/realgitdir" >expected && + echo "gitdir: $(pwd)/realgitdir" >expected && test_cmp expected newdir/.git && test_path_is_dir realgitdir/refs ' @@ -299,7 +299,7 @@ test_expect_success 're-init to update git link' ' cd newdir && git init --separate-git-dir ../surrealgitdir ) && - echo "gitdir: `pwd`/surrealgitdir" >expected && + echo "gitdir: $(pwd)/surrealgitdir" >expected && test_cmp expected newdir/.git && test_path_is_dir surrealgitdir/refs && test_path_is_missing realgitdir/refs @@ -312,7 +312,7 @@ test_expect_success 're-init to move gitdir' ' cd newdir && git init --separate-git-dir ../realgitdir ) && - echo "gitdir: `pwd`/realgitdir" >expected && + echo "gitdir: $(pwd)/realgitdir" >expected && test_cmp expected newdir/.git && test_path_is_dir realgitdir/refs ' @@ -326,7 +326,7 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' ' ln -s here .git && git init --separate-git-dir ../realgitdir ) && - echo "gitdir: `pwd`/realgitdir" >expected && + echo "gitdir: $(pwd)/realgitdir" >expected && test_cmp expected newdir/.git && test_cmp expected newdir/here && test_path_is_dir realgitdir/refs diff --git a/t/t0010-racy-git.sh b/t/t0010-racy-git.sh index e45a9e40..5657c5a8 100755 --- a/t/t0010-racy-git.sh +++ b/t/t0010-racy-git.sh @@ -14,7 +14,7 @@ do git update-index --add infocom echo xyzzy >infocom - files=`git diff-files -p` + files=$(git diff-files -p) test_expect_success \ "Racy GIT trial #$trial part A" \ 'test "" != "$files"' @@ -23,7 +23,7 @@ do echo xyzzy >cornerstone git update-index --add cornerstone - files=`git diff-files -p` + files=$(git diff-files -p) test_expect_success \ "Racy GIT trial #$trial part B" \ 'test "" != "$files"' diff --git a/t/t0011-hashmap.sh b/t/t0011-hashmap.sh index 391e2b64..f97c8055 100755 --- a/t/t0011-hashmap.sh +++ b/t/t0011-hashmap.sh @@ -237,4 +237,17 @@ test_expect_success 'grow / shrink' ' ' +test_expect_success 'string interning' ' + +test_hashmap "intern value1 +intern Value1 +intern value2 +intern value2 +" "value1 +Value1 +value2 +value2" + +' + test_done diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index e526184a..d2e51a81 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -20,14 +20,14 @@ test_expect_success setup ' git commit -m initial && - one=`git rev-parse HEAD:one` && - dir=`git rev-parse HEAD:dir` && - two=`git rev-parse HEAD:dir/two` && - three=`git rev-parse HEAD:three` && + one=$(git rev-parse HEAD:one) && + dir=$(git rev-parse HEAD:dir) && + two=$(git rev-parse HEAD:dir/two) && + three=$(git rev-parse HEAD:three) && for w in Some extra lines here; do echo $w; done >>one && git diff >patch.file && - patched=`git hash-object --stdin <one` && + patched=$(git hash-object --stdin <one) && git read-tree --reset -u HEAD && echo happy. @@ -111,7 +111,7 @@ test_expect_success 'update with autocrlf=input' ' } done && - differs=`git diff-index --cached HEAD` && + differs=$(git diff-index --cached HEAD) && test -z "$differs" || { echo Oops "$differs" false @@ -135,7 +135,7 @@ test_expect_success 'update with autocrlf=true' ' } done && - differs=`git diff-index --cached HEAD` && + differs=$(git diff-index --cached HEAD) && test -z "$differs" || { echo Oops "$differs" false @@ -158,9 +158,9 @@ test_expect_success 'checkout with autocrlf=true' ' break } done && - test "$one" = `git hash-object --stdin <one` && - test "$two" = `git hash-object --stdin <dir/two` && - differs=`git diff-index --cached HEAD` && + test "$one" = $(git hash-object --stdin <one) && + test "$two" = $(git hash-object --stdin <dir/two) && + differs=$(git diff-index --cached HEAD) && test -z "$differs" || { echo Oops "$differs" false @@ -184,9 +184,9 @@ test_expect_success 'checkout with autocrlf=input' ' git update-index -- $f fi done && - test "$one" = `git hash-object --stdin <one` && - test "$two" = `git hash-object --stdin <dir/two` && - differs=`git diff-index --cached HEAD` && + test "$one" = $(git hash-object --stdin <one) && + test "$two" = $(git hash-object --stdin <dir/two) && + differs=$(git diff-index --cached HEAD) && test -z "$differs" || { echo Oops "$differs" false @@ -200,7 +200,7 @@ test_expect_success 'apply patch (autocrlf=input)' ' git read-tree --reset -u HEAD && git apply patch.file && - test "$patched" = "`git hash-object --stdin <one`" || { + test "$patched" = "$(git hash-object --stdin <one)" || { echo "Eh? apply without index" false } @@ -213,7 +213,7 @@ test_expect_success 'apply patch --cached (autocrlf=input)' ' git read-tree --reset -u HEAD && git apply --cached patch.file && - test "$patched" = `git rev-parse :one` || { + test "$patched" = $(git rev-parse :one) || { echo "Eh? apply with --cached" false } @@ -226,8 +226,8 @@ test_expect_success 'apply patch --index (autocrlf=input)' ' git read-tree --reset -u HEAD && git apply --index patch.file && - test "$patched" = `git rev-parse :one` && - test "$patched" = `git hash-object --stdin <one` || { + test "$patched" = $(git rev-parse :one) && + test "$patched" = $(git hash-object --stdin <one) || { echo "Eh? apply with --index" false } @@ -240,7 +240,7 @@ test_expect_success 'apply patch (autocrlf=true)' ' git read-tree --reset -u HEAD && git apply patch.file && - test "$patched" = "`remove_cr <one | git hash-object --stdin`" || { + test "$patched" = "$(remove_cr <one | git hash-object --stdin)" || { echo "Eh? apply without index" false } @@ -253,7 +253,7 @@ test_expect_success 'apply patch --cached (autocrlf=true)' ' git read-tree --reset -u HEAD && git apply --cached patch.file && - test "$patched" = `git rev-parse :one` || { + test "$patched" = $(git rev-parse :one) || { echo "Eh? apply without index" false } @@ -266,8 +266,8 @@ test_expect_success 'apply patch --index (autocrlf=true)' ' git read-tree --reset -u HEAD && git apply --index patch.file && - test "$patched" = `git rev-parse :one` && - test "$patched" = "`remove_cr <one | git hash-object --stdin`" || { + test "$patched" = $(git rev-parse :one) && + test "$patched" = "$(remove_cr <one | git hash-object --stdin)" || { echo "Eh? apply with --index" false } diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index b92e6cb0..f890c54d 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -190,8 +190,6 @@ test_expect_success 'required filter clean failure' ' test_must_fail git add test.fc ' -test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE - test_expect_success EXPENSIVE 'filter large file' ' git config filter.largefile.smudge cat && git config filter.largefile.clean cat && diff --git a/t/t0025-crlf-auto.sh b/t/t0025-crlf-auto.sh index f5f67a63..c164b466 100755 --- a/t/t0025-crlf-auto.sh +++ b/t/t0025-crlf-auto.sh @@ -12,144 +12,144 @@ test_expect_success setup ' git config core.autocrlf false && - for w in Hello world how are you; do echo $w; done >one && - for w in I am very very fine thank you; do echo ${w}Q; done | q_to_cr >two && - for w in Oh here is a QNUL byte how alarming; do echo ${w}; done | q_to_nul >three && + for w in Hello world how are you; do echo $w; done >LFonly && + for w in I am very very fine thank you; do echo ${w}Q; done | q_to_cr >CRLFonly && + for w in Oh here is a QNUL byte how alarming; do echo ${w}; done | q_to_nul >LFwithNUL && git add . && git commit -m initial && - one=`git rev-parse HEAD:one` && - two=`git rev-parse HEAD:two` && - three=`git rev-parse HEAD:three` && + LFonly=$(git rev-parse HEAD:LFonly) && + CRLFonly=$(git rev-parse HEAD:CRLFonly) && + LFwithNUL=$(git rev-parse HEAD:LFwithNUL) && echo happy. ' test_expect_success 'default settings cause no changes' ' - rm -f .gitattributes tmp one two three && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && git read-tree --reset -u HEAD && - ! has_cr one && - has_cr two && - onediff=`git diff one` && - twodiff=`git diff two` && - threediff=`git diff three` && - test -z "$onediff" -a -z "$twodiff" -a -z "$threediff" + ! has_cr LFonly && + has_cr CRLFonly && + LFonlydiff=$(git diff LFonly) && + CRLFonlydiff=$(git diff CRLFonly) && + LFwithNULdiff=$(git diff LFwithNUL) && + test -z "$LFonlydiff" -a -z "$CRLFonlydiff" -a -z "$LFwithNULdiff" ' test_expect_success 'crlf=true causes a CRLF file to be normalized' ' # Backwards compatibility check - rm -f .gitattributes tmp one two three && - echo "two crlf" > .gitattributes && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && + echo "CRLFonly crlf" > .gitattributes && git read-tree --reset -u HEAD && # Note, "normalized" means that git will normalize it if added - has_cr two && - twodiff=`git diff two` && - test -n "$twodiff" + has_cr CRLFonly && + CRLFonlydiff=$(git diff CRLFonly) && + test -n "$CRLFonlydiff" ' test_expect_success 'text=true causes a CRLF file to be normalized' ' - rm -f .gitattributes tmp one two three && - echo "two text" > .gitattributes && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && + echo "CRLFonly text" > .gitattributes && git read-tree --reset -u HEAD && # Note, "normalized" means that git will normalize it if added - has_cr two && - twodiff=`git diff two` && - test -n "$twodiff" + has_cr CRLFonly && + CRLFonlydiff=$(git diff CRLFonly) && + test -n "$CRLFonlydiff" ' test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=false' ' - rm -f .gitattributes tmp one two three && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && git config core.autocrlf false && - echo "one eol=crlf" > .gitattributes && + echo "LFonly eol=crlf" > .gitattributes && git read-tree --reset -u HEAD && - has_cr one && - onediff=`git diff one` && - test -z "$onediff" + has_cr LFonly && + LFonlydiff=$(git diff LFonly) && + test -z "$LFonlydiff" ' test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=input' ' - rm -f .gitattributes tmp one two three && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && git config core.autocrlf input && - echo "one eol=crlf" > .gitattributes && + echo "LFonly eol=crlf" > .gitattributes && git read-tree --reset -u HEAD && - has_cr one && - onediff=`git diff one` && - test -z "$onediff" + has_cr LFonly && + LFonlydiff=$(git diff LFonly) && + test -z "$LFonlydiff" ' test_expect_success 'eol=lf gives a normalized file LFs with autocrlf=true' ' - rm -f .gitattributes tmp one two three && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && git config core.autocrlf true && - echo "one eol=lf" > .gitattributes && + echo "LFonly eol=lf" > .gitattributes && git read-tree --reset -u HEAD && - ! has_cr one && - onediff=`git diff one` && - test -z "$onediff" + ! has_cr LFonly && + LFonlydiff=$(git diff LFonly) && + test -z "$LFonlydiff" ' test_expect_success 'autocrlf=true does not normalize CRLF files' ' - rm -f .gitattributes tmp one two three && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && git config core.autocrlf true && git read-tree --reset -u HEAD && - has_cr one && - has_cr two && - onediff=`git diff one` && - twodiff=`git diff two` && - threediff=`git diff three` && - test -z "$onediff" -a -z "$twodiff" -a -z "$threediff" + has_cr LFonly && + has_cr CRLFonly && + LFonlydiff=$(git diff LFonly) && + CRLFonlydiff=$(git diff CRLFonly) && + LFwithNULdiff=$(git diff LFwithNUL) && + test -z "$LFonlydiff" -a -z "$CRLFonlydiff" -a -z "$LFwithNULdiff" ' test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' ' - rm -f .gitattributes tmp one two three && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && git config core.autocrlf true && echo "* text=auto" > .gitattributes && git read-tree --reset -u HEAD && - has_cr one && - has_cr two && - onediff=`git diff one` && - twodiff=`git diff two` && - threediff=`git diff three` && - test -z "$onediff" -a -n "$twodiff" -a -z "$threediff" + has_cr LFonly && + has_cr CRLFonly && + LFonlydiff=$(git diff LFonly) && + CRLFonlydiff=$(git diff CRLFonly) && + LFwithNULdiff=$(git diff LFwithNUL) && + test -z "$LFonlydiff" -a -n "$CRLFonlydiff" -a -z "$LFwithNULdiff" ' test_expect_success 'text=auto, autocrlf=true does not normalize binary files' ' - rm -f .gitattributes tmp one two three && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && git config core.autocrlf true && echo "* text=auto" > .gitattributes && git read-tree --reset -u HEAD && - ! has_cr three && - threediff=`git diff three` && - test -z "$threediff" + ! has_cr LFwithNUL && + LFwithNULdiff=$(git diff LFwithNUL) && + test -z "$LFwithNULdiff" ' test_expect_success 'eol=crlf _does_ normalize binary files' ' - rm -f .gitattributes tmp one two three && - echo "three eol=crlf" > .gitattributes && + rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && + echo "LFwithNUL eol=crlf" > .gitattributes && git read-tree --reset -u HEAD && - has_cr three && - threediff=`git diff three` && - test -z "$threediff" + has_cr LFwithNUL && + LFwithNULdiff=$(git diff LFwithNUL) && + test -z "$LFwithNULdiff" ' test_done diff --git a/t/t0026-eol-config.sh b/t/t0026-eol-config.sh index fe0164be..4807b0f0 100755 --- a/t/t0026-eol-config.sh +++ b/t/t0026-eol-config.sh @@ -20,8 +20,8 @@ test_expect_success setup ' git commit -m initial && - one=`git rev-parse HEAD:one` && - two=`git rev-parse HEAD:two` && + one=$(git rev-parse HEAD:one) && + two=$(git rev-parse HEAD:two) && echo happy. ' @@ -34,9 +34,9 @@ test_expect_success 'eol=lf puts LFs in normalized file' ' ! has_cr one && ! has_cr two && - onediff=`git diff one` && - twodiff=`git diff two` && - test -z "$onediff" -a -z "$twodiff" + onediff=$(git diff one) && + twodiff=$(git diff two) && + test -z "$onediff" && test -z "$twodiff" ' test_expect_success 'eol=crlf puts CRLFs in normalized file' ' @@ -47,9 +47,9 @@ test_expect_success 'eol=crlf puts CRLFs in normalized file' ' has_cr one && ! has_cr two && - onediff=`git diff one` && - twodiff=`git diff two` && - test -z "$onediff" -a -z "$twodiff" + onediff=$(git diff one) && + twodiff=$(git diff two) && + test -z "$onediff" && test -z "$twodiff" ' test_expect_success 'autocrlf=true overrides eol=lf' ' @@ -61,9 +61,9 @@ test_expect_success 'autocrlf=true overrides eol=lf' ' has_cr one && has_cr two && - onediff=`git diff one` && - twodiff=`git diff two` && - test -z "$onediff" -a -z "$twodiff" + onediff=$(git diff one) && + twodiff=$(git diff two) && + test -z "$onediff" && test -z "$twodiff" ' test_expect_success 'autocrlf=true overrides unset eol' ' @@ -75,9 +75,9 @@ test_expect_success 'autocrlf=true overrides unset eol' ' has_cr one && has_cr two && - onediff=`git diff one` && - twodiff=`git diff two` && - test -z "$onediff" -a -z "$twodiff" + onediff=$(git diff one) && + twodiff=$(git diff two) && + test -z "$onediff" && test -z "$twodiff" ' test_done diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh new file mode 100755 index 00000000..72dd3e8b --- /dev/null +++ b/t/t0027-auto-crlf.sh @@ -0,0 +1,265 @@ +#!/bin/sh + +test_description='CRLF conversion all combinations' + +. ./test-lib.sh + +if ! test_have_prereq EXPENSIVE +then + skip_all="EXPENSIVE not set" + test_done +fi + + +compare_files() +{ + od -c <"$1" >"$1".expect && + od -c <"$2" >"$2".actual && + test_cmp "$1".expect "$2".actual && + rm "$1".expect "$2".actual +} + +compare_ws_file() +{ + pfx=$1 + exp=$2.expect + act=$pfx.actual.$3 + od -c <"$2" >"$exp" && + od -c <"$3" >"$act" && + test_cmp $exp $act && + rm $exp $act +} + +create_gitattributes() +{ + txtbin=$1 + case "$txtbin" in + auto) + echo "*.txt text=auto" >.gitattributes + ;; + text) + echo "*.txt text" >.gitattributes + ;; + -text) + echo "*.txt -text" >.gitattributes + ;; + *) + echo >.gitattributes + ;; + esac +} + +create_file_in_repo() +{ + crlf=$1 + txtbin=$2 + create_gitattributes "$txtbin" && + for f in LF CRLF LF_mix_CR CRLF_mix_LF CRLF_nul + do + pfx=crlf_${crlf}_attr_${txtbin}_$f.txt && + cp $f $pfx && git -c core.autocrlf=$crlf add $pfx + done && + git commit -m "core.autocrlf $crlf" +} + +check_files_in_repo() +{ + crlf=$1 + txtbin=$2 + lfname=$3 + crlfname=$4 + lfmixcrlf=$5 + lfmixcr=$6 + crlfnul=$7 + pfx=crlf_${crlf}_attr_${txtbin}_ && + compare_files $lfname ${pfx}LF.txt && + compare_files $crlfname ${pfx}CRLF.txt && + compare_files $lfmixcrlf ${pfx}CRLF_mix_LF.txt && + compare_files $lfmixcr ${pfx}LF_mix_CR.txt && + compare_files $crlfnul ${pfx}CRLF_nul.txt +} + + +check_files_in_ws() +{ + eol=$1 + crlf=$2 + txtbin=$3 + lfname=$4 + crlfname=$5 + lfmixcrlf=$6 + lfmixcr=$7 + crlfnul=$8 + create_gitattributes $txtbin && + git config core.autocrlf $crlf && + pfx=eol_${eol}_crlf_${crlf}_attr_${txtbin}_ && + src=crlf_false_attr__ && + for f in LF CRLF LF_mix_CR CRLF_mix_LF CRLF_nul + do + rm $src$f.txt && + if test -z "$eol"; then + git checkout $src$f.txt + else + git -c core.eol=$eol checkout $src$f.txt + fi + done + + + test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$txtbin file=LF" " + compare_ws_file $pfx $lfname ${src}LF.txt + " + test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$txtbin file=CRLF" " + compare_ws_file $pfx $crlfname ${src}CRLF.txt + " + test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$txtbin file=CRLF_mix_LF" " + compare_ws_file $pfx $lfmixcrlf ${src}CRLF_mix_LF.txt + " + test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$txtbin file=LF_mix_CR" " + compare_ws_file $pfx $lfmixcr ${src}LF_mix_CR.txt + " + test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$txtbin file=CRLF_nul" " + compare_ws_file $pfx $crlfnul ${src}CRLF_nul.txt + " +} + +####### +( + type od >/dev/null && + printf "line1Q\r\nline2\r\nline3" | q_to_nul >CRLF_nul && + cat >expect <<-EOF && + 0000000 l i n e 1 \0 \r \n l i n e 2 \r \n l + 0000020 i n e 3 + 0000024 +EOF + od -c CRLF_nul | sed -e "s/[ ][ ]*/ /g" -e "s/ *$//" >actual + test_cmp expect actual && + rm expect actual +) || { + skip_all="od not found or od -c not usable" + exit 0 + test_done +} + +test_expect_success 'setup master' ' + echo >.gitattributes && + git checkout -b master && + git add .gitattributes && + git commit -m "add .gitattributes" "" && + printf "line1\nline2\nline3" >LF && + printf "line1\r\nline2\r\nline3" >CRLF && + printf "line1\r\nline2\nline3" >CRLF_mix_LF && + printf "line1\nline2\rline3" >LF_mix_CR && + printf "line1\r\nline2\rline3" >CRLF_mix_CR && + printf "line1Q\nline2\nline3" | q_to_nul >LF_nul +' +# CRLF_nul had been created above + +test_expect_success 'create files' ' + create_file_in_repo false "" && + create_file_in_repo true "" && + create_file_in_repo input "" && + + create_file_in_repo false "auto" && + create_file_in_repo true "auto" && + create_file_in_repo input "auto" && + + create_file_in_repo false "text" && + create_file_in_repo true "text" && + create_file_in_repo input "text" && + + create_file_in_repo false "-text" && + create_file_in_repo true "-text" && + create_file_in_repo input "-text" && + rm -f *.txt && + git reset --hard +' + +test_expect_success 'commit empty gitattribues' ' + check_files_in_repo false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul && + check_files_in_repo true "" LF LF LF LF_mix_CR CRLF_nul && + check_files_in_repo input "" LF LF LF LF_mix_CR CRLF_nul +' + +test_expect_success 'commit text=auto' ' + check_files_in_repo false "auto" LF LF LF LF_mix_CR CRLF_nul && + check_files_in_repo true "auto" LF LF LF LF_mix_CR CRLF_nul && + check_files_in_repo input "auto" LF LF LF LF_mix_CR CRLF_nul +' + +test_expect_success 'commit text' ' + check_files_in_repo false "text" LF LF LF LF_mix_CR LF_nul && + check_files_in_repo true "text" LF LF LF LF_mix_CR LF_nul && + check_files_in_repo input "text" LF LF LF LF_mix_CR LF_nul +' + +test_expect_success 'commit -text' ' + check_files_in_repo false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul && + check_files_in_repo true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul && + check_files_in_repo input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +' + +################################################################################ +# Check how files in the repo are changed when they are checked out +# How to read the table below: +# - check_files_in_ws will check multiple files, see below +# - parameter $1 : core.eol lf | crlf +# - parameter $2 : core.autocrlf false | true | input +# - parameter $3 : text in .gitattributs "" (empty) | auto | text | -text +# - parameter $4 : reference for a file with only LF in the repo +# - parameter $5 : reference for a file with only CRLF in the repo +# - parameter $6 : reference for a file with mixed LF and CRLF in the repo +# - parameter $7 : reference for a file with LF and CR in the repo (does somebody uses this ?) +# - parameter $8 : reference for a file with CRLF and a NUL (should be handled as binary when auto) + +check_files_in_ws lf false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws lf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws lf input "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + +check_files_in_ws lf false "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws lf true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul +check_files_in_ws lf input "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + +check_files_in_ws lf false "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws lf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul +check_files_in_ws lf input "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + +check_files_in_ws lf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws lf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws lf input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + +########### +#core.autocrlf=input is forbidden with core.eol=crlf +check_files_in_ws crlf false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws crlf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + +check_files_in_ws crlf false "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul +check_files_in_ws crlf true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul + +check_files_in_ws crlf false "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul +check_files_in_ws crlf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + +check_files_in_ws crlf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws crlf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + +if test_have_prereq MINGW +then +check_files_in_ws "" false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws "" true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws "" false "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws "" true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul +check_files_in_ws "" false "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws "" true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul +check_files_in_ws "" false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws "" true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + +check_files_in_ws native false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws native true "" CRLF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws native false "auto" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws native true "auto" CRLF CRLF CRLF LF_mix_CR CRLF_nul +check_files_in_ws native false "text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws native true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul +check_files_in_ws native false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +check_files_in_ws native true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul +fi + +test_done diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index a8e84d85..0333dd98 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -225,22 +225,22 @@ test_expect_success \ test_expect_success \ 'text without newline at end should end with newline' ' - test `printf "$ttt" | git stripspace | wc -l` -gt 0 && - test `printf "$ttt$ttt" | git stripspace | wc -l` -gt 0 && - test `printf "$ttt$ttt$ttt" | git stripspace | wc -l` -gt 0 && - test `printf "$ttt$ttt$ttt$ttt" | git stripspace | wc -l` -gt 0 + test $(printf "$ttt" | git stripspace | wc -l) -gt 0 && + test $(printf "$ttt$ttt" | git stripspace | wc -l) -gt 0 && + test $(printf "$ttt$ttt$ttt" | git stripspace | wc -l) -gt 0 && + test $(printf "$ttt$ttt$ttt$ttt" | git stripspace | wc -l) -gt 0 ' # text plus spaces at the end: test_expect_success \ 'text plus spaces without newline at end should end with newline' ' - test `printf "$ttt$sss" | git stripspace | wc -l` -gt 0 && - test `printf "$ttt$ttt$sss" | git stripspace | wc -l` -gt 0 && - test `printf "$ttt$ttt$ttt$sss" | git stripspace | wc -l` -gt 0 && - test `printf "$ttt$sss$sss" | git stripspace | wc -l` -gt 0 && - test `printf "$ttt$ttt$sss$sss" | git stripspace | wc -l` -gt 0 && - test `printf "$ttt$sss$sss$sss" | git stripspace | wc -l` -gt 0 + test $(printf "$ttt$sss" | git stripspace | wc -l) -gt 0 && + test $(printf "$ttt$ttt$sss" | git stripspace | wc -l) -gt 0 && + test $(printf "$ttt$ttt$ttt$sss" | git stripspace | wc -l) -gt 0 && + test $(printf "$ttt$sss$sss" | git stripspace | wc -l) -gt 0 && + test $(printf "$ttt$ttt$sss$sss" | git stripspace | wc -l) -gt 0 && + test $(printf "$ttt$sss$sss$sss" | git stripspace | wc -l) -gt 0 ' test_expect_success \ diff --git a/t/t0081-line-buffer.sh b/t/t0081-line-buffer.sh index bd83ed37..25dba008 100755 --- a/t/t0081-line-buffer.sh +++ b/t/t0081-line-buffer.sh @@ -29,7 +29,7 @@ test_expect_success '0-length read, send along greeting' ' test_cmp expect actual ' -test_expect_success 'read from file descriptor' ' +test_expect_success NOT_MINGW 'read from file descriptor' ' rm -f input && echo hello >expect && echo hello >input && diff --git a/t/t0110-urlmatch-normalization.sh b/t/t0110-urlmatch-normalization.sh index 8d6096d4..410d5768 100755 --- a/t/t0110-urlmatch-normalization.sh +++ b/t/t0110-urlmatch-normalization.sh @@ -117,7 +117,7 @@ test_expect_success 'url general escapes' ' test "$(test-urlmatch-normalization -p "X://W?'\!'")" = "x://w/?'\!'" ' -test_expect_success 'url high-bit escapes' ' +test_expect_success !MINGW 'url high-bit escapes' ' test "$(test-urlmatch-normalization -p "$(cat "$tu-1")")" = "x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12" && test "$(test-urlmatch-normalization -p "$(cat "$tu-2")")" = "x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F" && test "$(test-urlmatch-normalization -p "$(cat "$tu-3")")" = "x://q/%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F" && @@ -127,7 +127,10 @@ test_expect_success 'url high-bit escapes' ' test "$(test-urlmatch-normalization -p "$(cat "$tu-7")")" = "x://q/%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF" && test "$(test-urlmatch-normalization -p "$(cat "$tu-8")")" = "x://q/%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF" && test "$(test-urlmatch-normalization -p "$(cat "$tu-9")")" = "x://q/%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-10")")" = "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF" && + test "$(test-urlmatch-normalization -p "$(cat "$tu-10")")" = "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF" +' + +test_expect_success 'url utf-8 escapes' ' test "$(test-urlmatch-normalization -p "$(cat "$tu-11")")" = "x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD" ' diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 538ea5fb..57ea5a10 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -6,7 +6,7 @@ test_description='basic credential helper tests' test_expect_success 'setup helper scripts' ' cat >dump <<-\EOF && - whoami=`echo $0 | sed s/.*git-credential-//` + whoami=$(echo $0 | sed s/.*git-credential-//) echo >&2 "$whoami: $*" OIFS=$IFS IFS== diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index babcdd23..a0b79b48 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -519,10 +519,10 @@ test_expect_success \ 'rm -f .git/index F16 && echo F16 >F16 && git update-index --add F16 && - tree0=`git write-tree` && + tree0=$(git write-tree) && echo E16 >F16 && git update-index F16 && - tree1=`git write-tree` && + tree1=$(git write-tree) && read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 && git ls-files --stage' diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 3a24abf5..db1b6f5c 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -36,7 +36,7 @@ compare_change () { } check_cache_at () { - clean_if_empty=`git diff-files -- "$1"` + clean_if_empty=$(git diff-files -- "$1") case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; @@ -68,14 +68,14 @@ test_expect_success \ echo rezrov >rezrov && echo yomin >yomin && git update-index --add nitfol bozbar rezrov && - treeH=`git write-tree` && + treeH=$(git write-tree) && echo treeH $treeH && git ls-tree $treeH && cat bozbar-new >bozbar && git update-index --add frotz bozbar --force-remove rezrov && git ls-files --stage >M.out && - treeM=`git write-tree` && + treeM=$(git write-tree) && echo treeM $treeM && git ls-tree $treeM && git diff-tree $treeH $treeM' @@ -315,7 +315,7 @@ test_expect_success \ 'rm -f .git/index && echo DF >DF && git update-index --add DF && - treeDF=`git write-tree` && + treeDF=$(git write-tree) && echo treeDF $treeDF && git ls-tree $treeDF && @@ -323,7 +323,7 @@ test_expect_success \ mkdir DF && echo DF/DF >DF/DF && git update-index --add --remove DF DF/DF && - treeDFDF=`git write-tree` && + treeDFDF=$(git write-tree) && echo treeDFDF $treeDFDF && git ls-tree $treeDFDF && git ls-files --stage >DFDF.out' @@ -345,7 +345,7 @@ test_expect_success \ 'rm -f .git/index && : >a && git update-index --add a && - treeM=`git write-tree` && + treeM=$(git write-tree) && echo treeM $treeM && git ls-tree $treeM && git ls-files --stage >treeM.out && @@ -354,7 +354,7 @@ test_expect_success \ git update-index --remove a && mkdir a && : >a/b && - treeH=`git write-tree` && + treeH=$(git write-tree) && echo treeH $treeH && git ls-tree $treeH' @@ -372,7 +372,7 @@ test_expect_success \ mkdir c && : >c/d && git update-index --add a c/d && - treeM=`git write-tree` && + treeM=$(git write-tree) && echo treeM $treeM && git ls-tree $treeM && git ls-files --stage >treeM.out && @@ -381,7 +381,7 @@ test_expect_success \ mkdir a && : >a/b && git update-index --add --remove a a/b && - treeH=`git write-tree` && + treeH=$(git write-tree) && echo treeH $treeH && git ls-tree $treeH' diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index a847709a..fed877b2 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -21,7 +21,7 @@ compare_change () { } check_cache_at () { - clean_if_empty=`git diff-files -- "$1"` + clean_if_empty=$(git diff-files -- "$1") case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; @@ -41,14 +41,14 @@ test_expect_success \ echo bozbar >bozbar && echo rezrov >rezrov && git update-index --add nitfol bozbar rezrov && - treeH=`git write-tree` && + treeH=$(git write-tree) && echo treeH $treeH && git ls-tree $treeH && echo gnusto >bozbar && git update-index --add frotz bozbar --force-remove rezrov && git ls-files --stage >M.out && - treeM=`git write-tree` && + treeM=$(git write-tree) && echo treeM $treeM && git ls-tree $treeM && sum bozbar frotz nitfol >M.sum && @@ -318,7 +318,7 @@ test_expect_success \ 'rm -f .git/index && echo DF >DF && git update-index --add DF && - treeDF=`git write-tree` && + treeDF=$(git write-tree) && echo treeDF $treeDF && git ls-tree $treeDF && @@ -326,7 +326,7 @@ test_expect_success \ mkdir DF && echo DF/DF >DF/DF && git update-index --add --remove DF DF/DF && - treeDFDF=`git write-tree` && + treeDFDF=$(git write-tree) && echo treeDFDF $treeDFDF && git ls-tree $treeDFDF && git ls-files --stage >DFDF.out' diff --git a/t/t1003-read-tree-prefix.sh b/t/t1003-read-tree-prefix.sh index 8c6d67ed..b6111cd1 100755 --- a/t/t1003-read-tree-prefix.sh +++ b/t/t1003-read-tree-prefix.sh @@ -11,7 +11,7 @@ test_description='git read-tree --prefix test. test_expect_success setup ' echo hello >one && git update-index --add one && - tree=`git write-tree` && + tree=$(git write-tree) && echo tree is $tree ' diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index 3e72aff4..c70cf423 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -30,7 +30,7 @@ test_expect_success 'two-way not clobbering' ' echo >file2 master creates untracked file2 && echo >subdir/file2 master creates untracked subdir/file2 && - if err=`read_tree_u_must_succeed -m -u master side 2>&1` + if err=$(read_tree_u_must_succeed -m -u master side 2>&1) then echo should have complained false @@ -43,7 +43,7 @@ echo file2 >.gitignore test_expect_success 'two-way with incorrect --exclude-per-directory (1)' ' - if err=`read_tree_u_must_succeed -m --exclude-per-directory=.gitignore master side 2>&1` + if err=$(read_tree_u_must_succeed -m --exclude-per-directory=.gitignore master side 2>&1) then echo should have complained false @@ -54,7 +54,7 @@ test_expect_success 'two-way with incorrect --exclude-per-directory (1)' ' test_expect_success 'two-way with incorrect --exclude-per-directory (2)' ' - if err=`read_tree_u_must_succeed -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1` + if err=$(read_tree_u_must_succeed -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1) then echo should have complained false @@ -95,7 +95,7 @@ test_expect_success 'three-way not clobbering a working tree file' ' git checkout master && echo >file3 file three created in master, untracked && echo >subdir/file3 file three created in master, untracked && - if err=`read_tree_u_must_succeed -m -u branch-point master side 2>&1` + if err=$(read_tree_u_must_succeed -m -u branch-point master side 2>&1) then echo should have complained false diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh new file mode 100755 index 00000000..20526aed --- /dev/null +++ b/t/t1013-read-tree-submodule.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +test_description='read-tree can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +test_submodule_switch "git read-tree -u -m" + +test_submodule_forced_switch "git read-tree -u --reset" + +test_done diff --git a/t/t1014-read-tree-confusing.sh b/t/t1014-read-tree-confusing.sh deleted file mode 100755 index 2f5a25d5..00000000 --- a/t/t1014-read-tree-confusing.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh - -test_description='check that read-tree rejects confusing paths' -. ./test-lib.sh - -test_expect_success 'create base tree' ' - echo content >file && - git add file && - git commit -m base && - blob=$(git rev-parse HEAD:file) && - tree=$(git rev-parse HEAD^{tree}) -' - -test_expect_success 'enable core.protectHFS for rejection tests' ' - git config core.protectHFS true -' - -test_expect_success 'enable core.protectNTFS for rejection tests' ' - git config core.protectNTFS true -' - -while read path pretty; do - : ${pretty:=$path} - case "$path" in - *SPACE) - path="${path%SPACE} " - ;; - esac - test_expect_success "reject $pretty at end of path" ' - printf "100644 blob %s\t%s" "$blob" "$path" >tree && - bogus=$(git mktree <tree) && - test_must_fail git read-tree $bogus - ' - - test_expect_success "reject $pretty as subtree" ' - printf "040000 tree %s\t%s" "$tree" "$path" >tree && - bogus=$(git mktree <tree) && - test_must_fail git read-tree $bogus - ' -done <<-EOF -. -.. -.git -.GIT -${u200c}.Git {u200c}.Git -.gI${u200c}T .gI{u200c}T -.GiT${u200c} .GiT{u200c} -git~1 -.git.SPACE .git.{space} -.\\\\.GIT\\\\foobar backslashes -.git\\\\foobar backslashes2 -EOF - -test_expect_success 'utf-8 paths allowed with core.protectHFS off' ' - test_when_finished "git read-tree HEAD" && - test_config core.protectHFS false && - printf "100644 blob %s\t%s" "$blob" ".gi${u200c}t" >tree && - ok=$(git mktree <tree) && - git read-tree $ok -' - -test_done diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index 6902320e..62c0d25a 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -20,27 +20,27 @@ test_expect_success setup ' test_expect_success 'update-index and ls-files' ' git update-index --add one && - case "`git ls-files`" in + case "$(git ls-files)" in one) echo pass one ;; *) echo bad one; exit 1 ;; esac && ( cd dir && git update-index --add two && - case "`git ls-files`" in + case "$(git ls-files)" in two) echo pass two ;; *) echo bad two; exit 1 ;; esac ) && - case "`git ls-files`" in + case "$(git ls-files)" in dir/two"$LF"one) echo pass both ;; *) echo bad; exit 1 ;; esac ' test_expect_success 'cat-file' ' - two=`git ls-files -s dir/two` && - two=`expr "$two" : "[0-7]* \\([0-9a-f]*\\)"` && + two=$(git ls-files -s dir/two) && + two=$(expr "$two" : "[0-7]* \\([0-9a-f]*\\)") && echo "$two" && git cat-file -p "$two" >actual && cmp dir/two actual && @@ -55,18 +55,18 @@ rm -f actual dir/actual test_expect_success 'diff-files' ' echo a >>one && echo d >>dir/two && - case "`git diff-files --name-only`" in + case "$(git diff-files --name-only)" in dir/two"$LF"one) echo pass top ;; *) echo bad top; exit 1 ;; esac && # diff should not omit leading paths ( cd dir && - case "`git diff-files --name-only`" in + case "$(git diff-files --name-only)" in dir/two"$LF"one) echo pass subdir ;; *) echo bad subdir; exit 1 ;; esac && - case "`git diff-files --name-only .`" in + case "$(git diff-files --name-only .)" in dir/two) echo pass subdir limited ;; *) echo bad subdir limited; exit 1 ;; esac @@ -74,11 +74,11 @@ test_expect_success 'diff-files' ' ' test_expect_success 'write-tree' ' - top=`git write-tree` && + top=$(git write-tree) && echo $top && ( cd dir && - sub=`git write-tree` && + sub=$(git write-tree) && echo $sub && test "z$top" = "z$sub" ) @@ -96,7 +96,7 @@ test_expect_success 'checkout-index' ' test_expect_success 'read-tree' ' rm -f one dir/two && - tree=`git write-tree` && + tree=$(git write-tree) && read_tree_u_must_succeed --reset -u "$tree" && cmp one original.one && cmp dir/two original.two && diff --git a/t/t1050-large.sh b/t/t1050-large.sh index fd105280..aea49364 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -131,7 +131,7 @@ test_expect_success 'git-show a large file' ' ' test_expect_success 'index-pack' ' - git clone file://"`pwd`"/.git foo && + git clone file://"$(pwd)"/.git foo && GIT_DIR=non-existent git index-pack --strict --verify foo/.git/objects/pack/*.pack ' @@ -140,7 +140,7 @@ test_expect_success 'repack' ' ' test_expect_success 'pack-objects with large loose object' ' - SHA1=`git hash-object huge` && + SHA1=$(git hash-object huge) && test_create_repo loose && echo $SHA1 | git pack-objects --stdout | GIT_ALLOC_LIMIT=0 GIT_DIR=loose/.git git unpack-objects && diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 58cd5435..3f80ff0c 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -1158,4 +1158,14 @@ test_expect_failure 'adding a key into an empty section reuses header' ' test_cmp expect .git/config ' +test_expect_success POSIXPERM,PERL 'preserves existing permissions' ' + chmod 0600 .git/config && + git config imap.pass Hunter2 && + perl -e \ + "die q(badset) if ((stat(q(.git/config)))[2] & 07777) != 0600" && + git config --rename-section imap pop && + perl -e \ + "die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600" +' + test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index e130c528..0218e963 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -235,7 +235,7 @@ test_expect_success \ 'rm -f o e && git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e && test '"$B"' = $(cat o) && - test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"' + test "warning: Log for ref '"$m has gap after $gd"'." = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:38:00}" (middle of history)' \ 'rm -f o e && @@ -253,7 +253,7 @@ test_expect_success \ 'rm -f o e && git rev-parse --verify "master@{2005-05-28}" >o 2>e && test '"$D"' = $(cat o) && - test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"' + test "warning: Log for ref '"$m unexpectedly ended on $ld"'." = "$(cat e)"' rm -f .git/$m .git/logs/$m expect @@ -350,22 +350,28 @@ test_expect_success 'stdin fails on unknown command' ' grep "fatal: unknown command: unknown $a" err ' -test_expect_success 'stdin fails on badly quoted input' ' +test_expect_success 'stdin fails on unbalanced quotes' ' echo "create $a \"master" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && grep "fatal: badly quoted argument: \\\"master" err ' -test_expect_success 'stdin fails on arguments not separated by space' ' +test_expect_success 'stdin fails on invalid escape' ' + echo "create $a \"ma\zter\"" >stdin && + test_must_fail git update-ref --stdin <stdin 2>err && + grep "fatal: badly quoted argument: \\\"ma\\\\zter\\\"" err +' + +test_expect_success 'stdin fails on junk after quoted argument' ' echo "create \"$a\"master" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: expected SP but got: master" err + grep "fatal: unexpected character after quoted argument: \\\"$a\\\"master" err ' test_expect_success 'stdin fails create with no ref' ' echo "create " >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: create line missing <ref>" err + grep "fatal: create: missing <ref>" err ' test_expect_success 'stdin fails create with bad ref name' ' @@ -377,19 +383,19 @@ test_expect_success 'stdin fails create with bad ref name' ' test_expect_success 'stdin fails create with no new value' ' echo "create $a" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: create $a missing <newvalue>" err + grep "fatal: create $a: missing <newvalue>" err ' test_expect_success 'stdin fails create with too many arguments' ' echo "create $a $m $m" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: create $a has extra input: $m" err + grep "fatal: create $a: extra input: $m" err ' test_expect_success 'stdin fails update with no ref' ' echo "update " >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: update line missing <ref>" err + grep "fatal: update: missing <ref>" err ' test_expect_success 'stdin fails update with bad ref name' ' @@ -401,19 +407,19 @@ test_expect_success 'stdin fails update with bad ref name' ' test_expect_success 'stdin fails update with no new value' ' echo "update $a" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: update $a missing <newvalue>" err + grep "fatal: update $a: missing <newvalue>" err ' test_expect_success 'stdin fails update with too many arguments' ' echo "update $a $m $m $m" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: update $a has extra input: $m" err + grep "fatal: update $a: extra input: $m" err ' test_expect_success 'stdin fails delete with no ref' ' echo "delete " >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: delete line missing <ref>" err + grep "fatal: delete: missing <ref>" err ' test_expect_success 'stdin fails delete with bad ref name' ' @@ -425,13 +431,13 @@ test_expect_success 'stdin fails delete with bad ref name' ' test_expect_success 'stdin fails delete with too many arguments' ' echo "delete $a $m $m" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: delete $a has extra input: $m" err + grep "fatal: delete $a: extra input: $m" err ' test_expect_success 'stdin fails verify with too many arguments' ' echo "verify $a $m $m" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: verify $a has extra input: $m" err + grep "fatal: verify $a: extra input: $m" err ' test_expect_success 'stdin fails option with unknown name' ' @@ -458,6 +464,24 @@ test_expect_success 'stdin create ref works' ' test_cmp expect actual ' +test_expect_success 'stdin succeeds with quoted argument' ' + git update-ref -d $a && + echo "create $a \"$m\"" >stdin && + git update-ref --stdin <stdin && + git rev-parse $m >expect && + git rev-parse $a >actual && + test_cmp expect actual +' + +test_expect_success 'stdin succeeds with escaped character' ' + git update-ref -d $a && + echo "create $a \"ma\\163ter\"" >stdin && + git update-ref --stdin <stdin && + git rev-parse $m >expect && + git rev-parse $a >actual && + test_cmp expect actual +' + test_expect_success 'stdin update ref creates with zero old value' ' echo "update $b $m $Z" >stdin && git update-ref --stdin <stdin && @@ -494,21 +518,21 @@ test_expect_success 'stdin update ref fails with wrong old value' ' test_expect_success 'stdin update ref fails with bad old value' ' echo "update $c $m does-not-exist" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: invalid old value for ref $c: does-not-exist" err && + grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err && test_must_fail git rev-parse --verify -q $c ' test_expect_success 'stdin create ref fails with bad new value' ' echo "create $c does-not-exist" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: invalid new value for ref $c: does-not-exist" err && + grep "fatal: create $c: invalid <newvalue>: does-not-exist" err && test_must_fail git rev-parse --verify -q $c ' test_expect_success 'stdin create ref fails with zero new value' ' echo "create $c " >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: create $c given zero new value" err && + grep "fatal: create $c: zero <newvalue>" err && test_must_fail git rev-parse --verify -q $c ' @@ -532,7 +556,7 @@ test_expect_success 'stdin delete ref fails with wrong old value' ' test_expect_success 'stdin delete ref fails with zero old value' ' echo "delete $a " >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: delete $a given zero old value" err && + grep "fatal: delete $a: zero <oldvalue>" err && git rev-parse $m >expect && git rev-parse $a >actual && test_cmp expect actual @@ -673,7 +697,7 @@ test_expect_success 'stdin -z fails on unknown command' ' test_expect_success 'stdin -z fails create with no ref' ' printf $F "create " >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: create line missing <ref>" err + grep "fatal: create: missing <ref>" err ' test_expect_success 'stdin -z fails create with bad ref name' ' @@ -685,7 +709,7 @@ test_expect_success 'stdin -z fails create with bad ref name' ' test_expect_success 'stdin -z fails create with no new value' ' printf $F "create $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: create $a missing <newvalue>" err + grep "fatal: create $a: unexpected end of input when reading <newvalue>" err ' test_expect_success 'stdin -z fails create with too many arguments' ' @@ -697,25 +721,39 @@ test_expect_success 'stdin -z fails create with too many arguments' ' test_expect_success 'stdin -z fails update with no ref' ' printf $F "update " >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: update line missing <ref>" err + grep "fatal: update: missing <ref>" err +' + +test_expect_success 'stdin -z fails update with too few args' ' + printf $F "update $a" "$m" >stdin && + test_must_fail git update-ref -z --stdin <stdin 2>err && + grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err ' test_expect_success 'stdin -z fails update with bad ref name' ' - printf $F "update ~a" "$m" >stdin && + printf $F "update ~a" "$m" "" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && grep "fatal: invalid ref format: ~a" err ' +test_expect_success 'stdin -z emits warning with empty new value' ' + git update-ref $a $m && + printf $F "update $a" "" "" >stdin && + git update-ref -z --stdin <stdin 2>err && + grep "warning: update $a: missing <newvalue>, treating as zero" err && + test_must_fail git rev-parse --verify -q $a +' + test_expect_success 'stdin -z fails update with no new value' ' printf $F "update $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: update $a missing <newvalue>" err + grep "fatal: update $a: unexpected end of input when reading <newvalue>" err ' test_expect_success 'stdin -z fails update with no old value' ' printf $F "update $a" "$m" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: update $a missing \\[<oldvalue>\\] NUL" err + grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err ' test_expect_success 'stdin -z fails update with too many arguments' ' @@ -727,7 +765,7 @@ test_expect_success 'stdin -z fails update with too many arguments' ' test_expect_success 'stdin -z fails delete with no ref' ' printf $F "delete " >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: delete line missing <ref>" err + grep "fatal: delete: missing <ref>" err ' test_expect_success 'stdin -z fails delete with bad ref name' ' @@ -739,7 +777,7 @@ test_expect_success 'stdin -z fails delete with bad ref name' ' test_expect_success 'stdin -z fails delete with no old value' ' printf $F "delete $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: delete $a missing \\[<oldvalue>\\] NUL" err + grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err ' test_expect_success 'stdin -z fails delete with too many arguments' ' @@ -757,7 +795,7 @@ test_expect_success 'stdin -z fails verify with too many arguments' ' test_expect_success 'stdin -z fails verify with no old value' ' printf $F "verify $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: verify $a missing \\[<oldvalue>\\] NUL" err + grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err ' test_expect_success 'stdin -z fails option with unknown name' ' @@ -816,7 +854,7 @@ test_expect_success 'stdin -z update ref fails with wrong old value' ' test_expect_success 'stdin -z update ref fails with bad old value' ' printf $F "update $c" "$m" "does-not-exist" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: invalid old value for ref $c: does-not-exist" err && + grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err && test_must_fail git rev-parse --verify -q $c ' @@ -834,14 +872,14 @@ test_expect_success 'stdin -z create ref fails with bad new value' ' git update-ref -d "$c" && printf $F "create $c" "does-not-exist" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: invalid new value for ref $c: does-not-exist" err && + grep "fatal: create $c: invalid <newvalue>: does-not-exist" err && test_must_fail git rev-parse --verify -q $c ' -test_expect_success 'stdin -z create ref fails with zero new value' ' +test_expect_success 'stdin -z create ref fails with empty new value' ' printf $F "create $c" "" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: create $c given zero new value" err && + grep "fatal: create $c: missing <newvalue>" err && test_must_fail git rev-parse --verify -q $c ' @@ -865,7 +903,7 @@ test_expect_success 'stdin -z delete ref fails with wrong old value' ' test_expect_success 'stdin -z delete ref fails with zero old value' ' printf $F "delete $a" "$Z" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: delete $a given zero old value" err && + grep "fatal: delete $a: zero <oldvalue>" err && git rev-parse $m >expect && git rev-parse $a >actual && test_cmp expect actual @@ -923,7 +961,7 @@ test_expect_success 'stdin -z update refs works with identity updates' ' test_expect_success 'stdin -z update refs fails with wrong old value' ' git update-ref $c $m && - printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "" "$Z" >stdin && + printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err && git rev-parse $m >expect && diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index 1a5a5f39..4bc71412 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -48,6 +48,7 @@ invalid_ref './foo/bar' invalid_ref 'foo/./bar' invalid_ref 'foo/bar/.' invalid_ref '.refs/foo' +invalid_ref 'refs/heads/foo.' invalid_ref 'heads/foo..bar' invalid_ref 'heads/foo?bar' valid_ref 'foo./bar' diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 236b13a3..8cab06f9 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -245,4 +245,12 @@ test_expect_success 'gc.reflogexpire=false' ' ' +test_expect_success 'checkout should not delete log for packed ref' ' + test $(git reflog master | wc -l) = 4 && + git branch foo && + git pack-refs --all && + git checkout foo && + test $(git reflog master | wc -l) = 4 +' + test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 983568a4..8c739c96 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -251,40 +251,35 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' ' ) ' -while read name path pretty; do - while read mode type; do - : ${pretty:=$path} - test_expect_success "fsck notices $pretty as $type" ' - ( - git init $name-$type && - cd $name-$type && - echo content >file && - git add file && - git commit -m base && - blob=$(git rev-parse :file) && - tree=$(git rev-parse HEAD^{tree}) && - value=$(eval "echo \$$type") && - printf "$mode $type %s\t%s" "$value" "$path" >bad && - bad_tree=$(git mktree <bad) && - git fsck 2>out && - cat out && - grep "warning.*tree $bad_tree" out - )' - done <<-\EOF - 100644 blob - 040000 tree - EOF -done <<-EOF -dot . -dotdot .. -dotgit .git -dotgit-case .GIT -dotgit-unicode .gI${u200c}T .gI{u200c}T -dotgit-case2 .Git -git-tilde1 git~1 -dotgitdot .git. -dot-backslash-case .\\\\.GIT\\\\foobar -dotgit-case-backslash .git\\\\foobar -EOF +test_expect_success 'fsck notices "." and ".." in trees' ' + ( + git init dots && + cd dots && + blob=$(echo foo | git hash-object -w --stdin) && + tab=$(printf "\\t") && + git mktree <<-EOF && + 100644 blob $blob$tab. + 100644 blob $blob$tab.. + EOF + git fsck 2>out && + cat out && + grep "warning.*\\." out + ) +' + +test_expect_success 'fsck notices ".git" in trees' ' + ( + git init dotgit && + cd dotgit && + blob=$(echo foo | git hash-object -w --stdin) && + tab=$(printf "\\t") && + git mktree <<-EOF && + 100644 blob $blob$tab.git + EOF + git fsck 2>out && + cat out && + grep "warning.*\\.git" out + ) +' test_done diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh new file mode 100755 index 00000000..94fb473e --- /dev/null +++ b/t/t1700-split-index.sh @@ -0,0 +1,194 @@ +#!/bin/sh + +test_description='split index mode tests' + +. ./test-lib.sh + +# We need total control of index splitting here +sane_unset GIT_TEST_SPLIT_INDEX + +test_expect_success 'enable split index' ' + git update-index --split-index && + test-dump-split-index .git/index >actual && + cat >expect <<EOF && +own 8299b0bcd1ac364e5f1d7768efb62fa2da79a339 +base 39d890139ee5356c7ef572216cebcd27aa41f9df +replacements: +deletions: +EOF + test_cmp expect actual +' + +test_expect_success 'add one file' ' + : >one && + git update-index --add one && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +EOF + test_cmp ls-files.expect ls-files.actual && + + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<EOF && +base 39d890139ee5356c7ef572216cebcd27aa41f9df +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +replacements: +deletions: +EOF + test_cmp expect actual +' + +test_expect_success 'disable split index' ' + git update-index --no-split-index && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +EOF + test_cmp ls-files.expect ls-files.actual && + + BASE=`test-dump-split-index .git/index | grep "^own" | sed "s/own/base/"` && + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<EOF && +not a split index +EOF + test_cmp expect actual +' + +test_expect_success 'enable split index again, "one" now belongs to base index"' ' + git update-index --split-index && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +EOF + test_cmp ls-files.expect ls-files.actual && + + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<EOF && +$BASE +replacements: +deletions: +EOF + test_cmp expect actual +' + +test_expect_success 'modify original file, base index untouched' ' + echo modified >one && + git update-index one && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one +EOF + test_cmp ls-files.expect ls-files.actual && + + test-dump-split-index .git/index | sed "/^own/d" >actual && + q_to_tab >expect <<EOF && +$BASE +100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q +replacements: 0 +deletions: +EOF + test_cmp expect actual +' + +test_expect_success 'add another file, which stays index' ' + : >two && + git update-index --add two && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two +EOF + test_cmp ls-files.expect ls-files.actual && + + test-dump-split-index .git/index | sed "/^own/d" >actual && + q_to_tab >expect <<EOF && +$BASE +100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two +replacements: 0 +deletions: +EOF + test_cmp expect actual +' + +test_expect_success 'remove file not in base index' ' + git update-index --force-remove two && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one +EOF + test_cmp ls-files.expect ls-files.actual && + + test-dump-split-index .git/index | sed "/^own/d" >actual && + q_to_tab >expect <<EOF && +$BASE +100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q +replacements: 0 +deletions: +EOF + test_cmp expect actual +' + +test_expect_success 'remove file in base index' ' + git update-index --force-remove one && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +EOF + test_cmp ls-files.expect ls-files.actual && + + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<EOF && +$BASE +replacements: +deletions: 0 +EOF + test_cmp expect actual +' + +test_expect_success 'add original file back' ' + : >one && + git update-index --add one && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +EOF + test_cmp ls-files.expect ls-files.actual && + + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<EOF && +$BASE +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +replacements: +deletions: 0 +EOF + test_cmp expect actual +' + +test_expect_success 'add new file' ' + : >two && + git update-index --add two && + git ls-files --stage >actual && + cat >expect <<EOF && +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two +EOF + test_cmp expect actual +' + +test_expect_success 'unify index, two files remain' ' + git update-index --no-split-index && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<EOF && +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two +EOF + test_cmp ls-files.expect ls-files.actual + + test-dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<EOF && +not a split index +EOF + test_cmp expect actual +' + +test_done diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh index 06b18f8b..6847f758 100755 --- a/t/t2013-checkout-submodule.sh +++ b/t/t2013-checkout-submodule.sh @@ -3,6 +3,7 @@ test_description='checkout can handle submodules' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh test_expect_success 'setup' ' mkdir submodule && @@ -62,4 +63,8 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ ! test -s actual ' +test_submodule_switch "git checkout" + +test_submodule_forced_switch "git checkout -f" + test_done diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh index 29c1fb10..cc830da5 100755 --- a/t/t2104-update-index-skip-worktree.sh +++ b/t/t2104-update-index-skip-worktree.sh @@ -7,6 +7,8 @@ test_description='skip-worktree bit test' . ./test-lib.sh +sane_unset GIT_TEST_SPLIT_INDEX + test_set_index_version 3 cat >expect.full <<EOF diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh index e35d7811..8d44e043 100755 --- a/t/t3302-notes-index-expensive.sh +++ b/t/t3302-notes-index-expensive.sh @@ -7,9 +7,7 @@ test_description='Test commit notes index (expensive!)' . ./test-lib.sh -test_set_prereq NOT_EXPENSIVE test -n "$GIT_NOTES_TIMING_TESTS" && test_set_prereq EXPENSIVE -test -x /usr/bin/time && test_set_prereq USR_BIN_TIME create_repo () { number_of_commits=$1 @@ -17,43 +15,43 @@ create_repo () { test -d .git || { git init && ( - while [ $nr -lt $number_of_commits ]; do + while test $nr -lt $number_of_commits + do nr=$(($nr+1)) mark=$(($nr+$nr)) notemark=$(($mark+1)) test_tick && - cat <<INPUT_END && -commit refs/heads/master -mark :$mark -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -commit #$nr -COMMIT - -M 644 inline file -data <<EOF -file in commit #$nr -EOF - -blob -mark :$notemark -data <<EOF -note for commit #$nr -EOF - -INPUT_END - - echo "N :$notemark :$mark" >> note_commit + cat <<-INPUT_END && + commit refs/heads/master + mark :$mark + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit #$nr + COMMIT + + M 644 inline file + data <<EOF + file in commit #$nr + EOF + + blob + mark :$notemark + data <<EOF + note for commit #$nr + EOF + + INPUT_END + echo "N :$notemark :$mark" >>note_commit done && test_tick && - cat <<INPUT_END && -commit refs/notes/commits -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes -COMMIT + cat <<-INPUT_END && + commit refs/notes/commits + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes + COMMIT -INPUT_END + INPUT_END cat note_commit ) | @@ -65,62 +63,74 @@ INPUT_END test_notes () { count=$1 && git config core.notesRef refs/notes/commits && - git log | grep "^ " > output && + git log | grep "^ " >output && i=$count && - while [ $i -gt 0 ]; do + while test $i -gt 0 + do echo " commit #$i" && echo " note for commit #$i" && - i=$(($i-1)); - done > expect && + i=$(($i-1)) + done >expect && test_cmp expect output } -cat > time_notes << \EOF +write_script time_notes <<\EOF mode=$1 i=1 - while [ $i -lt $2 ]; do + while test $i -lt $2 + do case $1 in no-notes) - GIT_NOTES_REF=non-existing; export GIT_NOTES_REF - ;; + GIT_NOTES_REF=non-existing + export GIT_NOTES_REF + ;; notes) unset GIT_NOTES_REF - ;; + ;; esac - git log >/dev/null + git log i=$(($i+1)) - done + done >/dev/null EOF time_notes () { for mode in no-notes notes do echo $mode - /usr/bin/time "$SHELL_PATH" ../time_notes $mode $1 + /usr/bin/time ../time_notes $mode $1 done } do_tests () { - pr=$1 - count=$2 - - test_expect_success $pr 'setup / mkdir' ' - mkdir $count && - cd $count + count=$1 pr=${2-} + + test_expect_success $pr "setup $count" ' + mkdir "$count" && + ( + cd "$count" && + create_repo "$count" + ) ' - test_expect_success $pr "setup $count" "create_repo $count" - - test_expect_success $pr 'notes work' "test_notes $count" - - test_expect_success USR_BIN_TIME,$pr 'notes timing with /usr/bin/time' "time_notes 100" + test_expect_success $pr 'notes work' ' + ( + cd "$count" && + test_notes "$count" + ) + ' - test_expect_success $pr 'teardown / cd ..' 'cd ..' + test_expect_success "USR_BIN_TIME${pr:+,$pr}" 'notes timing with /usr/bin/time' ' + ( + cd "$count" && + time_notes 100 + ) + ' } -do_tests NOT_EXPENSIVE 10 -for count in 100 1000 10000; do - do_tests EXPENSIVE $count +do_tests 10 +for count in 100 1000 10000 +do + do_tests "$count" EXPENSIVE done test_done diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 80e0a951..47b56826 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -169,6 +169,29 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea test_cmp expect actual ' +test_expect_success 'cherry-picked commits and fork-point work together' ' + git checkout default-base && + echo Amended >A && + git commit -a --no-edit --amend && + test_commit B B && + test_commit new_B B "New B" && + test_commit C C && + git checkout default && + git reset --hard default-base@{4} && + test_commit D D && + git cherry-pick -2 default-base^ && + test_commit final_B B "Final B" && + git rebase && + echo Amended >expect && + test_cmp A expect && + echo "Final B" >expect && + test_cmp B expect && + echo C >expect && + test_cmp C expect && + echo D >expect && + test_cmp D expect +' + test_expect_success 'rebase -q is quiet' ' git checkout -b quiet topic && git rebase -q master >output.out 2>&1 && diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index c0023a5b..8197ed29 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -67,6 +67,14 @@ test_expect_success 'setup' ' SHELL= export SHELL +test_expect_success 'rebase --keep-empty' ' + git checkout -b emptybranch master && + git commit --allow-empty -m "empty" && + git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 6 actual +' + test_expect_success 'rebase -i with the exec command' ' git checkout master && ( diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh index e70ac10a..9292b499 100755 --- a/t/t3419-rebase-patch-id.sh +++ b/t/t3419-rebase-patch-id.sh @@ -4,12 +4,9 @@ test_description='git rebase - test patch id computation' . ./test-lib.sh -test_set_prereq NOT_EXPENSIVE test -n "$GIT_PATCHID_TIMING_TESTS" && test_set_prereq EXPENSIVE -test -x /usr/bin/time && test_set_prereq USR_BIN_TIME -count() -{ +count () { i=0 while test $i -lt $1 do @@ -18,8 +15,7 @@ count() done } -scramble() -{ +scramble () { i=0 while read x do @@ -28,12 +24,11 @@ scramble() echo "$x" fi i=$((($i+1) % 10)) - done < "$1" > "$1.new" + done <"$1" >"$1.new" mv -f "$1.new" "$1" } -run() -{ +run () { echo \$ "$@" /usr/bin/time "$@" >/dev/null } @@ -43,10 +38,8 @@ test_expect_success 'setup' ' git tag root ' -do_tests() -{ - pr=$1 - nlines=$2 +do_tests () { + nlines=$1 pr=${2-} test_expect_success $pr "setup: $nlines lines" " rm -f .gitattributes && @@ -103,7 +96,7 @@ do_tests() " } -do_tests NOT_EXPENSIVE 500 -do_tests EXPENSIVE 50000 +do_tests 500 +do_tests 50000 EXPENSIVE test_done diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh new file mode 100755 index 00000000..d5b896d4 --- /dev/null +++ b/t/t3426-rebase-submodule.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +test_description='rebase can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +git_rebase () { + git status -su >expect && + ls -1pR * >>expect && + git checkout -b ours HEAD && + echo x >>file1 && + git add file1 && + git commit -m add_x && + git revert HEAD && + git status -su >actual && + ls -1pR * >>actual && + test_cmp expect actual && + git rebase "$1" +} + +test_submodule_switch "git_rebase" + +git_rebase_interactive () { + git status -su >expect && + ls -1pR * >>expect && + git checkout -b ours HEAD && + echo x >>file1 && + git add file1 && + git commit -m add_x && + git revert HEAD && + git status -su >actual && + ls -1pR * >>actual && + test_cmp expect actual && + set_fake_editor && + echo "fake-editor.sh" >.git/info/exclude && + git rebase -i "$1" +} + +KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 +# The real reason "replace directory with submodule" fails is because a +# directory "sub1" exists, but we reuse the suppression added for merge here +test_submodule_switch "git_rebase_interactive" + +test_done diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh index 19c99d7e..b457333e 100755 --- a/t/t3508-cherry-pick-many-commits.sh +++ b/t/t3508-cherry-pick-many-commits.sh @@ -65,12 +65,15 @@ test_expect_success 'output to keep user entertained during multi-pick' ' cat <<-\EOF >expected && [master OBJID] second Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:14:13 2005 -0700 1 file changed, 1 insertion(+) [master OBJID] third Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:15:13 2005 -0700 1 file changed, 1 insertion(+) [master OBJID] fourth Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:16:13 2005 -0700 1 file changed, 1 insertion(+) EOF @@ -98,14 +101,17 @@ test_expect_success 'output during multi-pick indicates merge strategy' ' Trying simple merge. [master OBJID] second Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:14:13 2005 -0700 1 file changed, 1 insertion(+) Trying simple merge. [master OBJID] third Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:15:13 2005 -0700 1 file changed, 1 insertion(+) Trying simple merge. [master OBJID] fourth Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:16:13 2005 -0700 1 file changed, 1 insertion(+) EOF diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh new file mode 100755 index 00000000..6863b7bb --- /dev/null +++ b/t/t3512-cherry-pick-submodule.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +test_description='cherry-pick can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1 +KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 +KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 +test_submodule_switch "git cherry-pick" + +test_done diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh new file mode 100755 index 00000000..a1c4e021 --- /dev/null +++ b/t/t3513-revert-submodule.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description='revert can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +# Create a revert that moves from HEAD (including any test modifications to +# the work tree) to $1 by first checking out $1 and reverting it. Reverting +# the revert is the transition we test for. We tar the current work tree +# first so we can restore the work tree test setup after doing the checkout +# and revert. We test here that the restored work tree content is identical +# to that at the beginning. The last revert is then tested by the framework. +git_revert () { + git status -su >expect && + ls -1pR * >>expect && + tar czf "$TRASH_DIRECTORY/tmp.tgz" * && + git checkout "$1" && + git revert HEAD && + rm -rf * && + tar xzf "$TRASH_DIRECTORY/tmp.tgz" && + git status -su >actual && + ls -1pR * >>actual && + test_cmp expect actual && + git revert HEAD +} + +KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1 +KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 +test_submodule_switch "git_revert" + +test_done diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index 31a5770b..55c8a2f5 100755 --- a/t/t3901-i18n-patch.sh +++ b/t/t3901-i18n-patch.sh @@ -54,10 +54,13 @@ test_expect_success setup ' git add yours && git commit -s -m "Second on side" && - # the second one on the side branch is ISO-8859-1 - git config i18n.commitencoding ISO8859-1 && - # use author and committer name in ISO-8859-1 to match it. - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + if test_have_prereq NOT_MINGW + then + # the second one on the side branch is ISO-8859-1 + git config i18n.commitencoding ISO8859-1 && + # use author and committer name in ISO-8859-1 to match it. + . "$TEST_DIRECTORY"/t3901-8859-1.txt + fi && test_tick && echo Yet another >theirs && git add theirs && @@ -119,7 +122,7 @@ test_expect_success 'rebase (U/L)' ' check_encoding 2 ' -test_expect_success 'rebase (L/L)' ' +test_expect_success NOT_MINGW 'rebase (L/L)' ' # In this test we want ISO-8859-1 encoded commits as the result git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding ISO8859-1 && @@ -131,7 +134,7 @@ test_expect_success 'rebase (L/L)' ' check_encoding 2 8859 ' -test_expect_success 'rebase (L/U)' ' +test_expect_success NOT_MINGW 'rebase (L/U)' ' # This is pathological -- use UTF-8 as intermediate form # to get ISO-8859-1 results. git config i18n.commitencoding ISO8859-1 && @@ -159,7 +162,7 @@ test_expect_success 'cherry-pick(U/U)' ' check_encoding 3 ' -test_expect_success 'cherry-pick(L/L)' ' +test_expect_success NOT_MINGW 'cherry-pick(L/L)' ' # Both the commitencoding and logoutputencoding is set to ISO-8859-1 git config i18n.commitencoding ISO8859-1 && @@ -189,7 +192,7 @@ test_expect_success 'cherry-pick(U/L)' ' check_encoding 3 ' -test_expect_success 'cherry-pick(L/U)' ' +test_expect_success NOT_MINGW 'cherry-pick(L/U)' ' # Again, the commitencoding is set to ISO-8859-1 but # logoutputencoding is set to UTF-8. diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index a5e7e6b2..f372fc8c 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -96,8 +96,8 @@ test_expect_success 'stash pop after save --include-untracked leaves files untra git stash pop && git status --porcelain >actual && test_cmp expect actual && - test "1" = "`cat file2`" && - test untracked = "`cat untracked/untracked`" + test "1" = "$(cat file2)" && + test untracked = "$(cat untracked/untracked)" ' git clean --force --quiet -d diff --git a/t/t3906-stash-submodule.sh b/t/t3906-stash-submodule.sh new file mode 100755 index 00000000..d7219d6f --- /dev/null +++ b/t/t3906-stash-submodule.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description='stash apply can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +git_stash () { + git status -su >expect && + ls -1pR * >>expect && + git read-tree -u -m "$1" && + git stash && + git status -su >actual && + ls -1pR * >>actual && + test_cmp expect actual && + git stash apply +} + +KNOWN_FAILURE_STASH_DOES_IGNORE_SUBMODULE_CHANGES=1 +KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1 +KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 +test_submodule_switch "git_stash" + +test_done diff --git a/t/t3910-mac-os-precompose.sh b/t/t3910-mac-os-precompose.sh index e4ba6013..83193566 100755 --- a/t/t3910-mac-os-precompose.sh +++ b/t/t3910-mac-os-precompose.sh @@ -14,13 +14,13 @@ then fi # create utf-8 variables -Adiarnfc=`printf '\303\204'` -Adiarnfd=`printf 'A\314\210'` +Adiarnfc=$(printf '\303\204') +Adiarnfd=$(printf 'A\314\210') -Odiarnfc=`printf '\303\226'` -Odiarnfd=`printf 'O\314\210'` -AEligatu=`printf '\303\206'` -Invalidu=`printf '\303\377'` +Odiarnfc=$(printf '\303\226') +Odiarnfd=$(printf 'O\314\210') +AEligatu=$(printf '\303\206') +Invalidu=$(printf '\303\377') #Create a string with 255 bytes (decomposed) @@ -35,7 +35,7 @@ Alongc=$Alongc$Alongc$Alongc$Alongc$Alongc #250 Byte Alongc=$Alongc$AEligatu$AEligatu #254 Byte test_expect_success "detect if nfd needed" ' - precomposeunicode=`git config core.precomposeunicode` && + precomposeunicode=$(git config core.precomposeunicode) && test "$precomposeunicode" = true && git config core.precomposeunicode true ' @@ -140,13 +140,23 @@ test_expect_success "Add long precomposed filename" ' git add * && git commit -m "Long filename" ' + +test_expect_failure 'handle existing decomposed filenames' ' + echo content >"verbatim.$Adiarnfd" && + git -c core.precomposeunicode=false add "verbatim.$Adiarnfd" && + git commit -m "existing decomposed file" && + >expect && + git ls-files --exclude-standard -o "verbatim*" >untracked && + test_cmp expect untracked +' + # Test if the global core.precomposeunicode stops autosensing # Must be the last test case test_expect_success "respect git config --global core.precomposeunicode" ' git config --global core.precomposeunicode true && rm -rf .git && git init && - precomposeunicode=`git config core.precomposeunicode` && + precomposeunicode=$(git config core.precomposeunicode) && test "$precomposeunicode" = "true" ' diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh index 05911492..76f643b2 100755 --- a/t/t4006-diff-mode.sh +++ b/t/t4006-diff-mode.sh @@ -13,7 +13,7 @@ sed_script='s/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' test_expect_success 'setup' ' echo frotz >rezrov && git update-index --add rezrov && - tree=`git write-tree` && + tree=$(git write-tree) && echo $tree ' diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 2bb97365..bf078418 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -18,7 +18,7 @@ test_expect_success \ mkdir path1 && echo rezrov >path1/file1 && git update-index --add file0 path1/file1 && - tree=`git write-tree` && + tree=$(git write-tree) && echo "$tree" && echo nitfol >file0 && echo yomin >path1/file1 && @@ -131,7 +131,7 @@ test_expect_success 'diff multiple wildcard pathspecs' ' mkdir path2 && echo rezrov >path2/file1 && git update-index --add path2/file1 && - tree3=`git write-tree` && + tree3=$(git write-tree) && git diff --name-only $tree $tree3 -- "path2*1" "path1*1" >actual && cat <<-\EOF >expect && path1/file1 diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index 1215ae54..643d7291 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -67,18 +67,18 @@ test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' ' git diff >output && sed -e "s/-CIT/xCIT/" <output >broken && test_must_fail git apply --stat --summary broken 2>detected && - detected=`cat detected` && - detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` && - detected=`sed -ne "${detected}p" broken` && + detected=$(cat detected) && + detected=$(expr "$detected" : "fatal.*at line \\([0-9]*\\)\$") && + detected=$(sed -ne "${detected}p" broken) && test "$detected" = xCIT ' test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' ' git diff --binary | sed -e "s/-CIT/xCIT/" >broken && test_must_fail git apply --stat --summary broken 2>detected && - detected=`cat detected` && - detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` && - detected=`sed -ne "${detected}p" broken` && + detected=$(cat detected) && + detected=$(expr "$detected" : "fatal.*at line \\([0-9]*\\)\$") && + detected=$(sed -ne "${detected}p" broken) && test "$detected" = xCIT ' @@ -88,7 +88,7 @@ test_expect_success 'initial commit' 'git commit -a -m initial' test_expect_success 'diff-index with --binary' ' echo AIT >a && mv b e && echo CIT >c && cat e >d && git update-index --add --remove a b c d e && - tree0=`git write-tree` && + tree0=$(git write-tree) && git diff --cached --binary >current && git apply --stat --summary current ' @@ -96,7 +96,7 @@ test_expect_success 'diff-index with --binary' ' test_expect_success 'apply binary patch' ' git reset --hard && git apply --binary --index <current && - tree1=`git write-tree` && + tree1=$(git write-tree) && test "$tree1" = "$tree0" ' diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index f7a9af89..6ec60721 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -107,14 +107,14 @@ test_expect_success setup ' +*++ [initial] Initial EOF -V=`git version | sed -e 's/^git version //' -e 's/\./\\./g'` +V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g') while read cmd do case "$cmd" in '' | '#'*) continue ;; esac - test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'` - pfx=`printf "%04d" $test_count` + test=$(echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g') + pfx=$(printf "%04d" $test_count) expect="$TEST_DIRECTORY/t4013/diff.$test" actual="$pfx-diff.$test" diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 9c806331..256affce 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -43,7 +43,7 @@ test_expect_success setup ' test_expect_success "format-patch --ignore-if-in-upstream" ' git format-patch --stdout master..side >patch0 && - cnt=`grep "^From " patch0 | wc -l` && + cnt=$(grep "^From " patch0 | wc -l) && test $cnt = 3 ' @@ -52,7 +52,7 @@ test_expect_success "format-patch --ignore-if-in-upstream" ' git format-patch --stdout \ --ignore-if-in-upstream master..side >patch1 && - cnt=`grep "^From " patch1 | wc -l` && + cnt=$(grep "^From " patch1 | wc -l) && test $cnt = 2 ' @@ -69,7 +69,7 @@ test_expect_success "format-patch doesn't consider merge commits" ' git checkout -b merger master && test_tick && git merge --no-ff slave && - cnt=`git format-patch -3 --stdout | grep "^From " | wc -l` && + cnt=$(git format-patch -3 --stdout | grep "^From " | wc -l) && test $cnt = 3 ' @@ -77,7 +77,7 @@ test_expect_success "format-patch result applies" ' git checkout -b rebuild-0 master && git am -3 patch0 && - cnt=`git rev-list master.. | wc -l` && + cnt=$(git rev-list master.. | wc -l) && test $cnt = 2 ' @@ -85,7 +85,7 @@ test_expect_success "format-patch --ignore-if-in-upstream result applies" ' git checkout -b rebuild-1 master && git am -3 patch1 && - cnt=`git rev-list master.. | wc -l` && + cnt=$(git rev-list master.. | wc -l) && test $cnt = 2 ' @@ -762,6 +762,67 @@ test_expect_success 'format-patch --signature="" suppresses signatures' ' ! grep "^-- \$" output ' +test_expect_success 'prepare mail-signature input' ' + cat >mail-signature <<-\EOF + + Test User <test.email@kernel.org> + http://git.kernel.org/cgit/git/git.git + + git.kernel.org/?p=git/git.git;a=summary + + EOF +' + +test_expect_success '--signature-file=file works' ' + git format-patch --stdout --signature-file=mail-signature -1 >output && + check_patch output && + sed -e "1,/^-- \$/d" <output >actual && + { + cat mail-signature && echo + } >expect && + test_cmp expect actual +' + +test_expect_success 'format.signaturefile works' ' + test_config format.signaturefile mail-signature && + git format-patch --stdout -1 >output && + check_patch output && + sed -e "1,/^-- \$/d" <output >actual && + { + cat mail-signature && echo + } >expect && + test_cmp expect actual +' + +test_expect_success '--no-signature suppresses format.signaturefile ' ' + test_config format.signaturefile mail-signature && + git format-patch --stdout --no-signature -1 >output && + check_patch output && + ! grep "^-- \$" output +' + +test_expect_success '--signature-file overrides format.signaturefile' ' + cat >other-mail-signature <<-\EOF + Use this other signature instead of mail-signature. + EOF + test_config format.signaturefile mail-signature && + git format-patch --stdout \ + --signature-file=other-mail-signature -1 >output && + check_patch output && + sed -e "1,/^-- \$/d" <output >actual && + { + cat other-mail-signature && echo + } >expect && + test_cmp expect actual +' + +test_expect_success '--signature overrides format.signaturefile' ' + test_config format.signaturefile mail-signature && + git format-patch --stdout --signature="my sig" -1 >output && + check_patch output && + grep "my sig" output +' + test_expect_success TTY 'format-patch --stdout paginates' ' rm -f pager_used && test_terminal env GIT_PAGER="wc >pager_used" git format-patch --stdout --all && diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 34591c23..1dbaa386 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -52,15 +52,15 @@ do echo "*.java diff=$p" >.gitattributes && test_expect_code 1 git diff --no-index \ A.java B.java 2>msg && - ! test_i18ngrep fatal msg && - ! test_i18ngrep error msg + test_i18ngrep ! fatal msg && + test_i18ngrep ! error msg ' test_expect_success "builtin $p wordRegex pattern compiles" ' echo "*.java diff=$p" >.gitattributes && test_expect_code 1 git diff --no-index --word-diff \ A.java B.java 2>msg && - ! test_i18ngrep fatal msg && - ! test_i18ngrep error msg + test_i18ngrep ! fatal msg && + test_i18ngrep ! error msg ' done diff --git a/t/t4036-format-patch-signer-mime.sh b/t/t4036-format-patch-signer-mime.sh index ba43f185..98d9713d 100755 --- a/t/t4036-format-patch-signer-mime.sh +++ b/t/t4036-format-patch-signer-mime.sh @@ -42,7 +42,7 @@ test_expect_success 'attach and signoff do not duplicate mime headers' ' GIT_COMMITTER_NAME="はまの ふにおう" \ git format-patch -s --stdout -1 --attach >output && - test `grep -ci ^MIME-Version: output` = 1 + test $(grep -ci ^MIME-Version: output) = 1 ' diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index 1019d7b3..41913c3a 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -94,7 +94,7 @@ test_expect_success 'setup for --cc --raw' ' blob=$(echo file | git hash-object --stdin -w) && base_tree=$(echo "100644 blob $blob file" | git mktree) && trees= && - for i in `test_seq 1 40` + for i in $(test_seq 1 40) do blob=$(echo file$i | git hash-object --stdin -w) && trees="$trees$(echo "100644 blob $blob file" | git mktree)$LF" diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 463d63bd..e4328964 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -26,8 +26,10 @@ add_file () { echo "$name" >"$name" && git add "$name" && test_tick && - msg_added_iso88591=$(echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding) && - git -c "i18n.commitEncoding=$test_encoding" commit -m "$msg_added_iso88591" + # "git commit -m" would break MinGW, as Windows refuse to pass + # $test_encoding encoded parameter to git. + echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | + git -c "i18n.commitEncoding=$test_encoding" commit -F - done >/dev/null && git rev-parse --short --verify HEAD ) diff --git a/t/t4057-diff-combined-paths.sh b/t/t4057-diff-combined-paths.sh index 097e6321..dff36b77 100755 --- a/t/t4057-diff-combined-paths.sh +++ b/t/t4057-diff-combined-paths.sh @@ -5,7 +5,7 @@ test_description='combined diff show only paths that are different to all parent . ./test-lib.sh # verify that diffc.expect matches output of -# `git diff -c --name-only HEAD HEAD^ HEAD^2` +# $(git diff -c --name-only HEAD HEAD^ HEAD^2) diffc_verify () { git diff -c --name-only HEAD HEAD^ HEAD^2 >diffc.actual && test_cmp diffc.expect diffc.actual diff --git a/t/t4102-apply-rename.sh b/t/t4102-apply-rename.sh index 49e2d6c3..fae30597 100755 --- a/t/t4102-apply-rename.sh +++ b/t/t4102-apply-rename.sh @@ -52,6 +52,6 @@ EOF test_expect_success 'apply copy' \ 'git apply --index --stat --summary --apply test-patch && - test "$(cat bar)" = "This is bar" -a "$(cat foo)" = "This is foo"' + test "$(cat bar)" = "This is bar" && test "$(cat foo)" = "This is foo"' test_done diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh index 1e4d4380..ce8567f4 100755 --- a/t/t4116-apply-reverse.sh +++ b/t/t4116-apply-reverse.sh @@ -30,10 +30,10 @@ test_expect_success setup ' test_expect_success 'apply in forward' ' - T0=`git rev-parse "second^{tree}"` && + T0=$(git rev-parse "second^{tree}") && git reset --hard initial && git apply --index --binary patch && - T1=`git write-tree` && + T1=$(git write-tree) && test "$T0" = "$T1" ' @@ -62,22 +62,22 @@ test_expect_success 'setup separate repository lacking postimage' ' test_expect_success 'apply in forward without postimage' ' - T0=`git rev-parse "second^{tree}"` && + T0=$(git rev-parse "second^{tree}") && ( cd initial && git apply --index --binary ../patch && - T1=`git write-tree` && + T1=$(git write-tree) && test "$T0" = "$T1" ) ' test_expect_success 'apply in reverse without postimage' ' - T0=`git rev-parse "initial^{tree}"` && + T0=$(git rev-parse "initial^{tree}") && ( cd second && git apply --index --binary --reverse ../patch && - T1=`git write-tree` && + T1=$(git write-tree) && test "$T0" = "$T1" ) ' diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 3d0384da..c393be69 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -68,7 +68,7 @@ test_expect_success 'apply --whitespace=strip from config' ' check_result sub/file1 ' -D=`pwd` +D=$(pwd) test_expect_success 'apply --whitespace=strip in subdir' ' diff --git a/t/t4137-apply-submodule.sh b/t/t4137-apply-submodule.sh new file mode 100755 index 00000000..a9bd40a6 --- /dev/null +++ b/t/t4137-apply-submodule.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='git apply handling submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +apply_index () { + git diff --ignore-submodules=dirty "..$1" | git apply --index - +} + +test_submodule_switch "apply_index" + +apply_3way () { + git diff --ignore-submodules=dirty "..$1" | git apply --3way - +} + +test_submodule_switch "apply_3way" + +test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index 97fcb31d..565c020c 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -93,7 +93,7 @@ test_expect_success 'output from user-defined format is re-wrapped' ' test_cmp expect log.predictable ' -test_expect_success 'shortlog wrapping' ' +test_expect_success NOT_MINGW 'shortlog wrapping' ' cat >expect <<\EOF && A U Thor (5): Test @@ -114,7 +114,7 @@ EOF test_cmp expect out ' -test_expect_success 'shortlog from non-git directory' ' +test_expect_success NOT_MINGW 'shortlog from non-git directory' ' git log HEAD >log && GIT_DIR=non-existing git shortlog -w <log >out && test_cmp expect out @@ -159,7 +159,7 @@ $DSCHO (2): EOF -test_expect_success 'shortlog encoding' ' +test_expect_success NOT_MINGW 'shortlog encoding' ' git reset --hard "$commit" && git config --unset i18n.commitencoding && echo 2 > a1 && diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index d2c930de..baa9d3c8 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -5,27 +5,44 @@ test_description='git patch-id' . ./test-lib.sh test_expect_success 'setup' ' - test_commit initial foo a && - test_commit first foo b && - git checkout -b same HEAD^ && - test_commit same-msg foo b && - git checkout -b notsame HEAD^ && - test_commit notsame-msg foo c + as="a a a a a a a a" && # eight a + test_write_lines $as >foo && + test_write_lines $as >bar && + git add foo bar && + git commit -a -m initial && + test_write_lines $as b >foo && + test_write_lines $as b >bar && + git commit -a -m first && + git checkout -b same master && + git commit --amend -m same-msg && + git checkout -b notsame master && + echo c >foo && + echo c >bar && + git commit --amend -a -m notsame-msg && + test_write_lines bar foo >bar-then-foo && + test_write_lines foo bar >foo-then-bar ' test_expect_success 'patch-id output is well-formed' ' - git log -p -1 | git patch-id > output && + git log -p -1 | git patch-id >output && grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output ' +#calculate patch id. Make sure output is not empty. calc_patch_id () { - git patch-id | - sed "s# .*##" > patch-id_"$1" + name="$1" + shift + git patch-id "$@" | + sed "s/ .*//" >patch-id_"$name" && + test_line_count -gt 0 patch-id_"$name" +} + +get_top_diff () { + git log -p -1 "$@" -O bar-then-foo -- } get_patch_id () { - git log -p -1 "$1" | git patch-id | - sed "s# .*##" > patch-id_"$1" + get_top_diff "$1" | calc_patch_id "$@" } test_expect_success 'patch-id detects equality' ' @@ -45,8 +62,8 @@ test_expect_success 'patch-id supports git-format-patch output' ' git checkout same && git format-patch -1 --stdout | calc_patch_id same && test_cmp patch-id_master patch-id_same && - set `git format-patch -1 --stdout | git patch-id` && - test "$2" = `git rev-parse HEAD` + set $(git format-patch -1 --stdout | git patch-id) && + test "$2" = $(git rev-parse HEAD) ' test_expect_success 'whitespace is irrelevant in footer' ' @@ -56,6 +73,69 @@ test_expect_success 'whitespace is irrelevant in footer' ' test_cmp patch-id_master patch-id_same ' +cmp_patch_id () { + if + test "$1" = "relevant" + then + ! test_cmp patch-id_"$2" patch-id_"$3" + else + test_cmp patch-id_"$2" patch-id_"$3" + fi +} + +test_patch_id_file_order () { + relevant="$1" + shift + name="order-${1}-$relevant" + shift + get_top_diff "master" | calc_patch_id "$name" "$@" && + git checkout same && + git format-patch -1 --stdout -O foo-then-bar | + calc_patch_id "ordered-$name" "$@" && + cmp_patch_id $relevant "$name" "ordered-$name" + +} + +# combined test for options: add more tests here to make them +# run with all options +test_patch_id () { + test_patch_id_file_order "$@" +} + +# small tests with detailed diagnostic for basic options. +test_expect_success 'file order is irrelevant with --stable' ' + test_patch_id_file_order irrelevant --stable --stable +' + +test_expect_success 'file order is relevant with --unstable' ' + test_patch_id_file_order relevant --unstable --unstable +' + +#Now test various option combinations. +test_expect_success 'default is unstable' ' + test_patch_id relevant default +' + +test_expect_success 'patchid.stable = true is stable' ' + test_config patchid.stable true && + test_patch_id irrelevant patchid.stable=true +' + +test_expect_success 'patchid.stable = false is unstable' ' + test_config patchid.stable false && + test_patch_id relevant patchid.stable=false +' + +test_expect_success '--unstable overrides patchid.stable = true' ' + test_config patchid.stable true && + test_patch_id relevant patchid.stable=true--unstable --unstable +' + +test_expect_success '--stable overrides patchid.stable = false' ' + test_config patchid.stable false && + test_patch_id irrelevant patchid.stable=false--stable --stable +' + test_expect_success 'patch-id supports git-format-patch MIME output' ' get_patch_id master && git checkout same && diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index c84ec9ae..349c5319 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -31,7 +31,7 @@ test_expect_success 'set up basic repos' ' git add foo && test_tick && git config i18n.commitEncoding $test_encoding && - git commit -m "$(commit_msg $test_encoding)" && + commit_msg $test_encoding | git commit -F - && git add bar && test_tick && git commit -m "add bar" && diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh index 52a74729..9110404e 100755 --- a/t/t4210-log-i18n.sh +++ b/t/t4210-log-i18n.sh @@ -34,7 +34,7 @@ test_expect_success 'log --grep searches in log output encoding (utf8)' ' test_cmp expect actual ' -test_expect_success 'log --grep searches in log output encoding (latin1)' ' +test_expect_success NOT_MINGW 'log --grep searches in log output encoding (latin1)' ' cat >expect <<-\EOF && latin1 utf8 @@ -43,7 +43,7 @@ test_expect_success 'log --grep searches in log output encoding (latin1)' ' test_cmp expect actual ' -test_expect_success 'log --grep does not find non-reencoded values (utf8)' ' +test_expect_success NOT_MINGW 'log --grep does not find non-reencoded values (utf8)' ' >expect && git log --encoding=utf8 --format=%s --grep=$latin1_e >actual && test_cmp expect actual diff --git a/t/t4255-am-submodule.sh b/t/t4255-am-submodule.sh new file mode 100755 index 00000000..8bde7dbb --- /dev/null +++ b/t/t4255-am-submodule.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +test_description='git am handling submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +am () { + git format-patch --stdout --ignore-submodules=dirty "..$1" | git am - +} + +test_submodule_switch "am" + +am_3way () { + git format-patch --stdout --ignore-submodules=dirty "..$1" | git am --3way - +} + +KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 +test_submodule_switch "am_3way" + +test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 4efaf8c5..7b8babd8 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -72,7 +72,7 @@ check_tar() { for header in *.paxheader do data=${header%.paxheader}.data && - if test -h $data -o -e $data + if test -h $data || test -e $data then path=$(get_pax_header $header path) && if test -n "$path" @@ -119,14 +119,10 @@ test_expect_success \ 'echo ignore me >a/ignored && echo ignored export-ignore >.git/info/attributes' -test_expect_success \ - 'add files to repository' \ - 'find a -type f | xargs git update-index --add && - find a -type l | xargs git update-index --add && - treeid=`git write-tree` && - echo $treeid >treeid && - git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \ - git commit-tree $treeid </dev/null)' +test_expect_success 'add files to repository' ' + git add a && + GIT_COMMITTER_DATE="2005-05-27 22:00" git commit -m initial +' test_expect_success 'setup export-subst' ' echo "substfile?" export-subst >>.git/info/attributes && @@ -207,7 +203,7 @@ test_expect_success \ test_expect_success 'clients cannot access unreachable commits' ' test_commit unreachable && - sha1=`git rev-parse HEAD` && + sha1=$(git rev-parse HEAD) && git reset --hard HEAD^ && git archive $sha1 >remote.tar && test_must_fail git archive --remote=. $sha1 >remote.tar @@ -215,7 +211,7 @@ test_expect_success 'clients cannot access unreachable commits' ' test_expect_success 'upload-archive can allow unreachable commits' ' test_commit unreachable1 && - sha1=`git rev-parse HEAD` && + sha1=$(git rev-parse HEAD) && git reset --hard HEAD^ && git archive $sha1 >remote.tar && test_config uploadarchive.allowUnreachable true && diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index 21a5c93f..c929db56 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -61,14 +61,10 @@ test_expect_success \ 'echo ignore me >a/ignored && echo ignored export-ignore >.git/info/attributes' -test_expect_success \ - 'add files to repository' \ - 'find a -type f | xargs git update-index --add && - find a -type l | xargs git update-index --add && - treeid=`git write-tree` && - echo $treeid >treeid && - git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \ - git commit-tree $treeid </dev/null)' +test_expect_success 'add files to repository' ' + git add a && + GIT_COMMITTER_DATE="2005-05-27 22:00" git commit -m initial +' test_expect_success 'setup export-subst' ' echo "substfile?" export-subst >>.git/info/attributes && diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index f4f02ba9..0580258c 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -18,7 +18,7 @@ test_expect_success 'setup repo with moderate-sized history' ' git checkout master && blob=$(echo tagged-blob | git hash-object -w --stdin) && git tag tagged-blob $blob && - git config pack.writebitmaps true && + git config repack.writebitmaps true && git config pack.writebitmaphashcache true ' diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index 1753ef2b..fc898c9e 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -39,7 +39,7 @@ test_expect_success 'post-checkout receives the right arguments with HEAD unchan old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && - test $old = $new -a $flag = 1 + test $old = $new && test $flag = 1 ' test_expect_success 'post-checkout runs as expected ' ' @@ -52,7 +52,7 @@ test_expect_success 'post-checkout args are correct with git checkout -b ' ' old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && - test $old = $new -a $flag = 1 + test $old = $new && test $flag = 1 ' test_expect_success 'post-checkout receives the right args with HEAD changed ' ' @@ -60,7 +60,7 @@ test_expect_success 'post-checkout receives the right args with HEAD changed ' ' old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && - test $old != $new -a $flag = 1 + test $old != $new && test $flag = 1 ' test_expect_success 'post-checkout receives the right args when not switching branches ' ' @@ -68,7 +68,7 @@ test_expect_success 'post-checkout receives the right args when not switching br old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && - test $old = $new -a $flag = 0 + test $old = $new && test $flag = 0 ' if test "$(git config --bool core.filemode)" = true; then diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 29d59ef9..d78f3201 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -447,6 +447,43 @@ test_expect_success 'explicit pull should update tracking' ' ) ' +test_expect_success 'explicit --refmap is allowed only with command-line refspec' ' + cd "$D" && + ( + cd three && + test_must_fail git fetch --refmap="*:refs/remotes/none/*" + ) +' + +test_expect_success 'explicit --refmap option overrides remote.*.fetch' ' + cd "$D" && + git branch -f side && + ( + cd three && + git update-ref refs/remotes/origin/master base-origin-master && + o=$(git rev-parse --verify refs/remotes/origin/master) && + git fetch --refmap="refs/heads/*:refs/remotes/other/*" origin master && + n=$(git rev-parse --verify refs/remotes/origin/master) && + test "$o" = "$n" && + test_must_fail git rev-parse --verify refs/remotes/origin/side && + git rev-parse --verify refs/remotes/other/master + ) +' + +test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' ' + cd "$D" && + git branch -f side && + ( + cd three && + git update-ref refs/remotes/origin/master base-origin-master && + o=$(git rev-parse --verify refs/remotes/origin/master) && + git fetch --refmap="" origin master && + n=$(git rev-parse --verify refs/remotes/origin/master) && + test "$o" = "$n" && + test_must_fail git rev-parse --verify refs/remotes/origin/side + ) +' + test_expect_success 'configured fetch updates tracking' ' cd "$D" && diff --git a/t/t5511-refspec.sh b/t/t5511-refspec.sh index c2893221..de6db86c 100755 --- a/t/t5511-refspec.sh +++ b/t/t5511-refspec.sh @@ -5,7 +5,6 @@ test_description='refspec parsing' . ./test-lib.sh test_refspec () { - kind=$1 refspec=$2 expect=$3 git config remote.frotz.url "." && git config --remove-section remote.frotz && @@ -84,4 +83,9 @@ test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*' test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*' +good=$(printf '\303\204') +test_refspec fetch "refs/heads/${good}" +bad=$(printf '\011tab') +test_refspec fetch "refs/heads/${bad}" invalid + test_done diff --git a/t/t5539-fetch-http-shallow.sh b/t/t5539-fetch-http-shallow.sh index 94553e10..b4611884 100755 --- a/t/t5539-fetch-http-shallow.sh +++ b/t/t5539-fetch-http-shallow.sh @@ -54,6 +54,7 @@ EOF test_expect_success 'no shallow lines after receiving ACK ready' ' ( cd shallow && + test_tick && for i in $(test_seq 15) do git checkout --orphan unrelated$i && diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 1a3a2b6c..ac71418a 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -171,5 +171,30 @@ test_expect_success 'did not use upload-pack service' ' test_cmp exp act ' +test_expect_success 'git client shows text/plain errors' ' + test_must_fail git clone "$HTTPD_URL/error/text" 2>stderr && + grep "this is the error message" stderr +' + +test_expect_success 'git client does not show html errors' ' + test_must_fail git clone "$HTTPD_URL/error/html" 2>stderr && + ! grep "this is the error message" stderr +' + +test_expect_success 'git client shows text/plain with a charset' ' + test_must_fail git clone "$HTTPD_URL/error/charset" 2>stderr && + grep "this is the error message" stderr +' + +test_expect_success 'http error messages are reencoded' ' + test_must_fail git clone "$HTTPD_URL/error/utf16" 2>stderr && + grep "this is the error message" stderr +' + +test_expect_success 'reencoding is robust to whitespace oddities' ' + test_must_fail git clone "$HTTPD_URL/error/odd-spacing" 2>stderr && + grep "this is the error message" stderr +' + stop_httpd test_done diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index e07eaf35..6cbc12d9 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -213,8 +213,6 @@ test_expect_success 'cookies stored in http.cookiefile when http.savecookies set test_cmp expect_cookies.txt cookies_tail.txt ' -test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE - test_expect_success EXPENSIVE 'create 50,000 tags in the repo' ' ( cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && @@ -240,8 +238,7 @@ test_expect_success EXPENSIVE 'create 50,000 tags in the repo' ' ' test_expect_success EXPENSIVE 'clone the 50,000 tag repo to check OS command line overflow' ' - git clone $HTTPD_URL/smart/repo.git too-many-refs 2>err && - test_line_count = 0 err && + git clone $HTTPD_URL/smart/repo.git too-many-refs && ( cd too-many-refs && test $(git for-each-ref refs/tags | wc -l) = 50000 diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh new file mode 100755 index 00000000..accfa5cc --- /dev/null +++ b/t/t5572-pull-submodule.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +test_description='pull can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +reset_branch_to_HEAD () { + git branch -D "$1" && + git checkout -b "$1" HEAD && + git branch --set-upstream-to="origin/$1" "$1" +} + +git_pull () { + reset_branch_to_HEAD "$1" && + git pull +} + +# pulls without conflicts +test_submodule_switch "git_pull" + +git_pull_ff () { + reset_branch_to_HEAD "$1" && + git pull --ff +} + +test_submodule_switch "git_pull_ff" + +git_pull_ff_only () { + reset_branch_to_HEAD "$1" && + git pull --ff-only +} + +test_submodule_switch "git_pull_ff_only" + +git_pull_noff () { + reset_branch_to_HEAD "$1" && + git pull --no-ff +} + +KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 +KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 +test_submodule_switch "git_pull_noff" + +test_done diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index a00a6607..24194075 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -87,13 +87,29 @@ test_expect_success 'push new branch by name' ' compare_refs local HEAD server refs/heads/new-name ' -test_expect_failure 'push new branch with old:new refspec' ' +test_expect_success 'push new branch with old:new refspec' ' (cd local && git push origin new-name:new-refspec ) && compare_refs local HEAD server refs/heads/new-refspec ' +test_expect_success 'push new branch with HEAD:new refspec' ' + (cd local && + git checkout new-name + git push origin HEAD:new-refspec-2 + ) && + compare_refs local HEAD server refs/heads/new-refspec-2 +' + +test_expect_success 'push delete branch' ' + (cd local && + git push origin :new-name + ) && + test_must_fail git --git-dir="server/.git" \ + rev-parse --verify refs/heads/new-name +' + test_expect_success 'forced push' ' (cd local && git checkout -b force-test && diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 88ed3191..a02a45af 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -35,13 +35,13 @@ test_expect_success 'setup' ' : >foo && git add foo && git config i18n.commitEncoding $test_encoding && - git commit -m "$added_iso88591" && + echo "$added_iso88591" | git commit -F - && head1=$(git rev-parse --verify HEAD) && head1_short=$(git rev-parse --verify --short $head1) && tree1=$(git rev-parse --verify HEAD:) && tree1_short=$(git rev-parse --verify --short $tree1) && echo "$changed" > foo && - git commit -a -m "$changed_iso88591" && + echo "$changed_iso88591" | git commit -a -F - && head2=$(git rev-parse --verify HEAD) && head2_short=$(git rev-parse --verify --short $head2) && tree2=$(git rev-parse --verify HEAD:) && diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 432f086c..37589617 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -77,12 +77,29 @@ test_expect_success "merge without conflict (--quiet)" \ "git merge-file --quiet test.txt orig.txt new2.txt" cp new1.txt test2.txt -test_expect_success "merge without conflict (missing LF at EOF)" \ - "git merge-file test2.txt orig.txt new2.txt" +test_expect_failure "merge without conflict (missing LF at EOF)" \ + "git merge-file test2.txt orig.txt new4.txt" -test_expect_success "merge result added missing LF" \ +test_expect_failure "merge result added missing LF" \ "test_cmp test.txt test2.txt" +cp new4.txt test3.txt +test_expect_success "merge without conflict (missing LF at EOF, away from change in the other file)" \ + "git merge-file --quiet test3.txt new2.txt new3.txt" + +cat > expect.txt << EOF +DOMINUS regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +EOF +printf "propter nomen suum." >> expect.txt + +test_expect_success "merge does not add LF away of change" \ + "test_cmp test3.txt expect.txt" + cp test.txt backup.txt test_expect_success "merge with conflicts" \ "test_must_fail git merge-file test.txt orig.txt new3.txt" @@ -107,6 +124,55 @@ EOF test_expect_success "expected conflict markers" "test_cmp test.txt expect.txt" cp backup.txt test.txt + +cat > expect.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF +test_expect_success "merge conflicting with --ours" \ + "git merge-file --ours test.txt orig.txt new3.txt && test_cmp test.txt expect.txt" +cp backup.txt test.txt + +cat > expect.txt << EOF +DOMINUS regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF +test_expect_success "merge conflicting with --theirs" \ + "git merge-file --theirs test.txt orig.txt new3.txt && test_cmp test.txt expect.txt" +cp backup.txt test.txt + +cat > expect.txt << EOF +Dominus regit me, et nihil mihi deerit. +DOMINUS regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF +test_expect_success "merge conflicting with --union" \ + "git merge-file --union test.txt orig.txt new3.txt && test_cmp test.txt expect.txt" +cp backup.txt test.txt + test_expect_success "merge with conflicts, using -L" \ "test_must_fail git merge-file -L 1 -L 2 test.txt orig.txt new3.txt" @@ -260,4 +326,23 @@ test_expect_success 'marker size' ' test_cmp expect actual ' +printf "line1\nline2\nline3" >nolf-orig.txt +printf "line1\nline2\nline3x" >nolf-diff1.txt +printf "line1\nline2\nline3y" >nolf-diff2.txt + +test_expect_success 'conflict at EOF without LF resolved by --ours' \ + 'git merge-file -p --ours nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && + printf "line1\nline2\nline3x" >expect.txt && + test_cmp expect.txt output.txt' + +test_expect_success 'conflict at EOF without LF resolved by --theirs' \ + 'git merge-file -p --theirs nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && + printf "line1\nline2\nline3y" >expect.txt && + test_cmp expect.txt output.txt' + +test_expect_success 'conflict at EOF without LF resolved by --union' \ + 'git merge-file -p --union nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && + printf "line1\nline2\nline3x\nline3y" >expect.txt && + test_cmp expect.txt output.txt' + test_done diff --git a/t/t6041-bisect-submodule.sh b/t/t6041-bisect-submodule.sh new file mode 100755 index 00000000..c6b7aa69 --- /dev/null +++ b/t/t6041-bisect-submodule.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description='bisect can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +git_bisect () { + git status -su >expect && + ls -1pR * >>expect && + tar czf "$TRASH_DIRECTORY/tmp.tgz" * && + GOOD=$(git rev-parse --verify HEAD) && + git checkout "$1" && + echo "foo" >bar && + git add bar && + git commit -m "bisect bad" && + BAD=$(git rev-parse --verify HEAD) && + git reset --hard HEAD^^ && + git submodule update && + git bisect start && + git bisect good $GOOD && + rm -rf * && + tar xzf "$TRASH_DIRECTORY/tmp.tgz" && + git status -su >actual && + ls -1pR * >>actual && + test_cmp expect actual && + git bisect bad $BAD +} + +test_submodule_switch "git_bisect" + +test_done diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 719a1167..4d5a25ee 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -7,8 +7,9 @@ test_description='Tests replace refs functionality' exec </dev/null . ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" -add_and_commit_file() +add_and_commit_file () { _file="$1" _msg="$2" @@ -18,6 +19,38 @@ add_and_commit_file() git commit --quiet -m "$_file: $_msg" } +commit_buffer_contains_parents () +{ + git cat-file commit "$1" >payload && + sed -n -e '/^$/q' -e '/^parent /p' <payload >actual && + shift && + for _parent + do + echo "parent $_parent" + done >expected && + test_cmp expected actual +} + +commit_peeling_shows_parents () +{ + _parent_number=1 + _commit="$1" + shift && + for _parent + do + _found=$(git rev-parse --verify $_commit^$_parent_number) || return 1 + test "$_found" = "$_parent" || return 1 + _parent_number=$(( $_parent_number + 1 )) + done && + test_must_fail git rev-parse --verify $_commit^$_parent_number +} + +commit_has_parents () +{ + commit_buffer_contains_parents "$@" && + commit_peeling_shows_parents "$@" +} + HASH1= HASH2= HASH3= @@ -27,36 +60,36 @@ HASH6= HASH7= test_expect_success 'set up buggy branch' ' - echo "line 1" >> hello && - echo "line 2" >> hello && - echo "line 3" >> hello && - echo "line 4" >> hello && + echo "line 1" >>hello && + echo "line 2" >>hello && + echo "line 3" >>hello && + echo "line 4" >>hello && add_and_commit_file hello "4 lines" && HASH1=$(git rev-parse --verify HEAD) && - echo "line BUG" >> hello && - echo "line 6" >> hello && - echo "line 7" >> hello && - echo "line 8" >> hello && + echo "line BUG" >>hello && + echo "line 6" >>hello && + echo "line 7" >>hello && + echo "line 8" >>hello && add_and_commit_file hello "4 more lines with a BUG" && HASH2=$(git rev-parse --verify HEAD) && - echo "line 9" >> hello && - echo "line 10" >> hello && + echo "line 9" >>hello && + echo "line 10" >>hello && add_and_commit_file hello "2 more lines" && HASH3=$(git rev-parse --verify HEAD) && - echo "line 11" >> hello && + echo "line 11" >>hello && add_and_commit_file hello "1 more line" && HASH4=$(git rev-parse --verify HEAD) && - sed -e "s/BUG/5/" hello > hello.new && + sed -e "s/BUG/5/" hello >hello.new && mv hello.new hello && add_and_commit_file hello "BUG fixed" && HASH5=$(git rev-parse --verify HEAD) && - echo "line 12" >> hello && - echo "line 13" >> hello && + echo "line 12" >>hello && + echo "line 13" >>hello && add_and_commit_file hello "2 more lines" && HASH6=$(git rev-parse --verify HEAD) && - echo "line 14" >> hello && - echo "line 15" >> hello && - echo "line 16" >> hello && + echo "line 14" >>hello && + echo "line 15" >>hello && + echo "line 16" >>hello && add_and_commit_file hello "again 3 more lines" && HASH7=$(git rev-parse --verify HEAD) ' @@ -95,7 +128,7 @@ test_expect_success 'tag replaced commit' ' ' test_expect_success '"git fsck" works' ' - git fsck master > fsck_master.out && + git fsck master >fsck_master.out && grep "dangling commit $R" fsck_master.out && grep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out && test -z "$(git fsck)" @@ -217,14 +250,14 @@ test_expect_success 'fetch branch with replacement' ' ( cd clone_dir && git fetch origin refs/heads/tofetch:refs/heads/parallel3 && - git log --pretty=oneline parallel3 > output.txt && + git log --pretty=oneline parallel3 >output.txt && ! grep $PARA3 output.txt && - git show $PARA3 > para3.txt && + git show $PARA3 >para3.txt && grep "A U Thor" para3.txt && git fetch origin "refs/replace/*:refs/replace/*" && - git log --pretty=oneline parallel3 > output.txt && + git log --pretty=oneline parallel3 >output.txt && grep $PARA3 output.txt && - git show $PARA3 > para3.txt && + git show $PARA3 >para3.txt && grep "O Thor" para3.txt ) ' @@ -302,7 +335,7 @@ test_expect_success 'test --format medium' ' echo "$PARA3 -> $S" && echo "$MYTAG -> $HASH1" } | sort >expected && - git replace -l --format medium | sort > actual && + git replace -l --format medium | sort >actual && test_cmp expected actual ' @@ -314,14 +347,97 @@ test_expect_success 'test --format long' ' echo "$PARA3 (commit) -> $S (commit)" && echo "$MYTAG (tag) -> $HASH1 (commit)" } | sort >expected && - git replace --format=long | sort > actual && + git replace --format=long | sort >actual && test_cmp expected actual ' +test_expect_success 'setup a fake editor' ' + write_script fakeeditor <<-\EOF + sed -e "s/A U Thor/A fake Thor/" "$1" >"$1.new" + mv "$1.new" "$1" + EOF +' + +test_expect_success '--edit with and without already replaced object' ' + test_must_fail env GIT_EDITOR=./fakeeditor git replace --edit "$PARA3" && + GIT_EDITOR=./fakeeditor git replace --force --edit "$PARA3" && + git replace -l | grep "$PARA3" && + git cat-file commit "$PARA3" | grep "A fake Thor" && + git replace -d "$PARA3" && + GIT_EDITOR=./fakeeditor git replace --edit "$PARA3" && + git replace -l | grep "$PARA3" && + git cat-file commit "$PARA3" | grep "A fake Thor" +' + +test_expect_success '--edit and change nothing or command failed' ' + git replace -d "$PARA3" && + test_must_fail env GIT_EDITOR=true git replace --edit "$PARA3" && + test_must_fail env GIT_EDITOR="./fakeeditor;false" git replace --edit "$PARA3" && + GIT_EDITOR=./fakeeditor git replace --edit "$PARA3" && + git replace -l | grep "$PARA3" && + git cat-file commit "$PARA3" | grep "A fake Thor" +' + test_expect_success 'replace ref cleanup' ' test -n "$(git replace)" && git replace -d $(git replace) && test -z "$(git replace)" ' +test_expect_success '--graft with and without already replaced object' ' + test $(git log --oneline | wc -l) = 7 && + git replace --graft $HASH5 && + test $(git log --oneline | wc -l) = 3 && + commit_has_parents $HASH5 && + test_must_fail git replace --graft $HASH5 $HASH4 $HASH3 && + git replace --force -g $HASH5 $HASH4 $HASH3 && + commit_has_parents $HASH5 $HASH4 $HASH3 && + git replace -d $HASH5 +' + +test_expect_success GPG 'set up a signed commit' ' + echo "line 17" >>hello && + echo "line 18" >>hello && + git add hello && + test_tick && + git commit --quiet -S -m "hello: 2 more lines in a signed commit" && + HASH8=$(git rev-parse --verify HEAD) && + git verify-commit $HASH8 +' + +test_expect_success GPG '--graft with a signed commit' ' + git cat-file commit $HASH8 >orig && + git replace --graft $HASH8 && + git cat-file commit $HASH8 >repl && + commit_has_parents $HASH8 && + test_must_fail git verify-commit $HASH8 && + sed -n -e "/^tree /p" -e "/^author /p" -e "/^committer /p" orig >expected && + echo >>expected && + sed -e "/^$/q" repl >actual && + test_cmp expected actual && + git replace -d $HASH8 +' + +test_expect_success GPG 'set up a merge commit with a mergetag' ' + git reset --hard HEAD && + git checkout -b test_branch HEAD~2 && + echo "line 1 from test branch" >>hello && + echo "line 2 from test branch" >>hello && + git add hello && + test_tick && + git commit -m "hello: 2 more lines from a test branch" && + HASH9=$(git rev-parse --verify HEAD) && + git tag -s -m "tag for testing with a mergetag" test_tag HEAD && + git checkout master && + git merge -s ours test_tag && + HASH10=$(git rev-parse --verify HEAD) && + git cat-file commit $HASH10 | grep "^mergetag object" +' + +test_expect_success GPG '--graft on a commit with a mergetag' ' + test_must_fail git replace --graft $HASH10 $HASH8^1 && + git replace --graft $HASH10 $HASH8^1 $HASH9 && + git replace -d $HASH10 +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 143a8ea6..03666530 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1385,41 +1385,103 @@ test_expect_success 'lexical sort' ' git tag foo1.6 && git tag foo1.10 && git tag -l --sort=refname "foo*" >actual && - cat >expect <<EOF && -foo1.10 -foo1.3 -foo1.6 -EOF + cat >expect <<-\EOF && + foo1.10 + foo1.3 + foo1.6 + EOF test_cmp expect actual ' test_expect_success 'version sort' ' git tag -l --sort=version:refname "foo*" >actual && - cat >expect <<EOF && -foo1.3 -foo1.6 -foo1.10 -EOF + cat >expect <<-\EOF && + foo1.3 + foo1.6 + foo1.10 + EOF test_cmp expect actual ' test_expect_success 'reverse version sort' ' git tag -l --sort=-version:refname "foo*" >actual && - cat >expect <<EOF && -foo1.10 -foo1.6 -foo1.3 -EOF + cat >expect <<-\EOF && + foo1.10 + foo1.6 + foo1.3 + EOF test_cmp expect actual ' test_expect_success 'reverse lexical sort' ' git tag -l --sort=-refname "foo*" >actual && - cat >expect <<EOF && -foo1.6 -foo1.3 -foo1.10 -EOF + cat >expect <<-\EOF && + foo1.6 + foo1.3 + foo1.10 + EOF + test_cmp expect actual +' + +test_expect_success 'configured lexical sort' ' + git config tag.sort "v:refname" && + git tag -l "foo*" >actual && + cat >expect <<-\EOF && + foo1.3 + foo1.6 + foo1.10 + EOF + test_cmp expect actual +' + +test_expect_success 'option override configured sort' ' + git tag -l --sort=-refname "foo*" >actual && + cat >expect <<-\EOF && + foo1.6 + foo1.3 + foo1.10 + EOF + test_cmp expect actual +' + +test_expect_success 'invalid sort parameter on command line' ' + test_must_fail git tag -l --sort=notvalid "foo*" >actual +' + +test_expect_success 'invalid sort parameter in configuratoin' ' + git config tag.sort "v:notvalid" && + git tag -l "foo*" >actual && + cat >expect <<-\EOF && + foo1.10 + foo1.3 + foo1.6 + EOF + test_cmp expect actual +' + +run_with_limited_stack () { + (ulimit -s 64 && "$@") +} + +test_lazy_prereq ULIMIT 'run_with_limited_stack true' + +# we require ulimit, this excludes Windows +test_expect_success ULIMIT '--contains works in a deep repo' ' + >expect && + i=1 && + while test $i -lt 4000 + do + echo "commit refs/heads/master +committer A U Thor <author@example.com> $((1000000000 + $i * 100)) +0200 +data <<EOF +commit #$i +EOF" + test $i = 1 && echo "from refs/heads/master^0" + i=$(($i + 1)) + done | git fast-import && + git checkout master && + git tag far-far-away HEAD^ && + run_with_limited_stack git tag --contains HEAD >actual && test_cmp expect actual ' diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index ee703bed..98bcfe21 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -44,7 +44,9 @@ test_expect_success 'creating initial files and commits' ' echo "1st line 2nd file" >secondfile && echo "2nd line 2nd file" >>secondfile && - git -c "i18n.commitEncoding=$test_encoding" commit -a -m "$(commit_msg $test_encoding)" && + # "git commit -m" would break MinGW, as Windows refuse to pass + # $test_encoding encoded parameter to git. + commit_msg $test_encoding | git -c "i18n.commitEncoding=$test_encoding" commit -a -F - && head5=$(git rev-parse --verify HEAD) ' # git log --pretty=oneline # to see those SHA1 involved @@ -334,7 +336,9 @@ test_expect_success 'redoing the last two commits should succeed' ' echo "1st line 2nd file" >secondfile && echo "2nd line 2nd file" >>secondfile && - git -c "i18n.commitEncoding=$test_encoding" commit -a -m "$(commit_msg $test_encoding)" && + # "git commit -m" would break MinGW, as Windows refuse to pass + # $test_encoding encoded parameter to git. + commit_msg $test_encoding | git -c "i18n.commitEncoding=$test_encoding" commit -a -F - && check_changes $head5 ' diff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh new file mode 100755 index 00000000..2eda6ade --- /dev/null +++ b/t/t7112-reset-submodule.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +test_description='reset can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +test_submodule_switch "git reset --keep" + +test_submodule_switch "git reset --merge" + +test_submodule_forced_switch "git reset --hard" + +test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index d58b097f..63e04277 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -346,8 +346,21 @@ test_expect_success 'amend commit to fix date' ' ' -test_expect_success 'commit complains about bogus date' ' - test_must_fail git commit --amend --date=10.11.2010 +test_expect_success 'commit mentions forced date in output' ' + git commit --amend --date=2010-01-02T03:04:05 >output && + grep "Date: *Sat Jan 2 03:04:05 2010" output +' + +test_expect_success 'commit complains about completely bogus dates' ' + test_must_fail git commit --amend --date=seventeen +' + +test_expect_success 'commit --date allows approxidate' ' + git commit --amend \ + --date="midnight the 12th of october, anno domini 1979" && + echo "Fri Oct 12 00:00:00 1979 +0000" >expect && + git log -1 --format=%ad >actual && + test_cmp expect actual ' test_expect_success 'sign off (1)' ' diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 9a3f3a1b..051489ea 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -344,6 +344,13 @@ test_expect_success 'message shows author when it is not equal to committer' ' .git/COMMIT_EDITMSG ' +test_expect_success 'message shows date when it is explicitly set' ' + git commit --allow-empty -e -m foo --date="2010-01-02T03:04:05" && + test_i18ngrep \ + "^# Date: *Sat Jan 2 03:04:05 2010 +0000" \ + .git/COMMIT_EDITMSG +' + test_expect_success AUTOIDENT 'message shows committer when it is automatic' ' echo >>negative && @@ -563,4 +570,30 @@ test_expect_success 'commit --status with custom comment character' ' test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG ' +test_expect_success 'switch core.commentchar' ' + test_commit "#foo" foo && + GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend && + test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG +' + +test_expect_success 'switch core.commentchar but out of options' ' + cat >text <<\EOF && +# 1 +; 2 +@ 3 +! 4 +$ 5 +% 6 +^ 7 +& 8 +| 9 +: 10 +EOF + git commit --amend -F text && + ( + test_set_editor .git/FAKE_EDITOR && + test_must_fail git -c core.commentChar=auto commit --amend + ) +' + test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index d4800696..8ed57888 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1350,8 +1350,7 @@ test_expect_success "status (core.commentchar with submodule summary)" ' test_expect_success "status (core.commentchar with two chars with submodule summary)" ' test_config core.commentchar ";;" && - git -c status.displayCommentPrefix=true status >output && - test_i18ncmp expect output + test_must_fail git -c status.displayCommentPrefix=true status ' test_expect_success "--ignore-submodules=all suppresses submodule summary" ' diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index e97477a3..474dab38 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -48,10 +48,11 @@ test_expect_success GPG 'create signed commits' ' git tag eighth-signed-alt ' -test_expect_success GPG 'show signatures' ' +test_expect_success GPG 'verify and show signatures' ' ( for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed do + git verify-commit $commit && git show --pretty=short --show-signature $commit >actual && grep "Good signature from" actual && ! grep "BAD signature from" actual && @@ -61,6 +62,7 @@ test_expect_success GPG 'show signatures' ' ( for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned do + test_must_fail git verify-commit $commit && git show --pretty=short --show-signature $commit >actual && ! grep "Good signature from" actual && ! grep "BAD signature from" actual && @@ -79,11 +81,25 @@ test_expect_success GPG 'show signatures' ' ) ' +test_expect_success GPG 'show signed commit with signature' ' + git show -s initial >commit && + git show -s --show-signature initial >show && + git verify-commit -v initial >verify.1 2>verify.2 && + git cat-file commit initial >cat && + grep -v "gpg: " show >show.commit && + grep "gpg: " show >show.gpg && + grep -v "^ " cat | grep -v "^gpgsig " >cat.commit && + test_cmp show.commit commit && + test_cmp show.gpg verify.2 && + test_cmp cat.commit verify.1 +' + test_expect_success GPG 'detect fudged signature' ' git cat-file commit seventh-signed >raw && sed -e "s/seventh/7th forged/" raw >forged1 && git hash-object -w -t commit forged1 >forged1.commit && + ! git verify-commit $(cat forged1.commit) && git show --pretty=short --show-signature $(cat forged1.commit) >actual1 && grep "BAD signature from" actual1 && ! grep "Good signature from" actual1 @@ -94,6 +110,7 @@ test_expect_success GPG 'detect fudged signature with NUL' ' cat raw >forged2 && echo Qwik | tr "Q" "\000" >>forged2 && git hash-object -w -t commit forged2 >forged2.commit && + ! git verify-commit $(cat forged2.commit) && git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && grep "BAD signature from" actual2 && ! grep "Good signature from" actual2 @@ -102,6 +119,7 @@ test_expect_success GPG 'detect fudged signature with NUL' ' test_expect_success GPG 'amending already signed commit' ' git checkout fourth-signed^0 && git commit --amend -S --no-edit && + git verify-commit HEAD && git show -s --show-signature HEAD >actual && grep "Good signature from" actual && ! grep "BAD signature from" actual diff --git a/t/t7613-merge-submodule.sh b/t/t7613-merge-submodule.sh new file mode 100755 index 00000000..d1e9fcc7 --- /dev/null +++ b/t/t7613-merge-submodule.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='merge can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +# merges without conflicts +test_submodule_switch "git merge" + +test_submodule_switch "git merge --ff" + +test_submodule_switch "git merge --ff-only" + +KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 +KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 +test_submodule_switch "git merge --no-ff" + +test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 61e6ed37..021c5479 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -21,7 +21,7 @@ test_expect_success 'objects in packs marked .keep are not repacked' ' objsha1=$(git verify-pack -v pack-$packsha1.idx | head -n 1 | sed -e "s/^\([0-9a-f]\{40\}\).*/\1/") && mv pack-* .git/objects/pack/ && - git repack --no-pack-kept-objects -A -d -l && + git repack -A -d -l && git prune-packed && for p in .git/objects/pack/*.idx; do idx=$(basename $p) @@ -53,7 +53,7 @@ test_expect_success 'writing bitmaps via command-line can duplicate .keep object test_expect_success 'writing bitmaps via config can duplicate .keep objects' ' # build on $objsha1, $packsha1, and .keep state from previous - git -c pack.writebitmaps=true repack -Adl && + git -c repack.writebitmaps=true repack -Adl && test_when_finished "found_duplicate_object=" && for p in .git/objects/pack/*.idx; do idx=$(basename $p) diff --git a/t/t7702-repack-cyclic-alternate.sh b/t/t7702-repack-cyclic-alternate.sh new file mode 100755 index 00000000..93b74867 --- /dev/null +++ b/t/t7702-repack-cyclic-alternate.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# +# Copyright (c) 2014 Ephrim Khong +# + +test_description='repack involving cyclic alternate' +. ./test-lib.sh + +test_expect_success setup ' + GIT_OBJECT_DIRECTORY=.git//../.git/objects && + export GIT_OBJECT_DIRECTORY && + touch a && + git add a && + git commit -m 1 && + git repack -adl && + echo "$(pwd)"/.git/objects/../objects >.git/objects/info/alternates +' + +test_expect_success 're-packing repository with itsself as alternate' ' + git repack -adl && + git fsck +' + +test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 5a193c50..dc30a514 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -58,7 +58,7 @@ test_expect_success PERL 'custom tool commands override built-ins' ' test_expect_success PERL 'difftool ignores bad --tool values' ' : >expect && - test_expect_code 1 \ + test_must_fail \ git difftool --no-prompt --tool=bad-tool branch >actual && test_cmp expect actual ' diff --git a/t/t8005-blame-i18n.sh b/t/t8005-blame-i18n.sh index cb390559..a6e73d06 100755 --- a/t/t8005-blame-i18n.sh +++ b/t/t8005-blame-i18n.sh @@ -33,7 +33,7 @@ author $SJIS_NAME summary $SJIS_MSG EOF -test_expect_success \ +test_expect_success NOT_MINGW \ 'blame respects i18n.commitencoding' ' git blame --incremental file | \ egrep "^(author|summary) " > actual && @@ -49,7 +49,7 @@ author $EUC_JAPAN_NAME summary $EUC_JAPAN_MSG EOF -test_expect_success \ +test_expect_success NOT_MINGW \ 'blame respects i18n.logoutputencoding' ' git config i18n.logoutputencoding eucJP && git blame --incremental file | \ @@ -66,7 +66,7 @@ author $UTF8_NAME summary $UTF8_MSG EOF -test_expect_success \ +test_expect_success NOT_MINGW \ 'blame respects --encoding=UTF-8' ' git blame --incremental --encoding=UTF-8 file | \ egrep "^(author|summary) " > actual && @@ -82,7 +82,7 @@ author $UTF8_NAME summary $UTF8_MSG EOF -test_expect_success \ +test_expect_success NOT_MINGW \ 'blame respects --encoding=none' ' git blame --incremental --encoding=none file | \ egrep "^(author|summary) " > actual && diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 1ecdacb6..19a3ced6 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -1334,6 +1334,51 @@ test_expect_success $PREREQ '--force sends cover letter template anyway' ' test -n "$(ls msgtxt*)" ' +test_cover_addresses () { + header="$1" + shift + clean_fake_sendmail && + rm -fr outdir && + git format-patch --cover-letter -2 -o outdir && + cover=`echo outdir/0000-*.patch` && + mv $cover cover-to-edit.patch && + perl -pe "s/^From:/$header: extra\@address.com\nFrom:/" cover-to-edit.patch >"$cover" && + git send-email \ + --force \ + --from="Example <nobody@example.com>" \ + --no-to --no-cc \ + "$@" \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0000-*.patch \ + outdir/0001-*.patch \ + outdir/0002-*.patch \ + 2>errors >out && + grep "^$header: extra@address.com" msgtxt1 >to1 && + grep "^$header: extra@address.com" msgtxt2 >to2 && + grep "^$header: extra@address.com" msgtxt3 >to3 && + test_line_count = 1 to1 && + test_line_count = 1 to2 && + test_line_count = 1 to3 +} + +test_expect_success $PREREQ 'to-cover adds To to all mail' ' + test_cover_addresses "To" --to-cover +' + +test_expect_success $PREREQ 'cc-cover adds Cc to all mail' ' + test_cover_addresses "Cc" --cc-cover +' + +test_expect_success $PREREQ 'tocover adds To to all mail' ' + test_config sendemail.tocover true && + test_cover_addresses "To" +' + +test_expect_success $PREREQ 'cccover adds Cc to all mail' ' + test_config sendemail.cccover true && + test_cover_addresses "Cc" +' + test_expect_success $PREREQ 'sendemail.aliasfiletype=mailrc' ' clean_fake_sendmail && echo "alias sbd somebody@example.org" >.mailrc && diff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh index 83cc5fc9..2937f4c2 100755 --- a/t/t9138-git-svn-authors-prog.sh +++ b/t/t9138-git-svn-authors-prog.sh @@ -7,40 +7,39 @@ test_description='git svn authors prog tests' . ./lib-git-svn.sh -cat > svn-authors-prog <<'EOF' -#!/usr/bin/perl -$_ = shift; -if (s/-sub$//) { - print "$_ <$_\@sub.example.com>\n"; -} -else { - print "$_ <$_\@example.com>\n"; -} +write_script svn-authors-prog "$PERL_PATH" <<-\EOF + $_ = shift; + if (s/-sub$//) { + print "$_ <$_\@sub.example.com>\n"; + } else { + print "$_ <$_\@example.com>\n"; + } EOF -chmod +x svn-authors-prog -cat > svn-authors <<'EOF' -ff = FFFFFFF FFFFFFF <fFf@other.example.com> -EOF +test_expect_success 'svn-authors setup' ' + cat >svn-authors <<-\EOF + ff = FFFFFFF FFFFFFF <fFf@other.example.com> + EOF +' test_expect_success 'setup svnrepo' ' for i in aa bb cc-sub dd-sub ee-foo ff do svn mkdir -m $i --username $i "$svnrepo"/$i done - ' +' test_expect_success 'import authors with prog and file' ' git svn clone --authors-prog=./svn-authors-prog \ --authors-file=svn-authors "$svnrepo" x - ' +' test_expect_success 'imported 6 revisions successfully' ' ( cd x test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 6 ) - ' +' test_expect_success 'authors-prog ran correctly' ' ( @@ -56,7 +55,7 @@ test_expect_success 'authors-prog ran correctly' ' git rev-list -1 --pretty=raw refs/remotes/git-svn~5 | \ grep "^author aa <aa@example\.com> " ) - ' +' test_expect_success 'authors-file overrode authors-prog' ' ( @@ -64,7 +63,7 @@ test_expect_success 'authors-file overrode authors-prog' ' git rev-list -1 --pretty=raw refs/remotes/git-svn | \ grep "^author FFFFFFF FFFFFFF <fFf@other\.example\.com> " ) - ' +' git --git-dir=x/.git config --unset svn.authorsfile git --git-dir=x/.git config --unset svn.authorsprog diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 27263dfb..5fc9ef26 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -2999,4 +2999,22 @@ test_expect_success 'T: ls root tree' ' test_cmp expect actual ' +test_expect_success 'T: delete branch' ' + git branch to-delete && + git fast-import <<-EOF && + reset refs/heads/to-delete + from 0000000000000000000000000000000000000000 + EOF + test_must_fail git rev-parse --verify refs/heads/to-delete +' + +test_expect_success 'T: empty reset doesnt delete branch' ' + git branch not-to-delete && + git fast-import <<-EOF && + reset refs/heads/not-to-delete + EOF + git show-ref && + git rev-parse --verify refs/heads/not-to-delete +' + test_done diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 2312dec8..66c8b0a3 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -504,4 +504,22 @@ test_expect_success 'refs are updated even if no commits need to be exported' ' test_cmp expected actual ' +test_expect_success 'use refspec' ' + git fast-export --refspec refs/heads/master:refs/heads/foobar master | \ + grep "^commit " | sort | uniq > actual && + echo "commit refs/heads/foobar" > expected && + test_cmp expected actual +' + +test_expect_success 'delete refspec' ' + git branch to-delete && + git fast-export --refspec :refs/heads/to-delete to-delete ^to-delete > actual && + cat > expected <<-EOF && + reset refs/heads/to-delete + from 0000000000000000000000000000000000000000 + + EOF + test_cmp expected actual +' + test_done diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh index 665607c9..5b562122 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -145,7 +145,7 @@ test_expect_success 'exit when p4 fails to produce marshaled output' ' test_expect_code 1 git p4 clone --dest="$git" //depot >errs 2>&1 ) && cat errs && - ! test_i18ngrep Traceback errs + test_i18ngrep ! Traceback errs ' # Hide a file from p4d, make sure we catch its complaint. This won't fail in diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 4caf36e0..1f74a883 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -403,7 +403,8 @@ test_expect_success 'submit --prepare-p4-only' ' git commit -m "prep only add" && git p4 submit --prepare-p4-only >out && test_i18ngrep "prepared for submission" out && - test_i18ngrep "must be deleted" out + test_i18ngrep "must be deleted" out && + test_i18ngrep ! "everything below this line is just the diff" out ) && ( cd "$cli" && diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh index be802e0e..95f4421f 100755 --- a/t/t9814-git-p4-rename.sh +++ b/t/t9814-git-p4-rename.sh @@ -177,7 +177,10 @@ test_expect_success 'detect copies' ' level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") && test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 && src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && - test "$src" = file10 -o "$src" = file11 && + case "$src" in + file10 | file11) : ;; # happy + *) false ;; # not + && git config git-p4.detectCopies $(($level + 2)) && git p4 submit && p4 filelog //depot/file12 && @@ -191,7 +194,10 @@ test_expect_success 'detect copies' ' level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") && test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 && src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) && - test "$src" = file10 -o "$src" = file11 -o "$src" = file12 && + case "$src" in + file10 | file11 | file12) : ;; # happy + *) false ;; # not + && git config git-p4.detectCopies $(($level - 2)) && git p4 submit && p4 filelog //depot/file13 && diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 2d4beb5e..f10a7529 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -212,9 +212,18 @@ test_expect_success '__gitdir - non-existing $GIT_DIR' ' ) ' +function pwd_P_W () { + if test_have_prereq MINGW + then + pwd -W + else + pwd -P + fi +} + test_expect_success '__gitdir - gitfile in cwd' ' - echo "$(pwd -P)/otherrepo/.git" >expected && - echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" >subdir/.git && + echo "$(pwd_P_W)/otherrepo/.git" >expected && + echo "gitdir: $(pwd_P_W)/otherrepo/.git" >subdir/.git && test_when_finished "rm -f subdir/.git" && ( cd subdir && @@ -224,8 +233,8 @@ test_expect_success '__gitdir - gitfile in cwd' ' ' test_expect_success '__gitdir - gitfile in parent' ' - echo "$(pwd -P)/otherrepo/.git" >expected && - echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" >subdir/.git && + echo "$(pwd_P_W)/otherrepo/.git" >expected && + echo "gitdir: $(pwd_P_W)/otherrepo/.git" >subdir/.git && test_when_finished "rm -f subdir/.git" && ( cd subdir/subsubdir && @@ -550,6 +559,33 @@ test_expect_success 'complete files' ' test_completion "git add mom" "momified" ' +test_expect_success "completion uses <cmd> completion for alias: !sh -c 'git <cmd> ...'" ' + test_config alias.co "!sh -c '"'"'git checkout ...'"'"'" && + test_completion "git co m" <<-\EOF + master Z + mybranch Z + mytag Z + EOF +' + +test_expect_success 'completion uses <cmd> completion for alias: !f () { VAR=val git <cmd> ... }' ' + test_config alias.co "!f () { VAR=val git checkout ... ; } f" && + test_completion "git co m" <<-\EOF + master Z + mybranch Z + mytag Z + EOF +' + +test_expect_success 'completion used <cmd> completion for alias: !f() { : git <cmd> ; ... }' ' + test_config alias.co "!f() { : git checkout ; if ... } f" && + test_completion "git co m" <<-\EOF + master Z + mybranch Z + mytag Z + EOF +' + test_expect_failure 'complete with tilde expansion' ' git init tmp && cd tmp && test_when_finished "cd .. && rm -rf tmp" && diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index cf7b41f6..dafd6ad2 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -489,6 +489,17 @@ test_path_is_dir () { fi } +# Check if the directory exists and is empty as expected, barf otherwise. +test_dir_is_empty () { + test_path_is_dir "$1" && + if test -n "$(ls -a1 "$1" | egrep -v '^\.\.?$')" + then + echo "Directory '$1' is not empty, it contains:" + ls -la "$1" + return 1 + fi +} + test_path_is_missing () { if [ -e "$1" ] then @@ -542,7 +553,7 @@ test_must_fail () { if test $exit_code = 0; then echo >&2 "test_must_fail: command succeeded: $*" return 1 - elif test $exit_code -gt 129 -a $exit_code -le 192; then + elif test $exit_code -gt 129 && test $exit_code -le 192; then echo >&2 "test_must_fail: died by signal: $*" return 1 elif test $exit_code = 127; then @@ -569,7 +580,7 @@ test_must_fail () { test_might_fail () { "$@" exit_code=$? - if test $exit_code -gt 129 -a $exit_code -le 192; then + if test $exit_code -gt 129 && test $exit_code -le 192; then echo >&2 "test_might_fail: died by signal: $*" return 1 elif test $exit_code = 127; then @@ -723,6 +734,11 @@ test_ln_s_add () { fi } +# This function writes out its parameters, one per line +test_write_lines () { + printf "%s\n" "$@" +} + perl () { command "$PERL_PATH" "$@" } diff --git a/t/test-lib.sh b/t/test-lib.sh index e0266f03..b1bc65bf 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -91,6 +91,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' VALGRIND UNZIP PERF_ + CURL_VERBOSE )); my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); print join("\n", @vars); @@ -108,6 +109,10 @@ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR +# Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output +GIT_TRACE_BARE=1 +export GIT_TRACE_BARE + if test -n "${TEST_GIT_INDEX_VERSION:+isset}" then GIT_INDEX_VERSION="$TEST_GIT_INDEX_VERSION" @@ -164,11 +169,7 @@ _z40=0000000000000000000000000000000000000000 LF=' ' -# UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores -# when case-folding filenames -u200c=$(printf '\342\200\214') - -export _x05 _x40 _z40 LF u200c +export _x05 _x40 _z40 LF # Each test should start with something like this, after copyright notices: # @@ -195,6 +196,14 @@ do immediate=t; shift ;; -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;; + -r) + shift; test "$#" -ne 0 || { + echo 'error: -r requires an argument' >&2; + exit 1; + } + run_list=$1; shift ;; + --run=*) + run_list=$(expr "z$1" : 'z[^=]*=\(.*\)'); shift ;; -h|--h|--he|--hel|--help) help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) @@ -370,6 +379,99 @@ match_pattern_list () { return 1 } +match_test_selector_list () { + title="$1" + shift + arg="$1" + shift + test -z "$1" && return 0 + + # Both commas and whitespace are accepted as separators. + OLDIFS=$IFS + IFS=' ,' + set -- $1 + IFS=$OLDIFS + + # If the first selector is negative we include by default. + include= + case "$1" in + !*) include=t ;; + esac + + for selector + do + orig_selector=$selector + + positive=t + case "$selector" in + !*) + positive= + selector=${selector##?} + ;; + esac + + test -z "$selector" && continue + + case "$selector" in + *-*) + if expr "z${selector%%-*}" : "z[0-9]*[^0-9]" >/dev/null + then + echo "error: $title: invalid non-numeric in range" \ + "start: '$orig_selector'" >&2 + exit 1 + fi + if expr "z${selector#*-}" : "z[0-9]*[^0-9]" >/dev/null + then + echo "error: $title: invalid non-numeric in range" \ + "end: '$orig_selector'" >&2 + exit 1 + fi + ;; + *) + if expr "z$selector" : "z[0-9]*[^0-9]" >/dev/null + then + echo "error: $title: invalid non-numeric in test" \ + "selector: '$orig_selector'" >&2 + exit 1 + fi + esac + + # Short cut for "obvious" cases + test -z "$include" && test -z "$positive" && continue + test -n "$include" && test -n "$positive" && continue + + case "$selector" in + -*) + if test $arg -le ${selector#-} + then + include=$positive + fi + ;; + *-) + if test $arg -ge ${selector%-} + then + include=$positive + fi + ;; + *-*) + if test ${selector%%-*} -le $arg \ + && test $arg -le ${selector#*-} + then + include=$positive + fi + ;; + *) + if test $arg -eq $selector + then + include=$positive + fi + ;; + esac + done + + test -n "$include" +} + maybe_teardown_verbose () { test -z "$verbose_only" && return exec 4>/dev/null 3>/dev/null @@ -456,25 +558,35 @@ test_finish_ () { test_skip () { to_skip= + skipped_reason= if match_pattern_list $this_test.$test_count $GIT_SKIP_TESTS then to_skip=t + skipped_reason="GIT_SKIP_TESTS" fi if test -z "$to_skip" && test -n "$test_prereq" && ! test_have_prereq "$test_prereq" then to_skip=t - fi - case "$to_skip" in - t) + of_prereq= if test "$missing_prereq" != "$test_prereq" then of_prereq=" of $test_prereq" fi + skipped_reason="missing $missing_prereq${of_prereq}" + fi + if test -z "$to_skip" && test -n "$run_list" && + ! match_test_selector_list '--run' $test_count "$run_list" + then + to_skip=t + skipped_reason="--run" + fi + case "$to_skip" in + t) say_color skip >&3 "skipping test: $@" - say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})" + say_color skip "ok $test_count # skip $1 ($skipped_reason)" : true ;; *) @@ -868,6 +980,14 @@ test_lazy_prereq AUTOIDENT ' git var GIT_AUTHOR_IDENT ' +test_lazy_prereq EXPENSIVE ' + test -n "$GIT_TEST_LONG" +' + +test_lazy_prereq USR_BIN_TIME ' + test -x /usr/bin/time +' + # When the tests are run as root, permission tests will report that # things are writable when they shouldn't be. test -w / || test_set_prereq SANITY diff --git a/test-dump-cache-tree.c b/test-dump-cache-tree.c index 47eab976..330ba4f4 100644 --- a/test-dump-cache-tree.c +++ b/test-dump-cache-tree.c @@ -56,11 +56,12 @@ static int dump_cache_tree(struct cache_tree *it, int main(int ac, char **av) { + struct index_state istate; struct cache_tree *another = cache_tree(); if (read_cache() < 0) die("unable to read index file"); - cache_tree_update(another, - (const struct cache_entry * const *)active_cache, - active_nr, WRITE_TREE_DRY_RUN); + istate = the_index; + istate.cache_tree = another; + cache_tree_update(&istate, WRITE_TREE_DRY_RUN); return dump_cache_tree(active_cache_tree, another, ""); } diff --git a/test-dump-split-index.c b/test-dump-split-index.c new file mode 100644 index 00000000..9cf3112c --- /dev/null +++ b/test-dump-split-index.c @@ -0,0 +1,34 @@ +#include "cache.h" +#include "split-index.h" +#include "ewah/ewok.h" + +static void show_bit(size_t pos, void *data) +{ + printf(" %d", (int)pos); +} + +int main(int ac, char **av) +{ + struct split_index *si; + int i; + + do_read_index(&the_index, av[1], 1); + printf("own %s\n", sha1_to_hex(the_index.sha1)); + si = the_index.split_index; + if (!si) { + printf("not a split index\n"); + return 0; + } + printf("base %s\n", sha1_to_hex(si->base_sha1)); + for (i = 0; i < the_index.cache_nr; i++) { + struct cache_entry *ce = the_index.cache[i]; + printf("%06o %s %d\t%s\n", ce->ce_mode, + sha1_to_hex(ce->sha1), ce_stage(ce), ce->name); + } + printf("replacements:"); + ewah_each_bit(si->replace_bitmap, show_bit, NULL); + printf("\ndeletions:"); + ewah_each_bit(si->delete_bitmap, show_bit, NULL); + printf("\n"); + return 0; +} diff --git a/test-hashmap.c b/test-hashmap.c index f5183fb9..07aa7ecd 100644 --- a/test-hashmap.c +++ b/test-hashmap.c @@ -115,9 +115,8 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) for (j = 0; j < rounds; j++) { for (i = 0; i < TEST_SIZE; i++) { - struct hashmap_entry key; - hashmap_entry_init(&key, hashes[i]); - hashmap_get(&map, &key, entries[i]->key); + hashmap_get_from_hash(&map, hashes[i], + entries[i]->key); } } @@ -199,12 +198,8 @@ int main(int argc, char *argv[]) } else if (!strcmp("get", cmd) && l1) { - /* setup static key */ - struct hashmap_entry key; - hashmap_entry_init(&key, hash); - /* lookup entry in hashmap */ - entry = hashmap_get(&map, &key, p1); + entry = hashmap_get_from_hash(&map, hash, p1); /* print result */ if (!entry) @@ -239,6 +234,20 @@ int main(int argc, char *argv[]) /* print table sizes */ printf("%u %u\n", map.tablesize, map.size); + } else if (!strcmp("intern", cmd) && l1) { + + /* test that strintern works */ + const char *i1 = strintern(p1); + const char *i2 = strintern(p1); + if (strcmp(i1, p1)) + printf("strintern(%s) returns %s\n", p1, i1); + else if (i1 == p1) + printf("strintern(%s) returns input pointer\n", p1); + else if (i1 != i2) + printf("strintern(%s) != strintern(%s)", i1, i2); + else + printf("%s\n", i1); + } else if (!strcmp("perfhashmap", cmd) && l1 && l2) { perf_hashmap(atoi(p1), atoi(p2)); diff --git a/test-scrap-cache-tree.c b/test-scrap-cache-tree.c index 47280139..9ebcbca9 100644 --- a/test-scrap-cache-tree.c +++ b/test-scrap-cache-tree.c @@ -6,12 +6,11 @@ static struct lock_file index_lock; int main(int ac, char **av) { - int fd = hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, 1); if (read_cache() < 0) die("unable to read index file"); active_cache_tree = NULL; - if (write_cache(fd, active_cache, active_nr) - || commit_lock_file(&index_lock)) + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) die("unable to write index file"); return 0; } @@ -26,110 +26,250 @@ #include "quote.h" /* Get a trace file descriptor from "key" env variable. */ -static int get_trace_fd(const char *key, int *need_close) +static int get_trace_fd(struct trace_key *key) { - char *trace = getenv(key); + static struct trace_key trace_default = { "GIT_TRACE" }; + const char *trace; + + /* use default "GIT_TRACE" if NULL */ + if (!key) + key = &trace_default; + + /* don't open twice */ + if (key->initialized) + return key->fd; + + trace = getenv(key->key); if (!trace || !strcmp(trace, "") || !strcmp(trace, "0") || !strcasecmp(trace, "false")) - return 0; - if (!strcmp(trace, "1") || !strcasecmp(trace, "true")) - return STDERR_FILENO; - if (strlen(trace) == 1 && isdigit(*trace)) - return atoi(trace); - if (is_absolute_path(trace)) { + key->fd = 0; + else if (!strcmp(trace, "1") || !strcasecmp(trace, "true")) + key->fd = STDERR_FILENO; + else if (strlen(trace) == 1 && isdigit(*trace)) + key->fd = atoi(trace); + else if (is_absolute_path(trace)) { int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666); if (fd == -1) { fprintf(stderr, "Could not open '%s' for tracing: %s\n" "Defaulting to tracing on stderr...\n", trace, strerror(errno)); - return STDERR_FILENO; + key->fd = STDERR_FILENO; + } else { + key->fd = fd; + key->need_close = 1; } - *need_close = 1; - return fd; + } else { + fprintf(stderr, "What does '%s' for %s mean?\n" + "If you want to trace into a file, then please set " + "%s to an absolute pathname (starting with /).\n" + "Defaulting to tracing on stderr...\n", + trace, key->key, key->key); + key->fd = STDERR_FILENO; } - fprintf(stderr, "What does '%s' for %s mean?\n", trace, key); - fprintf(stderr, "If you want to trace into a file, " - "then please set %s to an absolute pathname " - "(starting with /).\n", key); - fprintf(stderr, "Defaulting to tracing on stderr...\n"); + key->initialized = 1; + return key->fd; +} - return STDERR_FILENO; +void trace_disable(struct trace_key *key) +{ + if (key->need_close) + close(key->fd); + key->fd = 0; + key->initialized = 1; + key->need_close = 0; } static const char err_msg[] = "Could not trace into fd given by " "GIT_TRACE environment variable"; -static void trace_vprintf(const char *key, const char *fmt, va_list ap) +static int prepare_trace_line(const char *file, int line, + struct trace_key *key, struct strbuf *buf) { - struct strbuf buf = STRBUF_INIT; + static struct trace_key trace_bare = TRACE_KEY_INIT(BARE); + struct timeval tv; + struct tm tm; + time_t secs; if (!trace_want(key)) - return; + return 0; set_try_to_free_routine(NULL); /* is never reset */ - strbuf_vaddf(&buf, fmt, ap); - trace_strbuf(key, &buf); - strbuf_release(&buf); + + /* unit tests may want to disable additional trace output */ + if (trace_want(&trace_bare)) + return 1; + + /* print current timestamp */ + gettimeofday(&tv, NULL); + secs = tv.tv_sec; + localtime_r(&secs, &tm); + strbuf_addf(buf, "%02d:%02d:%02d.%06ld ", tm.tm_hour, tm.tm_min, + tm.tm_sec, (long) tv.tv_usec); + +#ifdef HAVE_VARIADIC_MACROS + /* print file:line */ + strbuf_addf(buf, "%s:%d ", file, line); + /* align trace output (column 40 catches most files names in git) */ + while (buf->len < 40) + strbuf_addch(buf, ' '); +#endif + + return 1; +} + +static void print_trace_line(struct trace_key *key, struct strbuf *buf) +{ + /* append newline if missing */ + if (buf->len && buf->buf[buf->len - 1] != '\n') + strbuf_addch(buf, '\n'); + + write_or_whine_pipe(get_trace_fd(key), buf->buf, buf->len, err_msg); + strbuf_release(buf); +} + +static void trace_vprintf_fl(const char *file, int line, struct trace_key *key, + const char *format, va_list ap) +{ + struct strbuf buf = STRBUF_INIT; + + if (!prepare_trace_line(file, line, key, &buf)) + return; + + strbuf_vaddf(&buf, format, ap); + print_trace_line(key, &buf); +} + +static void trace_argv_vprintf_fl(const char *file, int line, + const char **argv, const char *format, + va_list ap) +{ + struct strbuf buf = STRBUF_INIT; + + if (!prepare_trace_line(file, line, NULL, &buf)) + return; + + strbuf_vaddf(&buf, format, ap); + + sq_quote_argv(&buf, argv, 0); + print_trace_line(NULL, &buf); +} + +void trace_strbuf_fl(const char *file, int line, struct trace_key *key, + const struct strbuf *data) +{ + struct strbuf buf = STRBUF_INIT; + + if (!prepare_trace_line(file, line, key, &buf)) + return; + + strbuf_addbuf(&buf, data); + print_trace_line(key, &buf); +} + +static struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE); + +static void trace_performance_vprintf_fl(const char *file, int line, + uint64_t nanos, const char *format, + va_list ap) +{ + struct strbuf buf = STRBUF_INIT; + + if (!prepare_trace_line(file, line, &trace_perf_key, &buf)) + return; + + strbuf_addf(&buf, "performance: %.9f s", (double) nanos / 1000000000); + + if (format && *format) { + strbuf_addstr(&buf, ": "); + strbuf_vaddf(&buf, format, ap); + } + + print_trace_line(&trace_perf_key, &buf); } -__attribute__((format (printf, 2, 3))) -void trace_printf_key(const char *key, const char *fmt, ...) +#ifndef HAVE_VARIADIC_MACROS + +void trace_printf(const char *format, ...) { va_list ap; - va_start(ap, fmt); - trace_vprintf(key, fmt, ap); + va_start(ap, format); + trace_vprintf_fl(NULL, 0, NULL, format, ap); va_end(ap); } -void trace_printf(const char *fmt, ...) +void trace_printf_key(struct trace_key *key, const char *format, ...) { va_list ap; - va_start(ap, fmt); - trace_vprintf("GIT_TRACE", fmt, ap); + va_start(ap, format); + trace_vprintf_fl(NULL, 0, key, format, ap); va_end(ap); } -void trace_strbuf(const char *key, const struct strbuf *buf) +void trace_argv_printf(const char **argv, const char *format, ...) { - int fd, need_close = 0; - - fd = get_trace_fd(key, &need_close); - if (!fd) - return; + va_list ap; + va_start(ap, format); + trace_argv_vprintf_fl(NULL, 0, argv, format, ap); + va_end(ap); +} - write_or_whine_pipe(fd, buf->buf, buf->len, err_msg); +void trace_strbuf(const char *key, const struct strbuf *data) +{ + trace_strbuf_fl(NULL, 0, key, data); +} - if (need_close) - close(fd); +void trace_performance(uint64_t nanos, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + trace_performance_vprintf_fl(NULL, 0, nanos, format, ap); + va_end(ap); } -void trace_argv_printf(const char **argv, const char *fmt, ...) +void trace_performance_since(uint64_t start, const char *format, ...) { - struct strbuf buf = STRBUF_INIT; va_list ap; - int fd, need_close = 0; + va_start(ap, format); + trace_performance_vprintf_fl(NULL, 0, getnanotime() - start, + format, ap); + va_end(ap); +} - fd = get_trace_fd("GIT_TRACE", &need_close); - if (!fd) - return; +#else - set_try_to_free_routine(NULL); /* is never reset */ - va_start(ap, fmt); - strbuf_vaddf(&buf, fmt, ap); +void trace_printf_key_fl(const char *file, int line, struct trace_key *key, + const char *format, ...) +{ + va_list ap; + va_start(ap, format); + trace_vprintf_fl(file, line, key, format, ap); va_end(ap); +} - sq_quote_argv(&buf, argv, 0); - strbuf_addch(&buf, '\n'); - write_or_whine_pipe(fd, buf.buf, buf.len, err_msg); - strbuf_release(&buf); +void trace_argv_printf_fl(const char *file, int line, const char **argv, + const char *format, ...) +{ + va_list ap; + va_start(ap, format); + trace_argv_vprintf_fl(file, line, argv, format, ap); + va_end(ap); +} - if (need_close) - close(fd); +void trace_performance_fl(const char *file, int line, uint64_t nanos, + const char *format, ...) +{ + va_list ap; + va_start(ap, format); + trace_performance_vprintf_fl(file, line, nanos, format, ap); + va_end(ap); } +#endif /* HAVE_VARIADIC_MACROS */ + + static const char *quote_crnl(const char *path) { static char new_path[PATH_MAX]; @@ -156,11 +296,11 @@ static const char *quote_crnl(const char *path) /* FIXME: move prefix to startup_info struct and get rid of this arg */ void trace_repo_setup(const char *prefix) { - static const char *key = "GIT_TRACE_SETUP"; + static struct trace_key key = TRACE_KEY_INIT(SETUP); const char *git_work_tree; char cwd[PATH_MAX]; - if (!trace_want(key)) + if (!trace_want(&key)) return; if (!getcwd(cwd, PATH_MAX)) @@ -172,18 +312,117 @@ void trace_repo_setup(const char *prefix) if (!prefix) prefix = "(null)"; - trace_printf_key(key, "setup: git_dir: %s\n", quote_crnl(get_git_dir())); - trace_printf_key(key, "setup: worktree: %s\n", quote_crnl(git_work_tree)); - trace_printf_key(key, "setup: cwd: %s\n", quote_crnl(cwd)); - trace_printf_key(key, "setup: prefix: %s\n", quote_crnl(prefix)); + trace_printf_key(&key, "setup: git_dir: %s\n", quote_crnl(get_git_dir())); + trace_printf_key(&key, "setup: worktree: %s\n", quote_crnl(git_work_tree)); + trace_printf_key(&key, "setup: cwd: %s\n", quote_crnl(cwd)); + trace_printf_key(&key, "setup: prefix: %s\n", quote_crnl(prefix)); } -int trace_want(const char *key) +int trace_want(struct trace_key *key) { - const char *trace = getenv(key); + return !!get_trace_fd(key); +} - if (!trace || !strcmp(trace, "") || - !strcmp(trace, "0") || !strcasecmp(trace, "false")) +#ifdef HAVE_CLOCK_GETTIME + +static inline uint64_t highres_nanos(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts)) return 0; - return 1; + return (uint64_t) ts.tv_sec * 1000000000 + ts.tv_nsec; +} + +#elif defined (GIT_WINDOWS_NATIVE) + +static inline uint64_t highres_nanos(void) +{ + static uint64_t high_ns, scaled_low_ns; + static int scale; + LARGE_INTEGER cnt; + + if (!scale) { + if (!QueryPerformanceFrequency(&cnt)) + return 0; + + /* high_ns = number of ns per cnt.HighPart */ + high_ns = (1000000000LL << 32) / (uint64_t) cnt.QuadPart; + + /* + * Number of ns per cnt.LowPart is 10^9 / frequency (or + * high_ns >> 32). For maximum precision, we scale this factor + * so that it just fits within 32 bit (i.e. won't overflow if + * multiplied with cnt.LowPart). + */ + scaled_low_ns = high_ns; + scale = 32; + while (scaled_low_ns >= 0x100000000LL) { + scaled_low_ns >>= 1; + scale--; + } + } + + /* if QPF worked on initialization, we expect QPC to work as well */ + QueryPerformanceCounter(&cnt); + + return (high_ns * cnt.HighPart) + + ((scaled_low_ns * cnt.LowPart) >> scale); +} + +#else +# define highres_nanos() 0 +#endif + +static inline uint64_t gettimeofday_nanos(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000; +} + +/* + * Returns nanoseconds since the epoch (01/01/1970), for performance tracing + * (i.e. favoring high precision over wall clock time accuracy). + */ +inline uint64_t getnanotime(void) +{ + static uint64_t offset; + if (offset > 1) { + /* initialization succeeded, return offset + high res time */ + return offset + highres_nanos(); + } else if (offset == 1) { + /* initialization failed, fall back to gettimeofday */ + return gettimeofday_nanos(); + } else { + /* initialize offset if high resolution timer works */ + uint64_t now = gettimeofday_nanos(); + uint64_t highres = highres_nanos(); + if (highres) + offset = now - highres; + else + offset = 1; + return now; + } +} + +static uint64_t command_start_time; +static struct strbuf command_line = STRBUF_INIT; + +static void print_command_performance_atexit(void) +{ + trace_performance_since(command_start_time, "git command:%s", + command_line.buf); +} + +void trace_command_performance(const char **argv) +{ + if (!trace_want(&trace_perf_key)) + return; + + if (!command_start_time) + atexit(print_command_performance_atexit); + + strbuf_reset(&command_line); + sq_quote_argv(&command_line, argv, 0); + command_start_time = getnanotime(); } diff --git a/trace.h b/trace.h new file mode 100644 index 00000000..ae6a3329 --- /dev/null +++ b/trace.h @@ -0,0 +1,113 @@ +#ifndef TRACE_H +#define TRACE_H + +#include "git-compat-util.h" +#include "strbuf.h" + +struct trace_key { + const char * const key; + int fd; + unsigned int initialized : 1; + unsigned int need_close : 1; +}; + +#define TRACE_KEY_INIT(name) { "GIT_TRACE_" #name, 0, 0, 0 } + +extern void trace_repo_setup(const char *prefix); +extern int trace_want(struct trace_key *key); +extern void trace_disable(struct trace_key *key); +extern uint64_t getnanotime(void); +extern void trace_command_performance(const char **argv); + +#ifndef HAVE_VARIADIC_MACROS + +__attribute__((format (printf, 1, 2))) +extern void trace_printf(const char *format, ...); + +__attribute__((format (printf, 2, 3))) +extern void trace_printf_key(struct trace_key *key, const char *format, ...); + +__attribute__((format (printf, 2, 3))) +extern void trace_argv_printf(const char **argv, const char *format, ...); + +extern void trace_strbuf(struct trace_key *key, const struct strbuf *data); + +/* Prints elapsed time (in nanoseconds) if GIT_TRACE_PERFORMANCE is enabled. */ +__attribute__((format (printf, 2, 3))) +extern void trace_performance(uint64_t nanos, const char *format, ...); + +/* Prints elapsed time since 'start' if GIT_TRACE_PERFORMANCE is enabled. */ +__attribute__((format (printf, 2, 3))) +extern void trace_performance_since(uint64_t start, const char *format, ...); + +#else + +/* + * Macros to add file:line - see above for C-style declarations of how these + * should be used. + */ + +/* + * TRACE_CONTEXT may be set to __FUNCTION__ if the compiler supports it. The + * default is __FILE__, as it is consistent with assert(), and static function + * names are not necessarily unique. + * + * __FILE__ ":" __FUNCTION__ doesn't work with GNUC, as __FILE__ is supplied + * by the preprocessor as a string literal, and __FUNCTION__ is filled in by + * the compiler as a string constant. + */ +#ifndef TRACE_CONTEXT +# define TRACE_CONTEXT __FILE__ +#endif + +/* + * Note: with C99 variadic macros, __VA_ARGS__ must include the last fixed + * parameter ('format' in this case). Otherwise, a call without variable + * arguments will have a surplus ','. E.g.: + * + * #define foo(format, ...) bar(format, __VA_ARGS__) + * foo("test"); + * + * will expand to + * + * bar("test",); + * + * which is invalid (note the ',)'). With GNUC, '##__VA_ARGS__' drops the + * comma, but this is non-standard. + */ + +#define trace_printf(...) \ + trace_printf_key_fl(TRACE_CONTEXT, __LINE__, NULL, __VA_ARGS__) + +#define trace_printf_key(key, ...) \ + trace_printf_key_fl(TRACE_CONTEXT, __LINE__, key, __VA_ARGS__) + +#define trace_argv_printf(argv, ...) \ + trace_argv_printf_fl(TRACE_CONTEXT, __LINE__, argv, __VA_ARGS__) + +#define trace_strbuf(key, data) \ + trace_strbuf_fl(TRACE_CONTEXT, __LINE__, key, data) + +#define trace_performance(nanos, ...) \ + trace_performance_fl(TRACE_CONTEXT, __LINE__, nanos, __VA_ARGS__) + +#define trace_performance_since(start, ...) \ + trace_performance_fl(TRACE_CONTEXT, __LINE__, getnanotime() - (start), \ + __VA_ARGS__) + +/* backend functions, use non-*fl macros instead */ +__attribute__((format (printf, 4, 5))) +extern void trace_printf_key_fl(const char *file, int line, struct trace_key *key, + const char *format, ...); +__attribute__((format (printf, 4, 5))) +extern void trace_argv_printf_fl(const char *file, int line, const char **argv, + const char *format, ...); +extern void trace_strbuf_fl(const char *file, int line, struct trace_key *key, + const struct strbuf *data); +__attribute__((format (printf, 4, 5))) +extern void trace_performance_fl(const char *file, int line, + uint64_t nanos, const char *fmt, ...); + +#endif /* HAVE_VARIADIC_MACROS */ + +#endif /* TRACE_H */ diff --git a/transport-helper.c b/transport-helper.c index b9ba0416..3d8fe7d8 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -101,7 +101,6 @@ static void do_take_over(struct transport *transport) static struct child_process *get_helper(struct transport *transport) { struct helper_data *data = transport->data; - struct argv_array argv = ARGV_ARRAY_INIT; struct strbuf buf = STRBUF_INIT; struct child_process *helper; const char **refspecs = NULL; @@ -123,10 +122,9 @@ static struct child_process *get_helper(struct transport *transport) helper->in = -1; helper->out = -1; helper->err = 0; - argv_array_pushf(&argv, "git-remote-%s", data->name); - argv_array_push(&argv, transport->remote->name); - argv_array_push(&argv, remove_ext_force(transport->url)); - helper->argv = argv_array_detach(&argv, NULL); + argv_array_pushf(&helper->args, "git-remote-%s", data->name); + argv_array_push(&helper->args, transport->remote->name); + argv_array_push(&helper->args, remove_ext_force(transport->url)); helper->git_cmd = 0; helper->silent_exec_failure = 1; @@ -155,7 +153,7 @@ static struct child_process *get_helper(struct transport *transport) write_constant(helper->in, "capabilities\n"); while (1) { - const char *capname; + const char *capname, *arg; int mandatory = 0; if (recvline(data, &buf)) exit(128); @@ -185,19 +183,19 @@ static struct child_process *get_helper(struct transport *transport) data->export = 1; else if (!strcmp(capname, "check-connectivity")) data->check_connectivity = 1; - else if (!data->refspecs && starts_with(capname, "refspec ")) { + else if (!data->refspecs && skip_prefix(capname, "refspec ", &arg)) { ALLOC_GROW(refspecs, refspec_nr + 1, refspec_alloc); - refspecs[refspec_nr++] = xstrdup(capname + strlen("refspec ")); + refspecs[refspec_nr++] = xstrdup(arg); } else if (!strcmp(capname, "connect")) { data->connect = 1; } else if (!strcmp(capname, "signed-tags")) { data->signed_tags = 1; - } else if (starts_with(capname, "export-marks ")) { - data->export_marks = xstrdup(capname + strlen("export-marks ")); - } else if (starts_with(capname, "import-marks")) { - data->import_marks = xstrdup(capname + strlen("import-marks ")); + } else if (skip_prefix(capname, "export-marks ", &arg)) { + data->export_marks = xstrdup(arg); + } else if (skip_prefix(capname, "import-marks ", &arg)) { + data->import_marks = xstrdup(arg); } else if (starts_with(capname, "no-private-update")) { data->no_private_update = 1; } else if (mandatory) { @@ -245,7 +243,6 @@ static int disconnect_helper(struct transport *transport) close(data->helper->out); fclose(data->out); res = finish_command(data->helper); - argv_array_free_detached(data->helper->argv); free(data->helper); data->helper = NULL; } @@ -397,18 +394,16 @@ static int get_importer(struct transport *transport, struct child_process *fasti { struct child_process *helper = get_helper(transport); struct helper_data *data = transport->data; - struct argv_array argv = ARGV_ARRAY_INIT; int cat_blob_fd, code; memset(fastimport, 0, sizeof(*fastimport)); fastimport->in = helper->out; - argv_array_push(&argv, "fast-import"); - argv_array_push(&argv, debug ? "--stats" : "--quiet"); + argv_array_push(&fastimport->args, "fast-import"); + argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet"); if (data->bidi_import) { cat_blob_fd = xdup(helper->in); - argv_array_pushf(&argv, "--cat-blob-fd=%d", cat_blob_fd); + argv_array_pushf(&fastimport->args, "--cat-blob-fd=%d", cat_blob_fd); } - fastimport->argv = argv.argv; fastimport->git_cmd = 1; code = start_command(fastimport); @@ -421,30 +416,24 @@ static int get_exporter(struct transport *transport, { struct helper_data *data = transport->data; struct child_process *helper = get_helper(transport); - int argc = 0, i; - struct strbuf tmp = STRBUF_INIT; + int i; memset(fastexport, 0, sizeof(*fastexport)); /* we need to duplicate helper->in because we want to use it after * fastexport is done with it. */ fastexport->out = dup(helper->in); - fastexport->argv = xcalloc(6 + revlist_args->nr, sizeof(*fastexport->argv)); - fastexport->argv[argc++] = "fast-export"; - fastexport->argv[argc++] = "--use-done-feature"; - fastexport->argv[argc++] = data->signed_tags ? - "--signed-tags=verbatim" : "--signed-tags=warn-strip"; - if (data->export_marks) { - strbuf_addf(&tmp, "--export-marks=%s.tmp", data->export_marks); - fastexport->argv[argc++] = strbuf_detach(&tmp, NULL); - } - if (data->import_marks) { - strbuf_addf(&tmp, "--import-marks=%s", data->import_marks); - fastexport->argv[argc++] = strbuf_detach(&tmp, NULL); - } + argv_array_push(&fastexport->args, "fast-export"); + argv_array_push(&fastexport->args, "--use-done-feature"); + argv_array_push(&fastexport->args, data->signed_tags ? + "--signed-tags=verbatim" : "--signed-tags=warn-strip"); + if (data->export_marks) + argv_array_pushf(&fastexport->args, "--export-marks=%s.tmp", data->export_marks); + if (data->import_marks) + argv_array_pushf(&fastexport->args, "--import-marks=%s", data->import_marks); for (i = 0; i < revlist_args->nr; i++) - fastexport->argv[argc++] = revlist_args->items[i].string; + argv_array_push(&fastexport->args, revlist_args->items[i].string); fastexport->git_cmd = 1; return start_command(fastexport); @@ -485,7 +474,6 @@ static int fetch_with_import(struct transport *transport, if (finish_command(&fastimport)) die("Error while running fast-import"); - argv_array_free_detached(fastimport.argv); /* * The fast-import stream of a remote helper that advertises @@ -862,7 +850,7 @@ static int push_refs_with_export(struct transport *transport, struct ref *ref; struct child_process *helper, exporter; struct helper_data *data = transport->data; - struct string_list revlist_args = STRING_LIST_INIT_NODUP; + struct string_list revlist_args = STRING_LIST_INIT_DUP; struct strbuf buf = STRBUF_INIT; if (!data->refspecs) @@ -882,15 +870,10 @@ static int push_refs_with_export(struct transport *transport, write_constant(helper->in, "export\n"); - strbuf_reset(&buf); - for (ref = remote_refs; ref; ref = ref->next) { char *private; unsigned char sha1[20]; - if (ref->deletion) - die("remote-helpers do not support ref deletion"); - private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name); if (private && !get_sha1(private, sha1)) { strbuf_addf(&buf, "^%s", private); @@ -900,15 +883,34 @@ static int push_refs_with_export(struct transport *transport, free(private); if (ref->peer_ref) { - if (strcmp(ref->peer_ref->name, ref->name)) - die("remote-helpers do not support old:new syntax"); - string_list_append(&revlist_args, ref->peer_ref->name); + if (strcmp(ref->name, ref->peer_ref->name)) { + if (!ref->deletion) { + const char *name; + int flag; + + /* Follow symbolic refs (mainly for HEAD). */ + name = resolve_ref_unsafe(ref->peer_ref->name, sha1, 1, &flag); + if (!name || !(flag & REF_ISSYMREF)) + name = ref->peer_ref->name; + + strbuf_addf(&buf, "%s:%s", name, ref->name); + } else + strbuf_addf(&buf, ":%s", ref->name); + + string_list_append(&revlist_args, "--refspec"); + string_list_append(&revlist_args, buf.buf); + strbuf_release(&buf); + } + if (!ref->deletion) + string_list_append(&revlist_args, ref->peer_ref->name); } } if (get_exporter(transport, &exporter, &revlist_args)) die("Couldn't run fast-export"); + string_list_clear(&revlist_args, 1); + if (finish_command(&exporter)) die("Error while running fast-export"); if (push_update_refs_status(data, remote_refs, flags)) diff --git a/transport.c b/transport.c index 325f03e1..662421bb 100644 --- a/transport.c +++ b/transport.c @@ -192,7 +192,9 @@ static void set_upstreams(struct transport *transport, struct ref *refs, static const char *rsync_url(const char *url) { - return !starts_with(url, "rsync://") ? skip_prefix(url, "rsync:") : url; + if (!starts_with(url, "rsync://")) + skip_prefix(url, "rsync:", &url); + return url; } static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) @@ -261,32 +263,20 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) static int fetch_objs_via_rsync(struct transport *transport, int nr_objs, struct ref **to_fetch) { - struct strbuf buf = STRBUF_INIT; struct child_process rsync; - const char *args[8]; - int result; - - strbuf_addstr(&buf, rsync_url(transport->url)); - strbuf_addstr(&buf, "/objects/"); memset(&rsync, 0, sizeof(rsync)); - rsync.argv = args; rsync.stdout_to_stderr = 1; - args[0] = "rsync"; - args[1] = (transport->verbose > 1) ? "-rv" : "-r"; - args[2] = "--ignore-existing"; - args[3] = "--exclude"; - args[4] = "info"; - args[5] = buf.buf; - args[6] = get_object_directory(); - args[7] = NULL; + argv_array_push(&rsync.args, "rsync"); + argv_array_push(&rsync.args, (transport->verbose > 1) ? "-rv" : "-r"); + argv_array_push(&rsync.args, "--ignore-existing"); + argv_array_push(&rsync.args, "--exclude"); + argv_array_push(&rsync.args, "info"); + argv_array_pushf(&rsync.args, "%s/objects/", rsync_url(transport->url)); + argv_array_push(&rsync.args, get_object_directory()); /* NEEDSWORK: handle one level of alternates */ - result = run_command(&rsync); - - strbuf_release(&buf); - - return result; + return run_command(&rsync); } static int write_one_ref(const char *name, const unsigned char *sha1, @@ -1186,10 +1176,8 @@ int transport_push(struct transport *transport, if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) { struct ref *ref = remote_refs; - struct string_list needs_pushing; + struct string_list needs_pushing = STRING_LIST_INIT_DUP; - memset(&needs_pushing, 0, sizeof(struct string_list)); - needs_pushing.strdup_strings = 1; for (; ref; ref = ref->next) if (!is_null_sha1(ref->new_sha1) && find_unpushed_submodules(ref->new_sha1, @@ -1369,11 +1357,11 @@ static int refs_from_alternate_cb(struct alternate_object_database *e, while (other[len-1] == '/') other[--len] = '\0'; if (len < 8 || memcmp(other + len - 8, "/objects", 8)) - return 0; + goto out; /* Is this a git repository with refs? */ memcpy(other + len - 8, "/refs", 6); if (!is_directory(other)) - return 0; + goto out; other[len - 8] = '\0'; remote = remote_get(other); transport = transport_get(remote, other); @@ -1382,6 +1370,7 @@ static int refs_from_alternate_cb(struct alternate_object_database *e, extra = extra->next) cb->fn(extra, cb->data); transport_disconnect(transport); +out: free(other); return 0; } diff --git a/tree-diff.c b/tree-diff.c index 11c35501..e7b378c8 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -6,121 +6,293 @@ #include "diffcore.h" #include "tree.h" -static void show_entry(struct diff_options *opt, const char *prefix, - struct tree_desc *desc, struct strbuf *base); +/* + * internal mode marker, saying a tree entry != entry of tp[imin] + * (see ll_diff_tree_paths for what it means there) + * + * we will update/use/emit entry for diff only with it unset. + */ +#define S_IFXMIN_NEQ S_DIFFTREE_IFXMIN_NEQ -static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, - struct strbuf *base, struct diff_options *opt) -{ - unsigned mode1, mode2; - const char *path1, *path2; - const unsigned char *sha1, *sha2; - int cmp, pathlen1, pathlen2; - int old_baselen = base->len; - sha1 = tree_entry_extract(t1, &path1, &mode1); - sha2 = tree_entry_extract(t2, &path2, &mode2); +static struct combine_diff_path *ll_diff_tree_paths( + struct combine_diff_path *p, const unsigned char *sha1, + const unsigned char **parents_sha1, int nparent, + struct strbuf *base, struct diff_options *opt); +static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new, + struct strbuf *base, struct diff_options *opt); + +/* + * Compare two tree entries, taking into account only path/S_ISDIR(mode), + * but not their sha1's. + * + * NOTE files and directories *always* compare differently, even when having + * the same name - thanks to base_name_compare(). + * + * NOTE empty (=invalid) descriptor(s) take part in comparison as +infty, + * so that they sort *after* valid tree entries. + * + * Due to this convention, if trees are scanned in sorted order, all + * non-empty descriptors will be processed first. + */ +static int tree_entry_pathcmp(struct tree_desc *t1, struct tree_desc *t2) +{ + struct name_entry *e1, *e2; + int cmp; - pathlen1 = tree_entry_len(&t1->entry); - pathlen2 = tree_entry_len(&t2->entry); - cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2); - if (cmp < 0) { - show_entry(opt, "-", t1, base); + /* empty descriptors sort after valid tree entries */ + if (!t1->size) + return t2->size ? 1 : 0; + else if (!t2->size) return -1; - } - if (cmp > 0) { - show_entry(opt, "+", t2, base); - return 1; - } - if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2) - return 0; - /* - * If the filemode has changed to/from a directory from/to a regular - * file, we need to consider it a remove and an add. - */ - if (S_ISDIR(mode1) != S_ISDIR(mode2)) { - show_entry(opt, "-", t1, base); - show_entry(opt, "+", t2, base); - return 0; - } + e1 = &t1->entry; + e2 = &t2->entry; + cmp = base_name_compare(e1->path, tree_entry_len(e1), e1->mode, + e2->path, tree_entry_len(e2), e2->mode); + return cmp; +} - strbuf_add(base, path1, pathlen1); - if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) { - if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) { - opt->change(opt, mode1, mode2, - sha1, sha2, 1, 1, base->buf, 0, 0); + +/* + * convert path -> opt->diff_*() callbacks + * + * emits diff to first parent only, and tells diff tree-walker that we are done + * with p and it can be freed. + */ +static int emit_diff_first_parent_only(struct diff_options *opt, struct combine_diff_path *p) +{ + struct combine_diff_parent *p0 = &p->parent[0]; + if (p->mode && p0->mode) { + opt->change(opt, p0->mode, p->mode, p0->sha1, p->sha1, + 1, 1, p->path, 0, 0); + } + else { + const unsigned char *sha1; + unsigned int mode; + int addremove; + + if (p->mode) { + addremove = '+'; + sha1 = p->sha1; + mode = p->mode; + } else { + addremove = '-'; + sha1 = p0->sha1; + mode = p0->mode; } - strbuf_addch(base, '/'); - diff_tree_sha1(sha1, sha2, base->buf, opt); - } else { - opt->change(opt, mode1, mode2, sha1, sha2, 1, 1, base->buf, 0, 0); + + opt->add_remove(opt, addremove, mode, sha1, 1, p->path, 0); } - strbuf_setlen(base, old_baselen); - return 0; + + return 0; /* we are done with p */ } -/* A whole sub-tree went away or appeared */ -static void show_tree(struct diff_options *opt, const char *prefix, - struct tree_desc *desc, struct strbuf *base) + +/* + * Make a new combine_diff_path from path/mode/sha1 + * and append it to paths list tail. + * + * Memory for created elements could be reused: + * + * - if last->next == NULL, the memory is allocated; + * + * - if last->next != NULL, it is assumed that p=last->next was returned + * earlier by this function, and p->next was *not* modified. + * The memory is then reused from p. + * + * so for clients, + * + * - if you do need to keep the element + * + * p = path_appendnew(p, ...); + * process(p); + * p->next = NULL; + * + * - if you don't need to keep the element after processing + * + * pprev = p; + * p = path_appendnew(p, ...); + * process(p); + * p = pprev; + * ; don't forget to free tail->next in the end + * + * p->parent[] remains uninitialized. + */ +static struct combine_diff_path *path_appendnew(struct combine_diff_path *last, + int nparent, const struct strbuf *base, const char *path, int pathlen, + unsigned mode, const unsigned char *sha1) { - enum interesting match = entry_not_interesting; - for (; desc->size; update_tree_entry(desc)) { - if (match != all_entries_interesting) { - match = tree_entry_interesting(&desc->entry, base, 0, - &opt->pathspec); - if (match == all_entries_not_interesting) - break; - if (match == entry_not_interesting) - continue; - } - show_entry(opt, prefix, desc, base); + struct combine_diff_path *p; + int len = base->len + pathlen; + int alloclen = combine_diff_path_size(nparent, len); + + /* if last->next is !NULL - it is a pre-allocated memory, we can reuse */ + p = last->next; + if (p && (alloclen > (intptr_t)p->next)) { + free(p); + p = NULL; + } + + if (!p) { + p = xmalloc(alloclen); + + /* + * until we go to it next round, .next holds how many bytes we + * allocated (for faster realloc - we don't need copying old data). + */ + p->next = (struct combine_diff_path *)(intptr_t)alloclen; } + + last->next = p; + + p->path = (char *)&(p->parent[nparent]); + memcpy(p->path, base->buf, base->len); + memcpy(p->path + base->len, path, pathlen); + p->path[len] = 0; + p->mode = mode; + hashcpy(p->sha1, sha1 ? sha1 : null_sha1); + + return p; } -/* A file entry went away or appeared */ -static void show_entry(struct diff_options *opt, const char *prefix, - struct tree_desc *desc, struct strbuf *base) +/* + * new path should be added to combine diff + * + * 3 cases on how/when it should be called and behaves: + * + * t, !tp -> path added, all parents lack it + * !t, tp -> path removed from all parents + * t, tp -> path modified/added + * (M for tp[i]=tp[imin], A otherwise) + */ +static struct combine_diff_path *emit_path(struct combine_diff_path *p, + struct strbuf *base, struct diff_options *opt, int nparent, + struct tree_desc *t, struct tree_desc *tp, + int imin) { unsigned mode; const char *path; - const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode); - int pathlen = tree_entry_len(&desc->entry); + const unsigned char *sha1; + int pathlen; int old_baselen = base->len; + int i, isdir, recurse = 0, emitthis = 1; + + /* at least something has to be valid */ + assert(t || tp); - strbuf_add(base, path, pathlen); - if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) { - enum object_type type; - struct tree_desc inner; - void *tree; - unsigned long size; + if (t) { + /* path present in resulting tree */ + sha1 = tree_entry_extract(t, &path, &mode); + pathlen = tree_entry_len(&t->entry); + isdir = S_ISDIR(mode); + } else { + /* + * a path was removed - take path from imin parent. Also take + * mode from that parent, to decide on recursion(1). + * + * 1) all modes for tp[i]=tp[imin] should be the same wrt + * S_ISDIR, thanks to base_name_compare(). + */ + tree_entry_extract(&tp[imin], &path, &mode); + pathlen = tree_entry_len(&tp[imin].entry); - tree = read_sha1_file(sha1, &type, &size); - if (!tree || type != OBJ_TREE) - die("corrupt tree sha %s", sha1_to_hex(sha1)); + isdir = S_ISDIR(mode); + sha1 = NULL; + mode = 0; + } - if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) - opt->add_remove(opt, *prefix, mode, sha1, 1, base->buf, 0); + if (DIFF_OPT_TST(opt, RECURSIVE) && isdir) { + recurse = 1; + emitthis = DIFF_OPT_TST(opt, TREE_IN_RECURSIVE); + } - strbuf_addch(base, '/'); + if (emitthis) { + int keep; + struct combine_diff_path *pprev = p; + p = path_appendnew(p, nparent, base, path, pathlen, mode, sha1); - init_tree_desc(&inner, tree, size); - show_tree(opt, prefix, &inner, base); - free(tree); - } else - opt->add_remove(opt, prefix[0], mode, sha1, 1, base->buf, 0); + for (i = 0; i < nparent; ++i) { + /* + * tp[i] is valid, if present and if tp[i]==tp[imin] - + * otherwise, we should ignore it. + */ + int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ); + + const unsigned char *sha1_i; + unsigned mode_i; + + p->parent[i].status = + !t ? DIFF_STATUS_DELETED : + tpi_valid ? + DIFF_STATUS_MODIFIED : + DIFF_STATUS_ADDED; + + if (tpi_valid) { + sha1_i = tp[i].entry.sha1; + mode_i = tp[i].entry.mode; + } + else { + sha1_i = NULL; + mode_i = 0; + } + + p->parent[i].mode = mode_i; + hashcpy(p->parent[i].sha1, sha1_i ? sha1_i : null_sha1); + } + + keep = 1; + if (opt->pathchange) + keep = opt->pathchange(opt, p); + + /* + * If a path was filtered or consumed - we don't need to add it + * to the list and can reuse its memory, leaving it as + * pre-allocated element on the tail. + * + * On the other hand, if path needs to be kept, we need to + * correct its .next to NULL, as it was pre-initialized to how + * much memory was allocated. + * + * see path_appendnew() for details. + */ + if (!keep) + p = pprev; + else + p->next = NULL; + } + + if (recurse) { + const unsigned char **parents_sha1; + + parents_sha1 = xalloca(nparent * sizeof(parents_sha1[0])); + for (i = 0; i < nparent; ++i) { + /* same rule as in emitthis */ + int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ); + + parents_sha1[i] = tpi_valid ? tp[i].entry.sha1 + : NULL; + } + + strbuf_add(base, path, pathlen); + strbuf_addch(base, '/'); + p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt); + xalloca_free(parents_sha1); + } strbuf_setlen(base, old_baselen); + return p; } static void skip_uninteresting(struct tree_desc *t, struct strbuf *base, - struct diff_options *opt, - enum interesting *match) + struct diff_options *opt) { + enum interesting match; + while (t->size) { - *match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec); - if (*match) { - if (*match == all_entries_not_interesting) + match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec); + if (match) { + if (match == all_entries_not_interesting) t->size = 0; break; } @@ -128,55 +300,260 @@ static void skip_uninteresting(struct tree_desc *t, struct strbuf *base, } } -int diff_tree(struct tree_desc *t1, struct tree_desc *t2, - const char *base_str, struct diff_options *opt) + +/* + * generate paths for combined diff D(sha1,parents_sha1[]) + * + * Resulting paths are appended to combine_diff_path linked list, and also, are + * emitted on the go via opt->pathchange() callback, so it is possible to + * process the result as batch or incrementally. + * + * The paths are generated scanning new tree and all parents trees + * simultaneously, similarly to what diff_tree() was doing for 2 trees. + * The theory behind such scan is as follows: + * + * + * D(T,P1...Pn) calculation scheme + * ------------------------------- + * + * D(T,P1...Pn) = D(T,P1) ^ ... ^ D(T,Pn) (regarding resulting paths set) + * + * D(T,Pj) - diff between T..Pj + * D(T,P1...Pn) - combined diff from T to parents P1,...,Pn + * + * + * We start from all trees, which are sorted, and compare their entries in + * lock-step: + * + * T P1 Pn + * - - - + * |t| |p1| |pn| + * |-| |--| ... |--| imin = argmin(p1...pn) + * | | | | | | + * |-| |--| |--| + * |.| |. | |. | + * . . . + * . . . + * + * at any time there could be 3 cases: + * + * 1) t < p[imin]; + * 2) t > p[imin]; + * 3) t = p[imin]. + * + * Schematic deduction of what every case means, and what to do, follows: + * + * 1) t < p[imin] -> ∀j t ∉ Pj -> "+t" ∈ D(T,Pj) -> D += "+t"; t↓ + * + * 2) t > p[imin] + * + * 2.1) ∃j: pj > p[imin] -> "-p[imin]" ∉ D(T,Pj) -> D += ø; ∀ pi=p[imin] pi↓ + * 2.2) ∀i pi = p[imin] -> pi ∉ T -> "-pi" ∈ D(T,Pi) -> D += "-p[imin]"; ∀i pi↓ + * + * 3) t = p[imin] + * + * 3.1) ∃j: pj > p[imin] -> "+t" ∈ D(T,Pj) -> only pi=p[imin] remains to investigate + * 3.2) pi = p[imin] -> investigate δ(t,pi) + * | + * | + * v + * + * 3.1+3.2) looking at δ(t,pi) ∀i: pi=p[imin] - if all != ø -> + * + * ⎧δ(t,pi) - if pi=p[imin] + * -> D += ⎨ + * ⎩"+t" - if pi>p[imin] + * + * + * in any case t↓ ∀ pi=p[imin] pi↓ + * + * + * ~~~~~~~~ + * + * NOTE + * + * Usual diff D(A,B) is by definition the same as combined diff D(A,[B]), + * so this diff paths generator can, and is used, for plain diffs + * generation too. + * + * Please keep attention to the common D(A,[B]) case when working on the + * code, in order not to slow it down. + * + * NOTE + * nparent must be > 0. + */ + + +/* ∀ pi=p[imin] pi↓ */ +static inline void update_tp_entries(struct tree_desc *tp, int nparent) { - struct strbuf base; - int baselen = strlen(base_str); - enum interesting t1_match = entry_not_interesting; - enum interesting t2_match = entry_not_interesting; + int i; + for (i = 0; i < nparent; ++i) + if (!(tp[i].entry.mode & S_IFXMIN_NEQ)) + update_tree_entry(&tp[i]); +} + +static struct combine_diff_path *ll_diff_tree_paths( + struct combine_diff_path *p, const unsigned char *sha1, + const unsigned char **parents_sha1, int nparent, + struct strbuf *base, struct diff_options *opt) +{ + struct tree_desc t, *tp; + void *ttree, **tptree; + int i; + + tp = xalloca(nparent * sizeof(tp[0])); + tptree = xalloca(nparent * sizeof(tptree[0])); + + /* + * load parents first, as they are probably already cached. + * + * ( log_tree_diff() parses commit->parent before calling here via + * diff_tree_sha1(parent, commit) ) + */ + for (i = 0; i < nparent; ++i) + tptree[i] = fill_tree_descriptor(&tp[i], parents_sha1[i]); + ttree = fill_tree_descriptor(&t, sha1); /* Enable recursion indefinitely */ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE); - strbuf_init(&base, PATH_MAX); - strbuf_add(&base, base_str, baselen); - for (;;) { + int imin, cmp; + if (diff_can_quit_early(opt)) break; + if (opt->pathspec.nr) { - skip_uninteresting(t1, &base, opt, &t1_match); - skip_uninteresting(t2, &base, opt, &t2_match); + skip_uninteresting(&t, base, opt); + for (i = 0; i < nparent; i++) + skip_uninteresting(&tp[i], base, opt); } - if (!t1->size) { - if (!t2->size) + + /* comparing is finished when all trees are done */ + if (!t.size) { + int done = 1; + for (i = 0; i < nparent; ++i) + if (tp[i].size) { + done = 0; + break; + } + if (done) break; - show_entry(opt, "+", t2, &base); - update_tree_entry(t2); - continue; } - if (!t2->size) { - show_entry(opt, "-", t1, &base); - update_tree_entry(t1); - continue; + + /* + * lookup imin = argmin(p1...pn), + * mark entries whether they =p[imin] along the way + */ + imin = 0; + tp[0].entry.mode &= ~S_IFXMIN_NEQ; + + for (i = 1; i < nparent; ++i) { + cmp = tree_entry_pathcmp(&tp[i], &tp[imin]); + if (cmp < 0) { + imin = i; + tp[i].entry.mode &= ~S_IFXMIN_NEQ; + } + else if (cmp == 0) { + tp[i].entry.mode &= ~S_IFXMIN_NEQ; + } + else { + tp[i].entry.mode |= S_IFXMIN_NEQ; + } + } + + /* fixup markings for entries before imin */ + for (i = 0; i < imin; ++i) + tp[i].entry.mode |= S_IFXMIN_NEQ; /* pi > p[imin] */ + + + + /* compare t vs p[imin] */ + cmp = tree_entry_pathcmp(&t, &tp[imin]); + + /* t = p[imin] */ + if (cmp == 0) { + /* are either pi > p[imin] or diff(t,pi) != ø ? */ + if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER)) { + for (i = 0; i < nparent; ++i) { + /* p[i] > p[imin] */ + if (tp[i].entry.mode & S_IFXMIN_NEQ) + continue; + + /* diff(t,pi) != ø */ + if (hashcmp(t.entry.sha1, tp[i].entry.sha1) || + (t.entry.mode != tp[i].entry.mode)) + continue; + + goto skip_emit_t_tp; + } + } + + /* D += {δ(t,pi) if pi=p[imin]; "+a" if pi > p[imin]} */ + p = emit_path(p, base, opt, nparent, + &t, tp, imin); + + skip_emit_t_tp: + /* t↓, ∀ pi=p[imin] pi↓ */ + update_tree_entry(&t); + update_tp_entries(tp, nparent); + } + + /* t < p[imin] */ + else if (cmp < 0) { + /* D += "+t" */ + p = emit_path(p, base, opt, nparent, + &t, /*tp=*/NULL, -1); + + /* t↓ */ + update_tree_entry(&t); } - switch (compare_tree_entry(t1, t2, &base, opt)) { - case -1: - update_tree_entry(t1); - continue; - case 0: - update_tree_entry(t1); - /* Fallthrough */ - case 1: - update_tree_entry(t2); - continue; + + /* t > p[imin] */ + else { + /* ∀i pi=p[imin] -> D += "-p[imin]" */ + if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER)) { + for (i = 0; i < nparent; ++i) + if (tp[i].entry.mode & S_IFXMIN_NEQ) + goto skip_emit_tp; + } + + p = emit_path(p, base, opt, nparent, + /*t=*/NULL, tp, imin); + + skip_emit_tp: + /* ∀ pi=p[imin] pi↓ */ + update_tp_entries(tp, nparent); } - die("git diff-tree: internal error"); } - strbuf_release(&base); - return 0; + free(ttree); + for (i = nparent-1; i >= 0; i--) + free(tptree[i]); + xalloca_free(tptree); + xalloca_free(tp); + + return p; +} + +struct combine_diff_path *diff_tree_paths( + struct combine_diff_path *p, const unsigned char *sha1, + const unsigned char **parents_sha1, int nparent, + struct strbuf *base, struct diff_options *opt) +{ + p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt); + + /* + * free pre-allocated last element, if any + * (see path_appendnew() for details about why) + */ + if (p->next) { + free(p->next); + p->next = NULL; + } + + return p; } /* @@ -190,7 +567,7 @@ static inline int diff_might_be_rename(void) !DIFF_FILE_VALID(diff_queued_diff.queue[0]->one); } -static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) +static void try_to_follow_renames(const unsigned char *old, const unsigned char *new, struct strbuf *base, struct diff_options *opt) { struct diff_options diff_opts; struct diff_queue_struct *q = &diff_queued_diff; @@ -228,7 +605,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co diff_opts.break_opt = opt->break_opt; diff_opts.rename_score = opt->rename_score; diff_setup_done(&diff_opts); - diff_tree(t1, t2, base, &diff_opts); + ll_diff_tree_sha1(old, new, base, &diff_opts); diffcore_std(&diff_opts); free_pathspec(&diff_opts.pathspec); @@ -287,25 +664,40 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co q->nr = 1; } -int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt) +static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new, + struct strbuf *base, struct diff_options *opt) { - void *tree1, *tree2; - struct tree_desc t1, t2; - unsigned long size1, size2; - int retval; + struct combine_diff_path phead, *p; + pathchange_fn_t pathchange_old = opt->pathchange; - tree1 = fill_tree_descriptor(&t1, old); - tree2 = fill_tree_descriptor(&t2, new); - size1 = t1.size; - size2 = t2.size; - retval = diff_tree(&t1, &t2, base, opt); - if (!*base && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) { - init_tree_desc(&t1, tree1, size1); - init_tree_desc(&t2, tree2, size2); - try_to_follow_renames(&t1, &t2, base, opt); + phead.next = NULL; + opt->pathchange = emit_diff_first_parent_only; + diff_tree_paths(&phead, new, &old, 1, base, opt); + + for (p = phead.next; p;) { + struct combine_diff_path *pprev = p; + p = p->next; + free(pprev); } - free(tree1); - free(tree2); + + opt->pathchange = pathchange_old; + return 0; +} + +int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base_str, struct diff_options *opt) +{ + struct strbuf base; + int retval; + + strbuf_init(&base, PATH_MAX); + strbuf_addstr(&base, base_str); + + retval = ll_diff_tree_sha1(old, new, &base, opt); + if (!*base_str && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) + try_to_follow_renames(old, new, &base, opt); + + strbuf_release(&base); + return retval; } diff --git a/tree-walk.c b/tree-walk.c index 4dc86c7f..5dd9a718 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -144,16 +144,6 @@ struct tree_desc_x { struct tree_desc_skip *skip; }; -static int name_compare(const char *a, int a_len, - const char *b, int b_len) -{ - int len = (a_len < b_len) ? a_len : b_len; - int cmp = memcmp(a, b, len); - if (cmp) - return cmp; - return (a_len - b_len); -} - static int check_entry_match(const char *a, int a_len, const char *b, int b_len) { /* diff --git a/unicode_width.h b/unicode_width.h new file mode 100644 index 00000000..47cdd236 --- /dev/null +++ b/unicode_width.h @@ -0,0 +1,323 @@ +static const struct interval zero_width[] = { +{ 0x0300, 0x036F }, +{ 0x0483, 0x0489 }, +{ 0x0591, 0x05BD }, +{ 0x05BF, 0x05BF }, +{ 0x05C1, 0x05C2 }, +{ 0x05C4, 0x05C5 }, +{ 0x05C7, 0x05C7 }, +{ 0x0600, 0x0605 }, +{ 0x0610, 0x061A }, +{ 0x061C, 0x061C }, +{ 0x064B, 0x065F }, +{ 0x0670, 0x0670 }, +{ 0x06D6, 0x06DD }, +{ 0x06DF, 0x06E4 }, +{ 0x06E7, 0x06E8 }, +{ 0x06EA, 0x06ED }, +{ 0x070F, 0x070F }, +{ 0x0711, 0x0711 }, +{ 0x0730, 0x074A }, +{ 0x07A6, 0x07B0 }, +{ 0x07EB, 0x07F3 }, +{ 0x0816, 0x0819 }, +{ 0x081B, 0x0823 }, +{ 0x0825, 0x0827 }, +{ 0x0829, 0x082D }, +{ 0x0859, 0x085B }, +{ 0x08E4, 0x0902 }, +{ 0x093A, 0x093A }, +{ 0x093C, 0x093C }, +{ 0x0941, 0x0948 }, +{ 0x094D, 0x094D }, +{ 0x0951, 0x0957 }, +{ 0x0962, 0x0963 }, +{ 0x0981, 0x0981 }, +{ 0x09BC, 0x09BC }, +{ 0x09C1, 0x09C4 }, +{ 0x09CD, 0x09CD }, +{ 0x09E2, 0x09E3 }, +{ 0x0A01, 0x0A02 }, +{ 0x0A3C, 0x0A3C }, +{ 0x0A41, 0x0A42 }, +{ 0x0A47, 0x0A48 }, +{ 0x0A4B, 0x0A4D }, +{ 0x0A51, 0x0A51 }, +{ 0x0A70, 0x0A71 }, +{ 0x0A75, 0x0A75 }, +{ 0x0A81, 0x0A82 }, +{ 0x0ABC, 0x0ABC }, +{ 0x0AC1, 0x0AC5 }, +{ 0x0AC7, 0x0AC8 }, +{ 0x0ACD, 0x0ACD }, +{ 0x0AE2, 0x0AE3 }, +{ 0x0B01, 0x0B01 }, +{ 0x0B3C, 0x0B3C }, +{ 0x0B3F, 0x0B3F }, +{ 0x0B41, 0x0B44 }, +{ 0x0B4D, 0x0B4D }, +{ 0x0B56, 0x0B56 }, +{ 0x0B62, 0x0B63 }, +{ 0x0B82, 0x0B82 }, +{ 0x0BC0, 0x0BC0 }, +{ 0x0BCD, 0x0BCD }, +{ 0x0C00, 0x0C00 }, +{ 0x0C3E, 0x0C40 }, +{ 0x0C46, 0x0C48 }, +{ 0x0C4A, 0x0C4D }, +{ 0x0C55, 0x0C56 }, +{ 0x0C62, 0x0C63 }, +{ 0x0C81, 0x0C81 }, +{ 0x0CBC, 0x0CBC }, +{ 0x0CBF, 0x0CBF }, +{ 0x0CC6, 0x0CC6 }, +{ 0x0CCC, 0x0CCD }, +{ 0x0CE2, 0x0CE3 }, +{ 0x0D01, 0x0D01 }, +{ 0x0D41, 0x0D44 }, +{ 0x0D4D, 0x0D4D }, +{ 0x0D62, 0x0D63 }, +{ 0x0DCA, 0x0DCA }, +{ 0x0DD2, 0x0DD4 }, +{ 0x0DD6, 0x0DD6 }, +{ 0x0E31, 0x0E31 }, +{ 0x0E34, 0x0E3A }, +{ 0x0E47, 0x0E4E }, +{ 0x0EB1, 0x0EB1 }, +{ 0x0EB4, 0x0EB9 }, +{ 0x0EBB, 0x0EBC }, +{ 0x0EC8, 0x0ECD }, +{ 0x0F18, 0x0F19 }, +{ 0x0F35, 0x0F35 }, +{ 0x0F37, 0x0F37 }, +{ 0x0F39, 0x0F39 }, +{ 0x0F71, 0x0F7E }, +{ 0x0F80, 0x0F84 }, +{ 0x0F86, 0x0F87 }, +{ 0x0F8D, 0x0F97 }, +{ 0x0F99, 0x0FBC }, +{ 0x0FC6, 0x0FC6 }, +{ 0x102D, 0x1030 }, +{ 0x1032, 0x1037 }, +{ 0x1039, 0x103A }, +{ 0x103D, 0x103E }, +{ 0x1058, 0x1059 }, +{ 0x105E, 0x1060 }, +{ 0x1071, 0x1074 }, +{ 0x1082, 0x1082 }, +{ 0x1085, 0x1086 }, +{ 0x108D, 0x108D }, +{ 0x109D, 0x109D }, +{ 0x1160, 0x11FF }, +{ 0x135D, 0x135F }, +{ 0x1712, 0x1714 }, +{ 0x1732, 0x1734 }, +{ 0x1752, 0x1753 }, +{ 0x1772, 0x1773 }, +{ 0x17B4, 0x17B5 }, +{ 0x17B7, 0x17BD }, +{ 0x17C6, 0x17C6 }, +{ 0x17C9, 0x17D3 }, +{ 0x17DD, 0x17DD }, +{ 0x180B, 0x180E }, +{ 0x18A9, 0x18A9 }, +{ 0x1920, 0x1922 }, +{ 0x1927, 0x1928 }, +{ 0x1932, 0x1932 }, +{ 0x1939, 0x193B }, +{ 0x1A17, 0x1A18 }, +{ 0x1A1B, 0x1A1B }, +{ 0x1A56, 0x1A56 }, +{ 0x1A58, 0x1A5E }, +{ 0x1A60, 0x1A60 }, +{ 0x1A62, 0x1A62 }, +{ 0x1A65, 0x1A6C }, +{ 0x1A73, 0x1A7C }, +{ 0x1A7F, 0x1A7F }, +{ 0x1AB0, 0x1ABE }, +{ 0x1B00, 0x1B03 }, +{ 0x1B34, 0x1B34 }, +{ 0x1B36, 0x1B3A }, +{ 0x1B3C, 0x1B3C }, +{ 0x1B42, 0x1B42 }, +{ 0x1B6B, 0x1B73 }, +{ 0x1B80, 0x1B81 }, +{ 0x1BA2, 0x1BA5 }, +{ 0x1BA8, 0x1BA9 }, +{ 0x1BAB, 0x1BAD }, +{ 0x1BE6, 0x1BE6 }, +{ 0x1BE8, 0x1BE9 }, +{ 0x1BED, 0x1BED }, +{ 0x1BEF, 0x1BF1 }, +{ 0x1C2C, 0x1C33 }, +{ 0x1C36, 0x1C37 }, +{ 0x1CD0, 0x1CD2 }, +{ 0x1CD4, 0x1CE0 }, +{ 0x1CE2, 0x1CE8 }, +{ 0x1CED, 0x1CED }, +{ 0x1CF4, 0x1CF4 }, +{ 0x1CF8, 0x1CF9 }, +{ 0x1DC0, 0x1DF5 }, +{ 0x1DFC, 0x1DFF }, +{ 0x200B, 0x200F }, +{ 0x202A, 0x202E }, +{ 0x2060, 0x2064 }, +{ 0x2066, 0x206F }, +{ 0x20D0, 0x20F0 }, +{ 0x2CEF, 0x2CF1 }, +{ 0x2D7F, 0x2D7F }, +{ 0x2DE0, 0x2DFF }, +{ 0x302A, 0x302D }, +{ 0x3099, 0x309A }, +{ 0xA66F, 0xA672 }, +{ 0xA674, 0xA67D }, +{ 0xA69F, 0xA69F }, +{ 0xA6F0, 0xA6F1 }, +{ 0xA802, 0xA802 }, +{ 0xA806, 0xA806 }, +{ 0xA80B, 0xA80B }, +{ 0xA825, 0xA826 }, +{ 0xA8C4, 0xA8C4 }, +{ 0xA8E0, 0xA8F1 }, +{ 0xA926, 0xA92D }, +{ 0xA947, 0xA951 }, +{ 0xA980, 0xA982 }, +{ 0xA9B3, 0xA9B3 }, +{ 0xA9B6, 0xA9B9 }, +{ 0xA9BC, 0xA9BC }, +{ 0xA9E5, 0xA9E5 }, +{ 0xAA29, 0xAA2E }, +{ 0xAA31, 0xAA32 }, +{ 0xAA35, 0xAA36 }, +{ 0xAA43, 0xAA43 }, +{ 0xAA4C, 0xAA4C }, +{ 0xAA7C, 0xAA7C }, +{ 0xAAB0, 0xAAB0 }, +{ 0xAAB2, 0xAAB4 }, +{ 0xAAB7, 0xAAB8 }, +{ 0xAABE, 0xAABF }, +{ 0xAAC1, 0xAAC1 }, +{ 0xAAEC, 0xAAED }, +{ 0xAAF6, 0xAAF6 }, +{ 0xABE5, 0xABE5 }, +{ 0xABE8, 0xABE8 }, +{ 0xABED, 0xABED }, +{ 0xFB1E, 0xFB1E }, +{ 0xFE00, 0xFE0F }, +{ 0xFE20, 0xFE2D }, +{ 0xFEFF, 0xFEFF }, +{ 0xFFF9, 0xFFFB }, +{ 0x101FD, 0x101FD }, +{ 0x102E0, 0x102E0 }, +{ 0x10376, 0x1037A }, +{ 0x10A01, 0x10A03 }, +{ 0x10A05, 0x10A06 }, +{ 0x10A0C, 0x10A0F }, +{ 0x10A38, 0x10A3A }, +{ 0x10A3F, 0x10A3F }, +{ 0x10AE5, 0x10AE6 }, +{ 0x11001, 0x11001 }, +{ 0x11038, 0x11046 }, +{ 0x1107F, 0x11081 }, +{ 0x110B3, 0x110B6 }, +{ 0x110B9, 0x110BA }, +{ 0x110BD, 0x110BD }, +{ 0x11100, 0x11102 }, +{ 0x11127, 0x1112B }, +{ 0x1112D, 0x11134 }, +{ 0x11173, 0x11173 }, +{ 0x11180, 0x11181 }, +{ 0x111B6, 0x111BE }, +{ 0x1122F, 0x11231 }, +{ 0x11234, 0x11234 }, +{ 0x11236, 0x11237 }, +{ 0x112DF, 0x112DF }, +{ 0x112E3, 0x112EA }, +{ 0x11301, 0x11301 }, +{ 0x1133C, 0x1133C }, +{ 0x11340, 0x11340 }, +{ 0x11366, 0x1136C }, +{ 0x11370, 0x11374 }, +{ 0x114B3, 0x114B8 }, +{ 0x114BA, 0x114BA }, +{ 0x114BF, 0x114C0 }, +{ 0x114C2, 0x114C3 }, +{ 0x115B2, 0x115B5 }, +{ 0x115BC, 0x115BD }, +{ 0x115BF, 0x115C0 }, +{ 0x11633, 0x1163A }, +{ 0x1163D, 0x1163D }, +{ 0x1163F, 0x11640 }, +{ 0x116AB, 0x116AB }, +{ 0x116AD, 0x116AD }, +{ 0x116B0, 0x116B5 }, +{ 0x116B7, 0x116B7 }, +{ 0x16AF0, 0x16AF4 }, +{ 0x16B30, 0x16B36 }, +{ 0x16F8F, 0x16F92 }, +{ 0x1BC9D, 0x1BC9E }, +{ 0x1BCA0, 0x1BCA3 }, +{ 0x1D167, 0x1D169 }, +{ 0x1D173, 0x1D182 }, +{ 0x1D185, 0x1D18B }, +{ 0x1D1AA, 0x1D1AD }, +{ 0x1D242, 0x1D244 }, +{ 0x1E8D0, 0x1E8D6 }, +{ 0xE0001, 0xE0001 }, +{ 0xE0020, 0xE007F }, +{ 0xE0100, 0xE01EF } +}; +static const struct interval double_width[] = { +{ /* plane */ 0x0, 0x1C }, +{ /* plane */ 0x1C, 0x21 }, +{ /* plane */ 0x21, 0x22 }, +{ /* plane */ 0x22, 0x23 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ /* plane */ 0x0, 0x0 }, +{ 0x1100, 0x115F }, +{ 0x2329, 0x232A }, +{ 0x2E80, 0x2E99 }, +{ 0x2E9B, 0x2EF3 }, +{ 0x2F00, 0x2FD5 }, +{ 0x2FF0, 0x2FFB }, +{ 0x3000, 0x303E }, +{ 0x3041, 0x3096 }, +{ 0x3099, 0x30FF }, +{ 0x3105, 0x312D }, +{ 0x3131, 0x318E }, +{ 0x3190, 0x31BA }, +{ 0x31C0, 0x31E3 }, +{ 0x31F0, 0x321E }, +{ 0x3220, 0x3247 }, +{ 0x3250, 0x32FE }, +{ 0x3300, 0x4DBF }, +{ 0x4E00, 0xA48C }, +{ 0xA490, 0xA4C6 }, +{ 0xA960, 0xA97C }, +{ 0xAC00, 0xD7A3 }, +{ 0xF900, 0xFAFF }, +{ 0xFE10, 0xFE19 }, +{ 0xFE30, 0xFE52 }, +{ 0xFE54, 0xFE66 }, +{ 0xFE68, 0xFE6B }, +{ 0xFF01, 0xFF60 }, +{ 0xFFE0, 0xFFE6 }, +{ 0x1B000, 0x1B001 }, +{ 0x1F200, 0x1F202 }, +{ 0x1F210, 0x1F23A }, +{ 0x1F240, 0x1F248 }, +{ 0x1F250, 0x1F251 }, +{ 0x20000, 0x2FFFD }, +{ 0x30000, 0x3FFFD } +}; diff --git a/unix-socket.c b/unix-socket.c index 01f119f9..91bd6b89 100644 --- a/unix-socket.c +++ b/unix-socket.c @@ -99,11 +99,12 @@ int unix_stream_listen(const char *path) struct sockaddr_un sa; struct unix_sockaddr_context ctx; + unlink(path); + if (unix_sockaddr_init(&sa, path, &ctx) < 0) return -1; fd = unix_stream_socket(); - unlink(path); if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) goto fail; diff --git a/unpack-trees.c b/unpack-trees.c index 02f69aee..c6aa8fb9 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -8,6 +8,7 @@ #include "progress.h" #include "refs.h" #include "attr.h" +#include "split-index.h" /* * Error messages expected by scripts out of plumbing commands such as @@ -56,17 +57,15 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, int i; const char **msgs = opts->msgs; const char *msg; - char *tmp; const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches"; + if (advice_commit_before_merge) msg = "Your local changes to the following files would be overwritten by %s:\n%%s" "Please, commit your changes or stash them before you can %s."; else msg = "Your local changes to the following files would be overwritten by %s:\n%%s"; - tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2); - sprintf(tmp, msg, cmd, cmd2); - msgs[ERROR_WOULD_OVERWRITE] = tmp; - msgs[ERROR_NOT_UPTODATE_FILE] = tmp; + msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] = + xstrfmt(msg, cmd, cmd2); msgs[ERROR_NOT_UPTODATE_DIR] = "Updating the following directories would lose untracked files in it:\n%s"; @@ -76,12 +75,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, "Please move or remove them before you can %s."; else msg = "The following untracked working tree files would be %s by %s:\n%%s"; - tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4); - sprintf(tmp, msg, "removed", cmd, cmd2); - msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp; - tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4); - sprintf(tmp, msg, "overwritten", cmd, cmd2); - msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp; + + msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, "removed", cmd, cmd2); + msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, "overwritten", cmd, cmd2); /* * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we @@ -102,7 +98,7 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, opts->unpack_rejects[i].strdup_strings = 1; } -static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, +static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, unsigned int set, unsigned int clear) { clear |= CE_HASHED; @@ -111,8 +107,8 @@ static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, set |= CE_WT_REMOVE; ce->ce_flags = (ce->ce_flags & ~clear) | set; - return add_index_entry(&o->result, ce, - ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); + add_index_entry(&o->result, ce, + ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } static struct cache_entry *dup_entry(const struct cache_entry *ce) @@ -246,7 +242,9 @@ static int verify_absent_sparse(const struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o); -static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_options *o) +static int apply_sparse_checkout(struct index_state *istate, + struct cache_entry *ce, + struct unpack_trees_options *o) { int was_skip_worktree = ce_skip_worktree(ce); @@ -254,6 +252,10 @@ static int apply_sparse_checkout(struct cache_entry *ce, struct unpack_trees_opt ce->ce_flags |= CE_SKIP_WORKTREE; else ce->ce_flags &= ~CE_SKIP_WORKTREE; + if (was_skip_worktree != ce_skip_worktree(ce)) { + ce->ce_flags |= CE_UPDATE_IN_BASE; + istate->cache_changed |= CE_ENTRY_CHANGED; + } /* * if (!was_skip_worktree && !ce_skip_worktree()) { @@ -607,9 +609,7 @@ static int unpack_nondirectories(int n, unsigned long mask, for (i = 0; i < n; i++) if (src[i] && src[i] != o->df_conflict_entry) - if (do_add_entry(o, src[i], 0, 0)) - return -1; - + do_add_entry(o, src[i], 0, 0); return 0; } @@ -624,17 +624,6 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message) return -1; } -/* NEEDSWORK: give this a better name and share with tree-walk.c */ -static int name_compare(const char *a, int a_len, - const char *b, int b_len) -{ - int len = (a_len < b_len) ? a_len : b_len; - int cmp = memcmp(a, b, len); - if (cmp) - return cmp; - return (a_len - b_len); -} - /* * The tree traversal is looking at name p. If we have a matching entry, * return it. If name p is a directory in the index, do not return @@ -1027,6 +1016,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options 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) @@ -1043,6 +1033,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->result.timestamp.sec = o->src_index->timestamp.sec; o->result.timestamp.nsec = o->src_index->timestamp.nsec; o->result.version = o->src_index->version; + o->result.split_index = o->src_index->split_index; + if (o->result.split_index) + o->result.split_index->refcount++; + hashcpy(o->result.sha1, o->src_index->sha1); o->merge_size = len; mark_all_ce_unused(o->src_index); @@ -1133,7 +1127,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options ret = -1; } - if (apply_sparse_checkout(ce, o)) { + if (apply_sparse_checkout(&o->result, ce, o)) { if (!o->show_all_errors) goto return_failed; ret = -1; @@ -1261,7 +1255,7 @@ static void invalidate_ce_path(const struct cache_entry *ce, struct unpack_trees_options *o) { if (ce) - cache_tree_invalidate_path(o->src_index->cache_tree, ce->name); + cache_tree_invalidate_path(o->src_index, ce->name); } /* diff --git a/update_unicode.sh b/update_unicode.sh new file mode 100755 index 00000000..000b937e --- /dev/null +++ b/update_unicode.sh @@ -0,0 +1,37 @@ +#!/bin/sh +#See http://www.unicode.org/reports/tr44/ +# +#Me Enclosing_Mark an enclosing combining mark +#Mn Nonspacing_Mark a nonspacing combining mark (zero advance width) +#Cf Format a format control character +# +UNICODEWIDTH_H=../unicode_width.h +if ! test -d unicode; then + mkdir unicode +fi && +( cd unicode && + if ! test -f UnicodeData.txt; then + wget http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt + fi && + if ! test -f EastAsianWidth.txt; then + wget http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt + fi && + if ! test -d uniset; then + git clone https://github.com/depp/uniset.git + fi && + ( + cd uniset && + if ! test -x uniset; then + autoreconf -i && + ./configure --enable-warnings=-Werror CFLAGS='-O0 -ggdb' + fi && + make + ) && + echo "static const struct interval zero_width[] = {" >$UNICODEWIDTH_H && + UNICODE_DIR=. ./uniset/uniset --32 cat:Me,Mn,Cf + U+1160..U+11FF - U+00AD | + grep -v plane >>$UNICODEWIDTH_H && + echo "};" >>$UNICODEWIDTH_H && + echo "static const struct interval double_width[] = {" >>$UNICODEWIDTH_H && + UNICODE_DIR=. ./uniset/uniset --32 eaw:F,W >>$UNICODEWIDTH_H && + echo "};" >>$UNICODEWIDTH_H +) @@ -121,7 +121,7 @@ void end_url_with_slash(struct strbuf *buf, const char *url) { strbuf_addstr(buf, url); if (buf->len && buf->buf[buf->len - 1] != '/') - strbuf_addstr(buf, "/"); + strbuf_addch(buf, '/'); } void str_end_url_with_slash(const char *url, char **dest) { @@ -483,8 +483,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb) int user_matched = 0; int retval; - key = skip_prefix(var, collect->section); - if (!key || *(key++) != '.') { + if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') { if (collect->cascade_fn) return collect->cascade_fn(var, value, cb); return 0; /* not interested */ @@ -80,52 +80,8 @@ static int git_wcwidth(ucs_char_t ch) { /* * Sorted list of non-overlapping intervals of non-spacing characters, - * generated by - * "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c". */ - static const struct interval combining[] = { - { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD }, - { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 }, - { 0x05C7, 0x05C7 }, { 0x0600, 0x0604 }, { 0x0610, 0x061A }, - { 0x064B, 0x065F }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 }, - { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, - { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 }, - { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, - { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, - { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, - { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, - { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, - { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, - { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, - { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, - { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, - { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, - { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, - { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, - { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, - { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x1712, 0x1714 }, - { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, - { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, - { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D }, - { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, - { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x200B, 0x200F }, - { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F }, - { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A }, - { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 }, - { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, - { 0x1D1AA, 0x1D1AD }, { 0xE0001, 0xE0001 }, - { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF } - }; +#include "unicode_width.h" /* test for 8-bit control characters */ if (ch == 0) @@ -134,34 +90,16 @@ static int git_wcwidth(ucs_char_t ch) return -1; /* binary search in table of non-spacing characters */ - if (bisearch(ch, combining, sizeof(combining) + if (bisearch(ch, zero_width, sizeof(zero_width) / sizeof(struct interval) - 1)) return 0; - /* - * If we arrive here, ch is neither a combining nor a C0/C1 - * control character. - */ + /* binary search in table of double width characters */ + if (bisearch(ch, double_width, sizeof(double_width) + / sizeof(struct interval) - 1)) + return 2; - return 1 + - (ch >= 0x1100 && - /* Hangul Jamo init. consonants */ - (ch <= 0x115f || - ch == 0x2329 || ch == 0x232a || - /* CJK ... Yi */ - (ch >= 0x2e80 && ch <= 0xa4cf && - ch != 0x303f) || - /* Hangul Syllables */ - (ch >= 0xac00 && ch <= 0xd7a3) || - /* CJK Compatibility Ideographs */ - (ch >= 0xf900 && ch <= 0xfaff) || - /* CJK Compatibility Forms */ - (ch >= 0xfe30 && ch <= 0xfe6f) || - /* Fullwidth Forms */ - (ch >= 0xff00 && ch <= 0xff60) || - (ch >= 0xffe0 && ch <= 0xffe6) || - (ch >= 0x20000 && ch <= 0x2fffd) || - (ch >= 0x30000 && ch <= 0x3fffd))); + return 1; } /* @@ -627,67 +565,3 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding) return chrlen; } - -/* - * Pick the next char from the stream, folding as an HFS+ filename comparison - * would. Note that this is _not_ complete by any means. It's just enough - * to make is_hfs_dotgit() work, and should not be used otherwise. - */ -static ucs_char_t next_hfs_char(const char **in) -{ - while (1) { - ucs_char_t out = pick_one_utf8_char(in, NULL); - /* - * check for malformed utf8. Technically this - * gets converted to a percent-sequence, but - * returning 0 is good enough for is_hfs_dotgit - * to realize it cannot be .git - */ - if (!*in) - return 0; - - /* these code points are ignored completely */ - switch (out) { - case 0x200c: /* ZERO WIDTH NON-JOINER */ - case 0x200d: /* ZERO WIDTH JOINER */ - case 0x200e: /* LEFT-TO-RIGHT MARK */ - case 0x200f: /* RIGHT-TO-LEFT MARK */ - case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */ - case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */ - case 0x202c: /* POP DIRECTIONAL FORMATTING */ - case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */ - case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */ - case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */ - case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */ - case 0x206c: /* INHIBIT ARABIC FORM SHAPING */ - case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */ - case 0x206e: /* NATIONAL DIGIT SHAPES */ - case 0x206f: /* NOMINAL DIGIT SHAPES */ - case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */ - continue; - } - - /* - * there's a great deal of other case-folding that occurs, - * but this is enough to catch anything that will convert - * to ".git" - */ - return tolower(out); - } -} - -int is_hfs_dotgit(const char *path) -{ - ucs_char_t c; - - if (next_hfs_char(&path) != '.' || - next_hfs_char(&path) != 'g' || - next_hfs_char(&path) != 'i' || - next_hfs_char(&path) != 't') - return 0; - c = next_hfs_char(&path); - if (c && !is_dir_sep(c)) - return 0; - - return 1; -} @@ -42,12 +42,4 @@ static inline char *reencode_string(const char *in, int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding); -/* - * Returns true if the the path would match ".git" after HFS case-folding. - * The path should be NUL-terminated, but we will match variants of both ".git\0" - * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck - * and verify_path(). - */ -int is_hfs_dotgit(const char *path); - #endif @@ -253,7 +253,8 @@ int walker_fetch(struct walker *walker, int targets, char **target, { struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *)); unsigned char *sha1 = xmalloc(targets * 20); - char *msg; + const char *msg; + char *to_free = NULL; int ret; int i; @@ -285,21 +286,19 @@ int walker_fetch(struct walker *walker, int targets, char **target, if (loop(walker)) goto unlock_and_fail; - if (write_ref_log_details) { - msg = xmalloc(strlen(write_ref_log_details) + 12); - sprintf(msg, "fetch from %s", write_ref_log_details); - } else { - msg = NULL; - } + if (write_ref_log_details) + msg = to_free = xstrfmt("fetch from %s", write_ref_log_details); + else + msg = "fetch (unknown)"; for (i = 0; i < targets; i++) { if (!write_ref || !write_ref[i]) continue; - ret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : "fetch (unknown)"); + ret = write_ref_sha1(lock[i], &sha1[20 * i], msg); lock[i] = NULL; if (ret) goto unlock_and_fail; } - free(msg); + free(to_free); return 0; @@ -307,6 +306,7 @@ unlock_and_fail: for (i = 0; i < targets; i++) if (lock[i]) unlock_ref(lock[i]); + free(to_free); return -1; } diff --git a/wt-status.c b/wt-status.c index 86fec898..27da5296 100644 --- a/wt-status.c +++ b/wt-status.c @@ -188,7 +188,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s) } else { status_printf_ln(s, c, _(" (use \"git add/rm <file>...\" as appropriate to mark resolution)")); } - status_printf_ln(s, c, ""); + status_printf_ln(s, c, "%s", ""); } static void wt_status_print_cached_header(struct wt_status *s) @@ -204,7 +204,7 @@ static void wt_status_print_cached_header(struct wt_status *s) status_printf_ln(s, c, _(" (use \"git reset %s <file>...\" to unstage)"), s->reference); else status_printf_ln(s, c, _(" (use \"git rm --cached <file>...\" to unstage)")); - status_printf_ln(s, c, ""); + status_printf_ln(s, c, "%s", ""); } static void wt_status_print_dirty_header(struct wt_status *s, @@ -223,7 +223,7 @@ static void wt_status_print_dirty_header(struct wt_status *s, status_printf_ln(s, c, _(" (use \"git checkout -- <file>...\" to discard changes in working directory)")); if (has_dirty_submodules) status_printf_ln(s, c, _(" (commit or discard the untracked or modified content in submodules)")); - status_printf_ln(s, c, ""); + status_printf_ln(s, c, "%s", ""); } static void wt_status_print_other_header(struct wt_status *s, @@ -235,12 +235,12 @@ static void wt_status_print_other_header(struct wt_status *s, if (!s->hints) return; status_printf_ln(s, c, _(" (use \"git %s <file>...\" to include in what will be committed)"), how); - status_printf_ln(s, c, ""); + status_printf_ln(s, c, "%s", ""); } static void wt_status_print_trailer(struct wt_status *s) { - status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); + status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", ""); } #define quote_path quote_path_relative @@ -574,14 +574,11 @@ static void wt_status_collect_untracked(struct wt_status *s) { int i; struct dir_struct dir; - struct timeval t_begin; + uint64_t t_begin = getnanotime(); if (!s->show_untracked_files) return; - if (advice_status_u_option) - gettimeofday(&t_begin, NULL); - memset(&dir, 0, sizeof(dir)); if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES) dir.flags |= @@ -612,13 +609,8 @@ static void wt_status_collect_untracked(struct wt_status *s) free(dir.ignored); clear_directory(&dir); - if (advice_status_u_option) { - struct timeval t_end; - gettimeofday(&t_end, NULL); - s->untracked_in_ms = - (uint64_t)t_end.tv_sec * 1000 + t_end.tv_usec / 1000 - - ((uint64_t)t_begin.tv_sec * 1000 + t_begin.tv_usec / 1000); - } + if (advice_status_u_option) + s->untracked_in_ms = (getnanotime() - t_begin) / 1000000; } void wt_status_collect(struct wt_status *s) @@ -734,37 +726,34 @@ static void wt_status_print_changed(struct wt_status *s) static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted) { struct child_process sm_summary; - char summary_limit[64]; - char index[PATH_MAX]; - const char *env[] = { NULL, NULL }; + struct argv_array env = ARGV_ARRAY_INIT; struct argv_array argv = ARGV_ARRAY_INIT; struct strbuf cmd_stdout = STRBUF_INIT; struct strbuf summary = STRBUF_INIT; char *summary_content; size_t len; - sprintf(summary_limit, "%d", s->submodule_summary); - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file); + argv_array_pushf(&env, "GIT_INDEX_FILE=%s", s->index_file); - env[0] = index; argv_array_push(&argv, "submodule"); argv_array_push(&argv, "summary"); argv_array_push(&argv, uncommitted ? "--files" : "--cached"); argv_array_push(&argv, "--for-status"); argv_array_push(&argv, "--summary-limit"); - argv_array_push(&argv, summary_limit); + argv_array_pushf(&argv, "%d", s->submodule_summary); if (!uncommitted) argv_array_push(&argv, s->amend ? "HEAD^" : "HEAD"); memset(&sm_summary, 0, sizeof(sm_summary)); sm_summary.argv = argv.argv; - sm_summary.env = env; + sm_summary.env = env.argv; sm_summary.git_cmd = 1; sm_summary.no_stdin = 1; fflush(s->fp); sm_summary.out = -1; run_command(&sm_summary); + argv_array_clear(&env); argv_array_clear(&argv); len = strbuf_read(&cmd_stdout, sm_summary.out, 1024); @@ -836,7 +825,7 @@ static void wt_status_print_other(struct wt_status *s, string_list_clear(&output, 0); strbuf_release(&buf); conclude: - status_printf_ln(s, GIT_COLOR_NORMAL, ""); + status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); } void wt_status_truncate_message_at_cut_line(struct strbuf *buf) @@ -923,7 +912,7 @@ static void wt_status_print_tracking(struct wt_status *s) color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c", comment_line_char); else - fprintf_ln(s->fp, ""); + fputs("", s->fp); } static int has_unmerged(struct wt_status *s) @@ -1339,7 +1328,7 @@ void wt_status_print(struct wt_status *s) on_what = _("Not currently on any branch."); } } - status_printf(s, color(WT_STATUS_HEADER, s), ""); + status_printf(s, color(WT_STATUS_HEADER, s), "%s", ""); status_printf_more(s, branch_status_color, "%s", on_what); status_printf_more(s, branch_color, "%s\n", branch_name); if (!s->is_initial) @@ -1352,9 +1341,9 @@ void wt_status_print(struct wt_status *s) free(state.detached_from); if (s->is_initial) { - status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); + status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", ""); status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit")); - status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); + status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", ""); } wt_status_print_updated(s); @@ -1371,7 +1360,7 @@ void wt_status_print(struct wt_status *s) if (s->show_ignored_files) wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f"); if (advice_status_u_option && 2000 < s->untracked_in_ms) { - status_printf_ln(s, GIT_COLOR_NORMAL, ""); + status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); status_printf_ln(s, GIT_COLOR_NORMAL, _("It took %.2f seconds to enumerate untracked files. 'status -uno'\n" "may speed it up, but you have to be careful not to forget to add\n" diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 9e13b25a..625198e0 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -245,11 +245,11 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, dest ? dest + size : NULL); /* Postimage from side #1 */ if (m->mode & 1) - size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + size += xdl_recs_copy(xe1, m->i1, m->chg1, (m->mode & 2), dest ? dest + size : NULL); /* Postimage from side #2 */ if (m->mode & 2) - size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + size += xdl_recs_copy(xe2, m->i2, m->chg2, 0, dest ? dest + size : NULL); } else continue; |