diff options
350 files changed, 9199 insertions, 5589 deletions
diff --git a/.gitattributes b/.gitattributes index 320e33c327..8ce9c6b888 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,9 @@ * whitespace=!indent,trail,space *.[ch] whitespace=indent,trail,space diff=cpp -*.sh whitespace=indent,trail,space +*.sh whitespace=indent,trail,space eol=lf +*.perl eol=lf +*.pm eol=lf +/Documentation/git-*.txt eol=lf +/command-list.txt eol=lf +/GIT-VERSION-GEN eol=lf +/mergetools/* eol=lf diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000000..64e605a02b --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,19 @@ +## Contributing to Git + +Thanks for taking the time to contribute to Git! Please be advised that the +Git community does not use github.com for their contributions. Instead, we use +a mailing list (git@vger.kernel.org) for code submissions, code +reviews, and bug reports. + +Nevertheless, you can use [submitGit](http://submitgit.herokuapp.com/) to +conveniently send your Pull Requests commits to our mailing list. + +Please read ["A note from the maintainer"](https://git.kernel.org/pub/scm/git/git.git/plain/MaintNotes?h=todo) +to learn how the Git project is managed, and how you can work with it. +In addition, we highly recommend you to read [our submission guidelines](../Documentation/SubmittingPatches). + +If you prefer video, then [this talk](https://www.youtube.com/watch?v=Q7i_qQW__q4&feature=youtu.be&t=6m4s) +might be useful to you as the presenter walks you through the contribution +process by example. + +Your friendly Git community! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..adba13e5ba --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +Thanks for taking the time to contribute to Git! Please be advised that the +Git community does not use github.com for their contributions. Instead, we use +a mailing list (git@vger.kernel.org) for code submissions, code reviews, and +bug reports. Nevertheless, you can use submitGit to conveniently send your Pull +Requests commits to our mailing list. + +Please read the "guidelines for contributing" linked above! diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 2248cf7324..c4cb5ff0d4 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -256,12 +256,12 @@ For C programs: Note however that a comment that explains a translatable string to translators uses a convention of starting with a magic token - "TRANSLATORS: " immediately after the opening delimiter, even when - it spans multiple lines. We do not add an asterisk at the beginning - of each line, either. E.g. + "TRANSLATORS: ", e.g. - /* TRANSLATORS: here is a comment that explains the string - to be translated, that follows immediately after it */ + /* + * TRANSLATORS: here is a comment that explains the string to + * be translated, that follows immediately after it. + */ _("Here is a translatable string explained by the above."); - Double negation is often harder to understand than no negation diff --git a/Documentation/RelNotes/2.13.1.txt b/Documentation/RelNotes/2.13.1.txt new file mode 100644 index 0000000000..ed7cd976d9 --- /dev/null +++ b/Documentation/RelNotes/2.13.1.txt @@ -0,0 +1,114 @@ +Git v2.13.1 Release Notes +========================= + +Fixes since v2.13 +----------------- + + * The Web interface to gmane news archive is long gone, even though + the articles are still accessible via NTTP. Replace the links with + ones to public-inbox.org. Because their message identification is + based on the actual message-id, it is likely that it will be easier + to migrate away from it if/when necessary. + + * Update tests to pass under GETTEXT_POISON (a mechanism to ensure + that output strings that should not be translated are not + translated by mistake), and tell TravisCI to run them. + + * Setting "log.decorate=false" in the configuration file did not take + effect in v2.13, which has been corrected. + + * An earlier update to test 7400 needed to be skipped on CYGWIN. + + * Git sometimes gives an advice in a rhetorical question that does + not require an answer, which can confuse new users and non native + speakers. Attempt to rephrase them. + + * "git read-tree -m" (no tree-ish) gave a nonsense suggestion "use + --empty if you want to clear the index". With "-m", such a request + will still fail anyway, as you'd need to name at least one tree-ish + to be merged. + + * The codepath in "git am" that is used when running "git rebase" + leaked memory held for the log message of the commits being rebased. + + * "pack-objects" can stream a slice of an existing packfile out when + the pack bitmap can tell that the reachable objects are all needed + in the output, without inspecting individual objects. This + strategy however would not work well when "--local" and other + options are in use, and need to be disabled. + + * Clarify documentation for include.path and includeIf.<condition>.path + configuration variables. + + * Tag objects, which are not reachable from any ref, that point at + missing objects were mishandled by "git gc" and friends (they + should silently be ignored instead) + + * A few http:// links that are redirected to https:// in the + documentation have been updated to https:// links. + + * Make sure our tests would pass when the sources are checked out + with "platform native" line ending convention by default on + Windows. Some "text" files out tests use and the test scripts + themselves that are meant to be run with /bin/sh, ought to be + checked out with eol=LF even on Windows. + + * Fix memory leaks pointed out by Coverity (and people). + + * The receive-pack program now makes sure that the push certificate + records the same set of push options used for pushing. + + * "git cherry-pick" and other uses of the sequencer machinery + mishandled a trailer block whose last line is an incomplete line. + This has been fixed so that an additional sign-off etc. are added + after completing the existing incomplete line. + + * The shell completion script (in contrib/) learned "git stash" has + a new "push" subcommand. + + * Travis CI gained a task to format the documentation with both + AsciiDoc and AsciiDoctor. + + * Update the C style recommendation for notes for translators, as + recent versions of gettext tools can work with our style of + multi-line comments. + + * "git clone --config var=val" is a way to populate the + per-repository configuration file of the new repository, but it did + not work well when val is an empty string. This has been fixed. + + * A few codepaths in "checkout" and "am" working on an unborn branch + tried to access an uninitialized piece of memory. + + * "git for-each-ref --format=..." with %(HEAD) in the format used to + resolve the HEAD symref as many times as it had processed refs, + which was wasteful, and "git branch" shared the same problem. + + * "git interpret-trailers", when used as GIT_EDITOR for "git commit + -v", looked for and appended to a trailer block at the very end, + i.e. at the end of the "diff" output. The command has been + corrected to pay attention to the cut-mark line "commit -v" adds to + the buffer---the real trailer block should appear just before it. + + * A test allowed both "git push" and "git receive-pack" on the other + end write their traces into the same file. This is OK on platforms + that allows atomically appending to a file opened with O_APPEND, + but on other platforms led to a mangled output, causing + intermittent test failures. This has been fixed by disabling + traces from "receive-pack" in the test. + + * "foo\bar\baz" in "git fetch foo\bar\baz", even though there is no + slashes in it, cannot be a nickname for a remote on Windows, as + that is likely to be a pathname on a local filesystem. + + * The "collision detecting" SHA-1 implementation shipped with 2.13 + was quite broken on some big-endian platforms and/or platforms that + do not like unaligned fetches. Update to the upstream code which + has already fixed these issues. + + * "git am -h" triggered a BUG(). + + * The interaction of "url.*.insteadOf" and custom URL scheme's + whitelisting is now documented better. + +Also contains various documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.13.2.txt b/Documentation/RelNotes/2.13.2.txt new file mode 100644 index 0000000000..8c2b20071e --- /dev/null +++ b/Documentation/RelNotes/2.13.2.txt @@ -0,0 +1,54 @@ +Git v2.13.2 Release Notes +========================= + +Fixes since v2.13.1 +------------------- + + * The "collision detecting" SHA-1 implementation shipped with 2.13.1 + was still broken on some platforms. Update to the upstream code + again to take their fix. + + * "git checkout --recurse-submodules" did not quite work with a + submodule that itself has submodules. + + * Introduce the BUG() macro to improve die("BUG: ..."). + + * The "run-command" API implementation has been made more robust + against dead-locking in a threaded environment. + + * A recent update to t5545-push-options.sh started skipping all the + tests in the script when a web server testing is disabled or + unavailable, not just the ones that require a web server. Non HTTP + tests have been salvaged to always run in this script. + + * "git clean -d" used to clean directories that has ignored files, + even though the command should not lose ignored ones without "-x". + "git status --ignored" did not list ignored and untracked files + without "-uall". These have been corrected. + + * The timestamp of the index file is now taken after the file is + closed, to help Windows, on which a stale timestamp is reported by + fstat() on a file that is opened for writing and data was written + but not yet closed. + + * "git pull --rebase --autostash" didn't auto-stash when the local history + fast-forwards to the upstream. + + * "git describe --contains" penalized light-weight tags so much that + they were almost never considered. Instead, give them about the + same chance to be considered as an annotated tag that is the same + age as the underlying commit would. + + * The result from "git diff" that compares two blobs, e.g. "git diff + $commit1:$path $commit2:$path", used to be shown with the full + object name as given on the command line, but it is more natural to + use the $path in the output and use it to look up .gitattributes. + + * A flaky test has been corrected. + + * Help contributors that visit us at GitHub. + + * "git stash push <pathspec>" did not work from a subdirectory at all. + Bugfix for a topic in v2.13 + +Also contains various documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.14.0.txt b/Documentation/RelNotes/2.14.0.txt index 4fc8f0db81..f57a7ecbf6 100644 --- a/Documentation/RelNotes/2.14.0.txt +++ b/Documentation/RelNotes/2.14.0.txt @@ -18,6 +18,10 @@ Backward compatibility notes. might still be cases that need to be addressed--bug reports are greatly appreciated. + * The experiment to improve the hunk-boundary selection of textual + diff output has finished, and the "indent heuristics" has now + become the default. + Updates since v2.13 ------------------- @@ -34,7 +38,58 @@ UI, Workflows & Features * "git archive --format=zip" learned to use zip64 extension when necessary to go beyond the 4GB limit. - (merge 867e40ff3a rs/large-zip later to maint). + + * "git reset" learned "--recurse-submodules" option. + + * "git diff --submodule=diff" now recurses into nested submodules. + + * "git repack" learned to accept the --threads=<n> option and pass it + to pack-objects. + + * "git send-email" learned to run sendemail-validate hook to inspect + and reject a message before sending it out. + + * There is no good reason why "git fetch $there $sha1" should fail + when the $sha1 names an object at the tip of an advertised ref, + even when the other side hasn't enabled allowTipSHA1InWant. + + * The recently introduced "[includeIf "gitdir:$dir"] path=..." + mechansim has further been taught to take symlinks into account. + The directory "$dir" specified in "gitdir:$dir" may be a symlink to + a real location, not something that $(getcwd) may return. In such + a case, a realpath of "$dir" is compared with the real path of the + current repository to determine if the contents from the named path + should be included. + + * Make the "indent" heuristics the default in "diff" and diff.indentHeuristics + configuration variable an escape hatch for those who do no want it. + + * Many commands learned to pay attention to submodule.recurse + configuration. + + * The convention for a command line is to follow "git cmdname + --options" with revisions followed by an optional "--" + disambiguator and then finally pathspecs. When "--" is not there, + we make sure early ones are all interpretable as revs (and do not + look like paths) and later ones are the other way around. A + pathspec with "magic" (e.g. ":/p/a/t/h" that matches p/a/t/h from + the top-level of the working tree, no matter what subdirectory you + are working from) are conservatively judged as "not a path", which + required disambiguation more often. The command line parser + learned to say "it's a pathspec" a bit more often when the syntax + looks like so. + + * Update "perl-compatible regular expression" support to enable JIT + and also allow linking with the newer PCRE v2 library. + + * "filter-branch" learned a pseudo filter "--setup" that can be used + to define a common function/variable that can be used by other + filters. + + * Using "git add d/i/r" when d/i/r is the top of the working tree of + a separate repository would create a gitlink in the index, which + would appear as a not-quite-initialized submodule to others. We + learned to give warnings when this happens. Performance, Internal Implementation, Development Support etc. @@ -45,14 +100,12 @@ Performance, Internal Implementation, Development Support etc. * Code to update the cache-tree has been tightened so that we won't accidentally write out any 0{40} entry in the tree object. - (merge a96d3cc3f6 jk/no-null-sha1-in-cache-tree later to maint). * Attempt to allow us notice "fishy" situation where we fail to remove the temporary directory used during the test. * Travis CI gained a task to format the documentation with both AsciiDoc and AsciiDoctor. - (merge 505ad91304 ls/travis-doc-asciidoctor later to maint). * Some platforms have ulong that is smaller than time_t, and our historical use of ulong for timestamp would mean they cannot @@ -65,6 +118,75 @@ Performance, Internal Implementation, Development Support etc. * We can trigger Windows auto-build tester (credits: Dscho & Microsoft) from our existing Travis CI tester now. + * Conversion from uchar[20] to struct object_id continues. + + * Simplify parse_pathspec() codepath and stop it from looking at the + default in-core index. + + * Add perf-test for wildmatch. + + * Code from "conversion using external process" codepath has been + extracted to a separate sub-process.[ch] module. + + * When "git checkout", "git merge", etc. manipulates the in-core + index, various pieces of information in the index extensions are + discarded from the original state, as it is usually not the case + that they are kept up-to-date and in-sync with the operation on the + main index. The untracked cache extension is copied across these + operations now, which would speed up "git status" (as long as the + cache is properly invalidated). + + * The internal implementation of "git grep" has seen some clean-up. + + * Update the C style recommendation for notes for translators, as + recent versions of gettext tools can work with our style of + multi-line comments. + + * The implementation of "ref" API around the "packed refs" have been + cleaned up, in preparation for further changes. + + * The internal logic used in "git blame" has been libified to make it + easier to use by cgit. + + * Our code often opens a path to an optional file, to work on its + contents when we can successfully open it. We can ignore a failure + to open if such an optional file does not exist, but we do want to + report a failure in opening for other reasons (e.g. we got an I/O + error, or the file is there, but we lack the permission to open). + + The exact errors we need to ignore are ENOENT (obviously) and + ENOTDIR (less obvious). Instead of repeating comparison of errno + with these two constants, introduce a helper function to do so. + + * We often try to open a file for reading whose existence is + optional, and silently ignore errors from open/fopen; report such + errors if they are not due to missing files. + + * When an existing repository is used for t/perf testing, we first + create bit-for-bit copy of it, which may grab a transient state of + the repository and freeze it into the repository used for testing, + which then may cause Git operations to fail. Single out "the index + being locked" case and forcibly drop the lock from the copy. + + * Three instances of the same helper function have been consolidated + to one. + + * "fast-import" uses a default pack chain depth that is consistent + with other parts of the system. + + * A new test to show the interaction between the pattern [^a-z] + (which matches '/') and a slash in a path has been added. The + pattern should not match the slash with "pathmatch", but should + with "wildmatch". + + * The 'diff-highlight' program (in contrib/) has been restructured + for easier reuse by an external project 'diff-so-fancy'. + (merge 0c977dbc81 jk/diff-highlight-module later to maint). + + * A common pattern to free a piece of memory and assign NULL to the + pointer that used to point at it has been replaced with a new + FREE_AND_NULL() macro. + Also contains various documentation updates and code clean-ups. @@ -83,44 +205,185 @@ notes for details). mishandled a trailer block whose last line is an incomplete line. This has been fixed so that an additional sign-off etc. are added after completing the existing incomplete line. - (merge 44dc738a39 jt/use-trailer-api-in-commands later to maint). * The codepath in "git am" that is used when running "git rebase" leaked memory held for the log message of the commits being rebased. - (merge 721f5f1e35 jk/am-leakfix later to maint). * "git clone --config var=val" is a way to populate the per-repository configuration file of the new repository, but it did not work well when val is an empty string. This has been fixed. - (merge db4eca1fea jn/clone-add-empty-config-from-command-line later to maint). * Setting "log.decorate=false" in the configuration file did not take effect in v2.13, which has been corrected. - (merge c74271aae7 ah/log-decorate-default-to-auto later to maint). * A few codepaths in "checkout" and "am" working on an unborn branch tried to access an uninitialized piece of memory. - (merge 57e0ef0e0e rs/checkout-am-fix-unborn later to maint). * The Web interface to gmane news archive is long gone, even though the articles are still accessible via NTTP. Replace the links with ones to public-inbox.org. Because their message identification is based on the actual message-id, it is likely that it will be easier to migrate away from it if/when necessary. - (merge 5840eb9d14 ab/doc-replace-gmane-links later to maint). * The receive-pack program now makes sure that the push certificate records the same set of push options used for pushing. - (merge cbaf82cc6b jt/push-options-doc later to maint). * Tests have been updated to pass under GETTEXT_POISON (a mechanism to ensure that output strings that should not be translated are not translated by mistake), and TravisCI is told to run them. - (merge b8e188f6f5 ab/fix-poison-tests later to maint). + + * "git checkout --recurse-submodules" did not quite work with a + submodule that itself has submodules. + + * "pack-objects" can stream a slice of an existing packfile out when + the pack bitmap can tell that the reachable objects are all needed + in the output, without inspecting individual objects. This + strategy however would not work well when "--local" and other + options are in use, and need to be disabled. + + * Fix memory leaks pointed out by Coverity (and people). + + * "git read-tree -m" (no tree-ish) gave a nonsense suggestion "use + --empty if you want to clear the index". With "-m", such a request + will still fail anyway, as you'd need to name at least one tree-ish + to be merged. + + * Make sure our tests would pass when the sources are checked out + with "platform native" line ending convention by default on + Windows. Some "text" files out tests use and the test scripts + themselves that are meant to be run with /bin/sh, ought to be + checked out with eol=LF even on Windows. + + * Introduce the BUG() macro to improve die("BUG: ..."). + + * Clarify documentation for include.path and includeIf.<condition>.path + configuration variables. + + * Git sometimes gives an advice in a rhetorical question that does + not require an answer, which can confuse new users and non native + speakers. Attempt to rephrase them. + + * A few http:// links that are redirected to https:// in the + documentation have been updated to https:// links. + + * "git for-each-ref --format=..." with %(HEAD) in the format used to + resolve the HEAD symref as many times as it had processed refs, + which was wasteful, and "git branch" shared the same problem. + + * Regression fix to topic recently merged to 'master'. + + * The shell completion script (in contrib/) learned "git stash" has + a new "push" subcommand. + + * "git interpret-trailers", when used as GIT_EDITOR for "git commit + -v", looked for and appended to a trailer block at the very end, + i.e. at the end of the "diff" output. The command has been + corrected to pay attention to the cut-mark line "commit -v" adds to + the buffer---the real trailer block should appear just before it. + + * A test allowed both "git push" and "git receive-pack" on the other + end write their traces into the same file. This is OK on platforms + that allows atomically appending to a file opened with O_APPEND, + but on other platforms led to a mangled output, causing + intermittent test failures. This has been fixed by disabling + traces from "receive-pack" in the test. + + * Tag objects, which are not reachable from any ref, that point at + missing objects were mishandled by "git gc" and friends (they + should silently be ignored instead) + + * "git describe --contains" penalized light-weight tags so much that + they were almost never considered. Instead, give them about the + same chance to be considered as an annotated tag that is the same + age as the underlying commit would. + + * The "run-command" API implementation has been made more robust + against dead-locking in a threaded environment. + + * A recent update to t5545-push-options.sh started skipping all the + tests in the script when a web server testing is disabled or + unavailable, not just the ones that require a web server. Non HTTP + tests have been salvaged to always run in this script. + + * "git send-email" now uses Net::SMTP::SSL, which is obsolete, only + when needed. Recent versions of Net::SMTP can do TLS natively. + + * "foo\bar\baz" in "git fetch foo\bar\baz", even though there is no + slashes in it, cannot be a nickname for a remote on Windows, as + that is likely to be a pathname on a local filesystem. + + * "git clean -d" used to clean directories that has ignored files, + even though the command should not lose ignored ones without "-x". + "git status --ignored" did not list ignored and untracked files + without "-uall". These have been corrected. + + * The result from "git diff" that compares two blobs, e.g. "git diff + $commit1:$path $commit2:$path", used to be shown with the full + object name as given on the command line, but it is more natural to + use the $path in the output and use it to look up .gitattributes. + + * The "collision detecting" SHA-1 implementation shipped with 2.13 + was quite broken on some big-endian platforms and/or platforms that + do not like unaligned fetches. Update to the upstream code which + has already fixed these issues. + + * "git am -h" triggered a BUG(). + + * The interaction of "url.*.insteadOf" and custom URL scheme's + whitelisting is now documented better. + + * The timestamp of the index file is now taken after the file is + closed, to help Windows, on which a stale timestamp is reported by + fstat() on a file that is opened for writing and data was written + but not yet closed. + + * "git pull --rebase --autostash" didn't auto-stash when the local history + fast-forwards to the upstream. + + * A flaky test has been corrected. + + * "git $cmd -h" for builtin commands calls the implementation of the + command (i.e. cmd_$cmd() function) without doing any repository + set-up, and the commands that expect RUN_SETUP is done by the Git + potty needs to be prepared to show the help text without barfing. + (merge d691551192 jk/consistent-h later to maint). + + * Help contributors that visit us at GitHub. + + * "git stash push <pathspec>" did not work from a subdirectory at all. + Bugfix for a topic in v2.13 + + * As there is no portable way to pass timezone information to + strftime, some output format from "git log" and friends are + impossible to produce. Teach our own strbuf_addftime to replace %z + and %Z with caller-supplied values to help working around this. + (merge 6eced3ec5e rs/strbuf-addftime-zZ later to maint). + + * "git mergetool" learned to work around a wrapper MacOS X adds + around underlying meld. + (merge 0af85f84bd da/mergetools-meld-output-opt-on-macos later to maint). + + * An example in documentation that does not work in multi worktree + configuration has been corrected. + (merge 773a88914f ah/doc-gitattributes-empty-index later to maint). + + * The pretty-format specifiers like '%h', '%t', etc. had an + optimization that no longer works correctly. In preparation/hope + of getting it correctly implemented, first discard the optimization + that is broken. + (merge fe9e2aefd4 rs/pretty-add-again later to maint). + + * The code to pick up and execute command alias definition from the + configuration used to switch to the top of the working tree and + then come back when the expanded alias was executed, which was + unnecessarilyl complex. Attempt to simplify the logic by using the + early-config mechanism that does not chdir around. + (merge a9bcf6586d js/alias-early-config later to maint). + + * Fix configuration codepath to pay proper attention to commondir + that is used in multi-worktree situation, and isolate config API + into its own header file. + (merge dc8441fdb4 bw/config-h later to maint). * Other minor doc, test and build updates and code cleanups. - (merge 515360f9e9 jn/credential-doc-on-clear later to maint). - (merge 0e6d899fee ab/aix-needs-compat-regex later to maint). - (merge e294e8959f jc/apply-fix-mismerge later to maint). - (merge 7f1b225153 bw/submodule-with-bs-path later to maint). - (merge c8f7c8b704 tb/dedup-crlf-tests later to maint). + (merge 68241cb9dd sb/t4005-modernize later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index e0b9fd0bc3..2845e8d5c4 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -79,14 +79,20 @@ escape sequences) are invalid. Includes ~~~~~~~~ +The `include` and `includeIf` sections allow you to include config +directives from another source. These sections behave identically to +each other with the exception that `includeIf` sections may be ignored +if their condition does not evaluate to true; see "Conditional includes" +below. + You can include a config file from another by setting the special -`include.path` variable to the name of the file to be included. The -variable takes a pathname as its value, and is subject to tilde -expansion. `include.path` can be given multiple times. +`include.path` (or `includeIf.*.path`) variable to the name of the file +to be included. The variable takes a pathname as its value, and is +subject to tilde expansion. These variables can be given multiple times. -The included file is expanded immediately, as if its contents had been -found at the location of the include directive. If the value of the -`include.path` variable is a relative path, the path is considered to +The contents of the included file are inserted immediately, as if they +had been found at the location of the include directive. If the value of the +variable is a relative path, the path is considered to be relative to the configuration file in which the include directive was found. See below for examples. @@ -95,8 +101,7 @@ Conditional includes You can include a config file from another conditionally by setting a `includeIf.<condition>.path` variable to the name of the file to be -included. The variable's value is treated the same way as -`include.path`. `includeIf.<condition>.path` can be given multiple times. +included. The condition starts with a keyword followed by a colon and some data whose format and meaning depends on the keyword. Supported keywords @@ -140,6 +145,16 @@ A few more notes on matching via `gitdir` and `gitdir/i`: * Symlinks in `$GIT_DIR` are not resolved before matching. + * Both the symlink & realpath versions of paths will be matched + outside of `$GIT_DIR`. E.g. if ~/git is a symlink to + /mnt/storage/git, both `gitdir:~/git` and `gitdir:/mnt/storage/git` + will match. ++ +This was not the case in the initial release of this feature in +v2.13.0, which only matched the realpath version. Configuration that +wants to be compatible with the initial release of this feature needs +to either specify only the realpath version, or both versions. + * Note that "../" is not special and will match literally, which is unlikely what you want. @@ -167,8 +182,8 @@ Example [include] path = /path/to/foo.inc ; include by absolute path - path = foo ; expand "foo" relative to the current file - path = ~/foo ; expand "foo" in your `$HOME` directory + path = foo.inc ; find "foo.inc" relative to the current file + path = ~/foo.inc ; find "foo.inc" in your `$HOME` directory ; include if $GIT_DIR is /path/to/foo/.git [includeIf "gitdir:/path/to/foo/.git"] @@ -182,6 +197,12 @@ Example [includeIf "gitdir:~/to/group/"] path = /path/to/foo.inc + ; relative paths are always relative to the including + ; file (if the condition is true); their location is not + ; affected by the condition + [includeIf "gitdir:/path/to/group/"] + path = foo.inc + Values ~~~~~~ @@ -327,6 +348,9 @@ advice.*:: rmHints:: In case of failure in the output of linkgit:git-rm[1], show directions on how to proceed from the current state. + addEmbeddedRepo:: + Advice on what to do when you've accidentally added one + git repo inside of another. -- core.fileMode:: @@ -334,7 +358,7 @@ core.fileMode:: is to be honored. + Some filesystems lose the executable bit when a file that is -marked as executable is checked out, or checks out an +marked as executable is checked out, or checks out a non-executable file with executable bit on. linkgit:git-clone[1] or linkgit:git-init[1] probe the filesystem to see if it handles the executable bit correctly @@ -862,6 +886,7 @@ core.abbrev:: computed based on the approximate number of packed objects in your repository, which hopefully is enough for abbreviated object names to stay unique for some time. + The minimum length is 4. add.ignoreErrors:: add.ignore-errors (deprecated):: @@ -2143,6 +2168,10 @@ log.showRoot:: Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which normally hide the root commit will now show it. True by default. +log.showSignature:: + If true, makes linkgit:git-log[1], linkgit:git-show[1], and + linkgit:git-whatchanged[1] assume `--show-signature`. + log.mailmap:: If true, makes linkgit:git-log[1], linkgit:git-show[1], and linkgit:git-whatchanged[1] assume `--use-mailmap`. @@ -2594,7 +2623,7 @@ rebase.autoSquash:: If set to true enable `--autosquash` option by default. rebase.autoStash:: - When set to true, automatically create a temporary stash + When set to true, automatically create a temporary stash entry before the operation begins, and apply it after the operation ends. This means that you can run rebase on a dirty worktree. However, use with care: the final stash application after a @@ -2966,6 +2995,11 @@ status.displayCommentPrefix:: behavior of linkgit:git-status[1] in Git 1.8.4 and previous. Defaults to false. +status.showStash:: + If set to true, linkgit:git-status[1] will display the number of + entries currently stashed away. + Defaults to false. + status.showUntrackedFiles:: By default, linkgit:git-status[1] and linkgit:git-commit[1] show files which are not currently tracked by Git. Directories which @@ -3003,12 +3037,12 @@ status.submoduleSummary:: stash.showPatch:: If this is set to true, the `git stash show` command without an - option will show the stash in patch form. Defaults to false. + option will show the stash entry in patch form. Defaults to false. See description of 'show' command in linkgit:git-stash[1]. stash.showStat:: If this is set to true, the `git stash show` command without an - option will show diffstat of the stash. Defaults to true. + option will show diffstat of the stash entry. Defaults to true. See description of 'show' command in linkgit:git-stash[1]. submodule.<name>.url:: @@ -3065,6 +3099,11 @@ submodule.active:: submodule's path to determine if the submodule is of interest to git commands. +submodule.recurse:: + Specifies if commands recurse into submodules by default. This + applies to all commands that have a `--recurse-submodules` option. + Defaults to false. + submodule.fetchJobs:: Specifies how many submodules are fetched/cloned at the same time. A positive integer allows up to that number of submodules fetched @@ -3210,6 +3249,13 @@ url.<base>.insteadOf:: the best alternative for the particular user, even for a never-before-seen repository on the site. When more than one insteadOf strings match a given URL, the longest match is used. ++ +Note that any protocol restrictions will be applied to the rewritten +URL. If the rewrite changes the URL to use a custom protocol or remote +helper, you may need to adjust the `protocol.*.allow` config to permit +the request. In particular, protocols you expect to use for submodules +must be set to `always` rather than the default of `user`. See the +description of `protocol.allow` above. url.<base>.pushInsteadOf:: Any URL that starts with this value will not be pushed to; diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 7ed63dce0b..f4169fb1ec 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -165,6 +165,13 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files. be ignored, no matter if they are already present in the work tree or not. +--no-warn-embedded-repo:: + By default, `git add` will warn when adding an embedded + repository to the index without using `git submodule add` to + create an entry in `.gitmodules`. This option will suppress the + warning (e.g., if you are manually performing operations on + submodules). + --chmod=(+|-)x:: Override the executable bit of the added files. The executable bit is only changed in the index, the files on disk are left diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 2b762654bf..3d3d219e58 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -121,7 +121,7 @@ Performance and Compression Tuning --depth=<n>:: Maximum delta depth, for blob and tree deltification. - Default is 10. + Default is 50. --export-pack-edges=<file>:: After creating a packfile, print a line of data to diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 6e4bb02204..9e5169aa64 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -8,11 +8,11 @@ git-filter-branch - Rewrite branches SYNOPSIS -------- [verse] -'git filter-branch' [--env-filter <command>] [--tree-filter <command>] - [--index-filter <command>] [--parent-filter <command>] - [--msg-filter <command>] [--commit-filter <command>] - [--tag-name-filter <command>] [--subdirectory-filter <directory>] - [--prune-empty] +'git filter-branch' [--setup <command>] [--env-filter <command>] + [--tree-filter <command>] [--index-filter <command>] + [--parent-filter <command>] [--msg-filter <command>] + [--commit-filter <command>] [--tag-name-filter <command>] + [--subdirectory-filter <directory>] [--prune-empty] [--original <namespace>] [-d <directory>] [-f | --force] [--] [<rev-list options>...] @@ -82,12 +82,18 @@ multiple commits. OPTIONS ------- +--setup <command>:: + This is not a real filter executed for each commit but a one + time setup just before the loop. Therefore no commit-specific + variables are defined yet. Functions or variables defined here + can be used or modified in the following filter steps except + the commit filter, for technical reasons. + --env-filter <command>:: This filter may be used if you only need to modify the environment in which the commit will be performed. Specifically, you might want to rewrite the author/committer name/email/time environment - variables (see linkgit:git-commit-tree[1] for details). Do not forget - to re-export the variables. + variables (see linkgit:git-commit-tree[1] for details). --tree-filter <command>:: This is the filter for rewriting the tree and its contents. @@ -340,12 +346,10 @@ git filter-branch --env-filter ' if test "$GIT_AUTHOR_EMAIL" = "root@localhost" then GIT_AUTHOR_EMAIL=john@example.com - export GIT_AUTHOR_EMAIL fi if test "$GIT_COMMITTER_EMAIL" = "root@localhost" then GIT_COMMITTER_EMAIL=john@example.com - export GIT_COMMITTER_EMAIL fi ' -- --all -------------------------------------------------------- diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 71f32f3508..5033483db4 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -161,8 +161,11 @@ OPTIONS -P:: --perl-regexp:: - Use Perl-compatible regexp for patterns. Requires libpcre to be - compiled in. + Use Perl-compatible regular expressions for patterns. ++ +Support for these types of regular expressions is an optional +compile-time dependency. If Git wasn't compiled with support for them +providing this option will cause it to die. -F:: --fixed-strings:: diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 09074c75a4..31cdeaecdf 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -123,7 +123,7 @@ trailer.ifexists:: same <token> in the message. + The valid values for this option are: `addIfDifferentNeighbor` (this -is the default), `addIfDifferent`, `add`, `overwrite` or `doNothing`. +is the default), `addIfDifferent`, `add`, `replace` or `doNothing`. + With `addIfDifferentNeighbor`, a new trailer will be added only if no trailer with the same (<token>, <value>) pair is above or below the line diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 942af8e0f7..9db5e08f4a 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -131,7 +131,7 @@ unless you have read linkgit:git-rebase[1] carefully. --autostash:: --no-autostash:: Before starting rebase, stash local modifications away (see - linkgit:git-stash[1]) if needed, and apply the stash when + linkgit:git-stash[1]) if needed, and apply the stash entry when done. `--no-autostash` is useful to override the `rebase.autoStash` configuration variable (see linkgit:git-config[1]). + @@ -159,15 +159,15 @@ present while on branch `<name>`, that value is used instead of In order to determine what URL to use to fetch from, the value of the configuration `remote.<origin>.url` is consulted -and if there is not any such variable, the value on `URL: ` line -in `$GIT_DIR/remotes/<origin>` file is used. +and if there is not any such variable, the value on the `URL:` line +in `$GIT_DIR/remotes/<origin>` is used. In order to determine what remote branches to fetch (and optionally store in the remote-tracking branches) when the command is run without any refspec parameters on the command line, values of the configuration variable `remote.<origin>.fetch` are consulted, and if there aren't any, `$GIT_DIR/remotes/<origin>` -file is consulted and its `Pull: ` lines are used. +is consulted and its `Pull:` lines are used. In addition to the refspec formats described in the OPTIONS section, you can have a globbing refspec that looks like this: diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index ed9d63ef4a..02576d8c0a 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -137,7 +137,7 @@ Merging ------- If `-m` is specified, 'git read-tree' can perform 3 kinds of merge, a single tree merge if only 1 tree is given, a -fast-forward merge with 2 trees, or a 3-way merge if 3 trees are +fast-forward merge with 2 trees, or a 3-way merge if 3 or more trees are provided. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 53f4e14444..a5afd602d8 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -446,7 +446,7 @@ used to override and disable this setting. --autostash:: --no-autostash:: - Automatically create a temporary stash before the operation + Automatically create a temporary stash entry before the operation begins, and apply it after the operation ends. This means that you can run rebase on a dirty worktree. However, use with care: the final stash application after a successful diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 26afe6ed54..ae750e9e11 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -9,7 +9,7 @@ git-repack - Pack unpacked objects in a repository SYNOPSIS -------- [verse] -'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [--window=<n>] [--depth=<n>] +'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [--window=<n>] [--depth=<n>] [--threads=<n>] DESCRIPTION ----------- @@ -92,6 +92,9 @@ other objects in that pack they already have locally. to be applied that many times to get to the necessary object. The default value for --window is 10 and --depth is 50. +--threads=<n>:: + This option is passed through to `git pack-objects`. + --window-memory=<n>:: This option provides an additional limit on top of `--window`; the window size will dynamically scale down so as to not take diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 8a21198d65..70f3753e1e 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -115,7 +115,7 @@ $ git pull git://info.example.com/ nitfol <4> in these files are in good order. You do not want to see them when you run "git diff", because you plan to work on other files and changes with these files are distracting. -<2> Somebody asks you to pull, and the changes sounds worthy of merging. +<2> Somebody asks you to pull, and the changes sound worthy of merging. <3> However, you already dirtied the index (i.e. your index does not match the HEAD commit). But you know the pull you are going to make does not affect frotz.c or filfre.c, so you revert the diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index c40c470448..b1293f24bb 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -126,6 +126,12 @@ can be used. 'git diff-{asterisk}'). In contrast to the `--sq-quote` option, the command input is still interpreted as usual. +--short[=length]:: + Same as `--verify` but shortens the object name to a unique + prefix with at least `length` characters. The minimum length + is 4, the default is the effective value of the `core.abbrev` + configuration variable (see linkgit:git-config[1]). + --not:: When showing object names, prefix them with '{caret}' and strip '{caret}' prefix from the object names that already have @@ -136,12 +142,6 @@ can be used. The option core.warnAmbiguousRefs is used to select the strict abbreviation mode. ---short:: ---short=number:: - Instead of outputting the full SHA-1 values of object names try to - abbreviate them to a shorter unique name. When no length is specified - 7 is used. The minimum length is 4. - --symbolic:: Usually the object names are output in SHA-1 form (with possible '{caret}' prefix); this option makes them output in a diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index f1efc116eb..8c87e8cdd7 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -140,10 +140,11 @@ Only submodules using a gitfile (which means they were cloned with a Git version 1.7.8 or newer) will be removed from the work tree, as their repository lives inside the .git directory of the superproject. If a submodule (or one of those nested inside it) -still uses a .git directory, `git rm` will fail - no matter if forced -or not - to protect the submodule's history. If it exists the -submodule.<name> section in the linkgit:gitmodules[5] file will also -be removed and that file will be staged (unless --cached or -n are used). +still uses a .git directory, `git rm` will move the submodules +git directory into the superprojects git directory to protect +the submodule's history. If it exists the submodule.<name> section +in the linkgit:gitmodules[5] file will also be removed and that file +will be staged (unless --cached or -n are used). A submodule is considered up-to-date when the HEAD is the same as recorded in the index, no tracked files are modified and no untracked diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 9d66166f69..bb23b02caf 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -377,6 +377,7 @@ have been specified, in which case default to 'compose'. Currently, validation means the following: + -- + * Invoke the sendemail-validate hook if present (see linkgit:githooks[5]). * Warn of patches that contain lines longer than 998 characters; this is due to SMTP limits as described by http://www.ietf.org/rfc/rfc2821.txt. -- diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 70191d06b6..00f95fee1f 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -51,18 +51,18 @@ OPTIONS save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]:: push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--] [<pathspec>...]:: - Save your local modifications to a new 'stash' and roll them + Save your local modifications to a new 'stash entry' and roll them back to HEAD (in the working tree and in the index). The <message> part is optional and gives the description along with the stashed state. + For quickly making a snapshot, you can omit "push". In this mode, non-option arguments are not allowed to prevent a misspelled -subcommand from making an unwanted stash. The two exceptions to this +subcommand from making an unwanted stash entry. The two exceptions to this are `stash -p` which acts as alias for `stash push -p` and pathspecs, which are allowed after a double hyphen `--` for disambiguation. + -When pathspec is given to 'git stash push', the new stash records the +When pathspec is given to 'git stash push', the new stash entry records the modified states only for the files that match the pathspec. The index entries and working tree files are then rolled back to the state in HEAD only for these files, too, leaving files that do not match the @@ -89,10 +89,10 @@ The `--patch` option implies `--keep-index`. You can use list [<options>]:: - List the stashes that you currently have. Each 'stash' is listed - with its name (e.g. `stash@{0}` is the latest stash, `stash@{1}` is + List the stash entries that you currently have. Each 'stash entry' is + listed with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is the one before, etc.), the name of the branch that was current when the - stash was made, and a short description of the commit the stash was + entry was made, and a short description of the commit the entry was based on. + ---------------------------------------------------------------- @@ -105,11 +105,12 @@ command to control what is shown and how. See linkgit:git-log[1]. show [<stash>]:: - Show the changes recorded in the stash as a diff between the - stashed state and its original parent. When no `<stash>` is given, - shows the latest one. By default, the command shows the diffstat, but - it will accept any format known to 'git diff' (e.g., `git stash show - -p stash@{1}` to view the second most recent stash in patch form). + Show the changes recorded in the stash entry as a diff between the + stashed contents and the commit back when the stash entry was first + created. When no `<stash>` is given, it shows the latest one. + By default, the command shows the diffstat, but it will accept any + format known to 'git diff' (e.g., `git stash show -p stash@{1}` + to view the second most recent entry in patch form). You can use stash.showStat and/or stash.showPatch config variables to change the default behavior. @@ -149,26 +150,27 @@ branch <branchname> [<stash>]:: + This is useful if the branch on which you ran `git stash save` has changed enough that `git stash apply` fails due to conflicts. Since -the stash is applied on top of the commit that was HEAD at the time -`git stash` was run, it restores the originally stashed state with -no conflicts. +the stash entry is applied on top of the commit that was HEAD at the +time `git stash` was run, it restores the originally stashed state +with no conflicts. clear:: - Remove all the stashed states. Note that those states will then + Remove all the stash entries. Note that those entries will then be subject to pruning, and may be impossible to recover (see 'Examples' below for a possible strategy). drop [-q|--quiet] [<stash>]:: - Remove a single stashed state from the stash list. When no `<stash>` - is given, it removes the latest one. i.e. `stash@{0}`, otherwise - `<stash>` must be a valid stash log reference of the form - `stash@{<revision>}`. + Remove a single stash entry from the list of stash entries. + When no `<stash>` is given, it removes the latest one. + i.e. `stash@{0}`, otherwise `<stash>` must be a valid stash + log reference of the form `stash@{<revision>}`. create:: - Create a stash (which is a regular commit object) and return its - object name, without storing it anywhere in the ref namespace. + Create a stash entry (which is a regular commit object) and + return its object name, without storing it anywhere in the ref + namespace. This is intended to be useful for scripts. It is probably not the command you want to use; see "save" above. @@ -182,10 +184,10 @@ store:: DISCUSSION ---------- -A stash is represented as a commit whose tree records the state of the -working directory, and its first parent is the commit at `HEAD` when -the stash was created. The tree of the second parent records the -state of the index when the stash is made, and it is made a child of +A stash entry is represented as a commit whose tree records the state +of the working directory, and its first parent is the commit at `HEAD` +when the entry was created. The tree of the second parent records the +state of the index when the entry is made, and it is made a child of the `HEAD` commit. The ancestry graph looks like this: .----W @@ -269,12 +271,12 @@ $ edit/build/test remaining parts $ git commit foo -m 'Remaining parts' ---------------------------------------------------------------- -Recovering stashes that were cleared/dropped erroneously:: +Recovering stash entries that were cleared/dropped erroneously:: -If you mistakenly drop or clear stashes, they cannot be recovered +If you mistakenly drop or clear stash entries, they cannot be recovered through the normal safety mechanisms. However, you can try the -following incantation to get a list of stashes that are still in your -repository, but not reachable any more: +following incantation to get a list of stash entries that are still in +your repository, but not reachable any more: + ---------------------------------------------------------------- git fsck --unreachable | diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index d70abc6afe..d47f198f15 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -32,6 +32,9 @@ OPTIONS --branch:: Show the branch and tracking info even in short-format. +--show-stash:: + Show the number of entries currently stashed away. + --porcelain[=<version>]:: Give the output in an easy-to-parse format for scripts. This is similar to the short output, but will remain stable diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 9bee9b0c4c..aa2aeabb60 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -95,6 +95,10 @@ If you still want the old default, you can get it by passing `--prefix ""` on the command line (`--prefix=""` may not work if your Perl's Getopt::Long is < v2.37). +--ignore-refs=<regex>;; + When passed to 'init' or 'clone' this regular expression will + be preserved as a config key. See 'fetch' for a description + of `--ignore-refs`. --ignore-paths=<regex>;; When passed to 'init' or 'clone' this regular expression will be preserved as a config key. See 'fetch' for a description @@ -138,6 +142,18 @@ the same local time zone. --parent;; Fetch only from the SVN parent of the current HEAD. +--ignore-refs=<regex>;; + Ignore refs for branches or tags matching the Perl regular + expression. A "negative look-ahead assertion" like + `^refs/remotes/origin/(?!tags/wanted-tag|wanted-branch).*$` + can be used to allow only certain refs. ++ +[verse] +config key: svn-remote.<name>.ignore-refs ++ +If the ignore-refs configuration key is set, and the command-line +option is also given, both regular expressions will be used. + --ignore-paths=<regex>;; This allows one to specify a Perl regular expression that will cause skipping of all matching paths from checkout from SVN. @@ -443,6 +459,21 @@ Any other arguments are passed directly to 'git log' (URL) may be omitted if you are working from a 'git svn'-aware repository (that has been `init`-ed with 'git svn'). The -r<revision> option is required for this. ++ +The commit message is supplied either directly with the `-m` or `-F` +option, or indirectly from the tag or commit when the second tree-ish +denotes such an object, or it is requested by invoking an editor (see +`--edit` option below). + +-m <msg>;; +--message=<msg>;; + Use the given `msg` as the commit message. This option + disables the `--edit` option. + +-F <filename>;; +--file=<filename>;; + Take the commit message from the given file. This option + disables the `--edit` option. 'info':: Shows information about a file or directory similar to what diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index f8a0b787f4..1eb15afa1c 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git tag' [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>] <tagname> [<commit> | <object>] 'git tag' -d <tagname>... -'git tag' [-n[<num>]] -l [--contains <commit>] [--contains <commit>] +'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>] [--points-at <object>] [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...] diff --git a/Documentation/git.txt b/Documentation/git.txt index fb10314c1d..7dd5e03280 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -35,516 +35,6 @@ manual page gives you an overview of the command-line command syntax. A formatted and hyperlinked copy of the latest Git documentation can be viewed at `https://git.github.io/htmldocs/git.html`. -ifdef::stalenotes[] -[NOTE] -============ - -You are reading the documentation for the latest (possibly -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.13.0/git.html[documentation for release 2.13] - -* release notes for - link:RelNotes/2.13.0.txt[2.13]. - - -* link:v2.12.3/git.html[documentation for release 2.12.3] - -* release notes for - link:RelNotes/2.12.3.txt[2.12.3], - link:RelNotes/2.12.2.txt[2.12.2], - link:RelNotes/2.12.1.txt[2.12.1], - link:RelNotes/2.12.0.txt[2.12]. - -* link:v2.11.1/git.html[documentation for release 2.11.1] - -* release notes for - link:RelNotes/2.11.2.txt[2.11.2], - link:RelNotes/2.11.1.txt[2.11.1], - link:RelNotes/2.11.0.txt[2.11]. - -* link:v2.10.3/git.html[documentation for release 2.10.3] - -* release notes for - link:RelNotes/2.10.3.txt[2.10.3], - link:RelNotes/2.10.2.txt[2.10.2], - link:RelNotes/2.10.1.txt[2.10.1], - link:RelNotes/2.10.0.txt[2.10]. - -* link:v2.9.4/git.html[documentation for release 2.9.4] - -* release notes for - link:RelNotes/2.9.4.txt[2.9.4], - link:RelNotes/2.9.3.txt[2.9.3], - link:RelNotes/2.9.2.txt[2.9.2], - link:RelNotes/2.9.1.txt[2.9.1], - link:RelNotes/2.9.0.txt[2.9]. - -* link:v2.8.5/git.html[documentation for release 2.8.5] - -* release notes for - link:RelNotes/2.8.5.txt[2.8.5], - link:RelNotes/2.8.4.txt[2.8.4], - link:RelNotes/2.8.3.txt[2.8.3], - link:RelNotes/2.8.2.txt[2.8.2], - link:RelNotes/2.8.1.txt[2.8.1], - link:RelNotes/2.8.0.txt[2.8]. - -* link:v2.7.5/git.html[documentation for release 2.7.5] - -* release notes for - link:RelNotes/2.7.5.txt[2.7.5], - link:RelNotes/2.7.4.txt[2.7.4], - link:RelNotes/2.7.3.txt[2.7.3], - link:RelNotes/2.7.2.txt[2.7.2], - link:RelNotes/2.7.1.txt[2.7.1], - link:RelNotes/2.7.0.txt[2.7]. - -* link:v2.6.7/git.html[documentation for release 2.6.7] - -* release notes for - link:RelNotes/2.6.7.txt[2.6.7], - link:RelNotes/2.6.6.txt[2.6.6], - link:RelNotes/2.6.5.txt[2.6.5], - link:RelNotes/2.6.4.txt[2.6.4], - link:RelNotes/2.6.3.txt[2.6.3], - link:RelNotes/2.6.2.txt[2.6.2], - link:RelNotes/2.6.1.txt[2.6.1], - link:RelNotes/2.6.0.txt[2.6]. - -* link:v2.5.6/git.html[documentation for release 2.5.6] - -* release notes for - link:RelNotes/2.5.6.txt[2.5.6], - link:RelNotes/2.5.5.txt[2.5.5], - link:RelNotes/2.5.4.txt[2.5.4], - link:RelNotes/2.5.3.txt[2.5.3], - link:RelNotes/2.5.2.txt[2.5.2], - link:RelNotes/2.5.1.txt[2.5.1], - link:RelNotes/2.5.0.txt[2.5]. - -* link:v2.4.12/git.html[documentation for release 2.4.12] - -* release notes for - link:RelNotes/2.4.12.txt[2.4.12], - link:RelNotes/2.4.11.txt[2.4.11], - link:RelNotes/2.4.10.txt[2.4.10], - link:RelNotes/2.4.9.txt[2.4.9], - link:RelNotes/2.4.8.txt[2.4.8], - link:RelNotes/2.4.7.txt[2.4.7], - link:RelNotes/2.4.6.txt[2.4.6], - link:RelNotes/2.4.5.txt[2.4.5], - link:RelNotes/2.4.4.txt[2.4.4], - link:RelNotes/2.4.3.txt[2.4.3], - link:RelNotes/2.4.2.txt[2.4.2], - link:RelNotes/2.4.1.txt[2.4.1], - link:RelNotes/2.4.0.txt[2.4]. - -* link:v2.3.10/git.html[documentation for release 2.3.10] - -* release notes for - link:RelNotes/2.3.10.txt[2.3.10], - link:RelNotes/2.3.9.txt[2.3.9], - link:RelNotes/2.3.8.txt[2.3.8], - link:RelNotes/2.3.7.txt[2.3.7], - link:RelNotes/2.3.6.txt[2.3.6], - link:RelNotes/2.3.5.txt[2.3.5], - link:RelNotes/2.3.4.txt[2.3.4], - link:RelNotes/2.3.3.txt[2.3.3], - link:RelNotes/2.3.2.txt[2.3.2], - link:RelNotes/2.3.1.txt[2.3.1], - link:RelNotes/2.3.0.txt[2.3]. - -* link:v2.2.3/git.html[documentation for release 2.2.3] - -* release notes for - link:RelNotes/2.2.3.txt[2.2.3], - link:RelNotes/2.2.2.txt[2.2.2], - link:RelNotes/2.2.1.txt[2.2.1], - link:RelNotes/2.2.0.txt[2.2]. - -* link:v2.1.4/git.html[documentation for release 2.1.4] - -* release notes for - link:RelNotes/2.1.4.txt[2.1.4], - link:RelNotes/2.1.3.txt[2.1.3], - link:RelNotes/2.1.2.txt[2.1.2], - link:RelNotes/2.1.1.txt[2.1.1], - link:RelNotes/2.1.0.txt[2.1]. - -* link:v2.0.5/git.html[documentation for release 2.0.5] - -* 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] - -* 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] - -* 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], - link:RelNotes/1.8.5.2.txt[1.8.5.2], - link:RelNotes/1.8.5.1.txt[1.8.5.1], - link:RelNotes/1.8.5.txt[1.8.5]. - -* link:v1.8.4.5/git.html[documentation for release 1.8.4.5] - -* release notes for - link:RelNotes/1.8.4.5.txt[1.8.4.5], - link:RelNotes/1.8.4.4.txt[1.8.4.4], - link:RelNotes/1.8.4.3.txt[1.8.4.3], - link:RelNotes/1.8.4.2.txt[1.8.4.2], - link:RelNotes/1.8.4.1.txt[1.8.4.1], - link:RelNotes/1.8.4.txt[1.8.4]. - -* link:v1.8.3.4/git.html[documentation for release 1.8.3.4] - -* release notes for - link:RelNotes/1.8.3.4.txt[1.8.3.4], - link:RelNotes/1.8.3.3.txt[1.8.3.3], - link:RelNotes/1.8.3.2.txt[1.8.3.2], - link:RelNotes/1.8.3.1.txt[1.8.3.1], - link:RelNotes/1.8.3.txt[1.8.3]. - -* link:v1.8.2.3/git.html[documentation for release 1.8.2.3] - -* release notes for - link:RelNotes/1.8.2.3.txt[1.8.2.3], - link:RelNotes/1.8.2.2.txt[1.8.2.2], - link:RelNotes/1.8.2.1.txt[1.8.2.1], - link:RelNotes/1.8.2.txt[1.8.2]. - -* link:v1.8.1.6/git.html[documentation for release 1.8.1.6] - -* release notes for - link:RelNotes/1.8.1.6.txt[1.8.1.6], - link:RelNotes/1.8.1.5.txt[1.8.1.5], - link:RelNotes/1.8.1.4.txt[1.8.1.4], - link:RelNotes/1.8.1.3.txt[1.8.1.3], - link:RelNotes/1.8.1.2.txt[1.8.1.2], - link:RelNotes/1.8.1.1.txt[1.8.1.1], - link:RelNotes/1.8.1.txt[1.8.1]. - -* link:v1.8.0.3/git.html[documentation for release 1.8.0.3] - -* release notes for - link:RelNotes/1.8.0.3.txt[1.8.0.3], - link:RelNotes/1.8.0.2.txt[1.8.0.2], - link:RelNotes/1.8.0.1.txt[1.8.0.1], - link:RelNotes/1.8.0.txt[1.8.0]. - -* link:v1.7.12.4/git.html[documentation for release 1.7.12.4] - -* release notes for - link:RelNotes/1.7.12.4.txt[1.7.12.4], - link:RelNotes/1.7.12.3.txt[1.7.12.3], - link:RelNotes/1.7.12.2.txt[1.7.12.2], - link:RelNotes/1.7.12.1.txt[1.7.12.1], - link:RelNotes/1.7.12.txt[1.7.12]. - -* link:v1.7.11.7/git.html[documentation for release 1.7.11.7] - -* release notes for - link:RelNotes/1.7.11.7.txt[1.7.11.7], - link:RelNotes/1.7.11.6.txt[1.7.11.6], - link:RelNotes/1.7.11.5.txt[1.7.11.5], - link:RelNotes/1.7.11.4.txt[1.7.11.4], - link:RelNotes/1.7.11.3.txt[1.7.11.3], - link:RelNotes/1.7.11.2.txt[1.7.11.2], - link:RelNotes/1.7.11.1.txt[1.7.11.1], - link:RelNotes/1.7.11.txt[1.7.11]. - -* link:v1.7.10.5/git.html[documentation for release 1.7.10.5] - -* release notes for - link:RelNotes/1.7.10.5.txt[1.7.10.5], - link:RelNotes/1.7.10.4.txt[1.7.10.4], - link:RelNotes/1.7.10.3.txt[1.7.10.3], - link:RelNotes/1.7.10.2.txt[1.7.10.2], - link:RelNotes/1.7.10.1.txt[1.7.10.1], - link:RelNotes/1.7.10.txt[1.7.10]. - -* link:v1.7.9.7/git.html[documentation for release 1.7.9.7] - -* release notes for - link:RelNotes/1.7.9.7.txt[1.7.9.7], - link:RelNotes/1.7.9.6.txt[1.7.9.6], - link:RelNotes/1.7.9.5.txt[1.7.9.5], - link:RelNotes/1.7.9.4.txt[1.7.9.4], - link:RelNotes/1.7.9.3.txt[1.7.9.3], - link:RelNotes/1.7.9.2.txt[1.7.9.2], - link:RelNotes/1.7.9.1.txt[1.7.9.1], - link:RelNotes/1.7.9.txt[1.7.9]. - -* link:v1.7.8.6/git.html[documentation for release 1.7.8.6] - -* release notes for - link:RelNotes/1.7.8.6.txt[1.7.8.6], - link:RelNotes/1.7.8.5.txt[1.7.8.5], - link:RelNotes/1.7.8.4.txt[1.7.8.4], - link:RelNotes/1.7.8.3.txt[1.7.8.3], - link:RelNotes/1.7.8.2.txt[1.7.8.2], - link:RelNotes/1.7.8.1.txt[1.7.8.1], - link:RelNotes/1.7.8.txt[1.7.8]. - -* link:v1.7.7.7/git.html[documentation for release 1.7.7.7] - -* release notes for - link:RelNotes/1.7.7.7.txt[1.7.7.7], - link:RelNotes/1.7.7.6.txt[1.7.7.6], - link:RelNotes/1.7.7.5.txt[1.7.7.5], - link:RelNotes/1.7.7.4.txt[1.7.7.4], - link:RelNotes/1.7.7.3.txt[1.7.7.3], - link:RelNotes/1.7.7.2.txt[1.7.7.2], - link:RelNotes/1.7.7.1.txt[1.7.7.1], - link:RelNotes/1.7.7.txt[1.7.7]. - -* link:v1.7.6.6/git.html[documentation for release 1.7.6.6] - -* release notes for - link:RelNotes/1.7.6.6.txt[1.7.6.6], - link:RelNotes/1.7.6.5.txt[1.7.6.5], - link:RelNotes/1.7.6.4.txt[1.7.6.4], - link:RelNotes/1.7.6.3.txt[1.7.6.3], - link:RelNotes/1.7.6.2.txt[1.7.6.2], - link:RelNotes/1.7.6.1.txt[1.7.6.1], - link:RelNotes/1.7.6.txt[1.7.6]. - -* link:v1.7.5.4/git.html[documentation for release 1.7.5.4] - -* release notes for - link:RelNotes/1.7.5.4.txt[1.7.5.4], - link:RelNotes/1.7.5.3.txt[1.7.5.3], - link:RelNotes/1.7.5.2.txt[1.7.5.2], - link:RelNotes/1.7.5.1.txt[1.7.5.1], - link:RelNotes/1.7.5.txt[1.7.5]. - -* link:v1.7.4.5/git.html[documentation for release 1.7.4.5] - -* release notes for - link:RelNotes/1.7.4.5.txt[1.7.4.5], - link:RelNotes/1.7.4.4.txt[1.7.4.4], - link:RelNotes/1.7.4.3.txt[1.7.4.3], - link:RelNotes/1.7.4.2.txt[1.7.4.2], - link:RelNotes/1.7.4.1.txt[1.7.4.1], - link:RelNotes/1.7.4.txt[1.7.4]. - -* link:v1.7.3.5/git.html[documentation for release 1.7.3.5] - -* release notes for - link:RelNotes/1.7.3.5.txt[1.7.3.5], - link:RelNotes/1.7.3.4.txt[1.7.3.4], - link:RelNotes/1.7.3.3.txt[1.7.3.3], - link:RelNotes/1.7.3.2.txt[1.7.3.2], - link:RelNotes/1.7.3.1.txt[1.7.3.1], - link:RelNotes/1.7.3.txt[1.7.3]. - -* link:v1.7.2.5/git.html[documentation for release 1.7.2.5] - -* release notes for - link:RelNotes/1.7.2.5.txt[1.7.2.5], - link:RelNotes/1.7.2.4.txt[1.7.2.4], - link:RelNotes/1.7.2.3.txt[1.7.2.3], - link:RelNotes/1.7.2.2.txt[1.7.2.2], - link:RelNotes/1.7.2.1.txt[1.7.2.1], - link:RelNotes/1.7.2.txt[1.7.2]. - -* link:v1.7.1.4/git.html[documentation for release 1.7.1.4] - -* release notes for - link:RelNotes/1.7.1.4.txt[1.7.1.4], - link:RelNotes/1.7.1.3.txt[1.7.1.3], - link:RelNotes/1.7.1.2.txt[1.7.1.2], - link:RelNotes/1.7.1.1.txt[1.7.1.1], - link:RelNotes/1.7.1.txt[1.7.1]. - -* link:v1.7.0.9/git.html[documentation for release 1.7.0.9] - -* release notes for - link:RelNotes/1.7.0.9.txt[1.7.0.9], - link:RelNotes/1.7.0.8.txt[1.7.0.8], - link:RelNotes/1.7.0.7.txt[1.7.0.7], - link:RelNotes/1.7.0.6.txt[1.7.0.6], - link:RelNotes/1.7.0.5.txt[1.7.0.5], - link:RelNotes/1.7.0.4.txt[1.7.0.4], - link:RelNotes/1.7.0.3.txt[1.7.0.3], - link:RelNotes/1.7.0.2.txt[1.7.0.2], - link:RelNotes/1.7.0.1.txt[1.7.0.1], - link:RelNotes/1.7.0.txt[1.7.0]. - -* link:v1.6.6.3/git.html[documentation for release 1.6.6.3] - -* release notes for - link:RelNotes/1.6.6.3.txt[1.6.6.3], - link:RelNotes/1.6.6.2.txt[1.6.6.2], - link:RelNotes/1.6.6.1.txt[1.6.6.1], - link:RelNotes/1.6.6.txt[1.6.6]. - -* link:v1.6.5.9/git.html[documentation for release 1.6.5.9] - -* release notes for - link:RelNotes/1.6.5.9.txt[1.6.5.9], - link:RelNotes/1.6.5.8.txt[1.6.5.8], - link:RelNotes/1.6.5.7.txt[1.6.5.7], - link:RelNotes/1.6.5.6.txt[1.6.5.6], - link:RelNotes/1.6.5.5.txt[1.6.5.5], - link:RelNotes/1.6.5.4.txt[1.6.5.4], - link:RelNotes/1.6.5.3.txt[1.6.5.3], - link:RelNotes/1.6.5.2.txt[1.6.5.2], - link:RelNotes/1.6.5.1.txt[1.6.5.1], - link:RelNotes/1.6.5.txt[1.6.5]. - -* link:v1.6.4.5/git.html[documentation for release 1.6.4.5] - -* release notes for - link:RelNotes/1.6.4.5.txt[1.6.4.5], - link:RelNotes/1.6.4.4.txt[1.6.4.4], - link:RelNotes/1.6.4.3.txt[1.6.4.3], - link:RelNotes/1.6.4.2.txt[1.6.4.2], - link:RelNotes/1.6.4.1.txt[1.6.4.1], - link:RelNotes/1.6.4.txt[1.6.4]. - -* link:v1.6.3.4/git.html[documentation for release 1.6.3.4] - -* release notes for - link:RelNotes/1.6.3.4.txt[1.6.3.4], - link:RelNotes/1.6.3.3.txt[1.6.3.3], - link:RelNotes/1.6.3.2.txt[1.6.3.2], - link:RelNotes/1.6.3.1.txt[1.6.3.1], - link:RelNotes/1.6.3.txt[1.6.3]. - -* release notes for - link:RelNotes/1.6.2.5.txt[1.6.2.5], - link:RelNotes/1.6.2.4.txt[1.6.2.4], - link:RelNotes/1.6.2.3.txt[1.6.2.3], - link:RelNotes/1.6.2.2.txt[1.6.2.2], - link:RelNotes/1.6.2.1.txt[1.6.2.1], - link:RelNotes/1.6.2.txt[1.6.2]. - -* link:v1.6.1.3/git.html[documentation for release 1.6.1.3] - -* release notes for - link:RelNotes/1.6.1.3.txt[1.6.1.3], - link:RelNotes/1.6.1.2.txt[1.6.1.2], - link:RelNotes/1.6.1.1.txt[1.6.1.1], - link:RelNotes/1.6.1.txt[1.6.1]. - -* link:v1.6.0.6/git.html[documentation for release 1.6.0.6] - -* release notes for - link:RelNotes/1.6.0.6.txt[1.6.0.6], - link:RelNotes/1.6.0.5.txt[1.6.0.5], - link:RelNotes/1.6.0.4.txt[1.6.0.4], - link:RelNotes/1.6.0.3.txt[1.6.0.3], - link:RelNotes/1.6.0.2.txt[1.6.0.2], - link:RelNotes/1.6.0.1.txt[1.6.0.1], - link:RelNotes/1.6.0.txt[1.6.0]. - -* link:v1.5.6.6/git.html[documentation for release 1.5.6.6] - -* release notes for - link:RelNotes/1.5.6.6.txt[1.5.6.6], - link:RelNotes/1.5.6.5.txt[1.5.6.5], - link:RelNotes/1.5.6.4.txt[1.5.6.4], - link:RelNotes/1.5.6.3.txt[1.5.6.3], - link:RelNotes/1.5.6.2.txt[1.5.6.2], - link:RelNotes/1.5.6.1.txt[1.5.6.1], - link:RelNotes/1.5.6.txt[1.5.6]. - -* link:v1.5.5.6/git.html[documentation for release 1.5.5.6] - -* release notes for - link:RelNotes/1.5.5.6.txt[1.5.5.6], - link:RelNotes/1.5.5.5.txt[1.5.5.5], - link:RelNotes/1.5.5.4.txt[1.5.5.4], - link:RelNotes/1.5.5.3.txt[1.5.5.3], - link:RelNotes/1.5.5.2.txt[1.5.5.2], - link:RelNotes/1.5.5.1.txt[1.5.5.1], - link:RelNotes/1.5.5.txt[1.5.5]. - -* link:v1.5.4.7/git.html[documentation for release 1.5.4.7] - -* release notes for - link:RelNotes/1.5.4.7.txt[1.5.4.7], - link:RelNotes/1.5.4.6.txt[1.5.4.6], - link:RelNotes/1.5.4.5.txt[1.5.4.5], - link:RelNotes/1.5.4.4.txt[1.5.4.4], - link:RelNotes/1.5.4.3.txt[1.5.4.3], - link:RelNotes/1.5.4.2.txt[1.5.4.2], - link:RelNotes/1.5.4.1.txt[1.5.4.1], - link:RelNotes/1.5.4.txt[1.5.4]. - -* link:v1.5.3.8/git.html[documentation for release 1.5.3.8] - -* release notes for - link:RelNotes/1.5.3.8.txt[1.5.3.8], - link:RelNotes/1.5.3.7.txt[1.5.3.7], - link:RelNotes/1.5.3.6.txt[1.5.3.6], - link:RelNotes/1.5.3.5.txt[1.5.3.5], - link:RelNotes/1.5.3.4.txt[1.5.3.4], - link:RelNotes/1.5.3.3.txt[1.5.3.3], - link:RelNotes/1.5.3.2.txt[1.5.3.2], - link:RelNotes/1.5.3.1.txt[1.5.3.1], - link:RelNotes/1.5.3.txt[1.5.3]. - -* link:v1.5.2.5/git.html[documentation for release 1.5.2.5] - -* release notes for - link:RelNotes/1.5.2.5.txt[1.5.2.5], - link:RelNotes/1.5.2.4.txt[1.5.2.4], - link:RelNotes/1.5.2.3.txt[1.5.2.3], - link:RelNotes/1.5.2.2.txt[1.5.2.2], - link:RelNotes/1.5.2.1.txt[1.5.2.1], - link:RelNotes/1.5.2.txt[1.5.2]. - -* link:v1.5.1.6/git.html[documentation for release 1.5.1.6] - -* release notes for - link:RelNotes/1.5.1.6.txt[1.5.1.6], - link:RelNotes/1.5.1.5.txt[1.5.1.5], - link:RelNotes/1.5.1.4.txt[1.5.1.4], - link:RelNotes/1.5.1.3.txt[1.5.1.3], - link:RelNotes/1.5.1.2.txt[1.5.1.2], - link:RelNotes/1.5.1.1.txt[1.5.1.1], - link:RelNotes/1.5.1.txt[1.5.1]. - -* link:v1.5.0.7/git.html[documentation for release 1.5.0.7] - -* release notes for - link:RelNotes/1.5.0.7.txt[1.5.0.7], - link:RelNotes/1.5.0.6.txt[1.5.0.6], - link:RelNotes/1.5.0.5.txt[1.5.0.5], - link:RelNotes/1.5.0.3.txt[1.5.0.3], - link:RelNotes/1.5.0.2.txt[1.5.0.2], - link:RelNotes/1.5.0.1.txt[1.5.0.1], - link:RelNotes/1.5.0.txt[1.5.0]. - -* documentation for release link:v1.4.4.4/git.html[1.4.4.4], - link:v1.3.3/git.html[1.3.3], - link:v1.2.6/git.html[1.2.6], - link:v1.0.13/git.html[1.0.13]. - -============ - -endif::stalenotes[] OPTIONS ------- diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 4736483865..2a2d7e2a4d 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -229,7 +229,7 @@ From a clean working directory: ------------------------------------------------- $ echo "* text=auto" >.gitattributes -$ rm .git/index # Remove the index to re-scan the working directory +$ git read-tree --empty # Clean index, force re-scan of working directory $ git add . $ git status # Show files that will be normalized $ git commit -m "Introduce end-of-line normalization" diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index dfe7d83727..9f13266a68 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -194,7 +194,7 @@ different things. * The `--index` option is used to ask a command that usually works on files in the working tree to *also* affect the index. For example, `git stash apply` usually - merges changes recorded in a stash to the working tree, + merges changes recorded in a stash entry to the working tree, but with the `--index` option, it also merges changes to the index as well. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 706091a569..b2514f4d44 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -447,6 +447,14 @@ rebase:: The commits are guaranteed to be listed in the order that they were processed by rebase. +sendemail-validate +~~~~~~~~~~~~~~~~~~ + +This hook is invoked by 'git send-email'. It takes a single parameter, +the name of the file that holds the e-mail to be sent. Exiting with a +non-zero status causes 'git send-email' to abort before sending any +e-mails. + GIT --- diff --git a/Documentation/gitweb.txt b/Documentation/gitweb.txt index 96156e5e1f..88450589af 100644 --- a/Documentation/gitweb.txt +++ b/Documentation/gitweb.txt @@ -84,7 +84,7 @@ separator (rules for Perl's "`split(" ", $line)`"). * Fields use modified URI encoding, defined in RFC 3986, section 2.1 (Percent-Encoding), or rather "Query string encoding" (see -http://en.wikipedia.org/wiki/Query_string#URL_encoding[]), the difference +https://en.wikipedia.org/wiki/Query_string#URL_encoding[]), the difference being that SP (" ") can be encoded as "{plus}" (and therefore "{plus}" has to be also percent-encoded). + diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 6e991c2469..b71b943b12 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -570,6 +570,10 @@ The most notable example is `HEAD`. is created by giving the `--depth` option to linkgit:git-clone[1], and its history can be later deepened with linkgit:git-fetch[1]. +[[def_stash]]stash entry:: + An <<def_object,object>> used to temporarily store the contents of a + <<def_dirty,dirty>> working directory and the index for future reuse. + [[def_submodule]]submodule:: A <<def_repository,repository>> that holds the history of a separate project inside another repository (the latter of diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 47b286b33e..a48d267e26 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -199,7 +199,7 @@ endif::git-rev-list[] than given and there are spaces on its left, use those spaces - '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)' respectively, but padding both sides (i.e. the text is centered) --%(trailers): display the trailers of the body as interpreted by +- %(trailers): display the trailers of the body as interpreted by linkgit:git-interpret-trailers[1] NOTE: Some placeholders may depend on other options given to the @@ -213,8 +213,8 @@ If you add a `+` (plus sign) after '%' of a placeholder, a line-feed is inserted immediately before the expansion if and only if the placeholder expands to a non-empty string. -If you add a `-` (minus sign) after '%' of a placeholder, line-feeds that -immediately precede the expansion are deleted if and only if the +If you add a `-` (minus sign) after '%' of a placeholder, all consecutive +line-feeds immediately preceding the expansion are deleted if and only if the placeholder expands to an empty string. If you add a ` ` (space) after '%' of a placeholder, a space diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index a02f7324c0..a6cf9eb380 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -91,9 +91,14 @@ endif::git-rev-list[] Consider the limiting patterns to be fixed strings (don't interpret pattern as a regular expression). +-P:: --perl-regexp:: - Consider the limiting patterns to be Perl-compatible regular expressions. - Requires libpcre to be compiled in. + Consider the limiting patterns to be Perl-compatible regular + expressions. ++ +Support for these types of regular expressions is an optional +compile-time dependency. If Git wasn't compiled with support for them +providing this option will cause it to die. --remove-empty:: Stop when a given path disappears from the tree. @@ -764,7 +769,8 @@ timezone value. 1970). As with `--raw`, this is always in UTC and therefore `-local` has no effect. + -`--date=format:...` feeds the format `...` to your system `strftime`. +`--date=format:...` feeds the format `...` to your system `strftime`, +except for %z and %Z, which are handled internally. Use `--date=format:%c` to show the date in your system locale's preferred format. See the `strftime` manual for a complete list of format placeholders. When using `-local`, the correct syntax is diff --git a/Documentation/technical/api-directory-listing.txt b/Documentation/technical/api-directory-listing.txt index 7f8e78d916..6c77b4920c 100644 --- a/Documentation/technical/api-directory-listing.txt +++ b/Documentation/technical/api-directory-listing.txt @@ -33,6 +33,12 @@ The notable options are: Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]` in addition to untracked files in `entries[]`. +`DIR_KEEP_UNTRACKED_CONTENTS`::: + + Only has meaning if `DIR_SHOW_IGNORED_TOO` is also set; if this is set, the + untracked contents of untracked directories are also returned in + `entries[]`. + `DIR_COLLECT_IGNORED`::: Special mode for git-add. Return ignored files in `ignored[]` and diff --git a/Documentation/technical/api-sub-process.txt b/Documentation/technical/api-sub-process.txt new file mode 100644 index 0000000000..793508cf3e --- /dev/null +++ b/Documentation/technical/api-sub-process.txt @@ -0,0 +1,59 @@ +sub-process API +=============== + +The sub-process API makes it possible to run background sub-processes +for the entire lifetime of a Git invocation. If Git needs to communicate +with an external process multiple times, then this can reduces the process +invocation overhead. Git and the sub-process communicate through stdin and +stdout. + +The sub-processes are kept in a hashmap by command name and looked up +via the subprocess_find_entry function. If an existing instance can not +be found then a new process should be created and started. When the +parent git command terminates, all sub-processes are also terminated. + +This API is based on the run-command API. + +Data structures +--------------- + +* `struct subprocess_entry` + +The sub-process structure. Members should not be accessed directly. + +Types +----- + +'int(*subprocess_start_fn)(struct subprocess_entry *entry)':: + + User-supplied function to initialize the sub-process. This is + typically used to negotiate the interface version and capabilities. + + +Functions +--------- + +`cmd2process_cmp`:: + + Function to test two subprocess hashmap entries for equality. + +`subprocess_start`:: + + Start a subprocess and add it to the subprocess hashmap. + +`subprocess_stop`:: + + Kill a subprocess and remove it from the subprocess hashmap. + +`subprocess_find_entry`:: + + Find a subprocess in the subprocess hashmap. + +`subprocess_get_child_process`:: + + Get the underlying `struct child_process` from a subprocess. + +`subprocess_read_status`:: + + Helper function to read packets looking for the last "status=<foo>" + key/value pair. @@ -19,16 +19,34 @@ all:: # have been written to the final string if enough space had been available. # # Define FREAD_READS_DIRECTORIES if you are on a system which succeeds -# when attempting to read from an fopen'ed directory. +# when attempting to read from an fopen'ed directory (or even to fopen +# it at all). # # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies BLK_SHA1. # -# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be -# able to use Perl-compatible regular expressions. -# -# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in -# /foo/bar/include and /foo/bar/lib directories. +# Define USE_LIBPCRE if you have and want to use libpcre. Various +# commands such as log and grep offer runtime options to use +# Perl-compatible regular expressions instead of standard or extended +# POSIX regular expressions. +# +# Currently USE_LIBPCRE is a synonym for USE_LIBPCRE1, define +# USE_LIBPCRE2 instead if you'd like to use version 2 of the PCRE +# library. The USE_LIBPCRE flag will likely be changed to mean v2 by +# default in future releases. +# +# When using USE_LIBPCRE1, define NO_LIBPCRE1_JIT if the PCRE v1 +# library is compiled without --enable-jit. We will auto-detect +# whether the version of the PCRE v1 library in use has JIT support at +# all, but we unfortunately can't auto-detect whether JIT support +# hasn't been compiled in in an otherwise JIT-supporting version. If +# you have link-time errors about a missing `pcre_jit_exec` define +# this, or recompile PCRE v1 with --enable-jit. +# +# Define LIBPCREDIR=/foo/bar if your PCRE header and library files are +# in /foo/bar/include and /foo/bar/lib directories. Which version of +# PCRE this points to determined by the USE_LIBPCRE1 and USE_LIBPCRE2 +# variables. # # Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header. # @@ -718,6 +736,7 @@ LIB_OBJS += argv-array.o LIB_OBJS += attr.o LIB_OBJS += base85.o LIB_OBJS += bisect.o +LIB_OBJS += blame.o LIB_OBJS += blob.o LIB_OBJS += branch.o LIB_OBJS += bulk-checkin.o @@ -842,6 +861,7 @@ LIB_OBJS += streaming.o LIB_OBJS += string-list.o LIB_OBJS += submodule.o LIB_OBJS += submodule-config.o +LIB_OBJS += sub-process.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += tempfile.o @@ -1085,13 +1105,29 @@ ifdef NO_LIBGEN_H COMPAT_OBJS += compat/basename.o endif -ifdef USE_LIBPCRE - BASIC_CFLAGS += -DUSE_LIBPCRE - ifdef LIBPCREDIR - BASIC_CFLAGS += -I$(LIBPCREDIR)/include - EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib) +USE_LIBPCRE1 ?= $(USE_LIBPCRE) + +ifneq (,$(USE_LIBPCRE1)) + ifdef USE_LIBPCRE2 +$(error Only set USE_LIBPCRE1 (or its alias USE_LIBPCRE) or USE_LIBPCRE2, not both!) endif + + BASIC_CFLAGS += -DUSE_LIBPCRE1 EXTLIBS += -lpcre + +ifdef NO_LIBPCRE1_JIT + BASIC_CFLAGS += -DNO_LIBPCRE1_JIT +endif +endif + +ifdef USE_LIBPCRE2 + BASIC_CFLAGS += -DUSE_LIBPCRE2 + EXTLIBS += -lpcre2-8 +endif + +ifdef LIBPCREDIR + BASIC_CFLAGS += -I$(LIBPCREDIR)/include + EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib) endif ifdef HAVE_ALLOCA_H @@ -1414,7 +1450,14 @@ else DC_SHA1 := YesPlease LIB_OBJS += sha1dc/sha1.o LIB_OBJS += sha1dc/ubc_check.o - BASIC_CFLAGS += -DSHA1_DC + BASIC_CFLAGS += \ + -DSHA1_DC \ + -DSHA1DC_NO_STANDARD_INCLUDES \ + -DSHA1DC_INIT_SAFE_HASH_DEFAULT=0 \ + -DSHA1DC_CUSTOM_INCLUDE_SHA1_C="\"cache.h\"" \ + -DSHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C="\"sha1dc_git.c\"" \ + -DSHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H="\"sha1dc_git.h\"" \ + -DSHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C="\"git-compat-util.h\"" endif endif endif @@ -2238,8 +2281,11 @@ GIT-BUILD-OPTIONS: FORCE @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@+ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@+ @echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@+ - @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@+ + @echo USE_LIBPCRE1=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE1)))'\' >>$@+ + @echo USE_LIBPCRE2=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE2)))'\' >>$@+ + @echo NO_LIBPCRE1_JIT=\''$(subst ','\'',$(subst ','\'',$(NO_LIBPCRE1_JIT)))'\' >>$@+ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+ + @echo NO_PTHREADS=\''$(subst ','\'',$(subst ','\'',$(NO_PTHREADS)))'\' >>$@+ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+ @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+ @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+ @@ -2270,6 +2316,9 @@ endif ifdef GIT_PERF_MAKE_OPTS @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@+ endif +ifdef GIT_PERF_MAKE_COMMAND + @echo GIT_PERF_MAKE_COMMAND=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_COMMAND)))'\' >>$@+ +endif ifdef GIT_INTEROP_MAKE_OPTS @echo GIT_INTEROP_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_INTEROP_MAKE_OPTS)))'\' >>$@+ endif @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" int advice_push_update_rejected = 1; int advice_push_non_ff_current = 1; @@ -15,6 +16,7 @@ int advice_detached_head = 1; int advice_set_upstream_failure = 1; int advice_object_name_warning = 1; int advice_rm_hints = 1; +int advice_add_embedded_repo = 1; static struct { const char *name; @@ -35,6 +37,7 @@ static struct { { "setupstreamfailure", &advice_set_upstream_failure }, { "objectnamewarning", &advice_object_name_warning }, { "rmhints", &advice_rm_hints }, + { "addembeddedrepo", &advice_add_embedded_repo }, /* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected } @@ -18,6 +18,7 @@ extern int advice_detached_head; extern int advice_set_upstream_failure; extern int advice_object_name_warning; extern int advice_rm_hints; +extern int advice_add_embedded_repo; int git_default_advice_config(const char *var, const char *value); __attribute__((format (printf, 1, 2))) @@ -1,14 +1,29 @@ #include "cache.h" +#include "config.h" + +struct config_alias_data { + const char *alias; + char *v; +}; + +static int config_alias_cb(const char *key, const char *value, void *d) +{ + struct config_alias_data *data = d; + const char *p; + + if (skip_prefix(key, "alias.", &p) && !strcmp(p, data->alias)) + return git_config_string((const char **)&data->v, key, value); + + return 0; +} char *alias_lookup(const char *alias) { - char *v = NULL; - struct strbuf key = STRBUF_INIT; - strbuf_addf(&key, "alias.%s", alias); - if (git_config_key_is_valid(key.buf)) - git_config_get_string(key.buf, &v); - strbuf_release(&key); - return v; + struct config_alias_data data = { alias, NULL }; + + read_early_config(config_alias_cb, &data); + + return data.v; } #define SPLIT_CMDLINE_BAD_ENDING 1 @@ -47,8 +62,7 @@ int split_cmdline(char *cmdline, const char ***argv) src++; c = cmdline[src]; if (!c) { - free(*argv); - *argv = NULL; + FREE_AND_NULL(*argv); return -SPLIT_CMDLINE_BAD_ENDING; } } @@ -60,8 +74,7 @@ int split_cmdline(char *cmdline, const char ***argv) cmdline[dst] = 0; if (quoted) { - free(*argv); - *argv = NULL; + FREE_AND_NULL(*argv); return -SPLIT_CMDLINE_UNCLOSED_QUOTE; } @@ -8,6 +8,7 @@ */ #include "cache.h" +#include "config.h" #include "blob.h" #include "delta.h" #include "diff.h" @@ -762,17 +763,6 @@ static char *find_name_traditional(struct apply_state *state, return find_name_common(state, line, def, p_value, line + len, 0); } -static int count_slashes(const char *cp) -{ - int cnt = 0; - char ch; - - while ((ch = *cp++)) - if (ch == '/') - cnt++; - return cnt; -} - /* * Given the string after "--- " or "+++ ", guess the appropriate * p_value for the given patch. @@ -2267,7 +2257,7 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) case S_IFREG: if (strbuf_read_file(buf, path, st->st_size) != st->st_size) return error(_("unable to open or read %s"), path); - convert_to_git(path, buf->buf, buf->len, buf, 0); + convert_to_git(&the_index, path, buf->buf, buf->len, buf, 0); return 0; default: return -1; @@ -3705,8 +3695,7 @@ static int check_preimage(struct apply_state *state, is_new: patch->is_new = 1; patch->is_delete = 0; - free(patch->old_name); - patch->old_name = NULL; + FREE_AND_NULL(patch->old_name); return 0; } @@ -3741,7 +3730,7 @@ static int check_to_create(struct apply_state *state, return 0; return EXISTS_IN_WORKTREE; - } else if ((errno != ENOENT) && (errno != ENOTDIR)) { + } else if (!is_missing_file_error(errno)) { return error_errno("%s", new_name); } return 0; diff --git a/archive-tar.c b/archive-tar.c index 073e60ebd3..c6ed96ee74 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -2,6 +2,7 @@ * Copyright (c) 2005, 2006 Rene Scharfe */ #include "cache.h" +#include "config.h" #include "tar.h" #include "archive.h" #include "streaming.h" diff --git a/archive-zip.c b/archive-zip.c index 27563e9e26..e8913e5a26 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -2,6 +2,7 @@ * Copyright (c) 2006 Rene Scharfe */ #include "cache.h" +#include "config.h" #include "archive.h" #include "streaming.h" #include "utf8.h" @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "refs.h" #include "commit.h" #include "tree-walk.h" @@ -9,6 +9,7 @@ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "config.h" #include "exec_cmd.h" #include "attr.h" #include "dir.h" @@ -638,13 +639,11 @@ void attr_check_reset(struct attr_check *check) void attr_check_clear(struct attr_check *check) { - free(check->items); - check->items = NULL; + FREE_AND_NULL(check->items); check->alloc = 0; check->nr = 0; - free(check->all_attrs); - check->all_attrs = NULL; + FREE_AND_NULL(check->all_attrs); check->all_attrs_nr = 0; drop_attr_stack(&check->stack); @@ -720,16 +719,13 @@ void git_attr_set_direction(enum git_attr_direction new_direction, static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) { - FILE *fp = fopen(path, "r"); + FILE *fp = fopen_or_warn(path, "r"); struct attr_stack *res; char buf[2048]; int lineno = 0; - if (!fp) { - if (errno != ENOENT && errno != ENOTDIR) - warn_on_inaccessible(path); + if (!fp) return NULL; - } res = xcalloc(1, sizeof(*res)); while (fgets(buf, sizeof(buf), fp)) { char *bufp = buf; @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "commit.h" #include "diff.h" #include "revision.h" @@ -438,10 +439,7 @@ static void read_bisect_paths(struct argv_array *array) { struct strbuf str = STRBUF_INIT; const char *filename = git_path_bisect_names(); - FILE *fp = fopen(filename, "r"); - - if (!fp) - die_errno(_("Could not open file '%s'"), filename); + FILE *fp = xfopen(filename, "r"); while (strbuf_getline_lf(&str, fp) != EOF) { strbuf_trim(&str); @@ -546,7 +544,7 @@ static unsigned get_prn(unsigned count) { /* * Custom integer square root from - * http://en.wikipedia.org/wiki/Integer_square_root + * https://en.wikipedia.org/wiki/Integer_square_root */ static int sqrti(int val) { @@ -669,7 +667,7 @@ static int is_expected_rev(const struct object_id *oid) if (stat(filename, &st) || !S_ISREG(st.st_mode)) return 0; - fp = fopen(filename, "r"); + fp = fopen_or_warn(filename, "r"); if (!fp) return 0; @@ -995,8 +993,10 @@ int bisect_next_all(const char *prefix, int no_checkout) steps_msg = xstrfmt(Q_("(roughly %d step)", "(roughly %d steps)", steps), steps); - /* TRANSLATORS: the last %s will be replaced with - "(roughly %d steps)" translation */ + /* + * TRANSLATORS: the last %s will be replaced with "(roughly %d + * steps)" translation. + */ printf(Q_("Bisecting: %d revision left to test after this %s\n", "Bisecting: %d revisions left to test after this %s\n", nr), nr, steps_msg); diff --git a/blame.c b/blame.c new file mode 100644 index 0000000000..91e26e93e8 --- /dev/null +++ b/blame.c @@ -0,0 +1,1862 @@ +#include "cache.h" +#include "refs.h" +#include "cache-tree.h" +#include "mergesort.h" +#include "diff.h" +#include "diffcore.h" +#include "tag.h" +#include "blame.h" + +void blame_origin_decref(struct blame_origin *o) +{ + if (o && --o->refcnt <= 0) { + struct blame_origin *p, *l = NULL; + if (o->previous) + blame_origin_decref(o->previous); + free(o->file.ptr); + /* 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"); + } +} + +/* + * 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 + * this function directly. + */ +static struct blame_origin *make_origin(struct commit *commit, const char *path) +{ + struct blame_origin *o; + FLEX_ALLOC_STR(o, path, path); + o->commit = commit; + o->refcnt = 1; + o->next = commit->util; + commit->util = o; + 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 blame_origin *get_origin(struct commit *commit, const char *path) +{ + struct blame_origin *o, *l; + + 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 blame_origin_incref(o); + } + } + return make_origin(commit, path); +} + + + +static void verify_working_tree_path(struct commit *work_tree, const char *path) +{ + struct commit_list *parents; + int pos; + + for (parents = work_tree->parents; parents; parents = parents->next) { + const struct object_id *commit_oid = &parents->item->object.oid; + struct object_id blob_oid; + unsigned mode; + + if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) && + sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB) + return; + } + + pos = cache_name_pos(path, strlen(path)); + if (pos >= 0) + ; /* path is in the index */ + else if (-1 - pos < active_nr && + !strcmp(active_cache[-1 - pos]->name, path)) + ; /* path is in the index, unmerged */ + else + die("no such path '%s' in HEAD", path); +} + +static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid) +{ + struct commit *parent; + + parent = lookup_commit_reference(oid); + if (!parent) + die("no such commit %s", oid_to_hex(oid)); + return &commit_list_insert(parent, tail)->next; +} + +static void append_merge_parents(struct commit_list **tail) +{ + int merge_head; + struct strbuf line = STRBUF_INIT; + + merge_head = open(git_path_merge_head(), O_RDONLY); + if (merge_head < 0) { + if (errno == ENOENT) + return; + die("cannot open '%s' for reading", git_path_merge_head()); + } + + while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { + struct object_id oid; + if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid)) + die("unknown line in '%s': %s", git_path_merge_head(), line.buf); + tail = append_parent(tail, &oid); + } + close(merge_head); + strbuf_release(&line); +} + +/* + * This isn't as simple as passing sb->buf and sb->len, because we + * want to transfer ownership of the buffer to the commit (so we + * must use detach). + */ +static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb) +{ + size_t len; + void *buf = strbuf_detach(sb, &len); + set_commit_buffer(c, buf, len); +} + +/* + * Prepare a dummy commit that represents the work tree (or staged) item. + * Note that annotating work tree item never works in the reverse. + */ +static struct commit *fake_working_tree_commit(struct diff_options *opt, + const char *path, + const char *contents_from) +{ + struct commit *commit; + struct blame_origin *origin; + struct commit_list **parent_tail, *parent; + struct object_id head_oid; + struct strbuf buf = STRBUF_INIT; + const char *ident; + time_t now; + int size, len; + struct cache_entry *ce; + unsigned mode; + struct strbuf msg = STRBUF_INIT; + + read_cache(); + time(&now); + commit = alloc_commit_node(); + commit->object.parsed = 1; + commit->date = now; + parent_tail = &commit->parents; + + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) + die("no such ref: HEAD"); + + parent_tail = append_parent(parent_tail, &head_oid); + append_merge_parents(parent_tail); + verify_working_tree_path(commit, path); + + origin = make_origin(commit, path); + + ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); + strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n"); + for (parent = commit->parents; parent; parent = parent->next) + strbuf_addf(&msg, "parent %s\n", + oid_to_hex(&parent->item->object.oid)); + strbuf_addf(&msg, + "author %s\n" + "committer %s\n\n" + "Version of %s from %s\n", + ident, ident, path, + (!contents_from ? path : + (!strcmp(contents_from, "-") ? "standard input" : contents_from))); + set_commit_buffer_from_strbuf(commit, &msg); + + if (!contents_from || strcmp("-", contents_from)) { + struct stat st; + const char *read_from; + char *buf_ptr; + unsigned long buf_len; + + if (contents_from) { + if (stat(contents_from, &st) < 0) + die_errno("Cannot stat '%s'", contents_from); + read_from = contents_from; + } + else { + if (lstat(path, &st) < 0) + die_errno("Cannot lstat '%s'", path); + read_from = path; + } + mode = canon_mode(st.st_mode); + + switch (st.st_mode & S_IFMT) { + case S_IFREG: + if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && + textconv_object(read_from, mode, &null_oid, 0, &buf_ptr, &buf_len)) + strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1); + else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) + die_errno("cannot open or read '%s'", read_from); + break; + case S_IFLNK: + if (strbuf_readlink(&buf, read_from, st.st_size) < 0) + die_errno("cannot readlink '%s'", read_from); + break; + default: + die("unsupported file type %s", read_from); + } + } + else { + /* Reading from stdin */ + mode = 0; + if (strbuf_read(&buf, 0, 0) < 0) + die_errno("failed to read from stdin"); + } + convert_to_git(&the_index, path, buf.buf, buf.len, &buf, 0); + origin->file.ptr = buf.buf; + origin->file.size = buf.len; + pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash); + + /* + * Read the current index, replace the path entry with + * origin->blob_sha1 without mucking with its mode or type + * bits; we are not going to write this index out -- we just + * want to run "diff-index --cached". + */ + discard_cache(); + read_cache(); + + len = strlen(path); + if (!mode) { + int pos = cache_name_pos(path, len); + if (0 <= pos) + mode = active_cache[pos]->ce_mode; + else + /* Let's not bother reading from HEAD tree */ + mode = S_IFREG | 0644; + } + size = cache_entry_size(len); + ce = xcalloc(1, size); + oidcpy(&ce->oid, &origin->blob_oid); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(0); + ce->ce_namelen = len; + ce->ce_mode = create_ce_mode(mode); + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + + cache_tree_invalidate_path(&the_index, path); + + return commit; +} + + + +static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, + xdl_emit_hunk_consume_func_t hunk_func, void *cb_data, int xdl_opts) +{ + xpparam_t xpp = {0}; + xdemitconf_t xecfg = {0}; + xdemitcb_t ecb = {NULL}; + + xpp.flags = xdl_opts; + xecfg.hunk_func = hunk_func; + ecb.priv = cb_data; + return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb); +} + +/* + * Given an origin, prepare mmfile_t structure to be used by the + * diff machinery + */ +static void fill_origin_blob(struct diff_options *opt, + struct blame_origin *o, mmfile_t *file, int *num_read_blob) +{ + if (!o->file.ptr) { + enum object_type type; + unsigned long file_size; + + (*num_read_blob)++; + if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && + textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size)) + ; + else + file->ptr = read_sha1_file(o->blob_oid.hash, &type, + &file_size); + file->size = file_size; + + if (!file->ptr) + die("Cannot read blob %s for path %s", + oid_to_hex(&o->blob_oid), + o->path); + o->file = *file; + } + else + *file = o->file; +} + +static void drop_origin_blob(struct blame_origin *o) +{ + if (o->file.ptr) { + FREE_AND_NULL(o->file.ptr); + } +} + +/* + * 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; +} + +void blame_sort_final(struct blame_scoreboard *sb) +{ + sb->ent = llist_mergesort(sb->ent, get_next_blame, set_next_blame, + compare_blame_final); +} + +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); +} + +/* + * For debugging -- origin is refcounted, and this asserts that + * we do not underflow. + */ +static void sanity_check_refcnt(struct blame_scoreboard *sb) +{ + int baa = 0; + struct blame_entry *ent; + + for (ent = sb->ent; ent; ent = ent->next) { + /* Nobody should have zero or negative refcnt */ + if (ent->suspect->refcnt <= 0) { + fprintf(stderr, "%s in %s has negative refcnt %d\n", + ent->suspect->path, + oid_to_hex(&ent->suspect->commit->object.oid), + ent->suspect->refcnt); + baa = 1; + } + } + if (baa) + sb->on_sanity_fail(sb, baa); +} + +/* + * If two blame entries that are next to each other came from + * contiguous lines in the same origin (i.e. <commit, path> pair), + * merge them together. + */ +void blame_coalesce(struct blame_scoreboard *sb) +{ + struct blame_entry *ent, *next; + + for (ent = sb->ent; ent && (next = ent->next); ent = next) { + if (ent->suspect == next->suspect && + ent->s_lno + ent->num_lines == next->s_lno) { + ent->num_lines += next->num_lines; + ent->next = next->next; + blame_origin_decref(next->suspect); + free(next); + ent->score = 0; + next = ent; /* again */ + } + } + + if (sb->debug) /* sanity */ + sanity_check_refcnt(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 blame_scoreboard *sb, struct blame_origin *porigin, + struct blame_entry *sorted) +{ + if (porigin->suspects) + porigin->suspects = blame_merge(porigin->suspects, sorted); + else { + struct blame_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); + } +} + +/* + * Fill the blob_sha1 field of an origin if it hasn't, so that later + * call to fill_origin_blob() can use it to locate the data. blob_sha1 + * for an origin is also used to pass the blame for the entire file to + * the parent to detect the case where a child's blob is identical to + * that of its parent's. + * + * This also fills origin->mode for corresponding tree path. + */ +static int fill_blob_sha1_and_mode(struct blame_origin *origin) +{ + if (!is_null_oid(&origin->blob_oid)) + return 0; + if (get_tree_entry(origin->commit->object.oid.hash, + origin->path, + origin->blob_oid.hash, &origin->mode)) + goto error_out; + if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB) + goto error_out; + return 0; + error_out: + oidclr(&origin->blob_oid); + origin->mode = S_IFINVALID; + return -1; +} + +/* + * We have an origin -- check if the same path exists in the + * parent and return an origin structure to represent it. + */ +static struct blame_origin *find_origin(struct commit *parent, + struct blame_origin *origin) +{ + struct blame_origin *porigin; + struct diff_options diff_opts; + const char *paths[2]; + + /* 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. + */ + return blame_origin_incref (porigin); + } + + /* See if the origin->path is different between parent + * and origin first. Most of the time they are the + * same and diff-tree is fairly efficient about this. + */ + diff_setup(&diff_opts); + DIFF_OPT_SET(&diff_opts, RECURSIVE); + diff_opts.detect_rename = 0; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + paths[0] = origin->path; + paths[1] = NULL; + + parse_pathspec(&diff_opts.pathspec, + PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, + PATHSPEC_LITERAL_PATH, "", paths); + diff_setup_done(&diff_opts); + + if (is_null_oid(&origin->commit->object.oid)) + do_diff_cache(&parent->tree->object.oid, &diff_opts); + else + diff_tree_oid(&parent->tree->object.oid, + &origin->commit->tree->object.oid, + "", &diff_opts); + diffcore_std(&diff_opts); + + if (!diff_queued_diff.nr) { + /* The path is the same as parent */ + porigin = get_origin(parent, origin->path); + oidcpy(&porigin->blob_oid, &origin->blob_oid); + porigin->mode = origin->mode; + } else { + /* + * Since origin->path is a pathspec, if the parent + * commit had it as a directory, we will see a whole + * bunch of deletion of files in the directory that we + * do not care about. + */ + int i; + struct diff_filepair *p = NULL; + for (i = 0; i < diff_queued_diff.nr; i++) { + const char *name; + p = diff_queued_diff.queue[i]; + name = p->one->path ? p->one->path : p->two->path; + if (!strcmp(name, origin->path)) + break; + } + if (!p) + die("internal error in blame::find_origin"); + switch (p->status) { + default: + die("internal error in blame::find_origin (%c)", + p->status); + case 'M': + porigin = get_origin(parent, origin->path); + oidcpy(&porigin->blob_oid, &p->one->oid); + porigin->mode = p->one->mode; + break; + case 'A': + case 'T': + /* Did not exist in parent, or type changed */ + break; + } + } + diff_flush(&diff_opts); + clear_pathspec(&diff_opts.pathspec); + return porigin; +} + +/* + * We have an origin -- find the path that corresponds to it in its + * parent and return an origin structure to represent it. + */ +static struct blame_origin *find_rename(struct commit *parent, + struct blame_origin *origin) +{ + struct blame_origin *porigin = NULL; + struct diff_options diff_opts; + int i; + + diff_setup(&diff_opts); + DIFF_OPT_SET(&diff_opts, RECURSIVE); + diff_opts.detect_rename = DIFF_DETECT_RENAME; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_opts.single_follow = origin->path; + diff_setup_done(&diff_opts); + + if (is_null_oid(&origin->commit->object.oid)) + do_diff_cache(&parent->tree->object.oid, &diff_opts); + else + diff_tree_oid(&parent->tree->object.oid, + &origin->commit->tree->object.oid, + "", &diff_opts); + diffcore_std(&diff_opts); + + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + if ((p->status == 'R' || p->status == 'C') && + !strcmp(p->two->path, origin->path)) { + porigin = get_origin(parent, p->one->path); + oidcpy(&porigin->blob_oid, &p->one->oid); + porigin->mode = p->one->mode; + break; + } + } + diff_flush(&diff_opts); + clear_pathspec(&diff_opts.pathspec); + return porigin; +} + +/* + * Append a new blame entry to a given output queue. + */ +static void add_blame_entry(struct blame_entry ***queue, + const struct blame_entry *src) +{ + struct blame_entry *e = xmalloc(sizeof(*e)); + memcpy(e, src, sizeof(*e)); + blame_origin_incref(e->suspect); + + 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 gets added to the given queue. The + * origin of dst loses a refcnt. + */ +static void dup_entry(struct blame_entry ***queue, + struct blame_entry *dst, struct blame_entry *src) +{ + blame_origin_incref(src->suspect); + blame_origin_decref(dst->suspect); + memcpy(dst, src, sizeof(*src)); + dst->next = **queue; + **queue = dst; + *queue = &dst->next; +} + +const char *blame_nth_line(struct blame_scoreboard *sb, long lno) +{ + return sb->final_buf + sb->lineno[lno]; +} + +/* + * It is known that lines between tlno to same came from parent, and e + * has an overlap with that range. it also is known that parent's + * line plno corresponds to e's line tlno. + * + * <---- e -----> + * <------> + * <------------> + * <------------> + * <------------------> + * + * Split e into potentially three parts; before this chunk, the chunk + * to be blamed for the parent, and after that portion. + */ +static void split_overlap(struct blame_entry *split, + struct blame_entry *e, + int tlno, int plno, int same, + struct blame_origin *parent) +{ + int chunk_end_lno; + memset(split, 0, sizeof(struct blame_entry [3])); + + if (e->s_lno < tlno) { + /* there is a pre-chunk part not blamed on parent */ + split[0].suspect = blame_origin_incref(e->suspect); + split[0].lno = e->lno; + split[0].s_lno = e->s_lno; + split[0].num_lines = tlno - e->s_lno; + split[1].lno = e->lno + tlno - e->s_lno; + split[1].s_lno = plno; + } + else { + split[1].lno = e->lno; + split[1].s_lno = plno + (e->s_lno - tlno); + } + + if (same < e->s_lno + e->num_lines) { + /* there is a post-chunk part not blamed on parent */ + split[2].suspect = blame_origin_incref(e->suspect); + split[2].lno = e->lno + (same - e->s_lno); + split[2].s_lno = e->s_lno + (same - e->s_lno); + split[2].num_lines = e->s_lno + e->num_lines - same; + chunk_end_lno = split[2].lno; + } + else + chunk_end_lno = e->lno + e->num_lines; + split[1].num_lines = chunk_end_lno - split[1].lno; + + /* + * if it turns out there is nothing to blame the parent for, + * forget about the splitting. !split[1].suspect signals this. + */ + if (split[1].num_lines < 1) + return; + split[1].suspect = blame_origin_incref(parent); +} + +/* + * split_overlap() divided an existing blame e into up to three parts + * in split. Any assigned blame is moved to queue to + * reflect the split. + */ +static void split_blame(struct blame_entry ***blamed, + struct blame_entry ***unblamed, + struct blame_entry *split, + struct blame_entry *e) +{ + if (split[0].suspect && split[2].suspect) { + /* The first part (reuse storage for the existing entry e) */ + dup_entry(unblamed, e, &split[0]); + + /* The last part -- me */ + add_blame_entry(unblamed, &split[2]); + + /* ... and the middle part -- parent */ + add_blame_entry(blamed, &split[1]); + } + 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(blamed, e, &split[1]); + else if (split[0].suspect) { + /* me and then parent */ + dup_entry(unblamed, e, &split[0]); + add_blame_entry(blamed, &split[1]); + } + else { + /* parent and then me */ + dup_entry(blamed, e, &split[1]); + add_blame_entry(unblamed, &split[2]); + } +} + +/* + * After splitting the blame, the origins used by the + * on-stack blame_entry should lose one refcnt each. + */ +static void decref_split(struct blame_entry *split) +{ + int i; + + for (i = 0; i < 3; i++) + blame_origin_decref(split[i].suspect); +} + +/* + * 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 struct blame_entry *reverse_blame(struct blame_entry *head, + struct blame_entry *tail) +{ + while (head) { + struct blame_entry *next = head->next; + head->next = tail; + tail = head; + head = next; + } + return tail; +} + +/* + * Process one hunk from the patch between the current suspect for + * 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 blame_entry ***dstq, struct blame_entry ***srcq, + int tlno, int offset, int same, + struct blame_origin *parent) +{ + struct blame_entry *e = **srcq; + struct blame_entry *samep = NULL, *diffp = NULL; + + 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 + blame_origin_decref(e->suspect); + /* Pass blame for everything before the differing + * chunk to the parent */ + e->suspect = blame_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 = blame_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 blame_origin *parent; + 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; + 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; +} + +/* + * We are looking at the origin 'target' and aiming to pass blame + * for the lines it is suspected to its parent. Run diff to find + * which lines came from parent and pass blame for them. + */ +static void pass_blame_to_parent(struct blame_scoreboard *sb, + struct blame_origin *target, + struct blame_origin *parent) +{ + mmfile_t file_p, file_o; + struct blame_chunk_cb_data d; + struct blame_entry *newdest = NULL; + + 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, &sb->num_read_blob); + fill_origin_blob(&sb->revs->diffopt, target, &file_o, &sb->num_read_blob); + sb->num_get_patch++; + + if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, sb->xdl_opts)) + die("unable to generate diff (%s -> %s)", + oid_to_hex(&parent->commit->object.oid), + oid_to_hex(&target->commit->object.oid)); + /* 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; +} + +/* + * The lines in blame_entry after splitting blames many times can become + * very small and trivial, and at some point it becomes pointless to + * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any + * ordinary C program, and it is not worth to say it was copied from + * totally unrelated file in the parent. + * + * Compute how trivial the lines in the blame_entry are. + */ +unsigned blame_entry_score(struct blame_scoreboard *sb, struct blame_entry *e) +{ + unsigned score; + const char *cp, *ep; + + if (e->score) + return e->score; + + score = 1; + cp = blame_nth_line(sb, e->lno); + ep = blame_nth_line(sb, e->lno + e->num_lines); + while (cp < ep) { + unsigned ch = *((unsigned char *)cp); + if (isalnum(ch)) + score++; + cp++; + } + e->score = score; + return score; +} + +/* + * best_so_far[] and this[] are both a split of an existing blame_entry + * that passes blame to the parent. Maintain best_so_far the best split + * so far, by comparing this and best_so_far and copying this into + * bst_so_far as needed. + */ +static void copy_split_if_better(struct blame_scoreboard *sb, + struct blame_entry *best_so_far, + struct blame_entry *this) +{ + int i; + + if (!this[1].suspect) + return; + if (best_so_far[1].suspect) { + if (blame_entry_score(sb, &this[1]) < blame_entry_score(sb, &best_so_far[1])) + return; + } + + for (i = 0; i < 3; i++) + blame_origin_incref(this[i].suspect); + decref_split(best_so_far); + memcpy(best_so_far, this, sizeof(struct blame_entry [3])); +} + +/* + * We are looking at a part of the final image represented by + * ent (tlno and same are offset by ent->s_lno). + * tlno is where we are looking at in the final image. + * up to (but not including) same match preimage. + * plno is where we are looking at in the preimage. + * + * <-------------- final image ----------------------> + * <------ent------> + * ^tlno ^same + * <---------preimage-----> + * ^plno + * + * All line numbers are 0-based. + */ +static void handle_split(struct blame_scoreboard *sb, + struct blame_entry *ent, + int tlno, int plno, int same, + struct blame_origin *parent, + struct blame_entry *split) +{ + if (ent->num_lines <= tlno) + return; + if (tlno < same) { + struct blame_entry this[3]; + tlno += ent->s_lno; + same += ent->s_lno; + split_overlap(this, ent, tlno, plno, same, parent); + copy_split_if_better(sb, split, this); + decref_split(this); + } +} + +struct handle_split_cb_data { + struct blame_scoreboard *sb; + struct blame_entry *ent; + struct blame_origin *parent; + struct blame_entry *split; + long plno; + long tlno; +}; + +static int handle_split_cb(long start_a, long count_a, + long start_b, long count_b, void *data) +{ + struct handle_split_cb_data *d = data; + handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent, + d->split); + d->plno = start_a + count_a; + d->tlno = start_b + count_b; + return 0; +} + +/* + * Find the lines from parent that are the same as ent so that + * we can pass blames to it. file_p has the blob contents for + * the parent. + */ +static void find_copy_in_blob(struct blame_scoreboard *sb, + struct blame_entry *ent, + struct blame_origin *parent, + struct blame_entry *split, + mmfile_t *file_p) +{ + const char *cp; + mmfile_t file_o; + struct handle_split_cb_data d; + + memset(&d, 0, sizeof(d)); + d.sb = sb; d.ent = ent; d.parent = parent; d.split = split; + /* + * Prepare mmfile that contains only the lines in ent. + */ + cp = blame_nth_line(sb, ent->lno); + file_o.ptr = (char *) cp; + file_o.size = blame_nth_line(sb, ent->lno + ent->num_lines) - cp; + + /* + * file_o is a part of final image we are annotating. + * file_p partially may match that image. + */ + memset(split, 0, sizeof(struct blame_entry [3])); + if (diff_hunks(file_p, &file_o, handle_split_cb, &d, sb->xdl_opts)) + die("unable to generate diff (%s)", + oid_to_hex(&parent->commit->object.oid)); + /* remainder, if any, all match the preimage */ + 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 blame_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 (blame_entry_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 void find_move_in_parent(struct blame_scoreboard *sb, + struct blame_entry ***blamed, + struct blame_entry **toosmall, + struct blame_origin *target, + struct blame_origin *parent) +{ + struct blame_entry *e, split[3]; + struct blame_entry *unblamed = target->suspects; + struct blame_entry *leftover = NULL; + mmfile_t file_p; + + if (!unblamed) + return; /* nothing remains for this target */ + + fill_origin_blob(&sb->revs->diffopt, parent, &file_p, &sb->num_read_blob); + if (!file_p.ptr) + return; + + /* 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 && + sb->move_score < blame_entry_score(sb, &split[1])) { + split_blame(blamed, &unblamedtail, split, e); + } else { + e->next = leftover; + leftover = e; + } + decref_split(split); + } + *unblamedtail = NULL; + toosmall = filter_small(sb, toosmall, &unblamed, sb->move_score); + } while (unblamed); + target->suspects = reverse_blame(leftover, NULL); +} + +struct blame_list { + struct blame_entry *ent; + struct blame_entry split[3]; +}; + +/* + * 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 blame_entry *unblamed, + int *num_ents_p) +{ + struct blame_entry *e; + int num_ents, i; + struct blame_list *blame_list = NULL; + + 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 = unblamed, i = 0; e; e = e->next) + blame_list[i++].ent = e; + } + *num_ents_p = num_ents; + return blame_list; +} + +/* + * 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 void find_copy_in_parent(struct blame_scoreboard *sb, + struct blame_entry ***blamed, + struct blame_entry **toosmall, + struct blame_origin *target, + struct commit *parent, + struct blame_origin *porigin, + int opt) +{ + struct diff_options diff_opts; + int i, j; + struct blame_list *blame_list; + int num_ents; + struct blame_entry *unblamed = target->suspects; + struct blame_entry *leftover = NULL; + + if (!unblamed) + return; /* nothing remains for this target */ + + diff_setup(&diff_opts); + DIFF_OPT_SET(&diff_opts, RECURSIVE); + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + + diff_setup_done(&diff_opts); + + /* Try "find copies harder" on new path if requested; + * we do not want to use diffcore_rename() actually to + * match things up; find_copies_harder is set only to + * force diff_tree_oid() to feed all filepairs to diff_queue, + * and this code needs to be after diff_setup_done(), which + * usually makes find-copies-harder imply copy detection. + */ + if ((opt & PICKAXE_BLAME_COPY_HARDEST) + || ((opt & PICKAXE_BLAME_COPY_HARDER) + && (!porigin || strcmp(target->path, porigin->path)))) + DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); + + if (is_null_oid(&target->commit->object.oid)) + do_diff_cache(&parent->tree->object.oid, &diff_opts); + else + diff_tree_oid(&parent->tree->object.oid, + &target->commit->tree->object.oid, + "", &diff_opts); + + if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) + diffcore_std(&diff_opts); + + 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]; + struct blame_origin *norigin; + mmfile_t file_p; + struct blame_entry this[3]; + + if (!DIFF_FILE_VALID(p->one)) + continue; /* does not exist in parent */ + if (S_ISGITLINK(p->one->mode)) + continue; /* ignore git links */ + if (porigin && !strcmp(p->one->path, porigin->path)) + /* find_move already dealt with this path */ + continue; + + norigin = get_origin(parent, p->one->path); + oidcpy(&norigin->blob_oid, &p->one->oid); + norigin->mode = p->one->mode; + fill_origin_blob(&sb->revs->diffopt, norigin, &file_p, &sb->num_read_blob); + if (!file_p.ptr) + continue; + + for (j = 0; j < num_ents; j++) { + find_copy_in_blob(sb, blame_list[j].ent, + norigin, this, &file_p); + copy_split_if_better(sb, blame_list[j].split, + this); + decref_split(this); + } + blame_origin_decref(norigin); + } + + for (j = 0; j < num_ents; j++) { + struct blame_entry *split = blame_list[j].split; + if (split[1].suspect && + sb->copy_score < blame_entry_score(sb, &split[1])) { + split_blame(blamed, &unblamedtail, split, + blame_list[j].ent); + } else { + blame_list[j].ent->next = leftover; + leftover = blame_list[j].ent; + } + decref_split(split); + } + free(blame_list); + *unblamedtail = NULL; + toosmall = filter_small(sb, toosmall, &unblamed, sb->copy_score); + } while (unblamed); + target->suspects = reverse_blame(leftover, NULL); + diff_flush(&diff_opts); + clear_pathspec(&diff_opts.pathspec); +} + +/* + * The blobs of origin and porigin exactly match, so everything + * origin is suspected for can be blamed on the parent. + */ +static void pass_whole_blame(struct blame_scoreboard *sb, + struct blame_origin *origin, struct blame_origin *porigin) +{ + struct blame_entry *e, *suspects; + + if (!porigin->file.ptr && origin->file.ptr) { + /* Steal its file */ + porigin->file = origin->file; + origin->file.ptr = NULL; + } + suspects = origin->suspects; + origin->suspects = NULL; + for (e = suspects; e; e = e->next) { + blame_origin_incref(porigin); + blame_origin_decref(e->suspect); + e->suspect = porigin; + } + queue_blames(sb, porigin, suspects); +} + +/* + * We pass blame from the current commit to its parents. We keep saying + * "parent" (and "porigin"), but what we mean is to find scapegoat to + * exonerate ourselves. + */ +static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit, + int reverse) +{ + if (!reverse) { + if (revs->first_parent_only && + commit->parents && + commit->parents->next) { + free_commit_list(commit->parents->next); + commit->parents->next = NULL; + } + return commit->parents; + } + return lookup_decoration(&revs->children, &commit->object); +} + +static int num_scapegoats(struct rev_info *revs, struct commit *commit, int reverse) +{ + struct commit_list *l = first_scapegoat(revs, commit, reverse); + return commit_list_count(l); +} + +/* Distribute collected unsorted blames to the respected sorted lists + * in the various origins. + */ +static void distribute_blame(struct blame_scoreboard *sb, struct blame_entry *blamed) +{ + blamed = llist_mergesort(blamed, get_next_blame, set_next_blame, + compare_blame_suspect); + while (blamed) + { + struct blame_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 + +static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin, int opt) +{ + struct rev_info *revs = sb->revs; + int i, pass, num_sg; + struct commit *commit = origin->commit; + struct commit_list *sg; + struct blame_origin *sg_buf[MAXSG]; + struct blame_origin *porigin, **sg_origin = sg_buf; + struct blame_entry *toosmall = NULL; + struct blame_entry *blames, **blametail = &blames; + + num_sg = num_scapegoats(revs, commit, sb->reverse); + if (!num_sg) + goto finish; + else if (num_sg < ARRAY_SIZE(sg_buf)) + memset(sg_buf, 0, sizeof(sg_buf)); + else + sg_origin = xcalloc(num_sg, sizeof(*sg_origin)); + + /* + * The first pass looks for unrenamed path to optimize for + * common cases, then we look for renames in the second pass. + */ + for (pass = 0; pass < 2 - sb->no_whole_file_rename; pass++) { + struct blame_origin *(*find)(struct commit *, struct blame_origin *); + find = pass ? find_rename : find_origin; + + for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse); + i < num_sg && sg; + sg = sg->next, i++) { + struct commit *p = sg->item; + int j, same; + + if (sg_origin[i]) + continue; + if (parse_commit(p)) + continue; + porigin = find(p, origin); + if (!porigin) + continue; + if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) { + pass_whole_blame(sb, origin, porigin); + blame_origin_decref(porigin); + goto finish; + } + for (j = same = 0; j < i; j++) + if (sg_origin[j] && + !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) { + same = 1; + break; + } + if (!same) + sg_origin[i] = porigin; + else + blame_origin_decref(porigin); + } + } + + sb->num_commits++; + for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse); + i < num_sg && sg; + sg = sg->next, i++) { + struct blame_origin *porigin = sg_origin[i]; + if (!porigin) + continue; + if (!origin->previous) { + blame_origin_incref(porigin); + origin->previous = porigin; + } + pass_blame_to_parent(sb, origin, porigin); + if (!origin->suspects) + goto finish; + } + + /* + * Optionally find moves in parents' files. + */ + if (opt & PICKAXE_BLAME_MOVE) { + filter_small(sb, &toosmall, &origin->suspects, sb->move_score); + if (origin->suspects) { + for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse); + i < num_sg && sg; + sg = sg->next, i++) { + struct blame_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 (sb->copy_score > sb->move_score) + filter_small(sb, &toosmall, &origin->suspects, sb->copy_score); + else if (sb->copy_score < sb->move_score) { + origin->suspects = blame_merge(origin->suspects, toosmall); + toosmall = NULL; + filter_small(sb, &toosmall, &origin->suspects, sb->copy_score); + } + if (!origin->suspects) + goto finish; + + for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse); + i < num_sg && sg; + sg = sg->next, i++) { + struct blame_origin *porigin = sg_origin[i]; + find_copy_in_parent(sb, &blametail, &toosmall, + origin, sg->item, porigin, opt); + if (!origin->suspects) + goto 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]); + blame_origin_decref(sg_origin[i]); + } + } + drop_origin_blob(origin); + if (sg_buf != sg_origin) + free(sg_origin); +} + +/* + * 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. */ +void assign_blame(struct blame_scoreboard *sb, int opt) +{ + struct rev_info *revs = sb->revs; + struct commit *commit = prio_queue_get(&sb->commits); + + while (commit) { + struct blame_entry *ent; + struct blame_origin *suspect = commit->util; + + /* find one suspect to break down */ + 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. + */ + blame_origin_incref(suspect); + parse_commit(commit); + if (sb->reverse || + (!(commit->object.flags & UNINTERESTING) && + !(revs->max_age != -1 && commit->date < revs->max_age))) + pass_blame(sb, suspect, opt); + else { + commit->object.flags |= UNINTERESTING; + if (commit->object.parsed) + mark_parents_uninteresting(commit); + } + /* treat root commit as boundary */ + if (!commit->parents && !sb->show_root) + commit->object.flags |= UNINTERESTING; + + /* Take responsibility for the remaining entries */ + ent = suspect->suspects; + if (ent) { + suspect->guilty = 1; + for (;;) { + struct blame_entry *next = ent->next; + if (sb->found_guilty_entry) + sb->found_guilty_entry(ent, sb->found_guilty_entry_data); + if (next) { + ent = next; + continue; + } + ent->next = sb->ent; + sb->ent = suspect->suspects; + suspect->suspects = NULL; + break; + } + } + blame_origin_decref(suspect); + + if (sb->debug) /* sanity */ + sanity_check_refcnt(sb); + } +} + +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. + */ +static int prepare_lines(struct blame_scoreboard *sb) +{ + const char *buf = sb->final_buf; + unsigned long len = sb->final_buf_size; + const char *end = buf + len; + const char *p; + int *lineno; + int num = 0; + + for (p = buf; p < end; p = get_next_line(p, end)) + num++; + + ALLOC_ARRAY(sb->lineno, num + 1); + lineno = sb->lineno; + + for (p = buf; p < end; p = get_next_line(p, end)) + *lineno++ = p - buf; + + *lineno = len; + + sb->num_lines = num; + return sb->num_lines; +} + +static struct commit *find_single_final(struct rev_info *revs, + const char **name_p) +{ + int i; + struct commit *found = NULL; + const char *name = NULL; + + for (i = 0; i < revs->pending.nr; i++) { + struct object *obj = revs->pending.objects[i].item; + if (obj->flags & UNINTERESTING) + continue; + obj = deref_tag(obj, NULL, 0); + if (obj->type != OBJ_COMMIT) + die("Non commit %s?", revs->pending.objects[i].name); + if (found) + die("More than one commit to dig from %s and %s?", + revs->pending.objects[i].name, name); + found = (struct commit *)obj; + name = revs->pending.objects[i].name; + } + if (name_p) + *name_p = name; + return found; +} + +static struct commit *dwim_reverse_initial(struct rev_info *revs, + const char **name_p) +{ + /* + * DWIM "git blame --reverse ONE -- PATH" as + * "git blame --reverse ONE..HEAD -- PATH" but only do so + * when it makes sense. + */ + struct object *obj; + struct commit *head_commit; + struct object_id head_oid; + + if (revs->pending.nr != 1) + return NULL; + + /* Is that sole rev a committish? */ + obj = revs->pending.objects[0].item; + obj = deref_tag(obj, NULL, 0); + if (obj->type != OBJ_COMMIT) + return NULL; + + /* Do we have HEAD? */ + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) + return NULL; + head_commit = lookup_commit_reference_gently(&head_oid, 1); + if (!head_commit) + return NULL; + + /* Turn "ONE" into "ONE..HEAD" then */ + obj->flags |= UNINTERESTING; + add_pending_object(revs, &head_commit->object, "HEAD"); + + if (name_p) + *name_p = revs->pending.objects[0].name; + return (struct commit *)obj; +} + +static struct commit *find_single_initial(struct rev_info *revs, + const char **name_p) +{ + int i; + struct commit *found = NULL; + const char *name = NULL; + + /* + * There must be one and only one negative commit, and it must be + * the boundary. + */ + for (i = 0; i < revs->pending.nr; i++) { + struct object *obj = revs->pending.objects[i].item; + if (!(obj->flags & UNINTERESTING)) + continue; + obj = deref_tag(obj, NULL, 0); + if (obj->type != OBJ_COMMIT) + die("Non commit %s?", revs->pending.objects[i].name); + if (found) + die("More than one commit to dig up from, %s and %s?", + revs->pending.objects[i].name, name); + found = (struct commit *) obj; + name = revs->pending.objects[i].name; + } + + if (!name) + found = dwim_reverse_initial(revs, &name); + if (!name) + die("No commit to dig up from?"); + + if (name_p) + *name_p = name; + return found; +} + +void init_scoreboard(struct blame_scoreboard *sb) +{ + memset(sb, 0, sizeof(struct blame_scoreboard)); + sb->move_score = BLAME_DEFAULT_MOVE_SCORE; + sb->copy_score = BLAME_DEFAULT_COPY_SCORE; +} + +void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blame_origin **orig) +{ + const char *final_commit_name = NULL; + struct blame_origin *o; + struct commit *final_commit = NULL; + enum object_type type; + + if (sb->reverse && sb->contents_from) + die(_("--contents and --reverse do not blend well.")); + + if (!sb->reverse) { + sb->final = find_single_final(sb->revs, &final_commit_name); + sb->commits.compare = compare_commits_by_commit_date; + } else { + sb->final = find_single_initial(sb->revs, &final_commit_name); + sb->commits.compare = compare_commits_by_reverse_commit_date; + } + + if (sb->final && sb->contents_from) + die(_("cannot use --contents with final commit object name")); + + if (sb->reverse && sb->revs->first_parent_only) + sb->revs->children.name = NULL; + + if (!sb->final) { + /* + * "--not A B -- path" without anything positive; + * do not default to HEAD, but use the working tree + * or "--contents". + */ + setup_work_tree(); + sb->final = fake_working_tree_commit(&sb->revs->diffopt, + path, sb->contents_from); + add_pending_object(sb->revs, &(sb->final->object), ":"); + } + + if (sb->reverse && sb->revs->first_parent_only) { + final_commit = find_single_final(sb->revs, NULL); + if (!final_commit) + die(_("--reverse and --first-parent together require specified latest commit")); + } + + /* + * If we have bottom, this will mark the ancestors of the + * bottom commits we would reach while traversing as + * uninteresting. + */ + if (prepare_revision_walk(sb->revs)) + die(_("revision walk setup failed")); + + if (sb->reverse && sb->revs->first_parent_only) { + struct commit *c = final_commit; + + sb->revs->children.name = "children"; + while (c->parents && + oidcmp(&c->object.oid, &sb->final->object.oid)) { + struct commit_list *l = xcalloc(1, sizeof(*l)); + + l->item = c; + if (add_decoration(&sb->revs->children, + &c->parents->item->object, l)) + die("BUG: not unique item in first-parent chain"); + c = c->parents->item; + } + + if (oidcmp(&c->object.oid, &sb->final->object.oid)) + die(_("--reverse --first-parent together require range along first-parent chain")); + } + + if (is_null_oid(&sb->final->object.oid)) { + o = sb->final->util; + sb->final_buf = xmemdupz(o->file.ptr, o->file.size); + sb->final_buf_size = o->file.size; + } + else { + o = get_origin(sb->final, path); + if (fill_blob_sha1_and_mode(o)) + die(_("no such path %s in %s"), path, final_commit_name); + + if (DIFF_OPT_TST(&sb->revs->diffopt, ALLOW_TEXTCONV) && + textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb->final_buf, + &sb->final_buf_size)) + ; + else + sb->final_buf = read_sha1_file(o->blob_oid.hash, &type, + &sb->final_buf_size); + + if (!sb->final_buf) + die(_("cannot read blob %s for path %s"), + oid_to_hex(&o->blob_oid), + path); + } + sb->num_read_blob++; + prepare_lines(sb); + + if (orig) + *orig = o; +} + + + +struct blame_entry *blame_entry_prepend(struct blame_entry *head, + long start, long end, + struct blame_origin *o) +{ + struct blame_entry *new_head = xcalloc(1, sizeof(struct blame_entry)); + new_head->lno = start; + new_head->num_lines = end - start; + new_head->suspect = o; + new_head->s_lno = start; + new_head->next = head; + blame_origin_incref(o); + return new_head; +} diff --git a/blame.h b/blame.h new file mode 100644 index 0000000000..a6c915c277 --- /dev/null +++ b/blame.h @@ -0,0 +1,175 @@ +#ifndef BLAME_H +#define BLAME_H + +#include "cache.h" +#include "commit.h" +#include "xdiff-interface.h" +#include "revision.h" +#include "prio-queue.h" +#include "diff.h" + +#define PICKAXE_BLAME_MOVE 01 +#define PICKAXE_BLAME_COPY 02 +#define PICKAXE_BLAME_COPY_HARDER 04 +#define PICKAXE_BLAME_COPY_HARDEST 010 + +#define BLAME_DEFAULT_MOVE_SCORE 20 +#define BLAME_DEFAULT_COPY_SCORE 40 + +/* + * One blob in a commit that is being suspected + */ +struct blame_origin { + int refcnt; + /* Record preceding blame record for this blob */ + struct blame_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 blame_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; + struct object_id blob_oid; + unsigned mode; + /* guilty gets set when shipping any suspects to the final + * blame list instead of other commits + */ + char guilty; + char path[FLEX_ARRAY]; +}; + +/* + * Each group of lines is described by a blame_entry; it can be split + * 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; + + /* the first line of this group in the final image; + * internally all line numbers are 0 based. + */ + int lno; + + /* how many lines this group has */ + int num_lines; + + /* the commit that introduced this group into the final image */ + struct blame_origin *suspect; + + /* the line number of the first line of this group in the + * suspect's file; internally all line numbers are 0 based. + */ + int s_lno; + + /* how significant this entry is -- cached to avoid + * scanning the lines over and over. + */ + unsigned score; +}; + +/* + * The current state of the blame assignment. + */ +struct blame_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; + + /* + * The contents in the final image. + * Used by many functions to obtain contents of the nth line, + * indexed with scoreboard.lineno[blame_entry.lno]. + */ + const char *final_buf; + unsigned long final_buf_size; + + /* linked list of blames */ + struct blame_entry *ent; + + /* look-up a line in the final buffer */ + int num_lines; + int *lineno; + + /* stats */ + int num_read_blob; + int num_get_patch; + int num_commits; + + /* + * blame for a blame_entry with score lower than these thresholds + * is not passed to the parent using move/copy logic. + */ + unsigned move_score; + unsigned copy_score; + + /* use this file's contents as the final image */ + const char *contents_from; + + /* flags */ + int reverse; + int show_root; + int xdl_opts; + int no_whole_file_rename; + int debug; + + /* callbacks */ + void(*on_sanity_fail)(struct blame_scoreboard *, int); + void(*found_guilty_entry)(struct blame_entry *, void *); + + void *found_guilty_entry_data; +}; + +/* + * Origin is refcounted and usually we keep the blob contents to be + * reused. + */ +static inline struct blame_origin *blame_origin_incref(struct blame_origin *o) +{ + if (o) + o->refcnt++; + return o; +} +extern void blame_origin_decref(struct blame_origin *o); + +extern void blame_coalesce(struct blame_scoreboard *sb); +extern void blame_sort_final(struct blame_scoreboard *sb); +extern unsigned blame_entry_score(struct blame_scoreboard *sb, struct blame_entry *e); +extern void assign_blame(struct blame_scoreboard *sb, int opt); +extern const char *blame_nth_line(struct blame_scoreboard *sb, long lno); + +extern void init_scoreboard(struct blame_scoreboard *sb); +extern void setup_scoreboard(struct blame_scoreboard *sb, const char *path, struct blame_origin **orig); + +extern struct blame_entry *blame_entry_prepend(struct blame_entry *head, long start, long end, struct blame_origin *o); + +#endif /* BLAME_H */ @@ -1,5 +1,6 @@ #include "git-compat-util.h" #include "cache.h" +#include "config.h" #include "branch.h" #include "refs.h" #include "remote.h" @@ -24,8 +25,7 @@ static int find_tracked_branch(struct remote *remote, void *priv) } else { free(tracking->spec.src); if (tracking->src) { - free(tracking->src); - tracking->src = NULL; + FREE_AND_NULL(tracking->src); } } tracking->spec.src = NULL; @@ -25,8 +25,6 @@ struct fmt_merge_msg_opts { extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out, struct fmt_merge_msg_opts *); -extern int textconv_object(const char *path, unsigned mode, const struct object_id *oid, int oid_valid, char **buf, unsigned long *buf_size); - extern int is_builtin(const char *s); extern int cmd_add(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index 9f53f020d0..e888fb8c5f 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -4,6 +4,7 @@ * Copyright (C) 2006 Linus Torvalds */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "lockfile.h" #include "dir.h" @@ -17,6 +18,7 @@ #include "revision.h" #include "bulk-checkin.h" #include "argv-array.h" +#include "submodule.h" static const char * const builtin_add_usage[] = { N_("git add [<options>] [--] <pathspec>..."), @@ -135,7 +137,7 @@ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, *dst++ = entry; } dir->nr = dst - dir->entries; - add_pathspec_matches_against_index(pathspec, seen); + add_pathspec_matches_against_index(pathspec, &the_index, seen); return seen; } @@ -248,6 +250,7 @@ N_("The following paths are ignored by one of your .gitignore files:\n"); static int verbose, show_only, ignored_too, refresh_only; static int ignore_add_errors, intent_to_add, ignore_missing; +static int warn_on_embedded_repo = 1; #define ADDREMOVE_DEFAULT 1 static int addremove = ADDREMOVE_DEFAULT; @@ -281,6 +284,8 @@ static struct option builtin_add_options[] = { OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), OPT_STRING( 0 , "chmod", &chmod_arg, N_("(+/-)x"), N_("override the executable bit of the listed files")), + OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, + N_("warn when adding an embedded repository")), OPT_END(), }; @@ -294,6 +299,45 @@ static int add_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static const char embedded_advice[] = N_( +"You've added another git repository inside your current repository.\n" +"Clones of the outer repository will not contain the contents of\n" +"the embedded repository and will not know how to obtain it.\n" +"If you meant to add a submodule, use:\n" +"\n" +" git submodule add <url> %s\n" +"\n" +"If you added this path by mistake, you can remove it from the\n" +"index with:\n" +"\n" +" git rm --cached %s\n" +"\n" +"See \"git help submodule\" for more information." +); + +static void check_embedded_repo(const char *path) +{ + struct strbuf name = STRBUF_INIT; + + if (!warn_on_embedded_repo) + return; + if (!ends_with(path, "/")) + return; + + /* Drop trailing slash for aesthetics */ + strbuf_addstr(&name, path); + strbuf_strip_suffix(&name, "/"); + + warning(_("adding embedded git repository: %s"), name.buf); + if (advice_add_embedded_repo) { + advise(embedded_advice, name.buf, name.buf); + /* there may be multiple entries; advise only once */ + advice_add_embedded_repo = 0; + } + + strbuf_release(&name); +} + static int add_files(struct dir_struct *dir, int flags) { int i, exit_status = 0; @@ -306,12 +350,14 @@ static int add_files(struct dir_struct *dir, int flags) exit_status = 1; } - for (i = 0; i < dir->nr; i++) + for (i = 0; i < dir->nr; i++) { + check_embedded_repo(dir->entries[i]->name); if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) { if (!ignore_add_errors) die(_("adding files failed")); exit_status = 1; } + } return exit_status; } @@ -379,16 +425,19 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); + die_in_unpopulated_submodule(&the_index, prefix); + /* * Check the "pathspec '%s' did not match any files" block * below before enabling new magic. */ parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_FULL | - PATHSPEC_SYMLINK_LEADING_PATH | - PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE, + PATHSPEC_SYMLINK_LEADING_PATH, prefix, argv); + die_path_inside_submodule(&the_index, &pathspec); + if (add_new_files) { int baselen; @@ -400,7 +449,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) } /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, &pathspec); + baselen = fill_directory(&dir, &the_index, &pathspec); if (pathspec.nr) seen = prune_directory(&dir, &pathspec, baselen); } @@ -414,7 +463,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) int i; if (!seen) - seen = find_pathspecs_matching_against_index(&pathspec); + seen = find_pathspecs_matching_against_index(&pathspec, &the_index); /* * file_exists() assumes exact match @@ -436,8 +485,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) !file_exists(path))) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (is_excluded(&dir, path, &dtype)) - dir_add_ignored(&dir, path, pathspec.items[i].len); + if (is_excluded(&dir, &the_index, path, &dtype)) + dir_add_ignored(&dir, &the_index, + path, pathspec.items[i].len); } else die(_("pathspec '%s' did not match any files"), pathspec.items[i].original); diff --git a/builtin/am.c b/builtin/am.c index 2ec1d7b71e..c973bd96dc 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -4,6 +4,7 @@ * Based on git-am.sh by Junio C Hamano. */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" @@ -483,8 +484,7 @@ static int run_applypatch_msg_hook(struct am_state *state) ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL); if (!ret) { - free(state->msg); - state->msg = NULL; + FREE_AND_NULL(state->msg); if (read_commit_msg(state) < 0) die(_("'%s' was deleted by the applypatch-msg hook"), am_path(state, "final-commit")); @@ -563,7 +563,7 @@ static int copy_notes_for_rebase(const struct am_state *state) goto finish; } - if (copy_note_for_rewrite(c, from_obj.hash, to_obj.hash)) + if (copy_note_for_rewrite(c, &from_obj, &to_obj)) ret = error(_("Failed to copy notes from '%s' to '%s'"), oid_to_hex(&from_obj), oid_to_hex(&to_obj)); } @@ -1073,17 +1073,10 @@ static void am_next(struct am_state *state) { struct object_id head; - free(state->author_name); - state->author_name = NULL; - - free(state->author_email); - state->author_email = NULL; - - free(state->author_date); - state->author_date = NULL; - - free(state->msg); - state->msg = NULL; + FREE_AND_NULL(state->author_name); + FREE_AND_NULL(state->author_email); + FREE_AND_NULL(state->author_date); + FREE_AND_NULL(state->msg); state->msg_len = 0; unlink(am_path(state, "author-script")); @@ -1275,12 +1268,8 @@ static int parse_mail(struct am_state *state, const char *mail) die("BUG: invalid value for state->scissors"); } - mi.input = fopen(mail, "r"); - if (!mi.input) - die("could not open input"); - mi.output = fopen(am_path(state, "info"), "w"); - if (!mi.output) - die("could not open output 'info'"); + mi.input = xfopen(mail, "r"); + mi.output = xfopen(am_path(state, "info"), "w"); if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch"))) die("could not parse patch"); @@ -1312,7 +1301,7 @@ static int parse_mail(struct am_state *state, const char *mail) } if (is_empty_file(am_path(state, "patch"))) { - printf_ln(_("Patch is empty. Was it split wrong?")); + printf_ln(_("Patch is empty.")); die_user_resolve(state); } @@ -1351,19 +1340,16 @@ static int get_mail_commit_oid(struct object_id *commit_id, const char *mail) struct strbuf sb = STRBUF_INIT; FILE *fp = xfopen(mail, "r"); const char *x; + int ret = 0; - if (strbuf_getline_lf(&sb, fp)) - return -1; - - if (!skip_prefix(sb.buf, "From ", &x)) - return -1; - - if (get_oid_hex(x, commit_id) < 0) - return -1; + if (strbuf_getline_lf(&sb, fp) || + !skip_prefix(sb.buf, "From ", &x) || + get_oid_hex(x, commit_id) < 0) + ret = -1; strbuf_release(&sb); fclose(fp); - return 0; + return ret; } /** @@ -1934,7 +1920,8 @@ static void am_resolve(struct am_state *state) if (unmerged_cache()) { printf_ln(_("You still have unmerged paths in your index.\n" - "Did you forget to use 'git add'?")); + "You should 'git add' each file with resolved conflicts to mark them as such.\n" + "You might run `git rm` on a file to accept \"deleted by them\" for it.")); die_user_resolve(state); } @@ -2313,6 +2300,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_END() }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(usage, options); + git_config(git_am_config, NULL); am_state_init(&state); diff --git a/builtin/blame.c b/builtin/blame.c index 1043e5376f..bda1a78726 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -6,21 +6,14 @@ */ #include "cache.h" -#include "refs.h" +#include "config.h" #include "builtin.h" -#include "blob.h" #include "commit.h" -#include "tag.h" -#include "tree-walk.h" #include "diff.h" -#include "diffcore.h" #include "revision.h" #include "quote.h" -#include "xdiff-interface.h" -#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" @@ -29,6 +22,7 @@ #include "line-log.h" #include "dir.h" #include "progress.h" +#include "blame.h" static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); @@ -62,1497 +56,21 @@ static struct string_list mailmap = STRING_LIST_INIT_NODUP; #define DEBUG 0 #endif -/* stats */ -static int num_read_blob; -static int num_get_patch; -static int num_commits; - -#define PICKAXE_BLAME_MOVE 01 -#define PICKAXE_BLAME_COPY 02 -#define PICKAXE_BLAME_COPY_HARDER 04 -#define PICKAXE_BLAME_COPY_HARDEST 010 - -/* - * blame for a blame_entry with score lower than these thresholds - * is not passed to the parent using move/copy logic. - */ static unsigned blame_move_score; static unsigned blame_copy_score; -#define BLAME_DEFAULT_MOVE_SCORE 20 -#define BLAME_DEFAULT_COPY_SCORE 40 /* Remember to update object flag allocation in object.h */ #define METAINFO_SHOWN (1u<<12) #define MORE_THAN_ONE_PATH (1u<<13) -/* - * One blob in a commit that is being suspected - */ -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; - struct object_id blob_oid; - unsigned mode; - /* guilty gets set when shipping any suspects to the final - * blame list instead of other commits - */ - char guilty; - char path[FLEX_ARRAY]; -}; - struct progress_info { struct progress *progress; int blamed_lines; }; -static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, - xdl_emit_hunk_consume_func_t hunk_func, void *cb_data) -{ - xpparam_t xpp = {0}; - xdemitconf_t xecfg = {0}; - xdemitcb_t ecb = {NULL}; - - xpp.flags = xdl_opts; - xecfg.hunk_func = hunk_func; - ecb.priv = cb_data; - return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb); -} - -/* - * Prepare diff_filespec and convert it using diff textconv API - * if the textconv driver exists. - * Return 1 if the conversion succeeds, 0 otherwise. - */ -int textconv_object(const char *path, - unsigned mode, - const struct object_id *oid, - int oid_valid, - char **buf, - unsigned long *buf_size) -{ - struct diff_filespec *df; - struct userdiff_driver *textconv; - - df = alloc_filespec(path); - fill_filespec(df, oid->hash, oid_valid, mode); - textconv = get_textconv(df); - if (!textconv) { - free_filespec(df); - return 0; - } - - *buf_size = fill_textconv(textconv, df, buf); - free_filespec(df); - return 1; -} - -/* - * Given an origin, prepare mmfile_t structure to be used by the - * diff machinery - */ -static void fill_origin_blob(struct diff_options *opt, - struct origin *o, mmfile_t *file) -{ - if (!o->file.ptr) { - enum object_type type; - unsigned long file_size; - - num_read_blob++; - if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size)) - ; - else - file->ptr = read_sha1_file(o->blob_oid.hash, &type, - &file_size); - file->size = file_size; - - if (!file->ptr) - die("Cannot read blob %s for path %s", - oid_to_hex(&o->blob_oid), - o->path); - o->file = *file; - } - else - *file = o->file; -} - -/* - * Origin is refcounted and usually we keep the blob contents to be - * reused. - */ -static inline struct origin *origin_incref(struct origin *o) -{ - if (o) - o->refcnt++; - return 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); - /* 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"); - } -} - -static void drop_origin_blob(struct origin *o) -{ - if (o->file.ptr) { - free(o->file.ptr); - o->file.ptr = NULL; - } -} - -/* - * Each group of lines is described by a blame_entry; it can be split - * 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; - - /* the first line of this group in the final image; - * internally all line numbers are 0 based. - */ - int lno; - - /* how many lines this group has */ - int num_lines; - - /* the commit that introduced this group into the final image */ - struct origin *suspect; - - /* the line number of the first line of this group in the - * suspect's file; internally all line numbers are 0 based. - */ - int s_lno; - - /* how significant this entry is -- cached to avoid - * scanning the lines over and over. - */ - unsigned score; -}; - -/* - * 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; - - /* - * The contents in the final image. - * Used by many functions to obtain contents of the nth line, - * indexed with scoreboard.lineno[blame_entry.lno]. - */ - const char *final_buf; - unsigned long final_buf_size; - - /* linked list of blames */ - struct blame_entry *ent; - - /* look-up a line in the final buffer */ - int num_lines; - int *lineno; -}; - -static void sanity_check_refcnt(struct scoreboard *); - -/* - * If two blame entries that are next to each other came from - * contiguous lines in the same origin (i.e. <commit, path> pair), - * merge them together. - */ -static void coalesce(struct scoreboard *sb) -{ - struct blame_entry *ent, *next; - - for (ent = sb->ent; ent && (next = ent->next); ent = next) { - if (ent->suspect == next->suspect && - ent->s_lno + ent->num_lines == next->s_lno) { - ent->num_lines += next->num_lines; - ent->next = next->next; - origin_decref(next->suspect); - free(next); - ent->score = 0; - next = ent; /* again */ - } - } - - if (DEBUG) /* sanity */ - sanity_check_refcnt(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 - * this function directly. - */ -static struct origin *make_origin(struct commit *commit, const char *path) -{ - struct origin *o; - FLEX_ALLOC_STR(o, path, path); - o->commit = commit; - o->refcnt = 1; - o->next = commit->util; - commit->util = o; - 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 origin *o, *l; - - 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); -} - -/* - * Fill the blob_sha1 field of an origin if it hasn't, so that later - * call to fill_origin_blob() can use it to locate the data. blob_sha1 - * for an origin is also used to pass the blame for the entire file to - * the parent to detect the case where a child's blob is identical to - * that of its parent's. - * - * This also fills origin->mode for corresponding tree path. - */ -static int fill_blob_sha1_and_mode(struct origin *origin) -{ - if (!is_null_oid(&origin->blob_oid)) - return 0; - if (get_tree_entry(origin->commit->object.oid.hash, - origin->path, - origin->blob_oid.hash, &origin->mode)) - goto error_out; - if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB) - goto error_out; - return 0; - error_out: - oidclr(&origin->blob_oid); - origin->mode = S_IFINVALID; - return -1; -} - -/* - * We have an origin -- check if the same path exists in the - * parent and return an origin structure to represent it. - */ -static struct origin *find_origin(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin; - struct diff_options diff_opts; - const char *paths[2]; - - /* 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. - */ - return origin_incref (porigin); - } - - /* See if the origin->path is different between parent - * and origin first. Most of the time they are the - * same and diff-tree is fairly efficient about this. - */ - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = 0; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - paths[0] = origin->path; - paths[1] = NULL; - - parse_pathspec(&diff_opts.pathspec, - PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, - PATHSPEC_LITERAL_PATH, "", paths); - diff_setup_done(&diff_opts); - - if (is_null_oid(&origin->commit->object.oid)) - do_diff_cache(&parent->tree->object.oid, &diff_opts); - else - diff_tree_sha1(parent->tree->object.oid.hash, - origin->commit->tree->object.oid.hash, - "", &diff_opts); - diffcore_std(&diff_opts); - - if (!diff_queued_diff.nr) { - /* The path is the same as parent */ - porigin = get_origin(sb, parent, origin->path); - oidcpy(&porigin->blob_oid, &origin->blob_oid); - porigin->mode = origin->mode; - } else { - /* - * Since origin->path is a pathspec, if the parent - * commit had it as a directory, we will see a whole - * bunch of deletion of files in the directory that we - * do not care about. - */ - int i; - struct diff_filepair *p = NULL; - for (i = 0; i < diff_queued_diff.nr; i++) { - const char *name; - p = diff_queued_diff.queue[i]; - name = p->one->path ? p->one->path : p->two->path; - if (!strcmp(name, origin->path)) - break; - } - if (!p) - die("internal error in blame::find_origin"); - switch (p->status) { - default: - die("internal error in blame::find_origin (%c)", - p->status); - case 'M': - porigin = get_origin(sb, parent, origin->path); - oidcpy(&porigin->blob_oid, &p->one->oid); - porigin->mode = p->one->mode; - break; - case 'A': - case 'T': - /* Did not exist in parent, or type changed */ - break; - } - } - diff_flush(&diff_opts); - clear_pathspec(&diff_opts.pathspec); - return porigin; -} - -/* - * We have an origin -- find the path that corresponds to it in its - * parent and return an origin structure to represent it. - */ -static struct origin *find_rename(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin = NULL; - struct diff_options diff_opts; - int i; - - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = DIFF_DETECT_RENAME; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_opts.single_follow = origin->path; - diff_setup_done(&diff_opts); - - if (is_null_oid(&origin->commit->object.oid)) - do_diff_cache(&parent->tree->object.oid, &diff_opts); - else - diff_tree_sha1(parent->tree->object.oid.hash, - origin->commit->tree->object.oid.hash, - "", &diff_opts); - diffcore_std(&diff_opts); - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - if ((p->status == 'R' || p->status == 'C') && - !strcmp(p->two->path, origin->path)) { - porigin = get_origin(sb, parent, p->one->path); - oidcpy(&porigin->blob_oid, &p->one->oid); - porigin->mode = p->one->mode; - break; - } - } - diff_flush(&diff_opts); - clear_pathspec(&diff_opts.pathspec); - return porigin; -} - -/* - * Append a new blame entry to a given output queue. - */ -static void add_blame_entry(struct blame_entry ***queue, - const struct blame_entry *src) -{ - struct blame_entry *e = xmalloc(sizeof(*e)); - memcpy(e, src, sizeof(*e)); - origin_incref(e->suspect); - - e->next = **queue; - **queue = e; - *queue = &e->next; -} - -/* - * src typically is on-stack; we want to copy the information in it to - * 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 ***queue, - struct blame_entry *dst, struct blame_entry *src) -{ - origin_incref(src->suspect); - origin_decref(dst->suspect); - memcpy(dst, src, sizeof(*src)); - dst->next = **queue; - **queue = dst; - *queue = &dst->next; -} - -static const char *nth_line(struct scoreboard *sb, long lno) -{ - return sb->final_buf + sb->lineno[lno]; -} - static const char *nth_line_cb(void *data, long lno) { - return nth_line((struct scoreboard *)data, lno); -} - -/* - * It is known that lines between tlno to same came from parent, and e - * has an overlap with that range. it also is known that parent's - * line plno corresponds to e's line tlno. - * - * <---- e -----> - * <------> - * <------------> - * <------------> - * <------------------> - * - * Split e into potentially three parts; before this chunk, the chunk - * to be blamed for the parent, and after that portion. - */ -static void split_overlap(struct blame_entry *split, - struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - int chunk_end_lno; - memset(split, 0, sizeof(struct blame_entry [3])); - - if (e->s_lno < tlno) { - /* there is a pre-chunk part not blamed on parent */ - split[0].suspect = origin_incref(e->suspect); - split[0].lno = e->lno; - split[0].s_lno = e->s_lno; - split[0].num_lines = tlno - e->s_lno; - split[1].lno = e->lno + tlno - e->s_lno; - split[1].s_lno = plno; - } - else { - split[1].lno = e->lno; - split[1].s_lno = plno + (e->s_lno - tlno); - } - - if (same < e->s_lno + e->num_lines) { - /* there is a post-chunk part not blamed on parent */ - split[2].suspect = origin_incref(e->suspect); - split[2].lno = e->lno + (same - e->s_lno); - split[2].s_lno = e->s_lno + (same - e->s_lno); - split[2].num_lines = e->s_lno + e->num_lines - same; - chunk_end_lno = split[2].lno; - } - else - chunk_end_lno = e->lno + e->num_lines; - split[1].num_lines = chunk_end_lno - split[1].lno; - - /* - * if it turns out there is nothing to blame the parent for, - * forget about the splitting. !split[1].suspect signals this. - */ - if (split[1].num_lines < 1) - return; - split[1].suspect = origin_incref(parent); -} - -/* - * split_overlap() divided an existing blame e into up to three parts - * in split. Any assigned blame is moved to queue to - * reflect the split. - */ -static void split_blame(struct blame_entry ***blamed, - struct blame_entry ***unblamed, - struct blame_entry *split, - struct blame_entry *e) -{ - if (split[0].suspect && split[2].suspect) { - /* The first part (reuse storage for the existing entry e) */ - dup_entry(unblamed, e, &split[0]); - - /* The last part -- me */ - add_blame_entry(unblamed, &split[2]); - - /* ... and the middle part -- parent */ - add_blame_entry(blamed, &split[1]); - } - 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(blamed, e, &split[1]); - else if (split[0].suspect) { - /* me and then parent */ - dup_entry(unblamed, e, &split[0]); - add_blame_entry(blamed, &split[1]); - } - else { - /* parent and then me */ - dup_entry(blamed, e, &split[1]); - add_blame_entry(unblamed, &split[2]); - } -} - -/* - * After splitting the blame, the origins used by the - * on-stack blame_entry should lose one refcnt each. - */ -static void decref_split(struct blame_entry *split) -{ - int i; - - for (i = 0; i < 3; i++) - origin_decref(split[i].suspect); -} - -/* - * 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 struct blame_entry *reverse_blame(struct blame_entry *head, - struct blame_entry *tail) -{ - while (head) { - struct blame_entry *next = head->next; - head->next = tail; - tail = head; - head = next; - } - return tail; -} - -/* - * Process one hunk from the patch between the current suspect for - * 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 blame_entry ***dstq, struct blame_entry ***srcq, - int tlno, int offset, int same, - struct origin *parent) -{ - struct blame_entry *e = **srcq; - struct blame_entry *samep = NULL, *diffp = NULL; - - 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 origin *parent; - 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; - 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; -} - -/* - * We are looking at the origin 'target' and aiming to pass blame - * for the lines it is suspected to its parent. Run diff to find - * which lines came from parent and pass blame for them. - */ -static void pass_blame_to_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) -{ - mmfile_t file_p, file_o; - struct blame_chunk_cb_data d; - struct blame_entry *newdest = NULL; - - 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++; - - if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d)) - die("unable to generate diff (%s -> %s)", - oid_to_hex(&parent->commit->object.oid), - oid_to_hex(&target->commit->object.oid)); - /* 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; -} - -/* - * The lines in blame_entry after splitting blames many times can become - * very small and trivial, and at some point it becomes pointless to - * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any - * ordinary C program, and it is not worth to say it was copied from - * totally unrelated file in the parent. - * - * Compute how trivial the lines in the blame_entry are. - */ -static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) -{ - unsigned score; - const char *cp, *ep; - - if (e->score) - return e->score; - - score = 1; - cp = nth_line(sb, e->lno); - ep = nth_line(sb, e->lno + e->num_lines); - while (cp < ep) { - unsigned ch = *((unsigned char *)cp); - if (isalnum(ch)) - score++; - cp++; - } - e->score = score; - return score; -} - -/* - * best_so_far[] and this[] are both a split of an existing blame_entry - * that passes blame to the parent. Maintain best_so_far the best split - * so far, by comparing this and best_so_far and copying this into - * bst_so_far as needed. - */ -static void copy_split_if_better(struct scoreboard *sb, - struct blame_entry *best_so_far, - struct blame_entry *this) -{ - int i; - - if (!this[1].suspect) - return; - if (best_so_far[1].suspect) { - if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1])) - return; - } - - for (i = 0; i < 3; i++) - origin_incref(this[i].suspect); - decref_split(best_so_far); - memcpy(best_so_far, this, sizeof(struct blame_entry [3])); -} - -/* - * We are looking at a part of the final image represented by - * ent (tlno and same are offset by ent->s_lno). - * tlno is where we are looking at in the final image. - * up to (but not including) same match preimage. - * plno is where we are looking at in the preimage. - * - * <-------------- final image ----------------------> - * <------ent------> - * ^tlno ^same - * <---------preimage-----> - * ^plno - * - * All line numbers are 0-based. - */ -static void handle_split(struct scoreboard *sb, - struct blame_entry *ent, - int tlno, int plno, int same, - struct origin *parent, - struct blame_entry *split) -{ - if (ent->num_lines <= tlno) - return; - if (tlno < same) { - struct blame_entry this[3]; - tlno += ent->s_lno; - same += ent->s_lno; - split_overlap(this, ent, tlno, plno, same, parent); - copy_split_if_better(sb, split, this); - decref_split(this); - } -} - -struct handle_split_cb_data { - struct scoreboard *sb; - struct blame_entry *ent; - struct origin *parent; - struct blame_entry *split; - long plno; - long tlno; -}; - -static int handle_split_cb(long start_a, long count_a, - long start_b, long count_b, void *data) -{ - struct handle_split_cb_data *d = data; - handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent, - d->split); - d->plno = start_a + count_a; - d->tlno = start_b + count_b; - return 0; -} - -/* - * Find the lines from parent that are the same as ent so that - * we can pass blames to it. file_p has the blob contents for - * the parent. - */ -static void find_copy_in_blob(struct scoreboard *sb, - struct blame_entry *ent, - struct origin *parent, - struct blame_entry *split, - mmfile_t *file_p) -{ - const char *cp; - mmfile_t file_o; - struct handle_split_cb_data d; - - memset(&d, 0, sizeof(d)); - d.sb = sb; d.ent = ent; d.parent = parent; d.split = split; - /* - * Prepare mmfile that contains only the lines in ent. - */ - cp = nth_line(sb, ent->lno); - file_o.ptr = (char *) cp; - file_o.size = nth_line(sb, ent->lno + ent->num_lines) - cp; - - /* - * file_o is a part of final image we are annotating. - * file_p partially may match that image. - */ - memset(split, 0, sizeof(struct blame_entry [3])); - if (diff_hunks(file_p, &file_o, handle_split_cb, &d)) - die("unable to generate diff (%s)", - oid_to_hex(&parent->commit->object.oid)); - /* remainder, if any, all match the preimage */ - 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 void find_move_in_parent(struct scoreboard *sb, - struct blame_entry ***blamed, - struct blame_entry **toosmall, - struct origin *target, - struct origin *parent) -{ - struct blame_entry *e, split[3]; - struct blame_entry *unblamed = target->suspects; - struct blame_entry *leftover = NULL; - mmfile_t file_p; - - if (!unblamed) - return; /* nothing remains for this target */ - - fill_origin_blob(&sb->revs->diffopt, parent, &file_p); - if (!file_p.ptr) - return; - - /* 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(blamed, &unblamedtail, split, e); - } else { - e->next = leftover; - leftover = e; - } - decref_split(split); - } - *unblamedtail = NULL; - toosmall = filter_small(sb, toosmall, &unblamed, blame_move_score); - } while (unblamed); - target->suspects = reverse_blame(leftover, NULL); -} - -struct blame_list { - struct blame_entry *ent; - struct blame_entry split[3]; -}; - -/* - * 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 blame_entry *unblamed, - int *num_ents_p) -{ - struct blame_entry *e; - int num_ents, i; - struct blame_list *blame_list = NULL; - - 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 = unblamed, i = 0; e; e = e->next) - blame_list[i++].ent = e; - } - *num_ents_p = num_ents; - return blame_list; -} - -/* - * 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 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; - struct blame_list *blame_list; - int num_ents; - struct blame_entry *unblamed = target->suspects; - struct blame_entry *leftover = NULL; - - if (!unblamed) - return; /* nothing remains for this target */ - - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - - diff_setup_done(&diff_opts); - - /* Try "find copies harder" on new path if requested; - * we do not want to use diffcore_rename() actually to - * match things up; find_copies_harder is set only to - * force diff_tree_sha1() to feed all filepairs to diff_queue, - * and this code needs to be after diff_setup_done(), which - * usually makes find-copies-harder imply copy detection. - */ - if ((opt & PICKAXE_BLAME_COPY_HARDEST) - || ((opt & PICKAXE_BLAME_COPY_HARDER) - && (!porigin || strcmp(target->path, porigin->path)))) - DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); - - if (is_null_oid(&target->commit->object.oid)) - do_diff_cache(&parent->tree->object.oid, &diff_opts); - else - diff_tree_sha1(parent->tree->object.oid.hash, - target->commit->tree->object.oid.hash, - "", &diff_opts); - - if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) - diffcore_std(&diff_opts); - - 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]; - struct origin *norigin; - mmfile_t file_p; - struct blame_entry this[3]; - - if (!DIFF_FILE_VALID(p->one)) - continue; /* does not exist in parent */ - if (S_ISGITLINK(p->one->mode)) - continue; /* ignore git links */ - if (porigin && !strcmp(p->one->path, porigin->path)) - /* find_move already dealt with this path */ - continue; - - norigin = get_origin(sb, parent, p->one->path); - oidcpy(&norigin->blob_oid, &p->one->oid); - norigin->mode = p->one->mode; - fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); - if (!file_p.ptr) - continue; - - for (j = 0; j < num_ents; j++) { - find_copy_in_blob(sb, blame_list[j].ent, - norigin, this, &file_p); - copy_split_if_better(sb, blame_list[j].split, - this); - decref_split(this); - } - origin_decref(norigin); - } - - for (j = 0; j < num_ents; j++) { - struct blame_entry *split = blame_list[j].split; - if (split[1].suspect && - blame_copy_score < ent_score(sb, &split[1])) { - split_blame(blamed, &unblamedtail, split, - blame_list[j].ent); - } else { - blame_list[j].ent->next = leftover; - leftover = blame_list[j].ent; - } - decref_split(split); - } - free(blame_list); - *unblamedtail = NULL; - toosmall = filter_small(sb, toosmall, &unblamed, blame_copy_score); - } while (unblamed); - target->suspects = reverse_blame(leftover, NULL); - diff_flush(&diff_opts); - clear_pathspec(&diff_opts.pathspec); -} - -/* - * The blobs of origin and porigin exactly match, so everything - * origin is suspected for can be blamed on the parent. - */ -static void pass_whole_blame(struct scoreboard *sb, - struct origin *origin, struct origin *porigin) -{ - struct blame_entry *e, *suspects; - - if (!porigin->file.ptr && origin->file.ptr) { - /* Steal its file */ - porigin->file = origin->file; - origin->file.ptr = NULL; - } - 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); -} - -/* - * We pass blame from the current commit to its parents. We keep saying - * "parent" (and "porigin"), but what we mean is to find scapegoat to - * exonerate ourselves. - */ -static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit) -{ - if (!reverse) { - if (revs->first_parent_only && - commit->parents && - commit->parents->next) { - free_commit_list(commit->parents->next); - commit->parents->next = NULL; - } - return commit->parents; - } - return lookup_decoration(&revs->children, &commit->object); -} - -static int num_scapegoats(struct rev_info *revs, struct commit *commit) -{ - struct commit_list *l = first_scapegoat(revs, commit); - 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 - -static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) -{ - struct rev_info *revs = sb->revs; - int i, pass, num_sg; - struct commit *commit = origin->commit; - 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) - goto finish; - else if (num_sg < ARRAY_SIZE(sg_buf)) - memset(sg_buf, 0, sizeof(sg_buf)); - else - sg_origin = xcalloc(num_sg, sizeof(*sg_origin)); - - /* - * The first pass looks for unrenamed path to optimize for - * common cases, then we look for renames in the second pass. - */ - for (pass = 0; pass < 2 - no_whole_file_rename; pass++) { - struct origin *(*find)(struct scoreboard *, - struct commit *, struct origin *); - find = pass ? find_rename : find_origin; - - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct commit *p = sg->item; - int j, same; - - if (sg_origin[i]) - continue; - if (parse_commit(p)) - continue; - porigin = find(sb, p, origin); - if (!porigin) - continue; - if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) { - pass_whole_blame(sb, origin, porigin); - origin_decref(porigin); - goto finish; - } - for (j = same = 0; j < i; j++) - if (sg_origin[j] && - !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) { - same = 1; - break; - } - if (!same) - sg_origin[i] = porigin; - else - origin_decref(porigin); - } - } - - num_commits++; - 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 (!origin->previous) { - origin_incref(porigin); - origin->previous = porigin; - } - pass_blame_to_parent(sb, origin, porigin); - if (!origin->suspects) - goto finish; - } - - /* - * Optionally find moves in parents' files. - */ - 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 (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]; - find_copy_in_parent(sb, &blametail, &toosmall, - origin, sg->item, porigin, opt); - if (!origin->suspects) - goto 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]); - origin_decref(sg_origin[i]); - } - } - drop_origin_blob(origin); - if (sg_buf != sg_origin) - free(sg_origin); + return blame_nth_line((struct blame_scoreboard *)data, lno); } /* @@ -1699,10 +217,10 @@ static void get_commit_info(struct commit *commit, * To allow LF and other nonportable characters in pathnames, * they are c-style quoted as needed. */ -static void write_filename_info(struct origin *suspect) +static void write_filename_info(struct blame_origin *suspect) { if (suspect->previous) { - struct origin *prev = suspect->previous; + struct blame_origin *prev = suspect->previous; printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); write_name_quoted(prev->path, stdout, '\n'); } @@ -1716,7 +234,7 @@ static void write_filename_info(struct origin *suspect) * the first time each commit appears in the output (unless the * user has specifically asked for us to repeat). */ -static int emit_one_suspect_detail(struct origin *suspect, int repeat) +static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat) { struct commit_info ci; @@ -1746,11 +264,12 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) * 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, - struct progress_info *pi) +static void found_guilty_entry(struct blame_entry *ent, void *data) { + struct progress_info *pi = (struct progress_info *)data; + if (incremental) { - struct origin *suspect = ent->suspect; + struct blame_origin *suspect = ent->suspect; printf("%s %d %d %d\n", oid_to_hex(&suspect->commit->object.oid), @@ -1763,80 +282,6 @@ static void found_guilty_entry(struct blame_entry *ent, display_progress(pi->progress, pi->blamed_lines); } -/* - * 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); - struct progress_info pi = { NULL, 0 }; - - if (show_progress) - pi.progress = start_progress_delay(_("Blaming lines"), - sb->num_lines, 50, 1); - - while (commit) { - struct blame_entry *ent; - struct origin *suspect = commit->util; - - /* find one suspect to break down */ - 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); - parse_commit(commit); - if (reverse || - (!(commit->object.flags & UNINTERESTING) && - !(revs->max_age != -1 && commit->date < revs->max_age))) - pass_blame(sb, suspect, opt); - else { - commit->object.flags |= UNINTERESTING; - if (commit->object.parsed) - mark_parents_uninteresting(commit); - } - /* treat root commit as boundary */ - if (!commit->parents && !show_root) - commit->object.flags |= UNINTERESTING; - - /* Take responsibility for the remaining entries */ - ent = suspect->suspects; - if (ent) { - suspect->guilty = 1; - for (;;) { - struct blame_entry *next = ent->next; - found_guilty_entry(ent, &pi); - if (next) { - ent = next; - continue; - } - ent->next = sb->ent; - sb->ent = suspect->suspects; - suspect->suspects = NULL; - break; - } - } - origin_decref(suspect); - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); - } - - stop_progress(&pi.progress); -} - static const char *format_time(timestamp_t time, const char *tz_str, int show_raw_time) { @@ -1876,20 +321,20 @@ static const char *format_time(timestamp_t time, const char *tz_str, #define OUTPUT_SHOW_EMAIL 0400 #define OUTPUT_LINE_PORCELAIN 01000 -static void emit_porcelain_details(struct origin *suspect, int repeat) +static void emit_porcelain_details(struct blame_origin *suspect, int repeat) { if (emit_one_suspect_detail(suspect, repeat) || (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) write_filename_info(suspect); } -static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, +static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent, int opt) { int repeat = opt & OUTPUT_LINE_PORCELAIN; int cnt; const char *cp; - struct origin *suspect = ent->suspect; + struct blame_origin *suspect = ent->suspect; char hex[GIT_MAX_HEXSZ + 1]; oid_to_hex_r(hex, &suspect->commit->object.oid); @@ -1900,7 +345,7 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, ent->num_lines); emit_porcelain_details(suspect, repeat); - cp = nth_line(sb, ent->lno); + cp = blame_nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; if (cnt) { @@ -1922,11 +367,11 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, putchar('\n'); } -static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) +static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt) { int cnt; const char *cp; - struct origin *suspect = ent->suspect; + struct blame_origin *suspect = ent->suspect; struct commit_info ci; char hex[GIT_MAX_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); @@ -1934,7 +379,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) get_commit_info(suspect->commit, &ci, 1); oid_to_hex_r(hex, &suspect->commit->object.oid); - cp = nth_line(sb, ent->lno); + cp = blame_nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev; @@ -2001,14 +446,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) commit_info_destroy(&ci); } -static void output(struct scoreboard *sb, int option) +static void output(struct blame_scoreboard *sb, int option) { struct blame_entry *ent; if (option & OUTPUT_PORCELAIN) { for (ent = sb->ent; ent; ent = ent->next) { int count = 0; - struct origin *suspect; + struct blame_origin *suspect; struct commit *commit = ent->suspect->commit; if (commit->object.flags & MORE_THAN_ONE_PATH) continue; @@ -2030,40 +475,6 @@ 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. - */ -static int prepare_lines(struct scoreboard *sb) -{ - const char *buf = sb->final_buf; - unsigned long len = sb->final_buf_size; - const char *end = buf + len; - const char *p; - int *lineno; - int num = 0; - - for (p = buf; p < end; p = get_next_line(p, end)) - num++; - - ALLOC_ARRAY(sb->lineno, num + 1); - lineno = sb->lineno; - - for (p = buf; p < end; p = get_next_line(p, end)) - *lineno++ = p - buf; - - *lineno = len; - - sb->num_lines = num; - return sb->num_lines; -} - /* * Add phony grafts for use with -S; this is primarily to * support git's cvsserver that wants to give a linear history @@ -2071,7 +482,7 @@ static int prepare_lines(struct scoreboard *sb) */ static int read_ancestry(const char *graft_file) { - FILE *fp = fopen(graft_file, "r"); + FILE *fp = fopen_or_warn(graft_file, "r"); struct strbuf buf = STRBUF_INIT; if (!fp) return -1; @@ -2086,7 +497,7 @@ static int read_ancestry(const char *graft_file) return 0; } -static int update_auto_abbrev(int auto_abbrev, struct origin *suspect) +static int update_auto_abbrev(int auto_abbrev, struct blame_origin *suspect) { const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash, auto_abbrev); @@ -2100,7 +511,7 @@ static int update_auto_abbrev(int auto_abbrev, struct origin *suspect) * How many columns do we need to show line numbers, authors, * and filenames? */ -static void find_alignment(struct scoreboard *sb, int *option) +static void find_alignment(struct blame_scoreboard *sb, int *option) { int longest_src_lines = 0; int longest_dst_lines = 0; @@ -2110,7 +521,7 @@ static void find_alignment(struct scoreboard *sb, int *option) int auto_abbrev = DEFAULT_ABBREV; for (e = sb->ent; e; e = e->next) { - struct origin *suspect = e->suspect; + struct blame_origin *suspect = e->suspect; int num; if (compute_auto_abbrev) @@ -2138,8 +549,8 @@ static void find_alignment(struct scoreboard *sb, int *option) num = e->lno + e->num_lines; if (longest_dst_lines < num) longest_dst_lines = num; - if (largest_score < ent_score(sb, e)) - largest_score = ent_score(sb, e); + if (largest_score < blame_entry_score(sb, e)) + largest_score = blame_entry_score(sb, e); } max_orig_digits = decimal_width(longest_src_lines); max_digits = decimal_width(longest_dst_lines); @@ -2150,31 +561,12 @@ static void find_alignment(struct scoreboard *sb, int *option) abbrev = auto_abbrev + 1; } -/* - * For debugging -- origin is refcounted, and this asserts that - * we do not underflow. - */ -static void sanity_check_refcnt(struct scoreboard *sb) +static void sanity_check_on_fail(struct blame_scoreboard *sb, int baa) { - int baa = 0; - struct blame_entry *ent; - - for (ent = sb->ent; ent; ent = ent->next) { - /* Nobody should have zero or negative refcnt */ - if (ent->suspect->refcnt <= 0) { - fprintf(stderr, "%s in %s has negative refcnt %d\n", - ent->suspect->path, - oid_to_hex(&ent->suspect->commit->object.oid), - ent->suspect->refcnt); - baa = 1; - } - } - if (baa) { - int opt = 0160; - find_alignment(sb, &opt); - output(sb, opt); - die("Baa %d!", baa); - } + int opt = OUTPUT_SHOW_SCORE | OUTPUT_SHOW_NUMBER | OUTPUT_SHOW_NAME; + find_alignment(sb, &opt); + output(sb, opt); + die("Baa %d!", baa); } static unsigned parse_score(const char *arg) @@ -2224,301 +616,6 @@ static int git_blame_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -static void verify_working_tree_path(struct commit *work_tree, const char *path) -{ - struct commit_list *parents; - int pos; - - for (parents = work_tree->parents; parents; parents = parents->next) { - const struct object_id *commit_oid = &parents->item->object.oid; - struct object_id blob_oid; - unsigned mode; - - if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) && - sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB) - return; - } - - pos = cache_name_pos(path, strlen(path)); - if (pos >= 0) - ; /* path is in the index */ - else if (-1 - pos < active_nr && - !strcmp(active_cache[-1 - pos]->name, path)) - ; /* path is in the index, unmerged */ - else - die("no such path '%s' in HEAD", path); -} - -static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid) -{ - struct commit *parent; - - parent = lookup_commit_reference(oid); - if (!parent) - die("no such commit %s", oid_to_hex(oid)); - return &commit_list_insert(parent, tail)->next; -} - -static void append_merge_parents(struct commit_list **tail) -{ - int merge_head; - struct strbuf line = STRBUF_INIT; - - merge_head = open(git_path_merge_head(), O_RDONLY); - if (merge_head < 0) { - if (errno == ENOENT) - return; - die("cannot open '%s' for reading", git_path_merge_head()); - } - - while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { - struct object_id oid; - if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid)) - die("unknown line in '%s': %s", git_path_merge_head(), line.buf); - tail = append_parent(tail, &oid); - } - close(merge_head); - strbuf_release(&line); -} - -/* - * This isn't as simple as passing sb->buf and sb->len, because we - * want to transfer ownership of the buffer to the commit (so we - * must use detach). - */ -static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb) -{ - size_t len; - void *buf = strbuf_detach(sb, &len); - set_commit_buffer(c, buf, len); -} - -/* - * Prepare a dummy commit that represents the work tree (or staged) item. - * Note that annotating work tree item never works in the reverse. - */ -static struct commit *fake_working_tree_commit(struct diff_options *opt, - const char *path, - const char *contents_from) -{ - struct commit *commit; - struct origin *origin; - struct commit_list **parent_tail, *parent; - struct object_id head_oid; - struct strbuf buf = STRBUF_INIT; - const char *ident; - time_t now; - int size, len; - struct cache_entry *ce; - unsigned mode; - struct strbuf msg = STRBUF_INIT; - - read_cache(); - time(&now); - commit = alloc_commit_node(); - commit->object.parsed = 1; - commit->date = now; - parent_tail = &commit->parents; - - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) - die("no such ref: HEAD"); - - parent_tail = append_parent(parent_tail, &head_oid); - append_merge_parents(parent_tail); - verify_working_tree_path(commit, path); - - origin = make_origin(commit, path); - - ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); - strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n"); - for (parent = commit->parents; parent; parent = parent->next) - strbuf_addf(&msg, "parent %s\n", - oid_to_hex(&parent->item->object.oid)); - strbuf_addf(&msg, - "author %s\n" - "committer %s\n\n" - "Version of %s from %s\n", - ident, ident, path, - (!contents_from ? path : - (!strcmp(contents_from, "-") ? "standard input" : contents_from))); - set_commit_buffer_from_strbuf(commit, &msg); - - if (!contents_from || strcmp("-", contents_from)) { - struct stat st; - const char *read_from; - char *buf_ptr; - unsigned long buf_len; - - if (contents_from) { - if (stat(contents_from, &st) < 0) - die_errno("Cannot stat '%s'", contents_from); - read_from = contents_from; - } - else { - if (lstat(path, &st) < 0) - die_errno("Cannot lstat '%s'", path); - read_from = path; - } - mode = canon_mode(st.st_mode); - - switch (st.st_mode & S_IFMT) { - case S_IFREG: - if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(read_from, mode, &null_oid, 0, &buf_ptr, &buf_len)) - strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1); - else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) - die_errno("cannot open or read '%s'", read_from); - break; - case S_IFLNK: - if (strbuf_readlink(&buf, read_from, st.st_size) < 0) - die_errno("cannot readlink '%s'", read_from); - break; - default: - die("unsupported file type %s", read_from); - } - } - else { - /* Reading from stdin */ - mode = 0; - if (strbuf_read(&buf, 0, 0) < 0) - die_errno("failed to read from stdin"); - } - convert_to_git(path, buf.buf, buf.len, &buf, 0); - origin->file.ptr = buf.buf; - origin->file.size = buf.len; - pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash); - - /* - * Read the current index, replace the path entry with - * origin->blob_sha1 without mucking with its mode or type - * bits; we are not going to write this index out -- we just - * want to run "diff-index --cached". - */ - discard_cache(); - read_cache(); - - len = strlen(path); - if (!mode) { - int pos = cache_name_pos(path, len); - if (0 <= pos) - mode = active_cache[pos]->ce_mode; - else - /* Let's not bother reading from HEAD tree */ - mode = S_IFREG | 0644; - } - size = cache_entry_size(len); - ce = xcalloc(1, size); - oidcpy(&ce->oid, &origin->blob_oid); - memcpy(ce->name, path, len); - ce->ce_flags = create_ce_flags(0); - ce->ce_namelen = len; - ce->ce_mode = create_ce_mode(mode); - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - - cache_tree_invalidate_path(&the_index, path); - - return commit; -} - -static struct commit *find_single_final(struct rev_info *revs, - const char **name_p) -{ - int i; - struct commit *found = NULL; - const char *name = NULL; - - for (i = 0; i < revs->pending.nr; i++) { - struct object *obj = revs->pending.objects[i].item; - if (obj->flags & UNINTERESTING) - continue; - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", revs->pending.objects[i].name); - if (found) - die("More than one commit to dig from %s and %s?", - revs->pending.objects[i].name, name); - found = (struct commit *)obj; - name = revs->pending.objects[i].name; - } - if (name_p) - *name_p = name; - return found; -} - -static char *prepare_final(struct scoreboard *sb) -{ - const char *name; - sb->final = find_single_final(sb->revs, &name); - return xstrdup_or_null(name); -} - -static const char *dwim_reverse_initial(struct scoreboard *sb) -{ - /* - * DWIM "git blame --reverse ONE -- PATH" as - * "git blame --reverse ONE..HEAD -- PATH" but only do so - * when it makes sense. - */ - struct object *obj; - struct commit *head_commit; - struct object_id head_oid; - - if (sb->revs->pending.nr != 1) - return NULL; - - /* Is that sole rev a committish? */ - obj = sb->revs->pending.objects[0].item; - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - return NULL; - - /* Do we have HEAD? */ - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) - return NULL; - head_commit = lookup_commit_reference_gently(&head_oid, 1); - if (!head_commit) - return NULL; - - /* Turn "ONE" into "ONE..HEAD" then */ - obj->flags |= UNINTERESTING; - add_pending_object(sb->revs, &head_commit->object, "HEAD"); - - sb->final = (struct commit *)obj; - return sb->revs->pending.objects[0].name; -} - -static char *prepare_initial(struct scoreboard *sb) -{ - int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; - - /* - * There must be one and only one negative commit, and it must be - * the boundary. - */ - for (i = 0; i < revs->pending.nr; i++) { - struct object *obj = revs->pending.objects[i].item; - if (!(obj->flags & UNINTERESTING)) - continue; - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", revs->pending.objects[i].name); - if (sb->final) - die("More than one commit to dig up from, %s and %s?", - revs->pending.objects[i].name, - final_commit_name); - sb->final = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; - } - - if (!final_commit_name) - final_commit_name = dwim_reverse_initial(sb); - if (!final_commit_name) - die("No commit to dig up from?"); - return xstrdup(final_commit_name); -} - static int blame_copy_callback(const struct option *option, const char *arg, int unset) { int *opt = option->value; @@ -2556,13 +653,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; const char *path; - struct scoreboard sb; - struct origin *o; + struct blame_scoreboard sb; + struct blame_origin *o; struct blame_entry *ent = NULL; long dashdash_pos, lno; - char *final_commit_name = NULL; - enum object_type type; - struct commit *final_commit = NULL; + struct progress_info pi = { NULL, 0 }; struct string_list range_list = STRING_LIST_INIT_NODUP; int output_option = 0, opt = 0; @@ -2688,12 +783,15 @@ parse_done: blame_date_width = sizeof("2006-10-19"); break; case DATE_RELATIVE: - /* TRANSLATORS: This string is used to tell us the maximum - display width for a relative timestamp in "git blame" - output. For C locale, "4 years, 11 months ago", which - takes 22 places, is the longest among various forms of - relative timestamps, but your language may need more or - fewer display columns. */ + /* + * TRANSLATORS: This string is used to tell us the + * maximum display width for a relative timestamp in + * "git blame" output. For C locale, "4 years, 11 + * months ago", which takes 22 places, is the longest + * among various forms of relative timestamps, but + * your language may need more or fewer display + * columns. + */ blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */ break; case DATE_NORMAL: @@ -2709,11 +807,6 @@ parse_done: opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE | PICKAXE_BLAME_COPY_HARDER); - if (!blame_move_score) - blame_move_score = BLAME_DEFAULT_MOVE_SCORE; - if (!blame_copy_score) - blame_copy_score = BLAME_DEFAULT_COPY_SCORE; - /* * We have collected options unknown to us in argv[1..unk] * which are to be passed to revision machinery if we are @@ -2766,94 +859,13 @@ parse_done: revs.disable_stdin = 1; setup_revisions(argc, argv, &revs, NULL); - memset(&sb, 0, sizeof(sb)); + init_scoreboard(&sb); sb.revs = &revs; - if (!reverse) { - final_commit_name = prepare_final(&sb); - sb.commits.compare = compare_commits_by_commit_date; - } - else if (contents_from) - die(_("--contents and --reverse do not blend well.")); - else { - final_commit_name = prepare_initial(&sb); - sb.commits.compare = compare_commits_by_reverse_commit_date; - if (revs.first_parent_only) - revs.children.name = NULL; - } - - if (!sb.final) { - /* - * "--not A B -- path" without anything positive; - * do not default to HEAD, but use the working tree - * or "--contents". - */ - setup_work_tree(); - sb.final = fake_working_tree_commit(&sb.revs->diffopt, - path, contents_from); - add_pending_object(&revs, &(sb.final->object), ":"); - } - else if (contents_from) - die(_("cannot use --contents with final commit object name")); - - if (reverse && revs.first_parent_only) { - final_commit = find_single_final(sb.revs, NULL); - if (!final_commit) - die(_("--reverse and --first-parent together require specified latest commit")); - } - - /* - * If we have bottom, this will mark the ancestors of the - * bottom commits we would reach while traversing as - * uninteresting. - */ - if (prepare_revision_walk(&revs)) - die(_("revision walk setup failed")); - - if (reverse && revs.first_parent_only) { - struct commit *c = final_commit; - - sb.revs->children.name = "children"; - while (c->parents && - oidcmp(&c->object.oid, &sb.final->object.oid)) { - struct commit_list *l = xcalloc(1, sizeof(*l)); - - l->item = c; - if (add_decoration(&sb.revs->children, - &c->parents->item->object, l)) - die("BUG: not unique item in first-parent chain"); - c = c->parents->item; - } - - if (oidcmp(&c->object.oid, &sb.final->object.oid)) - die(_("--reverse --first-parent together require range along first-parent chain")); - } - - if (is_null_oid(&sb.final->object.oid)) { - o = sb.final->util; - sb.final_buf = xmemdupz(o->file.ptr, o->file.size); - sb.final_buf_size = o->file.size; - } - else { - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1_and_mode(o)) - die(_("no such path %s in %s"), path, final_commit_name); - - if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && - textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb.final_buf, - &sb.final_buf_size)) - ; - else - sb.final_buf = read_sha1_file(o->blob_oid.hash, &type, - &sb.final_buf_size); - - if (!sb.final_buf) - die(_("cannot read blob %s for path %s"), - oid_to_hex(&o->blob_oid), - path); - } - num_read_blob++; - lno = prepare_lines(&sb); + sb.contents_from = contents_from; + sb.reverse = reverse; + setup_scoreboard(&sb, path, &o); + lno = sb.num_lines; if (lno && !range_list.nr) string_list_append(&range_list, "1"); @@ -2882,22 +894,13 @@ parse_done: for (range_i = ranges.nr; range_i > 0; --range_i) { const struct range *r = &ranges.ranges[range_i - 1]; - long bottom = r->start; - long top = r->end; - struct blame_entry *next = ent; - ent = xcalloc(1, sizeof(*ent)); - ent->lno = bottom; - ent->num_lines = top - bottom; - ent->suspect = o; - ent->s_lno = bottom; - ent->next = next; - origin_incref(o); + ent = blame_entry_prepend(ent, r->start, r->end, o); } o->suspects = ent; prio_queue_put(&sb.commits, o->commit); - origin_decref(o); + blame_origin_decref(o); range_set_release(&ranges); string_list_clear(&range_list, 0); @@ -2905,21 +908,38 @@ parse_done: sb.ent = NULL; sb.path = path; + if (blame_move_score) + sb.move_score = blame_move_score; + if (blame_copy_score) + sb.copy_score = blame_copy_score; + + sb.debug = DEBUG; + sb.on_sanity_fail = &sanity_check_on_fail; + + sb.show_root = show_root; + sb.xdl_opts = xdl_opts; + sb.no_whole_file_rename = no_whole_file_rename; + read_mailmap(&mailmap, NULL); + sb.found_guilty_entry = &found_guilty_entry; + sb.found_guilty_entry_data = π + if (show_progress) + pi.progress = start_progress_delay(_("Blaming lines"), + sb.num_lines, 50, 1); + assign_blame(&sb, opt); + stop_progress(&pi.progress); + if (!incremental) setup_pager(); - - free(final_commit_name); - - if (incremental) + else return 0; - sb.ent = blame_sort(sb.ent, compare_blame_final); + blame_sort_final(&sb); - coalesce(&sb); + blame_coalesce(&sb); if (!(output_option & OUTPUT_PORCELAIN)) find_alignment(&sb, &output_option); @@ -2933,9 +953,9 @@ parse_done: } if (show_stats) { - printf("num read blob: %d\n", num_read_blob); - printf("num get patch: %d\n", num_get_patch); - printf("num commits: %d\n", num_commits); + printf("num read blob: %d\n", sb.num_read_blob); + printf("num get patch: %d\n", sb.num_get_patch); + printf("num commits: %d\n", sb.num_commits); } return 0; } diff --git a/builtin/branch.c b/builtin/branch.c index 83fcda43dc..c958e93257 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "config.h" #include "color.h" #include "refs.h" #include "commit.h" diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 1890d7a639..7efbc4019a 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -4,7 +4,9 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "builtin.h" +#include "diff.h" #include "parse-options.h" #include "userdiff.h" #include "streaming.h" @@ -61,7 +63,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, if (unknown_type) flags |= LOOKUP_UNKNOWN_OBJECT; - if (get_sha1_with_context(obj_name, 0, oid.hash, &obj_context)) + if (get_sha1_with_context(obj_name, GET_SHA1_RECORD_PATH, + oid.hash, &obj_context)) die("Not a valid object name %s", obj_name); if (!path) @@ -165,6 +168,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, die("git cat-file %s: bad file", obj_name); write_or_die(1, buf, size); + free(buf); + free(obj_context.path); return 0; } diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 4d01ca0c8b..91444dc044 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "attr.h" #include "quote.h" #include "parse-options.h" diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 1d73d3ca3d..3e280b9c7a 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -1,9 +1,11 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "dir.h" #include "quote.h" #include "pathspec.h" #include "parse-options.h" +#include "submodule.h" static int quiet, verbose, stdin_paths, show_non_matching, no_index; static const char * const check_ignore_usage[] = { @@ -87,21 +89,23 @@ static int check_ignore(struct dir_struct *dir, parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, PATHSPEC_SYMLINK_LEADING_PATH | - PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE | PATHSPEC_KEEP_ORDER, prefix, argv); + die_path_inside_submodule(&the_index, &pathspec); + /* * look for pathspecs matching entries in the index, since these * should not be ignored, in order to be consistent with * 'git status', 'git add' etc. */ - seen = find_pathspecs_matching_against_index(&pathspec); + seen = find_pathspecs_matching_against_index(&pathspec, &the_index); for (i = 0; i < pathspec.nr; i++) { full_path = pathspec.items[i].match; exclude = NULL; if (!seen[i]) { - exclude = last_exclude_matching(dir, full_path, &dtype); + exclude = last_exclude_matching(dir, &the_index, + full_path, &dtype); } if (!quiet && (exclude || show_non_matching)) output_exclude(pathspec.items[i].original, exclude); diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c index cf0f54f6b9..cdce144f3b 100644 --- a/builtin/check-mailmap.c +++ b/builtin/check-mailmap.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "mailmap.h" #include "parse-options.h" #include "string-list.h" diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 07631d0c9c..39c8be05dc 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -5,6 +5,7 @@ * */ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "quote.h" #include "cache-tree.h" diff --git a/builtin/checkout.c b/builtin/checkout.c index 65877bacb1..9661e1bcba 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "parse-options.h" #include "refs.h" @@ -21,31 +22,12 @@ #include "submodule-config.h" #include "submodule.h" -static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; - static const char * const checkout_usage[] = { N_("git checkout [<options>] <branch>"), N_("git checkout [<options>] [<branch>] -- <file>..."), NULL, }; -static int option_parse_recurse_submodules(const struct option *opt, - const char *arg, int unset) -{ - if (unset) { - recurse_submodules = RECURSE_SUBMODULES_OFF; - return 0; - } - if (arg) - recurse_submodules = - parse_update_recurse_submodules_arg(opt->long_name, - arg); - else - recurse_submodules = RECURSE_SUBMODULES_ON; - - return 0; -} - struct checkout_opts { int patch_mode; int quiet; @@ -235,22 +217,24 @@ static int checkout_merged(int pos, const struct checkout *state) /* * NEEDSWORK: * There is absolutely no reason to write this as a blob object - * and create a phony cache entry just to leak. This hack is - * primarily to get to the write_entry() machinery that massages - * the contents to work-tree format and writes out which only - * allows it for a cache entry. The code in write_entry() needs - * to be refactored to allow us to feed a <buffer, size, mode> - * instead of a cache entry. Such a refactoring would help - * merge_recursive as well (it also writes the merge result to the - * object database even when it may contain conflicts). + * and create a phony cache entry. This hack is primarily to get + * to the write_entry() machinery that massages the contents to + * work-tree format and writes out which only allows it for a + * cache entry. The code in write_entry() needs to be refactored + * to allow us to feed a <buffer, size, mode> instead of a cache + * entry. Such a refactoring would help merge_recursive as well + * (it also writes the merge result to the object database even + * when it may contain conflicts). */ if (write_sha1_file(result_buf.ptr, result_buf.size, blob_type, oid.hash)) die(_("Unable to add merge result for '%s'"), path); + free(result_buf.ptr); ce = make_cache_entry(mode, oid.hash, path, 2, 0); if (!ce) die(_("make_cache_entry failed for path '%s'"), path); status = checkout_entry(ce, state, NULL); + free(ce); return status; } @@ -874,7 +858,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb) } if (starts_with(var, "submodule.")) - return parse_submodule_config_option(var, value); + return submodule_config(var, value, NULL); return git_xmerge_config(var, value, NULL); } @@ -1182,9 +1166,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) N_("second guess 'git checkout <no-such-branch>'")), OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, N_("do not check if another worktree is holding the given ref")), - { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "checkout", "control recursive updating of submodules", - PARSE_OPT_OPTARG, option_parse_recurse_submodules }, + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")), OPT_END(), }; @@ -1215,12 +1199,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) git_xmerge_config("merge.conflictstyle", conflict_style, NULL); } - if (recurse_submodules != RECURSE_SUBMODULES_OFF) { - git_config(submodule_config, NULL); - if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) - set_config_update_recurse_submodules(recurse_submodules); - } - if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1) die(_("-b, -B and --orphan are mutually exclusive")); @@ -1287,9 +1265,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * new_branch && argc > 1 will be caught later. */ if (opts.new_branch && argc == 1) - die(_("Cannot update paths and switch to branch '%s' at the same time.\n" - "Did you intend to checkout '%s' which can not be resolved as commit?"), - opts.new_branch, argv[0]); + die(_("'%s' is not a commit and a branch '%s' cannot be created from it"), + argv[0], opts.new_branch); if (opts.force_detach) die(_("git checkout: --detach does not take a path argument '%s'"), diff --git a/builtin/clean.c b/builtin/clean.c index d861f836a2..057fc97fe4 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -8,6 +8,7 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "dir.h" #include "parse-options.h" #include "string-list.h" @@ -683,7 +684,7 @@ static int filter_by_patterns_cmd(void) for_each_string_list_item(item, &del_list) { int dtype = DT_UNKNOWN; - if (is_excluded(&dir, item->string, &dtype)) { + if (is_excluded(&dir, &the_index, item->string, &dtype)) { *item->string = '\0'; changed++; } @@ -837,8 +838,7 @@ static void interactive_main_loop(void) int ret; ret = menus[*chosen].fn(); if (ret != MENU_RETURN_NO_LOOP) { - free(chosen); - chosen = NULL; + FREE_AND_NULL(chosen); if (!del_list.nr) { clean_print_color(CLEAN_COLOR_ERROR); printf_ln(_("No more files to clean, exiting.")); @@ -851,12 +851,43 @@ static void interactive_main_loop(void) quit_cmd(); } - free(chosen); - chosen = NULL; + FREE_AND_NULL(chosen); break; } } +static void correct_untracked_entries(struct dir_struct *dir) +{ + int src, dst, ign; + + for (src = dst = ign = 0; src < dir->nr; src++) { + /* skip paths in ignored[] that cannot be inside entries[src] */ + while (ign < dir->ignored_nr && + 0 <= cmp_dir_entry(&dir->entries[src], &dir->ignored[ign])) + ign++; + + if (ign < dir->ignored_nr && + check_dir_entry_contains(dir->entries[src], dir->ignored[ign])) { + /* entries[src] contains an ignored path, so we drop it */ + free(dir->entries[src]); + } else { + struct dir_entry *ent = dir->entries[src++]; + + /* entries[src] does not contain an ignored path, so we keep it */ + dir->entries[dst++] = ent; + + /* then discard paths in entries[] contained inside entries[src] */ + while (src < dir->nr && + check_dir_entry_contains(ent, dir->entries[src])) + free(dir->entries[src++]); + + /* compensate for the outer loop's loop control */ + src--; + } + } + dir->nr = dst; +} + int cmd_clean(int argc, const char **argv, const char *prefix) { int i, res; @@ -916,6 +947,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (remove_directories) + dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS; + if (read_cache() < 0) die(_("index file corrupt")); @@ -930,7 +964,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) PATHSPEC_PREFER_CWD, prefix, argv); - fill_directory(&dir, &pathspec); + fill_directory(&dir, &the_index, &pathspec); + correct_untracked_entries(&dir); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; @@ -958,6 +993,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix) string_list_append(&del_list, rel); } + for (i = 0; i < dir.nr; i++) + free(dir.entries[i]); + + for (i = 0; i < dir.ignored_nr; i++) + free(dir.ignored[i]); + if (interactive && del_list.nr > 0) interactive_main_loop(); diff --git a/builtin/clone.c b/builtin/clone.c index 743f16ae2a..08b5cc433c 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -9,6 +9,7 @@ */ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "parse-options.h" #include "fetch-pack.h" @@ -360,7 +361,7 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, * to turn entries with paths relative to the original * absolute, so that they can be used in the new repository. */ - FILE *in = fopen(src->buf, "r"); + FILE *in = xfopen(src->buf, "r"); struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in) != EOF) { diff --git a/builtin/column.c b/builtin/column.c index 33314b4d71..0c3223d64b 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "strbuf.h" #include "parse-options.h" #include "string-list.h" diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index f39c2b2737..a4a923d7c0 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "commit.h" #include "tree.h" #include "builtin.h" diff --git a/builtin/commit.c b/builtin/commit.c index 1d191a49f8..00a01f07c3 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "cache-tree.h" #include "color.h" @@ -253,7 +254,8 @@ static int list_paths(struct string_list *list, const char *with_tree, if (with_tree) { char *max_prefix = common_prefix(pattern); - overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix); + overlay_tree_on_index(&the_index, with_tree, + max_prefix ? max_prefix : prefix); free(max_prefix); } @@ -1295,6 +1297,10 @@ static int git_status_config(const char *k, const char *v, void *cb) status_deferred_config.show_branch = git_config_bool(k, v); return 0; } + if (!strcmp(k, "status.showstash")) { + s->show_stash = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { s->use_color = git_config_colorbool(k, v); return 0; @@ -1343,6 +1349,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) N_("show status concisely"), STATUS_FORMAT_SHORT), OPT_BOOL('b', "branch", &s.show_branch, N_("show branch information")), + OPT_BOOL(0, "show-stash", &s.show_stash, + N_("show stash information")), { OPTION_CALLBACK, 0, "porcelain", &status_format, N_("version"), N_("machine-readable output"), PARSE_OPT_OPTARG, opt_parse_porcelain }, @@ -1699,10 +1707,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!reflog_msg) reflog_msg = "commit (merge)"; pptr = commit_list_append(current_head, pptr); - fp = fopen(git_path_merge_head(), "r"); - if (fp == NULL) - die_errno(_("could not open '%s' for reading"), - git_path_merge_head()); + fp = xfopen(git_path_merge_head(), "r"); while (strbuf_getline_lf(&m, fp) != EOF) { struct commit *parent; @@ -1739,7 +1744,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (verbose || /* Truncate the message just before the diff, if any. */ cleanup_mode == CLEANUP_SCISSORS) - wt_status_truncate_message_at_cut_line(&sb); + strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len)); if (cleanup_mode != CLEANUP_NONE) strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL); @@ -1809,7 +1814,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) cfg = init_copy_notes_for_rewrite("amend"); if (cfg) { /* we are amending, so current_head is not NULL */ - copy_note_for_rewrite(cfg, current_head->object.oid.hash, oid.hash); + copy_note_for_rewrite(cfg, ¤t_head->object.oid, &oid); finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); } run_rewrite_hook(¤t_head->object.oid, &oid); diff --git a/builtin/config.c b/builtin/config.c index 3a554ad50c..70ff231e9c 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "color.h" #include "parse-options.h" #include "urlmatch.h" @@ -214,8 +215,7 @@ static int get_value(const char *key_, const char *regex_) key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { error("invalid key pattern: %s", key_); - free(key_regexp); - key_regexp = NULL; + FREE_AND_NULL(key_regexp); ret = CONFIG_INVALID_PATTERN; goto free_strings; } @@ -235,15 +235,14 @@ static int get_value(const char *key_, const char *regex_) regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { error("invalid pattern: %s", regex_); - free(regexp); - regexp = NULL; + FREE_AND_NULL(regexp); ret = CONFIG_INVALID_PATTERN; goto free_strings; } } - git_config_with_options(collect_config, &values, - &given_config_source, &config_options); + config_with_options(collect_config, &values, + &given_config_source, &config_options); ret = !values.nr; @@ -320,8 +319,8 @@ static void get_color(const char *var, const char *def_color) get_color_slot = var; get_color_found = 0; parsed_color[0] = '\0'; - git_config_with_options(git_get_color_config, NULL, - &given_config_source, &config_options); + config_with_options(git_get_color_config, NULL, + &given_config_source, &config_options); if (!get_color_found && def_color) { if (color_parse(def_color, parsed_color) < 0) @@ -352,8 +351,8 @@ static int get_colorbool(const char *var, int print) get_colorbool_found = -1; get_diff_color_found = -1; get_color_ui_found = -1; - git_config_with_options(git_get_colorbool_config, NULL, - &given_config_source, &config_options); + config_with_options(git_get_colorbool_config, NULL, + &given_config_source, &config_options); if (get_colorbool_found < 0) { if (!strcmp(get_colorbool_slot, "color.diff")) @@ -441,8 +440,8 @@ static int get_urlmatch(const char *var, const char *url) show_keys = 1; } - git_config_with_options(urlmatch_config_entry, &config, - &given_config_source, &config_options); + config_with_options(urlmatch_config_entry, &config, + &given_config_source, &config_options); ret = !values.nr; @@ -496,6 +495,9 @@ int cmd_config(int argc, const char **argv, const char *prefix) usage_with_options(builtin_config_usage, builtin_config_options); } + if (use_local_config && nongit) + die(_("--local can only be used inside a git repository")); + if (given_config_source.file && !strcmp(given_config_source.file, "-")) { given_config_source.file = NULL; @@ -535,6 +537,10 @@ int cmd_config(int argc, const char **argv, const char *prefix) config_options.respect_includes = !given_config_source.file; else config_options.respect_includes = respect_includes_opt; + if (!nongit) { + config_options.commondir = get_git_common_dir(); + config_options.git_dir = get_git_dir(); + } if (end_null) { term = '\0'; @@ -579,9 +585,9 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (actions == ACTION_LIST) { check_argc(argc, 0, 0); - if (git_config_with_options(show_all_config, NULL, - &given_config_source, - &config_options) < 0) { + if (config_with_options(show_all_config, NULL, + &given_config_source, + &config_options) < 0) { if (given_config_source.file) die_errno("unable to read config file '%s'", given_config_source.file); diff --git a/builtin/count-objects.c b/builtin/count-objects.c index acb05940fc..1d82e61f2a 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -5,6 +5,7 @@ */ #include "cache.h" +#include "config.h" #include "dir.h" #include "builtin.h" #include "parse-options.h" diff --git a/builtin/credential.c b/builtin/credential.c index 0412fa00f0..879acfbcda 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -10,9 +10,9 @@ int cmd_credential(int argc, const char **argv, const char *prefix) const char *op; struct credential c = CREDENTIAL_INIT; - op = argv[1]; - if (!op) + if (argc != 2 || !strcmp(argv[1], "-h")) usage(usage_msg); + op = argv[1]; if (credential_read(&c, stdin) < 0) die("unable to read credential from stdin"); diff --git a/builtin/describe.c b/builtin/describe.c index 893c8789f4..70eb144608 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "commit.h" #include "tag.h" diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 15c61fd8d1..17bf84d18f 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "diff.h" #include "commit.h" #include "revision.h" @@ -20,9 +21,12 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) int result; unsigned options = 0; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(diff_files_usage); + + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ init_revisions(&rev, prefix); gitmodules_config(); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ rev.abbrev = 0; precompose_argv(argc, argv); diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 1af373d002..185e6f9b58 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "diff.h" #include "commit.h" #include "revision.h" @@ -17,9 +18,12 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) int i; int result; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(diff_cache_usage); + + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ init_revisions(&rev, prefix); gitmodules_config(); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ rev.abbrev = 0; precompose_argv(argc, argv); diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 5ea1c14317..31d2cb4107 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "diff.h" #include "commit.h" #include "log-tree.h" @@ -7,7 +8,7 @@ static struct rev_info log_tree_opt; -static int diff_tree_commit_sha1(const struct object_id *oid) +static int diff_tree_commit_oid(const struct object_id *oid) { struct commit *commit = lookup_commit_reference(oid); if (!commit) @@ -49,8 +50,8 @@ static int stdin_diff_trees(struct tree *tree1, const char *p) return -1; printf("%s %s\n", oid_to_hex(&tree1->object.oid), oid_to_hex(&tree2->object.oid)); - diff_tree_sha1(tree1->object.oid.hash, tree2->object.oid.hash, - "", &log_tree_opt.diffopt); + diff_tree_oid(&tree1->object.oid, &tree2->object.oid, + "", &log_tree_opt.diffopt); log_tree_diff_flush(&log_tree_opt); return 0; } @@ -98,16 +99,18 @@ static void diff_tree_tweak_rev(struct rev_info *rev, struct setup_revision_opt int cmd_diff_tree(int argc, const char **argv, const char *prefix) { - int nr_sha1; char line[1000]; struct object *tree1, *tree2; static struct rev_info *opt = &log_tree_opt; struct setup_revision_opt s_r_opt; int read_stdin = 0; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(diff_tree_usage); + + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ init_revisions(opt, prefix); gitmodules_config(); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ opt->abbrev = 0; opt->diff = 1; opt->disable_stdin = 1; @@ -128,19 +131,20 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } /* - * NOTE! We expect "a ^b" to be equal to "a..b", so we - * reverse the order of the objects if the second one - * is marked UNINTERESTING. + * NOTE! We expect "a..b" to expand to "^a b" but it is + * perfectly valid for revision range parser to yield "b ^a", + * which means the same thing. If we get the latter, i.e. the + * second one is marked UNINTERESTING, we recover the original + * order the user gave, i.e. "a..b", by swapping the trees. */ - nr_sha1 = opt->pending.nr; - switch (nr_sha1) { + switch (opt->pending.nr) { case 0: if (!read_stdin) usage(diff_tree_usage); break; case 1: tree1 = opt->pending.objects[0].item; - diff_tree_commit_sha1(&tree1->oid); + diff_tree_commit_oid(&tree1->oid); break; case 2: tree1 = opt->pending.objects[0].item; @@ -148,9 +152,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) if (tree2->flags & UNINTERESTING) { SWAP(tree2, tree1); } - diff_tree_sha1(tree1->oid.hash, - tree2->oid.hash, - "", &opt->diffopt); + diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt); log_tree_diff_flush(opt); break; } diff --git a/builtin/diff.c b/builtin/diff.c index 8c03ddaf58..7cde6abbcf 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -4,6 +4,7 @@ * Copyright (c) 2006 Junio C Hamano */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "color.h" #include "commit.h" @@ -20,23 +21,22 @@ #define DIFF_NO_INDEX_EXPLICIT 1 #define DIFF_NO_INDEX_IMPLICIT 2 -struct blobinfo { - struct object_id oid; - const char *name; - unsigned mode; -}; - static const char builtin_diff_usage[] = "git diff [<options>] [<commit> [<commit>]] [--] [<path>...]"; +static const char *blob_path(struct object_array_entry *entry) +{ + return entry->path ? entry->path : entry->name; +} + static void stuff_change(struct diff_options *opt, unsigned old_mode, unsigned new_mode, const struct object_id *old_oid, const struct object_id *new_oid, int old_oid_valid, int new_oid_valid, - const char *old_name, - const char *new_name) + const char *old_path, + const char *new_path) { struct diff_filespec *one, *two; @@ -47,25 +47,25 @@ static void stuff_change(struct diff_options *opt, if (DIFF_OPT_TST(opt, REVERSE_DIFF)) { SWAP(old_mode, new_mode); SWAP(old_oid, new_oid); - SWAP(old_name, new_name); + SWAP(old_path, new_path); } if (opt->prefix && - (strncmp(old_name, opt->prefix, opt->prefix_length) || - strncmp(new_name, opt->prefix, opt->prefix_length))) + (strncmp(old_path, opt->prefix, opt->prefix_length) || + strncmp(new_path, opt->prefix, opt->prefix_length))) return; - one = alloc_filespec(old_name); - two = alloc_filespec(new_name); - fill_filespec(one, old_oid->hash, old_oid_valid, old_mode); - fill_filespec(two, new_oid->hash, new_oid_valid, new_mode); + one = alloc_filespec(old_path); + two = alloc_filespec(new_path); + fill_filespec(one, old_oid, old_oid_valid, old_mode); + fill_filespec(two, new_oid, new_oid_valid, new_mode); diff_queue(&diff_queued_diff, one, two); } static int builtin_diff_b_f(struct rev_info *revs, int argc, const char **argv, - struct blobinfo *blob) + struct object_array_entry **blob) { /* Blob vs file in the working tree*/ struct stat st; @@ -84,14 +84,15 @@ static int builtin_diff_b_f(struct rev_info *revs, diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/"); - if (blob[0].mode == S_IFINVALID) - blob[0].mode = canon_mode(st.st_mode); + if (blob[0]->mode == S_IFINVALID) + blob[0]->mode = canon_mode(st.st_mode); stuff_change(&revs->diffopt, - blob[0].mode, canon_mode(st.st_mode), - &blob[0].oid, &null_oid, + blob[0]->mode, canon_mode(st.st_mode), + &blob[0]->item->oid, &null_oid, 1, 0, - path, path); + blob[0]->path ? blob[0]->path : path, + path); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); return 0; @@ -99,24 +100,24 @@ static int builtin_diff_b_f(struct rev_info *revs, static int builtin_diff_blobs(struct rev_info *revs, int argc, const char **argv, - struct blobinfo *blob) + struct object_array_entry **blob) { unsigned mode = canon_mode(S_IFREG | 0644); if (argc > 1) usage(builtin_diff_usage); - if (blob[0].mode == S_IFINVALID) - blob[0].mode = mode; + if (blob[0]->mode == S_IFINVALID) + blob[0]->mode = mode; - if (blob[1].mode == S_IFINVALID) - blob[1].mode = mode; + if (blob[1]->mode == S_IFINVALID) + blob[1]->mode = mode; stuff_change(&revs->diffopt, - blob[0].mode, blob[1].mode, - &blob[0].oid, &blob[1].oid, + blob[0]->mode, blob[1]->mode, + &blob[0]->item->oid, &blob[1]->item->oid, 1, 1, - blob[0].name, blob[1].name); + blob_path(blob[0]), blob_path(blob[1])); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); return 0; @@ -174,7 +175,7 @@ static int builtin_diff_tree(struct rev_info *revs, swap = 1; oid[swap] = &ent0->item->oid; oid[1 - swap] = &ent1->item->oid; - diff_tree_sha1(oid[0]->hash, oid[1]->hash, "", &revs->diffopt); + diff_tree_oid(oid[0], oid[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; } @@ -194,7 +195,7 @@ static int builtin_diff_combined(struct rev_info *revs, revs->dense_combined_merges = revs->combine_merges = 1; for (i = 1; i < ents; i++) oid_array_append(&parents, &ent[i].item->oid); - diff_tree_combined(ent[0].item->oid.hash, &parents, + diff_tree_combined(&ent[0].item->oid, &parents, revs->dense_combined_merges, revs); oid_array_clear(&parents); return 0; @@ -259,7 +260,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) struct rev_info rev; struct object_array ent = OBJECT_ARRAY_INIT; int blobs = 0, paths = 0; - struct blobinfo blob[2]; + struct object_array_entry *blob[2]; int nongit = 0, no_index = 0; int result = 0; @@ -408,9 +409,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } else if (obj->type == OBJ_BLOB) { if (2 <= blobs) die(_("more than two blobs given: '%s'"), name); - oidcpy(&blob[blobs].oid, &obj->oid); - blob[blobs].name = name; - blob[blobs].mode = entry->mode; + blob[blobs] = entry; blobs++; } else { diff --git a/builtin/difftool.c b/builtin/difftool.c index 1354d0e462..9199227f6e 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -12,6 +12,7 @@ * Copyright (C) 2016 Johannes Schindelin */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "run-command.h" #include "exec_cmd.h" @@ -226,6 +227,7 @@ static void changed_files(struct hashmap *result, const char *index_path, hashmap_entry_init(entry, strhash(buf.buf)); hashmap_add(result, entry); } + fclose(fp); if (finish_command(&diff_files)) die("diff-files did not exit properly"); strbuf_release(&index_env); @@ -439,8 +441,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, } if (lmode && status != 'C') { - if (checkout_path(lmode, &loid, src_path, &lstate)) - return error("could not write '%s'", src_path); + if (checkout_path(lmode, &loid, src_path, &lstate)) { + ret = error("could not write '%s'", src_path); + goto finish; + } } if (rmode && !S_ISLNK(rmode)) { @@ -456,9 +460,12 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, hashmap_add(&working_tree_dups, entry); if (!use_wt_file(workdir, dst_path, &roid)) { - if (checkout_path(rmode, &roid, dst_path, &rstate)) - return error("could not write '%s'", - dst_path); + if (checkout_path(rmode, &roid, dst_path, + &rstate)) { + ret = error("could not write '%s'", + dst_path); + goto finish; + } } else if (!is_null_oid(&roid)) { /* * Changes in the working tree need special @@ -473,10 +480,12 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, ADD_CACHE_JUST_APPEND); add_path(&rdir, rdir_len, dst_path); - if (ensure_leading_directories(rdir.buf)) - return error("could not create " - "directory for '%s'", - dst_path); + if (ensure_leading_directories(rdir.buf)) { + ret = error("could not create " + "directory for '%s'", + dst_path); + goto finish; + } add_path(&wtdir, wtdir_len, dst_path); if (symlinks) { if (symlink(wtdir.buf, rdir.buf)) { @@ -497,13 +506,15 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, } } + fclose(fp); + fp = NULL; if (finish_command(&child)) { ret = error("error occurred running diff --raw"); goto finish; } if (!i) - return 0; + goto finish; /* * Changes to submodules require special treatment.This loop writes a @@ -626,6 +637,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, exit_cleanup(tmpdir, rc); finish: + if (fp) + fclose(fp); + free(lbase_dir); free(rbase_dir); strbuf_release(&ldir); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 969550531b..12d501bfde 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -5,6 +5,7 @@ */ #include "builtin.h" #include "cache.h" +#include "config.h" #include "refs.h" #include "commit.h" #include "object.h" @@ -562,12 +563,12 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) get_object_mark(&commit->parents->item->object) != 0 && !full_tree) { parse_commit_or_die(commit->parents->item); - diff_tree_sha1(commit->parents->item->tree->object.oid.hash, - commit->tree->object.oid.hash, "", &rev->diffopt); + diff_tree_oid(&commit->parents->item->tree->object.oid, + &commit->tree->object.oid, "", &rev->diffopt); } else - diff_root_tree_sha1(commit->tree->object.oid.hash, - "", &rev->diffopt); + diff_root_tree_oid(&commit->tree->object.oid, + "", &rev->diffopt); /* Export the referenced blobs, and remember the marks. */ for (i = 0; i < diff_queued_diff.nr; i++) @@ -734,6 +735,7 @@ static void handle_tag(const char *name, struct tag *tag) oid_to_hex(&tag->object.oid)); case DROP: /* Ignore this tag altogether */ + free(buf); return; case REWRITE: if (tagged->type != OBJ_COMMIT) { @@ -765,6 +767,7 @@ static void handle_tag(const char *name, struct tag *tag) (int)(tagger_end - tagger), tagger, tagger == tagger_end ? "" : "\n", (int)message_size, (int)message_size, message ? message : ""); + free(buf); } static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name) @@ -905,9 +908,7 @@ static void export_marks(char *file) static void import_marks(char *input_file) { char line[512]; - FILE *f = fopen(input_file, "r"); - if (!f) - die_errno("cannot read '%s'", input_file); + FILE *f = xfopen(input_file, "r"); while (fgets(line, sizeof(line), f)) { uint32_t mark; diff --git a/builtin/fetch.c b/builtin/fetch.c index 47708451bc..16cf8421ce 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -2,6 +2,7 @@ * "git fetch" */ #include "cache.h" +#include "config.h" #include "refs.h" #include "commit.h" #include "builtin.h" @@ -73,6 +74,13 @@ static int git_fetch_config(const char *k, const char *v, void *cb) fetch_prune_config = git_config_bool(k, v); return 0; } + + if (!strcmp(k, "submodule.recurse")) { + int r = git_config_bool(k, v) ? + RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; + recurse_submodules = r; + } + return git_default_config(k, v, cb); } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 70137b0b7e..10cbb43416 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "refs.h" #include "commit.h" #include "diff.h" diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index eca365bf89..52be99cbac 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "refs.h" #include "object.h" #include "parse-options.h" diff --git a/builtin/fsck.c b/builtin/fsck.c index cb2ba6cd1b..87c6756899 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "tree.h" #include "blob.h" @@ -280,8 +281,7 @@ static void check_unreachable_object(struct object *obj) free(filename); return; } - if (!(f = fopen(filename, "w"))) - die_errno("Could not open '%s'", filename); + f = xfopen(filename, "w"); if (obj->type == OBJ_BLOB) { if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) die_errno("Could not write '%s'", filename); diff --git a/builtin/gc.c b/builtin/gc.c index f484eda43c..bd91f136fe 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -11,6 +11,7 @@ */ #include "builtin.h" +#include "config.h" #include "tempfile.h" #include "lockfile.h" #include "parse-options.h" diff --git a/builtin/grep.c b/builtin/grep.c index e64e14e945..f61a9d938b 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -4,6 +4,7 @@ * Copyright (c) 2006 Junio C Hamano */ #include "cache.h" +#include "config.h" #include "blob.h" #include "tree.h" #include "commit.h" @@ -73,14 +74,14 @@ static pthread_mutex_t grep_mutex; static inline void grep_lock(void) { - if (num_threads) - pthread_mutex_lock(&grep_mutex); + assert(num_threads); + pthread_mutex_lock(&grep_mutex); } static inline void grep_unlock(void) { - if (num_threads) - pthread_mutex_unlock(&grep_mutex); + assert(num_threads); + pthread_mutex_unlock(&grep_mutex); } /* Signalled when a new work_item is added to todo. */ @@ -224,7 +225,8 @@ static void start_threads(struct grep_opt *opt) int err; struct grep_opt *o = grep_opt_dup(opt); o->output = strbuf_out; - o->debug = 0; + if (i) + o->debug = 0; compile_grep_patterns(o); err = pthread_create(&threads[i], NULL, run, o); @@ -289,8 +291,22 @@ static int grep_cmd_config(const char *var, const char *value, void *cb) if (num_threads < 0) die(_("invalid number of threads specified (%d) for %s"), num_threads, var); +#ifdef NO_PTHREADS + else if (num_threads && num_threads != 1) { + /* + * TRANSLATORS: %s is the configuration + * variable for tweaking threads, currently + * grep.threads + */ + warning(_("no threads support, ignoring %s"), var); + num_threads = 0; + } +#endif } + if (!strcmp(var, "submodule.recurse")) + recurse_submodules = git_config_bool(var, value); + return st; } @@ -327,7 +343,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid, #ifndef NO_PTHREADS if (num_threads) { - add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, oid); + add_work(opt, GREP_SOURCE_OID, pathbuf.buf, path, oid); strbuf_release(&pathbuf); return 0; } else @@ -336,7 +352,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid, struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, oid); + grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid); strbuf_release(&pathbuf); hit = grep_source(opt, &gs); @@ -495,6 +511,8 @@ static void compile_submodule_options(const struct grep_opt *opt, break; case GREP_PATTERN_TYPE_UNSPECIFIED: break; + default: + die("BUG: Added a new grep pattern type without updating switch statement"); } for (pattern = opt->pattern_list; pattern != NULL; @@ -570,7 +588,7 @@ static int grep_submodule_launch(struct grep_opt *opt, * with the object's name: 'tree-name:filename'. In order to * provide uniformity of output we want to pass the name of the * parent project's object name to the submodule so the submodule can - * prefix its output with the parent's name and not its own SHA1. + * prefix its output with the parent's name and not its own OID. */ if (gs->identifier && end_of_base) argv_array_pushf(&cp.args, "--parent-basename=%.*s", @@ -583,12 +601,12 @@ static int grep_submodule_launch(struct grep_opt *opt, * If there is a tree identifier for the submodule, add the * rev after adding the submodule options but before the * pathspecs. To do this we listen for the '--' and insert the - * sha1 before pushing the '--' onto the child process argv + * oid before pushing the '--' onto the child process argv * array. */ if (gs->identifier && !strcmp("--", submodule_options.argv[i])) { - argv_array_push(&cp.args, sha1_to_hex(gs->identifier)); + argv_array_push(&cp.args, oid_to_hex(gs->identifier)); } argv_array_push(&cp.args, submodule_options.argv[i]); @@ -618,11 +636,11 @@ static int grep_submodule_launch(struct grep_opt *opt, /* * Prep grep structures for a submodule grep - * sha1: the sha1 of the submodule or NULL if using the working tree + * oid: the oid of the submodule or NULL if using the working tree * filename: name of the submodule including tree name of parent * path: location of the submodule */ -static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, +static int grep_submodule(struct grep_opt *opt, const struct object_id *oid, const char *filename, const char *path) { if (!is_submodule_initialized(path)) @@ -632,7 +650,7 @@ static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, * If searching history, check for the presense of the * submodule's gitdir before skipping the submodule. */ - if (sha1) { + if (oid) { const struct submodule *sub = submodule_from_path(null_sha1, path); if (sub) @@ -647,7 +665,7 @@ static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, #ifndef NO_PTHREADS if (num_threads) { - add_work(opt, GREP_SOURCE_SUBMODULE, filename, path, sha1); + add_work(opt, GREP_SOURCE_SUBMODULE, filename, path, oid); return 0; } else #endif @@ -656,7 +674,7 @@ static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, int hit; grep_source_init(&gs, GREP_SOURCE_SUBMODULE, - filename, path, sha1); + filename, path, oid); hit = grep_submodule_launch(opt, &gs); grep_source_clear(&gs); @@ -775,7 +793,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, check_attr); free(data); } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { - hit |= grep_submodule(opt, entry.oid->hash, base->buf, + hit |= grep_submodule(opt, entry.oid, base->buf, base->buf + tn_len); } @@ -866,7 +884,7 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, if (exc_std) setup_standard_excludes(&dir); - fill_directory(&dir, pathspec); + fill_directory(&dir, &the_index, pathspec); for (i = 0; i < dir.nr; i++) { if (!dir_path_match(dir.entries[i], pathspec, 0, NULL)) continue; @@ -1154,8 +1172,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!opt.fixed && opt.ignore_case) opt.regflags |= REG_ICASE; - compile_grep_patterns(&opt); - /* * We have to find "--" in a separate pass, because its presence * influences how we will parse arguments that come before it. @@ -1190,7 +1206,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) break; } - if (get_sha1_with_context(arg, 0, oid.hash, &oc)) { + if (get_sha1_with_context(arg, GET_SHA1_RECORD_PATH, + oid.hash, &oc)) { if (seen_dashdash) die(_("unable to resolve revision: %s"), arg); break; @@ -1200,6 +1217,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!seen_dashdash) verify_non_filename(prefix, arg); add_object_array_with_path(object, arg, &list, oc.mode, oc.path); + free(oc.path); } /* @@ -1226,10 +1244,23 @@ int cmd_grep(int argc, const char **argv, const char *prefix) num_threads = GREP_NUM_THREADS_DEFAULT; else if (num_threads < 0) die(_("invalid number of threads specified (%d)"), num_threads); + if (num_threads == 1) + num_threads = 0; #else + if (num_threads) + warning(_("no threads support, ignoring --threads")); num_threads = 0; #endif + if (!num_threads) + /* + * The compiled patterns on the main path are only + * used when not using threading. Otherwise + * start_threads() below calls compile_grep_patterns() + * for each thread. + */ + compile_grep_patterns(&opt); + #ifndef NO_PTHREADS if (num_threads) { if (!(opt.name_only || opt.unmatch_name_only || opt.count) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index bbeaf20bcc..d04baf999a 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -5,6 +5,7 @@ * Copyright (C) Junio C Hamano, 2005 */ #include "builtin.h" +#include "config.h" #include "blob.h" #include "quote.h" #include "parse-options.h" diff --git a/builtin/help.c b/builtin/help.c index 49f7a07f85..334a8494ab 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -2,6 +2,7 @@ * Builtin help command */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 04b9dcaf0f..ad4de35178 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "delta.h" #include "pack.h" #include "csum-file.h" @@ -388,8 +389,7 @@ static struct base_data *alloc_base_data(void) static void free_base_data(struct base_data *c) { if (c->data) { - free(c->data); - c->data = NULL; + FREE_AND_NULL(c->data); get_thread_data()->base_cache_used -= c->size; } } @@ -605,8 +605,7 @@ static void *unpack_data(struct object_entry *obj, git_inflate_end(&stream); free(inbuf); if (consume) { - free(data); - data = NULL; + FREE_AND_NULL(data); } return data; } diff --git a/builtin/init-db.c b/builtin/init-db.c index 8a6acb0ec6..47823f9aa4 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "refs.h" #include "builtin.h" #include "exec_cmd.h" diff --git a/builtin/log.c b/builtin/log.c index a440601efe..8ca1de9894 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -5,6 +5,7 @@ * 2006 Junio Hamano */ #include "cache.h" +#include "config.h" #include "refs.h" #include "color.h" #include "commit.h" @@ -483,16 +484,20 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV)) return stream_blob_to_fd(1, oid, NULL, 0); - if (get_sha1_with_context(obj_name, 0, oidc.hash, &obj_context)) + if (get_sha1_with_context(obj_name, GET_SHA1_RECORD_PATH, + oidc.hash, &obj_context)) die(_("Not a valid object name %s"), obj_name); - if (!obj_context.path[0] || - !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) + if (!obj_context.path || + !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) { + free(obj_context.path); return stream_blob_to_fd(1, oid, NULL, 0); + } if (!buf) die(_("git show %s: bad file"), obj_name); write_or_die(1, buf, size); + free(obj_context.path); return 0; } @@ -842,8 +847,10 @@ static int open_next_file(struct commit *commit, const char *subject, if (output_directory) { strbuf_addstr(&filename, output_directory); if (filename.len >= - PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) + PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) { + strbuf_release(&filename); return error(_("name of output directory is too long")); + } strbuf_complete(&filename, '/'); } @@ -857,8 +864,11 @@ static int open_next_file(struct commit *commit, const char *subject, if (!quiet) printf("%s\n", filename.buf + outdir_offset); - if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) - return error(_("Cannot open patch file %s"), filename.buf); + if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) { + error_errno(_("Cannot open patch file %s"), filename.buf); + strbuf_release(&filename); + return -1; + } strbuf_release(&filename); return 0; @@ -1043,9 +1053,9 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, diff_setup_done(&opts); - diff_tree_sha1(origin->tree->object.oid.hash, - head->tree->object.oid.hash, - "", &opts); + diff_tree_oid(&origin->tree->object.oid, + &head->tree->object.oid, + "", &opts); diffcore_std(&opts); diff_flush(&opts); @@ -1354,7 +1364,7 @@ static void prepare_bases(struct base_tree_info *bases, struct object_id *patch_id; if (commit->util) continue; - if (commit_patch_id(commit, &diffopt, oid.hash, 0)) + if (commit_patch_id(commit, &diffopt, &oid, 0)) die(_("cannot get patch id")); ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id); patch_id = bases->patch_id + bases->nr_patch_id; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index f20edabe6d..b12d0bb612 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -6,6 +6,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "quote.h" #include "dir.h" #include "builtin.h" @@ -53,17 +54,17 @@ static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; -static void write_eolinfo(const struct cache_entry *ce, const char *path) +static void write_eolinfo(const struct index_state *istate, + const struct cache_entry *ce, const char *path) { - if (!show_eol) - return; - else { + if (show_eol) { struct stat st; const char *i_txt = ""; const char *w_txt = ""; const char *a_txt = get_convert_attr_ascii(path); if (ce && S_ISREG(ce->ce_mode)) - i_txt = get_cached_convert_stats_ascii(ce->name); + i_txt = get_cached_convert_stats_ascii(istate, + ce->name); if (!lstat(path, &st) && S_ISREG(st.st_mode)) w_txt = get_wt_convert_stats_ascii(path); printf("i/%-5s w/%-5s attr/%-17s\t", i_txt, w_txt, a_txt); @@ -93,34 +94,73 @@ static void write_name(const char *name) strbuf_release(&full_name); } +static const char *get_tag(const struct cache_entry *ce, const char *tag) +{ + static char alttag[4]; + + if (tag && *tag && show_valid_bit && (ce->ce_flags & CE_VALID)) { + memcpy(alttag, tag, 3); + + if (isalpha(tag[0])) { + alttag[0] = tolower(tag[0]); + } else if (tag[0] == '?') { + alttag[0] = '!'; + } else { + alttag[0] = 'v'; + alttag[1] = tag[0]; + alttag[2] = ' '; + alttag[3] = 0; + } + + tag = alttag; + } + + return tag; +} + +static void print_debug(const struct cache_entry *ce) +{ + if (debug_mode) { + const struct stat_data *sd = &ce->ce_stat_data; + + printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); + printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); + printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); + printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); + printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); + } +} + static void show_dir_entry(const char *tag, struct dir_entry *ent) { int len = max_prefix_len; - if (len >= ent->len) + if (len > ent->len) die("git ls-files: internal error - directory entry not superset of prefix"); if (!dir_path_match(ent, &pathspec, len, ps_matched)) return; fputs(tag, stdout); - write_eolinfo(NULL, ent->name); + write_eolinfo(NULL, NULL, ent->name); write_name(ent->name); } -static void show_other_files(struct dir_struct *dir) +static void show_other_files(const struct index_state *istate, + const struct dir_struct *dir) { int i; for (i = 0; i < dir->nr; i++) { struct dir_entry *ent = dir->entries[i]; - if (!cache_name_is_other(ent->name, ent->len)) + if (!index_name_is_other(istate, ent->name, ent->len)) continue; show_dir_entry(tag_other, ent); } } -static void show_killed_files(struct dir_struct *dir) +static void show_killed_files(const struct index_state *istate, + const struct dir_struct *dir) { int i; for (i = 0; i < dir->nr; i++) { @@ -134,29 +174,29 @@ static void show_killed_files(struct dir_struct *dir) /* If ent->name is prefix of an entry in the * cache, it will be killed. */ - pos = cache_name_pos(ent->name, ent->len); + pos = index_name_pos(istate, ent->name, ent->len); if (0 <= pos) die("BUG: killed-file %.*s not found", ent->len, ent->name); pos = -pos - 1; - while (pos < active_nr && - ce_stage(active_cache[pos])) + while (pos < istate->cache_nr && + ce_stage(istate->cache[pos])) pos++; /* skip unmerged */ - if (active_nr <= pos) + if (istate->cache_nr <= pos) break; /* pos points at a name immediately after * ent->name in the cache. Does it expect * ent->name to be a directory? */ - len = ce_namelen(active_cache[pos]); + len = ce_namelen(istate->cache[pos]); if ((ent->len < len) && - !strncmp(active_cache[pos]->name, + !strncmp(istate->cache[pos]->name, ent->name, ent->len) && - active_cache[pos]->name[ent->len] == '/') + istate->cache[pos]->name[ent->len] == '/') killed = 1; break; } - if (0 <= cache_name_pos(ent->name, sp - ent->name)) { + if (0 <= index_name_pos(istate, ent->name, sp - ent->name)) { /* If any of the leading directories in * ent->name is registered in the cache, * ent->name will be killed. @@ -230,7 +270,8 @@ static void show_gitlink(const struct cache_entry *ce) exit(status); } -static void show_ce_entry(const char *tag, const struct cache_entry *ce) +static void show_ce_entry(const struct index_state *istate, + const char *tag, const struct cache_entry *ce) { struct strbuf name = STRBUF_INIT; int len = max_prefix_len; @@ -238,7 +279,7 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce) strbuf_addstr(&name, super_prefix); strbuf_addstr(&name, ce->name); - if (len >= ce_namelen(ce)) + if (len > ce_namelen(ce)) die("git ls-files: internal error - cache entry not superset of prefix"); if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && @@ -248,22 +289,7 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce) len, ps_matched, S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) { - if (tag && *tag && show_valid_bit && - (ce->ce_flags & CE_VALID)) { - static char alttag[4]; - memcpy(alttag, tag, 3); - if (isalpha(tag[0])) - alttag[0] = tolower(tag[0]); - else if (tag[0] == '?') - alttag[0] = '!'; - else { - alttag[0] = 'v'; - alttag[1] = tag[0]; - alttag[2] = ' '; - alttag[3] = 0; - } - tag = alttag; - } + tag = get_tag(ce, tag); if (!show_stage) { fputs(tag, stdout); @@ -274,30 +300,22 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce) find_unique_abbrev(ce->oid.hash, abbrev), ce_stage(ce)); } - write_eolinfo(ce, ce->name); + write_eolinfo(istate, ce, ce->name); write_name(ce->name); - if (debug_mode) { - const struct stat_data *sd = &ce->ce_stat_data; - - printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); - printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); - printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); - printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); - printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); - } + print_debug(ce); } strbuf_release(&name); } -static void show_ru_info(void) +static void show_ru_info(const struct index_state *istate) { struct string_list_item *item; - if (!the_index.resolve_undo) + if (!istate->resolve_undo) return; - for_each_string_list_item(item, the_index.resolve_undo) { + for_each_string_list_item(item, istate->resolve_undo) { const char *path = item->string; struct resolve_undo_info *ui = item->util; int i, len; @@ -319,13 +337,14 @@ static void show_ru_info(void) } } -static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce) +static int ce_excluded(struct dir_struct *dir, struct index_state *istate, + const struct cache_entry *ce) { int dtype = ce_to_dtype(ce); - return is_excluded(dir, ce->name, &dtype); + return is_excluded(dir, istate, ce->name, &dtype); } -static void show_files(struct dir_struct *dir) +static void show_files(struct index_state *istate, struct dir_struct *dir) { int i; @@ -333,33 +352,33 @@ static void show_files(struct dir_struct *dir) if (show_others || show_killed) { if (!show_others) dir->flags |= DIR_COLLECT_KILLED_ONLY; - fill_directory(dir, &pathspec); + fill_directory(dir, istate, &pathspec); if (show_others) - show_other_files(dir); + show_other_files(istate, dir); if (show_killed) - show_killed_files(dir); + show_killed_files(istate, dir); } if (show_cached || show_stage) { - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, ce)) + !ce_excluded(dir, istate, ce)) continue; if (show_unmerged && !ce_stage(ce)) continue; if (ce->ce_flags & CE_UPDATE) continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : + show_ce_entry(istate, ce_stage(ce) ? tag_unmerged : (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); } } if (show_deleted || show_modified) { - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; struct stat st; int err; if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, ce)) + !ce_excluded(dir, istate, ce)) continue; if (ce->ce_flags & CE_UPDATE) continue; @@ -367,9 +386,9 @@ static void show_files(struct dir_struct *dir) continue; err = lstat(ce->name, &st); if (show_deleted && err) - show_ce_entry(tag_removed, ce); - if (show_modified && ce_modified(ce, &st, 0)) - show_ce_entry(tag_modified, ce); + show_ce_entry(istate, tag_removed, ce); + if (show_modified && ie_modified(istate, ce, &st, 0)) + show_ce_entry(istate, tag_modified, ce); } } } @@ -377,30 +396,50 @@ static void show_files(struct dir_struct *dir) /* * Prune the index to only contain stuff starting with "prefix" */ -static void prune_cache(const char *prefix, size_t prefixlen) +static void prune_index(struct index_state *istate, + const char *prefix, size_t prefixlen) { int pos; unsigned int first, last; if (!prefix) return; - pos = cache_name_pos(prefix, prefixlen); + pos = index_name_pos(istate, prefix, prefixlen); if (pos < 0) pos = -pos-1; first = pos; - last = active_nr; + last = istate->cache_nr; while (last > first) { int next = (last + first) >> 1; - const struct cache_entry *ce = active_cache[next]; + const struct cache_entry *ce = istate->cache[next]; if (!strncmp(ce->name, prefix, prefixlen)) { first = next+1; continue; } last = next; } - memmove(active_cache, active_cache + pos, + memmove(istate->cache, istate->cache + pos, (last - pos) * sizeof(struct cache_entry *)); - active_nr = last - pos; + istate->cache_nr = last - pos; +} + +static int get_common_prefix_len(const char *common_prefix) +{ + int common_prefix_len; + + if (!common_prefix) + return 0; + + common_prefix_len = strlen(common_prefix); + + /* + * If the prefix has a trailing slash, strip it so that submodules wont + * be pruned from the index. + */ + if (common_prefix[common_prefix_len - 1] == '/') + common_prefix_len--; + + return common_prefix_len; } /* @@ -411,7 +450,8 @@ static void prune_cache(const char *prefix, size_t prefixlen) * that were given from the command line. We are not * going to write this index out. */ -void overlay_tree_on_cache(const char *tree_name, const char *prefix) +void overlay_tree_on_index(struct index_state *istate, + const char *tree_name, const char *prefix) { struct tree *tree; struct object_id oid; @@ -426,8 +466,8 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) die("bad tree-ish %s", tree_name); /* Hoist the unmerged entries up to stage #3 to make room */ - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; if (!ce_stage(ce)) continue; ce->ce_flags |= CE_STAGEMASK; @@ -440,11 +480,11 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) PATHSPEC_PREFER_CWD, prefix, matchbuf); } else memset(&pathspec, 0, sizeof(pathspec)); - if (read_tree(tree, 1, &pathspec)) + if (read_tree(tree, 1, &pathspec, istate)) die("unable to read tree entries %s", tree_name); - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; switch (ce_stage(ce)) { case 0: last_stage0 = ce; @@ -624,8 +664,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) "--error-unmatch"); parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + PATHSPEC_PREFER_CWD, prefix, argv); /* @@ -637,7 +676,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) max_prefix = NULL; else max_prefix = common_prefix(&pathspec); - max_prefix_len = max_prefix ? strlen(max_prefix) : 0; + max_prefix_len = get_common_prefix_len(max_prefix); + + prune_index(&the_index, max_prefix, max_prefix_len); /* Treat unmatching pathspec elements as errors */ if (pathspec.nr && error_unmatch) @@ -651,7 +692,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) show_killed || show_modified || show_resolve_undo)) show_cached = 1; - prune_cache(max_prefix, max_prefix_len); if (with_tree) { /* * Basic sanity check; show-stages and show-unmerged @@ -659,11 +699,11 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) */ if (show_stage || show_unmerged) die("ls-files --with-tree is incompatible with -s or -u"); - overlay_tree_on_cache(with_tree, max_prefix); + overlay_tree_on_index(&the_index, with_tree, max_prefix); } - show_files(&dir); + show_files(&the_index, &dir); if (show_resolve_undo) - show_ru_info(); + show_ru_info(&the_index); if (ps_matched) { int bad; diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index ee7b293b11..ef965408e8 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "blob.h" #include "tree.h" #include "commit.h" diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 30681681c1..664400b816 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -232,6 +232,16 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, do { peek = fgetc(f); + if (peek == EOF) { + if (f == stdin) + /* empty stdin is OK */ + ret = skip; + else { + fclose(f); + error(_("empty mbox: '%s'"), file); + } + goto out; + } } while (isspace(peek)); ungetc(peek, f); diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 0c36a70ad8..6dbd167d3b 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "diff.h" diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 47dde7c39c..b08803e611 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" #include "parse-options.h" diff --git a/builtin/merge.c b/builtin/merge.c index a4a098f40f..900bafdb45 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -7,6 +7,7 @@ */ #include "cache.h" +#include "config.h" #include "parse-options.h" #include "builtin.h" #include "lockfile.h" @@ -415,7 +416,7 @@ static void finish(struct commit *head_commit, DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; diff_setup_done(&opts); - diff_tree_sha1(head->hash, new_head->hash, "", &opts); + diff_tree_oid(head, new_head, "", &opts); diffcore_std(&opts); diff_flush(&opts); } @@ -839,9 +840,7 @@ static int suggest_conflicts(void) struct strbuf msgbuf = STRBUF_INIT; filename = git_path_merge_msg(); - fp = fopen(filename, "a"); - if (!fp) - die_errno(_("Could not open '%s' for writing"), filename); + fp = xfopen(filename, "a"); append_conflicts_hint(&msgbuf); fputs(msgbuf.buf, fp); diff --git a/builtin/mktree.c b/builtin/mktree.c index de9b40fc63..da0fd8cd70 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -72,7 +72,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss unsigned mode; enum object_type mode_type; /* object type derived from mode */ enum object_type obj_type; /* object type derived from sha */ - char *path; + char *path, *to_free = NULL; unsigned char sha1[20]; ptr = buf; @@ -102,7 +102,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss struct strbuf p_uq = STRBUF_INIT; if (unquote_c_style(&p_uq, path, NULL)) die("invalid quoting"); - path = strbuf_detach(&p_uq, NULL); + path = to_free = strbuf_detach(&p_uq, NULL); } /* @@ -136,6 +136,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss } append_to_tree(mode, sha1, path); + free(to_free); } int cmd_mktree(int ac, const char **av, const char *prefix) diff --git a/builtin/mv.c b/builtin/mv.c index 61d20037ad..dcf6736b5b 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -4,6 +4,7 @@ * Copyright (C) 2006 Johannes Schindelin */ #include "builtin.h" +#include "config.h" #include "pathspec.h" #include "lockfile.h" #include "dir.h" diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 1bf4cc4ebb..e21715f1d0 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "tag.h" #include "refs.h" @@ -13,21 +14,61 @@ typedef struct rev_name { timestamp_t taggerdate; int generation; int distance; + int from_tag; } rev_name; -static long cutoff = LONG_MAX; +static timestamp_t cutoff = TIME_MAX; /* How many generations are maximally preferred over _one_ merge traversal? */ #define MERGE_TRAVERSAL_WEIGHT 65535 +static int is_better_name(struct rev_name *name, + const char *tip_name, + timestamp_t taggerdate, + int generation, + int distance, + int from_tag) +{ + /* + * When comparing names based on tags, prefer names + * based on the older tag, even if it is farther away. + */ + if (from_tag && name->from_tag) + return (name->taggerdate > taggerdate || + (name->taggerdate == taggerdate && + name->distance > distance)); + + /* + * We know that at least one of them is a non-tag at this point. + * favor a tag over a non-tag. + */ + if (name->from_tag != from_tag) + return from_tag; + + /* + * We are now looking at two non-tags. Tiebreak to favor + * shorter hops. + */ + if (name->distance != distance) + return name->distance > distance; + + /* ... or tiebreak to favor older date */ + if (name->taggerdate != taggerdate) + return name->taggerdate > taggerdate; + + /* keep the current one if we cannot decide */ + return 0; +} + static void name_rev(struct commit *commit, const char *tip_name, timestamp_t taggerdate, - int generation, int distance, + int generation, int distance, int from_tag, int deref) { struct rev_name *name = (struct rev_name *)commit->util; struct commit_list *parents; int parent_number = 1; + char *to_free = NULL; parse_commit(commit); @@ -35,7 +76,7 @@ static void name_rev(struct commit *commit, return; if (deref) { - tip_name = xstrfmt("%s^0", tip_name); + tip_name = to_free = xstrfmt("%s^0", tip_name); if (generation) die("generation: %d, but deref?", generation); @@ -45,16 +86,18 @@ static void name_rev(struct commit *commit, name = xmalloc(sizeof(rev_name)); commit->util = name; goto copy_data; - } else if (name->taggerdate > taggerdate || - (name->taggerdate == taggerdate && - name->distance > distance)) { + } else if (is_better_name(name, tip_name, taggerdate, + generation, distance, from_tag)) { copy_data: name->tip_name = tip_name; name->taggerdate = taggerdate; name->generation = generation; name->distance = distance; - } else + name->from_tag = from_tag; + } else { + free(to_free); return; + } for (parents = commit->parents; parents; @@ -72,10 +115,12 @@ copy_data: parent_number); name_rev(parents->item, new_name, taggerdate, 0, - distance + MERGE_TRAVERSAL_WEIGHT, 0); + distance + MERGE_TRAVERSAL_WEIGHT, + from_tag, 0); } else { name_rev(parents->item, tip_name, taggerdate, - generation + 1, distance + 1, 0); + generation + 1, distance + 1, + from_tag, 0); } } } @@ -206,9 +251,13 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo } if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; + int from_tag = starts_with(path, "refs/tags/"); + if (taggerdate == ULONG_MAX) + taggerdate = ((struct commit *)o)->date; path = name_ref_abbrev(path, can_abbreviate_output); - name_rev(commit, xstrdup(path), taggerdate, 0, 0, deref); + name_rev(commit, xstrdup(path), taggerdate, 0, 0, + from_tag, deref); } return 0; } diff --git a/builtin/notes.c b/builtin/notes.c index f2847c41e0..77573cf1ea 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -8,6 +8,7 @@ */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "notes.h" #include "blob.h" @@ -109,11 +110,11 @@ static void free_note_data(struct note_data *d) strbuf_release(&d->buf); } -static int list_each_note(const unsigned char *object_sha1, - const unsigned char *note_sha1, char *note_path, +static int list_each_note(const struct object_id *object_oid, + const struct object_id *note_oid, char *note_path, void *cb_data) { - printf("%s %s\n", sha1_to_hex(note_sha1), sha1_to_hex(object_sha1)); + printf("%s %s\n", oid_to_hex(note_oid), oid_to_hex(object_oid)); return 0; } @@ -129,10 +130,10 @@ static void copy_obj_to_fd(int fd, const unsigned char *sha1) } } -static void write_commented_object(int fd, const unsigned char *object) +static void write_commented_object(int fd, const struct object_id *object) { const char *show_args[5] = - {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL}; + {"show", "--stat", "--no-notes", oid_to_hex(object), NULL}; struct child_process show = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT; struct strbuf cbuf = STRBUF_INIT; @@ -145,7 +146,7 @@ static void write_commented_object(int fd, const unsigned char *object) show.git_cmd = 1; if (start_command(&show)) die(_("unable to start 'show' for object '%s'"), - sha1_to_hex(object)); + oid_to_hex(object)); if (strbuf_read(&buf, show.out, 0) < 0) die_errno(_("could not read 'show' output")); @@ -157,10 +158,10 @@ static void write_commented_object(int fd, const unsigned char *object) if (finish_command(&show)) die(_("failed to finish 'show' for object '%s'"), - sha1_to_hex(object)); + oid_to_hex(object)); } -static void prepare_note_data(const unsigned char *object, struct note_data *d, +static void prepare_note_data(const struct object_id *object, struct note_data *d, const unsigned char *old_note) { if (d->use_editor || !d->given) { @@ -243,16 +244,16 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) { struct note_data *d = opt->value; char *buf; - unsigned char object[20]; + struct object_id object; enum object_type type; unsigned long len; if (d->buf.len) strbuf_addch(&d->buf, '\n'); - if (get_sha1(arg, object)) + if (get_oid(arg, &object)) die(_("failed to resolve '%s' as a valid ref."), arg); - if (!(buf = read_sha1_file(object, &type, &len))) { + if (!(buf = read_sha1_file(object.hash, &type, &len))) { free(buf); die(_("failed to read object '%s'."), arg); } @@ -292,7 +293,7 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) } while (strbuf_getline_lf(&buf, stdin) != EOF) { - unsigned char from_obj[20], to_obj[20]; + struct object_id from_obj, to_obj; struct strbuf **split; int err; @@ -301,15 +302,15 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) die(_("malformed input line: '%s'."), buf.buf); strbuf_rtrim(split[0]); strbuf_rtrim(split[1]); - if (get_sha1(split[0]->buf, from_obj)) + if (get_oid(split[0]->buf, &from_obj)) die(_("failed to resolve '%s' as a valid ref."), split[0]->buf); - if (get_sha1(split[1]->buf, to_obj)) + if (get_oid(split[1]->buf, &to_obj)) die(_("failed to resolve '%s' as a valid ref."), split[1]->buf); if (rewrite_cmd) - err = copy_note_for_rewrite(c, from_obj, to_obj); + err = copy_note_for_rewrite(c, &from_obj, &to_obj); else - err = copy_note(t, from_obj, to_obj, force, + err = copy_note(t, &from_obj, &to_obj, force, combine_notes_overwrite); if (err) { @@ -340,8 +341,10 @@ static struct notes_tree *init_notes_check(const char *subcommand, ref = (flags & NOTES_INIT_WRITABLE) ? t->update_ref : t->ref; if (!starts_with(ref, "refs/notes/")) - /* TRANSLATORS: the first %s will be replaced by a - git notes command: 'add', 'merge', 'remove', etc.*/ + /* + * TRANSLATORS: the first %s will be replaced by a git + * notes command: 'add', 'merge', 'remove', etc. + */ die(_("refusing to %s notes in %s (outside of refs/notes/)"), subcommand, ref); return t; @@ -350,8 +353,8 @@ static struct notes_tree *init_notes_check(const char *subcommand, static int list(int argc, const char **argv, const char *prefix) { struct notes_tree *t; - unsigned char object[20]; - const unsigned char *note; + struct object_id object; + const struct object_id *note; int retval = -1; struct option options[] = { OPT_END() @@ -368,15 +371,15 @@ static int list(int argc, const char **argv, const char *prefix) t = init_notes_check("list", 0); if (argc) { - if (get_sha1(argv[0], object)) + if (get_oid(argv[0], &object)) die(_("failed to resolve '%s' as a valid ref."), argv[0]); - note = get_note(t, object); + note = get_note(t, &object); if (note) { - puts(sha1_to_hex(note)); + puts(oid_to_hex(note)); retval = 0; } else retval = error(_("no note found for object %s."), - sha1_to_hex(object)); + oid_to_hex(&object)); } else retval = for_each_note(t, 0, list_each_note, NULL); @@ -391,8 +394,8 @@ static int add(int argc, const char **argv, const char *prefix) int force = 0, allow_empty = 0; const char *object_ref; struct notes_tree *t; - unsigned char object[20], new_note[20]; - const unsigned char *note; + struct object_id object, new_note; + const struct object_id *note; struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct option options[] = { { OPTION_CALLBACK, 'm', "message", &d, N_("message"), @@ -423,11 +426,11 @@ static int add(int argc, const char **argv, const char *prefix) object_ref = argc > 1 ? argv[1] : "HEAD"; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("add", NOTES_INIT_WRITABLE); - note = get_note(t, object); + note = get_note(t, &object); if (note) { if (!force) { @@ -437,7 +440,7 @@ static int add(int argc, const char **argv, const char *prefix) return error(_("Cannot add notes. " "Found existing notes for object %s. " "Use '-f' to overwrite existing notes"), - sha1_to_hex(object)); + oid_to_hex(&object)); } /* * Redirect to "edit" subcommand. @@ -450,19 +453,19 @@ static int add(int argc, const char **argv, const char *prefix) return append_edit(argc, argv, prefix); } fprintf(stderr, _("Overwriting existing notes for object %s\n"), - sha1_to_hex(object)); + oid_to_hex(&object)); } - prepare_note_data(object, &d, note); + prepare_note_data(&object, &d, note->hash); if (d.buf.len || allow_empty) { - write_note_data(&d, new_note); - if (add_note(t, object, new_note, combine_notes_overwrite)) + write_note_data(&d, new_note.hash); + if (add_note(t, &object, &new_note, combine_notes_overwrite)) die("BUG: combine_notes_overwrite failed"); commit_notes(t, "Notes added by 'git notes add'"); } else { fprintf(stderr, _("Removing note for object %s\n"), - sha1_to_hex(object)); - remove_note(t, object); + oid_to_hex(&object)); + remove_note(t, object.hash); commit_notes(t, "Notes removed by 'git notes add'"); } @@ -474,9 +477,9 @@ static int add(int argc, const char **argv, const char *prefix) static int copy(int argc, const char **argv, const char *prefix) { int retval = 0, force = 0, from_stdin = 0; - const unsigned char *from_note, *note; + const struct object_id *from_note, *note; const char *object_ref; - unsigned char object[20], from_obj[20]; + struct object_id object, from_obj; struct notes_tree *t; const char *rewrite_cmd = NULL; struct option options[] = { @@ -509,37 +512,37 @@ static int copy(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_copy_usage, options); } - if (get_sha1(argv[0], from_obj)) + if (get_oid(argv[0], &from_obj)) die(_("failed to resolve '%s' as a valid ref."), argv[0]); object_ref = 1 < argc ? argv[1] : "HEAD"; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("copy", NOTES_INIT_WRITABLE); - note = get_note(t, object); + note = get_note(t, &object); if (note) { if (!force) { retval = error(_("Cannot copy notes. Found existing " "notes for object %s. Use '-f' to " "overwrite existing notes"), - sha1_to_hex(object)); + oid_to_hex(&object)); goto out; } fprintf(stderr, _("Overwriting existing notes for object %s\n"), - sha1_to_hex(object)); + oid_to_hex(&object)); } - from_note = get_note(t, from_obj); + from_note = get_note(t, &from_obj); if (!from_note) { retval = error(_("missing notes on source object %s. Cannot " - "copy."), sha1_to_hex(from_obj)); + "copy."), oid_to_hex(&from_obj)); goto out; } - if (add_note(t, object, from_note, combine_notes_overwrite)) + if (add_note(t, &object, from_note, combine_notes_overwrite)) die("BUG: combine_notes_overwrite failed"); commit_notes(t, "Notes added by 'git notes copy'"); out: @@ -552,8 +555,8 @@ static int append_edit(int argc, const char **argv, const char *prefix) int allow_empty = 0; const char *object_ref; struct notes_tree *t; - unsigned char object[20], new_note[20]; - const unsigned char *note; + struct object_id object, new_note; + const struct object_id *note; char *logmsg; const char * const *usage; struct note_data d = { 0, 0, NULL, STRBUF_INIT }; @@ -592,19 +595,19 @@ static int append_edit(int argc, const char **argv, const char *prefix) object_ref = 1 < argc ? argv[1] : "HEAD"; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check(argv[0], NOTES_INIT_WRITABLE); - note = get_note(t, object); + note = get_note(t, &object); - prepare_note_data(object, &d, edit ? note : NULL); + prepare_note_data(&object, &d, edit && note ? note->hash : NULL); if (note && !edit) { /* Append buf to previous note contents */ unsigned long size; enum object_type type; - char *prev_buf = read_sha1_file(note, &type, &size); + char *prev_buf = read_sha1_file(note->hash, &type, &size); strbuf_grow(&d.buf, size + 1); if (d.buf.len && prev_buf && size) @@ -615,14 +618,14 @@ static int append_edit(int argc, const char **argv, const char *prefix) } if (d.buf.len || allow_empty) { - write_note_data(&d, new_note); - if (add_note(t, object, new_note, combine_notes_overwrite)) + write_note_data(&d, new_note.hash); + if (add_note(t, &object, &new_note, combine_notes_overwrite)) die("BUG: combine_notes_overwrite failed"); logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]); } else { fprintf(stderr, _("Removing note for object %s\n"), - sha1_to_hex(object)); - remove_note(t, object); + oid_to_hex(&object)); + remove_note(t, object.hash); logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]); } commit_notes(t, logmsg); @@ -637,8 +640,8 @@ static int show(int argc, const char **argv, const char *prefix) { const char *object_ref; struct notes_tree *t; - unsigned char object[20]; - const unsigned char *note; + struct object_id object; + const struct object_id *note; int retval; struct option options[] = { OPT_END() @@ -654,17 +657,17 @@ static int show(int argc, const char **argv, const char *prefix) object_ref = argc ? argv[0] : "HEAD"; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("show", 0); - note = get_note(t, object); + note = get_note(t, &object); if (!note) retval = error(_("no note found for object %s."), - sha1_to_hex(object)); + oid_to_hex(&object)); else { - const char *show_args[3] = {"show", sha1_to_hex(note), NULL}; + const char *show_args[3] = {"show", oid_to_hex(note), NULL}; retval = execv_git_cmd(show_args); } free_notes(t); @@ -724,7 +727,7 @@ static int merge_commit(struct notes_merge_options *o) if (!o->local_ref) die(_("failed to resolve NOTES_MERGE_REF")); - if (notes_merge_commit(o, t, partial, oid.hash)) + if (notes_merge_commit(o, t, partial, &oid)) die(_("failed to finalize notes merge")); /* Reuse existing commit message in reflog message */ @@ -760,7 +763,7 @@ static int git_config_get_notes_strategy(const char *key, static int merge(int argc, const char **argv, const char *prefix) { struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; - unsigned char result_sha1[20]; + struct object_id result_oid; struct notes_tree *t; struct notes_merge_options o; int do_merge = 0, do_commit = 0, do_abort = 0; @@ -842,16 +845,16 @@ static int merge(int argc, const char **argv, const char *prefix) remote_ref.buf, default_notes_ref()); strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */ - result = notes_merge(&o, t, result_sha1); + result = notes_merge(&o, t, &result_oid); - if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ + if (result >= 0) /* Merge resulted (trivially) in result_oid */ /* Update default notes ref with new commit */ - update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, + update_ref(msg.buf, default_notes_ref(), result_oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ const struct worktree *wt; /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ - update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, + update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); @@ -878,10 +881,10 @@ static int merge(int argc, const char **argv, const char *prefix) static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag) { int status; - unsigned char sha1[20]; - if (get_sha1(name, sha1)) + struct object_id oid; + if (get_oid(name, &oid)) return error(_("Failed to resolve '%s' as a valid ref."), name); - status = remove_note(t, sha1); + status = remove_note(t, oid.hash); if (status) fprintf(stderr, _("Object %s has no note\n"), name); else diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 82555e410a..f4a8441fe9 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "attr.h" #include "object.h" #include "blob.h" @@ -264,8 +265,7 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent * make sure no cached delta data remains from a * previous attempt before a pack split occurred. */ - free(entry->delta_data); - entry->delta_data = NULL; + FREE_AND_NULL(entry->delta_data); entry->z_delta_size = 0; } else if (entry->delta_data) { size = entry->delta_size; @@ -1375,12 +1375,10 @@ static void cleanup_preferred_base(void) if (!pbase_tree_cache[i]) continue; free(pbase_tree_cache[i]->tree_data); - free(pbase_tree_cache[i]); - pbase_tree_cache[i] = NULL; + FREE_AND_NULL(pbase_tree_cache[i]); } - free(done_pbase_paths); - done_pbase_paths = NULL; + FREE_AND_NULL(done_pbase_paths); done_pbase_paths_num = done_pbase_paths_alloc = 0; } @@ -1970,8 +1968,7 @@ static unsigned long free_unpacked(struct unpacked *n) n->index = NULL; if (n->data) { freed_mem += n->entry->size; - free(n->data); - n->data = NULL; + FREE_AND_NULL(n->data); } n->entry = NULL; n->depth = 0; @@ -2483,8 +2480,10 @@ static int git_pack_config(const char *k, const char *v, void *cb) die("invalid number of threads specified (%d)", delta_search_threads); #ifdef NO_PTHREADS - if (delta_search_threads != 1) + if (delta_search_threads != 1) { warning("no threads support, ignoring %s", k); + delta_search_threads = 0; + } #endif return 0; } @@ -2728,7 +2727,11 @@ static void loosen_unused_packed_objects(struct rev_info *revs) */ static int pack_options_allow_reuse(void) { - return pack_to_stdout && allow_ofs_delta; + return pack_to_stdout && + allow_ofs_delta && + !ignore_packed_keep && + (!local || !have_non_local_packs) && + !incremental; } static int get_object_list_from_bitmap(struct rev_info *revs) diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 72c815844d..cb1df1c761 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -442,6 +442,7 @@ static void minimize(struct pack_list **min) /* return if there are no objects missing from the unique set */ if (missing->size == 0) { *min = unique; + free(missing); return; } diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 81552e02e4..970d0d30b4 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result) { diff --git a/builtin/pull.c b/builtin/pull.c index 318c273eb3..2ce311a52e 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -6,6 +6,7 @@ * Fetch one or more remote refs and merge it/them into the current HEAD. */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "parse-options.h" #include "exec_cmd.h" @@ -337,8 +338,7 @@ static void get_merge_heads(struct oid_array *merge_heads) struct strbuf sb = STRBUF_INIT; struct object_id oid; - if (!(fp = fopen(filename, "r"))) - die_errno(_("could not open '%s' for reading"), filename); + fp = xfopen(filename, "r"); while (strbuf_getline_lf(&sb, fp) != EOF) { if (get_oid_hex(sb.buf, &oid)) continue; /* invalid line: does not start with SHA1 */ @@ -772,6 +772,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) struct oid_array merge_heads = OID_ARRAY_INIT; struct object_id orig_head, curr_head; struct object_id rebase_fork_point; + int autostash; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); @@ -800,8 +801,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (!opt_rebase && opt_autostash != -1) die(_("--[no-]autostash option is only valid with --rebase.")); + autostash = config_autostash; if (opt_rebase) { - int autostash = config_autostash; if (opt_autostash != -1) autostash = opt_autostash; @@ -862,16 +863,18 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Cannot rebase onto multiple branches.")); if (opt_rebase) { - struct commit_list *list = NULL; - struct commit *merge_head, *head; - - head = lookup_commit_reference(&orig_head); - commit_list_insert(head, &list); - merge_head = lookup_commit_reference(&merge_heads.oid[0]); - if (is_descendant_of(merge_head, list)) { - /* we can fast-forward this without invoking rebase */ - opt_ff = "--ff-only"; - return run_merge(); + if (!autostash) { + struct commit_list *list = NULL; + struct commit *merge_head, *head; + + head = lookup_commit_reference(&orig_head); + commit_list_insert(head, &list); + merge_head = lookup_commit_reference(&merge_heads.oid[0]); + if (is_descendant_of(merge_head, list)) { + /* we can fast-forward this without invoking rebase */ + opt_ff = "--ff-only"; + return run_merge(); + } } return run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); } else { diff --git a/builtin/push.c b/builtin/push.c index a597759d8f..03846e8379 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -2,6 +2,7 @@ * "git push" */ #include "cache.h" +#include "config.h" #include "refs.h" #include "run-command.h" #include "builtin.h" @@ -498,6 +499,10 @@ static int git_push_config(const char *k, const char *v, void *cb) const char *value; if (!git_config_get_value("push.recursesubmodules", &value)) recurse_submodules = parse_push_recurse_submodules_arg(k, value); + } else if (!strcmp(k, "submodule.recurse")) { + int val = git_config_bool(k, v) ? + RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF; + recurse_submodules = val; } return git_default_config(k, v, NULL); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 6d45175f6a..d5f618d086 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -5,6 +5,7 @@ */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "object.h" #include "tree.h" @@ -21,7 +22,6 @@ static int nr_trees; static int read_empty; static struct tree *trees[MAX_UNPACK_TREES]; -static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int list_tree(struct object_id *oid) { @@ -99,21 +99,12 @@ static int debug_merge(const struct cache_entry * const *stages, return 0; } -static int option_parse_recurse_submodules(const struct option *opt, - const char *arg, int unset) +static int git_read_tree_config(const char *var, const char *value, void *cb) { - if (unset) { - recurse_submodules = RECURSE_SUBMODULES_OFF; - return 0; - } - if (arg) - recurse_submodules = - parse_update_recurse_submodules_arg(opt->long_name, - arg); - else - recurse_submodules = RECURSE_SUBMODULES_ON; + if (!strcmp(var, "submodule.recurse")) + return git_default_submodule_config(var, value, cb); - return 0; + return git_default_config(var, value, cb); } static struct lock_file lock_file; @@ -157,9 +148,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) N_("skip applying sparse checkout filter")), OPT_BOOL(0, "debug-unpack", &opts.debug_unpack, N_("debug unpack-trees")), - { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "checkout", "control recursive updating of submodules", - PARSE_OPT_OPTARG, option_parse_recurse_submodules }, + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, OPT_END() }; @@ -168,18 +159,14 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) opts.src_index = &the_index; opts.dst_index = &the_index; - git_config(git_default_config, NULL); + git_config(git_read_tree_config, NULL); argc = parse_options(argc, argv, unused_prefix, read_tree_options, read_tree_usage, 0); - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + load_submodule_cache(); - if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) { - gitmodules_config(); - git_config(submodule_config, NULL); - set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON); - } + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) @@ -210,7 +197,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("failed to unpack tree object %s", arg); stage++; } - if (nr_trees == 0 && !read_empty) + if (!nr_trees && !read_empty && !opts.merge) warning("read-tree: emptying the index with no arguments is deprecated; use --empty"); else if (nr_trees > 0 && read_empty) die("passing trees as arguments contradicts --empty"); @@ -226,9 +213,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) setup_work_tree(); if (opts.merge) { - if (stage < 2) - die("just how do you expect me to merge %d trees?", stage-1); switch (stage - 1) { + case 0: + die("you must specify at least one tree to merge"); + break; case 1: opts.fn = opts.prefix ? bind_merge : oneway_merge; break; diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index ca1ebb2fa1..c82b4dce68 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "parse-options.h" #include "sequencer.h" diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index eb8b64bfce..71c0c768db 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "pack.h" #include "refs.h" @@ -1028,7 +1029,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) { const char *name = cmd->ref_name; struct strbuf namespaced_name_buf = STRBUF_INIT; - const char *namespaced_name, *ret; + static char *namespaced_name; + const char *ret; struct object_id *old_oid = &cmd->old_oid; struct object_id *new_oid = &cmd->new_oid; @@ -1039,6 +1041,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) } strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name); + free(namespaced_name); namespaced_name = strbuf_detach(&namespaced_name_buf, NULL); if (is_ref_checked_out(namespaced_name)) { diff --git a/builtin/reflog.c b/builtin/reflog.c index 920c16dac0..44cdc2dbd0 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "commit.h" #include "refs.h" diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index 11b48bfb41..bfb21ba7d2 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -3,6 +3,9 @@ #include "run-command.h" #include "pkt-line.h" +static const char usage_msg[] = + "git remote-ext <remote> <url>"; + /* * URL syntax: * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments. @@ -193,7 +196,7 @@ static int command_loop(const char *child) int cmd_remote_ext(int argc, const char **argv, const char *prefix) { if (argc != 3) - die("Expected two arguments"); + usage(usage_msg); return command_loop(argv[2]); } diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c index 08d7121b6d..91dfe07e06 100644 --- a/builtin/remote-fd.c +++ b/builtin/remote-fd.c @@ -1,6 +1,9 @@ #include "builtin.h" #include "transport.h" +static const char usage_msg[] = + "git remote-fd <remote> <url>"; + /* * URL syntax: * 'fd::<inoutfd>[/<anything>]' Read/write socket pair @@ -57,7 +60,7 @@ int cmd_remote_fd(int argc, const char **argv, const char *prefix) char *end; if (argc != 3) - die("Expected two arguments"); + usage(usage_msg); input_fd = (int)strtoul(argv[2], &end, 10); diff --git a/builtin/remote.c b/builtin/remote.c index 5f52c5a6d6..6273c0c23c 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "parse-options.h" #include "transport.h" #include "remote.h" @@ -1151,8 +1152,11 @@ static int show(int argc, const char **argv) url_nr = states.remote->url_nr; } for (i = 0; i < url_nr; i++) - /* TRANSLATORS: the colon ':' should align with - the one in " Fetch URL: %s" translation */ + /* + * TRANSLATORS: the colon ':' should align + * with the one in " Fetch URL: %s" + * translation. + */ printf_ln(_(" Push URL: %s"), url[i]); if (!i) printf_ln(_(" Push URL: %s"), _("(no URL)")); diff --git a/builtin/repack.c b/builtin/repack.c index 677bc7c81a..f17a68a17d 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "dir.h" #include "parse-options.h" #include "run-command.h" @@ -155,6 +156,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) int keep_unreachable = 0; const char *window = NULL, *window_memory = NULL; const char *depth = NULL; + const char *threads = NULL; const char *max_pack_size = NULL; int no_reuse_delta = 0, no_reuse_object = 0; int no_update_server_info = 0; @@ -190,6 +192,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("same as the above, but limit memory size instead of entries count")), OPT_STRING(0, "depth", &depth, N_("n"), N_("limits the maximum delta depth")), + OPT_STRING(0, "threads", &threads, N_("n"), + N_("limits the maximum number of threads")), OPT_STRING(0, "max-pack-size", &max_pack_size, N_("bytes"), N_("maximum size of each packfile")), OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects, @@ -234,6 +238,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argv_array_pushf(&cmd.args, "--window-memory=%s", window_memory); if (depth) argv_array_pushf(&cmd.args, "--depth=%s", depth); + if (threads) + argv_array_pushf(&cmd.args, "--threads=%s", threads); if (max_pack_size) argv_array_pushf(&cmd.args, "--max-pack-size=%s", max_pack_size); if (no_reuse_delta) diff --git a/builtin/replace.c b/builtin/replace.c index c921bc976f..80a15cf35f 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -9,6 +9,7 @@ */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "refs.h" #include "parse-options.h" diff --git a/builtin/rerere.c b/builtin/rerere.c index 1bf72423bf..ffb66e2907 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "dir.h" #include "parse-options.h" #include "string-list.h" diff --git a/builtin/reset.c b/builtin/reset.c index c782739c21..7aeaea2737 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -8,6 +8,7 @@ * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "tag.h" #include "object.h" @@ -21,6 +22,8 @@ #include "parse-options.h" #include "unpack-trees.h" #include "cache-tree.h" +#include "submodule.h" +#include "submodule-config.h" static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"), @@ -236,7 +239,6 @@ static void parse_args(struct pathspec *pathspec, parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP | (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0), prefix, argv); } @@ -264,6 +266,14 @@ static int reset_refs(const char *rev, const struct object_id *oid) return update_ref_status; } +static int git_reset_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "submodule.recurse")) + return git_default_submodule_config(var, value, cb); + + return git_default_config(var, value, cb); +} + int cmd_reset(int argc, const char **argv, const char *prefix) { int reset_type = NONE, update_ref_status = 0, quiet = 0; @@ -283,18 +293,23 @@ int cmd_reset(int argc, const char **argv, const char *prefix) N_("reset HEAD, index and working tree"), MERGE), OPT_SET_INT(0, "keep", &reset_type, N_("reset HEAD but keep local changes"), KEEP), + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, + "reset", "control recursive updating of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that removed paths will be added later")), OPT_END() }; - git_config(git_default_config, NULL); + git_config(git_reset_config, NULL); argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); + load_submodule_cache(); + unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", oid.hash); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 718c6059c9..95d84d5cda 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "commit.h" #include "diff.h" #include "revision.h" @@ -277,6 +278,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int use_bitmap_index = 0; const char *show_progress = NULL; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(rev_list_usage); + git_config(git_default_config, NULL); init_revisions(&revs, prefix); revs.abbrev = DEFAULT_ABBREV; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index efdc14473b..c78b7b33d6 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "quote.h" diff --git a/builtin/revert.c b/builtin/revert.c index 345d9586a7..16028b9ea8 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "builtin.h" #include "parse-options.h" #include "diff.h" diff --git a/builtin/rm.c b/builtin/rm.c index fb79dcab18..52826d1379 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds 2006 */ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "dir.h" #include "cache-tree.h" @@ -129,7 +130,7 @@ static int check_local_mod(struct object_id *head, int index_only) ce = active_cache[pos]; if (lstat(ce->name, &st) < 0) { - if (errno != ENOENT && errno != ENOTDIR) + if (!is_missing_file_error(errno)) warning_errno(_("failed to stat '%s'"), ce->name); /* It already vanished from the working tree */ continue; @@ -271,8 +272,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) die(_("index file corrupt")); parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + PATHSPEC_PREFER_CWD, prefix, argv); refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b8e2e74fe0..633e0c3cdd 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "pkt-line.h" diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 7cff1839fc..43c4799ea9 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "diff.h" #include "string-list.h" diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 4a6cc6f490..527f69e283 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -1,10 +1,12 @@ #include "cache.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "builtin.h" #include "color.h" #include "argv-array.h" #include "parse-options.h" +#include "dir.h" static const char* show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" @@ -421,14 +423,6 @@ static int append_tag_ref(const char *refname, const struct object_id *oid, static const char *match_ref_pattern = NULL; static int match_ref_slash = 0; -static int count_slash(const char *s) -{ - int cnt = 0; - while (*s) - if (*s++ == '/') - cnt++; - return cnt; -} static int append_matching_ref(const char *refname, const struct object_id *oid, int flag, void *cb_data) @@ -438,7 +432,7 @@ static int append_matching_ref(const char *refname, const struct object_id *oid, * refs/tags/v0.99.9a and friends. */ const char *tail; - int slash = count_slash(refname); + int slash = count_slashes(refname); for (tail = refname; *tail && match_ref_slash < slash; ) if (*tail++ == '/') slash--; @@ -529,7 +523,7 @@ static void append_one_rev(const char *av) int saved_matches = ref_name_cnt; match_ref_pattern = av; - match_ref_slash = count_slash(av); + match_ref_slash = count_slashes(av); for_each_ref(append_matching_ref, NULL); if (saved_matches == ref_name_cnt && ref_name_cnt < MAX_REVS) diff --git a/builtin/stripspace.c b/builtin/stripspace.c index 1e62a008cb..bdf0328869 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "parse-options.h" #include "strbuf.h" diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 566a5b6a6f..8517032b3e 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "parse-options.h" #include "quote.h" #include "pathspec.h" @@ -233,8 +234,7 @@ static int module_list_compute(int argc, const char **argv, int i, result = 0; char *ps_matched = NULL; parse_pathspec(pathspec, 0, - PATHSPEC_PREFER_FULL | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + PATHSPEC_PREFER_FULL, prefix, argv); if (pathspec->nr) @@ -1222,9 +1222,8 @@ static struct cmd_struct commands[] = { int cmd_submodule__helper(int argc, const char **argv, const char *prefix) { int i; - if (argc < 2) - die(_("submodule--helper subcommand must be " - "called with a subcommand")); + if (argc < 2 || !strcmp(argv[1], "-h")) + usage("git submodule--helper <command>"); for (i = 0; i < ARRAY_SIZE(commands); i++) { if (!strcmp(argv[1], commands[i].cmd)) { diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 70addef158..df75cb9d4a 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "cache.h" #include "refs.h" #include "parse-options.h" diff --git a/builtin/tag.c b/builtin/tag.c index 1f74a56db7..01154ea8dc 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -7,6 +7,7 @@ */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "refs.h" #include "tag.h" diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 6fc6bcdf7f..73f1334191 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" static char *create_temp_file(unsigned char *sha1) { diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 8bc9997767..689a29fac1 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "object.h" #include "delta.h" #include "pack.h" @@ -112,8 +113,7 @@ static void *get_data(unsigned long size) break; if (ret != Z_OK) { error("inflate returned %d", ret); - free(buf); - buf = NULL; + FREE_AND_NULL(buf); if (!recover) exit(1); has_errors = 1; diff --git a/builtin/update-index.c b/builtin/update-index.c index ebfc09faa0..56721cf03d 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "quote.h" #include "cache-tree.h" @@ -257,7 +258,7 @@ static int remove_one_path(const char *path) */ static int process_lstat_error(const char *path, int err) { - if (err == ENOENT || err == ENOTDIR) + if (is_missing_file_error(err)) return remove_one_path(path); return error("lstat(\"%s\"): %s", path, strerror(err)); } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 0b2ecf41ae..40ccfc193b 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "refs.h" #include "builtin.h" #include "parse-options.h" diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index 6c8cc3edc1..873070e517 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "builtin.h" #include "parse-options.h" diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index cde06977b7..84532ae9a9 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -22,7 +22,7 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) struct argv_array sent_argv = ARGV_ARRAY_INIT; const char *arg_cmd = "argument "; - if (argc != 2) + if (argc != 2 || !strcmp(argv[1], "-h")) usage(upload_archive_usage); if (!enter_repo(argv[1], 0)) @@ -76,6 +76,9 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) { struct child_process writer = { argv }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(upload_archive_usage); + /* * Set up sideband subprocess. * diff --git a/builtin/var.c b/builtin/var.c index aedbb53a2d..6c6f46b4ae 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -4,6 +4,7 @@ * Copyright (C) Eric Biederman, 2005 */ #include "builtin.h" +#include "config.h" static const char var_usage[] = "git var (-l | <variable>)"; diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index 05b734e6d1..ba38ac9b15 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -6,6 +6,7 @@ * Based on git-verify-tag */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "commit.h" #include "run-command.h" diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index c94e156932..c2a1a5c504 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "run-command.h" #include "parse-options.h" diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 5199553d91..f9a5f7535a 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -6,6 +6,7 @@ * Based on git-verify-tag.sh */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "tag.h" #include "run-command.h" diff --git a/builtin/worktree.c b/builtin/worktree.c index 11f90d6e45..c98e2ce5f5 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "builtin.h" #include "dir.h" #include "parse-options.h" @@ -299,10 +300,8 @@ static int add_worktree(const char *path, const char *refname, } is_junk = 0; - free(junk_work_tree); - free(junk_git_dir); - junk_work_tree = NULL; - junk_git_dir = NULL; + FREE_AND_NULL(junk_work_tree); + FREE_AND_NULL(junk_git_dir); done: if (ret || !opts->keep_locked) { @@ -414,9 +413,11 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV)); if (wt->is_detached) strbuf_addstr(&sb, "(detached HEAD)"); - else if (wt->head_ref) - strbuf_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0)); - else + else if (wt->head_ref) { + char *ref = shorten_unambiguous_ref(wt->head_ref, 0); + strbuf_addf(&sb, "[%s]", ref); + free(ref); + } else strbuf_addstr(&sb, "(error)"); } printf("%s\n", sb.buf); diff --git a/builtin/write-tree.c b/builtin/write-tree.c index 084c0df783..bd0a78aa3c 100644 --- a/builtin/write-tree.c +++ b/builtin/write-tree.c @@ -5,6 +5,7 @@ */ #include "builtin.h" #include "cache.h" +#include "config.h" #include "tree.h" #include "cache-tree.h" #include "parse-options.h" @@ -525,12 +525,15 @@ extern void set_git_work_tree(const char *tree); extern void setup_work_tree(void); /* - * Find GIT_DIR of the repository that contains the current working directory, - * without changing the working directory or other global state. The result is - * appended to gitdir. The return value is either NULL if no repository was - * found, or pointing to the path inside gitdir's buffer. - */ -extern const char *discover_git_directory(struct strbuf *gitdir); + * Find the commondir and gitdir of the repository that contains the current + * working directory, without changing the working directory or other global + * state. The result is appended to commondir and gitdir. If the discovered + * gitdir does not correspond to a worktree, then 'commondir' and 'gitdir' will + * both have the same result appended to the buffer. The return value is + * either 0 upon success and non-zero if no repository was found. + */ +extern int discover_git_directory(struct strbuf *commondir, + struct strbuf *gitdir); extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern char *prefix_path(const char *prefix, int len, const char *path); @@ -597,6 +600,7 @@ extern int read_index_unmerged(struct index_state *); #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 void move_index_extensions(struct index_state *dst, struct index_state *src); extern int unmerged_index(const struct index_state *); extern int verify_path(const char *path); extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change); @@ -1025,6 +1029,13 @@ static inline void oidcpy(struct object_id *dst, const struct object_id *src) hashcpy(dst->hash, src->hash); } +static inline struct object_id *oiddup(const struct object_id *src) +{ + struct object_id *dst = xmalloc(sizeof(struct object_id)); + oidcpy(dst, src); + return dst; +} + static inline void hashclr(unsigned char *hash) { memset(hash, 0, GIT_SHA1_RAWSZ); @@ -1333,13 +1344,18 @@ static inline int hex2chr(const char *s) struct object_context { unsigned char tree[20]; - char path[PATH_MAX]; unsigned mode; /* * symlink_path is only used by get_tree_entry_follow_symlinks, * and only for symlinks that point outside the repository. */ struct strbuf symlink_path; + /* + * If GET_SHA1_RECORD_PATH is set, this will record path (if any) + * found when resolving the name. The caller is responsible for + * releasing the memory. + */ + char *path; }; #define GET_SHA1_QUIETLY 01 @@ -1349,6 +1365,7 @@ struct object_context { #define GET_SHA1_TREEISH 020 #define GET_SHA1_BLOB 040 #define GET_SHA1_FOLLOW_SYMLINKS 0100 +#define GET_SHA1_RECORD_PATH 0200 #define GET_SHA1_ONLY_TO_DIE 04000 #define GET_SHA1_DISAMBIGUATORS \ @@ -1363,7 +1380,7 @@ extern int get_sha1_tree(const char *str, unsigned char *sha1); extern int get_sha1_treeish(const char *str, unsigned char *sha1); extern int get_sha1_blob(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); -extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc); +extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *oc); extern int get_oid(const char *str, struct object_id *oid); @@ -1865,188 +1882,9 @@ extern int packed_object_info(struct packed_git *pack, off_t offset, struct obje /* Dumb servers support */ extern int update_server_info(int); -/* git_config_parse_key() returns these negated: */ -#define CONFIG_INVALID_KEY 1 -#define CONFIG_NO_SECTION_OR_NAME 2 -/* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */ -#define CONFIG_NO_LOCK -1 -#define CONFIG_INVALID_FILE 3 -#define CONFIG_NO_WRITE 4 -#define CONFIG_NOTHING_SET 5 -#define CONFIG_INVALID_PATTERN 6 -#define CONFIG_GENERIC_ERROR 7 - -#define CONFIG_REGEX_NONE ((void *)1) - -struct git_config_source { - unsigned int use_stdin:1; - const char *file; - const char *blob; -}; - -enum config_origin_type { - CONFIG_ORIGIN_BLOB, - CONFIG_ORIGIN_FILE, - CONFIG_ORIGIN_STDIN, - CONFIG_ORIGIN_SUBMODULE_BLOB, - CONFIG_ORIGIN_CMDLINE -}; - -struct config_options { - unsigned int respect_includes : 1; - const char *git_dir; -}; - -typedef int (*config_fn_t)(const char *, const char *, void *); -extern int git_default_config(const char *, const char *, void *); -extern int git_config_from_file(config_fn_t fn, const char *, void *); -extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type, - const char *name, const char *buf, size_t len, void *data); -extern int git_config_from_blob_sha1(config_fn_t fn, const char *name, - const unsigned char *sha1, void *data); -extern void git_config_push_parameter(const char *text); -extern int git_config_from_parameters(config_fn_t fn, void *data); -extern void read_early_config(config_fn_t cb, void *data); -extern void git_config(config_fn_t fn, void *); -extern int git_config_with_options(config_fn_t fn, void *, - struct git_config_source *config_source, - const struct config_options *opts); -extern int git_parse_ulong(const char *, unsigned long *); -extern int git_parse_maybe_bool(const char *); -extern int git_config_int(const char *, const char *); -extern int64_t git_config_int64(const char *, const char *); -extern unsigned long git_config_ulong(const char *, const char *); -extern ssize_t git_config_ssize_t(const char *, const char *); -extern int git_config_bool_or_int(const char *, const char *, int *); -extern int git_config_bool(const char *, const char *); -extern int git_config_maybe_bool(const char *, const char *); -extern int git_config_string(const char **, const char *, const char *); -extern int git_config_pathname(const char **, const char *, const char *); -extern int git_config_set_in_file_gently(const char *, const char *, const char *); -extern void git_config_set_in_file(const char *, const char *, const char *); -extern int git_config_set_gently(const char *, const char *); -extern void git_config_set(const char *, const char *); -extern int git_config_parse_key(const char *, char **, int *); -extern int git_config_key_is_valid(const char *key); -extern int git_config_set_multivar_gently(const char *, const char *, const char *, int); -extern void git_config_set_multivar(const char *, const char *, const char *, int); -extern int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, int); -extern void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); -extern int git_config_rename_section(const char *, const char *); -extern int git_config_rename_section_in_file(const char *, const char *, const char *); -extern const char *git_etc_gitconfig(void); -extern int git_env_bool(const char *, int); -extern unsigned long git_env_ulong(const char *, unsigned long); -extern int git_config_system(void); -extern int config_error_nonbool(const char *); -#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); -extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data); - -enum config_scope { - CONFIG_SCOPE_UNKNOWN = 0, - CONFIG_SCOPE_SYSTEM, - CONFIG_SCOPE_GLOBAL, - CONFIG_SCOPE_REPO, - CONFIG_SCOPE_CMDLINE, -}; - -extern enum config_scope current_config_scope(void); -extern const char *current_config_origin_type(void); -extern const char *current_config_name(void); - -struct config_include_data { - int depth; - config_fn_t fn; - void *data; - const struct config_options *opts; -}; -#define CONFIG_INCLUDE_INIT { 0 } -extern int git_config_include(const char *name, const char *value, void *data); - -/* - * Match and parse a config key of the form: - * - * section.(subsection.)?key - * - * (i.e., what gets handed to a config_fn_t). The caller provides the section; - * we return -1 if it does not match, 0 otherwise. The subsection and key - * out-parameters are filled by the function (and *subsection is NULL if it is - * missing). - * - * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if - * there is no subsection at all. - */ -extern int parse_config_key(const char *var, - const char *section, - const char **subsection, int *subsection_len, - const char **key); - -struct config_set_element { - struct hashmap_entry ent; - char *key; - struct string_list value_list; -}; - -struct configset_list_item { - struct config_set_element *e; - int value_index; -}; - -/* - * the contents of the list are ordered according to their - * position in the config files and order of parsing the files. - * (i.e. key-value pair at the last position of .git/config will - * be at the last item of the list) - */ -struct configset_list { - struct configset_list_item *items; - unsigned int nr, alloc; -}; - -struct config_set { - struct hashmap config_hash; - int hash_initialized; - struct configset_list list; -}; - -extern void git_configset_init(struct config_set *cs); -extern int git_configset_add_file(struct config_set *cs, const char *filename); -extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value); -extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key); -extern void git_configset_clear(struct config_set *cs); -extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest); -extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest); -extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest); -extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest); -extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); -extern int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest); -extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest); -extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest); - -extern int git_config_get_value(const char *key, const char **value); -extern const struct string_list *git_config_get_value_multi(const char *key); -extern void git_config_clear(void); -extern void git_config_iter(config_fn_t fn, void *data); -extern int git_config_get_string_const(const char *key, const char **dest); -extern int git_config_get_string(const char *key, char **dest); -extern int git_config_get_int(const char *key, int *dest); -extern int git_config_get_ulong(const char *key, unsigned long *dest); -extern int git_config_get_bool(const char *key, int *dest); -extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest); -extern int git_config_get_maybe_bool(const char *key, int *dest); -extern int git_config_get_pathname(const char *key, const char **dest); -extern int git_config_get_untracked_cache(void); -extern int git_config_get_split_index(void); -extern int git_config_get_max_percent_split_change(void); - -/* This dies if the configured or default date is in the future */ -extern int git_config_get_expiry(const char *key, const char **output); - /* * This is a hack for test programs like test-dump-untracked-cache to * ensure that they do not modify the untracked cache when reading it. @@ -2054,16 +1892,6 @@ extern int git_config_get_expiry(const char *key, const char **output); */ extern int ignore_untracked_cache_config; -struct key_value_info { - const char *filename; - int linenr; - enum config_origin_type origin_type; - enum config_scope scope; -}; - -extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3))); -extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr); - extern int committer_ident_sufficiently_given(void); extern int author_ident_sufficiently_given(void); @@ -2179,7 +2007,8 @@ extern int ws_blank_line(const char *line, int len, unsigned ws_rule); #define ws_tab_width(rule) ((rule) & WS_TAB_WIDTH_MASK) /* ls-files */ -void overlay_tree_on_cache(const char *tree_name, const char *prefix); +void overlay_tree_on_index(struct index_state *istate, + const char *tree_name, const char *prefix); char *alias_lookup(const char *alias); int split_cmdline(char *cmdline, const char ***argv); @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "color.h" static int git_use_color_default = GIT_COLOR_AUTO; @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "column.h" #include "string-list.h" #include "parse-options.h" diff --git a/combine-diff.c b/combine-diff.c index 2848034fe9..9e163d5ada 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -302,7 +302,7 @@ static char *grab_blob(const struct object_id *oid, unsigned int mode, return xcalloc(1, 1); } else if (textconv) { struct diff_filespec *df = alloc_filespec(path); - fill_filespec(df, oid->hash, 1, mode); + fill_filespec(df, oid, 1, mode); *size = fill_textconv(textconv, df, &blob); free_filespec(df); } else { @@ -1022,7 +1022,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, &result_size, NULL, NULL); } else if (textconv) { struct diff_filespec *df = alloc_filespec(elem->path); - fill_filespec(df, null_sha1, 0, st.st_mode); + fill_filespec(df, &null_oid, 0, st.st_mode); result_size = fill_textconv(textconv, df, &result); free_filespec(df); } else if (0 <= (fd = open(elem->path, O_RDONLY))) { @@ -1053,7 +1053,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, if (is_file) { struct strbuf buf = STRBUF_INIT; - if (convert_to_git(elem->path, result, len, &buf, safe_crlf)) { + if (convert_to_git(&the_index, elem->path, result, len, &buf, safe_crlf)) { free(result); result = strbuf_detach(&buf, &len); result_size = len; @@ -1311,7 +1311,7 @@ static const char *path_path(void *obj) /* find set of paths that every parent touches */ -static struct combine_diff_path *find_paths_generic(const unsigned char *sha1, +static struct combine_diff_path *find_paths_generic(const struct object_id *oid, const struct oid_array *parents, struct diff_options *opt) { struct combine_diff_path *paths = NULL; @@ -1336,7 +1336,7 @@ static struct combine_diff_path *find_paths_generic(const unsigned char *sha1, opt->output_format = stat_opt; else opt->output_format = DIFF_FORMAT_NO_OUTPUT; - diff_tree_sha1(parents->oid[i].hash, sha1, "", opt); + diff_tree_oid(&parents->oid[i], oid, "", opt); diffcore_std(opt); paths = intersect_paths(paths, i, num_parent); @@ -1360,31 +1360,31 @@ static struct combine_diff_path *find_paths_generic(const unsigned char *sha1, * rename/copy detection, etc, comparing all trees simultaneously (= faster). */ static struct combine_diff_path *find_paths_multitree( - const unsigned char *sha1, const struct oid_array *parents, + const struct object_id *oid, const struct oid_array *parents, struct diff_options *opt) { int i, nparent = parents->nr; - const unsigned char **parents_sha1; + const struct object_id **parents_oid; struct combine_diff_path paths_head; struct strbuf base; - ALLOC_ARRAY(parents_sha1, nparent); + ALLOC_ARRAY(parents_oid, nparent); for (i = 0; i < nparent; i++) - parents_sha1[i] = parents->oid[i].hash; + parents_oid[i] = &parents->oid[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); + diff_tree_paths(&paths_head, oid, parents_oid, nparent, &base, opt); strbuf_release(&base); - free(parents_sha1); + free(parents_oid); return paths_head.next; } -void diff_tree_combined(const unsigned char *sha1, +void diff_tree_combined(const struct object_id *oid, const struct oid_array *parents, int dense, struct rev_info *rev) @@ -1448,11 +1448,11 @@ void diff_tree_combined(const unsigned char *sha1, * diff(sha1,parent_i) for all i to do the job, specifically * for parent0. */ - paths = find_paths_generic(sha1, parents, &diffopts); + paths = find_paths_generic(oid, parents, &diffopts); } else { int stat_opt; - paths = find_paths_multitree(sha1, parents, &diffopts); + paths = find_paths_multitree(oid, parents, &diffopts); /* * show stat against the first parent even @@ -1463,7 +1463,7 @@ void diff_tree_combined(const unsigned char *sha1, if (stat_opt) { diffopts.output_format = stat_opt; - diff_tree_sha1(parents->oid[0].hash, sha1, "", &diffopts); + diff_tree_oid(&parents->oid[0], oid, "", &diffopts); diffcore_std(&diffopts); if (opt->orderfile) diffcore_order(opt->orderfile); @@ -1539,6 +1539,6 @@ void diff_tree_combined_merge(const struct commit *commit, int dense, oid_array_append(&parents, &parent->item->object.oid); parent = parent->next; } - diff_tree_combined(commit->object.oid.hash, &parents, dense, rev); + diff_tree_combined(&commit->object.oid, &parents, dense, rev); oid_array_clear(&parents); } diff --git a/commit-slab.h b/commit-slab.h index 42d16dcded..333d81e370 100644 --- a/commit-slab.h +++ b/commit-slab.h @@ -82,8 +82,7 @@ static MAYBE_UNUSED void clear_ ##slabname(struct slabname *s) \ for (i = 0; i < s->slab_count; i++) \ free(s->slab[i]); \ s->slab_count = 0; \ - free(s->slab); \ - s->slab = NULL; \ + FREE_AND_NULL(s->slab); \ } \ \ static MAYBE_UNUSED elemtype *slabname## _at_peek(struct slabname *s, \ @@ -11,6 +11,7 @@ #include "commit-slab.h" #include "prio-queue.h" #include "sha1-lookup.h" +#include "wt-status.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -167,7 +168,7 @@ bad_graft_data: static int read_graft_file(const char *graft_file) { - FILE *fp = fopen(graft_file, "r"); + FILE *fp = fopen_or_warn(graft_file, "r"); struct strbuf buf = STRBUF_INIT; if (!fp) return -1; @@ -286,8 +287,7 @@ void free_commit_buffer(struct commit *commit) { struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit); if (v) { - free(v->buffer); - v->buffer = NULL; + FREE_AND_NULL(v->buffer); v->size = 0; } } @@ -1648,10 +1648,9 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len /* * Inspect the given string and determine the true "end" of the log message, in * order to find where to put a new Signed-off-by: line. Ignored are - * trailing comment lines and blank lines, and also the traditional - * "Conflicts:" block that is not commented out, so that we can use - * "git commit -s --amend" on an existing commit that forgot to remove - * it. + * trailing comment lines and blank lines. To support "git commit -s + * --amend" on an existing commit, we also ignore "Conflicts:". To + * support "git commit -v", we truncate at cut lines. * * Returns the number of bytes from the tail to ignore, to be fed as * the second parameter to append_signoff(). @@ -1661,8 +1660,9 @@ int ignore_non_trailer(const char *buf, size_t len) int boc = 0; int bol = 0; int in_old_conflicts_block = 0; + size_t cutoff = wt_status_locate_end(buf, len); - while (bol < len) { + while (bol < cutoff) { const char *next_line = memchr(buf + bol, '\n', len - bol); if (!next_line) @@ -1688,5 +1688,5 @@ int ignore_non_trailer(const char *buf, size_t len) } bol = next_line - buf; } - return boc ? len - boc : 0; + return boc ? len - boc : len - cutoff; } diff --git a/compat/fopen.c b/compat/fopen.c index b5ca142fed..107b3e8182 100644 --- a/compat/fopen.c +++ b/compat/fopen.c @@ -1,14 +1,14 @@ /* * The order of the following two lines is important. * - * FREAD_READS_DIRECTORIES is undefined before including git-compat-util.h + * SUPPRESS_FOPEN_REDEFINITION is defined before including git-compat-util.h * to avoid the redefinition of fopen within git-compat-util.h. This is * necessary since fopen is a macro on some platforms which may be set * based on compiler options. For example, on AIX fopen is set to fopen64 * when _LARGE_FILES is defined. The previous technique of merely undefining * fopen after including git-compat-util.h is inadequate in this case. */ -#undef FREAD_READS_DIRECTORIES +#define SUPPRESS_FOPEN_REDEFINITION #include "../git-compat-util.h" FILE *git_fopen(const char *path, const char *mode) diff --git a/compat/mingw.c b/compat/mingw.c index 3fbfda5978..8b6fa0db44 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -423,6 +423,8 @@ FILE *mingw_fopen (const char *filename, const char *otype) return NULL; } file = _wfopen(wfilename, wotype); + if (!file && GetLastError() == ERROR_INVALID_NAME) + errno = ENOENT; if (file && hide && set_hidden_flag(wfilename, 1)) warning("could not mark '%s' as hidden.", filename); return file; @@ -941,64 +943,14 @@ static const char *parse_interpreter(const char *cmd) } /* - * Splits the PATH into parts. - */ -static char **get_path_split(void) -{ - char *p, **path, *envpath = mingw_getenv("PATH"); - int i, n = 0; - - if (!envpath || !*envpath) - return NULL; - - envpath = xstrdup(envpath); - p = envpath; - while (p) { - char *dir = p; - p = strchr(p, ';'); - if (p) *p++ = '\0'; - if (*dir) { /* not earlier, catches series of ; */ - ++n; - } - } - if (!n) - return NULL; - - ALLOC_ARRAY(path, n + 1); - p = envpath; - i = 0; - do { - if (*p) - path[i++] = xstrdup(p); - p = p+strlen(p)+1; - } while (i < n); - path[i] = NULL; - - free(envpath); - - return path; -} - -static void free_path_split(char **path) -{ - char **p = path; - - if (!path) - return; - - while (*p) - free(*p++); - free(path); -} - -/* * exe_only means that we only want to detect .exe files, but not scripts * (which do not have an extension) */ -static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_only) +static char *lookup_prog(const char *dir, int dirlen, const char *cmd, + int isexe, int exe_only) { char path[MAX_PATH]; - snprintf(path, sizeof(path), "%s/%s.exe", dir, cmd); + snprintf(path, sizeof(path), "%.*s\\%s.exe", dirlen, dir, cmd); if (!isexe && access(path, F_OK) == 0) return xstrdup(path); @@ -1013,17 +965,29 @@ static char *lookup_prog(const char *dir, const char *cmd, int isexe, int exe_on * Determines the absolute path of cmd using the split path in path. * If cmd contains a slash or backslash, no lookup is performed. */ -static char *path_lookup(const char *cmd, char **path, int exe_only) +static char *path_lookup(const char *cmd, int exe_only) { + const char *path; char *prog = NULL; int len = strlen(cmd); int isexe = len >= 4 && !strcasecmp(cmd+len-4, ".exe"); if (strchr(cmd, '/') || strchr(cmd, '\\')) - prog = xstrdup(cmd); + return xstrdup(cmd); + + path = mingw_getenv("PATH"); + if (!path) + return NULL; - while (!prog && *path) - prog = lookup_prog(*path++, cmd, isexe, exe_only); + while (!prog) { + const char *sep = strchrnul(path, ';'); + int dirlen = sep - path; + if (dirlen) + prog = lookup_prog(path, dirlen, cmd, isexe, exe_only); + if (!*sep) + break; + path = sep + 1; + } return prog; } @@ -1190,8 +1154,7 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **deltaenv, int fhin, int fhout, int fherr) { pid_t pid; - char **path = get_path_split(); - char *prog = path_lookup(cmd, path, 0); + char *prog = path_lookup(cmd, 0); if (!prog) { errno = ENOENT; @@ -1202,7 +1165,7 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **deltaenv, if (interpr) { const char *argv0 = argv[0]; - char *iprog = path_lookup(interpr, path, 1); + char *iprog = path_lookup(interpr, 1); argv[0] = prog; if (!iprog) { errno = ENOENT; @@ -1220,21 +1183,18 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **deltaenv, fhin, fhout, fherr); free(prog); } - free_path_split(path); return pid; } static int try_shell_exec(const char *cmd, char *const *argv) { const char *interpr = parse_interpreter(cmd); - char **path; char *prog; int pid = 0; if (!interpr) return 0; - path = get_path_split(); - prog = path_lookup(interpr, path, 1); + prog = path_lookup(interpr, 1); if (prog) { int argc = 0; const char **argv2; @@ -1253,7 +1213,6 @@ static int try_shell_exec(const char *cmd, char *const *argv) free(prog); free(argv2); } - free_path_split(path); return pid; } @@ -1275,8 +1234,7 @@ int mingw_execv(const char *cmd, char *const *argv) int mingw_execvp(const char *cmd, char *const *argv) { - char **path = get_path_split(); - char *prog = path_lookup(cmd, path, 0); + char *prog = path_lookup(cmd, 0); if (prog) { mingw_execv(prog, argv); @@ -1284,7 +1242,6 @@ int mingw_execvp(const char *cmd, char *const *argv) } else errno = ENOENT; - free_path_split(path); return -1; } diff --git a/compat/mingw.h b/compat/mingw.h index 3350169555..e03aecfe2e 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -398,7 +398,11 @@ HANDLE winansi_get_osfhandle(int fd); (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) int mingw_skip_dos_drive_prefix(char **path); #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix -#define is_dir_sep(c) ((c) == '/' || (c) == '\\') +static inline int mingw_is_dir_sep(int c) +{ + return c == '/' || c == '\\'; +} +#define is_dir_sep mingw_is_dir_sep static inline char *mingw_find_last_dir_sep(const char *path) { char *ret = NULL; diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c index 4293b53b17..de61c15d34 100644 --- a/compat/precompose_utf8.c +++ b/compat/precompose_utf8.c @@ -6,6 +6,7 @@ #define PRECOMPOSE_UNICODE_C #include "cache.h" +#include "config.h" #include "utf8.h" #include "precompose_utf8.h" diff --git a/compat/winansi.c b/compat/winansi.c index 793420f9d0..a11a0f16d2 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -105,6 +105,13 @@ static int is_console(int fd) if (!fd) { if (!GetConsoleMode(hcon, &mode)) return 0; + /* + * This code path is only reached if there is no console + * attached to stdout/stderr, i.e. we will not need to output + * any text to any console, therefore we might just as well + * use black as foreground color. + */ + sbi.wAttributes = 0; } else if (!GetConsoleScreenBufferInfo(hcon, &sbi)) return 0; @@ -133,6 +140,11 @@ static void write_console(unsigned char *str, size_t len) /* convert utf-8 to utf-16 */ int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len); + if (wlen < 0) { + wchar_t *err = L"[invalid]"; + WriteConsoleW(console, err, wcslen(err), &dummy, NULL); + return; + } /* write directly to console */ WriteConsoleW(console, wbuf, wlen, &dummy, NULL); @@ -6,6 +6,7 @@ * */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "exec_cmd.h" #include "strbuf.h" @@ -214,11 +215,10 @@ static int include_by_gitdir(const struct config_options *opts, struct strbuf pattern = STRBUF_INIT; int ret = 0, prefix; const char *git_dir; + int already_tried_absolute = 0; if (opts->git_dir) git_dir = opts->git_dir; - else if (have_git_dir()) - git_dir = get_git_dir(); else goto done; @@ -226,6 +226,7 @@ static int include_by_gitdir(const struct config_options *opts, strbuf_add(&pattern, cond, cond_len); prefix = prepare_include_condition_pattern(&pattern); +again: if (prefix < 0) goto done; @@ -245,6 +246,20 @@ static int include_by_gitdir(const struct config_options *opts, ret = !wildmatch(pattern.buf + prefix, text.buf + prefix, icase ? WM_CASEFOLD : 0, NULL); + if (!ret && !already_tried_absolute) { + /* + * We've tried e.g. matching gitdir:~/work, but if + * ~/work is a symlink to /mnt/storage/work + * strbuf_realpath() will expand it, so the rule won't + * match. Let's match against a + * strbuf_add_absolute_path() version of the path, + * which'll do the right thing + */ + strbuf_reset(&text); + strbuf_add_absolute_path(&text, git_dir); + already_tried_absolute = 1; + goto again; + } done: strbuf_release(&pattern); strbuf_release(&text); @@ -379,8 +394,7 @@ static int git_config_parse_key_1(const char *key, char **store_key, int *basele out_free_ret_1: if (store_key) { - free(*store_key); - *store_key = NULL; + FREE_AND_NULL(*store_key); } return -CONFIG_INVALID_KEY; } @@ -588,7 +602,8 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name) */ cf->linenr--; ret = fn(name->buf, value, data); - cf->linenr++; + if (ret >= 0) + cf->linenr++; return ret; } @@ -1422,7 +1437,7 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data) int ret = -1; FILE *f; - f = fopen(filename, "r"); + f = fopen_or_warn(filename, "r"); if (f) { flockfile(f); ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data); @@ -1529,10 +1544,8 @@ static int do_git_config_sequence(const struct config_options *opts, char *user_config = expand_user_path("~/.gitconfig", 0); char *repo_config; - if (opts->git_dir) - repo_config = mkpathdup("%s/config", opts->git_dir); - else if (have_git_dir()) - repo_config = git_pathdup("config"); + if (opts->commondir) + repo_config = mkpathdup("%s/config", opts->commondir); else repo_config = NULL; @@ -1563,9 +1576,9 @@ static int do_git_config_sequence(const struct config_options *opts, return ret; } -int git_config_with_options(config_fn_t fn, void *data, - struct git_config_source *config_source, - const struct config_options *opts) +int config_with_options(config_fn_t fn, void *data, + struct git_config_source *config_source, + const struct config_options *opts) { struct config_include_data inc = CONFIG_INCLUDE_INIT; @@ -1596,9 +1609,14 @@ static void git_config_raw(config_fn_t fn, void *data) struct config_options opts = {0}; opts.respect_includes = 1; - if (git_config_with_options(fn, data, NULL, &opts) < 0) + if (have_git_dir()) { + opts.commondir = get_git_common_dir(); + opts.git_dir = get_git_dir(); + } + + if (config_with_options(fn, data, NULL, &opts) < 0) /* - * git_config_with_options() normally returns only + * config_with_options() normally returns only * zero, as most errors are fatal, and * non-fatal potential errors are guarded by "if" * statements that are entered only when no error is @@ -1637,11 +1655,13 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) void read_early_config(config_fn_t cb, void *data) { struct config_options opts = {0}; - struct strbuf buf = STRBUF_INIT; + struct strbuf commondir = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; opts.respect_includes = 1; - if (have_git_dir()) + if (have_git_dir()) { + opts.commondir = get_git_common_dir(); opts.git_dir = get_git_dir(); /* * When setup_git_directory() was not yet asked to discover the @@ -1651,12 +1671,15 @@ void read_early_config(config_fn_t cb, void *data) * notably, the current working directory is still the same after the * call). */ - else if (discover_git_directory(&buf)) - opts.git_dir = buf.buf; + } else if (!discover_git_directory(&commondir, &gitdir)) { + opts.commondir = commondir.buf; + opts.git_dir = gitdir.buf; + } - git_config_with_options(cb, data, NULL, &opts); + config_with_options(cb, data, NULL, &opts); - strbuf_release(&buf); + strbuf_release(&commondir); + strbuf_release(&gitdir); } static void git_config_check_init(void); @@ -2621,7 +2644,7 @@ int git_config_rename_section_in_file(const char *config_filename, struct lock_file *lock; int out_fd; char buf[1024]; - FILE *config_file; + FILE *config_file = NULL; struct stat st; if (new_name && !section_name_is_ok(new_name)) { @@ -2640,6 +2663,9 @@ int git_config_rename_section_in_file(const char *config_filename, } if (!(config_file = fopen(config_filename, "rb"))) { + ret = warn_on_fopen_errors(config_filename); + if (ret) + goto out; /* no config file means nothing to rename, no error */ goto commit_and_out; } @@ -2703,11 +2729,14 @@ int git_config_rename_section_in_file(const char *config_filename, } } fclose(config_file); + config_file = NULL; commit_and_out: if (commit_lock_file(lock) < 0) ret = error_errno("could not write config file %s", config_filename); out: + if (config_file) + fclose(config_file); rollback_lock_file(lock); out_no_rollback: free(filename_buf); diff --git a/config.h b/config.h new file mode 100644 index 0000000000..9e038cce25 --- /dev/null +++ b/config.h @@ -0,0 +1,194 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/* git_config_parse_key() returns these negated: */ +#define CONFIG_INVALID_KEY 1 +#define CONFIG_NO_SECTION_OR_NAME 2 +/* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */ +#define CONFIG_NO_LOCK -1 +#define CONFIG_INVALID_FILE 3 +#define CONFIG_NO_WRITE 4 +#define CONFIG_NOTHING_SET 5 +#define CONFIG_INVALID_PATTERN 6 +#define CONFIG_GENERIC_ERROR 7 + +#define CONFIG_REGEX_NONE ((void *)1) + +struct git_config_source { + unsigned int use_stdin:1; + const char *file; + const char *blob; +}; + +enum config_origin_type { + CONFIG_ORIGIN_BLOB, + CONFIG_ORIGIN_FILE, + CONFIG_ORIGIN_STDIN, + CONFIG_ORIGIN_SUBMODULE_BLOB, + CONFIG_ORIGIN_CMDLINE +}; + +struct config_options { + unsigned int respect_includes : 1; + const char *commondir; + const char *git_dir; +}; + +typedef int (*config_fn_t)(const char *, const char *, void *); +extern int git_default_config(const char *, const char *, void *); +extern int git_config_from_file(config_fn_t fn, const char *, void *); +extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type, + const char *name, const char *buf, size_t len, void *data); +extern int git_config_from_blob_sha1(config_fn_t fn, const char *name, + const unsigned char *sha1, void *data); +extern void git_config_push_parameter(const char *text); +extern int git_config_from_parameters(config_fn_t fn, void *data); +extern void read_early_config(config_fn_t cb, void *data); +extern void git_config(config_fn_t fn, void *); +extern int config_with_options(config_fn_t fn, void *, + struct git_config_source *config_source, + const struct config_options *opts); +extern int git_parse_ulong(const char *, unsigned long *); +extern int git_parse_maybe_bool(const char *); +extern int git_config_int(const char *, const char *); +extern int64_t git_config_int64(const char *, const char *); +extern unsigned long git_config_ulong(const char *, const char *); +extern ssize_t git_config_ssize_t(const char *, const char *); +extern int git_config_bool_or_int(const char *, const char *, int *); +extern int git_config_bool(const char *, const char *); +extern int git_config_maybe_bool(const char *, const char *); +extern int git_config_string(const char **, const char *, const char *); +extern int git_config_pathname(const char **, const char *, const char *); +extern int git_config_set_in_file_gently(const char *, const char *, const char *); +extern void git_config_set_in_file(const char *, const char *, const char *); +extern int git_config_set_gently(const char *, const char *); +extern void git_config_set(const char *, const char *); +extern int git_config_parse_key(const char *, char **, int *); +extern int git_config_key_is_valid(const char *key); +extern int git_config_set_multivar_gently(const char *, const char *, const char *, int); +extern void git_config_set_multivar(const char *, const char *, const char *, int); +extern int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, int); +extern void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); +extern int git_config_rename_section(const char *, const char *); +extern int git_config_rename_section_in_file(const char *, const char *, const char *); +extern const char *git_etc_gitconfig(void); +extern int git_env_bool(const char *, int); +extern unsigned long git_env_ulong(const char *, unsigned long); +extern int git_config_system(void); +extern int config_error_nonbool(const char *); +#if defined(__GNUC__) +#define config_error_nonbool(s) (config_error_nonbool(s), const_error()) +#endif + +extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data); + +enum config_scope { + CONFIG_SCOPE_UNKNOWN = 0, + CONFIG_SCOPE_SYSTEM, + CONFIG_SCOPE_GLOBAL, + CONFIG_SCOPE_REPO, + CONFIG_SCOPE_CMDLINE, +}; + +extern enum config_scope current_config_scope(void); +extern const char *current_config_origin_type(void); +extern const char *current_config_name(void); + +struct config_include_data { + int depth; + config_fn_t fn; + void *data; + const struct config_options *opts; +}; +#define CONFIG_INCLUDE_INIT { 0 } +extern int git_config_include(const char *name, const char *value, void *data); + +/* + * Match and parse a config key of the form: + * + * section.(subsection.)?key + * + * (i.e., what gets handed to a config_fn_t). The caller provides the section; + * we return -1 if it does not match, 0 otherwise. The subsection and key + * out-parameters are filled by the function (and *subsection is NULL if it is + * missing). + * + * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if + * there is no subsection at all. + */ +extern int parse_config_key(const char *var, + const char *section, + const char **subsection, int *subsection_len, + const char **key); + +struct config_set_element { + struct hashmap_entry ent; + char *key; + struct string_list value_list; +}; + +struct configset_list_item { + struct config_set_element *e; + int value_index; +}; + +/* + * the contents of the list are ordered according to their + * position in the config files and order of parsing the files. + * (i.e. key-value pair at the last position of .git/config will + * be at the last item of the list) + */ +struct configset_list { + struct configset_list_item *items; + unsigned int nr, alloc; +}; + +struct config_set { + struct hashmap config_hash; + int hash_initialized; + struct configset_list list; +}; + +extern void git_configset_init(struct config_set *cs); +extern int git_configset_add_file(struct config_set *cs, const char *filename); +extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value); +extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key); +extern void git_configset_clear(struct config_set *cs); +extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest); +extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest); +extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest); +extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest); +extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest); + +extern int git_config_get_value(const char *key, const char **value); +extern const struct string_list *git_config_get_value_multi(const char *key); +extern void git_config_clear(void); +extern int git_config_get_string_const(const char *key, const char **dest); +extern int git_config_get_string(const char *key, char **dest); +extern int git_config_get_int(const char *key, int *dest); +extern int git_config_get_ulong(const char *key, unsigned long *dest); +extern int git_config_get_bool(const char *key, int *dest); +extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest); +extern int git_config_get_maybe_bool(const char *key, int *dest); +extern int git_config_get_pathname(const char *key, const char **dest); +extern int git_config_get_untracked_cache(void); +extern int git_config_get_split_index(void); +extern int git_config_get_max_percent_split_change(void); + +/* This dies if the configured or default date is in the future */ +extern int git_config_get_expiry(const char *key, const char **output); + +struct key_value_info { + const char *filename; + int linenr; + enum config_origin_type origin_type; + enum config_scope scope; +}; + +extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3))); +extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr); + +#endif /* CONFIG_H */ diff --git a/config.mak.uname b/config.mak.uname index 192629f143..adfb90b601 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -36,6 +36,7 @@ ifeq ($(uname_S),Linux) NEEDS_LIBRT = YesPlease HAVE_GETDELIM = YesPlease SANE_TEXT_GREP=-a + FREAD_READS_DIRECTORIES = UnfortunatelyYes endif ifeq ($(uname_S),GNU/kFreeBSD) HAVE_ALLOCA_H = YesPlease @@ -43,6 +44,7 @@ ifeq ($(uname_S),GNU/kFreeBSD) HAVE_PATHS_H = YesPlease DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease LIBC_CONTAINS_LIBINTL = YesPlease + FREAD_READS_DIRECTORIES = UnfortunatelyYes endif ifeq ($(uname_S),UnixWare) CC = cc @@ -108,6 +110,7 @@ ifeq ($(uname_S),Darwin) BASIC_CFLAGS += -DPRECOMPOSE_UNICODE BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1 HAVE_BSD_SYSCTL = YesPlease + FREAD_READS_DIRECTORIES = UnfortunatelyYes endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -201,6 +204,7 @@ ifeq ($(uname_S),FreeBSD) GMTIME_UNRELIABLE_ERRORS = UnfortunatelyYes HAVE_BSD_SYSCTL = YesPlease PAGER_ENV = LESS=FRX LV=-c MORE=FRX + FREAD_READS_DIRECTORIES = UnfortunatelyYes endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease @@ -551,6 +555,7 @@ else NO_GETTEXT = USE_GETTEXT_SCHEME = fallthrough USE_LIBPCRE= YesPlease + NO_LIBPCRE1_JIT = UnfortunatelyYes NO_CURL = USE_NED_ALLOCATOR = YesPlease else diff --git a/configure.ac b/configure.ac index 128165529f..2f55237e65 100644 --- a/configure.ac +++ b/configure.ac @@ -250,24 +250,66 @@ AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]), GIT_PARSE_WITH([openssl])) -# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be -# able to use Perl-compatible regular expressions. +# Define USE_LIBPCRE if you have and want to use libpcre. Various +# commands such as log and grep offer runtime options to use +# Perl-compatible regular expressions instead of standard or extended +# POSIX regular expressions. # -# Define LIBPCREDIR=/foo/bar if your libpcre header and library files are in +# Currently USE_LIBPCRE is a synonym for USE_LIBPCRE1, define +# USE_LIBPCRE2 instead if you'd like to use version 2 of the PCRE +# library. The USE_LIBPCRE flag will likely be changed to mean v2 by +# default in future releases. +# +# Define LIBPCREDIR=/foo/bar if your PCRE header and library files are in # /foo/bar/include and /foo/bar/lib directories. # AC_ARG_WITH(libpcre, -AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)]) +AS_HELP_STRING([--with-libpcre],[synonym for --with-libpcre1]), + if test "$withval" = "no"; then + USE_LIBPCRE1= + elif test "$withval" = "yes"; then + USE_LIBPCRE1=YesPlease + else + USE_LIBPCRE1=YesPlease + LIBPCREDIR=$withval + AC_MSG_NOTICE([Setting LIBPCREDIR to $LIBPCREDIR]) + dnl USE_LIBPCRE1 can still be modified below, so don't substitute + dnl it yet. + GIT_CONF_SUBST([LIBPCREDIR]) + fi) + +AC_ARG_WITH(libpcre1, +AS_HELP_STRING([--with-libpcre1],[support Perl-compatible regexes via libpcre1 (default is NO)]) +AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]), + if test "$withval" = "no"; then + USE_LIBPCRE1= + elif test "$withval" = "yes"; then + USE_LIBPCRE1=YesPlease + else + USE_LIBPCRE1=YesPlease + LIBPCREDIR=$withval + AC_MSG_NOTICE([Setting LIBPCREDIR to $LIBPCREDIR]) + dnl USE_LIBPCRE1 can still be modified below, so don't substitute + dnl it yet. + GIT_CONF_SUBST([LIBPCREDIR]) + fi) + +AC_ARG_WITH(libpcre2, +AS_HELP_STRING([--with-libpcre2],[support Perl-compatible regexes via libpcre2 (default is NO)]) AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]), + if test -n "$USE_LIBPCRE1"; then + AC_MSG_ERROR([Only supply one of --with-libpcre1 or --with-libpcre2!]) + fi + if test "$withval" = "no"; then - USE_LIBPCRE= + USE_LIBPCRE2= elif test "$withval" = "yes"; then - USE_LIBPCRE=YesPlease + USE_LIBPCRE2=YesPlease else - USE_LIBPCRE=YesPlease + USE_LIBPCRE2=YesPlease LIBPCREDIR=$withval AC_MSG_NOTICE([Setting LIBPCREDIR to $LIBPCREDIR]) - dnl USE_LIBPCRE can still be modified below, so don't substitute + dnl USE_LIBPCRE2 can still be modified below, so don't substitute dnl it yet. GIT_CONF_SUBST([LIBPCREDIR]) fi) @@ -499,11 +541,11 @@ GIT_CONF_SUBST([NEEDS_SSL_WITH_CRYPTO]) GIT_CONF_SUBST([NO_OPENSSL]) # -# Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be -# able to use Perl-compatible regular expressions. +# Handle the USE_LIBPCRE1 and USE_LIBPCRE2 options potentially set +# above. # -if test -n "$USE_LIBPCRE"; then +if test -n "$USE_LIBPCRE1"; then GIT_STASH_FLAGS($LIBPCREDIR) @@ -513,7 +555,22 @@ AC_CHECK_LIB([pcre], [pcre_version], GIT_UNSTASH_FLAGS($LIBPCREDIR) -GIT_CONF_SUBST([USE_LIBPCRE]) +GIT_CONF_SUBST([USE_LIBPCRE1]) + +fi + + +if test -n "$USE_LIBPCRE2"; then + +GIT_STASH_FLAGS($LIBPCREDIR) + +AC_CHECK_LIB([pcre2-8], [pcre2_config_8], +[USE_LIBPCRE2=YesPlease], +[USE_LIBPCRE2=]) + +GIT_UNSTASH_FLAGS($LIBPCREDIR) + +GIT_CONF_SUBST([USE_LIBPCRE2]) fi @@ -865,9 +922,9 @@ AC_CACHE_CHECK([whether system succeeds to read fopen'ed directory], [ AC_RUN_IFELSE( [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], - [[char c; + [[ FILE *f = fopen(".", "r"); - return f && fread(&c, 1, 1, f)]])], + return f)]])], [ac_cv_fread_reads_directories=no], [ac_cv_fread_reads_directories=yes]) ]) @@ -1,5 +1,6 @@ #include "git-compat-util.h" #include "cache.h" +#include "config.h" #include "pkt-line.h" #include "quote.h" #include "refs.h" @@ -71,7 +72,7 @@ static void parse_one_symref_info(struct string_list *symref, const char *val, i check_refname_format(target, REFNAME_ALLOW_ONELEVEL)) /* "symref=bogus:pair */ goto reject; - item = string_list_append(symref, sym); + item = string_list_append_nodup(symref, sym); item->util = target; return; reject: diff --git a/contrib/coccinelle/free.cocci b/contrib/coccinelle/free.cocci index c03ba737e5..f2d97e755b 100644 --- a/contrib/coccinelle/free.cocci +++ b/contrib/coccinelle/free.cocci @@ -9,3 +9,18 @@ expression E; @@ - if (!E) free(E); + +@@ +type T; +T *ptr; +@@ +- free(ptr); +- ptr = NULL; ++ FREE_AND_NULL(ptr); + +@@ +expression E; +@@ +- free(E); +- E = NULL; ++ FREE_AND_NULL(E); diff --git a/contrib/completion/.gitattributes b/contrib/completion/.gitattributes new file mode 100644 index 0000000000..19116944c1 --- /dev/null +++ b/contrib/completion/.gitattributes @@ -0,0 +1 @@ +*.bash eol=lf diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 1ed0a09fee..48a2f26622 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2336,14 +2336,23 @@ _git_config () esac __gitcomp " add.ignoreErrors + advice.amWorkDir advice.commitBeforeMerge advice.detachedHead advice.implicitIdentity - advice.pushNonFastForward + advice.pushAlreadyExists + advice.pushFetchFirst + advice.pushNeedsForce + advice.pushNonFFCurrent + advice.pushNonFFMatching + advice.pushUpdateRejected advice.resolveConflict + advice.rmHints advice.statusHints + advice.statusUoption alias. am.keepcr + am.threeWay apply.ignorewhitespace apply.whitespace branch.autosetupmerge @@ -2395,14 +2404,19 @@ _git_config () color.status.untracked color.status.updated color.ui + commit.cleanup + commit.gpgSign commit.status commit.template + commit.verbose core.abbrev core.askpass core.attributesfile core.autocrlf core.bare core.bigFileThreshold + core.checkStat + core.commentChar core.compression core.createObject core.deltaBaseCacheLimit @@ -2412,6 +2426,8 @@ _git_config () core.fileMode core.fsyncobjectfiles core.gitProxy + core.hideDotFiles + core.hooksPath core.ignoreStat core.ignorecase core.logAllRefUpdates @@ -2419,20 +2435,30 @@ _git_config () core.notesRef core.packedGitLimit core.packedGitWindowSize + core.packedRefsTimeout core.pager + core.precomposeUnicode core.preferSymlinkRefs core.preloadindex + core.protectHFS + core.protectNTFS core.quotepath core.repositoryFormatVersion core.safecrlf core.sharedRepository core.sparseCheckout + core.splitIndex + core.sshCommand core.symlinks core.trustctime core.untrackedCache core.warnAmbiguousRefs core.whitespace core.worktree + credential.helper + credential.useHttpPath + credential.username + credentialCache.ignoreSIGHUP diff.autorefreshindex diff.external diff.ignoreSubmodules @@ -2464,15 +2490,19 @@ _git_config () format.thread format.to gc. + gc.aggressiveDepth gc.aggressiveWindow gc.auto + gc.autoDetach gc.autopacklimit + gc.logExpiry gc.packrefs gc.pruneexpire gc.reflogexpire gc.reflogexpireunreachable gc.rerereresolved gc.rerereunresolved + gc.worktreePruneExpire gitcvs.allbinary gitcvs.commitmsgannotation gitcvs.dbTableNamePrefix @@ -2813,7 +2843,7 @@ _git_show_branch () _git_stash () { local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked' - local subcommands='save list show apply clear drop pop create branch' + local subcommands='push save list show apply clear drop pop create branch' local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then case "$cur" in @@ -2828,6 +2858,9 @@ _git_stash () esac else case "$subcommand,$cur" in + push,--*) + __gitcomp "$save_opts --message" + ;; save,--*) __gitcomp "$save_opts" ;; diff --git a/contrib/diff-highlight/.gitignore b/contrib/diff-highlight/.gitignore new file mode 100644 index 0000000000..c07454824e --- /dev/null +++ b/contrib/diff-highlight/.gitignore @@ -0,0 +1,2 @@ +shebang.perl +diff-highlight diff --git a/contrib/diff-highlight/diff-highlight b/contrib/diff-highlight/DiffHighlight.pm index 81bd8040e3..663992e530 100755..100644 --- a/contrib/diff-highlight/diff-highlight +++ b/contrib/diff-highlight/DiffHighlight.pm @@ -1,4 +1,4 @@ -#!/usr/bin/perl +package DiffHighlight; use 5.008; use warnings FATAL => 'all'; @@ -29,13 +29,14 @@ my @removed; my @added; my $in_hunk; -# Some scripts may not realize that SIGPIPE is being ignored when launching the -# pager--for instance scripts written in Python. -$SIG{PIPE} = 'DEFAULT'; +our $line_cb = sub { print @_ }; +our $flush_cb = sub { local $| = 1 }; + +sub handle_line { + local $_ = shift; -while (<>) { if (!$in_hunk) { - print; + $line_cb->($_); $in_hunk = /^$GRAPH*$COLOR*\@\@ /; } elsif (/^$GRAPH*$COLOR*-/) { @@ -49,7 +50,7 @@ while (<>) { @removed = (); @added = (); - print; + $line_cb->($_); $in_hunk = /^$GRAPH*$COLOR*[\@ ]/; } @@ -62,15 +63,22 @@ while (<>) { # place to flush. Flushing on a blank line is a heuristic that # happens to match git-log output. if (!length) { - local $| = 1; + $flush_cb->(); } } -# Flush any queued hunk (this can happen when there is no trailing context in -# the final diff of the input). -show_hunk(\@removed, \@added); +sub flush { + # Flush any queued hunk (this can happen when there is no trailing + # context in the final diff of the input). + show_hunk(\@removed, \@added); +} -exit 0; +sub highlight_stdin { + while (<STDIN>) { + handle_line($_); + } + flush(); +} # Ideally we would feed the default as a human-readable color to # git-config as the fallback value. But diff-highlight does @@ -88,7 +96,7 @@ sub show_hunk { # If one side is empty, then there is nothing to compare or highlight. if (!@$a || !@$b) { - print @$a, @$b; + $line_cb->(@$a, @$b); return; } @@ -97,17 +105,17 @@ sub show_hunk { # stupid, and only handle multi-line hunks that remove and add the same # number of lines. if (@$a != @$b) { - print @$a, @$b; + $line_cb->(@$a, @$b); return; } my @queue; for (my $i = 0; $i < @$a; $i++) { my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); - print $rm; + $line_cb->($rm); push @queue, $add; } - print @queue; + $line_cb->(@queue); } sub highlight_pair { diff --git a/contrib/diff-highlight/Makefile b/contrib/diff-highlight/Makefile index 9018724524..fbf5c58249 100644 --- a/contrib/diff-highlight/Makefile +++ b/contrib/diff-highlight/Makefile @@ -1,5 +1,20 @@ -# nothing to build -all: +all: diff-highlight -test: +PERL_PATH = /usr/bin/perl +-include ../../config.mak + +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) + +diff-highlight: shebang.perl DiffHighlight.pm diff-highlight.perl + cat $^ >$@+ + chmod +x $@+ + mv $@+ $@ + +shebang.perl: FORCE + @echo '#!$(PERL_PATH_SQ)' >$@+ + @cmp $@+ $@ >/dev/null 2>/dev/null || mv $@+ $@ + +test: all $(MAKE) -C t + +.PHONY: FORCE diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README index 836b97a730..d4c2343175 100644 --- a/contrib/diff-highlight/README +++ b/contrib/diff-highlight/README @@ -99,6 +99,36 @@ newHighlight = "black #aaffaa" --------------------------------------------- +Using diff-highlight as a module +-------------------------------- + +If you want to pre- or post- process the highlighted lines as part of +another perl script, you can use the DiffHighlight module. You can +either "require" it or just cat the module together with your script (to +avoid run-time dependencies). + +Your script may set up one or more of the following variables: + + - $DiffHighlight::line_cb - this should point to a function which is + called whenever DiffHighlight has lines (which may contain + highlights) to output. The default function prints each line to + stdout. Note that the function may be called with multiple lines. + + - $DiffHighlight::flush_cb - this should point to a function which + flushes the output (because DiffHighlight believes it has completed + processing a logical chunk of input). The default function flushes + stdout. + +The script may then feed lines, one at a time, to DiffHighlight::handle_line(). +When lines are done processing, they will be fed to $line_cb. Note that +DiffHighlight may queue up many input lines (to analyze a whole hunk) +before calling $line_cb. After providing all lines, call +DiffHighlight::flush() to flush any unprocessed lines. + +If you just want to process stdin, DiffHighlight::highlight_stdin() +is a convenience helper which will loop and flush for you. + + Bugs ---- diff --git a/contrib/diff-highlight/diff-highlight.perl b/contrib/diff-highlight/diff-highlight.perl new file mode 100644 index 0000000000..9b3e9c1f4d --- /dev/null +++ b/contrib/diff-highlight/diff-highlight.perl @@ -0,0 +1,8 @@ +package main; + +# Some scripts may not realize that SIGPIPE is being ignored when launching the +# pager--for instance scripts written in Python. +$SIG{PIPE} = 'DEFAULT'; + +DiffHighlight::highlight_stdin(); +exit 0; diff --git a/contrib/persistent-https/README b/contrib/persistent-https/README index f784dd2e66..7c4cd8d257 100644 --- a/contrib/persistent-https/README +++ b/contrib/persistent-https/README @@ -35,6 +35,16 @@ to use persistent-https: [url "persistent-http"] insteadof = http +You may also want to allow the use of the persistent-https helper for +submodule URLs (since any https URLs pointing to submodules will be +rewritten, and Git's out-of-the-box defaults forbid submodules from +using unknown remote helpers): + +[protocol "persistent-https"] + allow = always +[protocol "persistent-http"] + allow = always + ##################################################################### # BUILDING FROM SOURCE diff --git a/contrib/workdir/.gitattributes b/contrib/workdir/.gitattributes new file mode 100644 index 0000000000..1f78c5d1bd --- /dev/null +++ b/contrib/workdir/.gitattributes @@ -0,0 +1 @@ +/git-new-workdir eol=lf @@ -1,9 +1,12 @@ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "config.h" #include "attr.h" #include "run-command.h" #include "quote.h" #include "sigchain.h" #include "pkt-line.h" +#include "sub-process.h" /* * convert.c - convert a file when checking it out and checking it in. @@ -133,11 +136,12 @@ static const char *gather_convert_stats_ascii(const char *data, unsigned long si } } -const char *get_cached_convert_stats_ascii(const char *path) +const char *get_cached_convert_stats_ascii(const struct index_state *istate, + const char *path) { const char *ret; unsigned long sz; - void *data = read_blob_data_from_cache(path, &sz); + void *data = read_blob_data_from_index(istate, path, &sz); ret = gather_convert_stats_ascii(data, sz); free(data); return ret; @@ -216,13 +220,13 @@ static void check_safe_crlf(const char *path, enum crlf_action crlf_action, } } -static int has_cr_in_index(const char *path) +static int has_cr_in_index(const struct index_state *istate, const char *path) { unsigned long sz; void *data; int has_cr; - data = read_blob_data_from_cache(path, &sz); + data = read_blob_data_from_index(istate, path, &sz); if (!data) return 0; has_cr = memchr(data, '\r', sz) != NULL; @@ -252,7 +256,8 @@ static int will_convert_lf_to_crlf(size_t len, struct text_stat *stats, } -static int crlf_to_git(const char *path, const char *src, size_t len, +static int crlf_to_git(const struct index_state *istate, + const char *path, const char *src, size_t len, struct strbuf *buf, enum crlf_action crlf_action, enum safe_crlf checksafe) { @@ -284,7 +289,8 @@ static int crlf_to_git(const char *path, const char *src, size_t len, * unless we want to renormalize in a merge or * cherry-pick. */ - if ((checksafe != SAFE_CRLF_RENORMALIZE) && has_cr_in_index(path)) + if ((checksafe != SAFE_CRLF_RENORMALIZE) && + has_cr_in_index(istate, path)) convert_crlf_into_lf = 0; } if ((checksafe == SAFE_CRLF_WARN || @@ -497,126 +503,26 @@ static int apply_single_file_filter(const char *path, const char *src, size_t le #define CAP_SMUDGE (1u<<1) struct cmd2process { - struct hashmap_entry ent; /* must be the first member! */ + struct subprocess_entry subprocess; /* must be the first member! */ unsigned int supported_capabilities; - const char *cmd; - struct child_process process; }; -static int cmd_process_map_initialized; -static struct hashmap cmd_process_map; - -static int cmd2process_cmp(const struct cmd2process *e1, - const struct cmd2process *e2, - const void *unused) -{ - return strcmp(e1->cmd, e2->cmd); -} - -static struct cmd2process *find_multi_file_filter_entry(struct hashmap *hashmap, const char *cmd) -{ - struct cmd2process key; - hashmap_entry_init(&key, strhash(cmd)); - key.cmd = cmd; - return hashmap_get(hashmap, &key, NULL); -} +static int subprocess_map_initialized; +static struct hashmap subprocess_map; -static int packet_write_list(int fd, const char *line, ...) +static int start_multi_file_filter_fn(struct subprocess_entry *subprocess) { - va_list args; int err; - va_start(args, line); - for (;;) { - if (!line) - break; - if (strlen(line) > LARGE_PACKET_DATA_MAX) - return -1; - err = packet_write_fmt_gently(fd, "%s\n", line); - if (err) - return err; - line = va_arg(args, const char*); - } - va_end(args); - return packet_flush_gently(fd); -} - -static void read_multi_file_filter_status(int fd, struct strbuf *status) -{ - struct strbuf **pair; - char *line; - for (;;) { - line = packet_read_line(fd, NULL); - if (!line) - break; - pair = strbuf_split_str(line, '=', 2); - if (pair[0] && pair[0]->len && pair[1]) { - /* the last "status=<foo>" line wins */ - if (!strcmp(pair[0]->buf, "status=")) { - strbuf_reset(status); - strbuf_addbuf(status, pair[1]); - } - } - strbuf_list_free(pair); - } -} - -static void kill_multi_file_filter(struct hashmap *hashmap, struct cmd2process *entry) -{ - if (!entry) - return; - - entry->process.clean_on_exit = 0; - kill(entry->process.pid, SIGTERM); - finish_command(&entry->process); - - hashmap_remove(hashmap, entry, NULL); - free(entry); -} - -static void stop_multi_file_filter(struct child_process *process) -{ - sigchain_push(SIGPIPE, SIG_IGN); - /* Closing the pipe signals the filter to initiate a shutdown. */ - close(process->in); - close(process->out); - sigchain_pop(SIGPIPE); - /* Finish command will wait until the shutdown is complete. */ - finish_command(process); -} - -static struct cmd2process *start_multi_file_filter(struct hashmap *hashmap, const char *cmd) -{ - int err; - struct cmd2process *entry; - struct child_process *process; - const char *argv[] = { cmd, NULL }; + struct cmd2process *entry = (struct cmd2process *)subprocess; struct string_list cap_list = STRING_LIST_INIT_NODUP; char *cap_buf; const char *cap_name; - - entry = xmalloc(sizeof(*entry)); - entry->cmd = cmd; - entry->supported_capabilities = 0; - process = &entry->process; - - child_process_init(process); - process->argv = argv; - process->use_shell = 1; - process->in = -1; - process->out = -1; - process->clean_on_exit = 1; - process->clean_on_exit_handler = stop_multi_file_filter; - - if (start_command(process)) { - error("cannot fork to run external filter '%s'", cmd); - return NULL; - } - - hashmap_entry_init(entry, strhash(cmd)); + struct child_process *process = &subprocess->process; + const char *cmd = subprocess->cmd; sigchain_push(SIGPIPE, SIG_IGN); - err = packet_write_list(process->in, "git-filter-client", "version=2", NULL); + err = packet_writel(process->in, "git-filter-client", "version=2", NULL); if (err) goto done; @@ -632,7 +538,7 @@ static struct cmd2process *start_multi_file_filter(struct hashmap *hashmap, cons if (err) goto done; - err = packet_write_list(process->in, "capability=clean", "capability=smudge", NULL); + err = packet_writel(process->in, "capability=clean", "capability=smudge", NULL); for (;;) { cap_buf = packet_read_line(process->out, NULL); @@ -661,14 +567,7 @@ static struct cmd2process *start_multi_file_filter(struct hashmap *hashmap, cons done: sigchain_pop(SIGPIPE); - if (err || errno == EPIPE) { - error("initialization for external filter '%s' failed", cmd); - kill_multi_file_filter(hashmap, entry); - return NULL; - } - - hashmap_add(hashmap, entry); - return entry; + return err; } static int apply_multi_file_filter(const char *path, const char *src, size_t len, @@ -682,22 +581,26 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len struct strbuf filter_status = STRBUF_INIT; const char *filter_type; - if (!cmd_process_map_initialized) { - cmd_process_map_initialized = 1; - hashmap_init(&cmd_process_map, (hashmap_cmp_fn) cmd2process_cmp, 0); + if (!subprocess_map_initialized) { + subprocess_map_initialized = 1; + hashmap_init(&subprocess_map, (hashmap_cmp_fn) cmd2process_cmp, 0); entry = NULL; } else { - entry = find_multi_file_filter_entry(&cmd_process_map, cmd); + entry = (struct cmd2process *)subprocess_find_entry(&subprocess_map, cmd); } fflush(NULL); if (!entry) { - entry = start_multi_file_filter(&cmd_process_map, cmd); - if (!entry) + entry = xmalloc(sizeof(*entry)); + entry->supported_capabilities = 0; + + if (subprocess_start(&subprocess_map, &entry->subprocess, cmd, start_multi_file_filter_fn)) { + free(entry); return 0; + } } - process = &entry->process; + process = &entry->subprocess.process; if (!(wanted_capability & entry->supported_capabilities)) return 0; @@ -737,7 +640,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len if (err) goto done; - read_multi_file_filter_status(process->out, &filter_status); + err = subprocess_read_status(process->out, &filter_status); + if (err) + goto done; + err = strcmp(filter_status.buf, "success"); if (err) goto done; @@ -746,13 +652,16 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len if (err) goto done; - read_multi_file_filter_status(process->out, &filter_status); + err = subprocess_read_status(process->out, &filter_status); + if (err) + goto done; + err = strcmp(filter_status.buf, "success"); done: sigchain_pop(SIGPIPE); - if (err || errno == EPIPE) { + if (err) { if (!strcmp(filter_status.buf, "error")) { /* The filter signaled a problem with the file. */ } else if (!strcmp(filter_status.buf, "abort")) { @@ -768,7 +677,8 @@ done: * Force shutdown and restart if another blob requires filtering. */ error("external filter '%s' failed", cmd); - kill_multi_file_filter(&cmd_process_map, entry); + subprocess_stop(&subprocess_map, &entry->subprocess); + free(entry); } } else { strbuf_swap(dst, &nbuf); @@ -1176,7 +1086,8 @@ const char *get_convert_attr_ascii(const char *path) return ""; } -int convert_to_git(const char *path, const char *src, size_t len, +int convert_to_git(const struct index_state *istate, + const char *path, const char *src, size_t len, struct strbuf *dst, enum safe_crlf checksafe) { int ret = 0; @@ -1192,7 +1103,7 @@ int convert_to_git(const char *path, const char *src, size_t len, src = dst->buf; len = dst->len; } - ret |= crlf_to_git(path, src, len, dst, ca.crlf_action, checksafe); + ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, checksafe); if (ret && dst) { src = dst->buf; len = dst->len; @@ -1200,7 +1111,8 @@ int convert_to_git(const char *path, const char *src, size_t len, return ret | ident_to_git(path, src, len, dst, ca.ident); } -void convert_to_git_filter_fd(const char *path, int fd, struct strbuf *dst, +void convert_to_git_filter_fd(const struct index_state *istate, + const char *path, int fd, struct strbuf *dst, enum safe_crlf checksafe) { struct conv_attrs ca; @@ -1212,7 +1124,7 @@ void convert_to_git_filter_fd(const char *path, int fd, struct strbuf *dst, if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN)) die("%s: clean filter '%s' failed", path, ca.drv->name); - crlf_to_git(path, dst->buf, dst->len, dst, ca.crlf_action, checksafe); + crlf_to_git(istate, path, dst->buf, dst->len, dst, ca.crlf_action, checksafe); ident_to_git(path, dst->buf, dst->len, dst, ca.ident); } @@ -1255,14 +1167,15 @@ int convert_to_working_tree(const char *path, const char *src, size_t len, struc return convert_to_working_tree_internal(path, src, len, dst, 0); } -int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst) +int renormalize_buffer(const struct index_state *istate, const char *path, + const char *src, size_t len, struct strbuf *dst) { int ret = convert_to_working_tree_internal(path, src, len, dst, 1); if (ret) { src = dst->buf; len = dst->len; } - return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_RENORMALIZE); + return ret | convert_to_git(istate, path, src, len, dst, SAFE_CRLF_RENORMALIZE); } /***************************************************************** @@ -4,6 +4,8 @@ #ifndef CONVERT_H #define CONVERT_H +struct index_state; + enum safe_crlf { SAFE_CRLF_FALSE = 0, SAFE_CRLF_FAIL = 1, @@ -33,23 +35,28 @@ enum eol { }; extern enum eol core_eol; -extern const char *get_cached_convert_stats_ascii(const char *path); +extern const char *get_cached_convert_stats_ascii(const struct index_state *istate, + const char *path); extern const char *get_wt_convert_stats_ascii(const char *path); extern const char *get_convert_attr_ascii(const char *path); /* returns 1 if *dst was used */ -extern int convert_to_git(const char *path, const char *src, size_t len, +extern int convert_to_git(const struct index_state *istate, + const char *path, const char *src, size_t len, struct strbuf *dst, enum safe_crlf checksafe); extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); -extern int renormalize_buffer(const char *path, const char *src, size_t len, +extern int renormalize_buffer(const struct index_state *istate, + const char *path, const char *src, size_t len, struct strbuf *dst); -static inline int would_convert_to_git(const char *path) +static inline int would_convert_to_git(const struct index_state *istate, + const char *path) { - return convert_to_git(path, NULL, 0, NULL, 0); + return convert_to_git(istate, path, NULL, 0, NULL, 0); } /* Precondition: would_convert_to_git_filter_fd(path) == true */ -extern void convert_to_git_filter_fd(const char *path, int fd, +extern void convert_to_git_filter_fd(const struct index_state *istate, + const char *path, int fd, struct strbuf *dst, enum safe_crlf checksafe); extern int would_convert_to_git_filter_fd(const char *path); diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index f3814cc47a..0d5c625094 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "tempfile.h" #include "credential.h" #include "unix-socket.h" diff --git a/credential.c b/credential.c index aa996669fc..9747f47b18 100644 --- a/credential.c +++ b/credential.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "credential.h" #include "string-list.h" #include "run-command.h" @@ -93,8 +94,7 @@ static void credential_apply_config(struct credential *c) c->configured = 1; if (!c->use_http_path && proto_is_http(c->protocol)) { - free(c->path); - c->path = NULL; + FREE_AND_NULL(c->path); } } @@ -314,10 +314,8 @@ void credential_reject(struct credential *c) for (i = 0; i < c->helpers.nr; i++) credential_do(c, c->helpers.items[i].string, "erase"); - free(c->username); - c->username = NULL; - free(c->password); - c->password = NULL; + FREE_AND_NULL(c->username); + FREE_AND_NULL(c->password); c->approved = 0; } @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "pkt-line.h" #include "run-command.h" #include "strbuf.h" @@ -70,6 +70,12 @@ static struct tm *time_to_tm(timestamp_t time, int tz) return gmtime(&t); } +static struct tm *time_to_tm_local(timestamp_t time) +{ + time_t t = time; + return localtime(&t); +} + /* * What value of "tz" was in effect back then at "time" in the * local timezone? @@ -214,7 +220,10 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode) return timebuf.buf; } - tm = time_to_tm(time, tz); + if (mode->local) + tm = time_to_tm_local(time); + else + tm = time_to_tm(time, tz); if (!tm) { tm = time_to_tm(0, 0); tz = 0; @@ -246,7 +255,8 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode) month_names[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, tz); else if (mode->type == DATE_STRFTIME) - strbuf_addftime(&timebuf, mode->strftime_fmt, tm); + strbuf_addftime(&timebuf, mode->strftime_fmt, tm, tz, + mode->local ? NULL : ""); else strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d", weekday_names[tm->tm_wday], diff --git a/diff-lib.c b/diff-lib.c index 2982bf055a..2a52b07954 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -29,7 +29,7 @@ static int check_removed(const struct cache_entry *ce, struct stat *st) { if (lstat(ce->name, st) < 0) { - if (errno != ENOENT && errno != ENOTDIR) + if (!is_missing_file_error(errno)) return -1; return 1; } @@ -101,7 +101,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) struct cache_entry *ce = active_cache[i]; int changed; unsigned dirty_submodule = 0; - const unsigned char *old_sha1, *new_sha1; + const struct object_id *old_oid, *new_oid; if (diff_can_quit_early(&revs->diffopt)) break; @@ -179,8 +179,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) free(dpath); continue; } - free(dpath); - dpath = NULL; + FREE_AND_NULL(dpath); /* * Show the diff for the 'ce' if we found the one @@ -210,14 +209,14 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } diff_addremove(&revs->diffopt, '-', ce->ce_mode, - ce->oid.hash, + &ce->oid, !is_null_oid(&ce->oid), ce->name, 0); continue; } else if (revs->diffopt.ita_invisible_in_index && ce_intent_to_add(ce)) { diff_addremove(&revs->diffopt, '+', ce->ce_mode, - EMPTY_BLOB_SHA1_BIN, 0, + &empty_tree_oid, 0, ce->name, 0); continue; } @@ -233,12 +232,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } oldmode = ce->ce_mode; - old_sha1 = ce->oid.hash; - new_sha1 = changed ? null_sha1 : ce->oid.hash; + old_oid = &ce->oid; + new_oid = changed ? &null_oid : &ce->oid; diff_change(&revs->diffopt, oldmode, newmode, - old_sha1, new_sha1, - !is_null_sha1(old_sha1), - !is_null_sha1(new_sha1), + old_oid, new_oid, + !is_null_oid(old_oid), + !is_null_oid(new_oid), ce->name, 0, dirty_submodule); } @@ -255,21 +254,21 @@ int run_diff_files(struct rev_info *revs, unsigned int option) static void diff_index_show_file(struct rev_info *revs, const char *prefix, const struct cache_entry *ce, - const unsigned char *sha1, int sha1_valid, + const struct object_id *oid, int oid_valid, unsigned int mode, unsigned dirty_submodule) { diff_addremove(&revs->diffopt, prefix[0], mode, - sha1, sha1_valid, ce->name, dirty_submodule); + oid, oid_valid, ce->name, dirty_submodule); } static int get_stat_data(const struct cache_entry *ce, - const unsigned char **sha1p, + const struct object_id **oidp, unsigned int *modep, int cached, int match_missing, unsigned *dirty_submodule, struct diff_options *diffopt) { - const unsigned char *sha1 = ce->oid.hash; + const struct object_id *oid = &ce->oid; unsigned int mode = ce->ce_mode; if (!cached && !ce_uptodate(ce)) { @@ -280,7 +279,7 @@ static int get_stat_data(const struct cache_entry *ce, return -1; else if (changed) { if (match_missing) { - *sha1p = sha1; + *oidp = oid; *modep = mode; return 0; } @@ -290,11 +289,11 @@ static int get_stat_data(const struct cache_entry *ce, 0, dirty_submodule); if (changed) { mode = ce_mode_from_stat(ce, st.st_mode); - sha1 = null_sha1; + oid = &null_oid; } } - *sha1p = sha1; + *oidp = oid; *modep = mode; return 0; } @@ -303,7 +302,7 @@ static void show_new_file(struct rev_info *revs, const struct cache_entry *new, int cached, int match_missing) { - const unsigned char *sha1; + const struct object_id *oid; unsigned int mode; unsigned dirty_submodule = 0; @@ -311,11 +310,11 @@ static void show_new_file(struct rev_info *revs, * New file in the index: it might actually be different in * the working tree. */ - if (get_stat_data(new, &sha1, &mode, cached, match_missing, + if (get_stat_data(new, &oid, &mode, cached, match_missing, &dirty_submodule, &revs->diffopt) < 0) return; - diff_index_show_file(revs, "+", new, sha1, !is_null_sha1(sha1), mode, dirty_submodule); + diff_index_show_file(revs, "+", new, oid, !is_null_oid(oid), mode, dirty_submodule); } static int show_modified(struct rev_info *revs, @@ -325,20 +324,20 @@ static int show_modified(struct rev_info *revs, int cached, int match_missing) { unsigned int mode, oldmode; - const unsigned char *sha1; + const struct object_id *oid; unsigned dirty_submodule = 0; - if (get_stat_data(new, &sha1, &mode, cached, match_missing, + if (get_stat_data(new, &oid, &mode, cached, match_missing, &dirty_submodule, &revs->diffopt) < 0) { if (report_missing) diff_index_show_file(revs, "-", old, - old->oid.hash, 1, old->ce_mode, + &old->oid, 1, old->ce_mode, 0); return -1; } if (revs->combine_merges && !cached && - (hashcmp(sha1, old->oid.hash) || oidcmp(&old->oid, &new->oid))) { + (oidcmp(oid, &old->oid) || oidcmp(&old->oid, &new->oid))) { struct combine_diff_path *p; int pathlen = ce_namelen(new); @@ -362,12 +361,12 @@ static int show_modified(struct rev_info *revs, } oldmode = old->ce_mode; - if (mode == oldmode && !hashcmp(sha1, old->oid.hash) && !dirty_submodule && + if (mode == oldmode && !oidcmp(oid, &old->oid) && !dirty_submodule && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) return 0; diff_change(&revs->diffopt, oldmode, mode, - old->oid.hash, sha1, 1, !is_null_sha1(sha1), + &old->oid, oid, 1, !is_null_oid(oid), old->name, 0, dirty_submodule); return 0; } @@ -409,7 +408,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, struct diff_filepair *pair; pair = diff_unmerge(&revs->diffopt, idx->name); if (tree) - fill_filespec(pair->one, tree->oid.hash, 1, + fill_filespec(pair->one, &tree->oid, 1, tree->ce_mode); return; } @@ -426,7 +425,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, * Something removed from the tree? */ if (!idx) { - diff_index_show_file(revs, "-", tree, tree->oid.hash, 1, + diff_index_show_file(revs, "-", tree, &tree->oid, 1, tree->ce_mode, 0); return; } diff --git a/diff-no-index.c b/diff-no-index.c index 79229382b0..80ff17d460 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -82,7 +82,7 @@ static struct diff_filespec *noindex_filespec(const char *name, int mode) if (!name) name = "/dev/null"; s = alloc_filespec(name); - fill_filespec(s, null_sha1, 0, mode); + fill_filespec(s, &null_oid, 0, mode); if (name == file_from_standard_input) populate_from_stdin(s); return s; @@ -2,6 +2,7 @@ * Copyright (C) 2005 Junio C Hamano */ #include "cache.h" +#include "config.h" #include "tempfile.h" #include "quote.h" #include "diff.h" @@ -27,7 +28,7 @@ #endif static int diff_detect_rename_default; -static int diff_indent_heuristic; /* experimental */ +static int diff_indent_heuristic = 1; static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; @@ -290,9 +291,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) return 0; } - if (git_diff_heuristic_config(var, value, cb) < 0) - return -1; - if (!strcmp(var, "diff.wserrorhighlight")) { int val = parse_ws_error_highlight(value); if (val < 0) @@ -351,6 +349,9 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) if (starts_with(var, "submodule.")) return parse_submodule_config_option(var, value); + if (git_diff_heuristic_config(var, value, cb) < 0) + return -1; + return git_default_config(var, value, cb); } @@ -1218,8 +1219,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata) regfree(ecbdata->diff_words->word_regex); free(ecbdata->diff_words->word_regex); } - free(ecbdata->diff_words); - ecbdata->diff_words = NULL; + FREE_AND_NULL(ecbdata->diff_words); } } @@ -2702,13 +2702,13 @@ void free_filespec(struct diff_filespec *spec) } } -void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, - int sha1_valid, unsigned short mode) +void fill_filespec(struct diff_filespec *spec, const struct object_id *oid, + int oid_valid, unsigned short mode) { if (mode) { spec->mode = canon_mode(mode); - hashcpy(spec->oid.hash, sha1); - spec->oid_valid = sha1_valid; + oidcpy(&spec->oid, oid); + spec->oid_valid = oid_valid; } } @@ -2717,7 +2717,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, * the work tree has that object contents, return true, so that * prepare_temp_file() does not have to inflate and extract. */ -static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file) +static int reuse_worktree_file(const char *name, const struct object_id *oid, int want_file) { const struct cache_entry *ce; struct stat st; @@ -2748,14 +2748,14 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * objects however would tend to be slower as they need * to be individually opened and inflated. */ - if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1)) + if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(oid->hash)) return 0; /* * Similarly, if we'd have to convert the file contents anyway, that * makes the optimization not worthwhile. */ - if (!want_file && would_convert_to_git(name)) + if (!want_file && would_convert_to_git(&the_index, name)) return 0; len = strlen(name); @@ -2768,7 +2768,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int * This is not the sha1 we are looking for, or * unreusable because it is not a regular file. */ - if (hashcmp(sha1, ce->oid.hash) || !S_ISREG(ce->ce_mode)) + if (oidcmp(oid, &ce->oid) || !S_ISREG(ce->ce_mode)) return 0; /* @@ -2842,7 +2842,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) return diff_populate_gitlink(s, size_only); if (!s->oid_valid || - reuse_worktree_file(s->path, s->oid.hash, 0)) { + reuse_worktree_file(s->path, &s->oid, 0)) { struct strbuf buf = STRBUF_INIT; struct stat st; int fd; @@ -2877,7 +2877,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) * point if the path requires us to run the content * conversion. */ - if (size_only && !would_convert_to_git(s->path)) + if (size_only && !would_convert_to_git(&the_index, s->path)) return 0; /* @@ -2904,7 +2904,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags) /* * Convert from working tree format to canonical git format */ - if (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) { + if (convert_to_git(&the_index, s->path, s->data, s->size, &buf, crlf_warn)) { size_t size = 0; munmap(s->data, s->size); s->should_munmap = 0; @@ -2951,8 +2951,7 @@ void diff_free_filespec_blob(struct diff_filespec *s) void diff_free_filespec_data(struct diff_filespec *s) { diff_free_filespec_blob(s); - free(s->cnt_data); - s->cnt_data = NULL; + FREE_AND_NULL(s->cnt_data); } static void prep_temp_blob(const char *path, struct diff_tempfile *temp, @@ -3008,7 +3007,7 @@ static struct diff_tempfile *prepare_temp_file(const char *name, if (!S_ISGITLINK(one->mode) && (!one->oid_valid || - reuse_worktree_file(name, one->oid.hash, 1))) { + reuse_worktree_file(name, &one->oid, 1))) { struct stat st; if (lstat(name, &st) < 0) { if (errno == ENOENT) @@ -3030,13 +3029,13 @@ static struct diff_tempfile *prepare_temp_file(const char *name, /* we can borrow from the file in the work tree */ temp->name = name; if (!one->oid_valid) - sha1_to_hex_r(temp->hex, null_sha1); + oid_to_hex_r(temp->hex, &null_oid); else oid_to_hex_r(temp->hex, &one->oid); /* Even though we may sometimes borrow the * contents from the work tree, we always want * one->mode. mode is trustworthy even when - * !(one->sha1_valid), as long as + * !(one->oid_valid), as long as * DIFF_FILE_VALID(one). */ xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode); @@ -3239,7 +3238,7 @@ static void run_diff_cmd(const char *pgm, fprintf(o->file, "* Unmerged path %s\n", name); } -static void diff_fill_sha1_info(struct diff_filespec *one) +static void diff_fill_oid_info(struct diff_filespec *one) { if (DIFF_FILE_VALID(one)) { if (!one->oid_valid) { @@ -3298,8 +3297,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) return; } - diff_fill_sha1_info(one); - diff_fill_sha1_info(two); + diff_fill_oid_info(one); + diff_fill_oid_info(two); if (!pgm && DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && @@ -3344,8 +3343,8 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, if (o->prefix_length) strip_prefix(o->prefix_length, &name, &other); - diff_fill_sha1_info(p->one); - diff_fill_sha1_info(p->two); + diff_fill_oid_info(p->one); + diff_fill_oid_info(p->two); builtin_diffstat(name, other, p->one, p->two, diffstat, o, p); } @@ -3368,8 +3367,8 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) if (o->prefix_length) strip_prefix(o->prefix_length, &name, &other); - diff_fill_sha1_info(p->one); - diff_fill_sha1_info(p->two); + diff_fill_oid_info(p->one); + diff_fill_oid_info(p->two); builtin_checkdiff(name, other, attr_path, p->one, p->two, o); } @@ -4071,9 +4070,7 @@ int diff_opt_parse(struct diff_options *options, DIFF_OPT_CLR(options, FUNCCONTEXT); else if ((argcount = parse_long_opt("output", av, &optarg))) { char *path = prefix_filename(prefix, optarg); - options->file = fopen(path, "w"); - if (!options->file) - die_errno("Could not open '%s'", path); + options->file = xfopen(path, "w"); options->close_file = 1; if (options->use_color != GIT_COLOR_ALWAYS) options->use_color = GIT_COLOR_NEVER; @@ -4584,7 +4581,7 @@ static void patch_id_add_mode(git_SHA_CTX *ctx, unsigned mode) } /* returns 0 upon success, and writes result into sha1 */ -static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only) +static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only) { struct diff_queue_struct *q = &diff_queued_diff; int i; @@ -4616,8 +4613,8 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1, if (DIFF_PAIR_UNMERGED(p)) continue; - diff_fill_sha1_info(p->one); - diff_fill_sha1_info(p->two); + diff_fill_oid_info(p->one); + diff_fill_oid_info(p->two); len1 = remove_space(p->one->path, strlen(p->one->path)); len2 = remove_space(p->two->path, strlen(p->two->path)); @@ -4656,9 +4653,9 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1, if (diff_filespec_is_binary(p->one) || diff_filespec_is_binary(p->two)) { git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid), - 40); + GIT_SHA1_HEXSZ); git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid), - 40); + GIT_SHA1_HEXSZ); continue; } @@ -4671,15 +4668,15 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1, p->one->path); } - git_SHA1_Final(sha1, &ctx); + git_SHA1_Final(oid->hash, &ctx); return 0; } -int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only) +int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only) { struct diff_queue_struct *q = &diff_queued_diff; int i; - int result = diff_get_patch_id(options, sha1, diff_header_only); + int result = diff_get_patch_id(options, oid, diff_header_only); for (i = 0; i < q->nr; i++) diff_free_filepair(q->queue[i]); @@ -4807,9 +4804,7 @@ void diff_flush(struct diff_options *options) */ if (options->close_file) fclose(options->file); - options->file = fopen("/dev/null", "w"); - if (!options->file) - die_errno("Could not open /dev/null"); + options->file = xfopen("/dev/null", "w"); options->close_file = 1; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -5081,8 +5076,8 @@ static int is_submodule_ignored(const char *path, struct diff_options *options) void diff_addremove(struct diff_options *options, int addremove, unsigned mode, - const unsigned char *sha1, - int sha1_valid, + const struct object_id *oid, + int oid_valid, const char *concatpath, unsigned dirty_submodule) { struct diff_filespec *one, *two; @@ -5114,9 +5109,9 @@ void diff_addremove(struct diff_options *options, two = alloc_filespec(concatpath); if (addremove != '+') - fill_filespec(one, sha1, sha1_valid, mode); + fill_filespec(one, oid, oid_valid, mode); if (addremove != '-') { - fill_filespec(two, sha1, sha1_valid, mode); + fill_filespec(two, oid, oid_valid, mode); two->dirty_submodule = dirty_submodule; } @@ -5127,9 +5122,9 @@ void diff_addremove(struct diff_options *options, void diff_change(struct diff_options *options, unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, - int old_sha1_valid, int new_sha1_valid, + const struct object_id *old_oid, + const struct object_id *new_oid, + int old_oid_valid, int new_oid_valid, const char *concatpath, unsigned old_dirty_submodule, unsigned new_dirty_submodule) { @@ -5142,8 +5137,8 @@ void diff_change(struct diff_options *options, if (DIFF_OPT_TST(options, REVERSE_DIFF)) { SWAP(old_mode, new_mode); - SWAP(old_sha1, new_sha1); - SWAP(old_sha1_valid, new_sha1_valid); + SWAP(old_oid, new_oid); + SWAP(old_oid_valid, new_oid_valid); SWAP(old_dirty_submodule, new_dirty_submodule); } @@ -5153,8 +5148,8 @@ void diff_change(struct diff_options *options, one = alloc_filespec(concatpath); two = alloc_filespec(concatpath); - fill_filespec(one, old_sha1, old_sha1_valid, old_mode); - fill_filespec(two, new_sha1, new_sha1_valid, new_mode); + fill_filespec(one, old_oid, old_oid_valid, old_mode); + fill_filespec(two, new_oid, new_oid_valid, new_mode); one->dirty_submodule = old_dirty_submodule; two->dirty_submodule = new_dirty_submodule; p = diff_queue(&diff_queued_diff, one, two); @@ -5270,6 +5265,29 @@ size_t fill_textconv(struct userdiff_driver *driver, return size; } +int textconv_object(const char *path, + unsigned mode, + const struct object_id *oid, + int oid_valid, + char **buf, + unsigned long *buf_size) +{ + struct diff_filespec *df; + struct userdiff_driver *textconv; + + df = alloc_filespec(path); + fill_filespec(df, oid, oid_valid, mode); + textconv = get_textconv(df); + if (!textconv) { + free_filespec(df); + return 0; + } + + *buf_size = fill_textconv(textconv, df, buf); + free_filespec(df); + return 1; +} + void setup_diff_pager(struct diff_options *opt) { /* @@ -23,16 +23,16 @@ typedef int (*pathchange_fn_t)(struct diff_options *options, typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, - int old_sha1_valid, int new_sha1_valid, + const struct object_id *old_oid, + const struct object_id *new_oid, + int old_oid_valid, int new_oid_valid, const char *fullpath, unsigned old_dirty_submodule, unsigned new_dirty_submodule); typedef void (*add_remove_fn_t)(struct diff_options *options, int addremove, unsigned mode, - const unsigned char *sha1, - int sha1_valid, + const struct object_id *oid, + int oid_valid, const char *fullpath, unsigned dirty_submodule); typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, @@ -210,13 +210,14 @@ const char *diff_line_prefix(struct diff_options *); extern const char mime_boundary_leader[]; 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 combine_diff_path *p, const struct object_id *oid, + const struct object_id **parents_oid, 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, - struct diff_options *opt); +extern int diff_tree_oid(const struct object_id *old_oid, + const struct object_id *new_oid, + const char *base, struct diff_options *opt); +extern int diff_root_tree_oid(const struct object_id *new_oid, const char *base, + struct diff_options *opt); struct combine_diff_path { struct combine_diff_path *next; @@ -236,7 +237,7 @@ struct combine_diff_path { extern void show_combined_diff(struct combine_diff_path *elem, int num_parent, int dense, struct rev_info *); -extern void diff_tree_combined(const unsigned char *sha1, const struct oid_array *parents, int dense, struct rev_info *rev); +extern void diff_tree_combined(const struct object_id *oid, const struct oid_array *parents, int dense, struct rev_info *rev); extern void diff_tree_combined_merge(const struct commit *commit, int dense, struct rev_info *rev); @@ -247,16 +248,15 @@ extern int diff_can_quit_early(struct diff_options *); extern void diff_addremove(struct diff_options *, int addremove, unsigned mode, - const unsigned char *sha1, - int sha1_valid, + const struct object_id *oid, + int oid_valid, const char *fullpath, unsigned dirty_submodule); extern void diff_change(struct diff_options *, unsigned mode1, unsigned mode2, - const unsigned char *sha1, - const unsigned char *sha2, - int sha1_valid, - int sha2_valid, + const struct object_id *old_oid, + const struct object_id *new_oid, + int old_oid_valid, int new_oid_valid, const char *fullpath, unsigned dirty_submodule1, unsigned dirty_submodule2); @@ -355,7 +355,7 @@ extern int run_diff_files(struct rev_info *revs, unsigned int option); extern int run_diff_index(struct rev_info *revs, int cached); extern int do_diff_cache(const struct object_id *, struct diff_options *); -extern int diff_flush_patch_id(struct diff_options *, unsigned char *, int); +extern int diff_flush_patch_id(struct diff_options *, struct object_id *, int); extern int diff_result_code(struct diff_options *, int); @@ -385,6 +385,13 @@ extern size_t fill_textconv(struct userdiff_driver *driver, */ extern struct userdiff_driver *get_textconv(struct diff_filespec *one); +/* + * Prepare diff_filespec and convert it using diff textconv API + * if the textconv driver exists. + * Return 1 if the conversion succeeds, 0 otherwise. + */ +extern int textconv_object(const char *path, unsigned mode, const struct object_id *oid, int oid_valid, char **buf, unsigned long *buf_size); + extern int parse_rename_score(const char **cp_p); extern long parse_algorithm_value(const char *value); diff --git a/diffcore-rename.c b/diffcore-rename.c index f7444c86bd..1e4678b7d7 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -60,7 +60,7 @@ static int add_rename_dst(struct diff_filespec *two) memmove(rename_dst + first + 1, rename_dst + first, (rename_dst_nr - first - 1) * sizeof(*rename_dst)); rename_dst[first].two = alloc_filespec(two->path); - fill_filespec(rename_dst[first].two, two->oid.hash, two->oid_valid, + fill_filespec(rename_dst[first].two, &two->oid, two->oid_valid, two->mode); rename_dst[first].pair = NULL; return 0; @@ -464,7 +464,7 @@ void diffcore_rename(struct diff_options *options) strcmp(options->single_follow, p->two->path)) continue; /* not interested */ else if (!DIFF_OPT_TST(options, RENAME_EMPTY) && - is_empty_blob_sha1(p->two->oid.hash)) + is_empty_blob_oid(&p->two->oid)) continue; else if (add_rename_dst(p->two) < 0) { warning("skipping rename detection, detected" @@ -474,7 +474,7 @@ void diffcore_rename(struct diff_options *options) } } else if (!DIFF_OPT_TST(options, RENAME_EMPTY) && - is_empty_blob_sha1(p->one->oid.hash)) + is_empty_blob_oid(&p->one->oid)) continue; else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) { /* @@ -667,11 +667,9 @@ void diffcore_rename(struct diff_options *options) for (i = 0; i < rename_dst_nr; i++) free_filespec(rename_dst[i].two); - free(rename_dst); - rename_dst = NULL; + FREE_AND_NULL(rename_dst); rename_dst_nr = rename_dst_alloc = 0; - free(rename_src); - rename_src = NULL; + FREE_AND_NULL(rename_src); rename_src_nr = rename_src_alloc = 0; return; } diff --git a/diffcore.h b/diffcore.h index 6230241354..a30da161da 100644 --- a/diffcore.h +++ b/diffcore.h @@ -52,7 +52,7 @@ struct diff_filespec { extern struct diff_filespec *alloc_filespec(const char *); extern void free_filespec(struct diff_filespec *); -extern void fill_filespec(struct diff_filespec *, const unsigned char *, +extern void fill_filespec(struct diff_filespec *, const struct object_id *, int, unsigned short); #define CHECK_SIZE_ONLY 1 @@ -7,7 +7,9 @@ * Copyright (C) Linus Torvalds, 2005-2006 * Junio Hamano, 2005-2006 */ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "config.h" #include "dir.h" #include "attr.h" #include "refs.h" @@ -45,9 +47,20 @@ struct cached_dir { }; static enum path_treatment read_directory_recursive(struct dir_struct *dir, - const char *path, int len, struct untracked_cache_dir *untracked, + struct index_state *istate, const char *path, int len, + struct untracked_cache_dir *untracked, int check_only, const struct pathspec *pathspec); -static int get_dtype(struct dirent *de, const char *path, int len); +static int get_dtype(struct dirent *de, struct index_state *istate, + const char *path, int len); + +int count_slashes(const char *s) +{ + int cnt = 0; + while (*s) + if (*s++ == '/') + cnt++; + return cnt; +} int fspathcmp(const char *a, const char *b) { @@ -174,7 +187,9 @@ char *common_prefix(const struct pathspec *pathspec) return len ? xmemdupz(pathspec->items[0].match, len) : NULL; } -int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec) +int fill_directory(struct dir_struct *dir, + struct index_state *istate, + const struct pathspec *pathspec) { const char *prefix; size_t prefix_len; @@ -187,7 +202,7 @@ int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec) prefix = prefix_len ? pathspec->items[0].match : ""; /* Read the directory and prune it */ - read_directory(dir, prefix, prefix_len, pathspec); + read_directory(dir, istate, prefix, prefix_len, pathspec); return prefix_len; } @@ -587,7 +602,8 @@ void add_exclude(const char *string, const char *base, x->el = el; } -static void *read_skip_worktree_file_from_index(const char *path, size_t *size, +static void *read_skip_worktree_file_from_index(const struct index_state *istate, + const char *path, size_t *size, struct sha1_stat *sha1_stat) { int pos, len; @@ -596,12 +612,12 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size, void *data; len = strlen(path); - pos = cache_name_pos(path, len); + pos = index_name_pos(istate, path, len); if (pos < 0) return NULL; - if (!ce_skip_worktree(active_cache[pos])) + if (!ce_skip_worktree(istate->cache[pos])) return NULL; - data = read_sha1_file(active_cache[pos]->oid.hash, &type, &sz); + data = read_sha1_file(istate->cache[pos]->oid.hash, &type, &sz); if (!data || type != OBJ_BLOB) { free(data); return NULL; @@ -609,7 +625,7 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size, *size = xsize_t(sz); if (sha1_stat) { memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat)); - hashcpy(sha1_stat->sha1, active_cache[pos]->oid.hash); + hashcpy(sha1_stat->sha1, istate->cache[pos]->oid.hash); } return data; } @@ -727,7 +743,7 @@ static void invalidate_directory(struct untracked_cache *uc, /* * Given a file with name "fname", read it (either from disk, or from - * the index if "check_index" is non-zero), parse it and store the + * an index if 'istate' is non-null), parse it and store the * exclude rules in "el". * * If "ss" is not NULL, compute SHA-1 of the exclude file and fill @@ -735,7 +751,8 @@ static void invalidate_directory(struct untracked_cache *uc, * ss_valid is non-zero, "ss" must contain good value as input. */ static int add_excludes(const char *fname, const char *base, int baselen, - struct exclude_list *el, int check_index, + struct exclude_list *el, + struct index_state *istate, struct sha1_stat *sha1_stat) { struct stat st; @@ -745,12 +762,12 @@ static int add_excludes(const char *fname, const char *base, int baselen, fd = open(fname, O_RDONLY); if (fd < 0 || fstat(fd, &st) < 0) { - if (errno != ENOENT) - warn_on_inaccessible(fname); - if (0 <= fd) + if (fd < 0) + warn_on_fopen_errors(fname); + else close(fd); - if (!check_index || - (buf = read_skip_worktree_file_from_index(fname, &size, sha1_stat)) == NULL) + if (!istate || + (buf = read_skip_worktree_file_from_index(istate, fname, &size, sha1_stat)) == NULL) return -1; if (size == 0) { free(buf); @@ -782,15 +799,15 @@ static int add_excludes(const char *fname, const char *base, int baselen, if (sha1_stat) { int pos; if (sha1_stat->valid && - !match_stat_data_racy(&the_index, &sha1_stat->stat, &st)) + !match_stat_data_racy(istate, &sha1_stat->stat, &st)) ; /* no content change, ss->sha1 still good */ - else if (check_index && - (pos = cache_name_pos(fname, strlen(fname))) >= 0 && - !ce_stage(active_cache[pos]) && - ce_uptodate(active_cache[pos]) && - !would_convert_to_git(fname)) + else if (istate && + (pos = index_name_pos(istate, fname, strlen(fname))) >= 0 && + !ce_stage(istate->cache[pos]) && + ce_uptodate(istate->cache[pos]) && + !would_convert_to_git(istate, fname)) hashcpy(sha1_stat->sha1, - active_cache[pos]->oid.hash); + istate->cache[pos]->oid.hash); else hash_sha1_file(buf, size, "blob", sha1_stat->sha1); fill_stat_data(&sha1_stat->stat, &st); @@ -821,9 +838,9 @@ static int add_excludes(const char *fname, const char *base, int baselen, int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, struct exclude_list *el, - int check_index) + struct index_state *istate) { - return add_excludes(fname, base, baselen, el, check_index, NULL); + return add_excludes(fname, base, baselen, el, istate, NULL); } struct exclude_list *add_exclude_list(struct dir_struct *dir, @@ -855,7 +872,7 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname, if (!dir->untracked) dir->unmanaged_exclude_files++; el = add_exclude_list(dir, EXC_FILE, fname); - if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0) + if (add_excludes(fname, "", 0, el, NULL, sha1_stat) < 0) die("cannot use %s as an exclude file", fname); } @@ -958,7 +975,8 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, int pathlen, const char *basename, int *dtype, - struct exclude_list *el) + struct exclude_list *el, + struct index_state *istate) { struct exclude *exc = NULL; /* undecided */ int i; @@ -973,7 +991,7 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, if (x->flags & EXC_FLAG_MUSTBEDIR) { if (*dtype == DT_UNKNOWN) - *dtype = get_dtype(NULL, pathname, pathlen); + *dtype = get_dtype(NULL, istate, pathname, pathlen); if (*dtype != DT_DIR) continue; } @@ -1006,16 +1024,18 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname, */ int is_excluded_from_list(const char *pathname, int pathlen, const char *basename, int *dtype, - struct exclude_list *el) + struct exclude_list *el, struct index_state *istate) { struct exclude *exclude; - exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el); + exclude = last_exclude_matching_from_list(pathname, pathlen, basename, + dtype, el, istate); if (exclude) return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1; return -1; /* undecided */ } static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir, + struct index_state *istate, const char *pathname, int pathlen, const char *basename, int *dtype_p) { @@ -1027,7 +1047,7 @@ static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir, for (j = group->nr - 1; j >= 0; j--) { exclude = last_exclude_matching_from_list( pathname, pathlen, basename, dtype_p, - &group->el[j]); + &group->el[j], istate); if (exclude) return exclude; } @@ -1039,7 +1059,9 @@ static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir, * Loads the per-directory exclude list for the substring of base * which has a char length of baselen. */ -static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) +static void prep_exclude(struct dir_struct *dir, + struct index_state *istate, + const char *base, int baselen) { struct exclude_list_group *group; struct exclude_list *el; @@ -1118,6 +1140,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) int dt = DT_DIR; dir->basebuf.buf[stk->baselen - 1] = 0; dir->exclude = last_exclude_matching_from_lists(dir, + istate, dir->basebuf.buf, stk->baselen - 1, dir->basebuf.buf + current, &dt); dir->basebuf.buf[stk->baselen - 1] = '/'; @@ -1159,7 +1182,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) strbuf_addbuf(&sb, &dir->basebuf); strbuf_addstr(&sb, dir->exclude_per_dir); el->src = strbuf_detach(&sb, NULL); - add_excludes(el->src, el->src, stk->baselen, el, 1, + add_excludes(el->src, el->src, stk->baselen, el, istate, untracked ? &sha1_stat : NULL); } /* @@ -1194,19 +1217,20 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) * undecided. */ struct exclude *last_exclude_matching(struct dir_struct *dir, - const char *pathname, - int *dtype_p) + struct index_state *istate, + const char *pathname, + int *dtype_p) { int pathlen = strlen(pathname); const char *basename = strrchr(pathname, '/'); basename = (basename) ? basename+1 : pathname; - prep_exclude(dir, pathname, basename-pathname); + prep_exclude(dir, istate, pathname, basename-pathname); if (dir->exclude) return dir->exclude; - return last_exclude_matching_from_lists(dir, pathname, pathlen, + return last_exclude_matching_from_lists(dir, istate, pathname, pathlen, basename, dtype_p); } @@ -1215,10 +1239,11 @@ struct exclude *last_exclude_matching(struct dir_struct *dir, * scans all exclude lists to determine whether pathname is excluded. * Returns 1 if true, otherwise 0. */ -int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p) +int is_excluded(struct dir_struct *dir, struct index_state *istate, + const char *pathname, int *dtype_p) { struct exclude *exclude = - last_exclude_matching(dir, pathname, dtype_p); + last_exclude_matching(dir, istate, pathname, dtype_p); if (exclude) return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1; return 0; @@ -1233,18 +1258,22 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len) return ent; } -static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len) +static struct dir_entry *dir_add_name(struct dir_struct *dir, + struct index_state *istate, + const char *pathname, int len) { - if (cache_file_exists(pathname, len, ignore_case)) + if (index_file_exists(istate, pathname, len, ignore_case)) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc); return dir->entries[dir->nr++] = dir_entry_new(pathname, len); } -struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len) +struct dir_entry *dir_add_ignored(struct dir_struct *dir, + struct index_state *istate, + const char *pathname, int len) { - if (!cache_name_is_other(pathname, len)) + if (!index_name_is_other(istate, pathname, len)) return NULL; ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc); @@ -1262,14 +1291,15 @@ enum exist_status { * the directory name; instead, use the case insensitive * directory hash. */ -static enum exist_status directory_exists_in_index_icase(const char *dirname, int len) +static enum exist_status directory_exists_in_index_icase(struct index_state *istate, + const char *dirname, int len) { struct cache_entry *ce; - if (cache_dir_exists(dirname, len)) + if (index_dir_exists(istate, dirname, len)) return index_directory; - ce = cache_file_exists(dirname, len, ignore_case); + ce = index_file_exists(istate, dirname, len, ignore_case); if (ce && S_ISGITLINK(ce->ce_mode)) return index_gitdir; @@ -1283,18 +1313,19 @@ static enum exist_status directory_exists_in_index_icase(const char *dirname, in * the files it contains) will sort with the '/' at the * end. */ -static enum exist_status directory_exists_in_index(const char *dirname, int len) +static enum exist_status directory_exists_in_index(struct index_state *istate, + const char *dirname, int len) { int pos; if (ignore_case) - return directory_exists_in_index_icase(dirname, len); + return directory_exists_in_index_icase(istate, dirname, len); - pos = cache_name_pos(dirname, len); + pos = index_name_pos(istate, dirname, len); if (pos < 0) pos = -pos-1; - while (pos < active_nr) { - const struct cache_entry *ce = active_cache[pos++]; + while (pos < istate->cache_nr) { + const struct cache_entry *ce = istate->cache[pos++]; unsigned char endchar; if (strncmp(ce->name, dirname, len)) @@ -1344,12 +1375,13 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len) * (c) otherwise, we recurse into it. */ static enum path_treatment treat_directory(struct dir_struct *dir, + struct index_state *istate, struct untracked_cache_dir *untracked, const char *dirname, int len, int baselen, int exclude, const struct pathspec *pathspec) { /* The "len-1" is to strip the final '/' */ - switch (directory_exists_in_index(dirname, len-1)) { + switch (directory_exists_in_index(istate, dirname, len-1)) { case index_directory: return path_recurse; @@ -1374,7 +1406,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir, untracked = lookup_untracked(dir->untracked, untracked, dirname + baselen, len - baselen); - return read_directory_recursive(dir, dirname, len, + return read_directory_recursive(dir, istate, dirname, len, untracked, 1, pathspec); } @@ -1455,12 +1487,13 @@ static int exclude_matches_pathspec(const char *path, int pathlen, return 0; } -static int get_index_dtype(const char *path, int len) +static int get_index_dtype(struct index_state *istate, + const char *path, int len) { int pos; const struct cache_entry *ce; - ce = cache_file_exists(path, len, 0); + ce = index_file_exists(istate, path, len, 0); if (ce) { if (!ce_uptodate(ce)) return DT_UNKNOWN; @@ -1474,12 +1507,12 @@ static int get_index_dtype(const char *path, int len) } /* Try to look it up as a directory */ - pos = cache_name_pos(path, len); + pos = index_name_pos(istate, path, len); if (pos >= 0) return DT_UNKNOWN; pos = -pos-1; - while (pos < active_nr) { - ce = active_cache[pos++]; + while (pos < istate->cache_nr) { + ce = istate->cache[pos++]; if (strncmp(ce->name, path, len)) break; if (ce->name[len] > '/') @@ -1493,14 +1526,15 @@ static int get_index_dtype(const char *path, int len) return DT_UNKNOWN; } -static int get_dtype(struct dirent *de, const char *path, int len) +static int get_dtype(struct dirent *de, struct index_state *istate, + const char *path, int len) { int dtype = de ? DTYPE(de) : DT_UNKNOWN; struct stat st; if (dtype != DT_UNKNOWN) return dtype; - dtype = get_index_dtype(path, len); + dtype = get_index_dtype(istate, path, len); if (dtype != DT_UNKNOWN) return dtype; if (lstat(path, &st)) @@ -1516,16 +1550,17 @@ static int get_dtype(struct dirent *de, const char *path, int len) static enum path_treatment treat_one_path(struct dir_struct *dir, struct untracked_cache_dir *untracked, + struct index_state *istate, struct strbuf *path, int baselen, const struct pathspec *pathspec, int dtype, struct dirent *de) { int exclude; - int has_path_in_index = !!cache_file_exists(path->buf, path->len, ignore_case); + int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case); if (dtype == DT_UNKNOWN) - dtype = get_dtype(de, path->buf, path->len); + dtype = get_dtype(de, istate, path->buf, path->len); /* Always exclude indexed files */ if (dtype != DT_DIR && has_path_in_index) @@ -1552,10 +1587,10 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, if ((dir->flags & DIR_COLLECT_KILLED_ONLY) && (dtype == DT_DIR) && !has_path_in_index && - (directory_exists_in_index(path->buf, path->len) == index_nonexistent)) + (directory_exists_in_index(istate, path->buf, path->len) == index_nonexistent)) return path_none; - exclude = is_excluded(dir, path->buf, &dtype); + exclude = is_excluded(dir, istate, path->buf, &dtype); /* * Excluded? If we don't explicitly want to show @@ -1569,7 +1604,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, return path_none; case DT_DIR: strbuf_addch(path, '/'); - return treat_directory(dir, untracked, path->buf, path->len, + return treat_directory(dir, istate, untracked, path->buf, path->len, baselen, exclude, pathspec); case DT_REG: case DT_LNK: @@ -1580,6 +1615,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir, static enum path_treatment treat_path_fast(struct dir_struct *dir, struct untracked_cache_dir *untracked, struct cached_dir *cdir, + struct index_state *istate, struct strbuf *path, int baselen, const struct pathspec *pathspec) @@ -1598,7 +1634,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, * to its bottom. Verify again the same set of directories * with check_only set. */ - return read_directory_recursive(dir, path->buf, path->len, + return read_directory_recursive(dir, istate, path->buf, path->len, cdir->ucd, 1, pathspec); /* * We get path_recurse in the first run when @@ -1612,6 +1648,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir, static enum path_treatment treat_path(struct dir_struct *dir, struct untracked_cache_dir *untracked, struct cached_dir *cdir, + struct index_state *istate, struct strbuf *path, int baselen, const struct pathspec *pathspec) @@ -1620,7 +1657,7 @@ static enum path_treatment treat_path(struct dir_struct *dir, struct dirent *de = cdir->de; if (!de) - return treat_path_fast(dir, untracked, cdir, path, + return treat_path_fast(dir, untracked, cdir, istate, path, baselen, pathspec); if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) return path_none; @@ -1630,7 +1667,7 @@ static enum path_treatment treat_path(struct dir_struct *dir, return path_none; dtype = DTYPE(de); - return treat_one_path(dir, untracked, path, baselen, pathspec, dtype, de); + return treat_one_path(dir, untracked, istate, path, baselen, pathspec, dtype, de); } static void add_untracked(struct untracked_cache_dir *dir, const char *name) @@ -1644,6 +1681,7 @@ static void add_untracked(struct untracked_cache_dir *dir, const char *name) static int valid_cached_dir(struct dir_struct *dir, struct untracked_cache_dir *untracked, + struct index_state *istate, struct strbuf *path, int check_only) { @@ -1658,7 +1696,7 @@ static int valid_cached_dir(struct dir_struct *dir, return 0; } if (!untracked->valid || - match_stat_data_racy(&the_index, &untracked->stat_data, &st)) { + match_stat_data_racy(istate, &untracked->stat_data, &st)) { if (untracked->valid) invalidate_directory(dir->untracked, untracked); fill_stat_data(&untracked->stat_data, &st); @@ -1679,10 +1717,10 @@ static int valid_cached_dir(struct dir_struct *dir, */ if (path->len && path->buf[path->len - 1] != '/') { strbuf_addch(path, '/'); - prep_exclude(dir, path->buf, path->len); + prep_exclude(dir, istate, path->buf, path->len); strbuf_setlen(path, path->len - 1); } else - prep_exclude(dir, path->buf, path->len); + prep_exclude(dir, istate, path->buf, path->len); /* hopefully prep_exclude() haven't invalidated this entry... */ return untracked->valid; @@ -1691,12 +1729,13 @@ static int valid_cached_dir(struct dir_struct *dir, static int open_cached_dir(struct cached_dir *cdir, struct dir_struct *dir, struct untracked_cache_dir *untracked, + struct index_state *istate, struct strbuf *path, int check_only) { memset(cdir, 0, sizeof(*cdir)); cdir->untracked = untracked; - if (valid_cached_dir(dir, untracked, path, check_only)) + if (valid_cached_dir(dir, untracked, istate, path, check_only)) return 0; cdir->fdir = opendir(path->len ? path->buf : "."); if (dir->untracked) @@ -1759,9 +1798,9 @@ static void close_cached_dir(struct cached_dir *cdir) * Returns the most significant path_treatment value encountered in the scan. */ static enum path_treatment read_directory_recursive(struct dir_struct *dir, - const char *base, int baselen, - struct untracked_cache_dir *untracked, int check_only, - const struct pathspec *pathspec) + struct index_state *istate, const char *base, int baselen, + struct untracked_cache_dir *untracked, int check_only, + const struct pathspec *pathspec) { struct cached_dir cdir; enum path_treatment state, subdir_state, dir_state = path_none; @@ -1769,7 +1808,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, strbuf_add(&path, base, baselen); - if (open_cached_dir(&cdir, dir, untracked, &path, check_only)) + if (open_cached_dir(&cdir, dir, untracked, istate, &path, check_only)) goto out; if (untracked) @@ -1777,20 +1816,23 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, while (!read_cached_dir(&cdir)) { /* check how the file or directory should be treated */ - state = treat_path(dir, untracked, &cdir, &path, + state = treat_path(dir, untracked, &cdir, istate, &path, baselen, pathspec); if (state > dir_state) dir_state = state; /* recurse into subdir if instructed by treat_path */ - if (state == path_recurse) { + if ((state == path_recurse) || + ((state == path_untracked) && + (dir->flags & DIR_SHOW_IGNORED_TOO) && + (get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR))) { struct untracked_cache_dir *ud; ud = lookup_untracked(dir->untracked, untracked, path.buf + baselen, path.len - baselen); subdir_state = - read_directory_recursive(dir, path.buf, + read_directory_recursive(dir, istate, path.buf, path.len, ud, check_only, pathspec); if (subdir_state > dir_state) @@ -1812,18 +1854,18 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, switch (state) { case path_excluded: if (dir->flags & DIR_SHOW_IGNORED) - dir_add_name(dir, path.buf, path.len); + dir_add_name(dir, istate, path.buf, path.len); else if ((dir->flags & DIR_SHOW_IGNORED_TOO) || ((dir->flags & DIR_COLLECT_IGNORED) && exclude_matches_pathspec(path.buf, path.len, pathspec))) - dir_add_ignored(dir, path.buf, path.len); + dir_add_ignored(dir, istate, path.buf, path.len); break; case path_untracked: if (dir->flags & DIR_SHOW_IGNORED) break; - dir_add_name(dir, path.buf, path.len); + dir_add_name(dir, istate, path.buf, path.len); if (cdir.fdir) add_untracked(untracked, path.buf + baselen); break; @@ -1839,7 +1881,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, return dir_state; } -static int cmp_name(const void *p1, const void *p2) +int cmp_dir_entry(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; @@ -1847,7 +1889,16 @@ static int cmp_name(const void *p1, const void *p2) return name_compare(e1->name, e1->len, e2->name, e2->len); } +/* check if *out lexically strictly contains *in */ +int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in) +{ + return (out->len < in->len) && + (out->name[out->len - 1] == '/') && + !memcmp(out->name, in->name, out->len); +} + static int treat_leading_path(struct dir_struct *dir, + struct index_state *istate, const char *path, int len, const struct pathspec *pathspec) { @@ -1875,7 +1926,7 @@ static int treat_leading_path(struct dir_struct *dir, break; if (simplify_away(sb.buf, sb.len, pathspec)) break; - if (treat_one_path(dir, NULL, &sb, baselen, pathspec, + if (treat_one_path(dir, NULL, istate, &sb, baselen, pathspec, DT_DIR, NULL) == path_none) break; /* do not recurse into it */ if (len <= baselen) { @@ -2043,8 +2094,8 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d return root; } -int read_directory(struct dir_struct *dir, const char *path, - int len, const struct pathspec *pathspec) +int read_directory(struct dir_struct *dir, struct index_state *istate, + const char *path, int len, const struct pathspec *pathspec) { struct untracked_cache_dir *untracked; @@ -2058,10 +2109,33 @@ int read_directory(struct dir_struct *dir, const char *path, * e.g. prep_exclude() */ dir->untracked = NULL; - if (!len || treat_leading_path(dir, path, len, pathspec)) - read_directory_recursive(dir, path, len, untracked, 0, pathspec); - QSORT(dir->entries, dir->nr, cmp_name); - QSORT(dir->ignored, dir->ignored_nr, cmp_name); + if (!len || treat_leading_path(dir, istate, path, len, pathspec)) + read_directory_recursive(dir, istate, path, len, untracked, 0, pathspec); + QSORT(dir->entries, dir->nr, cmp_dir_entry); + QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry); + + /* + * If DIR_SHOW_IGNORED_TOO is set, read_directory_recursive() will + * also pick up untracked contents of untracked dirs; by default + * we discard these, but given DIR_KEEP_UNTRACKED_CONTENTS we do not. + */ + if ((dir->flags & DIR_SHOW_IGNORED_TOO) && + !(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) { + int i, j; + + /* remove from dir->entries untracked contents of untracked dirs */ + for (i = j = 0; j < dir->nr; j++) { + if (i && + check_dir_entry_contains(dir->entries[i - 1], dir->entries[j])) { + FREE_AND_NULL(dir->entries[j]); + } else { + dir->entries[i++] = dir->entries[j]; + } + } + + dir->nr = i; + } + if (dir->untracked) { static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS); trace_printf_key(&trace_untracked_stats, @@ -2073,14 +2147,13 @@ int read_directory(struct dir_struct *dir, const char *path, dir->untracked->gitignore_invalidated, dir->untracked->dir_invalidated, dir->untracked->dir_opened); - if (dir->untracked == the_index.untracked && + if (dir->untracked == istate->untracked && (dir->untracked->dir_opened || dir->untracked->gitignore_invalidated || dir->untracked->dir_invalidated)) - the_index.cache_changed |= UNTRACKED_CHANGED; - if (dir->untracked != the_index.untracked) { - free(dir->untracked); - dir->untracked = NULL; + istate->cache_changed |= UNTRACKED_CHANGED; + if (dir->untracked != istate->untracked) { + FREE_AND_NULL(dir->untracked); } } return dir->nr; @@ -2272,7 +2345,7 @@ int remove_path(const char *name) { char *slash; - if (unlink(name) && errno != ENOENT && errno != ENOTDIR) + if (unlink(name) && !is_missing_file_error(errno)) return -1; slash = strrchr(name, '/'); @@ -2423,8 +2496,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra strbuf_addbuf(out, &untracked->ident); strbuf_add(out, ouc, ouc_size(len)); - free(ouc); - ouc = NULL; + FREE_AND_NULL(ouc); if (!untracked->root) { varint_len = encode_varint(0, varbuf); @@ -151,7 +151,8 @@ struct dir_struct { DIR_NO_GITLINKS = 1<<3, DIR_COLLECT_IGNORED = 1<<4, DIR_SHOW_IGNORED_TOO = 1<<5, - DIR_COLLECT_KILLED_ONLY = 1<<6 + DIR_COLLECT_KILLED_ONLY = 1<<6, + DIR_KEEP_UNTRACKED_CONTENTS = 1<<7 } flags; struct dir_entry **entries; struct dir_entry **ignored; @@ -196,6 +197,9 @@ struct dir_struct { unsigned unmanaged_exclude_files; }; +/*Count the number of slashes for string s*/ +extern int count_slashes(const char *s); + /* * The ordering of these constants is significant, with * higher-numbered match types signifying "closer" (i.e. more @@ -214,12 +218,20 @@ extern int match_pathspec(const struct pathspec *pathspec, extern int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix); extern int within_depth(const char *name, int namelen, int depth, int max_depth); -extern int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec); -extern int read_directory(struct dir_struct *, const char *path, int len, const struct pathspec *pathspec); - -extern int is_excluded_from_list(const char *pathname, int pathlen, const char *basename, - int *dtype, struct exclude_list *el); -struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len); +extern int fill_directory(struct dir_struct *dir, + struct index_state *istate, + const struct pathspec *pathspec); +extern int read_directory(struct dir_struct *, struct index_state *istate, + const char *path, int len, + const struct pathspec *pathspec); + +extern int is_excluded_from_list(const char *pathname, int pathlen, + const char *basename, int *dtype, + struct exclude_list *el, + struct index_state *istate); +struct dir_entry *dir_add_ignored(struct dir_struct *dir, + struct index_state *istate, + const char *pathname, int len); /* * these implement the matching logic for dir.c:excluded_from_list and @@ -232,14 +244,17 @@ extern int match_pathname(const char *, int, const char *, int, int, unsigned); extern struct exclude *last_exclude_matching(struct dir_struct *dir, + struct index_state *istate, const char *name, int *dtype); -extern int is_excluded(struct dir_struct *dir, const char *name, int *dtype); +extern int is_excluded(struct dir_struct *dir, + struct index_state *istate, + const char *name, int *dtype); extern struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type, const char *src); extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, - struct exclude_list *el, int check_index); + struct exclude_list *el, struct index_state *istate); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen); extern void add_exclude(const char *string, const char *base, @@ -326,6 +341,9 @@ static inline int dir_path_match(const struct dir_entry *ent, has_trailing_dir); } +int cmp_dir_entry(const void *p1, const void *p2); +int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in); + void untracked_cache_invalidate_path(struct index_state *, const char *); void untracked_cache_remove_from_index(struct index_state *, const char *); void untracked_cache_add_to_index(struct index_state *, const char *); @@ -208,7 +208,8 @@ static int write_entry(struct cache_entry *ce, sub = submodule_from_ce(ce); if (sub) return submodule_move_head(ce->name, - NULL, oid_to_hex(&ce->oid), SUBMODULE_MOVE_HEAD_FORCE); + NULL, oid_to_hex(&ce->oid), + state->force ? SUBMODULE_MOVE_HEAD_FORCE : 0); break; default: return error("unknown file mode for %s in index", path); @@ -282,12 +283,11 @@ int checkout_entry(struct cache_entry *ce, unlink_or_warn(ce->name); return submodule_move_head(ce->name, - NULL, oid_to_hex(&ce->oid), - SUBMODULE_MOVE_HEAD_FORCE); + NULL, oid_to_hex(&ce->oid), 0); } else return submodule_move_head(ce->name, "HEAD", oid_to_hex(&ce->oid), - SUBMODULE_MOVE_HEAD_FORCE); + state->force ? SUBMODULE_MOVE_HEAD_FORCE : 0); } if (!changed) diff --git a/environment.c b/environment.c index 560408953c..d40b21fb72 100644 --- a/environment.c +++ b/environment.c @@ -8,6 +8,7 @@ * are. */ #include "cache.h" +#include "config.h" #include "refs.h" #include "fmt-merge-msg.h" #include "commit.h" @@ -169,7 +170,7 @@ static void setup_git_env(void) git_dir = getenv(GIT_DIR_ENVIRONMENT); if (!git_dir) { if (!startup_info->have_repository) - die("BUG: setup_git_env called without repository"); + BUG("setup_git_env called without repository"); git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; } gitfile = read_gitfile(git_dir); diff --git a/fast-import.c b/fast-import.c index e69d219682..a959161b46 100644 --- a/fast-import.c +++ b/fast-import.c @@ -154,6 +154,7 @@ Format of STDIN stream: #include "builtin.h" #include "cache.h" +#include "config.h" #include "lockfile.h" #include "object.h" #include "blob.h" @@ -280,7 +281,7 @@ struct recent_command { }; /* Configured limits on output */ -static unsigned long max_depth = 10; +static unsigned long max_depth = 50; static off_t max_packsize; static int unpack_limit = 100; static int force_update; @@ -1063,8 +1064,7 @@ discard_pack: close(pack_data->pack_fd); unlink_or_warn(pack_data->pack_name); } - free(pack_data); - pack_data = NULL; + FREE_AND_NULL(pack_data); running = 0; /* We can't carry a delta across packfiles. */ @@ -1149,8 +1149,7 @@ static int store_object( /* We cannot carry a delta into the new pack. */ if (delta) { - free(delta); - delta = NULL; + FREE_AND_NULL(delta); git_deflate_init(&s, pack_compression_level); s.next_in = (void *)dat->buf; @@ -3285,9 +3284,7 @@ static void option_export_pack_edges(const char *edges) { if (pack_edges) fclose(pack_edges); - pack_edges = fopen(edges, "a"); - if (!pack_edges) - die_errno("Cannot open '%s'", edges); + pack_edges = xfopen(edges, "a"); } static int parse_one_option(const char *option) diff --git a/fetch-pack.c b/fetch-pack.c index 44fa047bf4..fbbc99c888 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "refs.h" #include "pkt-line.h" @@ -15,6 +16,7 @@ #include "version.h" #include "prio-queue.h" #include "sha1-array.h" +#include "oidset.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -592,13 +594,38 @@ static void mark_recent_complete_commits(struct fetch_pack_args *args, } } +static void add_refs_to_oidset(struct oidset *oids, struct ref *refs) +{ + for (; refs; refs = refs->next) + oidset_insert(oids, &refs->old_oid); +} + +static int tip_oids_contain(struct oidset *tip_oids, + struct ref *unmatched, struct ref *newlist, + const struct object_id *id) +{ + /* + * Note that this only looks at the ref lists the first time it's + * called. This works out in filter_refs() because even though it may + * add to "newlist" between calls, the additions will always be for + * oids that are already in the set. + */ + if (!tip_oids->map.tablesize) { + add_refs_to_oidset(tip_oids, unmatched); + add_refs_to_oidset(tip_oids, newlist); + } + return oidset_contains(tip_oids, id); +} + static void filter_refs(struct fetch_pack_args *args, struct ref **refs, struct ref **sought, int nr_sought) { struct ref *newlist = NULL; struct ref **newtail = &newlist; + struct ref *unmatched = NULL; struct ref *ref, *next; + struct oidset tip_oids = OIDSET_INIT; int i; i = 0; @@ -631,7 +658,8 @@ static void filter_refs(struct fetch_pack_args *args, ref->next = NULL; newtail = &ref->next; } else { - free(ref); + ref->next = unmatched; + unmatched = ref; } } @@ -649,7 +677,9 @@ static void filter_refs(struct fetch_pack_args *args, continue; if ((allow_unadvertised_object_request & - (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1))) { + (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1)) || + tip_oids_contain(&tip_oids, unmatched, newlist, + &ref->old_oid)) { ref->match_status = REF_MATCHED; *newtail = copy_ref(ref); newtail = &(*newtail)->next; @@ -657,6 +687,13 @@ static void filter_refs(struct fetch_pack_args *args, ref->match_status = REF_UNADVERTISED_NOT_ALLOWED; } } + + oidset_clear(&tip_oids); + for (ref = unmatched; ref; ref = next) { + next = ref->next; + free(ref); + } + *refs = newlist; } diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 709a5f6ce6..79d675b5b0 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -46,7 +46,6 @@ my ($diff_new_color) = my $normal_color = $repo->get_color("", "reset"); my $diff_algorithm = $repo->config('diff.algorithm'); -my $diff_indent_heuristic = $repo->config_bool('diff.indentheuristic'); my $diff_filter = $repo->config('interactive.difffilter'); my $use_readkey = 0; @@ -730,9 +729,6 @@ sub parse_diff { if (defined $diff_algorithm) { splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}"; } - if ($diff_indent_heuristic) { - splice @diff_cmd, 1, 0, "--indent-heuristic"; - } if (defined $patch_mode_revision) { push @diff_cmd, get_diff_reference($patch_mode_revision); } diff --git a/git-compat-util.h b/git-compat-util.h index ab7552a7ce..047172d173 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -450,7 +450,6 @@ extern void (*get_error_routine(void))(const char *err, va_list params); extern void set_warn_routine(void (*routine)(const char *warn, va_list params)); extern void (*get_warn_routine(void))(const char *warn, va_list params); extern void set_die_is_recursing_routine(int (*routine)(void)); -extern void set_error_handle(FILE *); extern int starts_with(const char *str, const char *prefix); @@ -694,10 +693,12 @@ char *gitstrdup(const char *s); #endif #ifdef FREAD_READS_DIRECTORIES -#ifdef fopen -#undef fopen -#endif -#define fopen(a,b) git_fopen(a,b) +# if !defined(SUPPRESS_FOPEN_REDEFINITION) +# ifdef fopen +# undef fopen +# endif +# define fopen(a,b) git_fopen(a,b) +# endif extern FILE *git_fopen(const char*, const char*); #endif @@ -805,6 +806,13 @@ extern int xmkstemp(char *template); extern int xmkstemp_mode(char *template, int mode); extern char *xgetcwd(void); extern FILE *fopen_for_writing(const char *path); +extern FILE *fopen_or_warn(const char *path, const char *mode); + +/* + * FREE_AND_NULL(ptr) is like free(ptr) followed by ptr = NULL. Note + * that ptr is used twice, so don't pass e.g. ptr++. + */ +#define FREE_AND_NULL(p) do { free(p); (p) = NULL; } while (0) #define ALLOC_ARRAY(x, alloc) (x) = xmalloc(st_mult(sizeof(*(x)), (alloc))) #define REALLOC_ARRAY(x, alloc) (x) = xrealloc((x), st_mult(sizeof(*(x)), (alloc))) @@ -1069,6 +1077,15 @@ static inline int regexec_buf(const regex_t *preg, const char *buf, size_t size, #define HAVE_VARIADIC_MACROS 1 #endif +#ifdef HAVE_VARIADIC_MACROS +__attribute__((format (printf, 3, 4))) NORETURN +void BUG_fl(const char *file, int line, const char *fmt, ...); +#define BUG(...) BUG_fl(__FILE__, __LINE__, __VA_ARGS__) +#else +__attribute__((format (printf, 1, 2))) NORETURN +void BUG(const char *fmt, ...); +#endif + /* * Preserves errno, prints a message, but gives no warning for ENOENT. * Returns 0 on success, which includes trying to unlink an object that does @@ -1102,8 +1119,8 @@ int remove_or_warn(unsigned int mode, const char *path); int access_or_warn(const char *path, int mode, unsigned flag); int access_or_die(const char *path, int mode, unsigned flag); -/* Warn on an inaccessible file that ought to be accessible */ -void warn_on_inaccessible(const char *path); +/* Warn on an inaccessible file if errno indicates this is an error */ +int warn_on_fopen_errors(const char *path); #ifdef GMTIME_UNRELIABLE_ERRORS struct tm *git_gmtime(const time_t *); @@ -1126,6 +1143,21 @@ struct tm *git_gmtime_r(const time_t *, struct tm *); #define getc_unlocked(fh) getc(fh) #endif +/* + * Our code often opens a path to an optional file, to work on its + * contents when we can successfully open it. We can ignore a failure + * to open if such an optional file does not exist, but we do want to + * report a failure in opening for other reasons (e.g. we got an I/O + * error, or the file is there, but we lack the permission to open). + * + * Call this function after seeing an error from open() or fopen() to + * see if the errno indicates a missing file that we can safely ignore. + */ +static inline int is_missing_file_error(int errno_) +{ + return (errno_ == ENOENT || errno_ == ENOTDIR); +} + extern int cmd_main(int, const char **); #endif diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 2b8cdba157..3a74602ef3 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -81,12 +81,13 @@ set_ident () { finish_ident COMMITTER } -USAGE="[--env-filter <command>] [--tree-filter <command>] - [--index-filter <command>] [--parent-filter <command>] - [--msg-filter <command>] [--commit-filter <command>] - [--tag-name-filter <command>] [--subdirectory-filter <directory>] - [--original <namespace>] [-d <directory>] [-f | --force] - [<rev-list options>...]" +USAGE="[--setup <command>] [--env-filter <command>] + [--tree-filter <command>] [--index-filter <command>] + [--parent-filter <command>] [--msg-filter <command>] + [--commit-filter <command>] [--tag-name-filter <command>] + [--subdirectory-filter <directory>] [--original <namespace>] + [-d <directory>] [-f | --force] + [--] [<rev-list options>...]" OPTIONS_SPEC= . git-sh-setup @@ -96,6 +97,7 @@ if [ "$(is_bare_repository)" = false ]; then fi tempdir=.git-rewrite +filter_setup= filter_env= filter_tree= filter_index= @@ -148,6 +150,9 @@ do -d) tempdir="$OPTARG" ;; + --setup) + filter_setup="$OPTARG" + ;; --env-filter) filter_env="$OPTARG" ;; @@ -239,7 +244,7 @@ git rev-parse --no-flags --revs-only --symbolic-full-name \ sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads test -s "$tempdir"/heads || - die "Which ref do you want to rewrite?" + die "You must specify a ref to rewrite." GIT_INDEX_FILE="$(pwd)/../index" export GIT_INDEX_FILE @@ -317,6 +322,9 @@ else need_index= fi +eval "$filter_setup" < /dev/null || + die "filter setup failed: $filter_setup" + while read commit parents; do git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1)) diff --git a/git-gui/.gitattributes b/git-gui/.gitattributes index 33d07c06bd..59cd41dbff 100644 --- a/git-gui/.gitattributes +++ b/git-gui/.gitattributes @@ -2,3 +2,4 @@ * encoding=US-ASCII git-gui.sh encoding=UTF-8 /po/*.po encoding=UTF-8 +/GIT-VERSION-GEN eol=lf diff --git a/git-send-email.perl b/git-send-email.perl index eea0a517f7..7fd5874436 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -25,8 +25,9 @@ use Getopt::Long; use Text::ParseWords; use Term::ANSIColor; use File::Temp qw/ tempdir tempfile /; -use File::Spec::Functions qw(catfile); +use File::Spec::Functions qw(catdir catfile); use Error qw(:try); +use Cwd qw(abs_path cwd); use Git; use Git::I18N; @@ -1353,10 +1354,12 @@ EOF die __("The required SMTP server is not properly defined.") } + require Net::SMTP; + my $use_net_smtp_ssl = version->parse($Net::SMTP::VERSION) < version->parse("2.34"); + $smtp_domain ||= maildomain(); + if ($smtp_encryption eq 'ssl') { $smtp_server_port ||= 465; # ssmtp - require Net::SMTP::SSL; - $smtp_domain ||= maildomain(); require IO::Socket::SSL; # Suppress "variable accessed once" warning. @@ -1368,34 +1371,48 @@ EOF # Net::SMTP::SSL->new() does not forward any SSL options IO::Socket::SSL::set_client_defaults( ssl_verify_params()); - $smtp ||= Net::SMTP::SSL->new($smtp_server, - Hello => $smtp_domain, - Port => $smtp_server_port, - Debug => $debug_net_smtp); + + if ($use_net_smtp_ssl) { + require Net::SMTP::SSL; + $smtp ||= Net::SMTP::SSL->new($smtp_server, + Hello => $smtp_domain, + Port => $smtp_server_port, + Debug => $debug_net_smtp); + } + else { + $smtp ||= Net::SMTP->new($smtp_server, + Hello => $smtp_domain, + Port => $smtp_server_port, + Debug => $debug_net_smtp, + SSL => 1); + } } else { - require Net::SMTP; - $smtp_domain ||= maildomain(); $smtp_server_port ||= 25; $smtp ||= Net::SMTP->new($smtp_server, Hello => $smtp_domain, Debug => $debug_net_smtp, Port => $smtp_server_port); if ($smtp_encryption eq 'tls' && $smtp) { - require Net::SMTP::SSL; - $smtp->command('STARTTLS'); - $smtp->response(); - if ($smtp->code == 220) { + if ($use_net_smtp_ssl) { + $smtp->command('STARTTLS'); + $smtp->response(); + if ($smtp->code != 220) { + die sprintf(__("Server does not support STARTTLS! %s"), $smtp->message); + } + require Net::SMTP::SSL; $smtp = Net::SMTP::SSL->start_SSL($smtp, ssl_verify_params()) - or die "STARTTLS failed! ".IO::Socket::SSL::errstr(); - $smtp_encryption = ''; - # Send EHLO again to receive fresh - # supported commands - $smtp->hello($smtp_domain); - } else { - die sprintf(__("Server does not support STARTTLS! %s"), $smtp->message); + or die sprintf(__("STARTTLS failed! %s"), IO::Socket::SSL::errstr()); + } + else { + $smtp->starttls(ssl_verify_params()) + or die sprintf(__("STARTTLS failed! %s"), IO::Socket::SSL::errstr()); } + $smtp_encryption = ''; + # Send EHLO again to receive fresh + # supported commands + $smtp->hello($smtp_domain); } } @@ -1737,6 +1754,25 @@ sub unique_email_list { sub validate_patch { my $fn = shift; + + if ($repo) { + my $validate_hook = catfile(catdir($repo->repo_path(), 'hooks'), + 'sendemail-validate'); + my $hook_error; + if (-x $validate_hook) { + my $target = abs_path($fn); + # The hook needs a correct cwd and GIT_DIR. + my $cwd_save = cwd(); + chdir($repo->wc_path() or $repo->repo_path()) + or die("chdir: $!"); + local $ENV{"GIT_DIR"} = $repo->repo_path(); + $hook_error = "rejected by sendemail-validate hook" + if system($validate_hook, $target); + chdir($cwd_save) or die("chdir: $!"); + } + return $hook_error if $hook_error; + } + open(my $fh, '<', $fn) or die sprintf(__("unable to open %s: %s\n"), $fn, $!); while (my $line = <$fh>) { diff --git a/git-stash.sh b/git-stash.sh index 2fb651b2b8..9b6c2da7b4 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -19,6 +19,7 @@ OPTIONS_SPEC= START_DIR=$(pwd) . git-sh-setup require_work_tree +prefix=$(git rev-parse --show-prefix) || exit 1 cd_to_toplevel TMP="$GIT_DIR/.git-stash.$$" @@ -273,6 +274,8 @@ push_stash () { shift done + eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")" + if test -n "$patch_mode" && test -n "$untracked" then die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")" @@ -481,7 +484,7 @@ parse_flags_and_rev() case $# in 0) - have_stash || die "$(gettext "No stash found.")" + have_stash || die "$(gettext "No stash entries found.")" set -- ${ref_stash}@{0} ;; 1) @@ -573,7 +576,7 @@ apply_stash () { GIT_INDEX_FILE="$TMPindex" git-read-tree "$u_tree" && GIT_INDEX_FILE="$TMPindex" git checkout-index --all && rm -f "$TMPindex" || - die "$(gettext "Could not restore untracked files from stash")" + die "$(gettext "Could not restore untracked files from stash entry")" fi eval " @@ -627,7 +630,7 @@ pop_stash() { drop_stash "$@" else status=$? - say "$(gettext "The stash is kept in case you need it again.")" + say "$(gettext "The stash entry is kept in case you need it again.")" exit $status fi } diff --git a/git-submodule.sh b/git-submodule.sh index c0d0e9a4c6..e131760eec 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -213,7 +213,8 @@ cmd_add() die "$(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")" fi - if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1 + if test -z "$force" && + ! git add --dry-run --ignore-missing --no-warn-embedded-repo "$sm_path" > /dev/null 2>&1 then eval_gettextln "The following path is ignored by one of your .gitignore files: \$sm_path @@ -267,7 +268,7 @@ or you are unsure what this means choose another name with the '--name' option." fi git config submodule."$sm_name".url "$realrepo" - git add $force "$sm_path" || + git add --no-warn-embedded-repo $force "$sm_path" || die "$(eval_gettext "Failed to add submodule '\$sm_path'")" git config -f .gitmodules submodule."$sm_name".path "$sm_path" && @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "exec_cmd.h" #include "help.h" #include "run-command.h" @@ -16,50 +17,8 @@ const char git_more_info_string[] = "to read about a specific subcommand or concept."); static int use_pager = -1; -static char *orig_cwd; -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 save_restore_env_balance; - -static void save_env_before_alias(void) -{ - int i; - - assert(save_restore_env_balance == 0); - save_restore_env_balance = 1; - orig_cwd = xgetcwd(); - for (i = 0; i < ARRAY_SIZE(env_names); i++) { - orig_env[i] = getenv(env_names[i]); - orig_env[i] = xstrdup_or_null(orig_env[i]); - } -} -static void restore_env(int external_alias) -{ - int i; - - assert(save_restore_env_balance == 1); - save_restore_env_balance = 0; - if (!external_alias && orig_cwd && chdir(orig_cwd)) - die_errno("could not move to %s", orig_cwd); - free(orig_cwd); - for (i = 0; i < ARRAY_SIZE(env_names); i++) { - if (external_alias && - !strcmp(env_names[i], GIT_PREFIX_ENVIRONMENT)) - continue; - if (orig_env[i]) { - setenv(env_names[i], orig_env[i], 1); - free(orig_env[i]); - } else { - unsetenv(env_names[i]); - } - } -} +static void list_builtins(void); static void commit_pager_choice(void) { switch (use_pager) { @@ -232,6 +191,9 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) } (*argv)++; (*argc)--; + } else if (!strcmp(cmd, "--list-builtins")) { + list_builtins(); + exit(0); } else { fprintf(stderr, "Unknown option: %s\n", cmd); usage(git_usage_string); @@ -250,19 +212,18 @@ static int handle_alias(int *argcp, const char ***argv) const char **new_argv; const char *alias_command; char *alias_string; - int unused_nongit; - - save_env_before_alias(); - setup_git_directory_gently(&unused_nongit); alias_command = (*argv)[0]; alias_string = alias_lookup(alias_command); if (alias_string) { if (alias_string[0] == '!') { struct child_process child = CHILD_PROCESS_INIT; + int nongit_ok; + + /* Aliases expect GIT_PREFIX, GIT_DIR etc to be set */ + setup_git_directory_gently(&nongit_ok); commit_pager_choice(); - restore_env(1); child.use_shell = 1; argv_array_push(&child.args, alias_string + 1); @@ -308,8 +269,6 @@ static int handle_alias(int *argcp, const char ***argv) ret = 1; } - restore_env(0); - errno = saved_errno; return ret; @@ -529,6 +488,13 @@ int is_builtin(const char *s) return !!get_builtin(s); } +static void list_builtins(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(commands); i++) + printf("%s\n", commands[i].cmd); +} + #ifdef STRIP_EXTENSION static void strip_extension(const char **argv) { diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7cf68f07b7..d8209c7a02 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -8085,7 +8085,7 @@ sub git_search_help { <p><strong>Pattern</strong> is by default a normal string that is matched precisely (but without regard to case, except in the case of pickaxe). However, when you check the <em>re</em> checkbox, the pattern entered is recognized as the POSIX extended -<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case +<a href="https://en.wikipedia.org/wiki/Regular_expression">regular expression</a> (also case insensitive).</p> <dl> <dt><b>commit</b></dt> diff --git a/gpg-interface.c b/gpg-interface.c index e44cc27da1..d936f3a32f 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "run-command.h" #include "strbuf.h" #include "gpg-interface.h" @@ -13,16 +14,11 @@ 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; + FREE_AND_NULL(sigc->payload); + FREE_AND_NULL(sigc->gpg_output); + FREE_AND_NULL(sigc->gpg_status); + FREE_AND_NULL(sigc->signer); + FREE_AND_NULL(sigc->key); } static struct { @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "commit.h" #include "color.h" #include "graph.h" @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "grep.h" #include "userdiff.h" #include "xdiff-interface.h" @@ -178,26 +179,38 @@ static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, st case GREP_PATTERN_TYPE_BRE: opt->fixed = 0; - opt->pcre = 0; - opt->regflags &= ~REG_EXTENDED; + opt->pcre1 = 0; + opt->pcre2 = 0; break; case GREP_PATTERN_TYPE_ERE: opt->fixed = 0; - opt->pcre = 0; + opt->pcre1 = 0; + opt->pcre2 = 0; opt->regflags |= REG_EXTENDED; break; case GREP_PATTERN_TYPE_FIXED: opt->fixed = 1; - opt->pcre = 0; - opt->regflags &= ~REG_EXTENDED; + opt->pcre1 = 0; + opt->pcre2 = 0; break; case GREP_PATTERN_TYPE_PCRE: opt->fixed = 0; - opt->pcre = 1; - opt->regflags &= ~REG_EXTENDED; +#ifdef USE_LIBPCRE2 + opt->pcre1 = 0; + opt->pcre2 = 1; +#else + /* + * It's important that pcre1 always be assigned to + * even when there's no USE_LIBPCRE* defined. We still + * call the PCRE stub function, it just dies with + * "cannot use Perl-compatible regexes[...]". + */ + opt->pcre1 = 1; + opt->pcre2 = 0; +#endif break; } } @@ -324,8 +337,32 @@ static NORETURN void compile_regexp_failed(const struct grep_pat *p, die("%s'%s': %s", where, p->pattern, error); } -#ifdef USE_LIBPCRE -static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) +static int is_fixed(const char *s, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (is_regex_special(s[i])) + return 0; + } + + return 1; +} + +static int has_null(const char *s, size_t len) +{ + /* + * regcomp cannot accept patterns with NULs so when using it + * we consider any pattern containing a NUL fixed. + */ + if (memchr(s, 0, len)) + return 1; + + return 0; +} + +#ifdef USE_LIBPCRE1 +static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt) { const char *error; int erroffset; @@ -333,23 +370,36 @@ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) if (opt->ignore_case) { if (has_non_ascii(p->pattern)) - p->pcre_tables = pcre_maketables(); + p->pcre1_tables = pcre_maketables(); options |= PCRE_CASELESS; } if (is_utf8_locale() && has_non_ascii(p->pattern)) options |= PCRE_UTF8; - p->pcre_regexp = pcre_compile(p->pattern, options, &error, &erroffset, - p->pcre_tables); - if (!p->pcre_regexp) + p->pcre1_regexp = pcre_compile(p->pattern, options, &error, &erroffset, + p->pcre1_tables); + if (!p->pcre1_regexp) compile_regexp_failed(p, error); - p->pcre_extra_info = pcre_study(p->pcre_regexp, 0, &error); - if (!p->pcre_extra_info && error) + p->pcre1_extra_info = pcre_study(p->pcre1_regexp, PCRE_STUDY_JIT_COMPILE, &error); + if (!p->pcre1_extra_info && error) die("%s", error); + +#ifdef GIT_PCRE1_USE_JIT + pcre_config(PCRE_CONFIG_JIT, &p->pcre1_jit_on); + if (p->pcre1_jit_on == 1) { + p->pcre1_jit_stack = pcre_jit_stack_alloc(1, 1024 * 1024); + if (!p->pcre1_jit_stack) + die("Couldn't allocate PCRE JIT stack"); + pcre_assign_jit_stack(p->pcre1_extra_info, NULL, p->pcre1_jit_stack); + } else if (p->pcre1_jit_on != 0) { + die("BUG: The pcre1_jit_on variable should be 0 or 1, not %d", + p->pcre1_jit_on); + } +#endif } -static int pcrematch(struct grep_pat *p, const char *line, const char *eol, +static int pcre1match(struct grep_pat *p, const char *line, const char *eol, regmatch_t *match, int eflags) { int ovector[30], ret, flags = 0; @@ -357,8 +407,19 @@ static int pcrematch(struct grep_pat *p, const char *line, const char *eol, if (eflags & REG_NOTBOL) flags |= PCRE_NOTBOL; - ret = pcre_exec(p->pcre_regexp, p->pcre_extra_info, line, eol - line, - 0, flags, ovector, ARRAY_SIZE(ovector)); +#ifdef GIT_PCRE1_USE_JIT + if (p->pcre1_jit_on) { + ret = pcre_jit_exec(p->pcre1_regexp, p->pcre1_extra_info, line, + eol - line, 0, flags, ovector, + ARRAY_SIZE(ovector), p->pcre1_jit_stack); + } else +#endif + { + ret = pcre_exec(p->pcre1_regexp, p->pcre1_extra_info, line, + eol - line, 0, flags, ovector, + ARRAY_SIZE(ovector)); + } + if (ret < 0 && ret != PCRE_ERROR_NOMATCH) die("pcre_exec failed with error code %d", ret); if (ret > 0) { @@ -370,55 +431,165 @@ static int pcrematch(struct grep_pat *p, const char *line, const char *eol, return ret; } -static void free_pcre_regexp(struct grep_pat *p) +static void free_pcre1_regexp(struct grep_pat *p) { - pcre_free(p->pcre_regexp); - pcre_free(p->pcre_extra_info); - pcre_free((void *)p->pcre_tables); + pcre_free(p->pcre1_regexp); +#ifdef GIT_PCRE1_USE_JIT + if (p->pcre1_jit_on) { + pcre_free_study(p->pcre1_extra_info); + pcre_jit_stack_free(p->pcre1_jit_stack); + } else +#endif + { + pcre_free(p->pcre1_extra_info); + } + pcre_free((void *)p->pcre1_tables); } -#else /* !USE_LIBPCRE */ -static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt) +#else /* !USE_LIBPCRE1 */ +static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt) { die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); } -static int pcrematch(struct grep_pat *p, const char *line, const char *eol, +static int pcre1match(struct grep_pat *p, const char *line, const char *eol, regmatch_t *match, int eflags) { return 1; } -static void free_pcre_regexp(struct grep_pat *p) +static void free_pcre1_regexp(struct grep_pat *p) { } -#endif /* !USE_LIBPCRE */ +#endif /* !USE_LIBPCRE1 */ -static int is_fixed(const char *s, size_t len) +#ifdef USE_LIBPCRE2 +static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) { - size_t i; + int error; + PCRE2_UCHAR errbuf[256]; + PCRE2_SIZE erroffset; + int options = PCRE2_MULTILINE; + const uint8_t *character_tables = NULL; + int jitret; - /* regcomp cannot accept patterns with NULs so we - * consider any pattern containing a NUL fixed. - */ - if (memchr(s, 0, len)) - return 1; + assert(opt->pcre2); - for (i = 0; i < len; i++) { - if (is_regex_special(s[i])) - return 0; + p->pcre2_compile_context = NULL; + + if (opt->ignore_case) { + if (has_non_ascii(p->pattern)) { + character_tables = pcre2_maketables(NULL); + p->pcre2_compile_context = pcre2_compile_context_create(NULL); + pcre2_set_character_tables(p->pcre2_compile_context, character_tables); + } + options |= PCRE2_CASELESS; + } + if (is_utf8_locale() && has_non_ascii(p->pattern)) + options |= PCRE2_UTF; + + p->pcre2_pattern = pcre2_compile((PCRE2_SPTR)p->pattern, + p->patternlen, options, &error, &erroffset, + p->pcre2_compile_context); + + if (p->pcre2_pattern) { + p->pcre2_match_data = pcre2_match_data_create_from_pattern(p->pcre2_pattern, NULL); + if (!p->pcre2_match_data) + die("Couldn't allocate PCRE2 match data"); + } else { + pcre2_get_error_message(error, errbuf, sizeof(errbuf)); + compile_regexp_failed(p, (const char *)&errbuf); + } + + pcre2_config(PCRE2_CONFIG_JIT, &p->pcre2_jit_on); + if (p->pcre2_jit_on == 1) { + jitret = pcre2_jit_compile(p->pcre2_pattern, PCRE2_JIT_COMPLETE); + if (jitret) + die("Couldn't JIT the PCRE2 pattern '%s', got '%d'\n", p->pattern, jitret); + p->pcre2_jit_stack = pcre2_jit_stack_create(1, 1024 * 1024, NULL); + if (!p->pcre2_jit_stack) + die("Couldn't allocate PCRE2 JIT stack"); + p->pcre2_match_context = pcre2_match_context_create(NULL); + if (!p->pcre2_match_context) + die("Couldn't allocate PCRE2 match context"); + pcre2_jit_stack_assign(p->pcre2_match_context, NULL, p->pcre2_jit_stack); + } else if (p->pcre2_jit_on != 0) { + die("BUG: The pcre2_jit_on variable should be 0 or 1, not %d", + p->pcre1_jit_on); } +} + +static int pcre2match(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ + int ret, flags = 0; + PCRE2_SIZE *ovector; + PCRE2_UCHAR errbuf[256]; + + if (eflags & REG_NOTBOL) + flags |= PCRE2_NOTBOL; + if (p->pcre2_jit_on) + ret = pcre2_jit_match(p->pcre2_pattern, (unsigned char *)line, + eol - line, 0, flags, p->pcre2_match_data, + NULL); + else + ret = pcre2_match(p->pcre2_pattern, (unsigned char *)line, + eol - line, 0, flags, p->pcre2_match_data, + NULL); + + if (ret < 0 && ret != PCRE2_ERROR_NOMATCH) { + pcre2_get_error_message(ret, errbuf, sizeof(errbuf)); + die("%s failed with error code %d: %s", + (p->pcre2_jit_on ? "pcre2_jit_match" : "pcre2_match"), ret, + errbuf); + } + if (ret > 0) { + ovector = pcre2_get_ovector_pointer(p->pcre2_match_data); + ret = 0; + match->rm_so = (int)ovector[0]; + match->rm_eo = (int)ovector[1]; + } + + return ret; +} + +static void free_pcre2_pattern(struct grep_pat *p) +{ + pcre2_compile_context_free(p->pcre2_compile_context); + pcre2_code_free(p->pcre2_pattern); + pcre2_match_data_free(p->pcre2_match_data); + pcre2_jit_stack_free(p->pcre2_jit_stack); + pcre2_match_context_free(p->pcre2_match_context); +} +#else /* !USE_LIBPCRE2 */ +static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) +{ + /* + * Unreachable until USE_LIBPCRE2 becomes synonymous with + * USE_LIBPCRE. See the sibling comment in + * grep_set_pattern_type_option(). + */ + die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); +} + +static int pcre2match(struct grep_pat *p, const char *line, const char *eol, + regmatch_t *match, int eflags) +{ return 1; } +static void free_pcre2_pattern(struct grep_pat *p) +{ +} +#endif /* !USE_LIBPCRE2 */ + static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt) { struct strbuf sb = STRBUF_INIT; int err; - int regflags; + int regflags = opt->regflags; basic_regex_quote_buf(&sb, p->pattern); - regflags = opt->regflags & ~REG_EXTENDED; if (opt->ignore_case) regflags |= REG_ICASE; err = regcomp(&p->regexp, sb.buf, regflags); @@ -455,7 +626,9 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) * simple string match using kws. p->fixed tells us if we * want to use kws. */ - if (opt->fixed || is_fixed(p->pattern, p->patternlen)) + if (opt->fixed || + has_null(p->pattern, p->patternlen) || + is_fixed(p->pattern, p->patternlen)) p->fixed = !icase || ascii_only; else p->fixed = 0; @@ -475,8 +648,13 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) return; } - if (opt->pcre) { - compile_pcre_regexp(p, opt); + if (opt->pcre2) { + compile_pcre2_pattern(p, opt); + return; + } + + if (opt->pcre1) { + compile_pcre1_regexp(p, opt); return; } @@ -832,8 +1010,10 @@ void free_grep_patterns(struct grep_opt *opt) case GREP_PATTERN_BODY: if (p->kws) kwsfree(p->kws); - else if (p->pcre_regexp) - free_pcre_regexp(p); + else if (p->pcre1_regexp) + free_pcre1_regexp(p); + else if (p->pcre2_pattern) + free_pcre2_pattern(p); else regfree(&p->regexp); free(p->pattern); @@ -912,8 +1092,10 @@ static int patmatch(struct grep_pat *p, char *line, char *eol, if (p->fixed) hit = !fixmatch(p, line, eol, match); - else if (p->pcre_regexp) - hit = !pcrematch(p, line, eol, match, eflags); + else if (p->pcre1_regexp) + hit = !pcre1match(p, line, eol, match, eflags); + else if (p->pcre2_pattern) + hit = !pcre2match(p, line, eol, match, eflags); else hit = !regexec_buf(&p->regexp, line, eol - line, 1, match, eflags); @@ -1403,11 +1585,11 @@ static int fill_textconv_grep(struct userdiff_driver *driver, */ df = alloc_filespec(gs->path); switch (gs->type) { - case GREP_SOURCE_SHA1: + case GREP_SOURCE_OID: fill_filespec(df, gs->identifier, 1, 0100644); break; case GREP_SOURCE_FILE: - fill_filespec(df, null_sha1, 0, 0100644); + fill_filespec(df, &null_oid, 0, 0100644); break; default: die("BUG: attempt to textconv something without a path?"); @@ -1747,9 +1929,8 @@ void grep_source_init(struct grep_source *gs, enum grep_source_type type, * If the identifier is non-NULL (in the submodule case) it * will be a SHA1 that needs to be copied. */ - case GREP_SOURCE_SHA1: - gs->identifier = xmalloc(20); - hashcpy(gs->identifier, identifier); + case GREP_SOURCE_OID: + gs->identifier = oiddup(identifier); break; case GREP_SOURCE_BUF: gs->identifier = NULL; @@ -1759,12 +1940,9 @@ void grep_source_init(struct grep_source *gs, enum grep_source_type type, void grep_source_clear(struct grep_source *gs) { - free(gs->name); - gs->name = NULL; - free(gs->path); - gs->path = NULL; - free(gs->identifier); - gs->identifier = NULL; + FREE_AND_NULL(gs->name); + FREE_AND_NULL(gs->path); + FREE_AND_NULL(gs->identifier); grep_source_clear_data(gs); } @@ -1772,10 +1950,9 @@ void grep_source_clear_data(struct grep_source *gs) { switch (gs->type) { case GREP_SOURCE_FILE: - case GREP_SOURCE_SHA1: + case GREP_SOURCE_OID: case GREP_SOURCE_SUBMODULE: - free(gs->buf); - gs->buf = NULL; + FREE_AND_NULL(gs->buf); gs->size = 0; break; case GREP_SOURCE_BUF: @@ -1784,7 +1961,7 @@ void grep_source_clear_data(struct grep_source *gs) } } -static int grep_source_load_sha1(struct grep_source *gs) +static int grep_source_load_oid(struct grep_source *gs) { enum object_type type; @@ -1795,7 +1972,7 @@ static int grep_source_load_sha1(struct grep_source *gs) if (!gs->buf) return error(_("'%s': unable to read %s"), gs->name, - sha1_to_hex(gs->identifier)); + oid_to_hex(gs->identifier)); return 0; } @@ -1841,8 +2018,8 @@ static int grep_source_load(struct grep_source *gs) switch (gs->type) { case GREP_SOURCE_FILE: return grep_source_load_file(gs); - case GREP_SOURCE_SHA1: - return grep_source_load_sha1(gs); + case GREP_SOURCE_OID: + return grep_source_load_oid(gs); case GREP_SOURCE_BUF: return gs->buf ? 0 : -1; case GREP_SOURCE_SUBMODULE: @@ -1,11 +1,35 @@ #ifndef GREP_H #define GREP_H #include "color.h" -#ifdef USE_LIBPCRE +#ifdef USE_LIBPCRE1 #include <pcre.h> +#ifdef PCRE_CONFIG_JIT +#if PCRE_MAJOR >= 8 && PCRE_MINOR >= 32 +#ifndef NO_LIBPCRE1_JIT +#define GIT_PCRE1_USE_JIT +#endif +#endif +#endif +#ifndef PCRE_STUDY_JIT_COMPILE +#define PCRE_STUDY_JIT_COMPILE 0 +#endif +#if PCRE_MAJOR <= 8 && PCRE_MINOR < 20 +typedef int pcre_jit_stack; +#endif #else typedef int pcre; typedef int pcre_extra; +typedef int pcre_jit_stack; +#endif +#ifdef USE_LIBPCRE2 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include <pcre2.h> +#else +typedef int pcre2_code; +typedef int pcre2_match_data; +typedef int pcre2_compile_context; +typedef int pcre2_match_context; +typedef int pcre2_jit_stack; #endif #include "kwset.h" #include "thread-utils.h" @@ -46,9 +70,17 @@ struct grep_pat { size_t patternlen; enum grep_header_field field; regex_t regexp; - pcre *pcre_regexp; - pcre_extra *pcre_extra_info; - const unsigned char *pcre_tables; + pcre *pcre1_regexp; + pcre_extra *pcre1_extra_info; + pcre_jit_stack *pcre1_jit_stack; + const unsigned char *pcre1_tables; + int pcre1_jit_on; + pcre2_code *pcre2_pattern; + pcre2_match_data *pcre2_match_data; + pcre2_compile_context *pcre2_compile_context; + pcre2_match_context *pcre2_match_context; + pcre2_jit_stack *pcre2_jit_stack; + uint32_t pcre2_jit_on; kwset_t kws; unsigned fixed:1; unsigned ignore_case:1; @@ -111,7 +143,8 @@ struct grep_opt { int allow_textconv; int extended; int use_reflog_filter; - int pcre; + int pcre1; + int pcre2; int relative; int pathname; int null_following_name; @@ -158,7 +191,7 @@ struct grep_source { char *name; enum grep_source_type { - GREP_SOURCE_SHA1, + GREP_SOURCE_OID, GREP_SOURCE_FILE, GREP_SOURCE_BUF, GREP_SOURCE_SUBMODULE, @@ -1,6 +1,8 @@ #include "cache.h" +#include "config.h" #include "builtin.h" #include "exec_cmd.h" +#include "run-command.h" #include "levenshtein.h" #include "help.h" #include "common-cmds.h" @@ -8,6 +10,7 @@ #include "column.h" #include "version.h" #include "refs.h" +#include "parse-options.h" void add_cmdname(struct cmdnames *cmds, const char *name, int len) { @@ -96,48 +99,6 @@ static void pretty_print_cmdnames(struct cmdnames *cmds, unsigned int colopts) string_list_clear(&list, 0); } -static int is_executable(const char *name) -{ - struct stat st; - - if (stat(name, &st) || /* stat, not lstat */ - !S_ISREG(st.st_mode)) - return 0; - -#if defined(GIT_WINDOWS_NATIVE) - /* - * On Windows there is no executable bit. The file extension - * indicates whether it can be run as an executable, and Git - * has special-handling to detect scripts and launch them - * through the indicated script interpreter. We test for the - * file extension first because virus scanners may make - * it quite expensive to open many files. - */ - if (ends_with(name, ".exe")) - return S_IXUSR; - -{ - /* - * Now that we know it does not have an executable extension, - * peek into the file instead. - */ - char buf[3] = { 0 }; - int n; - int fd = open(name, O_RDONLY); - st.st_mode &= ~S_IXUSR; - if (fd >= 0) { - n = read(fd, buf, 2); - if (n == 2) - /* look for a she-bang */ - if (!strcmp(buf, "#!")) - st.st_mode |= S_IXUSR; - close(fd); - } -} -#endif - return st.st_mode & S_IXUSR; -} - static void list_commands_in_dir(struct cmdnames *cmds, const char *path, const char *prefix) @@ -308,9 +269,8 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old) for (i = 0; i < old->cnt; i++) cmds->names[cmds->cnt++] = old->names[i]; - free(old->names); + FREE_AND_NULL(old->names); old->cnt = 0; - old->names = NULL; } /* An empirically derived magic number */ @@ -330,7 +290,7 @@ const char *help_unknown_cmd(const char *cmd) memset(&other_cmds, 0, sizeof(other_cmds)); memset(&aliases, 0, sizeof(aliases)); - git_config(git_unknown_cmd_config, NULL); + read_early_config(git_unknown_cmd_config, NULL); load_command_list("git-", &main_cmds, &other_cmds); @@ -411,8 +371,8 @@ const char *help_unknown_cmd(const char *cmd) if (SIMILAR_ENOUGH(best_similarity)) { fprintf_ln(stderr, - Q_("\nDid you mean this?", - "\nDid you mean one of these?", + Q_("\nThe most similar command is", + "\nThe most similar commands are", n)); for (i = 0; i < n; i++) @@ -424,16 +384,30 @@ const char *help_unknown_cmd(const char *cmd) int cmd_version(int argc, const char **argv, const char *prefix) { + int build_options = 0; + const char * const usage[] = { + N_("git version [<options>]"), + NULL + }; + struct option options[] = { + OPT_BOOL(0, "build-options", &build_options, + "also print build options"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + /* * The format of this string should be kept stable for compatibility * with external projects that rely on the output of "git version". + * + * Always show the version, even if other options are given. */ printf("git version %s\n", git_version_string); - while (*++argv) { - if (!strcmp(*argv, "--build-options")) { - printf("sizeof-long: %d\n", (int)sizeof(long)); - /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ - } + + if (build_options) { + printf("sizeof-long: %d\n", (int)sizeof(long)); + /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ } return 0; } diff --git a/http-backend.c b/http-backend.c index ba5ff1aa29..519025d2c3 100644 --- a/http-backend.c +++ b/http-backend.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "refs.h" #include "pkt-line.h" #include "object.h" diff --git a/http-fetch.c b/http-fetch.c index 3b556d6619..8af380050c 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "exec_cmd.h" #include "http.h" #include "walker.h" diff --git a/http-push.c b/http-push.c index 67c4d4b472..c91f40a610 100644 --- a/http-push.c +++ b/http-push.c @@ -291,8 +291,7 @@ static void start_mkcol(struct transfer_request *request) request->state = RUN_MKCOL; } else { request->state = ABORTED; - free(request->url); - request->url = NULL; + FREE_AND_NULL(request->url); } } #endif @@ -409,8 +408,7 @@ static void start_put(struct transfer_request *request) request->state = RUN_PUT; } else { request->state = ABORTED; - free(request->url); - request->url = NULL; + FREE_AND_NULL(request->url); } } @@ -432,8 +430,7 @@ static void start_move(struct transfer_request *request) request->state = RUN_MOVE; } else { request->state = ABORTED; - free(request->url); - request->url = NULL; + FREE_AND_NULL(request->url); } } @@ -526,8 +523,7 @@ static void finish_request(struct transfer_request *request) /* URL is reused for MOVE after PUT */ if (request->state != RUN_PUT) { - free(request->url); - request->url = NULL; + FREE_AND_NULL(request->url); } if (request->state == RUN_MKCOL) { @@ -803,8 +799,7 @@ xml_start_tag(void *userData, const char *name, const char **atts) } xsnprintf(ctx->name + old_namelen, ctx->len - old_namelen, ".%s", c); - free(ctx->cdata); - ctx->cdata = NULL; + FREE_AND_NULL(ctx->cdata); ctx->userFunc(ctx, 0); } @@ -932,8 +927,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) free(lock->token); free(lock->owner); free(url); - free(lock); - lock = NULL; + FREE_AND_NULL(lock); } else { lock->url = url; lock->start_time = time(NULL); @@ -1105,8 +1099,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->dentry_flags |= IS_DIR; } } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - free(ls->dentry_name); - ls->dentry_name = NULL; + FREE_AND_NULL(ls->dentry_name); ls->dentry_flags = 0; } } @@ -1547,8 +1540,7 @@ static void fetch_symref(const char *path, char **symref, struct object_id *oid) curl_errorstr); free(url); - free(*symref); - *symref = NULL; + FREE_AND_NULL(*symref); oidclr(oid); if (buffer.len == 0) @@ -1,5 +1,6 @@ #include "git-compat-util.h" #include "http.h" +#include "config.h" #include "pack.h" #include "sideband.h" #include "run-command.h" @@ -1026,8 +1027,7 @@ void http_cleanup(void) if (proxy_auth.password) { memset(proxy_auth.password, 0, strlen(proxy_auth.password)); - free(proxy_auth.password); - proxy_auth.password = NULL; + FREE_AND_NULL(proxy_auth.password); } free((void *)curl_proxyuserpwd); @@ -1038,13 +1038,11 @@ void http_cleanup(void) if (cert_auth.password != NULL) { memset(cert_auth.password, 0, strlen(cert_auth.password)); - free(cert_auth.password); - cert_auth.password = NULL; + FREE_AND_NULL(cert_auth.password); } ssl_cert_password_required = 0; - free(cached_accept_language); - cached_accept_language = NULL; + FREE_AND_NULL(cached_accept_language); } struct active_request_slot *get_active_slot(void) @@ -1896,8 +1894,7 @@ static char *fetch_pack_index(unsigned char *sha1, const char *base_url) if (http_get_file(url, tmp, NULL) != HTTP_OK) { error("Unable to get pack index %s", url); - free(tmp); - tmp = NULL; + FREE_AND_NULL(tmp); } free(url); @@ -2328,8 +2325,7 @@ void release_http_object_request(struct http_object_request *freq) freq->localfile = -1; } if (freq->url != NULL) { - free(freq->url); - freq->url = NULL; + FREE_AND_NULL(freq->url); } if (freq->slot != NULL) { freq->slot->callback_func = NULL; @@ -6,6 +6,7 @@ * Copyright (C) 2005 Linus Torvalds */ #include "cache.h" +#include "config.h" static struct strbuf git_default_name = STRBUF_INIT; static struct strbuf git_default_email = STRBUF_INIT; @@ -72,12 +73,10 @@ static int add_mailname_host(struct strbuf *buf) FILE *mailname; struct strbuf mailnamebuf = STRBUF_INIT; - mailname = fopen("/etc/mailname", "r"); - if (!mailname) { - if (errno != ENOENT) - warning_errno("cannot open /etc/mailname"); + mailname = fopen_or_warn("/etc/mailname", "r"); + if (!mailname) return -1; - } + if (strbuf_getline(&mailnamebuf, mailname) == EOF) { if (ferror(mailname)) warning_errno("cannot read /etc/mailname"); diff --git a/imap-send.c b/imap-send.c index 857591660f..351e84aea1 100644 --- a/imap-send.c +++ b/imap-send.c @@ -23,6 +23,7 @@ */ #include "cache.h" +#include "config.h" #include "credential.h" #include "exec_cmd.h" #include "run-command.h" @@ -776,8 +777,7 @@ static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd) offsetof(struct imap_cmd, next)); if (cmdp->cb.data) { n = socket_write(&imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen); - free(cmdp->cb.data); - cmdp->cb.data = NULL; + FREE_AND_NULL(cmdp->cb.data); if (n != (int)cmdp->cb.dlen) return RESP_BAD; } else if (cmdp->cb.cont) { diff --git a/line-log.c b/line-log.c index a23b910471..ab0709f9ae 100644 --- a/line-log.c +++ b/line-log.c @@ -34,9 +34,8 @@ void range_set_init(struct range_set *rs, size_t prealloc) void range_set_release(struct range_set *rs) { - free(rs->ranges); + FREE_AND_NULL(rs->ranges); rs->alloc = rs->nr = 0; - rs->ranges = NULL; } /* dst must be uninitialized! */ @@ -500,12 +499,12 @@ static struct commit *check_single_commit(struct rev_info *revs) static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec) { unsigned mode; - unsigned char sha1[20]; + struct object_id oid; if (get_tree_entry(commit->object.oid.hash, spec->path, - sha1, &mode)) + oid.hash, &mode)) die("There is no path %s in the commit", spec->path); - fill_filespec(spec, sha1, 1, mode); + fill_filespec(spec, &oid, 1, mode); return; } @@ -610,8 +609,7 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args) line_log_data_insert(&ranges, full_name, begin, end); free_filespec(spec); - free(ends); - ends = NULL; + FREE_AND_NULL(ends); } for (p = ranges; p; p = p->next) @@ -819,8 +817,8 @@ static void queue_diffs(struct line_log_data *range, assert(commit); DIFF_QUEUE_CLEAR(&diff_queued_diff); - diff_tree_sha1(parent ? parent->tree->object.oid.hash : NULL, - commit->tree->object.oid.hash, "", opt); + diff_tree_oid(parent ? &parent->tree->object.oid : NULL, + &commit->tree->object.oid, "", opt); if (opt->detect_rename) { filter_diffs_for_paths(range, 1); if (diff_might_be_rename()) @@ -1125,6 +1123,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c changed = process_all_files(&parent_range, rev, &queue, range); if (parent) add_line_range(rev, parent, parent_range); + free_line_log_data(parent_range); return changed; } diff --git a/ll-merge.c b/ll-merge.c index ac0d4a5d78..9fb855a900 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -5,6 +5,7 @@ */ #include "cache.h" +#include "config.h" #include "attr.h" #include "xdiff-interface.h" #include "run-command.h" @@ -209,8 +210,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, result->size = st.st_size; result->ptr = xmallocz(result->size); if (read_in_full(fd, result->ptr, result->size) != result->size) { - free(result->ptr); - result->ptr = NULL; + FREE_AND_NULL(result->ptr); result->size = 0; } close_bad: @@ -339,7 +339,7 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr static void normalize_file(mmfile_t *mm, const char *path) { struct strbuf strbuf = STRBUF_INIT; - if (renormalize_buffer(path, mm->ptr, mm->size, &strbuf)) { + if (renormalize_buffer(&the_index, path, mm->ptr, mm->size, &strbuf)) { free(mm->ptr); mm->size = strbuf.len; mm->ptr = strbuf_detach(&strbuf, NULL); diff --git a/log-tree.c b/log-tree.c index a4ec11c2bf..410ab4f02d 100644 --- a/log-tree.c +++ b/log-tree.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "diff.h" #include "commit.h" #include "tag.h" @@ -655,7 +656,7 @@ void show_log(struct rev_info *opt) struct strbuf notebuf = STRBUF_INIT; raw = (opt->commit_format == CMIT_FMT_USERFORMAT); - format_display_notes(commit->object.oid.hash, ¬ebuf, + format_display_notes(&commit->object.oid, ¬ebuf, get_log_output_encoding(), raw); ctx.notes_message = notebuf.len ? strbuf_detach(¬ebuf, NULL) @@ -803,7 +804,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log parents = get_saved_parents(opt, commit); if (!parents) { if (opt->show_root_diff) { - diff_root_tree_sha1(oid->hash, "", &opt->diffopt); + diff_root_tree_oid(oid, "", &opt->diffopt); log_tree_diff_flush(opt); } return !opt->loginfo; @@ -822,8 +823,8 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log * we merged _in_. */ parse_commit_or_die(parents->item); - diff_tree_sha1(parents->item->tree->object.oid.hash, - oid->hash, "", &opt->diffopt); + diff_tree_oid(&parents->item->tree->object.oid, + oid, "", &opt->diffopt); log_tree_diff_flush(opt); return !opt->loginfo; } @@ -837,8 +838,8 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log struct commit *parent = parents->item; parse_commit_or_die(parent); - diff_tree_sha1(parent->tree->object.oid.hash, - oid->hash, "", &opt->diffopt); + diff_tree_oid(&parent->tree->object.oid, + oid, "", &opt->diffopt); log_tree_diff_flush(opt); showed_log |= !opt->loginfo; diff --git a/mailinfo.c b/mailinfo.c index 68037758f2..1652ec11f5 100644 --- a/mailinfo.c +++ b/mailinfo.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "utf8.h" #include "strbuf.h" #include "mailinfo.h" @@ -882,7 +883,10 @@ static int read_one_header_line(struct strbuf *line, FILE *in) for (;;) { int peek; - peek = fgetc(in); ungetc(peek, in); + peek = fgetc(in); + if (peek == EOF) + break; + ungetc(peek, in); if (peek != ' ' && peek != '\t') break; if (strbuf_getline_lf(&continuation, in)) @@ -916,8 +920,7 @@ again: /* we hit an end boundary */ /* pop the current boundary off the stack */ strbuf_release(*(mi->content_top)); - free(*(mi->content_top)); - *(mi->content_top) = NULL; + FREE_AND_NULL(*(mi->content_top)); /* technically won't happen as is_multipart_boundary() will fail first. But just in case.. @@ -1099,6 +1102,10 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch) do { peek = fgetc(mi->input); + if (peek == EOF) { + fclose(cmitmsg); + return error("empty patch: '%s'", patch); + } } while (isspace(peek)); ungetc(peek, mi->input); diff --git a/merge-recursive.c b/merge-recursive.c index ae5238d82c..59e5ee41a8 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -4,6 +4,7 @@ * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 */ #include "cache.h" +#include "config.h" #include "advice.h" #include "lockfile.h" #include "cache-tree.h" @@ -528,7 +529,7 @@ static struct string_list *get_renames(struct merge_options *o, opts.show_rename_progress = o->show_rename_progress; opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_setup_done(&opts); - diff_tree_sha1(o_tree->object.oid.hash, tree->object.oid.hash, "", &opts); + diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts); diffcore_std(&opts); if (opts.needed_rename_limit > o->needed_rename_limit) o->needed_rename_limit = opts.needed_rename_limit; @@ -1639,8 +1640,8 @@ static int blob_unchanged(struct merge_options *opt, * performed. Comparison can be skipped if both files are * unchanged since their sha1s have already been compared. */ - if (renormalize_buffer(path, o.buf, o.len, &o) | - renormalize_buffer(path, a.buf, a.len, &a)) + if (renormalize_buffer(&the_index, path, o.buf, o.len, &o) | + renormalize_buffer(&the_index, path, a.buf, a.len, &a)) ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len)); error_return: diff --git a/mergetools/meld b/mergetools/meld index bc178e8882..7a08470f88 100644 --- a/mergetools/meld +++ b/mergetools/meld @@ -10,7 +10,7 @@ merge_cmd () { if test "$meld_has_output_option" = true then - "$merge_tool_path" --output "$MERGED" \ + "$merge_tool_path" --output="$MERGED" \ "$LOCAL" "$BASE" "$REMOTE" else "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE" diff --git a/notes-cache.c b/notes-cache.c index 2843e98576..29b4cede5f 100644 --- a/notes-cache.c +++ b/notes-cache.c @@ -69,15 +69,15 @@ int notes_cache_write(struct notes_cache *c) char *notes_cache_get(struct notes_cache *c, struct object_id *key_oid, size_t *outsize) { - const unsigned char *value_sha1; + const struct object_id *value_oid; enum object_type type; char *value; unsigned long size; - value_sha1 = get_note(&c->tree, key_oid->hash); - if (!value_sha1) + value_oid = get_note(&c->tree, key_oid); + if (!value_oid) return NULL; - value = read_sha1_file(value_sha1, &type, &size); + value = read_sha1_file(value_oid->hash, &type, &size); *outsize = size; return value; @@ -90,5 +90,5 @@ int notes_cache_put(struct notes_cache *c, struct object_id *key_oid, if (write_sha1_file(data, size, "blob", value_oid.hash) < 0) return -1; - return add_note(&c->tree, key_oid->hash, value_oid.hash, NULL); + return add_note(&c->tree, key_oid, &value_oid, NULL); } diff --git a/notes-merge.c b/notes-merge.c index 6244f6af9c..70e3fbeefb 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -22,21 +22,21 @@ void init_notes_merge_options(struct notes_merge_options *o) o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT; } -static int path_to_sha1(const char *path, unsigned char *sha1) +static int path_to_oid(const char *path, struct object_id *oid) { - char hex_sha1[40]; + char hex_oid[GIT_SHA1_HEXSZ]; int i = 0; - while (*path && i < 40) { + while (*path && i < GIT_SHA1_HEXSZ) { if (*path != '/') - hex_sha1[i++] = *path; + hex_oid[i++] = *path; path++; } - if (*path || i != 40) + if (*path || i != GIT_SHA1_HEXSZ) return -1; - return get_sha1_hex(hex_sha1, sha1); + return get_oid_hex(hex_oid, oid); } -static int verify_notes_filepair(struct diff_filepair *p, unsigned char *sha1) +static int verify_notes_filepair(struct diff_filepair *p, struct object_id *oid) { switch (p->status) { case DIFF_STATUS_MODIFIED: @@ -54,11 +54,11 @@ static int verify_notes_filepair(struct diff_filepair *p, unsigned char *sha1) return -1; } assert(!strcmp(p->one->path, p->two->path)); - return path_to_sha1(p->one->path, sha1); + return path_to_oid(p->one->path, oid); } static struct notes_merge_pair *find_notes_merge_pair_pos( - struct notes_merge_pair *list, int len, unsigned char *obj, + struct notes_merge_pair *list, int len, struct object_id *obj, int insert_new, int *occupied) { /* @@ -75,7 +75,7 @@ static struct notes_merge_pair *find_notes_merge_pair_pos( int i = last_index < len ? last_index : len - 1; int prev_cmp = 0, cmp = -1; while (i >= 0 && i < len) { - cmp = hashcmp(obj, list[i].obj.hash); + cmp = oidcmp(obj, &list[i].obj); if (!cmp) /* obj belongs @ i */ break; else if (cmp < 0 && prev_cmp <= 0) /* obj belongs < i */ @@ -114,8 +114,8 @@ static struct object_id uninitialized = { }; static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, - const unsigned char *base, - const unsigned char *remote, + const struct object_id *base, + const struct object_id *remote, int *num_changes) { struct diff_options opt; @@ -123,13 +123,13 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, int i, len = 0; trace_printf("\tdiff_tree_remote(base = %.7s, remote = %.7s)\n", - sha1_to_hex(base), sha1_to_hex(remote)); + oid_to_hex(base), oid_to_hex(remote)); diff_setup(&opt); DIFF_OPT_SET(&opt, RECURSIVE); opt.output_format = DIFF_FORMAT_NO_OUTPUT; diff_setup_done(&opt); - diff_tree_sha1(base, remote, "", &opt); + diff_tree_oid(base, remote, "", &opt); diffcore_std(&opt); changes = xcalloc(diff_queued_diff.nr, sizeof(struct notes_merge_pair)); @@ -138,19 +138,19 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, struct diff_filepair *p = diff_queued_diff.queue[i]; struct notes_merge_pair *mp; int occupied; - unsigned char obj[20]; + struct object_id obj; - if (verify_notes_filepair(p, obj)) { + if (verify_notes_filepair(p, &obj)) { trace_printf("\t\tCannot merge entry '%s' (%c): " "%.7s -> %.7s. Skipping!\n", p->one->path, p->status, oid_to_hex(&p->one->oid), oid_to_hex(&p->two->oid)); continue; } - mp = find_notes_merge_pair_pos(changes, len, obj, 1, &occupied); + mp = find_notes_merge_pair_pos(changes, len, &obj, 1, &occupied); if (occupied) { /* We've found an addition/deletion pair */ - assert(!hashcmp(mp->obj.hash, obj)); + assert(!oidcmp(&mp->obj, &obj)); if (is_null_oid(&p->one->oid)) { /* addition */ assert(is_null_oid(&mp->remote)); oidcpy(&mp->remote, &p->two->oid); @@ -160,7 +160,7 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, } else assert(!"Invalid existing change recorded"); } else { - hashcpy(mp->obj.hash, obj); + oidcpy(&mp->obj, &obj); oidcpy(&mp->base, &p->one->oid); oidcpy(&mp->local, &uninitialized); oidcpy(&mp->remote, &p->two->oid); @@ -179,45 +179,45 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, static void diff_tree_local(struct notes_merge_options *o, struct notes_merge_pair *changes, int len, - const unsigned char *base, - const unsigned char *local) + const struct object_id *base, + const struct object_id *local) { struct diff_options opt; int i; trace_printf("\tdiff_tree_local(len = %i, base = %.7s, local = %.7s)\n", - len, sha1_to_hex(base), sha1_to_hex(local)); + len, oid_to_hex(base), oid_to_hex(local)); diff_setup(&opt); DIFF_OPT_SET(&opt, RECURSIVE); opt.output_format = DIFF_FORMAT_NO_OUTPUT; diff_setup_done(&opt); - diff_tree_sha1(base, local, "", &opt); + diff_tree_oid(base, local, "", &opt); diffcore_std(&opt); for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; struct notes_merge_pair *mp; int match; - unsigned char obj[20]; + struct object_id obj; - if (verify_notes_filepair(p, obj)) { + if (verify_notes_filepair(p, &obj)) { trace_printf("\t\tCannot merge entry '%s' (%c): " "%.7s -> %.7s. Skipping!\n", p->one->path, p->status, oid_to_hex(&p->one->oid), oid_to_hex(&p->two->oid)); continue; } - mp = find_notes_merge_pair_pos(changes, len, obj, 0, &match); + mp = find_notes_merge_pair_pos(changes, len, &obj, 0, &match); if (!match) { trace_printf("\t\tIgnoring local-only change for %s: " - "%.7s -> %.7s\n", sha1_to_hex(obj), + "%.7s -> %.7s\n", oid_to_hex(&obj), oid_to_hex(&p->one->oid), oid_to_hex(&p->two->oid)); continue; } - assert(!hashcmp(mp->obj.hash, obj)); + assert(!oidcmp(&mp->obj, &obj)); if (is_null_oid(&p->two->oid)) { /* deletion */ /* * Either this is a true deletion (1), or it is part @@ -292,11 +292,11 @@ static void check_notes_merge_worktree(struct notes_merge_options *o) git_path(NOTES_MERGE_WORKTREE)); } -static void write_buf_to_worktree(const unsigned char *obj, +static void write_buf_to_worktree(const struct object_id *obj, const char *buf, unsigned long size) { int fd; - char *path = git_pathdup(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj)); + char *path = git_pathdup(NOTES_MERGE_WORKTREE "/%s", oid_to_hex(obj)); if (safe_create_leading_directories_const(path)) die_errno("unable to create directory for '%s'", path); @@ -320,19 +320,19 @@ static void write_buf_to_worktree(const unsigned char *obj, free(path); } -static void write_note_to_worktree(const unsigned char *obj, - const unsigned char *note) +static void write_note_to_worktree(const struct object_id *obj, + const struct object_id *note) { enum object_type type; unsigned long size; - void *buf = read_sha1_file(note, &type, &size); + void *buf = read_sha1_file(note->hash, &type, &size); if (!buf) die("cannot read note %s for object %s", - sha1_to_hex(note), sha1_to_hex(obj)); + oid_to_hex(note), oid_to_hex(obj)); if (type != OBJ_BLOB) die("blob expected in note %s for object %s", - sha1_to_hex(note), sha1_to_hex(obj)); + oid_to_hex(note), oid_to_hex(obj)); write_buf_to_worktree(obj, buf, size); free(buf); } @@ -358,7 +358,7 @@ static int ll_merge_in_worktree(struct notes_merge_options *o, if ((status < 0) || !result_buf.ptr) die("Failed to execute internal merge"); - write_buf_to_worktree(p->obj.hash, result_buf.ptr, result_buf.size); + write_buf_to_worktree(&p->obj, result_buf.ptr, result_buf.size); free(result_buf.ptr); return status; @@ -393,7 +393,7 @@ static int merge_one_change_manual(struct notes_merge_options *o, "deleted in %s and modified in %s. Version from %s " "left in tree.\n", oid_to_hex(&p->obj), lref, rref, rref); - write_note_to_worktree(p->obj.hash, p->remote.hash); + write_note_to_worktree(&p->obj, &p->remote); } else if (is_null_oid(&p->remote)) { /* D/F conflict, checkout p->local */ assert(!is_null_oid(&p->local)); @@ -402,7 +402,7 @@ static int merge_one_change_manual(struct notes_merge_options *o, "deleted in %s and modified in %s. Version from %s " "left in tree.\n", oid_to_hex(&p->obj), rref, lref, lref); - write_note_to_worktree(p->obj.hash, p->local.hash); + write_note_to_worktree(&p->obj, &p->local); } else { /* "regular" conflict, checkout result of ll_merge() */ const char *reason = "content"; @@ -444,14 +444,14 @@ static int merge_one_change(struct notes_merge_options *o, if (o->verbosity >= 2) printf("Using remote notes for %s\n", oid_to_hex(&p->obj)); - if (add_note(t, p->obj.hash, p->remote.hash, combine_notes_overwrite)) + if (add_note(t, &p->obj, &p->remote, combine_notes_overwrite)) die("BUG: combine_notes_overwrite failed"); return 0; case NOTES_MERGE_RESOLVE_UNION: if (o->verbosity >= 2) printf("Concatenating local and remote notes for %s\n", oid_to_hex(&p->obj)); - if (add_note(t, p->obj.hash, p->remote.hash, combine_notes_concatenate)) + if (add_note(t, &p->obj, &p->remote, combine_notes_concatenate)) die("failed to concatenate notes " "(combine_notes_concatenate)"); return 0; @@ -459,7 +459,7 @@ static int merge_one_change(struct notes_merge_options *o, if (o->verbosity >= 2) printf("Concatenating unique lines in local and remote " "notes for %s\n", oid_to_hex(&p->obj)); - if (add_note(t, p->obj.hash, p->remote.hash, combine_notes_cat_sort_uniq)) + if (add_note(t, &p->obj, &p->remote, combine_notes_cat_sort_uniq)) die("failed to concatenate notes " "(combine_notes_cat_sort_uniq)"); return 0; @@ -491,7 +491,7 @@ static int merge_changes(struct notes_merge_options *o, !oidcmp(&p->local, &p->base)) { /* no local change; adopt remote change */ trace_printf("\t\t\tno local change, adopted remote\n"); - if (add_note(t, p->obj.hash, p->remote.hash, + if (add_note(t, &p->obj, &p->remote, combine_notes_overwrite)) die("BUG: combine_notes_overwrite failed"); } else { @@ -505,16 +505,17 @@ static int merge_changes(struct notes_merge_options *o, } static int merge_from_diffs(struct notes_merge_options *o, - const unsigned char *base, - const unsigned char *local, - const unsigned char *remote, struct notes_tree *t) + const struct object_id *base, + const struct object_id *local, + const struct object_id *remote, + struct notes_tree *t) { struct notes_merge_pair *changes; int num_changes, conflicts; trace_printf("\tmerge_from_diffs(base = %.7s, local = %.7s, " - "remote = %.7s)\n", sha1_to_hex(base), sha1_to_hex(local), - sha1_to_hex(remote)); + "remote = %.7s)\n", oid_to_hex(base), oid_to_hex(local), + oid_to_hex(remote)); changes = diff_tree_remote(o, base, remote, &num_changes); diff_tree_local(o, changes, num_changes, base, local); @@ -533,17 +534,17 @@ static int merge_from_diffs(struct notes_merge_options *o, int notes_merge(struct notes_merge_options *o, struct notes_tree *local_tree, - unsigned char *result_sha1) + struct object_id *result_oid) { struct object_id local_oid, remote_oid; struct commit *local, *remote; struct commit_list *bases = NULL; - const unsigned char *base_sha1, *base_tree_sha1; + const struct object_id *base_oid, *base_tree_oid; int result = 0; assert(o->local_ref && o->remote_ref); assert(!strcmp(o->local_ref, local_tree->ref)); - hashclr(result_sha1); + oidclr(result_oid); trace_printf("notes_merge(o->local_ref = %s, o->remote_ref = %s)\n", o->local_ref, o->remote_ref); @@ -553,16 +554,16 @@ int notes_merge(struct notes_merge_options *o, die("Failed to resolve local notes ref '%s'", o->local_ref); else if (!check_refname_format(o->local_ref, 0) && is_null_oid(&local_oid)) - local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */ + local = NULL; /* local_oid == null_oid indicates unborn ref */ else if (!(local = lookup_commit_reference(&local_oid))) die("Could not parse local commit %s (%s)", oid_to_hex(&local_oid), o->local_ref); trace_printf("\tlocal commit: %.7s\n", oid_to_hex(&local_oid)); - /* Dereference o->remote_ref into remote_sha1 */ + /* Dereference o->remote_ref into remote_oid */ if (get_oid(o->remote_ref, &remote_oid)) { /* - * Failed to get remote_sha1. If o->remote_ref looks like an + * Failed to get remote_oid. If o->remote_ref looks like an * unborn ref, perform the merge using an empty notes tree. */ if (!check_refname_format(o->remote_ref, 0)) { @@ -583,12 +584,12 @@ int notes_merge(struct notes_merge_options *o, "(%s)", o->remote_ref, o->local_ref); if (!local) { /* result == remote commit */ - hashcpy(result_sha1, remote_oid.hash); + oidcpy(result_oid, &remote_oid); goto found_result; } if (!remote) { /* result == local commit */ - hashcpy(result_sha1, local_oid.hash); + oidcpy(result_oid, &local_oid); goto found_result; } assert(local && remote); @@ -596,48 +597,48 @@ int notes_merge(struct notes_merge_options *o, /* Find merge bases */ bases = get_merge_bases(local, remote); if (!bases) { - base_sha1 = null_sha1; - base_tree_sha1 = EMPTY_TREE_SHA1_BIN; + base_oid = &null_oid; + base_tree_oid = &empty_tree_oid; if (o->verbosity >= 4) printf("No merge base found; doing history-less merge\n"); } else if (!bases->next) { - base_sha1 = bases->item->object.oid.hash; - base_tree_sha1 = bases->item->tree->object.oid.hash; + base_oid = &bases->item->object.oid; + base_tree_oid = &bases->item->tree->object.oid; if (o->verbosity >= 4) printf("One merge base found (%.7s)\n", - sha1_to_hex(base_sha1)); + oid_to_hex(base_oid)); } else { /* TODO: How to handle multiple merge-bases? */ - base_sha1 = bases->item->object.oid.hash; - base_tree_sha1 = bases->item->tree->object.oid.hash; + base_oid = &bases->item->object.oid; + base_tree_oid = &bases->item->tree->object.oid; if (o->verbosity >= 3) printf("Multiple merge bases found. Using the first " - "(%.7s)\n", sha1_to_hex(base_sha1)); + "(%.7s)\n", oid_to_hex(base_oid)); } if (o->verbosity >= 4) printf("Merging remote commit %.7s into local commit %.7s with " "merge-base %.7s\n", oid_to_hex(&remote->object.oid), oid_to_hex(&local->object.oid), - sha1_to_hex(base_sha1)); + oid_to_hex(base_oid)); - if (!hashcmp(remote->object.oid.hash, base_sha1)) { + if (!oidcmp(&remote->object.oid, base_oid)) { /* Already merged; result == local commit */ if (o->verbosity >= 2) printf("Already up-to-date!\n"); - hashcpy(result_sha1, local->object.oid.hash); + oidcpy(result_oid, &local->object.oid); goto found_result; } - if (!hashcmp(local->object.oid.hash, base_sha1)) { + if (!oidcmp(&local->object.oid, base_oid)) { /* Fast-forward; result == remote commit */ if (o->verbosity >= 2) printf("Fast-forward\n"); - hashcpy(result_sha1, remote->object.oid.hash); + oidcpy(result_oid, &remote->object.oid); goto found_result; } - result = merge_from_diffs(o, base_tree_sha1, local->tree->object.oid.hash, - remote->tree->object.oid.hash, local_tree); + result = merge_from_diffs(o, base_tree_oid, &local->tree->object.oid, + &remote->tree->object.oid, local_tree); if (result != 0) { /* non-trivial merge (with or without conflicts) */ /* Commit (partial) result */ @@ -646,28 +647,28 @@ int notes_merge(struct notes_merge_options *o, commit_list_insert(local, &parents); create_notes_commit(local_tree, parents, o->commit_msg.buf, o->commit_msg.len, - result_sha1); + result_oid->hash); } found_result: free_commit_list(bases); strbuf_release(&(o->commit_msg)); - trace_printf("notes_merge(): result = %i, result_sha1 = %.7s\n", - result, sha1_to_hex(result_sha1)); + trace_printf("notes_merge(): result = %i, result_oid = %.7s\n", + result, oid_to_hex(result_oid)); return result; } int notes_merge_commit(struct notes_merge_options *o, struct notes_tree *partial_tree, struct commit *partial_commit, - unsigned char *result_sha1) + struct object_id *result_oid) { /* * Iterate through files in .git/NOTES_MERGE_WORKTREE and add all * found notes to 'partial_tree'. Write the updated notes tree to * the DB, and commit the resulting tree object while reusing the * commit message and parents from 'partial_commit'. - * Finally store the new commit object SHA1 into 'result_sha1'. + * Finally store the new commit object OID into 'result_oid'. */ DIR *dir; struct dirent *e; @@ -693,12 +694,12 @@ int notes_merge_commit(struct notes_merge_options *o, baselen = path.len; while ((e = readdir(dir)) != NULL) { struct stat st; - unsigned char obj_sha1[20], blob_sha1[20]; + struct object_id obj_oid, blob_oid; if (is_dot_or_dotdot(e->d_name)) continue; - if (strlen(e->d_name) != 40 || get_sha1_hex(e->d_name, obj_sha1)) { + if (get_oid_hex(e->d_name, &obj_oid)) { if (o->verbosity >= 3) printf("Skipping non-SHA1 entry '%s%s'\n", path.buf, e->d_name); @@ -709,23 +710,23 @@ int notes_merge_commit(struct notes_merge_options *o, /* write file as blob, and add to partial_tree */ if (stat(path.buf, &st)) die_errno("Failed to stat '%s'", path.buf); - if (index_path(blob_sha1, path.buf, &st, HASH_WRITE_OBJECT)) + if (index_path(blob_oid.hash, path.buf, &st, HASH_WRITE_OBJECT)) die("Failed to write blob object from '%s'", path.buf); - if (add_note(partial_tree, obj_sha1, blob_sha1, NULL)) + if (add_note(partial_tree, &obj_oid, &blob_oid, NULL)) die("Failed to add resolved note '%s' to notes tree", path.buf); if (o->verbosity >= 4) printf("Added resolved note for object %s: %s\n", - sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1)); + oid_to_hex(&obj_oid), oid_to_hex(&blob_oid)); strbuf_setlen(&path, baselen); } create_notes_commit(partial_tree, partial_commit->parents, - msg, strlen(msg), result_sha1); + msg, strlen(msg), result_oid->hash); unuse_commit_buffer(partial_commit, buffer); if (o->verbosity >= 4) printf("Finalized notes merge commit: %s\n", - sha1_to_hex(result_sha1)); + oid_to_hex(result_oid)); strbuf_release(&path); closedir(dir); return 0; diff --git a/notes-merge.h b/notes-merge.h index 0d890563b5..f815f23451 100644 --- a/notes-merge.h +++ b/notes-merge.h @@ -32,16 +32,16 @@ void init_notes_merge_options(struct notes_merge_options *o); * outcomes: * * 1. The merge trivially results in an existing commit (e.g. fast-forward or - * already-up-to-date). 'local_tree' is untouched, the SHA1 of the result - * is written into 'result_sha1' and 0 is returned. + * already-up-to-date). 'local_tree' is untouched, the OID of the result + * is written into 'result_oid' and 0 is returned. * 2. The merge successfully completes, producing a merge commit. local_tree - * contains the updated notes tree, the SHA1 of the resulting commit is - * written into 'result_sha1', and 1 is returned. + * contains the updated notes tree, the OID of the resulting commit is + * written into 'result_oid', and 1 is returned. * 3. The merge results in conflicts. This is similar to #2 in that the * partial merge result (i.e. merge result minus the unmerged entries) - * are stored in 'local_tree', and the SHA1 or the resulting commit + * are stored in 'local_tree', and the OID or the resulting commit * (to be amended when the conflicts have been resolved) is written into - * 'result_sha1'. The unmerged entries are written into the + * 'result_oid'. The unmerged entries are written into the * .git/NOTES_MERGE_WORKTREE directory with conflict markers. * -1 is returned. * @@ -52,7 +52,7 @@ void init_notes_merge_options(struct notes_merge_options *o); */ int notes_merge(struct notes_merge_options *o, struct notes_tree *local_tree, - unsigned char *result_sha1); + struct object_id *result_oid); /* * Finalize conflict resolution from an earlier notes_merge() @@ -62,13 +62,13 @@ int notes_merge(struct notes_merge_options *o, * call to notes_merge(). * * This function will add the (now resolved) notes in .git/NOTES_MERGE_WORKTREE - * to 'partial_tree', and create a final notes merge commit, the SHA1 of which - * will be stored in 'result_sha1'. + * to 'partial_tree', and create a final notes merge commit, the OID of which + * will be stored in 'result_oid'. */ int notes_merge_commit(struct notes_merge_options *o, struct notes_tree *partial_tree, struct commit *partial_commit, - unsigned char *result_sha1); + struct object_id *result_oid); /* * Abort conflict resolution from an earlier notes_merge() diff --git a/notes-utils.c b/notes-utils.c index 325ff3daa3..9765deb41a 100644 --- a/notes-utils.c +++ b/notes-utils.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "notes-utils.h" @@ -132,8 +133,11 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) c->mode_from_env = 1; c->combine = parse_combine_notes_fn(rewrite_mode_env); if (!c->combine) - /* TRANSLATORS: The first %s is the name of the - environment variable, the second %s is its value */ + /* + * TRANSLATORS: The first %s is the name of + * the environment variable, the second %s is + * its value. + */ error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT, rewrite_mode_env); } @@ -155,7 +159,7 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) } int copy_note_for_rewrite(struct notes_rewrite_cfg *c, - const unsigned char *from_obj, const unsigned char *to_obj) + const struct object_id *from_obj, const struct object_id *to_obj) { int ret = 0; int i; diff --git a/notes-utils.h b/notes-utils.h index fa538e1d95..1190578398 100644 --- a/notes-utils.h +++ b/notes-utils.h @@ -40,7 +40,7 @@ struct notes_rewrite_cfg { int parse_notes_merge_strategy(const char *v, enum notes_merge_strategy *s); struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd); int copy_note_for_rewrite(struct notes_rewrite_cfg *c, - const unsigned char *from_obj, const unsigned char *to_obj); + const struct object_id *from_obj, const struct object_id *to_obj); void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c, const char *msg); #endif @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "notes.h" #include "blob.h" #include "tree.h" @@ -35,8 +36,8 @@ struct int_node { * subtree. */ struct leaf_node { - unsigned char key_sha1[20]; - unsigned char val_sha1[20]; + struct object_id key_oid; + struct object_id val_oid; }; /* @@ -51,7 +52,7 @@ struct non_note { struct non_note *next; /* grounded (last->next == NULL) */ char *path; unsigned int mode; - unsigned char sha1[20]; + struct object_id oid; }; #define PTR_TYPE_NULL 0 @@ -65,8 +66,10 @@ struct non_note { #define GET_NIBBLE(n, sha1) (((sha1[(n) >> 1]) >> ((~(n) & 0x01) << 2)) & 0x0f) +#define KEY_INDEX (GIT_SHA1_RAWSZ - 1) +#define FANOUT_PATH_SEPARATORS ((GIT_SHA1_HEXSZ / 2) - 1) #define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \ - (memcmp(key_sha1, subtree_sha1, subtree_sha1[19])) + (memcmp(key_sha1, subtree_sha1, subtree_sha1[KEY_INDEX])) struct notes_tree default_notes_tree; @@ -100,7 +103,7 @@ static void **note_tree_search(struct notes_tree *t, struct int_node **tree, if (GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE) { l = (struct leaf_node *) CLR_PTR_TYPE(p); - if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_oid.hash)) { /* unpack tree and resume search */ (*tree)->a[0] = NULL; load_subtree(t, l, *tree, *n); @@ -118,7 +121,7 @@ static void **note_tree_search(struct notes_tree *t, struct int_node **tree, return note_tree_search(t, tree, n, key_sha1); case PTR_TYPE_SUBTREE: l = (struct leaf_node *) CLR_PTR_TYPE(p); - if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { + if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_oid.hash)) { /* unpack tree and resume search */ (*tree)->a[i] = NULL; load_subtree(t, l, *tree, *n); @@ -143,7 +146,7 @@ static struct leaf_node *note_tree_find(struct notes_tree *t, void **p = note_tree_search(t, &tree, &n, key_sha1); if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) { struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p); - if (!hashcmp(key_sha1, l->key_sha1)) + if (!hashcmp(key_sha1, l->key_oid.hash)) return l; } return NULL; @@ -194,19 +197,19 @@ static void note_tree_remove(struct notes_tree *t, struct leaf_node *entry) { struct leaf_node *l; - struct int_node *parent_stack[20]; + struct int_node *parent_stack[GIT_SHA1_RAWSZ]; unsigned char i, j; - void **p = note_tree_search(t, &tree, &n, entry->key_sha1); + void **p = note_tree_search(t, &tree, &n, entry->key_oid.hash); assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ if (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE) return; /* type mismatch, nothing to remove */ l = (struct leaf_node *) CLR_PTR_TYPE(*p); - if (hashcmp(l->key_sha1, entry->key_sha1)) + if (oidcmp(&l->key_oid, &entry->key_oid)) return; /* key mismatch, nothing to remove */ /* we have found a matching entry */ - hashcpy(entry->val_sha1, l->val_sha1); + oidcpy(&entry->val_oid, &l->val_oid); free(l); *p = SET_PTR_TYPE(NULL, PTR_TYPE_NULL); @@ -216,14 +219,14 @@ static void note_tree_remove(struct notes_tree *t, /* first, build stack of ancestors between root and current node */ parent_stack[0] = t->root; for (i = 0; i < n; i++) { - j = GET_NIBBLE(i, entry->key_sha1); + j = GET_NIBBLE(i, entry->key_oid.hash); parent_stack[i + 1] = CLR_PTR_TYPE(parent_stack[i]->a[j]); } assert(i == n && parent_stack[i] == tree); /* next, unwind stack until note_tree_consolidate() is done */ while (i > 0 && !note_tree_consolidate(parent_stack[i], parent_stack[i - 1], - GET_NIBBLE(i - 1, entry->key_sha1))) + GET_NIBBLE(i - 1, entry->key_oid.hash))) i--; } @@ -246,7 +249,7 @@ static int note_tree_insert(struct notes_tree *t, struct int_node *tree, { struct int_node *new_node; struct leaf_node *l; - void **p = note_tree_search(t, &tree, &n, entry->key_sha1); + void **p = note_tree_search(t, &tree, &n, entry->key_oid.hash); int ret = 0; assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ @@ -254,7 +257,7 @@ static int note_tree_insert(struct notes_tree *t, struct int_node *tree, switch (GET_PTR_TYPE(*p)) { case PTR_TYPE_NULL: assert(!*p); - if (is_null_sha1(entry->val_sha1)) + if (is_null_oid(&entry->val_oid)) free(entry); else *p = SET_PTR_TYPE(entry, type); @@ -262,22 +265,22 @@ static int note_tree_insert(struct notes_tree *t, struct int_node *tree, case PTR_TYPE_NOTE: switch (type) { case PTR_TYPE_NOTE: - if (!hashcmp(l->key_sha1, entry->key_sha1)) { + if (!oidcmp(&l->key_oid, &entry->key_oid)) { /* skip concatenation if l == entry */ - if (!hashcmp(l->val_sha1, entry->val_sha1)) + if (!oidcmp(&l->val_oid, &entry->val_oid)) return 0; - ret = combine_notes(l->val_sha1, - entry->val_sha1); - if (!ret && is_null_sha1(l->val_sha1)) + ret = combine_notes(l->val_oid.hash, + entry->val_oid.hash); + if (!ret && is_null_oid(&l->val_oid)) note_tree_remove(t, tree, n, entry); free(entry); return ret; } break; case PTR_TYPE_SUBTREE: - if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, - entry->key_sha1)) { + if (!SUBTREE_SHA1_PREFIXCMP(l->key_oid.hash, + entry->key_oid.hash)) { /* unpack 'entry' */ load_subtree(t, entry, tree, n); free(entry); @@ -287,7 +290,7 @@ static int note_tree_insert(struct notes_tree *t, struct int_node *tree, } break; case PTR_TYPE_SUBTREE: - if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { + if (!SUBTREE_SHA1_PREFIXCMP(entry->key_oid.hash, l->key_oid.hash)) { /* unpack 'l' and restart insert */ *p = NULL; load_subtree(t, l, tree, n); @@ -301,7 +304,7 @@ static int note_tree_insert(struct notes_tree *t, struct int_node *tree, /* non-matching leaf_node */ assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); - if (is_null_sha1(entry->val_sha1)) { /* skip insertion of empty note */ + if (is_null_oid(&entry->val_oid)) { /* skip insertion of empty note */ free(entry); return 0; } @@ -341,21 +344,21 @@ static void note_tree_free(struct int_node *tree) * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2). * Pads sha1 with NULs up to sha1_len (not included in returned length). */ -static int get_sha1_hex_segment(const char *hex, unsigned int hex_len, - unsigned char *sha1, unsigned int sha1_len) +static int get_oid_hex_segment(const char *hex, unsigned int hex_len, + unsigned char *oid, unsigned int oid_len) { unsigned int i, len = hex_len >> 1; - if (hex_len % 2 != 0 || len > sha1_len) + if (hex_len % 2 != 0 || len > oid_len) return -1; for (i = 0; i < len; i++) { unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]); if (val & ~0xff) return -1; - *sha1++ = val; + *oid++ = val; hex += 2; } - for (; i < sha1_len; i++) - *sha1++ = 0; + for (; i < oid_len; i++) + *oid++ = 0; return len; } @@ -373,7 +376,7 @@ static void add_non_note(struct notes_tree *t, char *path, n->next = NULL; n->path = path; n->mode = mode; - hashcpy(n->sha1, sha1); + hashcpy(n->oid.hash, sha1); t->prev_non_note = n; if (!t->first_non_note) { @@ -399,7 +402,7 @@ static void add_non_note(struct notes_tree *t, char *path, if (non_note_cmp(p, n) == 0) { /* n ~= p; overwrite p with n */ assert(strcmp(p->path, n->path) == 0); p->mode = n->mode; - hashcpy(p->sha1, n->sha1); + oidcpy(&p->oid, &n->oid); free(n); t->prev_non_note = p; return; @@ -413,7 +416,7 @@ static void add_non_note(struct notes_tree *t, char *path, static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, struct int_node *node, unsigned int n) { - unsigned char object_sha1[20]; + struct object_id object_oid; unsigned int prefix_len; void *buf; struct tree_desc desc; @@ -422,18 +425,18 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, unsigned char type; struct leaf_node *l; - buf = fill_tree_descriptor(&desc, subtree->val_sha1); + buf = fill_tree_descriptor(&desc, subtree->val_oid.hash); if (!buf) die("Could not read %s for notes-index", - sha1_to_hex(subtree->val_sha1)); + oid_to_hex(&subtree->val_oid)); - prefix_len = subtree->key_sha1[19]; + prefix_len = subtree->key_oid.hash[KEY_INDEX]; assert(prefix_len * 2 >= n); - memcpy(object_sha1, subtree->key_sha1, prefix_len); + memcpy(object_oid.hash, subtree->key_oid.hash, prefix_len); while (tree_entry(&desc, &entry)) { path_len = strlen(entry.path); - len = get_sha1_hex_segment(entry.path, path_len, - object_sha1 + prefix_len, 20 - prefix_len); + len = get_oid_hex_segment(entry.path, path_len, + object_oid.hash + prefix_len, GIT_SHA1_RAWSZ - prefix_len); if (len < 0) goto handle_non_note; /* entry.path is not a SHA1 */ len += prefix_len; @@ -443,16 +446,16 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, * If object SHA1 is incomplete (len < 20), and current * component consists of 2 hex chars, assume note subtree */ - if (len <= 20) { + if (len <= GIT_SHA1_RAWSZ) { type = PTR_TYPE_NOTE; l = (struct leaf_node *) xcalloc(1, sizeof(struct leaf_node)); - hashcpy(l->key_sha1, object_sha1); - hashcpy(l->val_sha1, entry.oid->hash); - if (len < 20) { + oidcpy(&l->key_oid, &object_oid); + oidcpy(&l->val_oid, entry.oid); + if (len < GIT_SHA1_RAWSZ) { if (!S_ISDIR(entry.mode) || path_len != 2) goto handle_non_note; /* not subtree */ - l->key_sha1[19] = (unsigned char) len; + l->key_oid.hash[KEY_INDEX] = (unsigned char) len; type = PTR_TYPE_SUBTREE; } if (note_tree_insert(t, node, n, l, type, @@ -460,7 +463,7 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, die("Failed to load %s %s into notes tree " "from %s", type == PTR_TYPE_NOTE ? "note" : "subtree", - sha1_to_hex(l->key_sha1), t->ref); + oid_to_hex(&l->key_oid), t->ref); } continue; @@ -486,7 +489,7 @@ handle_non_note: */ { struct strbuf non_note_path = STRBUF_INIT; - const char *q = sha1_to_hex(subtree->key_sha1); + const char *q = oid_to_hex(&subtree->key_oid); int i; for (i = 0; i < prefix_len; i++) { strbuf_addch(&non_note_path, *q++); @@ -542,14 +545,14 @@ static unsigned char determine_fanout(struct int_node *tree, unsigned char n, } /* hex SHA1 + 19 * '/' + NUL */ -#define FANOUT_PATH_MAX 40 + 19 + 1 +#define FANOUT_PATH_MAX GIT_SHA1_HEXSZ + FANOUT_PATH_SEPARATORS + 1 static void construct_path_with_fanout(const unsigned char *sha1, unsigned char fanout, char *path) { unsigned int i = 0, j = 0; const char *hex_sha1 = sha1_to_hex(sha1); - assert(fanout < 20); + assert(fanout < GIT_SHA1_RAWSZ); while (fanout) { path[i++] = hex_sha1[j++]; path[i++] = hex_sha1[j++]; @@ -599,15 +602,17 @@ redo: flags & FOR_EACH_NOTE_YIELD_SUBTREES) { /* invoke callback with subtree */ unsigned int path_len = - l->key_sha1[19] * 2 + fanout; + l->key_oid.hash[KEY_INDEX] * 2 + fanout; assert(path_len < FANOUT_PATH_MAX - 1); - construct_path_with_fanout(l->key_sha1, fanout, + construct_path_with_fanout(l->key_oid.hash, + fanout, path); /* Create trailing slash, if needed */ if (path[path_len - 1] != '/') path[path_len++] = '/'; path[path_len] = '\0'; - ret = fn(l->key_sha1, l->val_sha1, path, + ret = fn(&l->key_oid, &l->val_oid, + path, cb_data); } if (n > fanout * 2 || @@ -621,8 +626,10 @@ redo: break; case PTR_TYPE_NOTE: l = (struct leaf_node *) CLR_PTR_TYPE(p); - construct_path_with_fanout(l->key_sha1, fanout, path); - ret = fn(l->key_sha1, l->val_sha1, path, cb_data); + construct_path_with_fanout(l->key_oid.hash, fanout, + path); + ret = fn(&l->key_oid, &l->val_oid, path, + cb_data); break; } if (ret) @@ -650,7 +657,7 @@ static void write_tree_entry(struct strbuf *buf, unsigned int mode, unsigned char *sha1) { strbuf_addf(buf, "%o %.*s%c", mode, path_len, path, '\0'); - strbuf_add(buf, sha1, 20); + strbuf_add(buf, sha1, GIT_SHA1_RAWSZ); } static void tree_write_stack_init_subtree(struct tree_write_stack *tws, @@ -662,7 +669,7 @@ static void tree_write_stack_init_subtree(struct tree_write_stack *tws, n = (struct tree_write_stack *) xmalloc(sizeof(struct tree_write_stack)); n->next = NULL; - strbuf_init(&n->buf, 256 * (32 + 40)); /* assume 256 entries per tree */ + strbuf_init(&n->buf, 256 * (32 + GIT_SHA1_HEXSZ)); /* assume 256 entries per tree */ n->path[0] = n->path[1] = '\0'; tws->next = n; tws->path[0] = path[0]; @@ -673,18 +680,18 @@ static int tree_write_stack_finish_subtree(struct tree_write_stack *tws) { int ret; struct tree_write_stack *n = tws->next; - unsigned char s[20]; + struct object_id s; if (n) { ret = tree_write_stack_finish_subtree(n); if (ret) return ret; - ret = write_sha1_file(n->buf.buf, n->buf.len, tree_type, s); + ret = write_sha1_file(n->buf.buf, n->buf.len, tree_type, s.hash); if (ret) return ret; strbuf_release(&n->buf); free(n); tws->next = NULL; - write_tree_entry(&tws->buf, 040000, tws->path, 2, s); + write_tree_entry(&tws->buf, 040000, tws->path, 2, s.hash); tws->path[0] = tws->path[1] = '\0'; } return 0; @@ -692,7 +699,7 @@ static int tree_write_stack_finish_subtree(struct tree_write_stack *tws) static int write_each_note_helper(struct tree_write_stack *tws, const char *path, unsigned int mode, - const unsigned char *sha1) + const struct object_id *oid) { size_t path_len = strlen(path); unsigned int n = 0; @@ -722,7 +729,7 @@ static int write_each_note_helper(struct tree_write_stack *tws, /* Finally add given entry to the current tree object */ write_tree_entry(&tws->buf, mode, path + 3 * n, path_len - (3 * n), - sha1); + oid->hash); return 0; } @@ -742,7 +749,7 @@ static int write_each_non_note_until(const char *note_path, ; /* do nothing, prefer note to non-note */ else { ret = write_each_note_helper(d->root, n->path, n->mode, - n->sha1); + &n->oid); if (ret) return ret; } @@ -752,8 +759,8 @@ static int write_each_non_note_until(const char *note_path, return 0; } -static int write_each_note(const unsigned char *object_sha1, - const unsigned char *note_sha1, char *note_path, +static int write_each_note(const struct object_id *object_oid, + const struct object_id *note_oid, char *note_path, void *cb_data) { struct write_each_note_data *d = @@ -767,11 +774,11 @@ static int write_each_note(const unsigned char *object_sha1, note_path[note_path_len] = '\0'; mode = 040000; } - assert(note_path_len <= 40 + 19); + assert(note_path_len <= GIT_SHA1_HEXSZ + FANOUT_PATH_SEPARATORS); /* Weave non-note entries into note entries */ return write_each_non_note_until(note_path, d) || - write_each_note_helper(d->root, note_path, mode, note_sha1); + write_each_note_helper(d->root, note_path, mode, note_oid); } struct note_delete_list { @@ -779,20 +786,20 @@ struct note_delete_list { const unsigned char *sha1; }; -static int prune_notes_helper(const unsigned char *object_sha1, - const unsigned char *note_sha1, char *note_path, +static int prune_notes_helper(const struct object_id *object_oid, + const struct object_id *note_oid, char *note_path, void *cb_data) { struct note_delete_list **l = (struct note_delete_list **) cb_data; struct note_delete_list *n; - if (has_sha1_file(object_sha1)) + if (has_object_file(object_oid)) return 0; /* nothing to do for this note */ /* failed to find object => prune this note */ n = (struct note_delete_list *) xmalloc(sizeof(*n)); n->next = *l; - n->sha1 = object_sha1; + n->sha1 = object_oid->hash; *l = n; return 0; } @@ -942,8 +949,8 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob) if (has_glob_specials(glob)) { for_each_glob_ref(string_list_add_one_ref, glob, list); } else { - unsigned char sha1[20]; - if (get_sha1(glob, sha1)) + struct object_id oid; + if (get_oid(glob, &oid)) warning("notes ref %s is invalid", glob); if (!unsorted_string_list_has_string(list, glob)) string_list_append(list, glob); @@ -1027,8 +1034,8 @@ void init_notes(struct notes_tree *t, const char *notes_ref, die("Failed to read notes tree referenced by %s (%s)", notes_ref, oid_to_hex(&object_oid)); - hashclr(root_tree.key_sha1); - hashcpy(root_tree.val_sha1, oid.hash); + oidclr(&root_tree.key_oid); + oidcpy(&root_tree.val_oid, &oid); load_subtree(t, &root_tree, t->root, 0); } @@ -1080,8 +1087,8 @@ void init_display_notes(struct display_notes_opt *opt) string_list_clear(&display_notes_refs, 0); } -int add_note(struct notes_tree *t, const unsigned char *object_sha1, - const unsigned char *note_sha1, combine_notes_fn combine_notes) +int add_note(struct notes_tree *t, const struct object_id *object_oid, + const struct object_id *note_oid, combine_notes_fn combine_notes) { struct leaf_node *l; @@ -1092,8 +1099,8 @@ int add_note(struct notes_tree *t, const unsigned char *object_sha1, if (!combine_notes) combine_notes = t->combine_notes; l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node)); - hashcpy(l->key_sha1, object_sha1); - hashcpy(l->val_sha1, note_sha1); + oidcpy(&l->key_oid, object_oid); + oidcpy(&l->val_oid, note_oid); return note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes); } @@ -1104,25 +1111,25 @@ int remove_note(struct notes_tree *t, const unsigned char *object_sha1) if (!t) t = &default_notes_tree; assert(t->initialized); - hashcpy(l.key_sha1, object_sha1); - hashclr(l.val_sha1); + hashcpy(l.key_oid.hash, object_sha1); + oidclr(&l.val_oid); note_tree_remove(t, t->root, 0, &l); - if (is_null_sha1(l.val_sha1)) /* no note was removed */ + if (is_null_oid(&l.val_oid)) /* no note was removed */ return 1; t->dirty = 1; return 0; } -const unsigned char *get_note(struct notes_tree *t, - const unsigned char *object_sha1) +const struct object_id *get_note(struct notes_tree *t, + const struct object_id *oid) { struct leaf_node *found; if (!t) t = &default_notes_tree; assert(t->initialized); - found = note_tree_find(t, t->root, 0, object_sha1); - return found ? found->val_sha1 : NULL; + found = note_tree_find(t, t->root, 0, oid->hash); + return found ? &found->val_oid : NULL; } int for_each_note(struct notes_tree *t, int flags, each_note_fn fn, @@ -1146,7 +1153,7 @@ int write_notes_tree(struct notes_tree *t, unsigned char *result) /* Prepare for traversal of current notes tree */ root.next = NULL; /* last forward entry in list is grounded */ - strbuf_init(&root.buf, 256 * (32 + 40)); /* assume 256 entries */ + strbuf_init(&root.buf, 256 * (32 + GIT_SHA1_HEXSZ)); /* assume 256 entries */ root.path[0] = root.path[1] = '\0'; cb_data.root = &root; cb_data.next_non_note = t->first_non_note; @@ -1209,11 +1216,11 @@ void free_notes(struct notes_tree *t) * (raw != 0) gives the %N userformat; otherwise, the note message is given * for human consumption. */ -static void format_note(struct notes_tree *t, const unsigned char *object_sha1, +static void format_note(struct notes_tree *t, const struct object_id *object_oid, struct strbuf *sb, const char *output_encoding, int raw) { static const char utf8[] = "utf-8"; - const unsigned char *sha1; + const struct object_id *oid; char *msg, *msg_p; unsigned long linelen, msglen; enum object_type type; @@ -1223,11 +1230,11 @@ static void format_note(struct notes_tree *t, const unsigned char *object_sha1, if (!t->initialized) init_notes(t, NULL, NULL, 0); - sha1 = get_note(t, object_sha1); - if (!sha1) + oid = get_note(t, object_oid); + if (!oid) return; - if (!(msg = read_sha1_file(sha1, &type, &msglen)) || type != OBJ_BLOB) { + if (!(msg = read_sha1_file(oid->hash, &type, &msglen)) || type != OBJ_BLOB) { free(msg); return; } @@ -1271,22 +1278,22 @@ static void format_note(struct notes_tree *t, const unsigned char *object_sha1, free(msg); } -void format_display_notes(const unsigned char *object_sha1, +void format_display_notes(const struct object_id *object_oid, struct strbuf *sb, const char *output_encoding, int raw) { int i; assert(display_notes_trees); for (i = 0; display_notes_trees[i]; i++) - format_note(display_notes_trees[i], object_sha1, sb, + format_note(display_notes_trees[i], object_oid, sb, output_encoding, raw); } int copy_note(struct notes_tree *t, - const unsigned char *from_obj, const unsigned char *to_obj, + const struct object_id *from_obj, const struct object_id *to_obj, int force, combine_notes_fn combine_notes) { - const unsigned char *note = get_note(t, from_obj); - const unsigned char *existing_note = get_note(t, to_obj); + const struct object_id *note = get_note(t, from_obj); + const struct object_id *existing_note = get_note(t, to_obj); if (!force && existing_note) return 1; @@ -1294,7 +1301,7 @@ int copy_note(struct notes_tree *t, if (note) return add_note(t, to_obj, note, combine_notes); else if (existing_note) - return add_note(t, to_obj, null_sha1, combine_notes); + return add_note(t, to_obj, &null_oid, combine_notes); return 0; } @@ -1311,9 +1318,9 @@ void expand_notes_ref(struct strbuf *sb) void expand_loose_notes_ref(struct strbuf *sb) { - unsigned char object[20]; + struct object_id object; - if (get_sha1(sb->buf, object)) { + if (get_oid(sb->buf, &object)) { /* fallback to expand_notes_ref */ expand_notes_ref(sb); } @@ -121,8 +121,8 @@ void init_notes(struct notes_tree *t, const char *notes_ref, * are not persistent until a subsequent call to write_notes_tree() returns * zero. */ -int add_note(struct notes_tree *t, const unsigned char *object_sha1, - const unsigned char *note_sha1, combine_notes_fn combine_notes); +int add_note(struct notes_tree *t, const struct object_id *object_oid, + const struct object_id *note_oid, combine_notes_fn combine_notes); /* * Remove the given note object from the given notes_tree structure @@ -140,8 +140,8 @@ int remove_note(struct notes_tree *t, const unsigned char *object_sha1); * * Return NULL if the given object has no notes. */ -const unsigned char *get_note(struct notes_tree *t, - const unsigned char *object_sha1); +const struct object_id *get_note(struct notes_tree *t, + const struct object_id *object_oid); /* * Copy a note from one object to another in the given notes_tree. @@ -156,7 +156,7 @@ const unsigned char *get_note(struct notes_tree *t, * zero. */ int copy_note(struct notes_tree *t, - const unsigned char *from_obj, const unsigned char *to_obj, + const struct object_id *from_obj, const struct object_id *to_obj, int force, combine_notes_fn combine_notes); /* @@ -202,8 +202,8 @@ int copy_note(struct notes_tree *t, * - copy_note() * - free_notes() */ -typedef int each_note_fn(const unsigned char *object_sha1, - const unsigned char *note_sha1, char *note_path, +typedef int each_note_fn(const struct object_id *object_oid, + const struct object_id *note_oid, char *note_path, void *cb_data); int for_each_note(struct notes_tree *t, int flags, each_note_fn fn, void *cb_data); @@ -277,7 +277,7 @@ void init_display_notes(struct display_notes_opt *opt); * * You *must* call init_display_notes() before using this function. */ -void format_display_notes(const unsigned char *object_sha1, +void format_display_notes(const struct object_id *object_oid, struct strbuf *sb, const char *output_encoding, int raw); /* @@ -377,8 +377,7 @@ void object_array_clear(struct object_array *array) int i; for (i = 0; i < array->nr; i++) object_array_release_entry(&array->objects[i]); - free(array->objects); - array->objects = NULL; + FREE_AND_NULL(array->objects); array->nr = array->alloc = 0; } @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "run-command.h" #include "sigchain.h" diff --git a/parse-options.c b/parse-options.c index a23a1e67f0..0dd9fc6a0d 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1,6 +1,7 @@ #include "git-compat-util.h" #include "parse-options.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "color.h" #include "utf8.h" @@ -589,8 +590,10 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx, fprintf_ln(outfile, _("usage: %s"), _(*usagestr++)); while (*usagestr && **usagestr) - /* TRANSLATORS: the colon here should align with the - one in "usage: %s" translation */ + /* + * TRANSLATORS: the colon here should align with the + * one in "usage: %s" translation. + */ fprintf_ln(outfile, _(" or: %s"), _(*usagestr++)); while (*usagestr) { if (**usagestr) diff --git a/patch-ids.c b/patch-ids.c index fa8f11de82..9c0ab9e67a 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -11,18 +11,18 @@ static int patch_id_defined(struct commit *commit) } int commit_patch_id(struct commit *commit, struct diff_options *options, - unsigned char *sha1, int diff_header_only) + struct object_id *oid, int diff_header_only) { if (!patch_id_defined(commit)) return -1; if (commit->parents) - diff_tree_sha1(commit->parents->item->object.oid.hash, - commit->object.oid.hash, "", options); + diff_tree_oid(&commit->parents->item->object.oid, + &commit->object.oid, "", options); else - diff_root_tree_sha1(commit->object.oid.hash, "", options); + diff_root_tree_oid(&commit->object.oid, "", options); diffcore_std(options); - return diff_flush_patch_id(options, sha1, diff_header_only); + return diff_flush_patch_id(options, oid, diff_header_only); } /* @@ -39,15 +39,15 @@ static int patch_id_cmp(struct patch_id *a, struct patch_id *b, struct diff_options *opt) { - if (is_null_sha1(a->patch_id) && - commit_patch_id(a->commit, opt, a->patch_id, 0)) + if (is_null_oid(&a->patch_id) && + commit_patch_id(a->commit, opt, &a->patch_id, 0)) return error("Could not get patch ID for %s", oid_to_hex(&a->commit->object.oid)); - if (is_null_sha1(b->patch_id) && - commit_patch_id(b->commit, opt, b->patch_id, 0)) + if (is_null_oid(&b->patch_id) && + commit_patch_id(b->commit, opt, &b->patch_id, 0)) return error("Could not get patch ID for %s", oid_to_hex(&b->commit->object.oid)); - return hashcmp(a->patch_id, b->patch_id); + return oidcmp(&a->patch_id, &b->patch_id); } int init_patch_ids(struct patch_ids *ids) @@ -71,13 +71,13 @@ static int init_patch_id_entry(struct patch_id *patch, struct commit *commit, struct patch_ids *ids) { - unsigned char header_only_patch_id[GIT_MAX_RAWSZ]; + struct object_id header_only_patch_id; patch->commit = commit; - if (commit_patch_id(commit, &ids->diffopts, header_only_patch_id, 1)) + if (commit_patch_id(commit, &ids->diffopts, &header_only_patch_id, 1)) return -1; - hashmap_entry_init(patch, sha1hash(header_only_patch_id)); + hashmap_entry_init(patch, sha1hash(header_only_patch_id.hash)); return 0; } @@ -99,11 +99,12 @@ struct patch_id *has_commit_patch_id(struct commit *commit, struct patch_id *add_commit_patch_id(struct commit *commit, struct patch_ids *ids) { - struct patch_id *key = xcalloc(1, sizeof(*key)); + struct patch_id *key; if (!patch_id_defined(commit)) return NULL; + key = xcalloc(1, sizeof(*key)); if (init_patch_id_entry(key, commit, ids)) { free(key); return NULL; diff --git a/patch-ids.h b/patch-ids.h index b9e5751f8e..bec0f727a7 100644 --- a/patch-ids.h +++ b/patch-ids.h @@ -3,7 +3,7 @@ struct patch_id { struct hashmap_entry ent; - unsigned char patch_id[GIT_MAX_RAWSZ]; + struct object_id patch_id; struct commit *commit; }; @@ -13,7 +13,7 @@ struct patch_ids { }; int commit_patch_id(struct commit *commit, struct diff_options *options, - unsigned char *sha1, int); + struct object_id *oid, int); int init_patch_ids(struct patch_ids *); int free_patch_ids(struct patch_ids *); struct patch_id *add_commit_patch_id(struct commit *, struct patch_ids *); diff --git a/pathspec.c b/pathspec.c index 50f76fff45..a5f4df31ea 100644 --- a/pathspec.c +++ b/pathspec.c @@ -1,4 +1,6 @@ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "config.h" #include "dir.h" #include "pathspec.h" #include "attr.h" @@ -17,6 +19,7 @@ * to use find_pathspecs_matching_against_index() instead. */ void add_pathspec_matches_against_index(const struct pathspec *pathspec, + const struct index_state *istate, char *seen) { int num_unmatched = 0, i; @@ -32,8 +35,8 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec, num_unmatched++; if (!num_unmatched) return; - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; ce_path_match(ce, pathspec, seen); } } @@ -46,10 +49,11 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec, * nature of the "closest" (i.e. most specific) matches which each of the * given pathspecs achieves against all items in the index. */ -char *find_pathspecs_matching_against_index(const struct pathspec *pathspec) +char *find_pathspecs_matching_against_index(const struct pathspec *pathspec, + const struct index_state *istate) { char *seen = xcalloc(pathspec->nr, 1); - add_pathspec_matches_against_index(pathspec, seen); + add_pathspec_matches_against_index(pathspec, istate, seen); return seen; } @@ -386,65 +390,6 @@ static const char *parse_element_magic(unsigned *magic, int *prefix_len, return parse_short_magic(magic, elem); } -static void strip_submodule_slash_cheap(struct pathspec_item *item) -{ - if (item->len >= 1 && item->match[item->len - 1] == '/') { - int i = cache_name_pos(item->match, item->len - 1); - - if (i >= 0 && S_ISGITLINK(active_cache[i]->ce_mode)) { - item->len--; - item->match[item->len] = '\0'; - } - } -} - -static void strip_submodule_slash_expensive(struct pathspec_item *item) -{ - int i; - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - int ce_len = ce_namelen(ce); - - if (!S_ISGITLINK(ce->ce_mode)) - continue; - - if (item->len <= ce_len || item->match[ce_len] != '/' || - memcmp(ce->name, item->match, ce_len)) - continue; - - if (item->len == ce_len + 1) { - /* strip trailing slash */ - item->len--; - item->match[item->len] = '\0'; - } else { - die(_("Pathspec '%s' is in submodule '%.*s'"), - item->original, ce_len, ce->name); - } - } -} - -static void die_inside_submodule_path(struct pathspec_item *item) -{ - int i; - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - int ce_len = ce_namelen(ce); - - if (!S_ISGITLINK(ce->ce_mode)) - continue; - - if (item->len < ce_len || - !(item->match[ce_len] == '/' || item->match[ce_len] == '\0') || - memcmp(ce->name, item->match, ce_len)) - continue; - - die(_("Pathspec '%s' is in submodule '%.*s'"), - item->original, ce_len, ce->name); - } -} - /* * Perform the initialization of a pathspec_item based on a pathspec element. */ @@ -517,12 +462,6 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, item->original = xstrdup(elt); } - if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) - strip_submodule_slash_cheap(item); - - if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE) - strip_submodule_slash_expensive(item); - if (magic & PATHSPEC_LITERAL) { item->nowildcard_len = item->len; } else { @@ -547,15 +486,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, /* sanity checks, pathspec matchers assume these are sane */ if (item->nowildcard_len > item->len || item->prefix > item->len) { - /* - * This case can be triggered by the user pointing us to a - * pathspec inside a submodule, which is an input error. - * Detect that here and complain, but fallback in the - * non-submodule case to a BUG, as we have no idea what - * would trigger that. - */ - die_inside_submodule_path(item); - die ("BUG: item->nowildcard_len > item->len || item->prefix > item->len)"); + die ("BUG: error initializing pathspec_item"); } } @@ -732,7 +663,6 @@ void clear_pathspec(struct pathspec *pathspec) attr_check_free(pathspec->items[i].attr_check); } - free(pathspec->items); - pathspec->items = NULL; + FREE_AND_NULL(pathspec->items); pathspec->nr = 0; } diff --git a/pathspec.h b/pathspec.h index 55e976972c..60e6500401 100644 --- a/pathspec.h +++ b/pathspec.h @@ -58,27 +58,17 @@ struct pathspec { #define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */ #define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */ #define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */ -/* strip the trailing slash if the given path is a gitlink */ -#define PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP (1<<3) /* die if a symlink is part of the given path's directory */ -#define PATHSPEC_SYMLINK_LEADING_PATH (1<<4) -/* - * This is like a combination of ..LEADING_PATH and .._SLASH_CHEAP - * (but not the same): it strips the trailing slash if the given path - * is a gitlink but also checks and dies if gitlink is part of the - * leading path (i.e. the given path goes beyond a submodule). It's - * safer than _SLASH_CHEAP and also more expensive. - */ -#define PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE (1<<5) -#define PATHSPEC_PREFIX_ORIGIN (1<<6) -#define PATHSPEC_KEEP_ORDER (1<<7) +#define PATHSPEC_SYMLINK_LEADING_PATH (1<<3) +#define PATHSPEC_PREFIX_ORIGIN (1<<4) +#define PATHSPEC_KEEP_ORDER (1<<5) /* * For the callers that just need pure paths from somewhere else, not * from command line. Global --*-pathspecs options are ignored. No * magic is parsed in each pathspec either. If PATHSPEC_LITERAL is * allowed, then it will automatically set for every pathspec. */ -#define PATHSPEC_LITERAL_PATH (1<<8) +#define PATHSPEC_LITERAL_PATH (1<<6) extern void parse_pathspec(struct pathspec *pathspec, unsigned magic_mask, @@ -106,7 +96,10 @@ static inline int ps_strcmp(const struct pathspec_item *item, return strcmp(s1, s2); } -extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec); -extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen); +extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, + const struct index_state *istate, + char *seen); +extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec, + const struct index_state *istate); #endif /* PATHSPEC_H */ diff --git a/pkt-line.c b/pkt-line.c index d4b6bfe076..9d845ecc3c 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -171,6 +171,25 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...) return status; } +int packet_writel(int fd, const char *line, ...) +{ + va_list args; + int err; + va_start(args, line); + for (;;) { + if (!line) + break; + if (strlen(line) > LARGE_PACKET_DATA_MAX) + return -1; + err = packet_write_fmt_gently(fd, "%s\n", line); + if (err) + return err; + line = va_arg(args, const char*); + } + va_end(args); + return packet_flush_gently(fd); +} + static int packet_write_gently(const int fd_out, const char *buf, size_t size) { static char packet_write_buffer[LARGE_PACKET_MAX]; @@ -315,7 +334,7 @@ static char *packet_read_line_generic(int fd, PACKET_READ_CHOMP_NEWLINE); if (dst_len) *dst_len = len; - return len ? packet_buffer : NULL; + return (len > 0) ? packet_buffer : NULL; } char *packet_read_line(int fd, int *len_p) @@ -323,6 +342,18 @@ char *packet_read_line(int fd, int *len_p) return packet_read_line_generic(fd, NULL, NULL, len_p); } +int packet_read_line_gently(int fd, int *dst_len, char **dst_line) +{ + int len = packet_read(fd, NULL, NULL, + packet_buffer, sizeof(packet_buffer), + PACKET_READ_CHOMP_NEWLINE|PACKET_READ_GENTLE_ON_EOF); + if (dst_len) + *dst_len = len; + if (dst_line) + *dst_line = (len > 0) ? packet_buffer : NULL; + return len; +} + char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len) { return packet_read_line_generic(-1, src, src_len, dst_len); diff --git a/pkt-line.h b/pkt-line.h index 18eac64830..450183b649 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -25,6 +25,8 @@ void packet_buf_flush(struct strbuf *buf); void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); int packet_flush_gently(int fd); int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +LAST_ARG_MUST_BE_NULL +int packet_writel(int fd, const char *line, ...); int write_packetized_from_fd(int fd_in, int fd_out); int write_packetized_from_buf(const char *src_in, size_t len, int fd_out); @@ -74,6 +76,17 @@ int packet_read(int fd, char **src_buffer, size_t *src_len, char char *packet_read_line(int fd, int *size); /* + * Convenience wrapper for packet_read that sets the PACKET_READ_GENTLE_ON_EOF + * and CHOMP_NEWLINE options. The return value specifies the number of bytes + * read into the buffer or -1 on truncated input. If the *dst_line parameter + * is not NULL it will return NULL for a flush packet or when the number of + * bytes copied is zero and otherwise points to a static buffer (that may be + * overwritten by subsequent calls). If the size parameter is not NULL, the + * length of the packet is written to it. + */ +int packet_read_line_gently(int fd, int *size, char **dst_line); + +/* * Same as packet_read_line, but read from a buf rather than a descriptor; * see packet_read for details on how src_* is used. */ @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "commit.h" #include "utf8.h" #include "diff.h" @@ -783,29 +784,9 @@ struct format_commit_context { size_t body_off; /* The following ones are relative to the result struct strbuf. */ - struct chunk abbrev_commit_hash; - struct chunk abbrev_tree_hash; - struct chunk abbrev_parent_hashes; size_t wrap_start; }; -static int add_again(struct strbuf *sb, struct chunk *chunk) -{ - if (chunk->len) { - strbuf_adddup(sb, chunk->off, chunk->len); - return 1; - } - - /* - * We haven't seen this chunk before. Our caller is surely - * going to add it the hard way now. Remember the most likely - * start of the to-be-added chunk: the current end of the - * struct strbuf. - */ - chunk->off = sb->len; - return 0; -} - static void parse_commit_header(struct format_commit_context *context) { const char *msg = context->message; @@ -1147,24 +1128,16 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ return 1; case 'h': /* abbreviated commit hash */ strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT)); - if (add_again(sb, &c->abbrev_commit_hash)) { - strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); - return 1; - } strbuf_add_unique_abbrev(sb, commit->object.oid.hash, c->pretty_ctx->abbrev); strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET)); - c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off; return 1; case 'T': /* tree hash */ strbuf_addstr(sb, oid_to_hex(&commit->tree->object.oid)); return 1; case 't': /* abbreviated tree hash */ - if (add_again(sb, &c->abbrev_tree_hash)) - return 1; strbuf_add_unique_abbrev(sb, commit->tree->object.oid.hash, c->pretty_ctx->abbrev); - c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off; return 1; case 'P': /* parent hashes */ for (p = commit->parents; p; p = p->next) { @@ -1174,16 +1147,12 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ } return 1; case 'p': /* abbreviated parent hashes */ - if (add_again(sb, &c->abbrev_parent_hashes)) - return 1; for (p = commit->parents; p; p = p->next) { if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_add_unique_abbrev(sb, p->item->object.oid.hash, c->pretty_ctx->abbrev); } - c->abbrev_parent_hashes.len = sb->len - - c->abbrev_parent_hashes.off; return 1; case 'm': /* left/right/bottom */ strbuf_addstr(sb, get_revision_mark(NULL, commit)); diff --git a/prio-queue.c b/prio-queue.c index fc3860fdcb..126d096727 100644 --- a/prio-queue.c +++ b/prio-queue.c @@ -27,10 +27,9 @@ void prio_queue_reverse(struct prio_queue *queue) void clear_prio_queue(struct prio_queue *queue) { - free(queue->array); + FREE_AND_NULL(queue->array); queue->nr = 0; queue->alloc = 0; - queue->array = NULL; queue->insertion_ctr = 0; } @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "run-command.h" #include "strbuf.h" #include "prompt.h" diff --git a/read-cache.c b/read-cache.c index 3339de8124..3c4354ee21 100644 --- a/read-cache.c +++ b/read-cache.c @@ -5,6 +5,7 @@ */ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "config.h" #include "tempfile.h" #include "lockfile.h" #include "cache-tree.h" @@ -1894,8 +1895,7 @@ int discard_index(struct index_state *istate) free_name_hash(istate); cache_tree_free(&(istate->cache_tree)); istate->initialized = 0; - free(istate->cache); - istate->cache = NULL; + FREE_AND_NULL(istate->cache); istate->cache_alloc = 0; discard_split_index(istate); free_untracked_cache(istate->untracked); @@ -2187,9 +2187,10 @@ void update_index_if_able(struct index_state *istate, struct lock_file *lockfile rollback_lock_file(lockfile); } -static int do_write_index(struct index_state *istate, int newfd, +static int do_write_index(struct index_state *istate, struct tempfile *tempfile, int strip_extensions) { + int newfd = tempfile->fd; git_SHA_CTX c; struct cache_header hdr; int i, err, removed, extended, hdr_version; @@ -2301,7 +2302,11 @@ static int do_write_index(struct index_state *istate, int newfd, return -1; } - if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st)) + if (ce_flush(&c, newfd, istate->sha1)) + return -1; + if (close_tempfile(tempfile)) + return error(_("could not close '%s'"), tempfile->filename.buf); + if (stat(tempfile->filename.buf, &st)) return -1; istate->timestamp.sec = (unsigned int)st.st_mtime; istate->timestamp.nsec = ST_MTIME_NSEC(st); @@ -2324,7 +2329,7 @@ static int commit_locked_index(struct 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, get_lock_file_fd(lock), 0); + int ret = do_write_index(istate, &lock->tempfile, 0); if (ret) return ret; assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) != @@ -2421,7 +2426,7 @@ static int write_shared_index(struct index_state *istate, return do_write_locked_index(istate, lock, flags); } move_cache_to_base_index(istate); - ret = do_write_index(si->base, fd, 1); + ret = do_write_index(si->base, &temporary_sharedindex, 1); if (ret) { delete_tempfile(&temporary_sharedindex); return ret; @@ -2604,8 +2609,7 @@ void *read_blob_data_from_index(const struct index_state *istate, void stat_validity_clear(struct stat_validity *sv) { - free(sv->sd); - sv->sd = NULL; + FREE_AND_NULL(sv->sd); } int stat_validity_check(struct stat_validity *sv, const char *path) @@ -2631,3 +2635,9 @@ void stat_validity_update(struct stat_validity *sv, int fd) fill_stat_data(sv->sd, &st); } } + +void move_index_extensions(struct index_state *dst, struct index_state *src) +{ + dst->untracked = src->untracked; + src->untracked = NULL; +} diff --git a/ref-filter.c b/ref-filter.c index 25ca56d62f..72e6cb8ecc 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -93,6 +93,7 @@ static struct used_atom { unsigned int length; } objectname; struct refname_atom refname; + char *head; } u; } *used_atom; static int used_atom_cnt, need_tagged, need_symref; @@ -287,6 +288,12 @@ static void if_atom_parser(struct used_atom *atom, const char *arg) } } +static void head_atom_parser(struct used_atom *atom, const char *arg) +{ + struct object_id unused; + + atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused.hash, NULL); +} static struct { const char *name; @@ -325,7 +332,7 @@ static struct { { "push", FIELD_STR, remote_ref_atom_parser }, { "symref", FIELD_STR, refname_atom_parser }, { "flag" }, - { "HEAD" }, + { "HEAD", FIELD_STR, head_atom_parser }, { "color", FIELD_STR, color_atom_parser }, { "align", FIELD_STR, align_atom_parser }, { "end" }, @@ -1251,13 +1258,17 @@ char *get_head_description(void) state.branch); else if (state.detached_from) { if (state.detached_at) - /* TRANSLATORS: make sure this matches - "HEAD detached at " in wt-status.c */ + /* + * TRANSLATORS: make sure this matches "HEAD + * detached at " in wt-status.c + */ strbuf_addf(&desc, _("(HEAD detached at %s)"), state.detached_from); else - /* TRANSLATORS: make sure this matches - "HEAD detached from " in wt-status.c */ + /* + * TRANSLATORS: make sure this matches "HEAD + * detached from " in wt-status.c + */ strbuf_addf(&desc, _("(HEAD detached from %s)"), state.detached_from); } @@ -1369,12 +1380,7 @@ static void populate_value(struct ref_array_item *ref) } else if (!deref && grab_objectname(name, ref->objectname.hash, v, atom)) { continue; } else if (!strcmp(name, "HEAD")) { - const char *head; - struct object_id oid; - - head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - oid.hash, NULL); - if (head && !strcmp(ref->refname, head)) + if (atom->u.head && !strcmp(ref->refname, atom->u.head)) v->s = "*"; else v->s = " "; @@ -1885,8 +1891,7 @@ void ref_array_clear(struct ref_array *array) for (i = 0; i < array->nr; i++) free_array_item(array->items[i]); - free(array->items); - array->items = NULL; + FREE_AND_NULL(array->items); array->nr = array->alloc = 0; } diff --git a/reflog-walk.c b/reflog-walk.c index 70905011ee..ed99437ad2 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -183,7 +183,11 @@ int add_reflog_for_walk(struct reflog_walk_info *info, if (!reflogs || reflogs->nr == 0) { struct object_id oid; char *b; - if (dwim_log(branch, strlen(branch), oid.hash, &b) == 1) { + int ret = dwim_log(branch, strlen(branch), + oid.hash, &b); + if (ret > 1) + free(b); + else if (ret == 1) { if (reflogs) { free(reflogs->ref); free(reflogs); @@ -193,17 +197,27 @@ int add_reflog_for_walk(struct reflog_walk_info *info, reflogs = read_complete_reflog(branch); } } - if (!reflogs || reflogs->nr == 0) + if (!reflogs || reflogs->nr == 0) { + if (reflogs) { + free(reflogs->ref); + free(reflogs); + } + free(branch); return -1; + } string_list_insert(&info->complete_reflogs, branch)->util = reflogs; } + free(branch); commit_reflog = xcalloc(1, sizeof(struct commit_reflog)); if (recno < 0) { commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp); if (commit_reflog->recno < 0) { - free(branch); + if (reflogs) { + free(reflogs->ref); + free(reflogs); + } free(commit_reflog); return -1; } @@ -3,6 +3,7 @@ */ #include "cache.h" +#include "config.h" #include "hashmap.h" #include "lockfile.h" #include "iterator.h" diff --git a/refs/files-backend.c b/refs/files-backend.c index b040bb3b0a..0404f2c233 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1,4 +1,5 @@ #include "../cache.h" +#include "../config.h" #include "../refs.h" #include "refs-internal.h" #include "ref-cache.h" @@ -2958,8 +2959,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, head_oid.hash, &head_type); if (head_ref && !(head_type & REF_ISSYMREF)) { - free(head_ref); - head_ref = NULL; + FREE_AND_NULL(head_ref); } /* diff --git a/refs/ref-cache.c b/refs/ref-cache.c index af2fcb2c12..76bb723c86 100644 --- a/refs/ref-cache.c +++ b/refs/ref-cache.c @@ -82,9 +82,8 @@ static void clear_ref_dir(struct ref_dir *dir) int i; for (i = 0; i < dir->nr; i++) free_ref_entry(dir->entries[i]); - free(dir->entries); + FREE_AND_NULL(dir->entries); dir->sorted = dir->nr = dir->alloc = 0; - dir->entries = NULL; } struct ref_entry *create_dir_entry(struct ref_cache *cache, diff --git a/remote-curl.c b/remote-curl.c index ece45993da..0053b09549 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "remote.h" #include "strbuf.h" #include "walker.h" diff --git a/remote-testsvn.c b/remote-testsvn.c index f87bf851ba..0ff4a31262 100644 --- a/remote-testsvn.c +++ b/remote-testsvn.c @@ -51,23 +51,22 @@ static void terminate_batch(void) } /* NOTE: 'ref' refers to a git reference, while 'rev' refers to a svn revision. */ -static char *read_ref_note(const unsigned char sha1[20]) +static char *read_ref_note(const struct object_id *oid) { - const unsigned char *note_sha1; + const struct object_id *note_oid; char *msg = NULL; unsigned long msglen; enum object_type type; init_notes(NULL, notes_ref, NULL, 0); - if (!(note_sha1 = get_note(NULL, sha1))) + if (!(note_oid = get_note(NULL, oid))) return NULL; /* note tree not found */ - if (!(msg = read_sha1_file(note_sha1, &type, &msglen))) + if (!(msg = read_sha1_file(note_oid->hash, &type, &msglen))) error("Empty notes tree. %s", notes_ref); else if (!msglen || type != OBJ_BLOB) { error("Note contains unusable content. " "Is something else using this notes tree? %s", notes_ref); - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } free_notes(NULL); return msg; @@ -99,8 +98,8 @@ static int parse_rev_note(const char *msg, struct rev_note *res) return -1; } -static int note2mark_cb(const unsigned char *object_sha1, - const unsigned char *note_sha1, char *note_path, +static int note2mark_cb(const struct object_id *object_oid, + const struct object_id *note_oid, char *note_path, void *cb_data) { FILE *file = (FILE *)cb_data; @@ -109,14 +108,14 @@ static int note2mark_cb(const unsigned char *object_sha1, enum object_type type; struct rev_note note; - if (!(msg = read_sha1_file(note_sha1, &type, &msglen)) || + if (!(msg = read_sha1_file(note_oid->hash, &type, &msglen)) || !msglen || type != OBJ_BLOB) { free(msg); return 1; } if (parse_rev_note(msg, ¬e)) return 2; - if (fprintf(file, ":%d %s\n", note.rev_nr, sha1_to_hex(object_sha1)) < 1) + if (fprintf(file, ":%d %s\n", note.rev_nr, oid_to_hex(object_oid)) < 1) return 3; return 0; } @@ -124,10 +123,8 @@ static int note2mark_cb(const unsigned char *object_sha1, static void regenerate_marks(void) { int ret; - FILE *marksfile = fopen(marksfilename, "w+"); + FILE *marksfile = xfopen(marksfilename, "w+"); - if (!marksfile) - die_errno("Couldn't create mark file %s.", marksfilename); ret = for_each_note(NULL, 0, note2mark_cb, marksfile); if (ret) die("Regeneration of marks failed, returned %d.", ret); @@ -148,9 +145,7 @@ static void check_or_regenerate_marks(int latestrev) marksfile = fopen(marksfilename, "r"); if (!marksfile) { regenerate_marks(); - marksfile = fopen(marksfilename, "r"); - if (!marksfile) - die_errno("cannot read marks file %s!", marksfilename); + marksfile = xfopen(marksfilename, "r"); fclose(marksfile); } else { strbuf_addf(&sb, ":%d ", latestrev); @@ -174,15 +169,15 @@ static int cmd_import(const char *line) int code; int dumpin_fd; char *note_msg; - unsigned char head_sha1[20]; + struct object_id head_oid; unsigned int startrev; struct child_process svndump_proc = CHILD_PROCESS_INIT; const char *command = "svnrdump"; - if (read_ref(private_ref, head_sha1)) + if (read_ref(private_ref, head_oid.hash)) startrev = 0; else { - note_msg = read_ref_note(head_sha1); + note_msg = read_ref_note(&head_oid); if(note_msg == NULL) { warning("No note found for %s.", private_ref); startrev = 0; @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "remote.h" #include "refs.h" #include "commit.h" @@ -251,7 +252,7 @@ static const char *skip_spaces(const char *s) static void read_remotes_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; - FILE *f = fopen(git_path("remotes/%s", remote->name), "r"); + FILE *f = fopen_or_warn(git_path("remotes/%s", remote->name), "r"); if (!f) return; @@ -277,7 +278,7 @@ static void read_branches_file(struct remote *remote) { char *frag; struct strbuf buf = STRBUF_INIT; - FILE *f = fopen(git_path("branches/%s", remote->name), "r"); + FILE *f = fopen_or_warn(git_path("branches/%s", remote->name), "r"); if (!f) return; @@ -477,26 +478,6 @@ static void read_config(void) alias_all_urls(); } -/* - * This function frees a refspec array. - * Warning: code paths should be checked to ensure that the src - * and dst pointers are always freeable pointers as well - * as the refspec pointer itself. - */ -static void free_refspecs(struct refspec *refspec, int nr_refspec) -{ - int i; - - if (!refspec) - return; - - for (i = 0; i < nr_refspec; i++) { - free(refspec[i].src); - free(refspec[i].dst); - } - free(refspec); -} - static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify) { int i; @@ -610,7 +591,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp * since it is only possible to reach this point from within * the for loop above. */ - free_refspecs(rs, i+1); + free_refspec(i+1, rs); return NULL; } die("Invalid refspec '%s'", refspec[i]); @@ -621,7 +602,7 @@ int valid_fetch_refspec(const char *fetch_refspec_str) struct refspec *refspec; refspec = parse_refspec_internal(1, &fetch_refspec_str, 1, 1); - free_refspecs(refspec, 1); + free_refspec(1, refspec); return !!refspec; } @@ -638,6 +619,10 @@ struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) void free_refspec(int nr_refspec, struct refspec *refspec) { int i; + + if (!refspec) + return; + for (i = 0; i < nr_refspec; i++) { free(refspec[i].src); free(refspec[i].dst); @@ -649,7 +634,12 @@ static int valid_remote_nick(const char *name) { if (!name[0] || is_dot_or_dotdot(name)) return 0; - return !strchr(name, '/'); /* no slash */ + + /* remote nicknames cannot contain slashes */ + while (*name) + if (is_dir_sep(*name++)) + return 0; + return 1; } const char *remote_for_branch(struct branch *branch, int *explicit) @@ -1191,9 +1181,10 @@ static int match_explicit(struct ref *src, struct ref *dst, else if (is_null_oid(&matched_src->new_oid)) error("unable to delete '%s': remote ref does not exist", dst_value); - else if ((dst_guess = guess_ref(dst_value, matched_src))) + else if ((dst_guess = guess_ref(dst_value, matched_src))) { matched_dst = make_linked_ref(dst_guess, dst_tail); - else + free(dst_guess); + } else error("unable to push to unqualified destination: %s\n" "The destination refspec neither matches an " "existing ref on the remote nor\n" @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "string-list.h" #include "rerere.h" @@ -39,9 +40,8 @@ static void free_rerere_dirs(void) free(rerere_dir[i]->status); free(rerere_dir[i]); } - free(rerere_dir); + FREE_AND_NULL(rerere_dir); rerere_dir_nr = rerere_dir_alloc = 0; - rerere_dir = NULL; } static void free_rerere_id(struct string_list_item *item) @@ -200,7 +200,7 @@ static struct rerere_id *new_rerere_id(unsigned char *sha1) static void read_rr(struct string_list *rr) { struct strbuf buf = STRBUF_INIT; - FILE *in = fopen(git_path_merge_rr(), "r"); + FILE *in = fopen_or_warn(git_path_merge_rr(), "r"); if (!in) return; @@ -484,13 +484,14 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output io.input = fopen(path, "r"); io.io.wrerror = 0; if (!io.input) - return error("Could not open %s", path); + return error_errno("Could not open %s", path); if (output) { io.io.output = fopen(output, "w"); if (!io.io.output) { + error_errno("Could not write %s", output); fclose(io.input); - return error("Could not write %s", output); + return -1; } } diff --git a/revision.c b/revision.c index 50039c92d6..e181ad1b70 100644 --- a/revision.c +++ b/revision.c @@ -230,7 +230,7 @@ static struct commit *handle_commit(struct rev_info *revs, die("bad tag"); object = parse_object(&tag->tagged->oid); if (!object) { - if (flags & UNINTERESTING) + if (revs->ignore_missing_links || (flags & UNINTERESTING)) return NULL; die("bad object %s", oid_to_hex(&tag->tagged->oid)); } @@ -401,8 +401,8 @@ static int tree_difference = REV_TREE_SAME; static void file_add_remove(struct diff_options *options, int addremove, unsigned mode, - const unsigned char *sha1, - int sha1_valid, + const struct object_id *oid, + int oid_valid, const char *fullpath, unsigned dirty_submodule) { int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD; @@ -414,9 +414,9 @@ static void file_add_remove(struct diff_options *options, static void file_change(struct diff_options *options, unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, - int old_sha1_valid, int new_sha1_valid, + const struct object_id *old_oid, + const struct object_id *new_oid, + int old_oid_valid, int new_oid_valid, const char *fullpath, unsigned old_dirty_submodule, unsigned new_dirty_submodule) { @@ -455,7 +455,7 @@ static int rev_compare_tree(struct rev_info *revs, tree_difference = REV_TREE_SAME; DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES); - if (diff_tree_sha1(t1->object.oid.hash, t2->object.oid.hash, "", + if (diff_tree_oid(&t1->object.oid, &t2->object.oid, "", &revs->pruning) < 0) return REV_TREE_DIFFERENT; return tree_difference; @@ -471,7 +471,7 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit) tree_difference = REV_TREE_SAME; DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES); - retval = diff_tree_sha1(NULL, t1->object.oid.hash, "", &revs->pruning); + retval = diff_tree_oid(NULL, &t1->object.oid, "", &revs->pruning); return retval >= 0 && (tree_difference == REV_TREE_SAME); } @@ -1429,134 +1429,168 @@ static void prepare_show_merge(struct rev_info *revs) revs->limited = 1; } +static int dotdot_missing(const char *arg, char *dotdot, + struct rev_info *revs, int symmetric) +{ + if (revs->ignore_missing) + return 0; + /* de-munge so we report the full argument */ + *dotdot = '.'; + die(symmetric + ? "Invalid symmetric difference expression %s" + : "Invalid revision range %s", arg); +} + +static int handle_dotdot_1(const char *arg, char *dotdot, + struct rev_info *revs, int flags, + int cant_be_filename, + struct object_context *a_oc, + struct object_context *b_oc) +{ + const char *a_name, *b_name; + struct object_id a_oid, b_oid; + struct object *a_obj, *b_obj; + unsigned int a_flags, b_flags; + int symmetric = 0; + unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM); + unsigned int oc_flags = GET_SHA1_COMMITTISH | GET_SHA1_RECORD_PATH; + + a_name = arg; + if (!*a_name) + a_name = "HEAD"; + + b_name = dotdot + 2; + if (*b_name == '.') { + symmetric = 1; + b_name++; + } + if (!*b_name) + b_name = "HEAD"; + + if (get_sha1_with_context(a_name, oc_flags, a_oid.hash, a_oc) || + get_sha1_with_context(b_name, oc_flags, b_oid.hash, b_oc)) + return -1; + + if (!cant_be_filename) { + *dotdot = '.'; + verify_non_filename(revs->prefix, arg); + *dotdot = '\0'; + } + + a_obj = parse_object(&a_oid); + b_obj = parse_object(&b_oid); + if (!a_obj || !b_obj) + return dotdot_missing(arg, dotdot, revs, symmetric); + + if (!symmetric) { + /* just A..B */ + b_flags = flags; + a_flags = flags_exclude; + } else { + /* A...B -- find merge bases between the two */ + struct commit *a, *b; + struct commit_list *exclude; + + a = lookup_commit_reference(&a_obj->oid); + b = lookup_commit_reference(&b_obj->oid); + if (!a || !b) + return dotdot_missing(arg, dotdot, revs, symmetric); + + exclude = get_merge_bases(a, b); + add_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE, + flags_exclude); + add_pending_commit_list(revs, exclude, flags_exclude); + free_commit_list(exclude); + + b_flags = flags; + a_flags = flags | SYMMETRIC_LEFT; + } + + a_obj->flags |= a_flags; + b_obj->flags |= b_flags; + add_rev_cmdline(revs, a_obj, a_name, REV_CMD_LEFT, a_flags); + add_rev_cmdline(revs, b_obj, b_name, REV_CMD_RIGHT, b_flags); + add_pending_object_with_path(revs, a_obj, a_name, a_oc->mode, a_oc->path); + add_pending_object_with_path(revs, b_obj, b_name, b_oc->mode, b_oc->path); + return 0; +} + +static int handle_dotdot(const char *arg, + struct rev_info *revs, int flags, + int cant_be_filename) +{ + struct object_context a_oc, b_oc; + char *dotdot = strstr(arg, ".."); + int ret; + + if (!dotdot) + return -1; + + memset(&a_oc, 0, sizeof(a_oc)); + memset(&b_oc, 0, sizeof(b_oc)); + + *dotdot = '\0'; + ret = handle_dotdot_1(arg, dotdot, revs, flags, cant_be_filename, + &a_oc, &b_oc); + *dotdot = '.'; + + free(a_oc.path); + free(b_oc.path); + + return ret; +} + int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt) { struct object_context oc; - char *dotdot; + char *mark; struct object *object; struct object_id oid; int local_flags; const char *arg = arg_; int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME; - unsigned get_sha1_flags = 0; + unsigned get_sha1_flags = GET_SHA1_RECORD_PATH; flags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM; - dotdot = strstr(arg, ".."); - if (dotdot) { - struct object_id from_oid; - const char *next = dotdot + 2; - const char *this = arg; - int symmetric = *next == '.'; - unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM); - static const char head_by_default[] = "HEAD"; - unsigned int a_flags; - - *dotdot = 0; - next += symmetric; - - if (!*next) - next = head_by_default; - if (dotdot == arg) - this = head_by_default; - if (this == head_by_default && next == head_by_default && - !symmetric) { - /* - * Just ".."? That is not a range but the - * pathspec for the parent directory. - */ - if (!cant_be_filename) { - *dotdot = '.'; - return -1; - } - } - if (!get_sha1_committish(this, from_oid.hash) && - !get_sha1_committish(next, oid.hash)) { - struct object *a_obj, *b_obj; - - if (!cant_be_filename) { - *dotdot = '.'; - verify_non_filename(revs->prefix, arg); - } - - a_obj = parse_object(&from_oid); - b_obj = parse_object(&oid); - if (!a_obj || !b_obj) { - missing: - if (revs->ignore_missing) - return 0; - die(symmetric - ? "Invalid symmetric difference expression %s" - : "Invalid revision range %s", arg); - } - - if (!symmetric) { - /* just A..B */ - a_flags = flags_exclude; - } else { - /* A...B -- find merge bases between the two */ - struct commit *a, *b; - struct commit_list *exclude; - - a = (a_obj->type == OBJ_COMMIT - ? (struct commit *)a_obj - : lookup_commit_reference(&a_obj->oid)); - b = (b_obj->type == OBJ_COMMIT - ? (struct commit *)b_obj - : lookup_commit_reference(&b_obj->oid)); - if (!a || !b) - goto missing; - exclude = get_merge_bases(a, b); - add_rev_cmdline_list(revs, exclude, - REV_CMD_MERGE_BASE, - flags_exclude); - add_pending_commit_list(revs, exclude, - flags_exclude); - free_commit_list(exclude); - - a_flags = flags | SYMMETRIC_LEFT; - } - - a_obj->flags |= a_flags; - b_obj->flags |= flags; - add_rev_cmdline(revs, a_obj, this, - REV_CMD_LEFT, a_flags); - add_rev_cmdline(revs, b_obj, next, - REV_CMD_RIGHT, flags); - add_pending_object(revs, a_obj, this); - add_pending_object(revs, b_obj, next); - return 0; - } - *dotdot = '.'; + if (!cant_be_filename && !strcmp(arg, "..")) { + /* + * Just ".."? That is not a range but the + * pathspec for the parent directory. + */ + return -1; } - dotdot = strstr(arg, "^@"); - if (dotdot && !dotdot[2]) { - *dotdot = 0; + if (!handle_dotdot(arg, revs, flags, revarg_opt)) + return 0; + + mark = strstr(arg, "^@"); + if (mark && !mark[2]) { + *mark = 0; if (add_parents_only(revs, arg, flags, 0)) return 0; - *dotdot = '^'; + *mark = '^'; } - dotdot = strstr(arg, "^!"); - if (dotdot && !dotdot[2]) { - *dotdot = 0; + mark = strstr(arg, "^!"); + if (mark && !mark[2]) { + *mark = 0; if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0)) - *dotdot = '^'; + *mark = '^'; } - dotdot = strstr(arg, "^-"); - if (dotdot) { + mark = strstr(arg, "^-"); + if (mark) { int exclude_parent = 1; - if (dotdot[2]) { + if (mark[2]) { char *end; - exclude_parent = strtoul(dotdot + 2, &end, 10); + exclude_parent = strtoul(mark + 2, &end, 10); if (*end != '\0' || !exclude_parent) return -1; } - *dotdot = 0; + *mark = 0; if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent)) - *dotdot = '^'; + *mark = '^'; } local_flags = 0; @@ -1566,7 +1600,7 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi } if (revarg_opt & REVARG_COMMITTISH) - get_sha1_flags = GET_SHA1_COMMITTISH; + get_sha1_flags |= GET_SHA1_COMMITTISH; if (get_sha1_with_context(arg, get_sha1_flags, oid.hash, &oc)) return revs->ignore_missing ? 0 : -1; @@ -1574,7 +1608,8 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, &oid, flags ^ local_flags); add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags); - add_pending_object_with_mode(revs, object, arg, oc.mode); + add_pending_object_with_path(revs, object, arg, oc.mode, oc.path); + free(oc.path); return 0; } @@ -1690,8 +1725,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->max_count = atoi(argv[1]); revs->no_walk = 0; return 2; - } else if (starts_with(arg, "-n")) { - revs->max_count = atoi(arg + 2); + } else if (skip_prefix(arg, "-n", &optarg)) { + revs->max_count = atoi(optarg); revs->no_walk = 0; } else if ((argcount = parse_long_opt("max-age", argv, &optarg))) { revs->max_age = atoi(optarg); @@ -1750,16 +1785,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--author-date-order")) { revs->sort_order = REV_SORT_BY_AUTHOR_DATE; revs->topo_order = 1; - } else if (starts_with(arg, "--early-output")) { - int count = 100; - switch (arg[14]) { - case '=': - count = atoi(arg+15); - /* Fallthrough */ - case 0: - revs->topo_order = 1; - revs->early_output = count; - } + } else if (!strcmp(arg, "--early-output")) { + revs->early_output = 100; + revs->topo_order = 1; + } else if (skip_prefix(arg, "--early-output=", &optarg)) { + if (strtoul_ui(optarg, 10, &revs->early_output) < 0) + die("'%s': not a non-negative integer", optarg); + revs->topo_order = 1; } else if (!strcmp(arg, "--parents")) { revs->rewrite_parents = 1; revs->print_parents = 1; @@ -1775,13 +1807,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->min_parents = 2; } else if (!strcmp(arg, "--no-merges")) { revs->max_parents = 1; - } else if (starts_with(arg, "--min-parents=")) { - revs->min_parents = atoi(arg+14); - } else if (starts_with(arg, "--no-min-parents")) { + } else if (skip_prefix(arg, "--min-parents=", &optarg)) { + revs->min_parents = atoi(optarg); + } else if (!strcmp(arg, "--no-min-parents")) { revs->min_parents = 0; - } else if (starts_with(arg, "--max-parents=")) { - revs->max_parents = atoi(arg+14); - } else if (starts_with(arg, "--no-max-parents")) { + } else if (skip_prefix(arg, "--max-parents=", &optarg)) { + revs->max_parents = atoi(optarg); + } else if (!strcmp(arg, "--no-max-parents")) { revs->max_parents = -1; } else if (!strcmp(arg, "--boundary")) { revs->boundary = 1; @@ -1862,14 +1894,15 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->verbose_header = 1; revs->pretty_given = 1; get_commit_format(NULL, revs); - } else if (starts_with(arg, "--pretty=") || starts_with(arg, "--format=")) { + } else if (skip_prefix(arg, "--pretty=", &optarg) || + skip_prefix(arg, "--format=", &optarg)) { /* * Detached form ("--pretty X" as opposed to "--pretty=X") * not allowed, since the argument is optional. */ revs->verbose_header = 1; revs->pretty_given = 1; - get_commit_format(arg+9, revs); + get_commit_format(optarg, revs); } else if (!strcmp(arg, "--expand-tabs")) { revs->expand_tabs_in_log = 8; } else if (!strcmp(arg, "--no-expand-tabs")) { @@ -1887,26 +1920,23 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->show_signature = 1; } else if (!strcmp(arg, "--no-show-signature")) { revs->show_signature = 0; - } else if (!strcmp(arg, "--show-linear-break") || - starts_with(arg, "--show-linear-break=")) { - if (starts_with(arg, "--show-linear-break=")) - revs->break_bar = xstrdup(arg + 20); - else - revs->break_bar = " .........."; + } else if (!strcmp(arg, "--show-linear-break")) { + revs->break_bar = " .........."; + revs->track_linear = 1; + revs->track_first_time = 1; + } else if (skip_prefix(arg, "--show-linear-break=", &optarg)) { + revs->break_bar = xstrdup(optarg); revs->track_linear = 1; revs->track_first_time = 1; - } else if (starts_with(arg, "--show-notes=") || - starts_with(arg, "--notes=")) { + } else if (skip_prefix(arg, "--show-notes=", &optarg) || + skip_prefix(arg, "--notes=", &optarg)) { struct strbuf buf = STRBUF_INIT; revs->show_notes = 1; revs->show_notes_given = 1; - if (starts_with(arg, "--show-notes")) { - if (revs->notes_opt.use_default_notes < 0) - revs->notes_opt.use_default_notes = 1; - strbuf_addstr(&buf, arg+13); - } - else - strbuf_addstr(&buf, arg+8); + if (starts_with(arg, "--show-notes=") && + revs->notes_opt.use_default_notes < 0) + revs->notes_opt.use_default_notes = 1; + strbuf_addstr(&buf, optarg); expand_notes_ref(&buf); string_list_append(&revs->notes_opt.extra_notes_refs, strbuf_detach(&buf, NULL)); @@ -1943,8 +1973,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->abbrev = 0; } else if (!strcmp(arg, "--abbrev")) { revs->abbrev = DEFAULT_ABBREV; - } else if (starts_with(arg, "--abbrev=")) { - revs->abbrev = strtoul(arg + 9, NULL, 10); + } else if (skip_prefix(arg, "--abbrev=", &optarg)) { + revs->abbrev = strtoul(optarg, NULL, 10); if (revs->abbrev < MINIMUM_ABBREV) revs->abbrev = MINIMUM_ABBREV; else if (revs->abbrev > 40) @@ -1991,11 +2021,12 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) { revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_ERE; } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) { + revs->grep_filter.ignore_case = 1; revs->grep_filter.regflags |= REG_ICASE; DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE); } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) { revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_FIXED; - } else if (!strcmp(arg, "--perl-regexp")) { + } else if (!strcmp(arg, "--perl-regexp") || !strcmp(arg, "-P")) { revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_PCRE; } else if (!strcmp(arg, "--all-match")) { revs->grep_filter.all_match = 1; @@ -2104,20 +2135,20 @@ static int handle_revision_pseudo_opt(const char *submodule, } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) { add_ref_exclusion(&revs->ref_excludes, optarg); return argcount; - } else if (starts_with(arg, "--branches=")) { + } else if (skip_prefix(arg, "--branches=", &optarg)) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); - for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb); + for_each_glob_ref_in(handle_one_ref, optarg, "refs/heads/", &cb); clear_ref_exclusion(&revs->ref_excludes); - } else if (starts_with(arg, "--tags=")) { + } else if (skip_prefix(arg, "--tags=", &optarg)) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); - for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb); + for_each_glob_ref_in(handle_one_ref, optarg, "refs/tags/", &cb); clear_ref_exclusion(&revs->ref_excludes); - } else if (starts_with(arg, "--remotes=")) { + } else if (skip_prefix(arg, "--remotes=", &optarg)) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); - for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb); + for_each_glob_ref_in(handle_one_ref, optarg, "refs/remotes/", &cb); clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--reflog")) { add_reflogs_to_pending(revs, *flags); @@ -2127,14 +2158,14 @@ static int handle_revision_pseudo_opt(const char *submodule, *flags ^= UNINTERESTING | BOTTOM; } else if (!strcmp(arg, "--no-walk")) { revs->no_walk = REVISION_WALK_NO_WALK_SORTED; - } else if (starts_with(arg, "--no-walk=")) { + } else if (skip_prefix(arg, "--no-walk=", &optarg)) { /* * Detached form ("--no-walk X" as opposed to "--no-walk=X") * not allowed, since the argument is optional. */ - if (!strcmp(arg + 10, "sorted")) + if (!strcmp(optarg, "sorted")) revs->no_walk = REVISION_WALK_NO_WALK_SORTED; - else if (!strcmp(arg + 10, "unsorted")) + else if (!strcmp(optarg, "unsorted")) revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED; else return error("invalid argument to --no-walk"); @@ -2908,7 +2939,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt) if (opt->show_notes) { if (!buf.len) strbuf_addstr(&buf, message); - format_display_notes(commit->object.oid.hash, &buf, encoding, 1); + format_display_notes(&commit->object.oid, &buf, encoding, 1); } /* diff --git a/revision.h b/revision.h index a91dd3d5d9..f96e7f7f49 100644 --- a/revision.h +++ b/revision.h @@ -74,8 +74,9 @@ struct rev_info { /* topo-sort */ enum rev_sort_order sort_order; - unsigned int early_output:1, - ignore_missing:1, + unsigned int early_output; + + unsigned int ignore_missing:1, ignore_missing_links:1; /* Traversal flags */ diff --git a/run-command.c b/run-command.c index 574b81d3e8..9e36151bf9 100644 --- a/run-command.c +++ b/run-command.c @@ -117,18 +117,65 @@ static inline void close_pair(int fd[2]) close(fd[1]); } -#ifndef GIT_WINDOWS_NATIVE -static inline void dup_devnull(int to) +int is_executable(const char *name) { - int fd = open("/dev/null", O_RDWR); - if (fd < 0) - die_errno(_("open /dev/null failed")); - if (dup2(fd, to) < 0) - die_errno(_("dup2(%d,%d) failed"), fd, to); - close(fd); + struct stat st; + + if (stat(name, &st) || /* stat, not lstat */ + !S_ISREG(st.st_mode)) + return 0; + +#if defined(GIT_WINDOWS_NATIVE) + /* + * On Windows there is no executable bit. The file extension + * indicates whether it can be run as an executable, and Git + * has special-handling to detect scripts and launch them + * through the indicated script interpreter. We test for the + * file extension first because virus scanners may make + * it quite expensive to open many files. + */ + if (ends_with(name, ".exe")) + return S_IXUSR; + +{ + /* + * Now that we know it does not have an executable extension, + * peek into the file instead. + */ + char buf[3] = { 0 }; + int n; + int fd = open(name, O_RDONLY); + st.st_mode &= ~S_IXUSR; + if (fd >= 0) { + n = read(fd, buf, 2); + if (n == 2) + /* look for a she-bang */ + if (!strcmp(buf, "#!")) + st.st_mode |= S_IXUSR; + close(fd); + } } #endif + return st.st_mode & S_IXUSR; +} +/* + * Search $PATH for a command. This emulates the path search that + * execvp would perform, without actually executing the command so it + * can be used before fork() to prepare to run a command using + * execve() or after execvp() to diagnose why it failed. + * + * The caller should ensure that file contains no directory + * separators. + * + * Returns the path to the command, as found in $PATH or NULL if the + * command could not be found. The caller inherits ownership of the memory + * used to store the resultant path. + * + * This should not be used on Windows, where the $PATH search rules + * are more complicated (e.g., a search for "foo" should find + * "foo.exe"). + */ static char *locate_in_PATH(const char *file) { const char *p = getenv("PATH"); @@ -149,7 +196,7 @@ static char *locate_in_PATH(const char *file) } strbuf_addstr(&buf, file); - if (!access(buf.buf, F_OK)) + if (is_executable(buf.buf)) return strbuf_detach(&buf, NULL); if (!*end) @@ -221,31 +268,248 @@ static const char **prepare_shell_cmd(struct argv_array *out, const char **argv) } #ifndef GIT_WINDOWS_NATIVE -static int execv_shell_cmd(const char **argv) +static int child_notifier = -1; + +enum child_errcode { + CHILD_ERR_CHDIR, + CHILD_ERR_DUP2, + CHILD_ERR_CLOSE, + CHILD_ERR_SIGPROCMASK, + CHILD_ERR_ENOENT, + CHILD_ERR_SILENT, + CHILD_ERR_ERRNO +}; + +struct child_err { + enum child_errcode err; + int syserr; /* errno */ +}; + +static void child_die(enum child_errcode err) { - struct argv_array nargv = ARGV_ARRAY_INIT; - prepare_shell_cmd(&nargv, argv); - trace_argv_printf(nargv.argv, "trace: exec:"); - sane_execvp(nargv.argv[0], (char **)nargv.argv); - argv_array_clear(&nargv); - return -1; + struct child_err buf; + + buf.err = err; + buf.syserr = errno; + + /* write(2) on buf smaller than PIPE_BUF (min 512) is atomic: */ + xwrite(child_notifier, &buf, sizeof(buf)); + _exit(1); } -#endif -#ifndef GIT_WINDOWS_NATIVE -static int child_notifier = -1; +static void child_dup2(int fd, int to) +{ + if (dup2(fd, to) < 0) + child_die(CHILD_ERR_DUP2); +} -static void notify_parent(void) +static void child_close(int fd) { + if (close(fd)) + child_die(CHILD_ERR_CLOSE); +} + +static void child_close_pair(int fd[2]) +{ + child_close(fd[0]); + child_close(fd[1]); +} + +/* + * parent will make it look like the child spewed a fatal error and died + * this is needed to prevent changes to t0061. + */ +static void fake_fatal(const char *err, va_list params) +{ + vreportf("fatal: ", err, params); +} + +static void child_error_fn(const char *err, va_list params) +{ + const char msg[] = "error() should not be called in child\n"; + xwrite(2, msg, sizeof(msg) - 1); +} + +static void child_warn_fn(const char *err, va_list params) +{ + const char msg[] = "warn() should not be called in child\n"; + xwrite(2, msg, sizeof(msg) - 1); +} + +static void NORETURN child_die_fn(const char *err, va_list params) +{ + const char msg[] = "die() should not be called in child\n"; + xwrite(2, msg, sizeof(msg) - 1); + _exit(2); +} + +/* this runs in the parent process */ +static void child_err_spew(struct child_process *cmd, struct child_err *cerr) +{ + static void (*old_errfn)(const char *err, va_list params); + + old_errfn = get_error_routine(); + set_error_routine(fake_fatal); + errno = cerr->syserr; + + switch (cerr->err) { + case CHILD_ERR_CHDIR: + error_errno("exec '%s': cd to '%s' failed", + cmd->argv[0], cmd->dir); + break; + case CHILD_ERR_DUP2: + error_errno("dup2() in child failed"); + break; + case CHILD_ERR_CLOSE: + error_errno("close() in child failed"); + break; + case CHILD_ERR_SIGPROCMASK: + error_errno("sigprocmask failed restoring signals"); + break; + case CHILD_ERR_ENOENT: + error_errno("cannot run %s", cmd->argv[0]); + break; + case CHILD_ERR_SILENT: + break; + case CHILD_ERR_ERRNO: + error_errno("cannot exec '%s'", cmd->argv[0]); + break; + } + set_error_routine(old_errfn); +} + +static void prepare_cmd(struct argv_array *out, const struct child_process *cmd) +{ + if (!cmd->argv[0]) + die("BUG: command is empty"); + + /* + * Add SHELL_PATH so in the event exec fails with ENOEXEC we can + * attempt to interpret the command with 'sh'. + */ + argv_array_push(out, SHELL_PATH); + + if (cmd->git_cmd) { + argv_array_push(out, "git"); + argv_array_pushv(out, cmd->argv); + } else if (cmd->use_shell) { + prepare_shell_cmd(out, cmd->argv); + } else { + argv_array_pushv(out, cmd->argv); + } + /* - * execvp failed. If possible, we'd like to let start_command - * know, so failures like ENOENT can be handled right away; but - * otherwise, finish_command will still report the error. + * If there are no '/' characters in the command then perform a path + * lookup and use the resolved path as the command to exec. If there + * are no '/' characters or if the command wasn't found in the path, + * have exec attempt to invoke the command directly. */ - xwrite(child_notifier, "", 1); + if (!strchr(out->argv[1], '/')) { + char *program = locate_in_PATH(out->argv[1]); + if (program) { + free((char *)out->argv[1]); + out->argv[1] = program; + } + } +} + +static char **prep_childenv(const char *const *deltaenv) +{ + extern char **environ; + char **childenv; + struct string_list env = STRING_LIST_INIT_DUP; + struct strbuf key = STRBUF_INIT; + const char *const *p; + int i; + + /* Construct a sorted string list consisting of the current environ */ + for (p = (const char *const *) environ; p && *p; p++) { + const char *equals = strchr(*p, '='); + + if (equals) { + strbuf_reset(&key); + strbuf_add(&key, *p, equals - *p); + string_list_append(&env, key.buf)->util = (void *) *p; + } else { + string_list_append(&env, *p)->util = (void *) *p; + } + } + string_list_sort(&env); + + /* Merge in 'deltaenv' with the current environ */ + for (p = deltaenv; p && *p; p++) { + const char *equals = strchr(*p, '='); + + if (equals) { + /* ('key=value'), insert or replace entry */ + strbuf_reset(&key); + strbuf_add(&key, *p, equals - *p); + string_list_insert(&env, key.buf)->util = (void *) *p; + } else { + /* otherwise ('key') remove existing entry */ + string_list_remove(&env, *p, 0); + } + } + + /* Create an array of 'char *' to be used as the childenv */ + childenv = xmalloc((env.nr + 1) * sizeof(char *)); + for (i = 0; i < env.nr; i++) + childenv[i] = env.items[i].util; + childenv[env.nr] = NULL; + + string_list_clear(&env, 0); + strbuf_release(&key); + return childenv; +} + +struct atfork_state { +#ifndef NO_PTHREADS + int cs; +#endif + sigset_t old; +}; + +#ifndef NO_PTHREADS +static void bug_die(int err, const char *msg) +{ + if (err) { + errno = err; + die_errno("BUG: %s", msg); + } } #endif +static void atfork_prepare(struct atfork_state *as) +{ + sigset_t all; + + if (sigfillset(&all)) + die_errno("sigfillset"); +#ifdef NO_PTHREADS + if (sigprocmask(SIG_SETMASK, &all, &as->old)) + die_errno("sigprocmask"); +#else + bug_die(pthread_sigmask(SIG_SETMASK, &all, &as->old), + "blocking all signals"); + bug_die(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &as->cs), + "disabling cancellation"); +#endif +} + +static void atfork_parent(struct atfork_state *as) +{ +#ifdef NO_PTHREADS + if (sigprocmask(SIG_SETMASK, &as->old, NULL)) + die_errno("sigprocmask"); +#else + bug_die(pthread_setcancelstate(as->cs, NULL), + "re-enabling cancellation"); + bug_die(pthread_sigmask(SIG_SETMASK, &as->old, NULL), + "restoring signal mask"); +#endif +} +#endif /* GIT_WINDOWS_NATIVE */ + static inline void set_cloexec(int fd) { int flags = fcntl(fd, F_GETFD); @@ -281,13 +545,6 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal) code += 128; } else if (WIFEXITED(status)) { code = WEXITSTATUS(status); - /* - * Convert special exit code when execvp failed. - */ - if (code == 127) { - code = -1; - failed_errno = ENOENT; - } } else { error("waitpid is confused (%s)", argv0); } @@ -372,109 +629,149 @@ fail_pipe: #ifndef GIT_WINDOWS_NATIVE { int notify_pipe[2]; + int null_fd = -1; + char **childenv; + struct argv_array argv = ARGV_ARRAY_INIT; + struct child_err cerr; + struct atfork_state as; + if (pipe(notify_pipe)) notify_pipe[0] = notify_pipe[1] = -1; + if (cmd->no_stdin || cmd->no_stdout || cmd->no_stderr) { + null_fd = open("/dev/null", O_RDWR | O_CLOEXEC); + if (null_fd < 0) + die_errno(_("open /dev/null failed")); + set_cloexec(null_fd); + } + + prepare_cmd(&argv, cmd); + childenv = prep_childenv(cmd->env); + atfork_prepare(&as); + + /* + * NOTE: In order to prevent deadlocking when using threads special + * care should be taken with the function calls made in between the + * fork() and exec() calls. No calls should be made to functions which + * require acquiring a lock (e.g. malloc) as the lock could have been + * held by another thread at the time of forking, causing the lock to + * never be released in the child process. This means only + * Async-Signal-Safe functions are permitted in the child. + */ cmd->pid = fork(); failed_errno = errno; if (!cmd->pid) { + int sig; /* - * Redirect the channel to write syscall error messages to - * before redirecting the process's stderr so that all die() - * in subsequent call paths use the parent's stderr. + * Ensure the default die/error/warn routines do not get + * called, they can take stdio locks and malloc. */ - if (cmd->no_stderr || need_err) { - int child_err = dup(2); - set_cloexec(child_err); - set_error_handle(fdopen(child_err, "w")); - } + set_die_routine(child_die_fn); + set_error_routine(child_error_fn); + set_warn_routine(child_warn_fn); close(notify_pipe[0]); set_cloexec(notify_pipe[1]); child_notifier = notify_pipe[1]; - atexit(notify_parent); if (cmd->no_stdin) - dup_devnull(0); + child_dup2(null_fd, 0); else if (need_in) { - dup2(fdin[0], 0); - close_pair(fdin); + child_dup2(fdin[0], 0); + child_close_pair(fdin); } else if (cmd->in) { - dup2(cmd->in, 0); - close(cmd->in); + child_dup2(cmd->in, 0); + child_close(cmd->in); } if (cmd->no_stderr) - dup_devnull(2); + child_dup2(null_fd, 2); else if (need_err) { - dup2(fderr[1], 2); - close_pair(fderr); + child_dup2(fderr[1], 2); + child_close_pair(fderr); } else if (cmd->err > 1) { - dup2(cmd->err, 2); - close(cmd->err); + child_dup2(cmd->err, 2); + child_close(cmd->err); } if (cmd->no_stdout) - dup_devnull(1); + child_dup2(null_fd, 1); else if (cmd->stdout_to_stderr) - dup2(2, 1); + child_dup2(2, 1); else if (need_out) { - dup2(fdout[1], 1); - close_pair(fdout); + child_dup2(fdout[1], 1); + child_close_pair(fdout); } else if (cmd->out > 1) { - dup2(cmd->out, 1); - close(cmd->out); + child_dup2(cmd->out, 1); + child_close(cmd->out); } if (cmd->dir && chdir(cmd->dir)) - die_errno("exec '%s': cd to '%s' failed", cmd->argv[0], - cmd->dir); - if (cmd->env) { - for (; *cmd->env; cmd->env++) { - if (strchr(*cmd->env, '=')) - putenv((char *)*cmd->env); - else - unsetenv(*cmd->env); - } + child_die(CHILD_ERR_CHDIR); + + /* + * restore default signal handlers here, in case + * we catch a signal right before execve below + */ + for (sig = 1; sig < NSIG; sig++) { + /* ignored signals get reset to SIG_DFL on execve */ + if (signal(sig, SIG_DFL) == SIG_IGN) + signal(sig, SIG_IGN); } - if (cmd->git_cmd) - execv_git_cmd(cmd->argv); - else if (cmd->use_shell) - execv_shell_cmd(cmd->argv); - else - sane_execvp(cmd->argv[0], (char *const*) cmd->argv); + + if (sigprocmask(SIG_SETMASK, &as.old, NULL) != 0) + child_die(CHILD_ERR_SIGPROCMASK); + + /* + * Attempt to exec using the command and arguments starting at + * argv.argv[1]. argv.argv[0] contains SHELL_PATH which will + * be used in the event exec failed with ENOEXEC at which point + * we will try to interpret the command using 'sh'. + */ + execve(argv.argv[1], (char *const *) argv.argv + 1, + (char *const *) childenv); + if (errno == ENOEXEC) + execve(argv.argv[0], (char *const *) argv.argv, + (char *const *) childenv); + if (errno == ENOENT) { - if (!cmd->silent_exec_failure) - error("cannot run %s: %s", cmd->argv[0], - strerror(ENOENT)); - exit(127); + if (cmd->silent_exec_failure) + child_die(CHILD_ERR_SILENT); + child_die(CHILD_ERR_ENOENT); } else { - die_errno("cannot exec '%s'", cmd->argv[0]); + child_die(CHILD_ERR_ERRNO); } } + atfork_parent(&as); if (cmd->pid < 0) error_errno("cannot fork() for %s", cmd->argv[0]); else if (cmd->clean_on_exit) mark_child_for_cleanup(cmd->pid, cmd); /* - * Wait for child's execvp. If the execvp succeeds (or if fork() + * Wait for child's exec. If the exec succeeds (or if fork() * failed), EOF is seen immediately by the parent. Otherwise, the - * child process sends a single byte. + * child process sends a child_err struct. * Note that use of this infrastructure is completely advisory, * therefore, we keep error checks minimal. */ close(notify_pipe[1]); - if (read(notify_pipe[0], ¬ify_pipe[1], 1) == 1) { + if (xread(notify_pipe[0], &cerr, sizeof(cerr)) == sizeof(cerr)) { /* - * At this point we know that fork() succeeded, but execvp() + * At this point we know that fork() succeeded, but exec() * failed. Errors have been reported to our stderr. */ wait_or_whine(cmd->pid, cmd->argv[0], 0); + child_err_spew(cmd, &cerr); failed_errno = errno; cmd->pid = -1; } close(notify_pipe[0]); + + if (null_fd >= 0) + close(null_fd); + argv_array_clear(&argv); + free(childenv); } #else { diff --git a/run-command.h b/run-command.h index 4fa8f65adb..3932420ec8 100644 --- a/run-command.h +++ b/run-command.h @@ -51,6 +51,7 @@ struct child_process { #define CHILD_PROCESS_INIT { NULL, ARGV_ARRAY_INIT, ARGV_ARRAY_INIT } void child_process_init(struct child_process *); void child_process_clear(struct child_process *); +extern int is_executable(const char *name); int start_command(struct child_process *); int finish_command(struct child_process *); diff --git a/send-pack.c b/send-pack.c index 78bb34ebec..ed3cee3211 100644 --- a/send-pack.c +++ b/send-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "pkt-line.h" diff --git a/sequencer.c b/sequencer.c index 9dfedbc243..98cdfe74fd 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "sequencer.h" #include "dir.h" @@ -464,7 +465,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - /* TRANSLATORS: %s will be "revert", "cherry-pick" or + /* + * TRANSLATORS: %s will be "revert", "cherry-pick" or * "rebase -i". */ return error(_("%s: Unable to write new index file"), @@ -898,8 +900,8 @@ static void flush_rewritten_pending(void) { FILE *out; if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 && - !get_sha1("HEAD", newsha1) && - (out = fopen(rebase_path_rewritten_list(), "a"))) { + !get_sha1("HEAD", newsha1) && + (out = fopen_or_warn(rebase_path_rewritten_list(), "a"))) { char *bol = buf.buf, *eol; while (*bol) { @@ -918,7 +920,7 @@ static void flush_rewritten_pending(void) { static void record_in_rewritten(struct object_id *oid, enum todo_command next_command) { - FILE *out = fopen(rebase_path_rewritten_pending(), "a"); + FILE *out = fopen_or_warn(rebase_path_rewritten_pending(), "a"); if (!out) return; @@ -1210,8 +1212,7 @@ struct todo_list { static void todo_list_release(struct todo_list *todo_list) { strbuf_release(&todo_list->buf); - free(todo_list->items); - todo_list->items = NULL; + FREE_AND_NULL(todo_list->items); todo_list->nr = todo_list->alloc = 0; } @@ -1380,7 +1381,7 @@ static int read_populate_todo(struct todo_list *todo_list, if (is_rebase_i(opts)) { struct todo_list done = TODO_LIST_INIT; - FILE *f = fopen(rebase_path_msgtotal(), "w"); + FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && !parse_insn_buffer(done.buf.buf, &done)) @@ -1915,11 +1916,13 @@ static int apply_autostash(struct replay_opts *opts) strbuf_trim(&stash_sha1); child.git_cmd = 1; + child.no_stdout = 1; + child.no_stderr = 1; argv_array_push(&child.args, "stash"); argv_array_push(&child.args, "apply"); argv_array_push(&child.args, stash_sha1.buf); if (!run_command(&child)) - printf(_("Applied autostash.")); + printf(_("Applied autostash.\n")); else { struct child_process store = CHILD_PROCESS_INIT; @@ -2090,6 +2093,7 @@ cleanup_head_ref: res = error(_("could not read orig-head")); goto cleanup_head_ref; } + strbuf_reset(&buf); if (!read_oneliner(&buf, rebase_path_onto(), 0)) { res = error(_("could not read 'onto'")); goto cleanup_head_ref; @@ -2126,8 +2130,8 @@ cleanup_head_ref: if (read_oneliner(&buf, rebase_path_orig_head(), 0) && !get_sha1(buf.buf, orig.hash) && !get_sha1("HEAD", head.hash)) { - diff_tree_sha1(orig.hash, head.hash, - "", &log_tree_opt.diffopt); + diff_tree_oid(&orig, &head, "", + &log_tree_opt.diffopt); log_tree_diff_flush(&log_tree_opt); } } diff --git a/server-info.c b/server-info.c index 6f865b73a3..5ec5b1d827 100644 --- a/server-info.c +++ b/server-info.c @@ -133,7 +133,7 @@ static int read_pack_info_file(const char *infofile) char line[1000]; int old_cnt = 0; - fp = fopen(infofile, "r"); + fp = fopen_or_warn(infofile, "r"); if (!fp) return 1; /* nonexistent is not an error. */ @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "dir.h" #include "string-list.h" @@ -134,23 +135,27 @@ int path_inside_repo(const char *prefix, const char *path) int check_filename(const char *prefix, const char *arg) { - const char *name; char *to_free = NULL; struct stat st; - if (starts_with(arg, ":/")) { - if (arg[2] == '\0') /* ":/" is root dir, always exists */ + if (skip_prefix(arg, ":/", &arg)) { + if (!*arg) /* ":/" is root dir, always exists */ return 1; - name = arg + 2; - } else if (prefix) - name = to_free = prefix_filename(prefix, arg); - else - name = arg; - if (!lstat(name, &st)) { + prefix = NULL; + } else if (skip_prefix(arg, ":!", &arg) || + skip_prefix(arg, ":^", &arg)) { + if (!*arg) /* excluding everything is silly, but allowed */ + return 1; + } + + if (prefix) + arg = to_free = prefix_filename(prefix, arg); + + if (!lstat(arg, &st)) { free(to_free); return 1; /* file exists */ } - if (errno == ENOENT || errno == ENOTDIR) { + if (is_missing_file_error(errno)) { free(to_free); return 0; /* file does not exist */ } @@ -182,6 +187,24 @@ static void NORETURN die_verify_filename(const char *prefix, } /* + * Check for arguments that don't resolve as actual files, + * but which look sufficiently like pathspecs that we'll consider + * them such for the purposes of rev/pathspec DWIM parsing. + */ +static int looks_like_pathspec(const char *arg) +{ + /* anything with a wildcard character */ + if (!no_wildcard(arg)) + return 1; + + /* long-form pathspec magic */ + if (starts_with(arg, ":(")) + return 1; + + return 0; +} + +/* * Verify a filename that we got as an argument for a pathspec * entry. Note that a filename that begins with "-" never verifies * as true, because even if such a filename were to exist, we want @@ -207,7 +230,7 @@ void verify_filename(const char *prefix, { if (*arg == '-') die("bad flag '%s' used after filename", arg); - if (check_filename(prefix, arg) || !no_wildcard(arg)) + if (looks_like_pathspec(arg) || check_filename(prefix, arg)) return; die_verify_filename(prefix, arg, diagnose_misspelt_rev); } @@ -703,11 +726,16 @@ static const char *setup_discovered_git_dir(const char *gitdir, /* --work-tree is set without --git-dir; use discovered one */ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { + char *to_free = NULL; + const char *ret; + if (offset != cwd->len && !is_absolute_path(gitdir)) - gitdir = real_pathdup(gitdir, 1); + gitdir = to_free = real_pathdup(gitdir, 1); if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); - return setup_explicit_git_dir(gitdir, cwd, nongit_ok); + ret = setup_explicit_git_dir(gitdir, cwd, nongit_ok); + free(to_free); + return ret; } /* #16.2, #17.2, #20.2, #21.2, #24, #25, #28, #29 (see t1510) */ @@ -748,7 +776,7 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset, /* --work-tree is set without --git-dir; use discovered one */ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { - const char *gitdir; + static const char *gitdir; gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset); if (chdir(cwd->buf)) @@ -940,19 +968,21 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, } } -const char *discover_git_directory(struct strbuf *gitdir) +int discover_git_directory(struct strbuf *commondir, + struct strbuf *gitdir) { struct strbuf dir = STRBUF_INIT, err = STRBUF_INIT; size_t gitdir_offset = gitdir->len, cwd_len; + size_t commondir_offset = commondir->len; struct repository_format candidate; if (strbuf_getcwd(&dir)) - return NULL; + return -1; cwd_len = dir.len; if (setup_git_directory_gently_1(&dir, gitdir, 0) <= 0) { strbuf_release(&dir); - return NULL; + return -1; } /* @@ -968,8 +998,10 @@ const char *discover_git_directory(struct strbuf *gitdir) strbuf_insert(gitdir, gitdir_offset, dir.buf, dir.len); } + get_common_dir(commondir, gitdir->buf + gitdir_offset); + strbuf_reset(&dir); - strbuf_addf(&dir, "%s/config", gitdir->buf + gitdir_offset); + strbuf_addf(&dir, "%s/config", commondir->buf + commondir_offset); read_repository_format(&candidate, dir.buf); strbuf_release(&dir); @@ -977,10 +1009,12 @@ const char *discover_git_directory(struct strbuf *gitdir) warning("ignoring git dir '%s': %s", gitdir->buf + gitdir_offset, err.buf); strbuf_release(&err); - return NULL; + strbuf_setlen(commondir, commondir_offset); + strbuf_setlen(gitdir, gitdir_offset); + return -1; } - return gitdir->buf + gitdir_offset; + return 0; } const char *setup_git_directory_gently(int *nongit_ok) diff --git a/sha1-array.c b/sha1-array.c index 7d646ab5b8..838b3bf847 100644 --- a/sha1-array.c +++ b/sha1-array.c @@ -35,8 +35,7 @@ int oid_array_lookup(struct oid_array *array, const struct object_id *oid) void oid_array_clear(struct oid_array *array) { - free(array->oid); - array->oid = NULL; + FREE_AND_NULL(array->oid); array->nr = 0; array->alloc = 0; array->sorted = 0; diff --git a/sha1_file.c b/sha1_file.c index 59a4ed2ed3..fb1fd809dc 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -7,6 +7,7 @@ * creation etc. */ #include "cache.h" +#include "config.h" #include "string-list.h" #include "lockfile.h" #include "delta.h" @@ -610,8 +611,7 @@ char *compute_alternate_path(const char *path, struct strbuf *err) out: if (seen_error) { - free(ref_git); - ref_git = NULL; + FREE_AND_NULL(ref_git); } return ref_git; @@ -3546,7 +3546,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size, */ if ((type == OBJ_BLOB) && path) { struct strbuf nbuf = STRBUF_INIT; - if (convert_to_git(path, buf, size, &nbuf, + if (convert_to_git(&the_index, path, buf, size, &nbuf, write_object ? safe_crlf : SAFE_CRLF_FALSE)) { buf = strbuf_detach(&nbuf, &size); re_allocated = 1; @@ -3580,7 +3580,7 @@ static int index_stream_convert_blob(unsigned char *sha1, int fd, assert(path); assert(would_convert_to_git_filter_fd(path)); - convert_to_git_filter_fd(path, fd, &sbuf, + convert_to_git_filter_fd(&the_index, path, fd, &sbuf, write_object ? safe_crlf : SAFE_CRLF_FALSE); if (write_object) @@ -3668,7 +3668,7 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, else if (!S_ISREG(st->st_mode)) ret = index_pipe(sha1, fd, type, path, flags); else if (st->st_size <= big_file_threshold || type != OBJ_BLOB || - (path && would_convert_to_git(path))) + (path && would_convert_to_git(&the_index, path))) ret = index_core(sha1, fd, xsize_t(st->st_size), type, path, flags); else diff --git a/sha1_name.c b/sha1_name.c index 389276e9d3..d2d732c19b 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "tag.h" #include "commit.h" #include "tree.h" @@ -1408,7 +1409,7 @@ static void diagnose_invalid_sha1_path(const char *prefix, if (file_exists(filename)) die("Path '%s' exists on disk, but not in '%.*s'.", filename, object_name_len, object_name); - if (errno == ENOENT || errno == ENOTDIR) { + if (is_missing_file_error(errno)) { char *fullname = xstrfmt("%s%s", prefix, filename); if (!get_tree_entry(tree_sha1, fullname, @@ -1473,7 +1474,7 @@ static void diagnose_invalid_index_path(int stage, if (file_exists(filename)) die("Path '%s' exists on disk, but not in the index.", filename); - if (errno == ENOENT || errno == ENOTDIR) + if (is_missing_file_error(errno)) die("Path '%s' does not exist (neither on disk nor in the index).", filename); @@ -1511,6 +1512,7 @@ static int get_sha1_with_context_1(const char *name, memset(oc, 0, sizeof(*oc)); oc->mode = S_IFINVALID; + strbuf_init(&oc->symlink_path, 0); ret = get_sha1_1(name, namelen, sha1, flags); if (!ret) return ret; @@ -1549,7 +1551,8 @@ static int get_sha1_with_context_1(const char *name, namelen = strlen(cp); } - strlcpy(oc->path, cp, sizeof(oc->path)); + if (flags & GET_SHA1_RECORD_PATH) + oc->path = xstrdup(cp); if (!active_cache) read_cache(); @@ -1612,7 +1615,8 @@ static int get_sha1_with_context_1(const char *name, } } hashcpy(oc->tree, tree_sha1); - strlcpy(oc->path, filename, sizeof(oc->path)); + if (flags & GET_SHA1_RECORD_PATH) + oc->path = xstrdup(filename); free(new_filename); return ret; @@ -1638,9 +1642,9 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix) get_sha1_with_context_1(name, GET_SHA1_ONLY_TO_DIE, prefix, sha1, &oc); } -int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc) +int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *oc) { if (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE) die("BUG: incompatible flags for get_sha1_with_context"); - return get_sha1_with_context_1(str, flags, NULL, sha1, orc); + return get_sha1_with_context_1(str, flags, NULL, sha1, oc); } diff --git a/sha1dc/.gitattributes b/sha1dc/.gitattributes new file mode 100644 index 0000000000..da53f40548 --- /dev/null +++ b/sha1dc/.gitattributes @@ -0,0 +1 @@ +* whitespace=-indent-with-non-tab diff --git a/sha1dc/sha1.c b/sha1dc/sha1.c index 35e9dd5bf4..facea1bb56 100644 --- a/sha1dc/sha1.c +++ b/sha1dc/sha1.c @@ -5,9 +5,23 @@ * https://opensource.org/licenses/MIT ***/ -#include "cache.h" -#include "sha1dc/sha1.h" -#include "sha1dc/ubc_check.h" +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <string.h> +#include <memory.h> +#include <stdio.h> +#include <stdlib.h> +#endif + +#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_INCLUDE_SHA1_C +#endif + +#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT +#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 +#endif + +#include "sha1.h" +#include "ubc_check.h" /* @@ -18,15 +32,48 @@ If you are compiling on a big endian platform and your compiler does not define one of these, you will have to add whatever macros your tool chain defines to indicate Big-Endianness. */ -#if (defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)) || \ - (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__)) || \ - defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ - defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) +#ifdef SHA1DC_BIGENDIAN +#undef SHA1DC_BIGENDIAN +#endif + +#if (defined(_BYTE_ORDER) || defined(__BYTE_ORDER) || defined(__BYTE_ORDER__)) + +#if ((defined(_BYTE_ORDER) && (_BYTE_ORDER == _BIG_ENDIAN)) || \ + (defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__)) ) +#define SHA1DC_BIGENDIAN +#endif -#define SHA1DC_BIGENDIAN 1 #else + +#if (defined(_BIG_ENDIAN) || defined(__BIG_ENDIAN) || defined(__BIG_ENDIAN__) || \ + defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \ + defined(__sparc)) +#define SHA1DC_BIGENDIAN +#endif + +#endif + +#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) #undef SHA1DC_BIGENDIAN -#endif /*ENDIANNESS SELECTION*/ +#endif +#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) +#define SHA1DC_BIGENDIAN +#endif +/*ENDIANNESS SELECTION*/ + +#if (defined SHA1DC_FORCE_UNALIGNED_ACCESS || \ + defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ + defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ + defined(__386) || defined(_M_X64) || defined(_M_AMD64)) + +#define SHA1DC_ALLOW_UNALIGNED_ACCESS + +#endif /*UNALIGNMENT DETECTION*/ + #define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n)))) #define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n)))) @@ -36,11 +83,11 @@ #define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) -#if defined(SHA1DC_BIGENDIAN) +#ifdef SHA1DC_BIGENDIAN #define sha1_load(m, t, temp) { temp = m[t]; } #else #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } -#endif /* !defined(SHA1DC_BIGENDIAN) */ +#endif #define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x @@ -869,6 +916,11 @@ static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], co ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4127) /* Complier complains about the checks in the above macro being constant. */ +#endif + #ifdef DOSTORESTATE0 SHA1_RECOMPRESS(0) #endif @@ -1189,6 +1241,10 @@ SHA1_RECOMPRESS(78) SHA1_RECOMPRESS(79) #endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) { switch (step) @@ -1662,7 +1718,7 @@ void SHA1DCInit(SHA1_CTX* ctx) ctx->ihv[3] = 0x10325476; ctx->ihv[4] = 0xC3D2E1F0; ctx->found_collision = 0; - ctx->safe_hash = 0; + ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; ctx->ubc_check = 1; ctx->detect_coll = 1; ctx->reduced_round_coll = 0; @@ -1710,6 +1766,7 @@ void SHA1DCSetCallback(SHA1_CTX* ctx, collision_block_callback callback) void SHA1DCUpdate(SHA1_CTX* ctx, const char* buf, size_t len) { unsigned left, fill; + if (len == 0) return; @@ -1728,7 +1785,13 @@ void SHA1DCUpdate(SHA1_CTX* ctx, const char* buf, size_t len) while (len >= 64) { ctx->total += 64; + +#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) sha1_process(ctx, (uint32_t*)(buf)); +#else + memcpy(ctx->buffer, buf, 64); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); +#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ buf += 64; len -= 64; } @@ -1788,22 +1851,6 @@ int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) return ctx->found_collision; } -void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx) -{ - if (!SHA1DCFinal(hash, ctx)) - return; - die("SHA-1 appears to be part of a collision attack: %s", - sha1_to_hex(hash)); -} - -void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, unsigned long len) -{ - const char *data = vdata; - /* We expect an unsigned long, but sha1dc only takes an int */ - while (len > INT_MAX) { - SHA1DCUpdate(ctx, data, INT_MAX); - data += INT_MAX; - len -= INT_MAX; - } - SHA1DCUpdate(ctx, data, len); -} +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#endif diff --git a/sha1dc/sha1.h b/sha1dc/sha1.h index bd8bd928fb..1e4e94be54 100644 --- a/sha1dc/sha1.h +++ b/sha1dc/sha1.h @@ -4,6 +4,7 @@ * See accompanying file LICENSE.txt or copy at * https://opensource.org/licenses/MIT ***/ + #ifndef SHA1DC_SHA1_H #define SHA1DC_SHA1_H @@ -11,36 +12,30 @@ extern "C" { #endif -/* uses SHA-1 message expansion to expand the first 16 words of W[] to 80 words */ -/* void sha1_message_expansion(uint32_t W[80]); */ - -/* sha-1 compression function; first version takes a message block pre-parsed as 16 32-bit integers, second version takes an already expanded message) */ -/* void sha1_compression(uint32_t ihv[5], const uint32_t m[16]); -void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]); */ +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif -/* same as sha1_compression_W, but additionally store intermediate states */ +/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */ /* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); /* -// function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) -// where 0 <= T < 80 -// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference) -// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block -// the function will return: -// ihvin: the reconstructed input chaining value -// ihvout: the reconstructed output chaining value +// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]). +// Where 0 <= T < 80 +// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.) +// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block. +// The function will return: +// ihvin: The reconstructed input chaining value. +// ihvout: The reconstructed output chaining value. */ typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); -/* table of sha1_recompression_step_0, ... , sha1_recompression_step_79 */ -/* extern sha1_recompression_type sha1_recompression_step[80];*/ - -/* a callback function type that can be set to be called when a collision block has been found: */ +/* A callback function type that can be set to be called when a collision block has been found: */ /* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); -/* the SHA-1 context */ +/* The SHA-1 context. */ typedef struct { uint64_t total; uint32_t ihv[5]; @@ -59,30 +54,34 @@ typedef struct { uint32_t states[80][5]; } SHA1_CTX; -/* initialize SHA-1 context */ +/* Initialize SHA-1 context. */ void SHA1DCInit(SHA1_CTX*); /* -// function to enable safe SHA-1 hashing: -// collision attacks are thwarted by hashing a detected near-collision block 3 times -// think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: -// the best collision attacks against SHA-1 have complexity about 2^60, -// thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would 2^180 -// an attacker would be better off using a generic birthday search of complexity 2^80 -// -// enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected -// but it will result in a different SHA-1 hash for messages where a collision attack was detected -// this will automatically invalidate SHA-1 based digital signature forgeries -// enabled by default + Function to enable safe SHA-1 hashing: + Collision attacks are thwarted by hashing a detected near-collision block 3 times. + Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: + The best collision attacks against SHA-1 have complexity about 2^60, + thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. + An attacker would be better off using a generic birthday search of complexity 2^80. + + Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected, + but it will result in a different SHA-1 hash for messages where a collision attack was detected. + This will automatically invalidate SHA-1 based digital signature forgeries. + Enabled by default. */ void SHA1DCSetSafeHash(SHA1_CTX*, int); -/* function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up) */ -/* enabled by default */ +/* + Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up). + Enabled by default + */ void SHA1DCSetUseUBC(SHA1_CTX*, int); -/* function to disable or enable the use of Collision Detection */ -/* enabled by default */ +/* + Function to disable or enable the use of Collision Detection. + Enabled by default. + */ void SHA1DCSetUseDetectColl(SHA1_CTX*, int); /* function to disable or enable the detection of reduced-round SHA-1 collisions */ @@ -100,23 +99,12 @@ void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); /* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ int SHA1DCFinal(unsigned char[20], SHA1_CTX*); -/* - * Same as SHA1DCFinal, but convert collision attack case into a verbose die(). - */ -void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *); - -/* - * Same as SHA1DCUpdate, but adjust types to match git's usual interface. - */ -void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len); - -#define platform_SHA_CTX SHA1_CTX -#define platform_SHA1_Init SHA1DCInit -#define platform_SHA1_Update git_SHA1DCUpdate -#define platform_SHA1_Final git_SHA1DCFinal - #if defined(__cplusplus) } #endif -#endif /* SHA1DC_SHA1_H */ +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#endif + +#endif diff --git a/sha1dc/ubc_check.c b/sha1dc/ubc_check.c index 089dd4743d..b3beff2afb 100644 --- a/sha1dc/ubc_check.c +++ b/sha1dc/ubc_check.c @@ -24,8 +24,13 @@ // ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section */ -#include "git-compat-util.h" -#include "sha1dc/ubc_check.h" +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif +#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#endif +#include "ubc_check.h" static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; @@ -361,3 +366,7 @@ if (mask) { dvmask[0]=mask; } + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#endif diff --git a/sha1dc/ubc_check.h b/sha1dc/ubc_check.h index b64c306d77..d7e17dc734 100644 --- a/sha1dc/ubc_check.h +++ b/sha1dc/ubc_check.h @@ -20,13 +20,17 @@ // thus one needs to do the recompression check for each DV that has its bit set */ -#ifndef UBC_CHECK_H -#define UBC_CHECK_H +#ifndef SHA1DC_UBC_CHECK_H +#define SHA1DC_UBC_CHECK_H #if defined(__cplusplus) extern "C" { #endif +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include <stdint.h> +#endif + #define DVMASKSIZE 1 typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; extern dv_info_t sha1_dvs[]; @@ -41,4 +45,8 @@ void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); } #endif -#endif /* UBC_CHECK_H */ +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#endif + +#endif diff --git a/sha1dc_git.c b/sha1dc_git.c new file mode 100644 index 0000000000..4d32b4f77e --- /dev/null +++ b/sha1dc_git.c @@ -0,0 +1,24 @@ +/* + * This code is included at the end of sha1dc/sha1.c with the + * SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C macro. + */ + +void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx) +{ + if (!SHA1DCFinal(hash, ctx)) + return; + die("SHA-1 appears to be part of a collision attack: %s", + sha1_to_hex(hash)); +} + +void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, unsigned long len) +{ + const char *data = vdata; + /* We expect an unsigned long, but sha1dc only takes an int */ + while (len > INT_MAX) { + SHA1DCUpdate(ctx, data, INT_MAX); + data += INT_MAX; + len -= INT_MAX; + } + SHA1DCUpdate(ctx, data, len); +} diff --git a/sha1dc_git.h b/sha1dc_git.h new file mode 100644 index 0000000000..a8a5c1da16 --- /dev/null +++ b/sha1dc_git.h @@ -0,0 +1,19 @@ +/* + * This code is included at the end of sha1dc/sha1.h with the + * SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H macro. + */ + +/* + * Same as SHA1DCFinal, but convert collision attack case into a verbose die(). + */ +void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *); + +/* + * Same as SHA1DCUpdate, but adjust types to match git's usual interface. + */ +void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len); + +#define platform_SHA_CTX SHA1_CTX +#define platform_SHA1_Init SHA1DCInit +#define platform_SHA1_Update git_SHA1DCUpdate +#define platform_SHA1_Final git_SHA1DCFinal @@ -473,11 +473,15 @@ static void paint_down(struct paint_info *info, const struct object_id *oid, struct commit_list *head = NULL; int bitmap_nr = (info->nr_bits + 31) / 32; size_t bitmap_size = st_mult(sizeof(uint32_t), bitmap_nr); - uint32_t *tmp = xmalloc(bitmap_size); /* to be freed before return */ - uint32_t *bitmap = paint_alloc(info); struct commit *c = lookup_commit_reference_gently(oid, 1); + uint32_t *tmp; /* to be freed before return */ + uint32_t *bitmap; + if (!c) return; + + tmp = xmalloc(bitmap_size); + bitmap = paint_alloc(info); memset(bitmap, 0, bitmap_size); bitmap[id / 32] |= (1U << (id % 32)); commit_list_insert(c, &head); diff --git a/split-index.c b/split-index.c index f519e60f87..83e39ec8d7 100644 --- a/split-index.c +++ b/split-index.c @@ -167,10 +167,9 @@ void merge_base_index(struct index_state *istate) ewah_free(si->delete_bitmap); ewah_free(si->replace_bitmap); - free(si->saved_cache); + FREE_AND_NULL(si->saved_cache); si->delete_bitmap = NULL; si->replace_bitmap = NULL; - si->saved_cache = NULL; si->saved_cache_nr = 0; } @@ -204,13 +204,6 @@ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) strbuf_setlen(sb, sb->len + sb2->len); } -void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len) -{ - strbuf_grow(sb, len); - memcpy(sb->buf + sb->len, sb->buf + pos, len); - strbuf_setlen(sb, sb->len + len); -} - void strbuf_addchars(struct strbuf *sb, int c, size_t n) { strbuf_grow(sb, n); @@ -785,14 +778,48 @@ char *xstrfmt(const char *fmt, ...) return ret; } -void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm) +void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm, + int tz_offset, const char *tz_name) { + struct strbuf munged_fmt = STRBUF_INIT; size_t hint = 128; size_t len; if (!*fmt) return; + /* + * There is no portable way to pass timezone information to + * strftime, so we handle %z and %Z here. + */ + for (;;) { + const char *percent = strchrnul(fmt, '%'); + strbuf_add(&munged_fmt, fmt, percent - fmt); + if (!*percent) + break; + fmt = percent + 1; + switch (*fmt) { + case '%': + strbuf_addstr(&munged_fmt, "%%"); + fmt++; + break; + case 'z': + strbuf_addf(&munged_fmt, "%+05d", tz_offset); + fmt++; + break; + case 'Z': + if (tz_name) { + strbuf_addstr(&munged_fmt, tz_name); + fmt++; + break; + } + /* FALLTHROUGH */ + default: + strbuf_addch(&munged_fmt, '%'); + } + } + fmt = munged_fmt.buf; + strbuf_grow(sb, hint); len = strftime(sb->buf + sb->len, sb->alloc - sb->len, fmt, tm); @@ -804,17 +831,16 @@ void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm) * output contains at least one character, and then drop the extra * character before returning. */ - struct strbuf munged_fmt = STRBUF_INIT; - strbuf_addf(&munged_fmt, "%s ", fmt); + strbuf_addch(&munged_fmt, ' '); while (!len) { hint *= 2; strbuf_grow(sb, hint); len = strftime(sb->buf + sb->len, sb->alloc - sb->len, munged_fmt.buf, tm); } - strbuf_release(&munged_fmt); len--; /* drop munged space */ } + strbuf_release(&munged_fmt); strbuf_setlen(sb, sb->len + len); } @@ -264,12 +264,6 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) extern void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2); /** - * Copy part of the buffer from a given position till a given length to the - * end of the buffer. - */ -extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len); - -/** * This function can be used to expand a format string containing * placeholders. To that end, it parses the string and calls the specified * function for every percent sign found. @@ -340,8 +334,14 @@ extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap); /** * Add the time specified by `tm`, as formatted by `strftime`. - */ -extern void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm); + * `tz_name` is used to expand %Z internally unless it's NULL. + * `tz_offset` is in decimal hhmm format, e.g. -600 means six hours west + * of Greenwich, and it's used to expand %z internally. However, tokens + * with modifiers (e.g. %Ez) are passed to `strftime`. + */ +extern void strbuf_addftime(struct strbuf *sb, const char *fmt, + const struct tm *tm, int tz_offset, + const char *tz_name); /** * Read a given size of data from a FILE* pointer to the buffer. diff --git a/string-list.c b/string-list.c index 003ca1879e..c650500c6e 100644 --- a/string-list.c +++ b/string-list.c @@ -64,6 +64,24 @@ struct string_list_item *string_list_insert(struct string_list *list, const char return list->items + index; } +void string_list_remove(struct string_list *list, const char *string, + int free_util) +{ + int exact_match; + int i = get_entry_index(list, string, &exact_match); + + if (exact_match) { + if (list->strdup_strings) + free(list->items[i].string); + if (free_util) + free(list->items[i].util); + + list->nr--; + memmove(list->items + i, list->items + i + 1, + (list->nr - i) * sizeof(struct string_list_item)); + } +} + int string_list_has_string(const struct string_list *list, const char *string) { int exact_match; diff --git a/string-list.h b/string-list.h index d3809a1417..29bfb7ae45 100644 --- a/string-list.h +++ b/string-list.h @@ -63,6 +63,13 @@ int string_list_find_insert_index(const struct string_list *list, const char *st struct string_list_item *string_list_insert(struct string_list *list, const char *string); /* + * Removes the given string from the sorted list. + * If the string doesn't exist, the list is not altered. + */ +extern void string_list_remove(struct string_list *list, const char *string, + int free_util); + +/* * Checks if the given string is part of a sorted list. If it is part of the list, * return the coresponding string_list_item, NULL otherwise. */ diff --git a/sub-process.c b/sub-process.c new file mode 100644 index 0000000000..92f8aea70a --- /dev/null +++ b/sub-process.c @@ -0,0 +1,106 @@ +/* + * Generic implementation of background process infrastructure. + */ +#include "sub-process.h" +#include "sigchain.h" +#include "pkt-line.h" + +int cmd2process_cmp(const struct subprocess_entry *e1, + const struct subprocess_entry *e2, + const void *unused) +{ + return strcmp(e1->cmd, e2->cmd); +} + +struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd) +{ + struct subprocess_entry key; + + hashmap_entry_init(&key, strhash(cmd)); + key.cmd = cmd; + return hashmap_get(hashmap, &key, NULL); +} + +int subprocess_read_status(int fd, struct strbuf *status) +{ + struct strbuf **pair; + char *line; + int len; + + for (;;) { + len = packet_read_line_gently(fd, NULL, &line); + if ((len < 0) || !line) + break; + pair = strbuf_split_str(line, '=', 2); + if (pair[0] && pair[0]->len && pair[1]) { + /* the last "status=<foo>" line wins */ + if (!strcmp(pair[0]->buf, "status=")) { + strbuf_reset(status); + strbuf_addbuf(status, pair[1]); + } + } + strbuf_list_free(pair); + } + + return (len < 0) ? len : 0; +} + +void subprocess_stop(struct hashmap *hashmap, struct subprocess_entry *entry) +{ + if (!entry) + return; + + entry->process.clean_on_exit = 0; + kill(entry->process.pid, SIGTERM); + finish_command(&entry->process); + + hashmap_remove(hashmap, entry, NULL); +} + +static void subprocess_exit_handler(struct child_process *process) +{ + sigchain_push(SIGPIPE, SIG_IGN); + /* Closing the pipe signals the subprocess to initiate a shutdown. */ + close(process->in); + close(process->out); + sigchain_pop(SIGPIPE); + /* Finish command will wait until the shutdown is complete. */ + finish_command(process); +} + +int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd, + subprocess_start_fn startfn) +{ + int err; + struct child_process *process; + const char *argv[] = { cmd, NULL }; + + entry->cmd = cmd; + process = &entry->process; + + child_process_init(process); + process->argv = argv; + process->use_shell = 1; + process->in = -1; + process->out = -1; + process->clean_on_exit = 1; + process->clean_on_exit_handler = subprocess_exit_handler; + + err = start_command(process); + if (err) { + error("cannot fork to run subprocess '%s'", cmd); + return err; + } + + hashmap_entry_init(entry, strhash(cmd)); + + err = startfn(entry); + if (err) { + error("initialization for subprocess '%s' failed", cmd); + subprocess_stop(hashmap, entry); + return err; + } + + hashmap_add(hashmap, entry); + return 0; +} diff --git a/sub-process.h b/sub-process.h new file mode 100644 index 0000000000..d9a45cd359 --- /dev/null +++ b/sub-process.h @@ -0,0 +1,49 @@ +#ifndef SUBPROCESS_H +#define SUBPROCESS_H + +#include "git-compat-util.h" +#include "hashmap.h" +#include "run-command.h" + +/* + * Generic implementation of background process infrastructure. + * See: Documentation/technical/api-sub-process.txt + */ + + /* data structures */ + +struct subprocess_entry { + struct hashmap_entry ent; /* must be the first member! */ + const char *cmd; + struct child_process process; +}; + +/* subprocess functions */ + +int cmd2process_cmp(const struct subprocess_entry *e1, + const struct subprocess_entry *e2, const void *unused); + +typedef int(*subprocess_start_fn)(struct subprocess_entry *entry); +int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd, + subprocess_start_fn startfn); + +void subprocess_stop(struct hashmap *hashmap, struct subprocess_entry *entry); + +struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd); + +/* subprocess helper functions */ + +static inline struct child_process *subprocess_get_child_process( + struct subprocess_entry *entry) +{ + return &entry->process; +} + +/* + * Helper function that will read packets looking for "status=<foo>" + * key/value pairs and return the value from the last "status" packet + */ + +int subprocess_read_status(int fd, struct strbuf *status); + +#endif diff --git a/submodule-config.c b/submodule-config.c index 4f58491ddb..d8f8d5ea32 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "submodule-config.h" #include "submodule.h" #include "strbuf.h" diff --git a/submodule.c b/submodule.c index bdeeff3a5d..da0b805493 100644 --- a/submodule.c +++ b/submodule.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "submodule-config.h" #include "submodule.h" #include "dir.h" @@ -16,9 +17,10 @@ #include "quote.h" #include "remote.h" #include "worktree.h" +#include "parse-options.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; -static int config_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF; static int parallel_jobs = 1; static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP; static int initialized_fetch_ref_tips; @@ -153,7 +155,8 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, } } -int submodule_config(const char *var, const char *value, void *cb) +/* For loading from the .gitmodules file. */ +static int git_modules_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "submodule.fetchjobs")) { parallel_jobs = git_config_int(var, value); @@ -169,6 +172,56 @@ int submodule_config(const char *var, const char *value, void *cb) return 0; } +/* Loads all submodule settings from the config. */ +int submodule_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "submodule.recurse")) { + int v = git_config_bool(var, value) ? + RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; + config_update_recurse_submodules = v; + return 0; + } else { + return git_modules_config(var, value, cb); + } +} + +/* Cheap function that only determines if we're interested in submodules at all */ +int git_default_submodule_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "submodule.recurse")) { + int v = git_config_bool(var, value) ? + RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; + config_update_recurse_submodules = v; + } + return 0; +} + +int option_parse_recurse_submodules_worktree_updater(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + config_update_recurse_submodules = RECURSE_SUBMODULES_OFF; + return 0; + } + if (arg) + config_update_recurse_submodules = + parse_update_recurse_submodules_arg(opt->long_name, + arg); + else + config_update_recurse_submodules = RECURSE_SUBMODULES_ON; + + return 0; +} + +void load_submodule_cache(void) +{ + if (config_update_recurse_submodules == RECURSE_SUBMODULES_OFF) + return; + + gitmodules_config(); + git_config(submodule_config, NULL); +} + void gitmodules_config(void) { const char *work_tree = get_git_work_tree(); @@ -196,7 +249,8 @@ void gitmodules_config(void) } if (!gitmodules_is_unmerged) - git_config_from_file(submodule_config, gitmodules_path.buf, NULL); + git_config_from_file(git_modules_config, + gitmodules_path.buf, NULL); strbuf_release(&gitmodules_path); } } @@ -207,7 +261,7 @@ void gitmodules_config_sha1(const unsigned char *commit_sha1) unsigned char sha1[20]; if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) { - git_config_from_blob_sha1(submodule_config, rev.buf, + git_config_from_blob_sha1(git_modules_config, rev.buf, sha1, NULL); } strbuf_release(&rev); @@ -282,6 +336,69 @@ int is_submodule_populated_gently(const char *path, int *return_error_code) return ret; } +/* + * Dies if the provided 'prefix' corresponds to an unpopulated submodule + */ +void die_in_unpopulated_submodule(const struct index_state *istate, + const char *prefix) +{ + int i, prefixlen; + + if (!prefix) + return; + + prefixlen = strlen(prefix); + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + if (prefixlen <= ce_len) + continue; + if (strncmp(ce->name, prefix, ce_len)) + continue; + if (prefix[ce_len] != '/') + continue; + + die(_("in unpopulated submodule '%s'"), ce->name); + } +} + +/* + * Dies if any paths in the provided pathspec descends into a submodule + */ +void die_path_inside_submodule(const struct index_state *istate, + const struct pathspec *ps) +{ + int i, j; + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + int ce_len = ce_namelen(ce); + + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + for (j = 0; j < ps->nr ; j++) { + const struct pathspec_item *item = &ps->items[j]; + + if (item->len <= ce_len) + continue; + if (item->match[ce_len] != '/') + continue; + if (strncmp(ce->name, item->match, ce_len)) + continue; + if (item->len == ce_len + 1) + continue; + + die(_("Pathspec '%s' is in submodule '%.*s'"), + item->original, ce_len, ce->name); + } + } +} + int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst) { @@ -554,7 +671,8 @@ void show_submodule_inline_diff(FILE *f, const char *path, cp.no_stdin = 1; /* TODO: other options may need to be passed here. */ - argv_array_push(&cp.args, "diff"); + argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL); + argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix); if (DIFF_OPT_TST(o, REVERSE_DIFF)) { argv_array_pushf(&cp.args, "--src-prefix=%s%s/", @@ -596,11 +714,6 @@ void set_config_fetch_recurse_submodules(int value) config_fetch_recurse_submodules = value; } -void set_config_update_recurse_submodules(int value) -{ - config_update_recurse_submodules = value; -} - int should_update_submodules(void) { return config_update_recurse_submodules == RECURSE_SUBMODULES_ON; @@ -1356,7 +1469,7 @@ static int submodule_has_dirty_index(const struct submodule *sub) { struct child_process cp = CHILD_PROCESS_INIT; - prepare_submodule_repo_env_no_git_dir(&cp.env_array); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; argv_array_pushl(&cp.args, "diff-index", "--quiet", @@ -1373,7 +1486,7 @@ static int submodule_has_dirty_index(const struct submodule *sub) static void submodule_reset_index(const char *path) { struct child_process cp = CHILD_PROCESS_INIT; - prepare_submodule_repo_env_no_git_dir(&cp.env_array); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; @@ -1402,6 +1515,23 @@ int submodule_move_head(const char *path, int ret = 0; struct child_process cp = CHILD_PROCESS_INIT; const struct submodule *sub; + int *error_code_ptr, error_code; + + if (!is_submodule_initialized(path)) + return 0; + + if (flags & SUBMODULE_MOVE_HEAD_FORCE) + /* + * Pass non NULL pointer to is_submodule_populated_gently + * to prevent die()-ing. We'll use connect_work_tree_and_git_dir + * to fixup the submodule in the force case later. + */ + error_code_ptr = &error_code; + else + error_code_ptr = NULL; + + if (old && !is_submodule_populated_gently(path, error_code_ptr)) + return 0; sub = submodule_from_path(null_sha1, path); @@ -1420,18 +1550,24 @@ int submodule_move_head(const char *path, absorb_git_dir_into_superproject("", path, ABSORB_GITDIR_RECURSE_SUBMODULES); } else { - struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "%s/modules/%s", + char *gitdir = xstrfmt("%s/modules/%s", get_git_common_dir(), sub->name); - connect_work_tree_and_git_dir(path, sb.buf); - strbuf_release(&sb); + connect_work_tree_and_git_dir(path, gitdir); + free(gitdir); /* make sure the index is clean as well */ submodule_reset_index(path); } + + if (old && (flags & SUBMODULE_MOVE_HEAD_FORCE)) { + char *gitdir = xstrfmt("%s/modules/%s", + get_git_common_dir(), sub->name); + connect_work_tree_and_git_dir(path, gitdir); + free(gitdir); + } } - prepare_submodule_repo_env_no_git_dir(&cp.env_array); + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; @@ -1439,7 +1575,7 @@ int submodule_move_head(const char *path, argv_array_pushf(&cp.args, "--super-prefix=%s%s/", get_super_prefix_or_empty(), path); - argv_array_pushl(&cp.args, "read-tree", NULL); + argv_array_pushl(&cp.args, "read-tree", "--recurse-submodules", NULL); if (flags & SUBMODULE_MOVE_HEAD_DRY_RUN) argv_array_push(&cp.args, "-n"); @@ -1461,15 +1597,16 @@ int submodule_move_head(const char *path, if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) { if (new) { - struct child_process cp1 = CHILD_PROCESS_INIT; + child_process_init(&cp); /* also set the HEAD accordingly */ - cp1.git_cmd = 1; - cp1.no_stdin = 1; - cp1.dir = path; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.dir = path; - argv_array_pushl(&cp1.args, "update-ref", "HEAD", new, NULL); + prepare_submodule_repo_env(&cp.env_array); + argv_array_pushl(&cp.args, "update-ref", "HEAD", new, NULL); - if (run_command(&cp1)) { + if (run_command(&cp)) { ret = -1; goto out; } diff --git a/submodule.h b/submodule.h index 89c2ed2191..cbe5c1726f 100644 --- a/submodule.h +++ b/submodule.h @@ -39,6 +39,12 @@ extern void stage_updated_gitmodules(void); extern void set_diffopt_flags_from_submodule_config(struct diff_options *, const char *path); extern int submodule_config(const char *var, const char *value, void *cb); +extern int git_default_submodule_config(const char *var, const char *value, void *cb); + +struct option; +int option_parse_recurse_submodules_worktree_updater(const struct option *opt, + const char *arg, int unset); +void load_submodule_cache(void); extern void gitmodules_config(void); extern void gitmodules_config_sha1(const unsigned char *commit_sha1); extern int is_submodule_initialized(const char *path); @@ -49,6 +55,10 @@ extern int is_submodule_initialized(const char *path); * Otherwise the return error code is the same as of resolve_gitdir_gently. */ extern int is_submodule_populated_gently(const char *path, int *return_error_code); +extern void die_in_unpopulated_submodule(const struct index_state *istate, + const char *prefix); +extern void die_path_inside_submodule(const struct index_state *istate, + const struct pathspec *ps); extern int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); @@ -65,7 +75,6 @@ extern void show_submodule_inline_diff(FILE *f, const char *path, const char *del, const char *add, const char *reset, const struct diff_options *opt); extern void set_config_fetch_recurse_submodules(int value); -extern void set_config_update_recurse_submodules(int value); /* Check if we want to update any submodule.*/ extern int should_update_submodules(void); /* diff --git a/t/.gitattributes b/t/.gitattributes index 2d44088f56..3bd959ae52 100644 --- a/t/.gitattributes +++ b/t/.gitattributes @@ -1,2 +1,22 @@ t[0-9][0-9][0-9][0-9]/* -whitespace -t0110/url-* binary +/diff-lib/* eol=lf +/t0110/url-* binary +/t3900/*.txt eol=lf +/t3901/*.txt eol=lf +/t4034/*/* eol=lf +/t4013/* eol=lf +/t4018/* eol=lf +/t4051/* eol=lf +/t4100/* eol=lf +/t4101/* eol=lf +/t4109/* eol=lf +/t4110/* eol=lf +/t4135/* eol=lf +/t4211/* eol=lf +/t4252/* eol=lf +/t5100/* eol=lf +/t5515/* eol=lf +/t556x_common eol=lf +/t7500/* eol=lf +/t8005/*.txt eol=lf +/t9*/*.dump eol=lf @@ -803,9 +803,9 @@ use these, and "test_set_prereq" for how to define your own. Test is not run by root user, and an attempt to write to an unwritable file is expected to fail correctly. - - LIBPCRE + - PCRE - Git was compiled with USE_LIBPCRE=YesPlease. Wrap any tests + Git was compiled with support for PCRE. Wrap any tests that use git-grep --perl-regexp or git-grep -P in these. - CASE_INSENSITIVE_FS @@ -817,6 +817,10 @@ use these, and "test_set_prereq" for how to define your own. Test is run on a filesystem which converts decomposed utf-8 (nfd) to precomposed utf-8 (nfc). + - PTHREADS + + Git wasn't compiled with NO_PTHREADS=YesPlease. + Tips for Writing Tests ---------------------- diff --git a/t/helper/test-config.c b/t/helper/test-config.c index 8e3ed6a76c..1a7b8bd3d6 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "string-list.h" /* diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c index 2f144d539a..c6c57bba0d 100644 --- a/t/helper/test-submodule-config.c +++ b/t/helper/test-submodule-config.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "submodule-config.h" #include "submodule.h" diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index fb4f7b014e..2d26f86800 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -73,6 +73,7 @@ create_lib_submodule_repo () { git checkout -b "add_sub1" && git submodule add ../submodule_update_sub1 sub1 && + git submodule add ../submodule_update_sub1 uninitialized_sub && git config -f .gitmodules submodule.sub1.ignore all && git config submodule.sub1.ignore all && git add .gitmodules && @@ -780,18 +781,14 @@ test_submodule_forced_switch () { # - Removing a submodule with a git directory absorbs the submodules # git directory first into the superproject. -test_submodule_switch_recursing () { - command="$1" +test_submodule_switch_recursing_with_args () { + cmd_args="$1" + command="git $cmd_args --recurse-submodules" RESULTDS=success if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 then RESULTDS=failure fi - RESULTR=success - if test "$KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED" = 1 - then - RESULTR=failure - fi RESULTOI=success if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 then @@ -988,6 +985,18 @@ test_submodule_switch_recursing () { ) ' + test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t modify_sub1 origin/modify_sub1 && + git -c submodule.recurse=true $cmd_args modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 + ) + ' + # Updating a submodule to an invalid sha1 doesn't update the # superproject nor the submodule's work tree. test_expect_success "$command: updating to a missing submodule commit fails" ' @@ -1003,7 +1012,7 @@ test_submodule_switch_recursing () { ' # recursing deeper than one level doesn't work yet. - test_expect_$RESULTR "$command: modified submodule updates submodule recursively" ' + test_expect_success "$command: modified submodule updates submodule recursively" ' prolog && reset_work_tree_to_interested add_nested_sub && ( @@ -1020,8 +1029,9 @@ test_submodule_switch_recursing () { # Test that submodule contents are updated when switching between commits # that change a submodule, but throwing away local changes in # the superproject as well as the submodule is allowed. -test_submodule_forced_switch_recursing () { - command="$1" +test_submodule_forced_switch_recursing_with_args () { + cmd_args="$1" + command="git $cmd_args --recurse-submodules" RESULT=success if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 then @@ -1212,14 +1222,31 @@ test_submodule_forced_switch_recursing () { ) ' # Updating a submodule from an invalid sha1 updates - test_expect_success "$command: modified submodule does not update submodule work tree from invalid commit" ' + test_expect_success "$command: modified submodule does update submodule work tree from invalid commit" ' prolog && reset_work_tree_to_interested invalid_sub1 && ( cd submodule_update && git branch -t valid_sub1 origin/valid_sub1 && - test_must_fail $command valid_sub1 && - test_superproject_content origin/invalid_sub1 + $command valid_sub1 && + test_superproject_content origin/valid_sub1 && + test_submodule_content sub1 origin/valid_sub1 + ) + ' + + # Old versions of Git were buggy writing the .git link file + # (e.g. before f8eaa0ba98b and then moving the superproject repo + # whose submodules contained absolute paths) + test_expect_success "$command: updating submodules fixes .git links" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t modify_sub1 origin/modify_sub1 && + echo "gitdir: bogus/path" >sub1/.git && + $command modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 ) ' } diff --git a/t/perf/README b/t/perf/README index 49ea4349be..21321a0f36 100644 --- a/t/perf/README +++ b/t/perf/README @@ -60,7 +60,22 @@ You can set the following variables (also in your config.mak): GIT_PERF_MAKE_OPTS Options to use when automatically building a git tree for - performance testing. E.g., -j6 would be useful. + performance testing. E.g., -j6 would be useful. Passed + directly to make as "make $GIT_PERF_MAKE_OPTS". + + GIT_PERF_MAKE_COMMAND + An arbitrary command that'll be run in place of the make + command, if set the GIT_PERF_MAKE_OPTS variable is + ignored. Useful in cases where source tree changes might + require issuing a different make command to different + revisions. + + This can be (ab)used to monkeypatch or otherwise change the + tree about to be built. Note that the build directory can be + re-used for subsequent runs so the make command might get + executed multiple times on the same tree, but don't count on + any of that, that's an implementation detail that might change + in the future. GIT_PERF_REPO GIT_PERF_LARGE_REPO @@ -106,6 +121,7 @@ sources perf-lib.sh: After that you will want to use some of the following: + test_perf_fresh_repo # sets up an empty repository test_perf_default_repo # sets up a "normal" repository test_perf_large_repo # sets up a "large" repository diff --git a/t/perf/p0004-lazy-init-name-hash.sh b/t/perf/p0004-lazy-init-name-hash.sh index 5afa8c8df3..8de5a98cfc 100755 --- a/t/perf/p0004-lazy-init-name-hash.sh +++ b/t/perf/p0004-lazy-init-name-hash.sh @@ -7,13 +7,50 @@ test_perf_large_repo test_checkout_worktree test_expect_success 'verify both methods build the same hashmaps' ' - $GIT_BUILD_DIR/t/helper/test-lazy-init-name-hash$X --dump --single | sort >out.single && - $GIT_BUILD_DIR/t/helper/test-lazy-init-name-hash$X --dump --multi | sort >out.multi && - test_cmp out.single out.multi + test-lazy-init-name-hash --dump --single >out.single && + if test-lazy-init-name-hash --dump --multi >out.multi + then + test_set_prereq REPO_BIG_ENOUGH_FOR_MULTI && + sort <out.single >sorted.single && + sort <out.multi >sorted.multi && + test_cmp sorted.single sorted.multi + fi ' -test_expect_success 'multithreaded should be faster' ' - $GIT_BUILD_DIR/t/helper/test-lazy-init-name-hash$X --perf >out.perf +test_expect_success 'calibrate' ' + entries=$(wc -l <out.single) && + + case $entries in + ?) count=1000000 ;; + ??) count=100000 ;; + ???) count=10000 ;; + ????) count=1000 ;; + ?????) count=100 ;; + ??????) count=10 ;; + *) count=1 ;; + esac && + export count && + + case $entries in + 1) entries_desc="1 entry" ;; + *) entries_desc="$entries entries" ;; + esac && + + case $count in + 1) count_desc="1 round" ;; + *) count_desc="$count rounds" ;; + esac && + + desc="$entries_desc, $count_desc" && + export desc ' +test_perf "single-threaded, $desc" " + test-lazy-init-name-hash --single --count=$count +" + +test_perf REPO_BIG_ENOUGH_FOR_MULTI "multi-threaded, $desc" " + test-lazy-init-name-hash --multi --count=$count +" + test_done diff --git a/t/perf/p0100-globbing.sh b/t/perf/p0100-globbing.sh new file mode 100755 index 0000000000..dd18a9ce2b --- /dev/null +++ b/t/perf/p0100-globbing.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description="Tests pathological globbing performance + +Shows how Git's globbing performance performs when given the sort of +pathological patterns described in at https://research.swtch.com/glob +" + +. ./perf-lib.sh + +test_globs_big='10 25 50 75 100' +test_globs_small='1 2 3 4 5 6' + +test_perf_fresh_repo + +test_expect_success 'setup' ' + for i in $(test_seq 1 100) + do + printf "a" >>refname && + for j in $(test_seq 1 $i) + do + printf "a*" >>refglob.$i + done && + echo b >>refglob.$i + done && + test_commit test $(cat refname).t "" $(cat refname).t +' + +for i in $test_globs_small +do + test_perf "refglob((a*)^nb) against tag (a^100).t; n = $i" ' + git for-each-ref "refs/tags/$(cat refglob.'$i')b" + ' +done + +for i in $test_globs_small +do + test_perf "fileglob((a*)^nb) against file (a^100).t; n = $i" ' + git ls-files "$(cat refglob.'$i')b" + ' +done + +test_done diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh index b3e7d525d2..ce271ca4c1 100755 --- a/t/perf/p3400-rebase.sh +++ b/t/perf/p3400-rebase.sh @@ -5,7 +5,7 @@ test_description='Tests rebase performance' test_perf_default_repo -test_expect_success 'setup' ' +test_expect_success 'setup rebasing on top of a lot of changes' ' git checkout -f -b base && git checkout -b to-rebase && git checkout -b upstream && @@ -33,4 +33,24 @@ test_perf 'rebase on top of a lot of unrelated changes' ' git rebase --onto base HEAD^ ' +test_expect_success 'setup rebasing many changes without split-index' ' + git config core.splitIndex false && + git checkout -b upstream2 to-rebase && + git checkout -b to-rebase2 upstream +' + +test_perf 'rebase a lot of unrelated changes without split-index' ' + git rebase --onto upstream2 base && + git rebase --onto base upstream2 +' + +test_expect_success 'setup rebasing many changes with split-index' ' + git config core.splitIndex true +' + +test_perf 'rebase a lot of unrelated changes with split-index' ' + git rebase --onto upstream2 base && + git rebase --onto base upstream2 +' + test_done diff --git a/t/perf/p4220-log-grep-engines.sh b/t/perf/p4220-log-grep-engines.sh new file mode 100755 index 0000000000..2bc47ded4d --- /dev/null +++ b/t/perf/p4220-log-grep-engines.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +test_description="Comparison of git-log's --grep regex engines + +Set GIT_PERF_4220_LOG_OPTS in the environment to pass options to +git-grep. Make sure to include a leading space, +e.g. GIT_PERF_4220_LOG_OPTS=' -i'. Some options to try: + + -i + --invert-grep + -i --invert-grep +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +for pattern in \ + 'how.to' \ + '^how to' \ + '[how] to' \ + '\(e.t[^ ]*\|v.ry\) rare' \ + 'm\(ú\|u\)lt.b\(æ\|y\)te' +do + for engine in basic extended perl + do + if test $engine != "basic" + then + # Poor man's basic -> extended converter. + pattern=$(echo $pattern | sed 's/\\//g') + fi + if test $engine = "perl" && ! test_have_prereq PCRE + then + prereq="PCRE" + else + prereq="" + fi + test_perf $prereq "$engine log$GIT_PERF_4220_LOG_OPTS --grep='$pattern'" " + git -c grep.patternType=$engine log --pretty=format:%h$GIT_PERF_4220_LOG_OPTS --grep='$pattern' >'out.$engine' || : + " + done + + test_expect_success "assert that all engines found the same for$GIT_PERF_4220_LOG_OPTS '$pattern'" ' + test_cmp out.basic out.extended && + if test_have_prereq PCRE + then + test_cmp out.basic out.perl + fi + ' +done + +test_done diff --git a/t/perf/p4221-log-grep-engines-fixed.sh b/t/perf/p4221-log-grep-engines-fixed.sh new file mode 100755 index 0000000000..060971265a --- /dev/null +++ b/t/perf/p4221-log-grep-engines-fixed.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description="Comparison of git-log's --grep regex engines with -F + +Set GIT_PERF_4221_LOG_OPTS in the environment to pass options to +git-grep. Make sure to include a leading space, +e.g. GIT_PERF_4221_LOG_OPTS=' -i'. Some options to try: + + -i + --invert-grep + -i --invert-grep +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +for pattern in 'int' 'uncommon' 'æ' +do + for engine in fixed basic extended perl + do + if test $engine = "perl" && ! test_have_prereq PCRE + then + prereq="PCRE" + else + prereq="" + fi + test_perf $prereq "$engine log$GIT_PERF_4221_LOG_OPTS --grep='$pattern'" " + git -c grep.patternType=$engine log --pretty=format:%h$GIT_PERF_4221_LOG_OPTS --grep='$pattern' >'out.$engine' || : + " + done + + test_expect_success "assert that all engines found the same for$GIT_PERF_4221_LOG_OPTS '$pattern'" ' + test_cmp out.fixed out.basic && + test_cmp out.fixed out.extended && + if test_have_prereq PCRE + then + test_cmp out.fixed out.perl + fi + ' +done + +test_done diff --git a/t/perf/p7820-grep-engines.sh b/t/perf/p7820-grep-engines.sh new file mode 100755 index 0000000000..62aba19e76 --- /dev/null +++ b/t/perf/p7820-grep-engines.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +test_description="Comparison of git-grep's regex engines + +Set GIT_PERF_7820_GREP_OPTS in the environment to pass options to +git-grep. Make sure to include a leading space, +e.g. GIT_PERF_7820_GREP_OPTS=' -i'. Some options to try: + + -i + -w + -v + -vi + -vw + -viw +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +for pattern in \ + 'how.to' \ + '^how to' \ + '[how] to' \ + '\(e.t[^ ]*\|v.ry\) rare' \ + 'm\(ú\|u\)lt.b\(æ\|y\)te' +do + for engine in basic extended perl + do + if test $engine != "basic" + then + # Poor man's basic -> extended converter. + pattern=$(echo "$pattern" | sed 's/\\//g') + fi + if test $engine = "perl" && ! test_have_prereq PCRE + then + prereq="PCRE" + else + prereq="" + fi + test_perf $prereq "$engine grep$GIT_PERF_7820_GREP_OPTS '$pattern'" " + git -c grep.patternType=$engine grep$GIT_PERF_7820_GREP_OPTS -- '$pattern' >'out.$engine' || : + " + done + + test_expect_success "assert that all engines found the same for$GIT_PERF_7820_GREP_OPTS '$pattern'" ' + test_cmp out.basic out.extended && + if test_have_prereq PCRE + then + test_cmp out.basic out.perl + fi + ' +done + +test_done diff --git a/t/perf/p7821-grep-engines-fixed.sh b/t/perf/p7821-grep-engines-fixed.sh new file mode 100755 index 0000000000..c7ef1e198f --- /dev/null +++ b/t/perf/p7821-grep-engines-fixed.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +test_description="Comparison of git-grep's regex engines with -F + +Set GIT_PERF_7821_GREP_OPTS in the environment to pass options to +git-grep. Make sure to include a leading space, +e.g. GIT_PERF_7821_GREP_OPTS=' -w'. See p7820-grep-engines.sh for more +options to try. +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +for pattern in 'int' 'uncommon' 'æ' +do + for engine in fixed basic extended perl + do + if test $engine = "perl" && ! test_have_prereq PCRE + then + prereq="PCRE" + else + prereq="" + fi + test_perf $prereq "$engine grep$GIT_PERF_7821_GREP_OPTS $pattern" " + git -c grep.patternType=$engine grep$GIT_PERF_7821_GREP_OPTS $pattern >'out.$engine' || : + " + done + + test_expect_success "assert that all engines found the same for$GIT_PERF_7821_GREP_OPTS $pattern" ' + test_cmp out.fixed out.basic && + test_cmp out.fixed out.extended && + if test_have_prereq PCRE + then + test_cmp out.fixed out.perl + fi + ' +done + +test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index ab4b8b06ae..b50211b259 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -78,6 +78,10 @@ if test -z "$GIT_PERF_LARGE_REPO"; then GIT_PERF_LARGE_REPO=$TEST_DIRECTORY/.. fi +test_perf_do_repo_symlink_config_ () { + test_have_prereq SYMLINKS || git config core.symlinks false +} + test_perf_create_repo_from () { test "$#" = 2 || error "bug in the test script: not 2 parameters to test-create-repo" @@ -102,15 +106,29 @@ test_perf_create_repo_from () { ) && ( cd "$repo" && - "$MODERN_GIT" init -q && { - test_have_prereq SYMLINKS || - git config core.symlinks false - } && - mv .git/hooks .git/hooks-disabled 2>/dev/null + "$MODERN_GIT" init -q && + test_perf_do_repo_symlink_config_ && + mv .git/hooks .git/hooks-disabled 2>/dev/null && + if test -f .git/index.lock + then + # We may be copying a repo that can't run "git + # status" due to a locked index. Since we have + # a copy it's fine to remove the lock. + rm .git/index.lock + fi ) || error "failed to copy repository '$source' to '$repo'" } # call at least one of these to establish an appropriately-sized repository +test_perf_fresh_repo () { + repo="${1:-$TRASH_DIRECTORY}" + "$MODERN_GIT" init -q "$repo" && + ( + cd "$repo" && + test_perf_do_repo_symlink_config_ + ) +} + test_perf_default_repo () { test_perf_create_repo_from "${1:-$TRASH_DIRECTORY}" "$GIT_PERF_REPO" } diff --git a/t/perf/run b/t/perf/run index c788d713ae..beb4acc0e4 100755 --- a/t/perf/run +++ b/t/perf/run @@ -24,6 +24,7 @@ run_one_dir () { unpack_git_rev () { rev=$1 + echo "=== Unpacking $rev in build/$rev ===" mkdir -p build/$rev (cd "$(git rev-parse --show-cdup)" && git archive --format=tar $rev) | (cd build/$rev && tar x) @@ -37,8 +38,16 @@ build_git_rev () { cp "../../$config" "build/$rev/" fi done - (cd build/$rev && make $GIT_PERF_MAKE_OPTS) || - die "failed to build revision '$mydir'" + echo "=== Building $rev ===" + ( + cd build/$rev && + if test -n "$GIT_PERF_MAKE_COMMAND" + then + sh -c "$GIT_PERF_MAKE_COMMAND" + else + make $GIT_PERF_MAKE_OPTS + fi + ) || die "failed to build revision '$mydir'" } run_dirs_helper () { diff --git a/t/t0006-date.sh b/t/t0006-date.sh index 42d4ea61ef..7ac9466d50 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -31,9 +31,11 @@ check_show () { format=$1 time=$2 expect=$3 - test_expect_success $4 "show date ($format:$time)" ' + prereqs=$4 + zone=$5 + test_expect_success $prereqs "show date ($format:$time)" ' echo "$time -> $expect" >expect && - test-date show:$format "$time" >actual && + TZ=${zone:-$TZ} test-date show:"$format" "$time" >actual && test_cmp expect actual ' } @@ -51,6 +53,16 @@ check_show iso-local "$TIME" '2016-06-15 14:13:20 +0000' check_show raw-local "$TIME" '1466000000 +0000' check_show unix-local "$TIME" '1466000000' +check_show 'format:%z' "$TIME" '+0200' +check_show 'format-local:%z' "$TIME" '+0000' +check_show 'format:%Z' "$TIME" '' +check_show 'format-local:%Z' "$TIME" 'UTC' +check_show 'format:%%z' "$TIME" '%z' +check_show 'format-local:%%z' "$TIME" '%z' + +check_show 'format:%Y-%m-%d %H:%M:%S' "$TIME" '2016-06-15 16:13:20' +check_show 'format-local:%Y-%m-%d %H:%M:%S' "$TIME" '2016-06-15 09:13:20' '' EST5 + # arbitrary time absurdly far in the future FUTURE="5758122296 -0400" check_show iso "$FUTURE" "2152-06-19 18:24:56 -0400" TIME_IS_64BIT,TIME_T_IS_64BIT diff --git a/t/t0012-help.sh b/t/t0012-help.sh index 8faba2e8bc..487b92a5de 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh @@ -49,4 +49,16 @@ test_expect_success "--help does not work for guides" " test_i18ncmp expect actual " +test_expect_success 'generate builtin list' ' + git --list-builtins >builtins +' + +while read builtin +do + test_expect_success "$builtin can handle -h" ' + test_expect_code 129 git $builtin -h >output 2>&1 && + test_i18ngrep usage output + ' +done <builtins + test_done diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 12228b4aa6..e4739170aa 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -26,6 +26,47 @@ test_expect_success 'run_command can run a command' ' test_cmp empty err ' +test_expect_success !MINGW 'run_command can run a script without a #! line' ' + cat >hello <<-\EOF && + cat hello-script + EOF + chmod +x hello && + test-run-command run-command ./hello >actual 2>err && + + test_cmp hello-script actual && + test_cmp empty err +' + +test_expect_success 'run_command does not try to execute a directory' ' + test_when_finished "rm -rf bin1 bin2" && + mkdir -p bin1/greet bin2 && + write_script bin2/greet <<-\EOF && + cat bin2/greet + EOF + + PATH=$PWD/bin1:$PWD/bin2:$PATH \ + test-run-command run-command greet >actual 2>err && + test_cmp bin2/greet actual && + test_cmp empty err +' + +test_expect_success POSIXPERM 'run_command passes over non-executable file' ' + test_when_finished "rm -rf bin1 bin2" && + mkdir -p bin1 bin2 && + write_script bin1/greet <<-\EOF && + cat bin1/greet + EOF + chmod -x bin1/greet && + write_script bin2/greet <<-\EOF && + cat bin2/greet + EOF + + PATH=$PWD/bin1:$PWD/bin2:$PATH \ + test-run-command run-command greet >actual 2>err && + test_cmp bin2/greet actual && + test_cmp empty err +' + test_expect_success POSIXPERM 'run_command reports EACCES' ' cat hello-script >hello.sh && chmod -x hello.sh && diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh index a212460081..71b0d74b4d 100755 --- a/t/t0203-gettext-setlocale-sanity.sh +++ b/t/t0203-gettext-setlocale-sanity.sh @@ -8,7 +8,7 @@ test_description="The Git C functions aren't broken by setlocale(3)" . ./lib-gettext.sh test_expect_success 'git show a ISO-8859-1 commit under C locale' ' - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && test_commit "iso-c-commit" iso-under-c && git show >out 2>err && ! test -s err && @@ -16,7 +16,7 @@ test_expect_success 'git show a ISO-8859-1 commit under C locale' ' ' test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' ' - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && test_commit "iso-utf8-commit" iso-under-utf8 && LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err && ! test -s err && diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh index de1ba02dc5..91a6fafcb4 100755 --- a/t/t1013-read-tree-submodule.sh +++ b/t/t1013-read-tree-submodule.sh @@ -5,13 +5,12 @@ test_description='read-tree can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh -KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1 KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1 -test_submodule_switch_recursing "git read-tree --recurse-submodules -u -m" +test_submodule_switch_recursing_with_args "read-tree -u -m" -test_submodule_forced_switch_recursing "git read-tree --recurse-submodules -u --reset" +test_submodule_forced_switch_recursing_with_args "read-tree -u --reset" test_submodule_switch "git read-tree -u -m" diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index afcca0d52c..a37ef04222 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -703,6 +703,12 @@ test_expect_success 'invalid unit' ' test_i18ngrep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual ' +test_expect_success 'line number is reported correctly' ' + printf "[bool]\n\tvar\n" >invalid && + test_must_fail git config -f invalid --path bool.var 2>actual && + test_i18ngrep "line 2" actual +' + test_expect_success 'invalid stdin config' ' echo "[broken" | test_must_fail git config --list --file - >output 2>&1 && test_i18ngrep "bad config line 1 in standard input" output @@ -1539,4 +1545,10 @@ test_expect_success !MINGW '--show-origin blob ref' ' test_cmp expect output ' +test_expect_success '--local requires a repo' ' + # we expect 128 to ensure that we do not simply + # fail to find anything and return code "1" + test_expect_code 128 nongit git config --local foo.bar +' + test_done diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index 933915ec06..d9d2f545a4 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh @@ -273,6 +273,29 @@ test_expect_success SYMLINKS 'conditional include, relative path with symlinks' ) ' +test_expect_success SYMLINKS 'conditional include, gitdir matching symlink' ' + ln -s foo bar && + ( + cd bar && + echo "[includeIf \"gitdir:bar/\"]path=bar7" >>.git/config && + echo "[test]seven=7" >.git/bar7 && + echo 7 >expect && + git config test.seven >actual && + test_cmp expect actual + ) +' + +test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icase' ' + ( + cd bar && + echo "[includeIf \"gitdir/i:BAR/\"]path=bar8" >>.git/config && + echo "[test]eight=8" >.git/bar8 && + echo 8 >expect && + git config test.eight >actual && + test_cmp expect actual + ) +' + test_expect_success 'include cycles are detected' ' cat >.gitconfig <<-\EOF && [test]value = gitconfig diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh index ff50960cca..bafed5c9b8 100755 --- a/t/t1308-config-set.sh +++ b/t/t1308-config-set.sh @@ -183,11 +183,22 @@ test_expect_success 'proper error on non-existent files' ' test_cmp expect actual ' +test_expect_success 'proper error on directory "files"' ' + echo "Error (-1) reading configuration file a-directory." >expect && + mkdir a-directory && + test_expect_code 2 test-config configset_get_value foo.bar a-directory 2>output && + grep "^warning:" output && + grep "^Error" output >actual && + test_cmp expect actual +' + test_expect_success POSIXPERM,SANITY 'proper error on non-accessible files' ' chmod -r .git/config && test_when_finished "chmod +r .git/config" && echo "Error (-1) reading configuration file .git/config." >expect && - test_expect_code 2 test-config configset_get_value foo.bar .git/config 2>actual && + test_expect_code 2 test-config configset_get_value foo.bar .git/config 2>output && + grep "^warning:" output && + grep "^Error" output >actual && test_cmp expect actual ' @@ -215,7 +226,9 @@ test_expect_success 'check line errors for malformed values' ' br EOF test_expect_code 128 git br 2>result && - test_i18ngrep "fatal: .*alias\.br.*\.git/config.*line 2" result + test_i18ngrep "missing value for .alias\.br" result && + test_i18ngrep "fatal: .*\.git/config" result && + test_i18ngrep "fatal: .*line 2" result ' test_expect_success 'error on modifying repo config without repo' ' diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh index e8f70b806f..6ef15738e4 100755 --- a/t/t2013-checkout-submodule.sh +++ b/t/t2013-checkout-submodule.sh @@ -64,10 +64,9 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ ' KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 -KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1 -test_submodule_switch_recursing "git checkout --recurse-submodules" +test_submodule_switch_recursing_with_args "checkout" -test_submodule_forced_switch_recursing "git checkout -f --recurse-submodules" +test_submodule_forced_switch_recursing_with_args "checkout -f" test_submodule_switch "git checkout" diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh index ef509df351..8fd70d3aa2 100755 --- a/t/t3070-wildmatch.sh +++ b/t/t3070-wildmatch.sh @@ -82,6 +82,7 @@ match 1 0 'foo/bar' 'foo/**/bar' match 1 0 'foo/bar' 'foo/**/**/bar' match 0 0 'foo/bar' 'foo?bar' match 0 0 'foo/bar' 'foo[/]bar' +match 0 0 'foo/bar' 'foo[^a-z]bar' match 0 0 'foo/bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r' match 1 1 'foo-bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r' match 1 0 'foo' '**/foo' @@ -135,7 +136,6 @@ match 1 x '5' '[[:xdigit:]]' match 1 x 'f' '[[:xdigit:]]' match 1 x 'D' '[[:xdigit:]]' match 1 x '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]' -match 1 x '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]' match 1 x '.' '[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]' match 1 x '5' '[a-c[:digit:]x-z]' match 1 x 'b' '[a-c[:digit:]x-z]' @@ -226,6 +226,7 @@ pathmatch 0 foo/bba/arr 'foo/*z' pathmatch 0 foo/bba/arr 'foo/**z' pathmatch 1 foo/bar 'foo?bar' pathmatch 1 foo/bar 'foo[/]bar' +pathmatch 1 foo/bar 'foo[^a-z]bar' pathmatch 0 foo '*/*/*' pathmatch 0 foo/bar '*/*/*' pathmatch 1 foo/bba/arr '*/*/*' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index fe62e7c775..48d152b9a9 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -100,6 +100,23 @@ test_expect_success 'git branch -m n/n n should work' ' git reflog exists refs/heads/n ' +# The topmost entry in reflog for branch bbb is about branch creation. +# Hence, we compare bbb@{1} (instead of bbb@{0}) with aaa@{0}. + +test_expect_success 'git branch -m bbb should rename checked out branch' ' + test_when_finished git branch -D bbb && + test_when_finished git checkout master && + git checkout -b aaa && + git commit --allow-empty -m "a new commit" && + git rev-parse aaa@{0} >expect && + git branch -m bbb && + git rev-parse bbb@{1} >actual && + test_cmp expect actual && + git symbolic-ref HEAD >actual && + echo refs/heads/bbb >expect && + test_cmp expect actual +' + test_expect_success 'git branch -m o/o o should fail when o/p exists' ' git branch o/o && git branch o/p && @@ -338,7 +355,7 @@ test_expect_success 'git branch -m s/s s should work when s/t is deleted' ' test_expect_success 'config information was renamed, too' ' test $(git config branch.s.dummy) = Hello && - test_must_fail git config branch.s/s/dummy + test_must_fail git config branch.s/s.dummy ' test_expect_success 'deleting a symref' ' diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index f663d567c8..923eb01f0e 100755 --- a/t/t3901-i18n-patch.sh +++ b/t/t3901-i18n-patch.sh @@ -31,7 +31,7 @@ test_expect_success setup ' # use UTF-8 in author and committer name to match the # i18n.commitencoding settings - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && test_tick && echo "$GIT_AUTHOR_NAME" >mine && @@ -55,7 +55,7 @@ test_expect_success setup ' # 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 + . "$TEST_DIRECTORY"/t3901/8859-1.txt fi && test_tick && echo Yet another >theirs && @@ -100,7 +100,7 @@ test_expect_success 'rebase (U/U)' ' # The result will be committed by GIT_COMMITTER_NAME -- # we want UTF-8 encoded name. - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git checkout -b test && git rebase master && @@ -110,7 +110,7 @@ test_expect_success 'rebase (U/U)' ' test_expect_success 'rebase (U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard side && git rebase master && @@ -122,7 +122,7 @@ test_expect_success !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 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard side && git rebase master && @@ -135,7 +135,7 @@ test_expect_success !MINGW 'rebase (L/U)' ' # to get ISO-8859-1 results. git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard side && git rebase master && @@ -148,7 +148,7 @@ test_expect_success 'cherry-pick(U/U)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && git cherry-pick side^ && @@ -163,7 +163,7 @@ test_expect_success !MINGW 'cherry-pick(L/L)' ' git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard master && git cherry-pick side^ && @@ -178,7 +178,7 @@ test_expect_success 'cherry-pick(U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && git cherry-pick side^ && @@ -194,7 +194,7 @@ test_expect_success !MINGW 'cherry-pick(L/U)' ' git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard master && git cherry-pick side^ && @@ -207,7 +207,7 @@ test_expect_success !MINGW 'cherry-pick(L/U)' ' test_expect_success 'rebase --merge (U/U)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard side && git rebase --merge master && @@ -218,7 +218,7 @@ test_expect_success 'rebase --merge (U/U)' ' test_expect_success 'rebase --merge (U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard side && git rebase --merge master && @@ -230,7 +230,7 @@ test_expect_success 'rebase --merge (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 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard side && git rebase --merge master && @@ -243,7 +243,7 @@ test_expect_success 'rebase --merge (L/U)' ' # to get ISO-8859-1 results. git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard side && git rebase --merge master && @@ -254,7 +254,7 @@ test_expect_success 'rebase --merge (L/U)' ' test_expect_success 'am (U/U)' ' # Apply UTF-8 patches with UTF-8 commitencoding git config i18n.commitencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && git am out-u1 out-u2 && @@ -265,7 +265,7 @@ test_expect_success 'am (U/U)' ' test_expect_success !MINGW 'am (L/L)' ' # Apply ISO-8859-1 patches with ISO-8859-1 commitencoding git config i18n.commitencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard master && git am out-l1 out-l2 && @@ -276,7 +276,7 @@ test_expect_success !MINGW 'am (L/L)' ' test_expect_success 'am (U/L)' ' # Apply ISO-8859-1 patches with UTF-8 commitencoding git config i18n.commitencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && # am specifies --utf8 by default. @@ -288,7 +288,7 @@ test_expect_success 'am (U/L)' ' test_expect_success 'am --no-utf8 (U/L)' ' # Apply ISO-8859-1 patches with UTF-8 commitencoding git config i18n.commitencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && git am --no-utf8 out-l1 out-l2 2>err && @@ -303,7 +303,7 @@ test_expect_success 'am --no-utf8 (U/L)' ' test_expect_success !MINGW 'am (L/U)' ' # Apply UTF-8 patches with ISO-8859-1 commitencoding git config i18n.commitencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard master && # mailinfo will re-code the commit message to the charset specified by diff --git a/t/t3901-8859-1.txt b/t/t3901/8859-1.txt index 38c21a6a7f..38c21a6a7f 100755 --- a/t/t3901-8859-1.txt +++ b/t/t3901/8859-1.txt diff --git a/t/t3901-utf8.txt b/t/t3901/utf8.txt index 5f5205cd02..5f5205cd02 100755 --- a/t/t3901-utf8.txt +++ b/t/t3901/utf8.txt diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 3b4bed5c9a..4046817d70 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -812,6 +812,22 @@ test_expect_success 'stash -- <pathspec> stashes and restores the file' ' test_path_is_file bar ' +test_expect_success 'stash -- <pathspec> stashes in subdirectory' ' + mkdir sub && + >foo && + >bar && + git add foo bar && + ( + cd sub && + git stash push -- ../foo + ) && + test_path_is_file bar && + test_path_is_missing foo && + git stash pop && + test_path_is_file foo && + test_path_is_file bar +' + test_expect_success 'stash with multiple pathspec arguments' ' >foo && >bar && diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh index 135addbfbd..f542d2929d 100755 --- a/t/t4005-diff-rename-2.sh +++ b/t/t4005-diff-rename-2.sh @@ -3,84 +3,75 @@ # Copyright (c) 2005 Junio C Hamano # -test_description='Same rename detection as t4003 but testing diff-raw. +test_description='Same rename detection as t4003 but testing diff-raw.' -' . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash -test_expect_success \ - 'prepare reference tree' \ - 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && - echo frotz >rezrov && - git update-index --add COPYING rezrov && - tree=$(git write-tree) && - echo $tree' - -test_expect_success \ - 'prepare work tree' \ - 'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 && - sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 && - rm -f COPYING && - git update-index --add --remove COPYING COPYING.?' +test_expect_success 'setup reference tree' ' + cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && + echo frotz >rezrov && + git update-index --add COPYING rezrov && + tree=$(git write-tree) && + echo $tree && + sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 && + sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 && + origoid=$(git hash-object COPYING) && + oid1=$(git hash-object COPYING.1) && + oid2=$(git hash-object COPYING.2) +' +################################################################ # tree has COPYING and rezrov. work tree has COPYING.1 and COPYING.2, # both are slightly edited, and unchanged rezrov. We say COPYING.1 # and COPYING.2 are based on COPYING, and do not say anything about # rezrov. -git diff-index -C $tree >current - -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234 COPYING COPYING.2 -EOF +test_expect_success 'validate output from rename/copy detection (#1)' ' + rm -f COPYING && + git update-index --add --remove COPYING COPYING.? && -test_expect_success \ - 'validate output from rename/copy detection (#1)' \ - 'compare_diff_raw current expected' + cat <<-EOF >expected && + :100644 100644 $origoid $oid1 C1234 COPYING COPYING.1 + :100644 100644 $origoid $oid2 R1234 COPYING COPYING.2 + EOF + git diff-index -C $tree >current && + compare_diff_raw expected current +' ################################################################ - -test_expect_success \ - 'prepare work tree again' \ - 'mv COPYING.2 COPYING && - git update-index --add --remove COPYING COPYING.1 COPYING.2' - # tree has COPYING and rezrov. work tree has COPYING and COPYING.1, # both are slightly edited, and unchanged rezrov. We say COPYING.1 # is based on COPYING and COPYING is still there, and do not say anything # about rezrov. -git diff-index -C $tree >current -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M COPYING -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1 -EOF +test_expect_success 'validate output from rename/copy detection (#2)' ' + mv COPYING.2 COPYING && + git update-index --add --remove COPYING COPYING.1 COPYING.2 && -test_expect_success \ - 'validate output from rename/copy detection (#2)' \ - 'compare_diff_raw current expected' + cat <<-EOF >expected && + :100644 100644 $origoid $oid2 M COPYING + :100644 100644 $origoid $oid1 C1234 COPYING COPYING.1 + EOF + git diff-index -C $tree >current && + compare_diff_raw current expected +' ################################################################ - # tree has COPYING and rezrov. work tree has the same COPYING and # copy-edited COPYING.1, and unchanged rezrov. We should not say # anything about rezrov or COPYING, since the revised again diff-raw # nows how to say Copy. -test_expect_success \ - 'prepare work tree once again' \ - 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && - git update-index --add --remove COPYING COPYING.1' - -git diff-index -C --find-copies-harder $tree >current -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1 -EOF +test_expect_success 'validate output from rename/copy detection (#3)' ' + cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && + git update-index --add --remove COPYING COPYING.1 && -test_expect_success \ - 'validate output from rename/copy detection (#3)' \ - 'compare_diff_raw current expected' + cat <<-EOF >expected && + :100644 100644 $origoid $oid1 C1234 COPYING COPYING.1 + EOF + git diff-index -C --find-copies-harder $tree >current && + compare_diff_raw current expected +' test_done diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 2d9731b52d..058ee0829d 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -430,9 +430,11 @@ test_expect_success 'deleted submodule' ' test_cmp expected actual ' -test_create_repo sm2 && -head7=$(add_file sm2 foo8 foo9) && -git add sm2 +test_expect_success 'create second submodule' ' + test_create_repo sm2 && + head7=$(add_file sm2 foo8 foo9) && + git add sm2 +' test_expect_success 'multiple submodules' ' git diff-index -p --submodule=log HEAD >actual && diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh index 6154acb456..3e6b485ecb 100755 --- a/t/t4051-diff-function-context.sh +++ b/t/t4051-diff-function-context.sh @@ -72,7 +72,8 @@ test_expect_success 'setup' ' # overlap function context of 1st change and -u context of 2nd change grep -v "delete me from hello" <"$dir/hello.c" >file.c && - sed 2p <"$dir/dummy.c" >>file.c && + sed "2a\\ + extra line" <"$dir/dummy.c" >>file.c && commit_and_tag changed_hello_dummy file.c && git checkout initial && diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh index d4a3ffa69c..4b168d0ed7 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -643,9 +643,11 @@ test_expect_success 'deleted submodule' ' test_cmp expected actual ' -test_create_repo sm2 && -head7=$(add_file sm2 foo8 foo9) && -git add sm2 +test_expect_success 'create second submodule' ' + test_create_repo sm2 && + head7=$(add_file sm2 foo8 foo9) && + git add sm2 +' test_expect_success 'multiple submodules' ' git diff-index -p --submodule=diff HEAD >actual && @@ -775,4 +777,45 @@ test_expect_success 'diff --submodule=diff with moved nested submodule HEAD' ' test_cmp expected actual ' +test_expect_success 'diff --submodule=diff recurses into nested submodules' ' + cat >expected <<-EOF && + Submodule sm2 contains modified content + Submodule sm2 a5a65c9..280969a: + diff --git a/sm2/.gitmodules b/sm2/.gitmodules + new file mode 100644 + index 0000000..3a816b8 + --- /dev/null + +++ b/sm2/.gitmodules + @@ -0,0 +1,3 @@ + +[submodule "nested"] + + path = nested + + url = ../sm2 + Submodule nested 0000000...b55928c (new submodule) + diff --git a/sm2/nested/file b/sm2/nested/file + new file mode 100644 + index 0000000..ca281f5 + --- /dev/null + +++ b/sm2/nested/file + @@ -0,0 +1 @@ + +nested content + diff --git a/sm2/nested/foo8 b/sm2/nested/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/nested/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/nested/foo9 b/sm2/nested/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/nested/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + git diff --submodule=diff >actual 2>err && + test_must_be_empty err && + test_cmp expected actual +' + test_done diff --git a/t/t4061-diff-indent.sh b/t/t4061-diff-indent.sh index 556450609b..2affd7a100 100755 --- a/t/t4061-diff-indent.sh +++ b/t/t4061-diff-indent.sh @@ -152,26 +152,28 @@ test_expect_success 'prepare' ' EOF ' +# --- diff tests ---------------------------------------------------------- + test_expect_success 'diff: ugly spaces' ' - git diff old new -- spaces.txt >out && + git diff --no-indent-heuristic old new -- spaces.txt >out && compare_diff spaces-expect out ' +test_expect_success 'diff: --no-indent-heuristic overrides config' ' + git -c diff.indentHeuristic=true diff --no-indent-heuristic old new -- spaces.txt >out2 && + compare_diff spaces-expect out2 +' + test_expect_success 'diff: nice spaces with --indent-heuristic' ' - git diff --indent-heuristic old new -- spaces.txt >out-compacted && + git -c diff.indentHeuristic=false diff --indent-heuristic old new -- spaces.txt >out-compacted && compare_diff spaces-compacted-expect out-compacted ' -test_expect_success 'diff: nice spaces with diff.indentHeuristic' ' +test_expect_success 'diff: nice spaces with diff.indentHeuristic=true' ' git -c diff.indentHeuristic=true diff old new -- spaces.txt >out-compacted2 && compare_diff spaces-compacted-expect out-compacted2 ' -test_expect_success 'diff: --no-indent-heuristic overrides config' ' - git -c diff.indentHeuristic=true diff --no-indent-heuristic old new -- spaces.txt >out2 && - compare_diff spaces-expect out2 -' - test_expect_success 'diff: --indent-heuristic with --patience' ' git diff --indent-heuristic --patience old new -- spaces.txt >out-compacted3 && compare_diff spaces-compacted-expect out-compacted3 @@ -183,7 +185,7 @@ test_expect_success 'diff: --indent-heuristic with --histogram' ' ' test_expect_success 'diff: ugly functions' ' - git diff old new -- functions.c >out && + git diff --no-indent-heuristic old new -- functions.c >out && compare_diff functions-expect out ' @@ -192,25 +194,175 @@ test_expect_success 'diff: nice functions with --indent-heuristic' ' compare_diff functions-compacted-expect out-compacted ' -test_expect_success 'blame: ugly spaces' ' - git blame old..new -- spaces.txt >out-blame && - compare_blame spaces-expect out-blame -' +# --- blame tests --------------------------------------------------------- test_expect_success 'blame: nice spaces with --indent-heuristic' ' git blame --indent-heuristic old..new -- spaces.txt >out-blame-compacted && compare_blame spaces-compacted-expect out-blame-compacted ' -test_expect_success 'blame: nice spaces with diff.indentHeuristic' ' +test_expect_success 'blame: nice spaces with diff.indentHeuristic=true' ' git -c diff.indentHeuristic=true blame old..new -- spaces.txt >out-blame-compacted2 && compare_blame spaces-compacted-expect out-blame-compacted2 ' +test_expect_success 'blame: ugly spaces with --no-indent-heuristic' ' + git blame --no-indent-heuristic old..new -- spaces.txt >out-blame && + compare_blame spaces-expect out-blame +' + +test_expect_success 'blame: ugly spaces with diff.indentHeuristic=false' ' + git -c diff.indentHeuristic=false blame old..new -- spaces.txt >out-blame2 && + compare_blame spaces-expect out-blame2 +' + test_expect_success 'blame: --no-indent-heuristic overrides config' ' - git -c diff.indentHeuristic=true blame --no-indent-heuristic old..new -- spaces.txt >out-blame2 && + git -c diff.indentHeuristic=true blame --no-indent-heuristic old..new -- spaces.txt >out-blame3 && git blame old..new -- spaces.txt >out-blame && - compare_blame spaces-expect out-blame2 + compare_blame spaces-expect out-blame3 +' + +test_expect_success 'blame: --indent-heuristic overrides config' ' + git -c diff.indentHeuristic=false blame --indent-heuristic old..new -- spaces.txt >out-blame-compacted3 && + compare_blame spaces-compacted-expect out-blame-compacted2 +' + +# --- diff-tree tests ----------------------------------------------------- + +test_expect_success 'diff-tree: nice spaces with --indent-heuristic' ' + git diff-tree --indent-heuristic -p old new -- spaces.txt >out-diff-tree-compacted && + compare_diff spaces-compacted-expect out-diff-tree-compacted +' + +test_expect_success 'diff-tree: nice spaces with diff.indentHeuristic=true' ' + git -c diff.indentHeuristic=true diff-tree -p old new -- spaces.txt >out-diff-tree-compacted2 && + compare_diff spaces-compacted-expect out-diff-tree-compacted2 +' + +test_expect_success 'diff-tree: ugly spaces with --no-indent-heuristic' ' + git diff-tree --no-indent-heuristic -p old new -- spaces.txt >out-diff-tree && + compare_diff spaces-expect out-diff-tree +' + +test_expect_success 'diff-tree: ugly spaces with diff.indentHeuristic=false' ' + git -c diff.indentHeuristic=false diff-tree -p old new -- spaces.txt >out-diff-tree2 && + compare_diff spaces-expect out-diff-tree2 +' + +test_expect_success 'diff-tree: --indent-heuristic overrides config' ' + git -c diff.indentHeuristic=false diff-tree --indent-heuristic -p old new -- spaces.txt >out-diff-tree-compacted3 && + compare_diff spaces-compacted-expect out-diff-tree-compacted3 +' + +test_expect_success 'diff-tree: --no-indent-heuristic overrides config' ' + git -c diff.indentHeuristic=true diff-tree --no-indent-heuristic -p old new -- spaces.txt >out-diff-tree3 && + compare_diff spaces-expect out-diff-tree3 +' + +# --- diff-index tests ---------------------------------------------------- + +test_expect_success 'diff-index: nice spaces with --indent-heuristic' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git diff-index --indent-heuristic -p old -- spaces.txt >out-diff-index-compacted && + compare_diff spaces-compacted-expect out-diff-index-compacted && + git checkout -f master +' + +test_expect_success 'diff-index: nice spaces with diff.indentHeuristic=true' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git -c diff.indentHeuristic=true diff-index -p old -- spaces.txt >out-diff-index-compacted2 && + compare_diff spaces-compacted-expect out-diff-index-compacted2 && + git checkout -f master +' + +test_expect_success 'diff-index: ugly spaces with --no-indent-heuristic' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git diff-index --no-indent-heuristic -p old -- spaces.txt >out-diff-index && + compare_diff spaces-expect out-diff-index && + git checkout -f master +' + +test_expect_success 'diff-index: ugly spaces with diff.indentHeuristic=false' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git -c diff.indentHeuristic=false diff-index -p old -- spaces.txt >out-diff-index2 && + compare_diff spaces-expect out-diff-index2 && + git checkout -f master +' + +test_expect_success 'diff-index: --indent-heuristic overrides config' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git -c diff.indentHeuristic=false diff-index --indent-heuristic -p old -- spaces.txt >out-diff-index-compacted3 && + compare_diff spaces-compacted-expect out-diff-index-compacted3 && + git checkout -f master +' + +test_expect_success 'diff-index: --no-indent-heuristic overrides config' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git -c diff.indentHeuristic=true diff-index --no-indent-heuristic -p old -- spaces.txt >out-diff-index3 && + compare_diff spaces-expect out-diff-index3 && + git checkout -f master +' + +# --- diff-files tests ---------------------------------------------------- + +test_expect_success 'diff-files: nice spaces with --indent-heuristic' ' + git checkout -B diff-files && + git reset HEAD~ && + git diff-files --indent-heuristic -p spaces.txt >out-diff-files-raw && + grep -v index out-diff-files-raw >out-diff-files-compacted && + compare_diff spaces-compacted-expect out-diff-files-compacted && + git checkout -f master +' + +test_expect_success 'diff-files: nice spaces with diff.indentHeuristic=true' ' + git checkout -B diff-files && + git reset HEAD~ && + git -c diff.indentHeuristic=true diff-files -p spaces.txt >out-diff-files-raw2 && + grep -v index out-diff-files-raw2 >out-diff-files-compacted2 && + compare_diff spaces-compacted-expect out-diff-files-compacted2 && + git checkout -f master +' + +test_expect_success 'diff-files: ugly spaces with --no-indent-heuristic' ' + git checkout -B diff-files && + git reset HEAD~ && + git diff-files --no-indent-heuristic -p spaces.txt >out-diff-files-raw && + grep -v index out-diff-files-raw >out-diff-files && + compare_diff spaces-expect out-diff-files && + git checkout -f master +' + +test_expect_success 'diff-files: ugly spaces with diff.indentHeuristic=false' ' + git checkout -B diff-files && + git reset HEAD~ && + git -c diff.indentHeuristic=false diff-files -p spaces.txt >out-diff-files-raw2 && + grep -v index out-diff-files-raw2 >out-diff-files && + compare_diff spaces-expect out-diff-files && + git checkout -f master +' + +test_expect_success 'diff-files: --indent-heuristic overrides config' ' + git checkout -B diff-files && + git reset HEAD~ && + git -c diff.indentHeuristic=false diff-files --indent-heuristic -p spaces.txt >out-diff-files-raw3 && + grep -v index out-diff-files-raw3 >out-diff-files-compacted && + compare_diff spaces-compacted-expect out-diff-files-compacted && + git checkout -f master +' + +test_expect_success 'diff-files: --no-indent-heuristic overrides config' ' + git checkout -B diff-files && + git reset HEAD~ && + git -c diff.indentHeuristic=true diff-files --no-indent-heuristic -p spaces.txt >out-diff-files-raw4 && + grep -v index out-diff-files-raw4 >out-diff-files && + compare_diff spaces-expect out-diff-files && + git checkout -f master ' test_done diff --git a/t/t4063-diff-blobs.sh b/t/t4063-diff-blobs.sh new file mode 100755 index 0000000000..bc69e26c52 --- /dev/null +++ b/t/t4063-diff-blobs.sh @@ -0,0 +1,96 @@ +#!/bin/sh + +test_description='test direct comparison of blobs via git-diff' +. ./test-lib.sh + +run_diff () { + # use full-index to make it easy to match the index line + git diff --full-index "$@" >diff +} + +check_index () { + grep "^index $1\\.\\.$2" diff +} + +check_mode () { + grep "^old mode $1" diff && + grep "^new mode $2" diff +} + +check_paths () { + grep "^diff --git a/$1 b/$2" diff +} + +test_expect_success 'create some blobs' ' + echo one >one && + echo two >two && + chmod +x two && + git add . && + + # cover systems where modes are ignored + git update-index --chmod=+x two && + + git commit -m base && + + sha1_one=$(git rev-parse HEAD:one) && + sha1_two=$(git rev-parse HEAD:two) +' + +test_expect_success 'diff by sha1' ' + run_diff $sha1_one $sha1_two +' +test_expect_success 'index of sha1 diff' ' + check_index $sha1_one $sha1_two +' +test_expect_success 'sha1 diff uses arguments as paths' ' + check_paths $sha1_one $sha1_two +' +test_expect_success 'sha1 diff has no mode change' ' + ! grep mode diff +' + +test_expect_success 'diff by tree:path (run)' ' + run_diff HEAD:one HEAD:two +' +test_expect_success 'index of tree:path diff' ' + check_index $sha1_one $sha1_two +' +test_expect_success 'tree:path diff uses filenames as paths' ' + check_paths one two +' +test_expect_success 'tree:path diff shows mode change' ' + check_mode 100644 100755 +' + +test_expect_success 'diff by ranged tree:path' ' + run_diff HEAD:one..HEAD:two +' +test_expect_success 'index of ranged tree:path diff' ' + check_index $sha1_one $sha1_two +' +test_expect_success 'ranged tree:path diff uses filenames as paths' ' + check_paths one two +' +test_expect_success 'ranged tree:path diff shows mode change' ' + check_mode 100644 100755 +' + +test_expect_success 'diff blob against file' ' + run_diff HEAD:one two +' +test_expect_success 'index of blob-file diff' ' + check_index $sha1_one $sha1_two +' +test_expect_success 'blob-file diff uses filename as paths' ' + check_paths one two +' +test_expect_success FILEMODE 'blob-file diff shows mode change' ' + check_mode 100644 100755 +' + +test_expect_success 'blob-file diff prefers filename to sha1' ' + run_diff $sha1_one two && + check_paths two two +' + +test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 1c7d6729c6..3f3531f0a4 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -231,14 +231,47 @@ second initial EOF test_expect_success 'log --invert-grep --grep' ' - git log --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual && - test_cmp expect actual + # Fixed + git -c grep.patternType=fixed log --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual && + test_cmp expect actual && + + # POSIX basic + git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual && + + # POSIX extended + git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual && + + # PCRE + if test_have_prereq PCRE + then + git -c grep.patternType=perl log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual + fi ' test_expect_success 'log --invert-grep --grep -i' ' echo initial >expect && - git log --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual && - test_cmp expect actual + + # Fixed + git -c grep.patternType=fixed log --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual && + test_cmp expect actual && + + # POSIX basic + git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual && + + # POSIX extended + git -c grep.patternType=extended log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual && + + # PCRE + if test_have_prereq PCRE + then + git -c grep.patternType=perl log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual + fi ' test_expect_success 'log --grep option parsing' ' @@ -256,13 +289,53 @@ test_expect_success 'log -i --grep' ' test_expect_success 'log --grep -i' ' echo Second >expect && + + # Fixed git log -1 --pretty="tformat:%s" --grep=sec -i >actual && - test_cmp expect actual + test_cmp expect actual && + + # POSIX basic + git -c grep.patternType=basic log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual && + test_cmp expect actual && + + # POSIX extended + git -c grep.patternType=extended log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual && + test_cmp expect actual && + + # PCRE + if test_have_prereq PCRE + then + git -c grep.patternType=perl log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual && + test_cmp expect actual + fi ' test_expect_success 'log -F -E --grep=<ere> uses ere' ' echo second >expect && - git log -1 --pretty="tformat:%s" -F -E --grep=s.c.nd >actual && + # basic would need \(s\) to do the same + git log -1 --pretty="tformat:%s" -F -E --grep="(s).c.nd" >actual && + test_cmp expect actual +' + +test_expect_success PCRE 'log -F -E --perl-regexp --grep=<pcre> uses PCRE' ' + test_when_finished "rm -rf num_commits" && + git init num_commits && + ( + cd num_commits && + test_commit 1d && + test_commit 2e + ) && + + # In PCRE \d in [\d] is like saying "0-9", and matches the 2 + # in 2e... + echo 2e >expect && + git -C num_commits log -1 --pretty="tformat:%s" -F -E --perl-regexp --grep="[\d]" >actual && + test_cmp expect actual && + + # ...in POSIX basic and extended it is the same as [d], + # i.e. "d", which matches 1d, but does not match 2e. + echo 1d >expect && + git -C num_commits log -1 --pretty="tformat:%s" -F -E --grep="[\d]" >actual && test_cmp expect actual ' @@ -280,6 +353,93 @@ test_expect_success 'log with grep.patternType configuration and command line' ' test_cmp expect actual ' +test_expect_success 'log with various grep.patternType configurations & command-lines' ' + git init pattern-type && + ( + cd pattern-type && + test_commit 1 file A && + + # The tagname is overridden here because creating a + # tag called "(1|2)" as test_commit would otherwise + # implicitly do would fail on e.g. MINGW. + test_commit "(1|2)" file B 2 && + + echo "(1|2)" >expect.fixed && + cp expect.fixed expect.basic && + cp expect.fixed expect.extended && + cp expect.fixed expect.perl && + + # A strcmp-like match with fixed. + git -c grep.patternType=fixed log --pretty=tformat:%s \ + --grep="(1|2)" >actual.fixed && + + # POSIX basic matches (, | and ) literally. + git -c grep.patternType=basic log --pretty=tformat:%s \ + --grep="(.|.)" >actual.basic && + + # POSIX extended needs to have | escaped to match it + # literally, whereas under basic this is the same as + # (|2), i.e. it would also match "1". This test checks + # for extended by asserting that it is not matching + # what basic would match. + git -c grep.patternType=extended log --pretty=tformat:%s \ + --grep="\|2" >actual.extended && + if test_have_prereq PCRE + then + # Only PCRE would match [\d]\| with only + # "(1|2)" due to [\d]. POSIX basic would match + # both it and "1" since similarly to the + # extended match above it is the same as + # \([\d]\|\). POSIX extended would + # match neither. + git -c grep.patternType=perl log --pretty=tformat:%s \ + --grep="[\d]\|" >actual.perl && + test_cmp expect.perl actual.perl + fi && + test_cmp expect.fixed actual.fixed && + test_cmp expect.basic actual.basic && + test_cmp expect.extended actual.extended && + + git log --pretty=tformat:%s -F \ + --grep="(1|2)" >actual.fixed.short-arg && + git log --pretty=tformat:%s -E \ + --grep="\|2" >actual.extended.short-arg && + if test_have_prereq PCRE + then + git log --pretty=tformat:%s -P \ + --grep="[\d]\|" >actual.perl.short-arg + else + test_must_fail git log -P \ + --grep="[\d]\|" + fi && + test_cmp expect.fixed actual.fixed.short-arg && + test_cmp expect.extended actual.extended.short-arg && + if test_have_prereq PCRE + then + test_cmp expect.perl actual.perl.short-arg + fi && + + git log --pretty=tformat:%s --fixed-strings \ + --grep="(1|2)" >actual.fixed.long-arg && + git log --pretty=tformat:%s --basic-regexp \ + --grep="(.|.)" >actual.basic.long-arg && + git log --pretty=tformat:%s --extended-regexp \ + --grep="\|2" >actual.extended.long-arg && + if test_have_prereq PCRE + then + git log --pretty=tformat:%s --perl-regexp \ + --grep="[\d]\|" >actual.perl.long-arg && + test_cmp expect.perl actual.perl.long-arg + else + test_must_fail git log --perl-regexp \ + --grep="[\d]\|" + fi && + test_cmp expect.fixed actual.fixed.long-arg && + test_cmp expect.basic actual.basic.long-arg && + test_cmp expect.extended actual.extended.long-arg + ) +' + cat > expect <<EOF * Second * sixth @@ -399,7 +559,7 @@ cat > expect <<\EOF | | | | Merge branch 'side' | | -| * commit side +| * commit tags/side-2 | | Author: A U Thor <author@example.com> | | | | side-2 @@ -1392,4 +1552,13 @@ test_expect_success 'log --source paints tag names' ' test_cmp expect actual ' +test_expect_success 'log --source paints symmetric ranges' ' + cat >expect <<-\EOF && + 09e12a9 source-b three + 8e393e1 source-a two + EOF + git log --oneline --source source-a...source-b >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh index 001343e2fc..935df6a65c 100755 --- a/t/t4208-log-magic-pathspec.sh +++ b/t/t4208-log-magic-pathspec.sh @@ -29,6 +29,12 @@ test_expect_success '"git log -- :/a" should not be ambiguous' ' git log -- :/a ' +# This differs from the ":/a" check above in that :/in looks like a pathspec, +# but doesn't match an actual file. +test_expect_success '"git log :/in" should not be ambiguous' ' + git log :/in +' + test_expect_success '"git log :" should be ambiguous' ' test_must_fail git log : 2>error && test_i18ngrep ambiguous error @@ -46,6 +52,32 @@ test_expect_success 'git log HEAD -- :/' ' test_cmp expected actual ' +test_expect_success '"git log :^sub" is not ambiguous' ' + git log :^sub +' + +test_expect_success '"git log :^does-not-exist" does not match anything' ' + test_must_fail git log :^does-not-exist +' + +test_expect_success '"git log :!" behaves the same as :^' ' + git log :!sub && + test_must_fail git log :!does-not-exist +' + +test_expect_success '"git log :(exclude)sub" is not ambiguous' ' + git log ":(exclude)sub" +' + +test_expect_success '"git log :(exclude)sub --" must resolve as an object' ' + test_must_fail git log ":(exclude)sub" -- +' + +test_expect_success '"git log :(unknown-magic) complains of bogus magic' ' + test_must_fail git log ":(unknown-magic)" 2>error && + test_i18ngrep pathspec.magic error +' + test_expect_success 'command line pathspec parsing for "git log"' ' git reset --hard && >a && diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index 7171f67539..9690dcad4f 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -171,4 +171,46 @@ test_expect_success 'mailinfo with mailinfo.scissors config' ' ' +test_expect_success 'mailinfo no options' ' + subj="$(echo "Subject: [PATCH] [other] [PATCH] message" | + git mailinfo /dev/null /dev/null)" && + test z"$subj" = z"Subject: message" +' + +test_expect_success 'mailinfo -k' ' + subj="$(echo "Subject: [PATCH] [other] [PATCH] message" | + git mailinfo -k /dev/null /dev/null)" && + test z"$subj" = z"Subject: [PATCH] [other] [PATCH] message" +' + +test_expect_success 'mailinfo -b no [PATCH]' ' + subj="$(echo "Subject: [other] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: [other] message" +' + +test_expect_success 'mailinfo -b leading [PATCH]' ' + subj="$(echo "Subject: [PATCH] [other] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: [other] message" +' + +test_expect_success 'mailinfo -b double [PATCH]' ' + subj="$(echo "Subject: [PATCH] [PATCH] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: message" +' + +test_expect_failure 'mailinfo -b trailing [PATCH]' ' + subj="$(echo "Subject: [other] [PATCH] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: [other] message" +' + +test_expect_failure 'mailinfo -b separated double [PATCH]' ' + subj="$(echo "Subject: [PATCH] [other] [PATCH] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: [other] message" +' + test_done diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 43a672c345..9c68b99251 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -421,6 +421,42 @@ test_expect_success 'index-pack <pack> works in non-repo' ' test_path_is_file foo.idx ' +test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'index-pack --threads=N or pack.threads=N warns when no pthreads' ' + test_must_fail git index-pack --threads=2 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring --threads=2" err && + + test_must_fail git -c pack.threads=2 index-pack 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring pack.threads" err && + + test_must_fail git -c pack.threads=2 index-pack --threads=4 2>err && + grep ^warning: err >warnings && + test_line_count = 2 warnings && + grep -F "no threads support, ignoring --threads=4" err && + grep -F "no threads support, ignoring pack.threads" err +' + +test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'pack-objects --threads=N or pack.threads=N warns when no pthreads' ' + git pack-objects --threads=2 --stdout --all </dev/null >/dev/null 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring --threads" err && + + git -c pack.threads=2 pack-objects --stdout --all </dev/null >/dev/null 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring pack.threads" err && + + git -c pack.threads=2 pack-objects --threads=4 --stdout --all </dev/null >/dev/null 2>err && + grep ^warning: err >warnings && + test_line_count = 2 warnings && + grep -F "no threads support, ignoring --threads" err && + grep -F "no threads support, ignoring pack.threads" err +' + # # WARNING! # diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 424bec7d77..20e2473a03 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -20,11 +20,13 @@ has_any () { } test_expect_success 'setup repo with moderate-sized history' ' - for i in $(test_seq 1 10); do + for i in $(test_seq 1 10) + do test_commit $i done && git checkout -b other HEAD~5 && - for i in $(test_seq 1 10); do + for i in $(test_seq 1 10) + do test_commit side-$i done && git checkout master && @@ -104,7 +106,8 @@ test_expect_success 'clone from bitmapped repository' ' ' test_expect_success 'setup further non-bitmapped commits' ' - for i in $(test_seq 1 10); do + for i in $(test_seq 1 10) + do test_commit further-$i done ' @@ -289,4 +292,43 @@ test_expect_success 'splitting packs does not generate bogus bitmaps' ' git -C no-bitmaps.git fetch .. HEAD ' +test_expect_success 'set up reusable pack' ' + rm -f .git/objects/pack/*.keep && + git repack -adb && + reusable_pack () { + git for-each-ref --format="%(objectname)" | + git pack-objects --delta-base-offset --revs --stdout "$@" + } +' + +test_expect_success 'pack reuse respects --honor-pack-keep' ' + test_when_finished "rm -f .git/objects/pack/*.keep" && + for i in .git/objects/pack/*.pack + do + >${i%.pack}.keep + done && + reusable_pack --honor-pack-keep >empty.pack && + git index-pack empty.pack && + >expect && + git show-index <empty.idx >actual && + test_cmp expect actual +' + +test_expect_success 'pack reuse respects --local' ' + mv .git/objects/pack/* alt.git/objects/pack/ && + test_when_finished "mv alt.git/objects/pack/* .git/objects/pack/" && + reusable_pack --local >empty.pack && + git index-pack empty.pack && + >expect && + git show-index <empty.idx >actual && + test_cmp expect actual +' + +test_expect_success 'pack reuse respects --incremental' ' + reusable_pack --incremental >empty.pack && + git index-pack empty.pack && + >expect && + git show-index <empty.idx >actual && + test_cmp expect actual +' test_done diff --git a/t/t5313-pack-bounds-checks.sh b/t/t5313-pack-bounds-checks.sh index a8a587abc3..9372508c99 100755 --- a/t/t5313-pack-bounds-checks.sh +++ b/t/t5313-pack-bounds-checks.sh @@ -139,7 +139,13 @@ test_expect_success 'bogus offset into v2 extended table' ' test_expect_success 'bogus offset inside v2 extended table' ' # We need two objects here, so we can plausibly require # an extended table (if the first object were larger than 2^31). - do_pack "$object $(git rev-parse HEAD)" --index-version=2 && + # + # Note that the value is important here. We want $object as + # the second entry in sorted-sha1 order. The sha1 of 1485 starts + # with "000", which sorts before that of $object (which starts + # with "fff"). + second=$(echo 1485 | git hash-object -w --stdin) && + do_pack "$object $second" --index-version=2 && # We have to make extra room for the table, so we cannot # just munge in place as usual. diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 3331e0f534..d375d7110d 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -288,7 +288,10 @@ test_expect_success 'receive-pack de-dupes .have lines' ' $shared .have EOF - GIT_TRACE_PACKET=$(pwd)/trace git push fork HEAD:foo && + GIT_TRACE_PACKET=$(pwd)/trace \ + git push \ + --receive-pack="unset GIT_TRACE_PACKET; git-receive-pack" \ + fork HEAD:foo && extract_ref_advertisement <trace >refs && test_cmp expect refs ' diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index b5865b385d..80a1a3239a 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -547,6 +547,41 @@ test_expect_success 'fetch-pack can fetch a raw sha1' ' git fetch-pack hidden $(git -C hidden rev-parse refs/hidden/one) ' +test_expect_success 'fetch-pack can fetch a raw sha1 that is advertised as a ref' ' + rm -rf server client && + git init server && + test_commit -C server 1 && + + git init client && + git -C client fetch-pack ../server \ + $(git -C server rev-parse refs/heads/master) +' + +test_expect_success 'fetch-pack can fetch a raw sha1 overlapping a named ref' ' + rm -rf server client && + git init server && + test_commit -C server 1 && + test_commit -C server 2 && + + git init client && + git -C client fetch-pack ../server \ + $(git -C server rev-parse refs/tags/1) refs/tags/1 +' + +test_expect_success 'fetch-pack cannot fetch a raw sha1 that is not advertised as a ref' ' + rm -rf server && + + git init server && + test_commit -C server 5 && + git -C server tag -d 5 && + test_commit -C server 6 && + + git init client && + test_must_fail git -C client fetch-pack ../server \ + $(git -C server rev-parse refs/heads/master^) 2>err && + test_i18ngrep "Server does not allow request for unadvertised object" err +' + check_prot_path () { cat >expected <<-EOF && Diag: url=$1 diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 94fc9be9ce..02106c9226 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -85,8 +85,15 @@ test_expect_success 'use branch.<name>.remote if possible' ' ' test_expect_success 'confuses pattern as remote when no remote specified' ' - cat >exp <<-\EOF && - fatal: '\''refs*master'\'' does not appear to be a git repository + if test_have_prereq MINGW + then + # Windows does not like asterisks in pathname + does_not_exist=master + else + does_not_exist="refs*master" + fi && + cat >exp <<-EOF && + fatal: '\''$does_not_exist'\'' does not appear to be a git repository fatal: Could not read from remote repository. Please make sure you have the correct access rights @@ -98,7 +105,7 @@ test_expect_success 'confuses pattern as remote when no remote specified' ' # fetch <branch>. # We could just as easily have used "master"; the "*" emphasizes its # role as a pattern. - test_must_fail git ls-remote refs*master >actual 2>&1 && + test_must_fail git ls-remote "$does_not_exist" >actual 2>&1 && test_i18ncmp exp actual ' diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 17f4d0fe4e..f15f7a3329 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -272,6 +272,24 @@ test_expect_success '--rebase fast forward' ' test_cmp reflog.expected reflog.fuzzy ' +test_expect_success '--rebase --autostash fast forward' ' + test_when_finished " + git reset --hard + git checkout to-rebase + git branch -D to-rebase-ff + git branch -D behind" && + git branch behind && + git checkout -b to-rebase-ff && + echo another modification >>file && + git add file && + git commit -m mod && + + git checkout behind && + echo dirty >file && + git pull --rebase --autostash . to-rebase-ff && + test "$(git rev-parse HEAD)" = "$(git rev-parse to-rebase-ff)" +' + test_expect_success '--rebase with conflicts shows advice' ' test_when_finished "git rebase --abort; git checkout -f to-rebase" && git checkout -b seq && diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index f3b0a8d30a..162baf101f 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -71,6 +71,16 @@ test_expect_success "fetch --recurse-submodules recurses into submodules" ' test_i18ncmp expect.err actual.err ' +test_expect_success "submodule.recurse option triggers recursive fetch" ' + add_upstream_commit && + ( + cd downstream && + git -c submodule.recurse fetch >../actual.out 2>../actual.err + ) && + test_must_be_empty actual.out && + test_i18ncmp expect.err actual.err +' + test_expect_success "fetch --recurse-submodules -j2 has the same output behaviour" ' add_upstream_commit && ( diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 57ba322628..beff65b8ac 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='unpack-objects' +test_description='test push with submodules' . ./test-lib.sh @@ -27,7 +27,7 @@ test_expect_success setup ' ) ' -test_expect_success push ' +test_expect_success 'push works with recorded gitlink' ' ( cd work && git push ../pub.git master @@ -126,6 +126,27 @@ test_expect_success 'push succeeds if submodule commit not on remote but using o ) ' +test_expect_success 'push succeeds if submodule commit not on remote but using auto-on-demand via submodule.recurse config' ' + ( + cd work/gar/bage && + >recurse-on-demand-from-submodule-recurse-config && + git add recurse-on-demand-from-submodule-recurse-config && + git commit -m "Recurse submodule.recurse from config junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse submodule.recurse from config for gar/bage" && + git -c submodule.recurse push ../pub.git master && + # Check that the supermodule commit got there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # Check that the submodule commit got there too + cd gar/bage && + git diff --quiet origin/master master + ) +' + test_expect_success 'push recurse-submodules on command line overrides config' ' ( cd work/gar/bage && diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh index f9232f5d0f..90a4b0d2fe 100755 --- a/t/t5545-push-options.sh +++ b/t/t5545-push-options.sh @@ -3,8 +3,6 @@ test_description='pushing to a repository using push options' . ./test-lib.sh -. "$TEST_DIRECTORY"/lib-httpd.sh -start_httpd mk_repo_pair () { rm -rf workbench upstream && @@ -102,46 +100,6 @@ test_expect_success 'two push options work' ' test_cmp expect upstream/.git/hooks/post-receive.push_options ' -test_expect_success 'push option denied properly by http server' ' - test_when_finished "rm -rf test_http_clone" && - test_when_finished "rm -rf \"$HTTPD_DOCUMENT_ROOT_PATH\"/upstream.git" && - mk_repo_pair && - git -C upstream config receive.advertisePushOptions false && - git -C upstream config http.receivepack true && - cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git && - git clone "$HTTPD_URL"/smart/upstream test_http_clone && - test_commit -C test_http_clone one && - test_must_fail git -C test_http_clone push --push-option=asdf origin master 2>actual && - test_i18ngrep "the receiving end does not support push options" actual && - git -C test_http_clone push origin master -' - -test_expect_success 'push options work properly across http' ' - test_when_finished "rm -rf test_http_clone" && - test_when_finished "rm -rf \"$HTTPD_DOCUMENT_ROOT_PATH\"/upstream.git" && - mk_repo_pair && - git -C upstream config receive.advertisePushOptions true && - git -C upstream config http.receivepack true && - cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git && - git clone "$HTTPD_URL"/smart/upstream test_http_clone && - - test_commit -C test_http_clone one && - git -C test_http_clone push origin master && - git -C "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git rev-parse --verify master >expect && - git -C test_http_clone rev-parse --verify master >actual && - test_cmp expect actual && - - test_commit -C test_http_clone two && - git -C test_http_clone push --push-option=asdf --push-option="more structured text" origin master && - printf "asdf\nmore structured text\n" >expect && - test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/pre-receive.push_options && - test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/post-receive.push_options && - - git -C "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git rev-parse --verify master >expect && - git -C test_http_clone rev-parse --verify master >actual && - test_cmp expect actual -' - test_expect_success 'push options and submodules' ' test_when_finished "rm -rf parent" && test_when_finished "rm -rf parent_upstream" && @@ -182,6 +140,49 @@ test_expect_success 'push options and submodules' ' test_cmp expect parent_upstream/.git/hooks/post-receive.push_options ' +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'push option denied properly by http server' ' + test_when_finished "rm -rf test_http_clone" && + test_when_finished "rm -rf \"$HTTPD_DOCUMENT_ROOT_PATH\"/upstream.git" && + mk_repo_pair && + git -C upstream config receive.advertisePushOptions false && + git -C upstream config http.receivepack true && + cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git && + git clone "$HTTPD_URL"/smart/upstream test_http_clone && + test_commit -C test_http_clone one && + test_must_fail git -C test_http_clone push --push-option=asdf origin master 2>actual && + test_i18ngrep "the receiving end does not support push options" actual && + git -C test_http_clone push origin master +' + +test_expect_success 'push options work properly across http' ' + test_when_finished "rm -rf test_http_clone" && + test_when_finished "rm -rf \"$HTTPD_DOCUMENT_ROOT_PATH\"/upstream.git" && + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + git -C upstream config http.receivepack true && + cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git && + git clone "$HTTPD_URL"/smart/upstream test_http_clone && + + test_commit -C test_http_clone one && + git -C test_http_clone push origin master && + git -C "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git rev-parse --verify master >expect && + git -C test_http_clone rev-parse --verify master >actual && + test_cmp expect actual && + + test_commit -C test_http_clone two && + git -C test_http_clone push --push-option=asdf --push-option="more structured text" origin master && + printf "asdf\nmore structured text\n" >expect && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/pre-receive.push_options && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/post-receive.push_options && + + git -C "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git rev-parse --verify master >expect && + git -C test_http_clone rev-parse --verify master >actual && + test_cmp expect actual +' + stop_httpd test_done diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 87308cdced..8552184e74 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -20,8 +20,9 @@ test_expect_success 'create http-accessible bare repository with loose objects' (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && git config core.bare true && mkdir -p hooks && - echo "exec git update-server-info" >hooks/post-update && - chmod +x hooks/post-update && + write_script "hooks/post-update" <<-\EOF && + exec git update-server-info + EOF hooks/post-update ) && git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && diff --git a/t/t5580-clone-push-unc.sh b/t/t5580-clone-push-unc.sh index b195f71ea9..b322c2f722 100755 --- a/t/t5580-clone-push-unc.sh +++ b/t/t5580-clone-push-unc.sh @@ -1,10 +1,10 @@ #!/bin/sh -test_description='various UNC path tests (Windows-only)' +test_description='various Windows-only path tests' . ./test-lib.sh if ! test_have_prereq MINGW; then - skip_all='skipping UNC path tests, requires Windows' + skip_all='skipping Windows-only path tests' test_done fi @@ -45,4 +45,10 @@ test_expect_success push ' test "$rev" = "$(git rev-parse --verify refs/heads/to-push)" ' +test_expect_success 'remote nick cannot contain backslashes' ' + BACKSLASHED="$(pwd | tr / \\\\)" && + git ls-remote "$BACKSLASHED" >out 2>err && + test_i18ngrep ! "unable to access" err +' + test_done diff --git a/t/t6134-pathspec-in-submodule.sh b/t/t6134-pathspec-in-submodule.sh index 99a8982ab1..c670668409 100755 --- a/t/t6134-pathspec-in-submodule.sh +++ b/t/t6134-pathspec-in-submodule.sh @@ -24,13 +24,9 @@ test_expect_success 'error message for path inside submodule' ' test_i18ncmp expect actual ' -cat <<EOF >expect -fatal: Pathspec '.' is in submodule 'sub' -EOF - test_expect_success 'error message for path inside submodule from within submodule' ' test_must_fail git -C sub add . 2>actual && - test_i18ncmp expect actual + test_i18ngrep "in unpopulated submodule" actual ' test_done diff --git a/t/t6501-freshen-objects.sh b/t/t6501-freshen-objects.sh index cf076dcd94..394b169ead 100755 --- a/t/t6501-freshen-objects.sh +++ b/t/t6501-freshen-objects.sh @@ -129,7 +129,7 @@ for repack in '' true; do ' done -test_expect_success 'do not complain about existing broken links' ' +test_expect_success 'do not complain about existing broken links (commit)' ' cat >broken-commit <<-\EOF && tree 0000000000000000000000000000000000000001 parent 0000000000000000000000000000000000000002 @@ -144,4 +144,29 @@ test_expect_success 'do not complain about existing broken links' ' test_must_be_empty stderr ' +test_expect_success 'do not complain about existing broken links (tree)' ' + cat >broken-tree <<-\EOF && + 100644 blob 0000000000000000000000000000000000000003 foo + EOF + tree=$(git mktree --missing <broken-tree) && + git gc 2>stderr && + git cat-file -e $tree && + test_must_be_empty stderr +' + +test_expect_success 'do not complain about existing broken links (tag)' ' + cat >broken-tag <<-\EOF && + object 0000000000000000000000000000000000000004 + type commit + tag broken + tagger whatever <whatever@example.com> 1234 -0000 + + this is a broken tag + EOF + tag=$(git hash-object -t tag -w broken-tag) && + git gc 2>stderr && + git cat-file -e $tag && + test_must_be_empty stderr +' + test_done diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 4f3794d415..20b4d83c28 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -391,6 +391,17 @@ test_expect_success TTY 'core.pager in repo config works and retains cwd' ' ) ' +test_expect_success TTY 'core.pager is found via alias in subdirectory' ' + sane_unset GIT_PAGER && + test_config core.pager "cat >via-alias" && + ( + cd sub && + rm -f via-alias && + test_terminal git -c alias.r="-p rev-parse" r HEAD && + test_path_is_file via-alias + ) +' + test_doesnt_paginate expect_failure test_must_fail 'git -p nonsense' test_pager_choices 'git shortlog' diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh index 9c9c378119..615e7e0162 100755 --- a/t/t7008-grep-binary.sh +++ b/t/t7008-grep-binary.sh @@ -4,8 +4,43 @@ test_description='git grep in binary files' . ./test-lib.sh +nul_match () { + matches=$1 + flags=$2 + pattern=$3 + pattern_human=$(echo "$pattern" | sed 's/Q/<NUL>/g') + + if test "$matches" = 1 + then + test_expect_success "git grep -f f $flags '$pattern_human' a" " + printf '$pattern' | q_to_nul >f && + git grep -f f $flags a + " + elif test "$matches" = 0 + then + test_expect_success "git grep -f f $flags '$pattern_human' a" " + printf '$pattern' | q_to_nul >f && + test_must_fail git grep -f f $flags a + " + elif test "$matches" = T1 + then + test_expect_failure "git grep -f f $flags '$pattern_human' a" " + printf '$pattern' | q_to_nul >f && + git grep -f f $flags a + " + elif test "$matches" = T0 + then + test_expect_failure "git grep -f f $flags '$pattern_human' a" " + printf '$pattern' | q_to_nul >f && + test_must_fail git grep -f f $flags a + " + else + test_expect_success "PANIC: Test framework error. Unknown matches value $matches" 'false' + fi +} + test_expect_success 'setup' " - echo 'binaryQfile' | q_to_nul >a && + echo 'binaryQfileQm[*]cQ*æQð' | q_to_nul >a && git add a && git commit -m. " @@ -69,35 +104,71 @@ test_expect_failure 'git grep .fi a' ' git grep .fi a ' -test_expect_success 'git grep -F y<NUL>f a' " - printf 'yQf' | q_to_nul >f && - git grep -f f -F a -" - -test_expect_success 'git grep -F y<NUL>x a' " - printf 'yQx' | q_to_nul >f && - test_must_fail git grep -f f -F a -" - -test_expect_success 'git grep -Fi Y<NUL>f a' " - printf 'YQf' | q_to_nul >f && - git grep -f f -Fi a -" - -test_expect_success 'git grep -Fi Y<NUL>x a' " - printf 'YQx' | q_to_nul >f && - test_must_fail git grep -f f -Fi a -" - -test_expect_success 'git grep y<NUL>f a' " - printf 'yQf' | q_to_nul >f && - git grep -f f a -" - -test_expect_success 'git grep y<NUL>x a' " - printf 'yQx' | q_to_nul >f && - test_must_fail git grep -f f a -" +nul_match 1 '-F' 'yQf' +nul_match 0 '-F' 'yQx' +nul_match 1 '-Fi' 'YQf' +nul_match 0 '-Fi' 'YQx' +nul_match 1 '' 'yQf' +nul_match 0 '' 'yQx' +nul_match 1 '' 'æQð' +nul_match 1 '-F' 'eQm[*]c' +nul_match 1 '-Fi' 'EQM[*]C' + +# Regex patterns that would match but shouldn't with -F +nul_match 0 '-F' 'yQ[f]' +nul_match 0 '-F' '[y]Qf' +nul_match 0 '-Fi' 'YQ[F]' +nul_match 0 '-Fi' '[Y]QF' +nul_match 0 '-F' 'æQ[ð]' +nul_match 0 '-F' '[æ]Qð' +nul_match 0 '-Fi' 'ÆQ[Ã]' +nul_match 0 '-Fi' '[Æ]QÃ' + +# kwset is disabled on -i & non-ASCII. No way to match non-ASCII \0 +# patterns case-insensitively. +nul_match T1 '-i' 'ÆQÃ' + +# \0 implicitly disables regexes. This is an undocumented internal +# limitation. +nul_match T1 '' 'yQ[f]' +nul_match T1 '' '[y]Qf' +nul_match T1 '-i' 'YQ[F]' +nul_match T1 '-i' '[Y]Qf' +nul_match T1 '' 'æQ[ð]' +nul_match T1 '' '[æ]Qð' +nul_match T1 '-i' 'ÆQ[Ã]' + +# ... because of \0 implicitly disabling regexes regexes that +# should/shouldn't match don't do the right thing. +nul_match T1 '' 'eQm.*cQ' +nul_match T1 '-i' 'EQM.*cQ' +nul_match T0 '' 'eQm[*]c' +nul_match T0 '-i' 'EQM[*]C' + +# Due to the REG_STARTEND extension when kwset() is disabled on -i & +# non-ASCII the string will be matched in its entirety, but the +# pattern will be cut off at the first \0. +nul_match 0 '-i' 'NOMATCHQð' +nul_match T0 '-i' '[Æ]QNOMATCH' +nul_match T0 '-i' '[æ]QNOMATCH' +# Matches, but for the wrong reasons, just stops at [æ] +nul_match 1 '-i' '[Æ]Qð' +nul_match 1 '-i' '[æ]Qð' + +# Ensure that the matcher doesn't regress to something that stops at +# \0 +nul_match 0 '-F' 'yQ[f]' +nul_match 0 '-Fi' 'YQ[F]' +nul_match 0 '' 'yQNOMATCH' +nul_match 0 '' 'QNOMATCH' +nul_match 0 '-i' 'YQNOMATCH' +nul_match 0 '-i' 'QNOMATCH' +nul_match 0 '-F' 'æQ[ð]' +nul_match 0 '-Fi' 'ÆQ[Ã]' +nul_match 0 '' 'yQNÓMATCH' +nul_match 0 '' 'QNÓMATCH' +nul_match 0 '-i' 'YQNÓMATCH' +nul_match 0 '-i' 'QNÓMATCH' test_expect_success 'grep respects binary diff attribute' ' echo text >t && @@ -162,7 +233,7 @@ test_expect_success 'grep does not honor textconv' ' ' test_expect_success 'grep --textconv honors textconv' ' - echo "a:binaryQfile" >expect && + echo "a:binaryQfileQm[*]cQ*æQð" >expect && git grep --textconv Qfile >actual && test_cmp expect actual ' @@ -172,7 +243,7 @@ test_expect_success 'grep --no-textconv does not honor textconv' ' ' test_expect_success 'grep --textconv blob honors textconv' ' - echo "HEAD:a:binaryQfile" >expect && + echo "HEAD:a:binaryQfileQm[*]cQ*æQð" >expect && git grep --textconv Qfile HEAD:a >actual && test_cmp expect actual ' diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh index cdc0747bf0..fc6013ba3c 100755 --- a/t/t7061-wtstatus-ignore.sh +++ b/t/t7061-wtstatus-ignore.sh @@ -9,6 +9,7 @@ cat >expected <<\EOF ?? actual ?? expected ?? untracked/ +!! untracked/ignored EOF test_expect_success 'status untracked directory with --ignored' ' diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index 0667bd9dd3..e5fb892f95 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -661,4 +661,26 @@ test_expect_success 'test ident field is working' ' test_i18ncmp ../expect ../err ' +test_expect_success 'untracked cache survives a checkout' ' + git commit --allow-empty -m empty && + test-dump-untracked-cache >../before && + test_when_finished "git checkout master" && + git checkout -b other_branch && + test-dump-untracked-cache >../after && + test_cmp ../before ../after && + test_commit test && + test-dump-untracked-cache >../before && + git checkout master && + test-dump-untracked-cache >../after && + test_cmp ../before ../after +' + +test_expect_success 'untracked cache survives a commit' ' + test-dump-untracked-cache >../before && + git add done/two && + git commit -m commit && + test-dump-untracked-cache >../after && + test_cmp ../before ../after +' + test_done diff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh index 2eda6adeb1..a1cb9ff858 100755 --- a/t/t7112-reset-submodule.sh +++ b/t/t7112-reset-submodule.sh @@ -5,6 +5,14 @@ test_description='reset can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1 +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 +KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1 + +test_submodule_switch_recursing_with_args "reset --keep" + +test_submodule_forced_switch_recursing_with_args "reset --hard" + test_submodule_switch "git reset --keep" test_submodule_switch "git reset --merge" diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index b89fd2a6ad..7b36954d63 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -653,4 +653,20 @@ test_expect_success 'git clean -d respects pathspecs (pathspec is prefix of dir) test_path_is_dir foobar ' +test_expect_success 'git clean -d skips untracked dirs containing ignored files' ' + echo /foo/bar >.gitignore && + echo ignoreme >>.gitignore && + rm -rf foo && + mkdir -p foo/a/aa/aaa foo/b/bb/bbb && + touch foo/bar foo/baz foo/a/aa/ignoreme foo/b/ignoreme foo/b/bb/1 foo/b/bb/2 && + git clean -df && + test_path_is_dir foo && + test_path_is_file foo/bar && + test_path_is_missing foo/baz && + test_path_is_file foo/a/aa/ignoreme && + test_path_is_missing foo/a/aa/aaa && + test_path_is_file foo/b/ignoreme && + test_path_is_missing foo/b/bb +' + test_done diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 366746f0d4..4e4c455502 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -241,9 +241,11 @@ EOF test_cmp expected actual " -test_create_repo sm2 && -head7=$(add_file sm2 foo8 foo9) && -git add sm2 +test_expect_success 'create second submodule' ' + test_create_repo sm2 && + head7=$(add_file sm2 foo8 foo9) && + git add sm2 +' test_expect_success 'multiple submodules' " git submodule summary >actual && diff --git a/t/t7414-submodule-mistakes.sh b/t/t7414-submodule-mistakes.sh new file mode 100755 index 0000000000..f2e7df59cf --- /dev/null +++ b/t/t7414-submodule-mistakes.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +test_description='handling of common mistakes people may make with submodules' +. ./test-lib.sh + +test_expect_success 'create embedded repository' ' + git init embed && + test_commit -C embed one +' + +test_expect_success 'git-add on embedded repository warns' ' + test_when_finished "git rm --cached -f embed" && + git add embed 2>stderr && + test_i18ngrep warning stderr +' + +test_expect_success '--no-warn-embedded-repo suppresses warning' ' + test_when_finished "git rm --cached -f embed" && + git add --no-warn-embedded-repo embed 2>stderr && + test_i18ngrep ! warning stderr +' + +test_expect_success 'no warning when updating entry' ' + test_when_finished "git rm --cached -f embed" && + git add embed && + git -C embed commit --allow-empty -m two && + git add embed 2>stderr && + test_i18ngrep ! warning stderr +' + +test_expect_success 'submodule add does not warn' ' + test_when_finished "git rm -rf submodule .gitmodules" && + git submodule add ./embed submodule 2>stderr && + test_i18ngrep ! warning stderr +' + +test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 79427840a4..7121a550c7 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1608,4 +1608,36 @@ test_expect_success 'git commit -m will commit a staged but ignored submodule' ' git config -f .gitmodules --remove-section submodule.subname ' +test_expect_success 'show stash info with "--show-stash"' ' + git reset --hard && + git stash clear && + echo 1 >file && + git add file && + git stash && + git status >expected_default && + git status --show-stash >expected_with_stash && + test_i18ngrep "^Your stash currently has 1 entry$" expected_with_stash +' + +test_expect_success 'no stash info with "--show-stash --no-show-stash"' ' + git status --show-stash --no-show-stash >expected_without_stash && + test_cmp expected_default expected_without_stash +' + +test_expect_success '"status.showStash=false" weaker than "--show-stash"' ' + git -c status.showStash=false status --show-stash >actual && + test_cmp expected_with_stash actual +' + +test_expect_success '"status.showStash=true" weaker than "--no-show-stash"' ' + git -c status.showStash=true status --no-show-stash >actual && + test_cmp expected_without_stash actual +' + +test_expect_success 'no additionnal info if no stash entries' ' + git stash clear && + git -c status.showStash=true status >actual && + test_cmp expected_without_stash actual +' + test_done diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 4dd1d7c520..0c6f91c433 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -1258,4 +1258,21 @@ test_expect_success 'with no command and no key' ' test_cmp expected actual ' +test_expect_success 'with cut line' ' + cat >expected <<-\EOF && + my subject + + review: Brian + sign: A U Thor <author@example.com> + # ------------------------ >8 ------------------------ + ignore this + EOF + git interpret-trailers --trailer review:Brian >actual <<-\EOF && + my subject + # ------------------------ >8 ------------------------ + ignore this + EOF + test_cmp expected actual +' + test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index cee42097b0..f106387820 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -275,12 +275,16 @@ do test_cmp expected actual ' - test_expect_success LIBPCRE "grep $L with grep.patterntype=perl" ' + test_expect_success PCRE "grep $L with grep.patterntype=perl" ' echo "${HC}ab:a+b*c" >expected && git -c grep.patterntype=perl grep "a\x{2b}b\x{2a}c" $H ab >actual && test_cmp expected actual ' + test_expect_success !PCRE "grep $L with grep.patterntype=perl errors without PCRE" ' + test_must_fail git -c grep.patterntype=perl grep "foo.*bar" + ' + test_expect_success "grep $L with grep.patternType=default and grep.extendedRegexp=true" ' echo "${HC}ab:abc" >expected && git \ @@ -771,6 +775,40 @@ test_expect_success 'grep -W with userdiff' ' test_cmp expected actual ' +for threads in $(test_seq 0 10) +do + test_expect_success "grep --threads=$threads & -c grep.threads=$threads" " + git grep --threads=$threads . >actual.$threads && + if test $threads -ge 1 + then + test_cmp actual.\$(($threads - 1)) actual.$threads + fi && + git -c grep.threads=$threads grep . >actual.$threads && + if test $threads -ge 1 + then + test_cmp actual.\$(($threads - 1)) actual.$threads + fi + " +done + +test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'grep --threads=N or pack.threads=N warns when no pthreads' ' + git grep --threads=2 Hello hello_world 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring --threads" err && + git -c grep.threads=2 grep Hello hello_world 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring grep.threads" err && + git -c grep.threads=2 grep --threads=4 Hello hello_world 2>err && + grep ^warning: err >warnings && + test_line_count = 2 warnings && + grep -F "no threads support, ignoring --threads" err && + grep -F "no threads support, ignoring grep.threads" err && + git -c grep.threads=0 grep --threads=0 Hello hello_world 2>err && + test_line_count = 0 err +' + test_expect_success 'grep from a subdirectory to search wider area (1)' ' mkdir -p s && ( @@ -1053,16 +1091,24 @@ hello.c:int main(int argc, const char **argv) hello.c: printf("Hello world.\n"); EOF -test_expect_success LIBPCRE 'grep --perl-regexp pattern' ' +test_expect_success PCRE 'grep --perl-regexp pattern' ' git grep --perl-regexp "\p{Ps}.*?\p{Pe}" hello.c >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P pattern' ' +test_expect_success !PCRE 'grep --perl-regexp pattern errors without PCRE' ' + test_must_fail git grep --perl-regexp "foo.*bar" +' + +test_expect_success PCRE 'grep -P pattern' ' git grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual && test_cmp expected actual ' +test_expect_success !PCRE 'grep -P pattern errors without PCRE' ' + test_must_fail git grep -P "foo.*bar" +' + test_expect_success 'grep pattern with grep.extendedRegexp=true' ' >empty && test_must_fail git -c grep.extendedregexp=true \ @@ -1070,13 +1116,13 @@ test_expect_success 'grep pattern with grep.extendedRegexp=true' ' test_cmp empty actual ' -test_expect_success LIBPCRE 'grep -P pattern with grep.extendedRegexp=true' ' +test_expect_success PCRE 'grep -P pattern with grep.extendedRegexp=true' ' git -c grep.extendedregexp=true \ grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P -v pattern' ' +test_expect_success PCRE 'grep -P -v pattern' ' { echo "ab:a+b*c" echo "ab:a+bc" @@ -1085,7 +1131,7 @@ test_expect_success LIBPCRE 'grep -P -v pattern' ' test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P -i pattern' ' +test_expect_success PCRE 'grep -P -i pattern' ' cat >expected <<-EOF && hello.c: printf("Hello world.\n"); EOF @@ -1093,7 +1139,7 @@ test_expect_success LIBPCRE 'grep -P -i pattern' ' test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P -w pattern' ' +test_expect_success PCRE 'grep -P -w pattern' ' { echo "hello_world:Hello world" echo "hello_world:HeLLo world" @@ -1102,6 +1148,13 @@ test_expect_success LIBPCRE 'grep -P -w pattern' ' test_cmp expected actual ' +test_expect_success PCRE 'grep -P backreferences work (the PCRE NO_AUTO_CAPTURE flag is not set)' ' + git grep -P -h "(?P<one>.)(?P=one)" hello_world >actual && + test_cmp hello_world actual && + git grep -P -h "(.)\1" hello_world >actual && + test_cmp hello_world actual +' + test_expect_success 'grep -G invalidpattern properly dies ' ' test_must_fail git grep -G "a[" ' @@ -1118,11 +1171,11 @@ test_expect_success 'grep invalidpattern properly dies with grep.patternType=ext test_must_fail git -c grep.patterntype=extended grep "a[" ' -test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' ' +test_expect_success PCRE 'grep -P invalidpattern properly dies ' ' test_must_fail git grep -P "a[" ' -test_expect_success LIBPCRE 'grep invalidpattern properly dies with grep.patternType=perl' ' +test_expect_success PCRE 'grep invalidpattern properly dies with grep.patternType=perl' ' test_must_fail git -c grep.patterntype=perl grep "a[" ' @@ -1191,13 +1244,13 @@ test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =perl, =e test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -G -F -E -P pattern' ' +test_expect_success PCRE 'grep -G -F -E -P pattern' ' echo "d0:0" >expected && git grep -G -F -E -P "[\d]" d0 >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' ' +test_expect_success PCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' ' echo "d0:0" >expected && git \ -c grep.patterntype=fixed \ @@ -1208,7 +1261,7 @@ test_expect_success LIBPCRE 'grep pattern with grep.patternType=fixed, =basic, = test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P pattern with grep.patternType=fixed' ' +test_expect_success PCRE 'grep -P pattern with grep.patternType=fixed' ' echo "ab:a+b*c" >expected && git \ -c grep.patterntype=fixed \ @@ -1343,12 +1396,12 @@ space: line with leading space2 space: line with leading space3 EOF -test_expect_success LIBPCRE 'grep -E "^ "' ' +test_expect_success PCRE 'grep -E "^ "' ' git grep -E "^ " space >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P "^ "' ' +test_expect_success PCRE 'grep -P "^ "' ' git grep -P "^ " space >actual && test_cmp expected actual ' diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh index 169fd8d706..0059a1f837 100755 --- a/t/t7812-grep-icase-non-ascii.sh +++ b/t/t7812-grep-icase-non-ascii.sh @@ -20,13 +20,13 @@ test_expect_success REGEX_LOCALE 'grep literal string, no -F' ' git grep -i "TILRAUN: HALLÓ HEIMUR!" ' -test_expect_success GETTEXT_LOCALE,LIBPCRE 'grep pcre utf-8 icase' ' +test_expect_success GETTEXT_LOCALE,PCRE 'grep pcre utf-8 icase' ' git grep --perl-regexp "TILRAUN: H.lló Heimur!" && git grep --perl-regexp -i "TILRAUN: H.lló Heimur!" && git grep --perl-regexp -i "TILRAUN: H.LLÓ HEIMUR!" ' -test_expect_success GETTEXT_LOCALE,LIBPCRE 'grep pcre utf-8 string with "+"' ' +test_expect_success GETTEXT_LOCALE,PCRE 'grep pcre utf-8 string with "+"' ' test_write_lines "TILRAUN: Hallóó Heimur!" >file2 && git add file2 && git grep -l --perl-regexp "TILRAUN: H.lló+ Heimur!" >actual && @@ -36,29 +36,14 @@ test_expect_success GETTEXT_LOCALE,LIBPCRE 'grep pcre utf-8 string with "+"' ' ' test_expect_success REGEX_LOCALE 'grep literal string, with -F' ' - git grep --debug -i -F "TILRAUN: Halló Heimur!" 2>&1 >/dev/null | - grep fixed >debug1 && - test_write_lines "fixed TILRAUN: Halló Heimur!" >expect1 && - test_cmp expect1 debug1 && - - git grep --debug -i -F "TILRAUN: HALLÓ HEIMUR!" 2>&1 >/dev/null | - grep fixed >debug2 && - test_write_lines "fixed TILRAUN: HALLÓ HEIMUR!" >expect2 && - test_cmp expect2 debug2 + git grep -i -F "TILRAUN: Halló Heimur!" && + git grep -i -F "TILRAUN: HALLÓ HEIMUR!" ' test_expect_success REGEX_LOCALE 'grep string with regex, with -F' ' - test_write_lines "^*TILR^AUN:.* \\Halló \$He[]imur!\$" >file && - - git grep --debug -i -F "^*TILR^AUN:.* \\Halló \$He[]imur!\$" 2>&1 >/dev/null | - grep fixed >debug1 && - test_write_lines "fixed \\^*TILR^AUN:\\.\\* \\\\Halló \$He\\[]imur!\\\$" >expect1 && - test_cmp expect1 debug1 && - - git grep --debug -i -F "^*TILR^AUN:.* \\HALLÓ \$HE[]IMUR!\$" 2>&1 >/dev/null | - grep fixed >debug2 && - test_write_lines "fixed \\^*TILR^AUN:\\.\\* \\\\HALLÓ \$HE\\[]IMUR!\\\$" >expect2 && - test_cmp expect2 debug2 + test_write_lines "TILRAUN: Halló Heimur [abc]!" >file3 && + git add file3 && + git grep -i -F "TILRAUN: Halló Heimur [abc]!" file3 ' test_expect_success REGEX_LOCALE 'pickaxe -i on non-ascii' ' diff --git a/t/t7813-grep-icase-iso.sh b/t/t7813-grep-icase-iso.sh index efef7fb81f..701e08a8e5 100755 --- a/t/t7813-grep-icase-iso.sh +++ b/t/t7813-grep-icase-iso.sh @@ -11,7 +11,7 @@ test_expect_success GETTEXT_ISO_LOCALE 'setup' ' export LC_ALL ' -test_expect_success GETTEXT_ISO_LOCALE,LIBPCRE 'grep pcre string' ' +test_expect_success GETTEXT_ISO_LOCALE,PCRE 'grep pcre string' ' git grep --perl-regexp -i "TILRAUN: H.lló Heimur!" && git grep --perl-regexp -i "TILRAUN: H.LLÓ HEIMUR!" ' diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index 5b6eb3a65e..7184113b9b 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -9,13 +9,13 @@ submodules. . ./test-lib.sh test_expect_success 'setup directory structure and submodule' ' - echo "foobar" >a && + echo "(1|2)d(3|4)" >a && mkdir b && - echo "bar" >b/b && + echo "(3|4)" >b/b && git add a b && git commit -m "add a and b" && git init submodule && - echo "foobar" >submodule/a && + echo "(1|2)d(3|4)" >submodule/a && git -C submodule add a && git -C submodule commit -m "add a" && git submodule add ./submodule && @@ -24,18 +24,36 @@ test_expect_success 'setup directory structure and submodule' ' test_expect_success 'grep correctly finds patterns in a submodule' ' cat >expect <<-\EOF && - a:foobar - b/b:bar - submodule/a:foobar + a:(1|2)d(3|4) + b/b:(3|4) + submodule/a:(1|2)d(3|4) EOF - git grep -e "bar" --recurse-submodules >actual && + git grep -e "(3|4)" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep finds patterns in a submodule via config' ' + test_config submodule.recurse true && + # expect from previous test + git grep -e "(3|4)" >actual && + test_cmp expect actual +' + +test_expect_success 'grep --no-recurse-submodules overrides config' ' + test_config submodule.recurse true && + cat >expect <<-\EOF && + a:(1|2)d(3|4) + b/b:(3|4) + EOF + + git grep -e "(3|4)" --no-recurse-submodules >actual && test_cmp expect actual ' test_expect_success 'grep and basic pathspecs' ' cat >expect <<-\EOF && - submodule/a:foobar + submodule/a:(1|2)d(3|4) EOF git grep -e. --recurse-submodules -- submodule >actual && @@ -44,7 +62,7 @@ test_expect_success 'grep and basic pathspecs' ' test_expect_success 'grep and nested submodules' ' git init submodule/sub && - echo "foobar" >submodule/sub/a && + echo "(1|2)d(3|4)" >submodule/sub/a && git -C submodule/sub add a && git -C submodule/sub commit -m "add a" && git -C submodule submodule add ./sub && @@ -54,117 +72,117 @@ test_expect_success 'grep and nested submodules' ' git commit -m "updated submodule" && cat >expect <<-\EOF && - a:foobar - b/b:bar - submodule/a:foobar - submodule/sub/a:foobar + a:(1|2)d(3|4) + b/b:(3|4) + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) EOF - git grep -e "bar" --recurse-submodules >actual && + git grep -e "(3|4)" --recurse-submodules >actual && test_cmp expect actual ' test_expect_success 'grep and multiple patterns' ' cat >expect <<-\EOF && - a:foobar - submodule/a:foobar - submodule/sub/a:foobar + a:(1|2)d(3|4) + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) EOF - git grep -e "bar" --and -e "foo" --recurse-submodules >actual && + git grep -e "(3|4)" --and -e "(1|2)" --recurse-submodules >actual && test_cmp expect actual ' test_expect_success 'grep and multiple patterns' ' cat >expect <<-\EOF && - b/b:bar + b/b:(3|4) EOF - git grep -e "bar" --and --not -e "foo" --recurse-submodules >actual && + git grep -e "(3|4)" --and --not -e "(1|2)" --recurse-submodules >actual && test_cmp expect actual ' test_expect_success 'basic grep tree' ' cat >expect <<-\EOF && - HEAD:a:foobar - HEAD:b/b:bar - HEAD:submodule/a:foobar - HEAD:submodule/sub/a:foobar + HEAD:a:(1|2)d(3|4) + HEAD:b/b:(3|4) + HEAD:submodule/a:(1|2)d(3|4) + HEAD:submodule/sub/a:(1|2)d(3|4) EOF - git grep -e "bar" --recurse-submodules HEAD >actual && + git grep -e "(3|4)" --recurse-submodules HEAD >actual && test_cmp expect actual ' test_expect_success 'grep tree HEAD^' ' cat >expect <<-\EOF && - HEAD^:a:foobar - HEAD^:b/b:bar - HEAD^:submodule/a:foobar + HEAD^:a:(1|2)d(3|4) + HEAD^:b/b:(3|4) + HEAD^:submodule/a:(1|2)d(3|4) EOF - git grep -e "bar" --recurse-submodules HEAD^ >actual && + git grep -e "(3|4)" --recurse-submodules HEAD^ >actual && test_cmp expect actual ' test_expect_success 'grep tree HEAD^^' ' cat >expect <<-\EOF && - HEAD^^:a:foobar - HEAD^^:b/b:bar + HEAD^^:a:(1|2)d(3|4) + HEAD^^:b/b:(3|4) EOF - git grep -e "bar" --recurse-submodules HEAD^^ >actual && + git grep -e "(3|4)" --recurse-submodules HEAD^^ >actual && test_cmp expect actual ' test_expect_success 'grep tree and pathspecs' ' cat >expect <<-\EOF && - HEAD:submodule/a:foobar - HEAD:submodule/sub/a:foobar + HEAD:submodule/a:(1|2)d(3|4) + HEAD:submodule/sub/a:(1|2)d(3|4) EOF - git grep -e "bar" --recurse-submodules HEAD -- submodule >actual && + git grep -e "(3|4)" --recurse-submodules HEAD -- submodule >actual && test_cmp expect actual ' test_expect_success 'grep tree and pathspecs' ' cat >expect <<-\EOF && - HEAD:submodule/a:foobar - HEAD:submodule/sub/a:foobar + HEAD:submodule/a:(1|2)d(3|4) + HEAD:submodule/sub/a:(1|2)d(3|4) EOF - git grep -e "bar" --recurse-submodules HEAD -- "submodule*a" >actual && + git grep -e "(3|4)" --recurse-submodules HEAD -- "submodule*a" >actual && test_cmp expect actual ' test_expect_success 'grep tree and more pathspecs' ' cat >expect <<-\EOF && - HEAD:submodule/a:foobar + HEAD:submodule/a:(1|2)d(3|4) EOF - git grep -e "bar" --recurse-submodules HEAD -- "submodul?/a" >actual && + git grep -e "(3|4)" --recurse-submodules HEAD -- "submodul?/a" >actual && test_cmp expect actual ' test_expect_success 'grep tree and more pathspecs' ' cat >expect <<-\EOF && - HEAD:submodule/sub/a:foobar + HEAD:submodule/sub/a:(1|2)d(3|4) EOF - git grep -e "bar" --recurse-submodules HEAD -- "submodul*/sub/a" >actual && + git grep -e "(3|4)" --recurse-submodules HEAD -- "submodul*/sub/a" >actual && test_cmp expect actual ' test_expect_success !MINGW 'grep recurse submodule colon in name' ' git init parent && test_when_finished "rm -rf parent" && - echo "foobar" >"parent/fi:le" && + echo "(1|2)d(3|4)" >"parent/fi:le" && git -C parent add "fi:le" && git -C parent commit -m "add fi:le" && git init "su:b" && test_when_finished "rm -rf su:b" && - echo "foobar" >"su:b/fi:le" && + echo "(1|2)d(3|4)" >"su:b/fi:le" && git -C "su:b" add "fi:le" && git -C "su:b" commit -m "add fi:le" && @@ -172,30 +190,30 @@ test_expect_success !MINGW 'grep recurse submodule colon in name' ' git -C parent commit -m "add submodule" && cat >expect <<-\EOF && - fi:le:foobar - su:b/fi:le:foobar + fi:le:(1|2)d(3|4) + su:b/fi:le:(1|2)d(3|4) EOF - git -C parent grep -e "foobar" --recurse-submodules >actual && + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules >actual && test_cmp expect actual && cat >expect <<-\EOF && - HEAD:fi:le:foobar - HEAD:su:b/fi:le:foobar + HEAD:fi:le:(1|2)d(3|4) + HEAD:su:b/fi:le:(1|2)d(3|4) EOF - git -C parent grep -e "foobar" --recurse-submodules HEAD >actual && + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules HEAD >actual && test_cmp expect actual ' test_expect_success 'grep history with moved submoules' ' git init parent && test_when_finished "rm -rf parent" && - echo "foobar" >parent/file && + echo "(1|2)d(3|4)" >parent/file && git -C parent add file && git -C parent commit -m "add file" && git init sub && test_when_finished "rm -rf sub" && - echo "foobar" >sub/file && + echo "(1|2)d(3|4)" >sub/file && git -C sub add file && git -C sub commit -m "add file" && @@ -203,82 +221,82 @@ test_expect_success 'grep history with moved submoules' ' git -C parent commit -m "add submodule" && cat >expect <<-\EOF && - dir/sub/file:foobar - file:foobar + dir/sub/file:(1|2)d(3|4) + file:(1|2)d(3|4) EOF - git -C parent grep -e "foobar" --recurse-submodules >actual && + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules >actual && test_cmp expect actual && git -C parent mv dir/sub sub-moved && git -C parent commit -m "moved submodule" && cat >expect <<-\EOF && - file:foobar - sub-moved/file:foobar + file:(1|2)d(3|4) + sub-moved/file:(1|2)d(3|4) EOF - git -C parent grep -e "foobar" --recurse-submodules >actual && + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules >actual && test_cmp expect actual && cat >expect <<-\EOF && - HEAD^:dir/sub/file:foobar - HEAD^:file:foobar + HEAD^:dir/sub/file:(1|2)d(3|4) + HEAD^:file:(1|2)d(3|4) EOF - git -C parent grep -e "foobar" --recurse-submodules HEAD^ >actual && + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules HEAD^ >actual && test_cmp expect actual ' test_expect_success 'grep using relative path' ' test_when_finished "rm -rf parent sub" && git init sub && - echo "foobar" >sub/file && + echo "(1|2)d(3|4)" >sub/file && git -C sub add file && git -C sub commit -m "add file" && git init parent && - echo "foobar" >parent/file && + echo "(1|2)d(3|4)" >parent/file && git -C parent add file && mkdir parent/src && - echo "foobar" >parent/src/file2 && + echo "(1|2)d(3|4)" >parent/src/file2 && git -C parent add src/file2 && git -C parent submodule add ../sub && git -C parent commit -m "add files and submodule" && # From top works cat >expect <<-\EOF && - file:foobar - src/file2:foobar - sub/file:foobar + file:(1|2)d(3|4) + src/file2:(1|2)d(3|4) + sub/file:(1|2)d(3|4) EOF - git -C parent grep --recurse-submodules -e "foobar" >actual && + git -C parent grep --recurse-submodules -e "(1|2)d(3|4)" >actual && test_cmp expect actual && # Relative path to top cat >expect <<-\EOF && - ../file:foobar - file2:foobar - ../sub/file:foobar + ../file:(1|2)d(3|4) + file2:(1|2)d(3|4) + ../sub/file:(1|2)d(3|4) EOF - git -C parent/src grep --recurse-submodules -e "foobar" -- .. >actual && + git -C parent/src grep --recurse-submodules -e "(1|2)d(3|4)" -- .. >actual && test_cmp expect actual && # Relative path to submodule cat >expect <<-\EOF && - ../sub/file:foobar + ../sub/file:(1|2)d(3|4) EOF - git -C parent/src grep --recurse-submodules -e "foobar" -- ../sub >actual && + git -C parent/src grep --recurse-submodules -e "(1|2)d(3|4)" -- ../sub >actual && test_cmp expect actual ' test_expect_success 'grep from a subdir' ' test_when_finished "rm -rf parent sub" && git init sub && - echo "foobar" >sub/file && + echo "(1|2)d(3|4)" >sub/file && git -C sub add file && git -C sub commit -m "add file" && git init parent && mkdir parent/src && - echo "foobar" >parent/src/file && + echo "(1|2)d(3|4)" >parent/src/file && git -C parent add src/file && git -C parent submodule add ../sub src/sub && git -C parent submodule add ../sub sub && @@ -286,19 +304,19 @@ test_expect_success 'grep from a subdir' ' # Verify grep from root works cat >expect <<-\EOF && - src/file:foobar - src/sub/file:foobar - sub/file:foobar + src/file:(1|2)d(3|4) + src/sub/file:(1|2)d(3|4) + sub/file:(1|2)d(3|4) EOF - git -C parent grep --recurse-submodules -e "foobar" >actual && + git -C parent grep --recurse-submodules -e "(1|2)d(3|4)" >actual && test_cmp expect actual && # Verify grep from a subdir works cat >expect <<-\EOF && - file:foobar - sub/file:foobar + file:(1|2)d(3|4) + sub/file:(1|2)d(3|4) EOF - git -C parent/src grep --recurse-submodules -e "foobar" >actual && + git -C parent/src grep --recurse-submodules -e "(1|2)d(3|4)" >actual && test_cmp expect actual ' @@ -313,4 +331,53 @@ test_incompatible_with_recurse_submodules () test_incompatible_with_recurse_submodules --untracked test_incompatible_with_recurse_submodules --no-index +test_expect_success 'grep --recurse-submodules should pass the pattern type along' ' + # Fixed + test_must_fail git grep -F --recurse-submodules -e "(.|.)[\d]" && + test_must_fail git -c grep.patternType=fixed grep --recurse-submodules -e "(.|.)[\d]" && + + # Basic + git grep -G --recurse-submodules -e "(.|.)[\d]" >actual && + cat >expect <<-\EOF && + a:(1|2)d(3|4) + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) + EOF + test_cmp expect actual && + git -c grep.patternType=basic grep --recurse-submodules -e "(.|.)[\d]" >actual && + test_cmp expect actual && + + # Extended + git grep -E --recurse-submodules -e "(.|.)[\d]" >actual && + cat >expect <<-\EOF && + .gitmodules:[submodule "submodule"] + .gitmodules: path = submodule + .gitmodules: url = ./submodule + a:(1|2)d(3|4) + submodule/.gitmodules:[submodule "sub"] + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) + EOF + test_cmp expect actual && + git -c grep.patternType=extended grep --recurse-submodules -e "(.|.)[\d]" >actual && + test_cmp expect actual && + git -c grep.extendedRegexp=true grep --recurse-submodules -e "(.|.)[\d]" >actual && + test_cmp expect actual && + + # Perl + if test_have_prereq PCRE + then + git grep -P --recurse-submodules -e "(.|.)[\d]" >actual && + cat >expect <<-\EOF && + a:(1|2)d(3|4) + b/b:(3|4) + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) + EOF + test_cmp expect actual && + git -c grep.patternType=perl grep --recurse-submodules -e "(.|.)[\d]" >actual && + test_cmp expect actual + fi +' + test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 60a80f60b2..d1e4e8ad19 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -1913,4 +1913,52 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' ' test_cmp expected-list actual-list ' +test_expect_success $PREREQ 'invoke hook' ' + mkdir -p .git/hooks && + + write_script .git/hooks/sendemail-validate <<-\EOF && + # test that we have the correct environment variable, pwd, and + # argument + case "$GIT_DIR" in + *.git) + true + ;; + *) + false + ;; + esac && + test -f 0001-add-master.patch && + grep "add master" "$1" + EOF + + mkdir subdir && + ( + # Test that it works even if we are not at the root of the + # working tree + cd subdir && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/../fake.sendmail" \ + ../0001-add-master.patch && + + # Verify error message when a patch is rejected by the hook + sed -e "s/add master/x/" ../0001-add-master.patch >../another.patch && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/../fake.sendmail" \ + ../another.patch 2>err + test_i18ngrep "rejected by sendemail-validate hook" err + ) +' + +test_expect_success $PREREQ 'test that send-email works outside a repo' ' + nongit git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + "$(pwd)/0001-add-master.patch" +' + test_done diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index b5149fde6e..8dcb05c4a5 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -70,7 +70,7 @@ test_expect_success '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 && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && test_tick && echo rosten >file && git commit -s -m den file && diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 6d06ed96cb..cc8d463e01 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -519,7 +519,7 @@ test_expect_success \ test_expect_success \ 'encode(commit): utf8' \ - '. "$TEST_DIRECTORY"/t3901-utf8.txt && + '. "$TEST_DIRECTORY"/t3901/utf8.txt && test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" && test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && echo "UTF-8" >> file && @@ -529,7 +529,7 @@ test_expect_success \ test_expect_success \ 'encode(commit): iso-8859-1' \ - '. "$TEST_DIRECTORY"/t3901-8859-1.txt && + '. "$TEST_DIRECTORY"/t3901/8859-1.txt && test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" && test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && echo "ISO-8859-1" >> file && diff --git a/t/test-lib.sh b/t/test-lib.sh index 26b3edfb2e..2306574dc9 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -745,20 +745,25 @@ test_done () { fi case "$test_failure" in 0) - # Maybe print SKIP message - if test -n "$skip_all" && test $test_count -gt 0 - then - error "Can't use skip_all after running some tests" - fi - test -z "$skip_all" || skip_all=" # SKIP $skip_all" - if test $test_external_has_tap -eq 0 then if test $test_remaining -gt 0 then say_color pass "# passed all $msg" fi - say "1..$test_count$skip_all" + + # Maybe print SKIP message + test -z "$skip_all" || skip_all="# SKIP $skip_all" + case "$test_count" in + 0) + say "1..$test_count${skip_all:+ $skip_all}" + ;; + *) + test -z "$skip_all" || + say_color warn "$skip_all" + say "1..$test_count" + ;; + esac fi if test -z "$debug" @@ -1013,8 +1018,9 @@ esac ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1 test -z "$NO_PERL" && test_set_prereq PERL +test -z "$NO_PTHREADS" && test_set_prereq PTHREADS test -z "$NO_PYTHON" && test_set_prereq PYTHON -test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE +test -n "$USE_LIBPCRE1$USE_LIBPCRE2" && test_set_prereq PCRE test -z "$NO_GETTEXT" && test_set_prereq GETTEXT # Can we rely on git's output in the C locale? @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "string-list.h" #include "run-command.h" #include "commit.h" diff --git a/transport-helper.c b/transport-helper.c index 36408046eb..33cff38cc0 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -242,8 +242,7 @@ static int disconnect_helper(struct transport *transport) close(data->helper->out); fclose(data->out); res = finish_command(data->helper); - free(data->helper); - data->helper = NULL; + FREE_AND_NULL(data->helper); } return res; } @@ -711,43 +710,35 @@ static int push_update_ref_status(struct strbuf *buf, if (!strcmp(msg, "no match")) { status = REF_STATUS_NONE; - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } else if (!strcmp(msg, "up to date")) { status = REF_STATUS_UPTODATE; - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } else if (!strcmp(msg, "non-fast forward")) { status = REF_STATUS_REJECT_NONFASTFORWARD; - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } else if (!strcmp(msg, "already exists")) { status = REF_STATUS_REJECT_ALREADY_EXISTS; - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } else if (!strcmp(msg, "fetch first")) { status = REF_STATUS_REJECT_FETCH_FIRST; - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } else if (!strcmp(msg, "needs force")) { status = REF_STATUS_REJECT_NEEDS_FORCE; - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } else if (!strcmp(msg, "stale info")) { status = REF_STATUS_REJECT_STALE; - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } else if (!strcmp(msg, "forced update")) { forced = 1; - free(msg); - msg = NULL; + FREE_AND_NULL(msg); } } diff --git a/transport.c b/transport.c index 9bfcf870f9..d75ff0514d 100644 --- a/transport.c +++ b/transport.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "transport.h" #include "run-command.h" #include "pkt-line.h" @@ -1145,8 +1146,7 @@ void transport_unlock_pack(struct transport *transport) { if (transport->pack_lockfile) { unlink_or_warn(transport->pack_lockfile); - free(transport->pack_lockfile); - transport->pack_lockfile = NULL; + FREE_AND_NULL(transport->pack_lockfile); } } diff --git a/tree-diff.c b/tree-diff.c index e164e532b2..bd6d65a409 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -26,11 +26,12 @@ } while(0) 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 combine_diff_path *p, const struct object_id *oid, + const struct object_id **parents_oid, 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); +static int ll_diff_tree_oid(const struct object_id *old_oid, + const struct object_id *new_oid, + struct strbuf *base, struct diff_options *opt); /* * Compare two tree entries, taking into account only path/S_ISDIR(mode), @@ -74,25 +75,25 @@ static int emit_diff_first_parent_only(struct diff_options *opt, struct combine_ { struct combine_diff_parent *p0 = &p->parent[0]; if (p->mode && p0->mode) { - opt->change(opt, p0->mode, p->mode, p0->oid.hash, p->oid.hash, + opt->change(opt, p0->mode, p->mode, &p0->oid, &p->oid, 1, 1, p->path, 0, 0); } else { - const unsigned char *sha1; + const struct object_id *oid; unsigned int mode; int addremove; if (p->mode) { addremove = '+'; - sha1 = p->oid.hash; + oid = &p->oid; mode = p->mode; } else { addremove = '-'; - sha1 = p0->oid.hash; + oid = &p0->oid; mode = p0->mode; } - opt->add_remove(opt, addremove, mode, sha1, 1, p->path, 0); + opt->add_remove(opt, addremove, mode, oid, 1, p->path, 0); } return 0; /* we are done with p */ @@ -131,7 +132,7 @@ static int emit_diff_first_parent_only(struct diff_options *opt, struct combine_ */ 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) + unsigned mode, const struct object_id *oid) { struct combine_diff_path *p; size_t len = st_add(base->len, pathlen); @@ -140,8 +141,7 @@ static struct combine_diff_path *path_appendnew(struct combine_diff_path *last, /* 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; + FREE_AND_NULL(p); } if (!p) { @@ -161,7 +161,7 @@ static struct combine_diff_path *path_appendnew(struct combine_diff_path *last, memcpy(p->path + base->len, path, pathlen); p->path[len] = 0; p->mode = mode; - hashcpy(p->oid.hash, sha1 ? sha1 : null_sha1); + oidcpy(&p->oid, oid ? oid : &null_oid); return p; } @@ -183,7 +183,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, { unsigned mode; const char *path; - const unsigned char *sha1; + const struct object_id *oid; int pathlen; int old_baselen = base->len; int i, isdir, recurse = 0, emitthis = 1; @@ -193,7 +193,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, if (t) { /* path present in resulting tree */ - sha1 = tree_entry_extract(t, &path, &mode)->hash; + oid = tree_entry_extract(t, &path, &mode); pathlen = tree_entry_len(&t->entry); isdir = S_ISDIR(mode); } else { @@ -208,7 +208,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, pathlen = tree_entry_len(&tp[imin].entry); isdir = S_ISDIR(mode); - sha1 = NULL; + oid = NULL; mode = 0; } @@ -220,7 +220,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, if (emitthis) { int keep; struct combine_diff_path *pprev = p; - p = path_appendnew(p, nparent, base, path, pathlen, mode, sha1); + p = path_appendnew(p, nparent, base, path, pathlen, mode, oid); for (i = 0; i < nparent; ++i) { /* @@ -229,7 +229,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, */ int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ); - const unsigned char *sha1_i; + const struct object_id *oid_i; unsigned mode_i; p->parent[i].status = @@ -239,16 +239,16 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, DIFF_STATUS_ADDED; if (tpi_valid) { - sha1_i = tp[i].entry.oid->hash; + oid_i = tp[i].entry.oid; mode_i = tp[i].entry.mode; } else { - sha1_i = NULL; + oid_i = &null_oid; mode_i = 0; } p->parent[i].mode = mode_i; - hashcpy(p->parent[i].oid.hash, sha1_i ? sha1_i : null_sha1); + oidcpy(&p->parent[i].oid, oid_i); } keep = 1; @@ -273,21 +273,20 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, } if (recurse) { - const unsigned char **parents_sha1; + const struct object_id **parents_oid; - FAST_ARRAY_ALLOC(parents_sha1, nparent); + FAST_ARRAY_ALLOC(parents_oid, nparent); 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.oid->hash - : NULL; + parents_oid[i] = tpi_valid ? tp[i].entry.oid : NULL; } strbuf_add(base, path, pathlen); strbuf_addch(base, '/'); - p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt); - FAST_ARRAY_FREE(parents_sha1, nparent); + p = ll_diff_tree_paths(p, oid, parents_oid, nparent, base, opt); + FAST_ARRAY_FREE(parents_oid, nparent); } strbuf_setlen(base, old_baselen); @@ -312,7 +311,7 @@ static void skip_uninteresting(struct tree_desc *t, struct strbuf *base, /* - * generate paths for combined diff D(sha1,parents_sha1[]) + * generate paths for combined diff D(sha1,parents_oid[]) * * 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 @@ -404,8 +403,8 @@ static inline void update_tp_entries(struct tree_desc *tp, int nparent) } 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 combine_diff_path *p, const struct object_id *oid, + const struct object_id **parents_oid, int nparent, struct strbuf *base, struct diff_options *opt) { struct tree_desc t, *tp; @@ -419,11 +418,11 @@ static struct combine_diff_path *ll_diff_tree_paths( * load parents first, as they are probably already cached. * * ( log_tree_diff() parses commit->parent before calling here via - * diff_tree_sha1(parent, commit) ) + * diff_tree_oid(parent, commit) ) */ for (i = 0; i < nparent; ++i) - tptree[i] = fill_tree_descriptor(&tp[i], parents_sha1[i]); - ttree = fill_tree_descriptor(&t, sha1); + tptree[i] = fill_tree_descriptor(&tp[i], parents_oid[i]->hash); + ttree = fill_tree_descriptor(&t, oid->hash); /* Enable recursion indefinitely */ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE); @@ -548,19 +547,18 @@ static struct combine_diff_path *ll_diff_tree_paths( } struct combine_diff_path *diff_tree_paths( - struct combine_diff_path *p, const unsigned char *sha1, - const unsigned char **parents_sha1, int nparent, + struct combine_diff_path *p, const struct object_id *oid, + const struct object_id **parents_oid, int nparent, struct strbuf *base, struct diff_options *opt) { - p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt); + p = ll_diff_tree_paths(p, oid, parents_oid, 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; + FREE_AND_NULL(p->next); } return p; @@ -577,7 +575,9 @@ static inline int diff_might_be_rename(void) !DIFF_FILE_VALID(diff_queued_diff.queue[0]->one); } -static void try_to_follow_renames(const unsigned char *old, const unsigned char *new, struct strbuf *base, struct diff_options *opt) +static void try_to_follow_renames(const struct object_id *old_oid, + const struct object_id *new_oid, + struct strbuf *base, struct diff_options *opt) { struct diff_options diff_opts; struct diff_queue_struct *q = &diff_queued_diff; @@ -615,7 +615,7 @@ static void try_to_follow_renames(const unsigned char *old, const unsigned char diff_opts.break_opt = opt->break_opt; diff_opts.rename_score = opt->rename_score; diff_setup_done(&diff_opts); - ll_diff_tree_sha1(old, new, base, &diff_opts); + ll_diff_tree_oid(old_oid, new_oid, base, &diff_opts); diffcore_std(&diff_opts); clear_pathspec(&diff_opts.pathspec); @@ -674,15 +674,16 @@ static void try_to_follow_renames(const unsigned char *old, const unsigned char q->nr = 1; } -static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new, - struct strbuf *base, struct diff_options *opt) +static int ll_diff_tree_oid(const struct object_id *old_oid, + const struct object_id *new_oid, + struct strbuf *base, struct diff_options *opt) { struct combine_diff_path phead, *p; pathchange_fn_t pathchange_old = opt->pathchange; phead.next = NULL; opt->pathchange = emit_diff_first_parent_only; - diff_tree_paths(&phead, new, &old, 1, base, opt); + diff_tree_paths(&phead, new_oid, &old_oid, 1, base, opt); for (p = phead.next; p;) { struct combine_diff_path *pprev = p; @@ -694,7 +695,9 @@ static int ll_diff_tree_sha1(const unsigned char *old, const unsigned char *new, return 0; } -int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base_str, struct diff_options *opt) +int diff_tree_oid(const struct object_id *old_oid, + const struct object_id *new_oid, + const char *base_str, struct diff_options *opt) { struct strbuf base; int retval; @@ -702,16 +705,16 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha strbuf_init(&base, PATH_MAX); strbuf_addstr(&base, base_str); - retval = ll_diff_tree_sha1(old, new, &base, opt); + retval = ll_diff_tree_oid(old_oid, new_oid, &base, opt); if (!*base_str && DIFF_OPT_TST(opt, FOLLOW_RENAMES) && diff_might_be_rename()) - try_to_follow_renames(old, new, &base, opt); + try_to_follow_renames(old_oid, new_oid, &base, opt); strbuf_release(&base); return retval; } -int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_options *opt) +int diff_root_tree_oid(const struct object_id *new_oid, const char *base, struct diff_options *opt) { - return diff_tree_sha1(NULL, new, base, opt); + return diff_tree_oid(NULL, new_oid, base, opt); } diff --git a/tree-walk.c b/tree-walk.c index f25a08fddf..6a42e402b0 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -589,7 +589,6 @@ enum follow_symlinks_result get_tree_entry_follow_symlinks(unsigned char *tree_s int i; init_tree_desc(&t, NULL, 0UL); - strbuf_init(result_path, 0); strbuf_addstr(&namebuf, name); hashcpy(current_tree_sha1, tree_sha1); @@ -1,3 +1,4 @@ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "cache-tree.h" #include "tree.h" @@ -8,7 +9,11 @@ const char *tree_type = "tree"; -static int read_one_entry_opt(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage, int opt) +static int read_one_entry_opt(struct index_state *istate, + const unsigned char *sha1, + const char *base, int baselen, + const char *pathname, + unsigned mode, int stage, int opt) { int len; unsigned int size; @@ -27,14 +32,15 @@ static int read_one_entry_opt(const unsigned char *sha1, const char *base, int b memcpy(ce->name, base, baselen); memcpy(ce->name + baselen, pathname, len+1); hashcpy(ce->oid.hash, sha1); - return add_cache_entry(ce, opt); + return add_index_entry(istate, ce, opt); } static int read_one_entry(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *context) { - return read_one_entry_opt(sha1, base->buf, base->len, pathname, + struct index_state *istate = context; + return read_one_entry_opt(istate, sha1, base->buf, base->len, pathname, mode, stage, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); } @@ -47,7 +53,8 @@ static int read_one_entry_quick(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *context) { - return read_one_entry_opt(sha1, base->buf, base->len, pathname, + struct index_state *istate = context; + return read_one_entry_opt(istate, sha1, base->buf, base->len, pathname, mode, stage, ADD_CACHE_JUST_APPEND); } @@ -144,7 +151,8 @@ static int cmp_cache_name_compare(const void *a_, const void *b_) ce2->name, ce2->ce_namelen, ce_stage(ce2)); } -int read_tree(struct tree *tree, int stage, struct pathspec *match) +int read_tree(struct tree *tree, int stage, struct pathspec *match, + struct index_state *istate) { read_tree_fn_t fn = NULL; int i, err; @@ -164,23 +172,23 @@ int read_tree(struct tree *tree, int stage, struct pathspec *match) * do it the original slow way, otherwise, append and then * sort at the end. */ - for (i = 0; !fn && i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + for (i = 0; !fn && i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; if (ce_stage(ce) == stage) fn = read_one_entry; } if (!fn) fn = read_one_entry_quick; - err = read_tree_recursive(tree, "", 0, stage, match, fn, NULL); + err = read_tree_recursive(tree, "", 0, stage, match, fn, istate); if (fn == read_one_entry || err) return err; /* * Sort the cache entry -- we need to nuke the cache tree, though. */ - cache_tree_free(&active_cache_tree); - QSORT(active_cache, active_nr, cmp_cache_name_compare); + cache_tree_free(&istate->cache_tree); + QSORT(istate->cache, istate->cache_nr, cmp_cache_name_compare); return 0; } @@ -226,8 +234,7 @@ int parse_tree_gently(struct tree *item, int quiet_on_missing) void free_tree_buffer(struct tree *tree) { - free(tree->buffer); - tree->buffer = NULL; + FREE_AND_NULL(tree->buffer); tree->size = 0; tree->object.parsed = 0; } @@ -34,6 +34,7 @@ extern int read_tree_recursive(struct tree *tree, int stage, const struct pathspec *pathspec, read_tree_fn_t fn, void *context); -extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec); +extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec, + struct index_state *istate); #endif /* TREE_H */ diff --git a/unpack-trees.c b/unpack-trees.c index aa15111fef..dd535bc849 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1,5 +1,6 @@ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "config.h" #include "dir.h" #include "tree.h" #include "tree-walk.h" @@ -252,14 +253,18 @@ static int check_submodule_move_head(const struct cache_entry *ce, const char *new_id, struct unpack_trees_options *o) { + unsigned flags = SUBMODULE_MOVE_HEAD_DRY_RUN; const struct submodule *sub = submodule_from_ce(ce); if (!sub) return 0; + if (o->reset) + flags |= SUBMODULE_MOVE_HEAD_FORCE; + switch (sub->update_strategy.type) { case SM_UPDATE_UNSPECIFIED: case SM_UPDATE_CHECKOUT: - if (submodule_move_head(ce->name, old_id, new_id, SUBMODULE_MOVE_HEAD_DRY_RUN)) + if (submodule_move_head(ce->name, old_id, new_id, flags)) return o->gently ? -1 : add_rejected_path(o, ERROR_WOULD_LOSE_SUBMODULE, ce->name); return 0; @@ -308,6 +313,7 @@ static void unlink_entry(const struct cache_entry *ce) case SM_UPDATE_CHECKOUT: case SM_UPDATE_REBASE: case SM_UPDATE_MERGE: + /* state.force is set at the caller. */ submodule_move_head(ce->name, "HEAD", NULL, SUBMODULE_MOVE_HEAD_FORCE); break; @@ -1068,7 +1074,7 @@ static int clear_ce_flags_dir(struct cache_entry **cache, int nr, struct cache_entry **cache_end; int dtype = DT_DIR; int ret = is_excluded_from_list(prefix->buf, prefix->len, - basename, &dtype, el); + basename, &dtype, el, &the_index); int rc; strbuf_addch(prefix, '/'); @@ -1171,7 +1177,7 @@ static int clear_ce_flags_1(struct cache_entry **cache, int nr, /* Non-directory */ dtype = ce_to_dtype(ce); ret = is_excluded_from_list(ce->name, ce_namelen(ce), - name, &dtype, el); + name, &dtype, el, &the_index); if (ret < 0) ret = defval; if (ret > 0) @@ -1251,7 +1257,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->skip_sparse_checkout = 1; if (!o->skip_sparse_checkout) { char *sparse = git_pathdup("info/sparse-checkout"); - if (add_excludes_from_file_to_list(sparse, "", 0, &el, 0) < 0) + if (add_excludes_from_file_to_list(sparse, "", 0, &el, NULL) < 0) o->skip_sparse_checkout = 1; else o->el = ⪙ @@ -1391,6 +1397,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options WRITE_TREE_SILENT | WRITE_TREE_REPAIR); } + move_index_extensions(&o->result, o->dst_index); discard_index(o->dst_index); *o->dst_index = o->result; } else { @@ -1592,7 +1599,7 @@ static int verify_clean_subdirectory(const struct cache_entry *ce, memset(&d, 0, sizeof(d)); if (o->dir) d.exclude_per_dir = o->dir->exclude_per_dir; - i = read_directory(&d, pathbuf, namelen+1, NULL); + i = read_directory(&d, &the_index, pathbuf, namelen+1, NULL); if (i) return o->gently ? -1 : add_rejected_path(o, ERROR_NOT_UPTODATE_DIR, ce->name); @@ -1634,7 +1641,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype, return 0; if (o->dir && - is_excluded(o->dir, name, &dtype)) + is_excluded(o->dir, &the_index, name, &dtype)) /* * ce->name is explicitly excluded, so it is Ok to * overwrite it. diff --git a/upload-pack.c b/upload-pack.c index 5330c02c14..7efff2fbfd 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "refs.h" #include "pkt-line.h" #include "sideband.h" @@ -6,12 +6,9 @@ #include "git-compat-util.h" #include "cache.h" -static FILE *error_handle; - void vreportf(const char *prefix, const char *err, va_list params) { char msg[4096]; - FILE *fh = error_handle ? error_handle : stderr; char *p; vsnprintf(msg, sizeof(msg), err, params); @@ -19,7 +16,7 @@ void vreportf(const char *prefix, const char *err, va_list params) if (iscntrl(*p) && *p != '\t' && *p != '\n') *p = '?'; } - fprintf(fh, "%s%s\n", prefix, msg); + fprintf(stderr, "%s%s\n", prefix, msg); } static NORETURN void usage_builtin(const char *err, va_list params) @@ -88,11 +85,6 @@ void set_die_is_recursing_routine(int (*routine)(void)) die_is_recursing = routine; } -void set_error_handle(FILE *fh) -{ - error_handle = fh; -} - void NORETURN usagef(const char *err, ...) { va_list params; @@ -201,3 +193,35 @@ void warning(const char *warn, ...) warn_routine(warn, params); va_end(params); } + +static NORETURN void BUG_vfl(const char *file, int line, const char *fmt, va_list params) +{ + char prefix[256]; + + /* truncation via snprintf is OK here */ + if (file) + snprintf(prefix, sizeof(prefix), "BUG: %s:%d: ", file, line); + else + snprintf(prefix, sizeof(prefix), "BUG: "); + + vreportf(prefix, fmt, params); + abort(); +} + +#ifdef HAVE_VARIADIC_MACROS +NORETURN void BUG_fl(const char *file, int line, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + BUG_vfl(file, line, fmt, ap); + va_end(ap); +} +#else +NORETURN void BUG(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + BUG_vfl(NULL, 0, fmt, ap); + va_end(ap); +} +#endif diff --git a/userdiff.c b/userdiff.c index 8b732e40bc..2c1502f719 100644 --- a/userdiff.c +++ b/userdiff.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "userdiff.h" #include "attr.h" diff --git a/versioncmp.c b/versioncmp.c index 9f81dc1062..069ee94a4d 100644 --- a/versioncmp.c +++ b/versioncmp.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "string-list.h" /* diff --git a/worktree.c b/worktree.c index 726f732e5e..2801c6d52b 100644 --- a/worktree.c +++ b/worktree.c @@ -351,6 +351,7 @@ int submodule_uses_worktrees(const char *path) /* The env would be set for the superproject. */ get_common_dir_noenv(&sb, submodule_gitdir); + free(submodule_gitdir); /* * The check below is only known to be good for repository format @@ -370,7 +371,6 @@ int submodule_uses_worktrees(const char *path) /* See if there is any file inside the worktrees directory. */ dir = opendir(sb.buf); strbuf_release(&sb); - free(submodule_gitdir); if (!dir) return 0; @@ -2,6 +2,7 @@ * Various trivial helper wrappers around standard functions */ #include "cache.h" +#include "config.h" static void do_nothing(size_t size) { @@ -418,6 +419,32 @@ FILE *fopen_for_writing(const char *path) return ret; } +static void warn_on_inaccessible(const char *path) +{ + warning_errno(_("unable to access '%s'"), path); +} + +int warn_on_fopen_errors(const char *path) +{ + if (errno != ENOENT && errno != ENOTDIR) { + warn_on_inaccessible(path); + return -1; + } + + return 0; +} + +FILE *fopen_or_warn(const char *path, const char *mode) +{ + FILE *fp = fopen(path, mode); + + if (fp) + return fp; + + warn_on_fopen_errors(path); + return NULL; +} + int xmkstemp(char *template) { int fd; @@ -576,15 +603,10 @@ int remove_or_warn(unsigned int mode, const char *file) return S_ISGITLINK(mode) ? rmdir_or_warn(file) : unlink_or_warn(file); } -void warn_on_inaccessible(const char *path) -{ - warning_errno(_("unable to access '%s'"), path); -} - static int access_error_is_ok(int err, unsigned flag) { - return err == ENOENT || err == ENOTDIR || - ((flag & ACCESS_EACCES_OK) && err == EACCES); + return (is_missing_file_error(err) || + ((flag & ACCESS_EACCES_OK) && err == EACCES)); } int access_or_warn(const char *path, int mode, unsigned flag) diff --git a/wt-status.c b/wt-status.c index 48c9854d53..7992a73902 100644 --- a/wt-status.c +++ b/wt-status.c @@ -137,6 +137,7 @@ void wt_status_prepare(struct wt_status *s) s->untracked.strdup_strings = 1; s->ignored.strdup_strings = 1; s->show_branch = -1; /* unspecified */ + s->show_stash = 0; s->display_comment_prefix = 0; } @@ -665,7 +666,7 @@ static void wt_status_collect_untracked(struct wt_status *s) dir.untracked = the_index.untracked; setup_standard_excludes(&dir); - fill_directory(&dir, &s->pathspec); + fill_directory(&dir, &the_index, &s->pathspec); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; @@ -801,6 +802,27 @@ static void wt_longstatus_print_changed(struct wt_status *s) wt_longstatus_print_trailer(s); } +static int stash_count_refs(struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, + const char *message, void *cb_data) +{ + int *c = cb_data; + (*c)++; + return 0; +} + +static void wt_longstatus_print_stash_summary(struct wt_status *s) +{ + int stash_count = 0; + + for_each_reflog_ent("refs/stash", stash_count_refs, &stash_count); + if (stash_count > 0) + status_printf_ln(s, GIT_COLOR_NORMAL, + Q_("Your stash currently has %d entry", + "Your stash currently has %d entries", stash_count), + stash_count); +} + static void wt_longstatus_print_submodule_summary(struct wt_status *s, int uncommitted) { struct child_process sm_summary = CHILD_PROCESS_INIT; @@ -896,17 +918,18 @@ conclude: status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); } -void wt_status_truncate_message_at_cut_line(struct strbuf *buf) +size_t wt_status_locate_end(const char *s, size_t len) { const char *p; struct strbuf pattern = STRBUF_INIT; strbuf_addf(&pattern, "\n%c %s", comment_line_char, cut_line); - if (starts_with(buf->buf, pattern.buf + 1)) - strbuf_setlen(buf, 0); - else if ((p = strstr(buf->buf, pattern.buf))) - strbuf_setlen(buf, p - buf->buf + 1); + if (starts_with(s, pattern.buf + 1)) + len = 0; + else if ((p = strstr(s, pattern.buf))) + len = p - s + 1; strbuf_release(&pattern); + return len; } void wt_status_add_cut_line(FILE *fp) @@ -1065,7 +1088,8 @@ static void show_am_in_progress(struct wt_status *s, static char *read_line_from_git_path(const char *filename) { struct strbuf buf = STRBUF_INIT; - FILE *fp = fopen(git_path("%s", filename), "r"); + FILE *fp = fopen_or_warn(git_path("%s", filename), "r"); + if (!fp) { strbuf_release(&buf); return NULL; @@ -1082,29 +1106,29 @@ static char *read_line_from_git_path(const char *filename) static int split_commit_in_progress(struct wt_status *s) { int split_in_progress = 0; - char *head = read_line_from_git_path("HEAD"); - char *orig_head = read_line_from_git_path("ORIG_HEAD"); - char *rebase_amend = read_line_from_git_path("rebase-merge/amend"); - char *rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head"); + char *head, *orig_head, *rebase_amend, *rebase_orig_head; - if (!head || !orig_head || !rebase_amend || !rebase_orig_head || + if ((!s->amend && !s->nowarn && !s->workdir_dirty) || !s->branch || strcmp(s->branch, "HEAD")) - return split_in_progress; + return 0; - if (!strcmp(rebase_amend, rebase_orig_head)) { - if (strcmp(head, rebase_amend)) - split_in_progress = 1; - } else if (strcmp(orig_head, rebase_orig_head)) { - split_in_progress = 1; - } + head = read_line_from_git_path("HEAD"); + orig_head = read_line_from_git_path("ORIG_HEAD"); + rebase_amend = read_line_from_git_path("rebase-merge/amend"); + rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head"); - if (!s->amend && !s->nowarn && !s->workdir_dirty) - split_in_progress = 0; + if (!head || !orig_head || !rebase_amend || !rebase_orig_head) + ; /* fall through, no split in progress */ + else if (!strcmp(rebase_amend, rebase_orig_head)) + split_in_progress = !!strcmp(head, rebase_amend); + else if (strcmp(orig_head, rebase_orig_head)) + split_in_progress = 1; free(head); free(orig_head); free(rebase_amend); free(rebase_orig_head); + return split_in_progress; } @@ -1168,6 +1192,7 @@ static int read_rebase_todolist(const char *fname, struct string_list *lines) abbrev_sha1_in_line(&line); string_list_append(lines, line.buf); } + fclose(f); return 0; } @@ -1639,6 +1664,8 @@ static void wt_longstatus_print(struct wt_status *s) } else printf(_("nothing to commit, working tree clean\n")); } + if(s->show_stash) + wt_longstatus_print_stash_summary(s); } static void wt_shortstatus_unmerged(struct string_list_item *it, diff --git a/wt-status.h b/wt-status.h index 6018c627b1..d8ae2e590d 100644 --- a/wt-status.h +++ b/wt-status.h @@ -77,6 +77,7 @@ struct wt_status { unsigned colopts; int null_termination; int show_branch; + int show_stash; int hints; enum wt_status_format status_format; @@ -112,7 +113,7 @@ struct wt_status_state { unsigned char cherry_pick_head_sha1[20]; }; -void wt_status_truncate_message_at_cut_line(struct strbuf *); +size_t wt_status_locate_end(const char *s, size_t len); void wt_status_add_cut_line(FILE *fp); void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); diff --git a/xdiff-interface.c b/xdiff-interface.c index 060038c2d6..018e033089 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "xdiff-interface.h" #include "xdiff/xtypes.h" #include "xdiff/xdiffi.h" @@ -164,9 +165,9 @@ int read_mmfile(mmfile_t *ptr, const char *filename) size_t sz; if (stat(filename, &st)) - return error("Could not stat %s", filename); + return error_errno("Could not stat %s", filename); if ((f = fopen(filename, "rb")) == NULL) - return error("Could not open %s", filename); + return error_errno("Could not open %s", filename); sz = xsize_t(st.st_size); ptr->ptr = xmalloc(sz ? sz : 1); if (sz && fread(ptr->ptr, sz, 1, f) != 1) { |