diff options
184 files changed, 14968 insertions, 8603 deletions
diff --git a/Documentation/RelNotes/2.10.1.txt b/Documentation/RelNotes/2.10.1.txt new file mode 100644 index 0000000000..75c07e199e --- /dev/null +++ b/Documentation/RelNotes/2.10.1.txt @@ -0,0 +1,33 @@ +Git v2.10.1 Release Notes +========================= + +Fixes since v2.10 +----------------- + + * Clarify various ways to specify the "revision ranges" in the + documentation. + + * "diff-highlight" script (in contrib/) learned to work better with + "git log -p --graph" output. + + * The test framework left the number of tests and success/failure + count in the t/test-results directory, keyed by the name of the + test script plus the process ID. The latter however turned out not + to serve any useful purpose. The process ID part of the filename + has been removed. + + * Having a submodule whose ".git" repository is somehow corrupt + caused a few commands that recurse into submodules loop forever. + + * "git symbolic-ref -d HEAD" happily removes the symbolic ref, but + the resulting repository becomes an invalid one. Teach the command + to forbid removal of HEAD. + + * A test spawned a short-lived background process, which sometimes + prevented the test directory from getting removed at the end of the + script on some platforms. + + * Update a few tests that used to use GIT_CURL_VERBOSE to use the + newer GIT_TRACE_CURL. + +Also contains minor documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.11.0.txt b/Documentation/RelNotes/2.11.0.txt new file mode 100644 index 0000000000..8b29f7712d --- /dev/null +++ b/Documentation/RelNotes/2.11.0.txt @@ -0,0 +1,204 @@ +Git 2.11 Release Notes +====================== + +Updates since v2.10 +------------------- + +UI, Workflows & Features + + * "git format-patch --cover-letter HEAD^" to format a single patch + with a separate cover letter now numbers the output as [PATCH 0/1] + and [PATCH 1/1] by default. + + * An incoming "git push" that attempts to push too many bytes can now + be rejected by setting a new configuration variable at the receiving + end. + + * "git nosuchcommand --help" said "No manual entry for gitnosuchcommand", + which was not intuitive, given that "git nosuchcommand" said "git: + 'nosuchcommand' is not a git command". + + * "git clone --resurse-submodules --reference $path $URL" is a way to + reduce network transfer cost by borrowing objects in an existing + $path repository when cloning the superproject from $URL; it + learned to also peek into $path for presense of corresponding + repositories of submodules and borrow objects from there when able. + + * The "git diff --submodule={short,log}" mechanism has been enhanced + to allow "--submodule=diff" to show the patch between the submodule + commits bound to the superproject. + + * Even though "git hash-objects", which is a tool to take an + on-filesystem data stream and put it into the Git object store, + allowed to perform the "outside-world-to-Git" conversions (e.g. + end-of-line conversions and application of the clean-filter), and + it had the feature on by default from very early days, its reverse + operation "git cat-file", which takes an object from the Git object + store and externalize for the consumption by the outside world, + lacked an equivalent mechanism to run the "Git-to-outside-world" + conversion. The command learned the "--filters" option to do so. + + +Performance, Internal Implementation, Development Support etc. + + * The delta-base-cache mechanism has been a key to the performance in + a repository with a tightly packed packfile, but it did not scale + well even with a larger value of core.deltaBaseCacheLimit. + + * Enhance "git status --porcelain" output by collecting more data on + the state of the index and the working tree files, which may + further be used to teach git-prompt (in contrib/) to make fewer + calls to git. + + * Extract a small helper out of the function that reads the authors + script file "git am" internally uses. + (merge a77598e jc/am-read-author-file later to maint). + + * Lifts calls to exit(2) and die() higher in the callchain in + sequencer.c files so that more helper functions in it can be used + by callers that want to handle error conditions themselves. + + * "git am" has been taught to make an internal call to "git apply"'s + innards without spawning the latter as a separate process. + + * The ref-store abstraction was introduced to the refs API so that we + can plug in different backends to store references. + + * The "unsigned char sha1[20]" to "struct object_id" conversion + continues. Notable changes in this round includes that ce->sha1, + i.e. the object name recorded in the cache_entry, turns into an + object_id. + + * JGit can show a fake ref "capabilities^{}" to "git fetch" when it + does not advertise any refs, but "git fetch" was not prepared to + see such an advertisement. When the other side disconnects without + giving any ref advertisement, we used to say "there may not be a + repository at that URL", but we may have seen other advertisement + like "shallow" and ".have" in which case we definitely know that a + repository is there. The code to detect this case has also been + updated. + + * Some codepaths in "git pack-objects" were not ready to use an + existing pack bitmap; now they are and as the result they have + become faster. + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v2.10 +----------------- + +Unless otherwise noted, all the fixes since v2.9 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * Clarify various ways to specify the "revision ranges" in the + documentation. + + * "diff-highlight" script (in contrib/) learned to work better with + "git log -p --graph" output. + + * The test framework left the number of tests and success/failure + count in the t/test-results directory, keyed by the name of the + test script plus the process ID. The latter however turned out not + to serve any useful purpose. The process ID part of the filename + has been removed. + + * Having a submodule whose ".git" repository is somehow corrupt + caused a few commands that recurse into submodules loop forever. + + * "git symbolic-ref -d HEAD" happily removes the symbolic ref, but + the resulting repository becomes an invalid one. Teach the command + to forbid removal of HEAD. + + * A test spawned a short-lived background process, which sometimes + prevented the test directory from getting removed at the end of the + script on some platforms. + + * Update a few tests that used to use GIT_CURL_VERBOSE to use the + newer GIT_TRACE_CURL. + + * "git pack-objects --include-tag" was taught that when we know that + we are sending an object C, we want a tag B that directly points at + C but also a tag A that points at the tag B. We used to miss the + intermediate tag B in some cases. + (merge b773dde jk/pack-tag-of-tag later to maint). + + * Update Japanese translation for "git-gui". + (merge 02748bc sy/git-gui-i18n-ja later to maint). + + * "git fetch http::/site/path" did not die correctly and segfaulted + instead. + (merge d63ed6e jk/fix-remote-curl-url-wo-proto later to maint). + + * "git commit-tree" stopped reading commit.gpgsign configuration + variable that was meant for Porcelain "git commit" in Git 2.9; we + forgot to update "git gui" to look at the configuration to match + this change. + (merge f14a310 js/git-gui-commit-gpgsign later to maint). + + * "git add --chmod=+x" added recently lacked documentation, which has + been corrected. + (merge 7ef7903 et/add-chmod-x later to maint). + + * "git log --cherry-pick" used to include merge commits as candidates + to be matched up with other commits, resulting a lot of wasted time. + The patch-id generation logic has been updated to ignore merges to + avoid the wastage. + (merge 7c81040 jk/patch-ids-no-merges later to maint). + + * The http transport (with curl-multi option, which is the default + these days) failed to remove curl-easy handle from a curlm session, + which led to unnecessary API failures. + (merge 2abc848 ew/http-do-not-forget-to-call-curl-multi-remove-handle later to maint). + + * There were numerous corner cases in which the configuration files + are read and used or not read at all depending on the directory a + Git command was run, leading to inconsistent behaviour. The code + to set-up repository access at the beginning of a Git process has + been updated to fix them. + (merge 4d0efa1 jk/setup-sequence-update later to maint). + + * "git diff -W" output needs to extend the context backward to + include the header line of the current function and also forward to + include the body of the entire current function up to the header + line of the next one. This process may have to merge to adjacent + hunks, but the code forgot to do so in some cases. + (merge 45d2f75 rs/xdiff-merge-overlapping-hunks-for-W-context later to maint). + + * Performance tests done via "t/perf" did not use the same set of + build configuration if the user relied on autoconf generated + configuration. + (merge cd5c281 ks/perf-build-with-autoconf later to maint). + + * "git format-patch --base=..." feature that was recently added + showed the base commit information after "-- " e-mail signature + line, which turned out to be inconvenient. The base information + has been moved above the signature line. + (merge 480871e jt/format-patch-base-info-above-sig later to maint). + + * More i18n. + (merge 43073f8 va/i18n later to maint). + + * Even when "git pull --rebase=preserve" (and the underlying "git + rebase --preserve") can complete without creating any new commit + (i.e. fast-forwards), it still insisted on having a usable ident + information (read: user.email is set correctly), which was less + than nice. As the underlying commands used inside "git rebase" + would fail with a more meaningful error message and advice text + when the bogus ident matters, this extra check was removed. + (merge 1e461c4 jk/rebase-i-drop-ident-check later to maint). + + * "git gc --aggressive" used to limit the delta-chain length to 250, + which is way too deep for gaining additional space savings and is + detrimental for runtime performance. The limit has been reduced to + 50. + (merge 07e7dbf jk/reduce-gc-aggressive-depth later to maint). + + * Other minor doc, test and build updates and code cleanups. + (merge e78d57e bw/pathspec-remove-unused-extern-decl later to maint). + (merge ce25e4c rs/checkout-some-states-are-const later to maint). + (merge a8342a4 rs/strbuf-remove-fix later to maint). + (merge b56aa5b rs/unpack-trees-reduce-file-scope-global later to maint). + (merge 5efc60c mr/vcs-svn-printf-ulong later to maint). diff --git a/Documentation/RelNotes/2.9.4.txt b/Documentation/RelNotes/2.9.4.txt new file mode 100644 index 0000000000..01e864278b --- /dev/null +++ b/Documentation/RelNotes/2.9.4.txt @@ -0,0 +1,83 @@ +Git v2.9.4 Release Notes +======================== + +Fixes since v2.9.3 +------------------ + + * There are certain house-keeping tasks that need to be performed at + the very beginning of any Git program, and programs that are not + built-in commands had to do them exactly the same way as "git" + potty does. It was easy to make mistakes in one-off standalone + programs (like test helpers). A common "main()" function that + calls cmd_main() of individual program has been introduced to + make it harder to make mistakes. + + * "git merge" with renormalization did not work well with + merge-recursive, due to "safer crlf" conversion kicking in when it + shouldn't. + + * The reflog output format is documented better, and a new format + --date=unix to report the seconds-since-epoch (without timezone) + has been added. + + * "git push --force-with-lease" already had enough logic to allow + ensuring that such a push results in creation of a ref (i.e. the + receiving end did not have another push from sideways that would be + discarded by our force-pushing), but didn't expose this possibility + to the users. It does so now. + + * "import-tars" fast-import script (in contrib/) used to ignore a + hardlink target and replaced it with an empty file, which has been + corrected to record the same blob as the other file the hardlink is + shared with. + + * "git mv dir non-existing-dir/" did not work in some environments + the same way as existing mainstream platforms. The code now moves + "dir" to "non-existing-dir", without relying on rename("A", "B/") + that strips the trailing slash of '/'. + + * The "t/" hierarchy is prone to get an unusual pathname; "make test" + has been taught to make sure they do not contain paths that cannot + be checked out on Windows (and the mechanism can be reusable to + catch pathnames that are not portable to other platforms as need + arises). + + * When "git merge-recursive" works on history with many criss-cross + merges in "verbose" mode, the names the command assigns to the + virtual merge bases could have overwritten each other by unintended + reuse of the same piece of memory. + + * "git checkout --detach <branch>" used to give the same advice + message as that is issued when "git checkout <tag>" (or anything + that is not a branch name) is given, but asking with "--detach" is + an explicit enough sign that the user knows what is going on. The + advice message has been squelched in this case. + + * "git difftool" by default ignores the error exit from the backend + commands it spawns, because often they signal that they found + differences by exiting with a non-zero status code just like "diff" + does; the exit status codes 126 and above however are special in + that they are used to signal that the command is not executable, + does not exist, or killed by a signal. "git difftool" has been + taught to notice these exit status codes. + + * On Windows, help.browser configuration variable used to be ignored, + which has been corrected. + + * The "git -c var[=val] cmd" facility to append a configuration + variable definition at the end of the search order was described in + git(1) manual page, but not in git-config(1), which was more likely + place for people to look for when they ask "can I make a one-shot + override, and if so how?" + + * The tempfile (hence its user lockfile) API lets the caller to open + a file descriptor to a temporary file, write into it and then + finalize it by first closing the filehandle and then either + removing or renaming the temporary file. When the process spawns a + subprocess after obtaining the file descriptor, and if the + subprocess has not exited when the attempt to remove or rename is + made, the last step fails on Windows, because the subprocess has + the file descriptor still open. Open tempfile with O_CLOEXEC flag + to avoid this (on Windows, this is mapped to O_NOINHERIT). + +Also contains minor documentation updates and code clean-ups. diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 500230c054..08352deaae 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -122,9 +122,14 @@ without external resources. Instead of giving a URL to a mailing list archive, summarize the relevant points of the discussion. If you want to reference a previous commit in the history of a stable -branch use the format "abbreviated sha1 (subject, date)". So for example -like this: "Commit f86a374 (pack-bitmap.c: fix a memleak, 2015-03-30) -noticed [...]". +branch, use the format "abbreviated sha1 (subject, date)", +with the subject enclosed in a pair of double-quotes, like this: + + Commit f86a374 ("pack-bitmap.c: fix a memleak", 2015-03-30) + noticed that ... + +The "Copy commit summary" command of gitk can be used to obtain this +format. (3) Generate your patch using Git tools out of your commits. diff --git a/Documentation/config.txt b/Documentation/config.txt index 0bcb6790d6..e78293b6db 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -953,7 +953,8 @@ color.branch:: A boolean to enable/disable color in the output of linkgit:git-branch[1]. May be set to `always`, `false` (or `never`) or `auto` (or `true`), in which case colors are used - only when the output is to a terminal. Defaults to false. + only when the output is to a terminal. If unset, then the + value of `color.ui` is used (`auto` by default). color.branch.<slot>:: Use customized color for branch coloration. `<slot>` is one of @@ -968,7 +969,8 @@ color.diff:: linkgit:git-log[1], and linkgit:git-show[1] will use color for all patches. If it is set to `true` or `auto`, those commands will only use color when output is to the terminal. - Defaults to false. + If unset, then the value of `color.ui` is used (`auto` by + default). + This does not affect linkgit:git-format-patch[1] or the 'git-diff-{asterisk}' plumbing commands. Can be overridden on the @@ -991,7 +993,8 @@ color.decorate.<slot>:: color.grep:: When set to `always`, always highlight matches. When `false` (or `never`), never. When set to `true` or `auto`, use color only - when the output is written to the terminal. Defaults to `false`. + when the output is written to the terminal. If unset, then the + value of `color.ui` is used (`auto` by default). color.grep.<slot>:: Use customized color for grep colorization. `<slot>` specifies which @@ -1024,7 +1027,8 @@ color.interactive:: and displays (such as those used by "git-add --interactive" and "git-clean --interactive"). When false (or `never`), never. When set to `true` or `auto`, use colors only when the output is - to the terminal. Defaults to false. + to the terminal. If unset, then the value of `color.ui` is + used (`auto` by default). color.interactive.<slot>:: Use customized color for 'git add --interactive' and 'git clean @@ -1040,13 +1044,15 @@ color.showBranch:: A boolean to enable/disable color in the output of linkgit:git-show-branch[1]. May be set to `always`, `false` (or `never`) or `auto` (or `true`), in which case colors are used - only when the output is to a terminal. Defaults to false. + only when the output is to a terminal. If unset, then the + value of `color.ui` is used (`auto` by default). color.status:: A boolean to enable/disable color in the output of linkgit:git-status[1]. May be set to `always`, `false` (or `never`) or `auto` (or `true`), in which case colors are used - only when the output is to a terminal. Defaults to false. + only when the output is to a terminal. If unset, then the + value of `color.ui` is used (`auto` by default). color.status.<slot>:: Use customized color for status colorization. `<slot>` is @@ -1366,7 +1372,7 @@ fsck.skipList:: gc.aggressiveDepth:: The depth parameter used in the delta compression algorithm used by 'git gc --aggressive'. This defaults - to 250. + to 50. gc.aggressiveWindow:: The window size parameter used in the delta compression @@ -2517,6 +2523,12 @@ receive.unpackLimit:: especially on slow filesystems. If not set, the value of `transfer.unpackLimit` is used instead. +receive.maxInputSize:: + If the size of the incoming pack stream is larger than this + limit, then git-receive-pack will error out, instead of + accepting the pack file. If not set or set to 0, then the size + is unlimited. + receive.denyDeletes:: If set to true, git-receive-pack will deny a ref update that deletes the ref. Use this to prevent such a ref deletion via a push. @@ -2847,6 +2859,18 @@ submodule.fetchJobs:: in parallel. A value of 0 will give some reasonable default. If unset, it defaults to 1. +submodule.alternateLocation:: + Specifies how the submodules obtain alternates when submodules are + cloned. Possible values are `no`, `superproject`. + By default `no` is assumed, which doesn't add references. When the + value is set to `superproject` the submodule to be cloned computes + its alternates location relative to the superprojects alternate. + +submodule.alternateErrorStrategy + Specifies how to treat errors with the alternates for a submodule + as computed via `submodule.alternateLocation`. Possible values are + `ignore`, `info`, `die`. Default is `die`. + tag.forceSignAnnotated:: A boolean to specify whether annotated tags created should be GPG signed. If `--annotate` is specified on the command line, it takes diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index d5a5b17d50..b27a38f896 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -122,10 +122,11 @@ diff.suppressBlankEmpty:: diff.submodule:: Specify the format in which differences in submodules are - shown. The "log" format lists the commits in the range like - linkgit:git-submodule[1] `summary` does. The "short" format - format just shows the names of the commits at the beginning - and end of the range. Defaults to short. + shown. The "short" format just shows the names of the commits + at the beginning and end of the range. The "log" format lists + the commits in the range like linkgit:git-submodule[1] `summary` + does. The "diff" format shows an inline diff of the changed + contents of the submodule. Defaults to "short". diff.wordRegex:: A POSIX Extended Regular Expression used to determine what is a "word" @@ -170,10 +171,11 @@ diff.tool:: include::mergetools-diff.txt[] +diff.indentHeuristic:: diff.compactionHeuristic:: - Set this option to `true` to enable an experimental heuristic that - shifts the hunk boundary in an attempt to make the resulting - patch easier to read. + Set one of these options to `true` to enable one of two + experimental heuristics that shift diff hunk boundaries to + make patches easier to read. diff.algorithm:: Choose a diff algorithm. The variants are as follows: diff --git a/Documentation/diff-heuristic-options.txt b/Documentation/diff-heuristic-options.txt new file mode 100644 index 0000000000..36cb549df9 --- /dev/null +++ b/Documentation/diff-heuristic-options.txt @@ -0,0 +1,7 @@ +--indent-heuristic:: +--no-indent-heuristic:: +--compaction-heuristic:: +--no-compaction-heuristic:: + These are to help debugging and tuning experimental heuristics + (which are off by default) that shift diff hunk boundaries to + make patches easier to read. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 705a873942..2d77a19626 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -63,12 +63,7 @@ ifndef::git-format-patch[] Synonym for `-p --raw`. endif::git-format-patch[] ---compaction-heuristic:: ---no-compaction-heuristic:: - These are to help debugging and tuning an experimental - heuristic (which is off by default) that shifts the hunk - boundary in an attempt to make the resulting patch easier - to read. +include::diff-heuristic-options.txt[] --minimal:: Spend extra time to make sure the smallest possible @@ -210,13 +205,16 @@ any of those replacements occurred. of the `--diff-filter` option on what the status letters mean. --submodule[=<format>]:: - Specify how differences in submodules are shown. When `--submodule` - or `--submodule=log` is given, the 'log' format is used. This format lists - the commits in the range like linkgit:git-submodule[1] `summary` does. - Omitting the `--submodule` option or specifying `--submodule=short`, - uses the 'short' format. This format just shows the names of the commits - at the beginning and end of the range. Can be tweaked via the - `diff.submodule` configuration variable. + Specify how differences in submodules are shown. When specifying + `--submodule=short` the 'short' format is used. This format just + shows the names of the commits at the beginning and end of the range. + When `--submodule` or `--submodule=log` is specified, the 'log' + format is used. This format lists the commits in the range like + linkgit:git-submodule[1] `summary` does. When `--submodule=diff` + is specified, the 'diff' format is used. This format shows an + inline diff of the changes in the submodule contents between the + commit range. Defaults to `diff.submodule` or the 'short' format + if the config option is unset. --color[=<when>]:: Show colored diff. @@ -569,5 +567,8 @@ endif::git-format-patch[] --no-prefix:: Do not show any source or destination prefix. +--line-prefix=<prefix>:: + Prepend an additional prefix to every line of output. + For more detailed explanation on these common options, see also linkgit:gitdiffcore[7]. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 6a96a669c2..7ed63dce0b 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p] [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] - [--] [<pathspec>...] + [--chmod=(+|-)x] [--] [<pathspec>...] DESCRIPTION ----------- @@ -165,6 +165,11 @@ 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. +--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 + unchanged. + \--:: This option can be used to separate command-line options from the list of files, (useful when filenames might be mistaken diff --git a/Documentation/git-annotate.txt b/Documentation/git-annotate.txt index 05fd482b74..94be4b85e0 100644 --- a/Documentation/git-annotate.txt +++ b/Documentation/git-annotate.txt @@ -23,6 +23,7 @@ familiar command name for people coming from other SCM systems. OPTIONS ------- include::blame-options.txt[] +include::diff-heuristic-options.txt[] SEE ALSO -------- diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index ba5417567c..9dccb3319b 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -89,6 +89,8 @@ include::blame-options.txt[] abbreviated object name, use <n>+1 digits. Note that 1 column is used for a caret to mark the boundary commit. +include::diff-heuristic-options.txt[] + THE PORCELAIN FORMAT -------------------- diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 18d03d8e8b..204541c690 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -9,18 +9,22 @@ git-cat-file - Provide content or type and size information for repository objec SYNOPSIS -------- [verse] -'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv ) <object> -'git cat-file' (--batch | --batch-check) [--follow-symlinks] +'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) [--path=<path>] <object> +'git cat-file' (--batch | --batch-check) [ --textconv | --filters ] [--follow-symlinks] DESCRIPTION ----------- In its first form, the command provides the content or the type of an object in the repository. The type is required unless `-t` or `-p` is used to find the -object type, or `-s` is used to find the object size, or `--textconv` is used -(which implies type "blob"). +object type, or `-s` is used to find the object size, or `--textconv` or +`--filters` is used (which imply type "blob"). In the second form, a list of objects (separated by linefeeds) is provided on -stdin, and the SHA-1, type, and size of each object is printed on stdout. +stdin, and the SHA-1, type, and size of each object is printed on stdout. The +output format can be overridden using the optional `<format>` argument. If +either `--textconv` or `--filters` was specified, the input is expected to +list the object names followed by the path name, separated by a single white +space, so that the appropriate drivers can be determined. OPTIONS ------- @@ -54,19 +58,35 @@ OPTIONS --textconv:: Show the content as transformed by a textconv filter. In this case, - <object> has be of the form <tree-ish>:<path>, or :<path> in order - to apply the filter to the content recorded in the index at <path>. + <object> has to be of the form <tree-ish>:<path>, or :<path> in + order to apply the filter to the content recorded in the index at + <path>. + +--filters:: + Show the content as converted by the filters configured in + the current working tree for the given <path> (i.e. smudge filters, + end-of-line conversion, etc). In this case, <object> has to be of + the form <tree-ish>:<path>, or :<path>. + +--path=<path>:: + For use with --textconv or --filters, to allow specifying an object + name and a path separately, e.g. when it is difficult to figure out + the revision from which the blob came. --batch:: --batch=<format>:: Print object information and contents for each object provided - on stdin. May not be combined with any other options or arguments. - See the section `BATCH OUTPUT` below for details. + on stdin. May not be combined with any other options or arguments + except `--textconv` or `--filters`, in which case the input lines + also need to specify the path, separated by white space. See the + section `BATCH OUTPUT` below for details. --batch-check:: --batch-check=<format>:: Print object information for each object provided on stdin. May - not be combined with any other options or arguments. See the + not be combined with any other options or arguments except + `--textconv` or `--filters`, in which case the input lines also + need to specify the path, separated by white space. See the section `BATCH OUTPUT` below for details. --batch-all-objects:: diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index 91a3622ee4..8611a99120 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -118,8 +118,8 @@ $ git check-ref-format --branch @{-1} * Determine the reference name to use for a new branch: + ------------ -$ ref=$(git check-ref-format --normalize "refs/heads/$newbranch") || -die "we do not like '$newbranch' as a branch name." +$ ref=$(git check-ref-format --normalize "refs/heads/$newbranch")|| +{ echo "we do not like '$newbranch' as a branch name." >&2 ; exit 1 ; } ------------ GIT diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index ec41d3d698..e316c4bd51 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -90,13 +90,16 @@ If you want to break the dependency of a repository cloned with `-s` on its source repository, you can simply run `git repack -a` to copy all objects from the source repository into a pack in the cloned repository. ---reference <repository>:: +--reference[-if-able] <repository>:: If the reference repository is on the local machine, automatically setup `.git/objects/info/alternates` to obtain objects from the reference repository. Using an already existing repository as an alternate will require fewer objects to be copied from the repository being cloned, reducing network and local storage costs. + When using the `--reference-if-able`, a non existing + directory is skipped with a warning instead of aborting + the clone. + *NOTE*: see the NOTE for the `--shared` option, and also the `--dissociate` option. diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 9624c84a65..9b200b379b 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -19,7 +19,8 @@ SYNOPSIS [--start-number <n>] [--numbered-files] [--in-reply-to=Message-Id] [--suffix=.<sfx>] [--ignore-if-in-upstream] - [--subject-prefix=Subject-Prefix] [(--reroll-count|-v) <n>] + [--rfc] [--subject-prefix=Subject-Prefix] + [(--reroll-count|-v) <n>] [--to=<email>] [--cc=<email>] [--[no-]cover-letter] [--quiet] [--notes[=<ref>]] [<common diff options>] @@ -172,6 +173,11 @@ will want to ensure that threading is disabled for `git send-email`. allows for useful naming of a patch series, and can be combined with the `--numbered` option. +--rfc:: + Alias for `--subject-prefix="RFC PATCH"`. RFC means "Request For + Comments"; use this when sending an experimental patch for + discussion rather than application. + -v <n>:: --reroll-count=<n>:: Mark the series as the <n>-th iteration of the topic. The diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 7a4e055520..1b4b65d665 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -87,6 +87,8 @@ OPTIONS Specifying 0 will cause Git to auto-detect the number of CPU's and use maximum 3 threads. +--max-input-size=<size>:: + Die, if the pack is larger than <size>. Note ---- diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 000ee8dba2..0ccd5fbc78 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -33,6 +33,9 @@ post-update hooks found in the Documentation/howto directory. option, which tells it if updates to a ref should be denied if they are not fast-forwards. +A number of other receive.* config options are available to tweak +its behavior, see linkgit:git-config[1]. + OPTIONS ------- <directory>:: diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index e1e8f57cdd..725065ef2d 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -32,11 +32,14 @@ OPTIONS --branch:: Show the branch and tracking info even in short-format. ---porcelain:: +--porcelain[=<version>]:: Give the output in an easy-to-parse format for scripts. This is similar to the short output, but will remain stable across Git versions and regardless of user configuration. See below for details. ++ +The version parameter is used to specify the format version. +This is optional and defaults to the original version 'v1' format. --long:: Give the output in the long-format. This is the default. @@ -96,7 +99,7 @@ configuration variable documented in linkgit:git-config[1]. -z:: Terminate entries with NUL, instead of LF. This implies - the `--porcelain` output format if no other format is given. + the `--porcelain=v1` output format if no other format is given. --column[=<options>]:: --no-column:: @@ -180,12 +183,12 @@ in which case `XY` are `!!`. If -b is used the short-format status is preceded by a line -## branchname tracking info + ## branchname tracking info -Porcelain Format -~~~~~~~~~~~~~~~~ +Porcelain Format Version 1 +~~~~~~~~~~~~~~~~~~~~~~~~~~ -The porcelain format is similar to the short format, but is guaranteed +Version 1 porcelain format is similar to the short format, but is guaranteed not to change in a backwards-incompatible way between Git versions or based on user configuration. This makes it ideal for parsing by scripts. The description of the short format above also describes the porcelain @@ -207,6 +210,124 @@ field from the first filename). Third, filenames containing special characters are not specially formatted; no quoting or backslash-escaping is performed. +Porcelain Format Version 2 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Version 2 format adds more detailed information about the state of +the worktree and changed items. Version 2 also defines an extensible +set of easy to parse optional headers. + +Header lines start with "#" and are added in response to specific +command line arguments. Parsers should ignore headers they +don't recognize. + +### Branch Headers + +If `--branch` is given, a series of header lines are printed with +information about the current branch. + + Line Notes + ------------------------------------------------------------ + # branch.oid <commit> | (initial) Current commit. + # branch.head <branch> | (detached) Current branch. + # branch.upstream <upstream_branch> If upstream is set. + # branch.ab +<ahead> -<behind> If upstream is set and + the commit is present. + ------------------------------------------------------------ + +### Changed Tracked Entries + +Following the headers, a series of lines are printed for tracked +entries. One of three different line formats may be used to describe +an entry depending on the type of change. Tracked entries are printed +in an undefined order; parsers should allow for a mixture of the 3 +line types in any order. + +Ordinary changed entries have the following format: + + 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path> + +Renamed or copied entries have the following format: + + 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath> + + Field Meaning + -------------------------------------------------------- + <XY> A 2 character field containing the staged and + unstaged XY values described in the short format, + with unchanged indicated by a "." rather than + a space. + <sub> A 4 character field describing the submodule state. + "N..." when the entry is not a submodule. + "S<c><m><u>" when the entry is a submodule. + <c> is "C" if the commit changed; otherwise ".". + <m> is "M" if it has tracked changes; otherwise ".". + <u> is "U" if there are untracked changes; otherwise ".". + <mH> The octal file mode in HEAD. + <mI> The octal file mode in the index. + <mW> The octal file mode in the worktree. + <hH> The object name in HEAD. + <hI> The object name in the index. + <X><score> The rename or copy score (denoting the percentage + of similarity between the source and target of the + move or copy). For example "R100" or "C75". + <path> The pathname. In a renamed/copied entry, this + is the path in the index and in the working tree. + <sep> When the `-z` option is used, the 2 pathnames are separated + with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09) + byte separates them. + <origPath> The pathname in the commit at HEAD. This is only + present in a renamed/copied entry, and tells + where the renamed/copied contents came from. + -------------------------------------------------------- + +Unmerged entries have the following format; the first character is +a "u" to distinguish from ordinary changed entries. + + u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path> + + Field Meaning + -------------------------------------------------------- + <XY> A 2 character field describing the conflict type + as described in the short format. + <sub> A 4 character field describing the submodule state + as described above. + <m1> The octal file mode in stage 1. + <m2> The octal file mode in stage 2. + <m3> The octal file mode in stage 3. + <mW> The octal file mode in the worktree. + <h1> The object name in stage 1. + <h2> The object name in stage 2. + <h3> The object name in stage 3. + <path> The pathname. + -------------------------------------------------------- + +### Other Items + +Following the tracked entries (and if requested), a series of +lines will be printed for untracked and then ignored items +found in the worktree. + +Untracked items have the following format: + + ? <path> + +Ignored items have the following format: + + ! <path> + +### Pathname Format Notes and -z + +When the `-z` option is given, pathnames are printed as is and +without any quoting and lines are terminated with a NUL (ASCII 0x00) +byte. + +Otherwise, all pathnames will be "C-quoted" if they contain any tab, +linefeed, double quote, or backslash characters. In C-quoting, these +characters will be replaced with the corresponding C-style escape +sequences and the resulting pathname will be double quoted. + + CONFIGURATION ------------- diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index 3e887d1610..b3de50d710 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -44,6 +44,9 @@ OPTIONS --strict:: Don't write objects with broken content or links. +--max-input-size=<size>:: + Die, if the pack is larger than <size>. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index a68d860fa3..e382dd96df 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -70,7 +70,7 @@ linkgit:git-rev-list[1] for a complete list. --left-right:: - Mark which side of a symmetric diff a commit is reachable + Mark which side of a symmetric difference a commit is reachable from. Commits from the left side are prefixed with a `<` symbol and those from the right with a `>` symbol. diff --git a/Documentation/gitrevisions.txt b/Documentation/gitrevisions.txt index e903eb7860..27dec5b91d 100644 --- a/Documentation/gitrevisions.txt +++ b/Documentation/gitrevisions.txt @@ -15,9 +15,9 @@ DESCRIPTION Many Git commands take revision parameters as arguments. Depending on the command, they denote a specific commit or, for commands which -walk the revision graph (such as linkgit:git-log[1]), all commits which can -be reached from that commit. In the latter case one can also specify a -range of revisions explicitly. +walk the revision graph (such as linkgit:git-log[1]), all commits which are +reachable from that commit. For commands that walk the revision graph one can +also specify a range of revisions explicitly. In addition, some Git commands (such as linkgit:git-show[1]) also take revision parameters which denote other objects than commits, e.g. blobs diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index b95d67ec01..a942d57f73 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -172,7 +172,7 @@ endif::git-rev-list[] respecting the `auto` settings of the former if we are going to a terminal). `auto` alone (i.e. `%C(auto)`) will turn on auto coloring on the next placeholders until the color is switched again. -- '%m': left, right or boundary mark +- '%m': left (`<`), right (`>`) or boundary (`-`) mark - '%n': newline - '%%': a raw '%' - '%x00': print a byte from a hex code diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index a779c9dfec..7e462d3841 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -225,7 +225,7 @@ excluded from the output. --left-only:: --right-only:: - List only commits on the respective side of a symmetric range, + List only commits on the respective side of a symmetric difference, i.e. only those which would be marked `<` resp. `>` by `--left-right`. + @@ -796,7 +796,7 @@ ifdef::git-rev-list[] endif::git-rev-list[] --left-right:: - Mark which side of a symmetric diff a commit is reachable from. + Mark which side of a symmetric difference a commit is reachable from. Commits from the left side are prefixed with `<` and those from the right with `>`. If combined with `--boundary`, those commits are prefixed with `-`. diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index abae363983..4bed5b1ab7 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -237,48 +237,74 @@ SPECIFYING RANGES ----------------- History traversing commands such as `git log` operate on a set -of commits, not just a single commit. To these commands, -specifying a single revision with the notation described in the -previous section means the set of commits reachable from that -commit, following the commit ancestry chain. - -To exclude commits reachable from a commit, a prefix '{caret}' -notation is used. E.g. '{caret}r1 r2' means commits reachable -from 'r2' but exclude the ones reachable from 'r1'. - -This set operation appears so often that there is a shorthand -for it. When you have two commits 'r1' and 'r2' (named according -to the syntax explained in SPECIFYING REVISIONS above), you can ask -for commits that are reachable from r2 excluding those that are reachable -from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'. - -A similar notation 'r1\...r2' is called symmetric difference -of 'r1' and 'r2' and is defined as -'r1 r2 --not $(git merge-base --all r1 r2)'. -It is the set of commits that are reachable from either one of -'r1' or 'r2' but not from both. - -In these two shorthands, you can omit one end and let it default to HEAD. +of commits, not just a single commit. + +For these commands, +specifying a single revision, using the notation described in the +previous section, means the set of commits `reachable` from the given +commit. + +A commit's reachable set is the commit itself and the commits in +its ancestry chain. + + +Commit Exclusions +~~~~~~~~~~~~~~~~~ + +'{caret}<rev>' (caret) Notation:: + To exclude commits reachable from a commit, a prefix '{caret}' + notation is used. E.g. '{caret}r1 r2' means commits reachable + from 'r2' but exclude the ones reachable from 'r1' (i.e. 'r1' and + its ancestors). + +Dotted Range Notations +~~~~~~~~~~~~~~~~~~~~~~ + +The '..' (two-dot) Range Notation:: + The '{caret}r1 r2' set operation appears so often that there is a shorthand + for it. When you have two commits 'r1' and 'r2' (named according + to the syntax explained in SPECIFYING REVISIONS above), you can ask + for commits that are reachable from r2 excluding those that are reachable + from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'. + +The '...' (three dot) Symmetric Difference Notation:: + A similar notation 'r1\...r2' is called symmetric difference + of 'r1' and 'r2' and is defined as + 'r1 r2 --not $(git merge-base --all r1 r2)'. + It is the set of commits that are reachable from either one of + 'r1' (left side) or 'r2' (right side) but not from both. + +In these two shorthand notations, you can omit one end and let it default to HEAD. For example, 'origin..' is a shorthand for 'origin..HEAD' and asks "What did I do since I forked from the origin branch?" Similarly, '..origin' is a shorthand for 'HEAD..origin' and asks "What did the origin do since I forked from them?" Note that '..' would mean 'HEAD..HEAD' which is an empty range that is both reachable and unreachable from HEAD. -Two other shorthands for naming a set that is formed by a commit -and its parent commits exist. The 'r1{caret}@' notation means all -parents of 'r1'. 'r1{caret}!' includes commit 'r1' but excludes -all of its parents. +Other <rev>{caret} Parent Shorthand Notations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Two other shorthands exist, particularly useful for merge commits, +for naming a set that is formed by a commit and its parent commits. + +The 'r1{caret}@' notation means all parents of 'r1'. + +The 'r1{caret}!' notation includes commit 'r1' but excludes all of its parents. +By itself, this notation denotes the single commit 'r1'. + +While '<rev>{caret}<n>' was about specifying a single commit parent, these +two notations consider all its parents. For example you can say +'HEAD{caret}2{caret}@', however you cannot say 'HEAD{caret}@{caret}2'. -To summarize: +Revision Range Summary +---------------------- '<rev>':: - Include commits that are reachable from (i.e. ancestors of) - <rev>. + Include commits that are reachable from <rev> (i.e. <rev> and its + ancestors). '{caret}<rev>':: - Exclude commits that are reachable from (i.e. ancestors of) - <rev>. + Exclude commits that are reachable from <rev> (i.e. <rev> and its + ancestors). '<rev1>..<rev2>':: Include commits that are reachable from <rev2> but exclude @@ -300,16 +326,27 @@ To summarize: as giving commit '<rev>' and then all its parents prefixed with '{caret}' to exclude them (and their ancestors). -Here are a handful of examples: - - D G H D - D F G H I J D F - ^G D H D - ^D B E I J F B - B..C C - B...C G H D E B C - ^D B C E I J F B C - C I J F C - C^@ I J F - C^! C - F^! D G H D F +Here are a handful of examples using the Loeliger illustration above, +with each step in the notation's expansion and selection carefully +spelt out: + + Args Expanded arguments Selected commits + D G H D + D F G H I J D F + ^G D H D + ^D B E I J F B + ^D B C E I J F B C + C I J F C + B..C = ^B C C + B...C = B ^F C G H D E B C + C^@ = C^1 + = F I J F + B^@ = B^1 B^2 B^3 + = D E F D G H E F I J + C^! = C ^C^@ + = C ^C^1 + = C ^F C + B^! = B ^B^@ + = B ^B^1 ^B^2 ^B^3 + = B ^D ^E ^F B + F^! D = F ^I ^J D G H D F diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 6754ab076e..55e88b02d4 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.10.0 +DEF_VER=v2.10.0.GIT LF=' ' @@ -296,6 +296,11 @@ all:: # Define USE_NED_ALLOCATOR if you want to replace the platforms default # memory allocators with the nedmalloc allocator written by Niall Douglas. # +# Define OVERRIDE_STRDUP to override the libc version of strdup(3). +# This is necessary when using a custom allocator in order to avoid +# crashes due to allocation and free working on different 'heaps'. +# It's defined automatically if USE_NED_ALLOCATOR is set. +# # Define NO_REGEX if you have no or inferior regex support in your C library. # # Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the @@ -456,6 +461,7 @@ CURL_CONFIG = curl-config PTHREAD_LIBS = -lpthread PTHREAD_CFLAGS = GCOV = gcov +SPATCH = spatch export TCL_PATH TCLTK_PATH @@ -691,6 +697,7 @@ LIB_OBJS += abspath.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o +LIB_OBJS += apply.o LIB_OBJS += archive.o LIB_OBJS += archive-tar.o LIB_OBJS += archive-zip.o @@ -1456,8 +1463,14 @@ ifdef NATIVE_CRLF endif ifdef USE_NED_ALLOCATOR - COMPAT_CFLAGS += -Icompat/nedmalloc - COMPAT_OBJS += compat/nedmalloc/nedmalloc.o + COMPAT_CFLAGS += -Icompat/nedmalloc + COMPAT_OBJS += compat/nedmalloc/nedmalloc.o + OVERRIDE_STRDUP = YesPlease +endif + +ifdef OVERRIDE_STRDUP + COMPAT_CFLAGS += -DOVERRIDE_STRDUP + COMPAT_OBJS += compat/strdup.o endif ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT @@ -2029,7 +2042,7 @@ endif ifdef USE_NED_ALLOCATOR compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ - -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR + -DNDEBUG -DREPLACE_SYSTEM_ALLOCATOR compat/nedmalloc/nedmalloc.sp: SPARSE_FLAGS += -Wno-non-pointer-null endif @@ -2296,6 +2309,18 @@ check: common-cmds.h exit 1; \ fi +C_SOURCES = $(patsubst %.o,%.c,$(C_OBJ)) +%.cocci.patch: %.cocci $(C_SOURCES) + @echo ' ' SPATCH $<; \ + for f in $(C_SOURCES); do \ + $(SPATCH) --sp-file $< $$f; \ + done >$@ 2>$@.log; \ + if test -s $@; \ + then \ + echo ' ' SPATCH result: $@; \ + fi +coccicheck: $(patsubst %.cocci,%.cocci.patch,$(wildcard contrib/coccinelle/*.cocci)) + ### Installation rules ifneq ($(filter /%,$(firstword $(template_dir))),) @@ -2487,6 +2512,7 @@ clean: profile-clean coverage-clean $(RM) -r $(GIT_TARNAME) .doc-tmp-dir $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz $(RM) $(htmldocs).tar.gz $(manpages).tar.gz + $(RM) contrib/coccinelle/*.cocci.patch* $(MAKE) -C Documentation/ clean ifndef NO_PERL $(MAKE) -C gitweb clean @@ -1 +1 @@ -Documentation/RelNotes/2.10.0.txt
\ No newline at end of file +Documentation/RelNotes/2.11.0.txt
\ No newline at end of file diff --git a/apply.c b/apply.c new file mode 100644 index 0000000000..e32702153c --- /dev/null +++ b/apply.c @@ -0,0 +1,4988 @@ +/* + * apply.c + * + * Copyright (C) Linus Torvalds, 2005 + * + * This applies patches on top of some (arbitrary) version of the SCM. + * + */ + +#include "cache.h" +#include "blob.h" +#include "delta.h" +#include "diff.h" +#include "dir.h" +#include "xdiff-interface.h" +#include "ll-merge.h" +#include "lockfile.h" +#include "parse-options.h" +#include "quote.h" +#include "rerere.h" +#include "apply.h" + +static void git_apply_config(void) +{ + git_config_get_string_const("apply.whitespace", &apply_default_whitespace); + git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace); + git_config(git_default_config, NULL); +} + +static int parse_whitespace_option(struct apply_state *state, const char *option) +{ + if (!option) { + state->ws_error_action = warn_on_ws_error; + return 0; + } + if (!strcmp(option, "warn")) { + state->ws_error_action = warn_on_ws_error; + return 0; + } + if (!strcmp(option, "nowarn")) { + state->ws_error_action = nowarn_ws_error; + return 0; + } + if (!strcmp(option, "error")) { + state->ws_error_action = die_on_ws_error; + return 0; + } + if (!strcmp(option, "error-all")) { + state->ws_error_action = die_on_ws_error; + state->squelch_whitespace_errors = 0; + return 0; + } + if (!strcmp(option, "strip") || !strcmp(option, "fix")) { + state->ws_error_action = correct_ws_error; + return 0; + } + return error(_("unrecognized whitespace option '%s'"), option); +} + +static int parse_ignorewhitespace_option(struct apply_state *state, + const char *option) +{ + if (!option || !strcmp(option, "no") || + !strcmp(option, "false") || !strcmp(option, "never") || + !strcmp(option, "none")) { + state->ws_ignore_action = ignore_ws_none; + return 0; + } + if (!strcmp(option, "change")) { + state->ws_ignore_action = ignore_ws_change; + return 0; + } + return error(_("unrecognized whitespace ignore option '%s'"), option); +} + +int init_apply_state(struct apply_state *state, + const char *prefix, + struct lock_file *lock_file) +{ + memset(state, 0, sizeof(*state)); + state->prefix = prefix; + state->prefix_length = state->prefix ? strlen(state->prefix) : 0; + state->lock_file = lock_file; + state->newfd = -1; + state->apply = 1; + state->line_termination = '\n'; + state->p_value = 1; + state->p_context = UINT_MAX; + state->squelch_whitespace_errors = 5; + state->ws_error_action = warn_on_ws_error; + state->ws_ignore_action = ignore_ws_none; + state->linenr = 1; + string_list_init(&state->fn_table, 0); + string_list_init(&state->limit_by_name, 0); + string_list_init(&state->symlink_changes, 0); + strbuf_init(&state->root, 0); + + git_apply_config(); + if (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace)) + return -1; + if (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace)) + return -1; + return 0; +} + +void clear_apply_state(struct apply_state *state) +{ + string_list_clear(&state->limit_by_name, 0); + string_list_clear(&state->symlink_changes, 0); + strbuf_release(&state->root); + + /* &state->fn_table is cleared at the end of apply_patch() */ +} + +static void mute_routine(const char *msg, va_list params) +{ + /* do nothing */ +} + +int check_apply_state(struct apply_state *state, int force_apply) +{ + int is_not_gitdir = !startup_info->have_repository; + + if (state->apply_with_reject && state->threeway) + return error("--reject and --3way cannot be used together."); + if (state->cached && state->threeway) + return error("--cached and --3way cannot be used together."); + if (state->threeway) { + if (is_not_gitdir) + return error(_("--3way outside a repository")); + state->check_index = 1; + } + if (state->apply_with_reject) { + state->apply = 1; + if (state->apply_verbosity == verbosity_normal) + state->apply_verbosity = verbosity_verbose; + } + if (!force_apply && (state->diffstat || state->numstat || state->summary || state->check || state->fake_ancestor)) + state->apply = 0; + if (state->check_index && is_not_gitdir) + return error(_("--index outside a repository")); + if (state->cached) { + if (is_not_gitdir) + return error(_("--cached outside a repository")); + state->check_index = 1; + } + if (state->check_index) + state->unsafe_paths = 0; + if (!state->lock_file) + return error("BUG: state->lock_file should not be NULL"); + + if (state->apply_verbosity <= verbosity_silent) { + state->saved_error_routine = get_error_routine(); + state->saved_warn_routine = get_warn_routine(); + set_error_routine(mute_routine); + set_warn_routine(mute_routine); + } + + return 0; +} + +static void set_default_whitespace_mode(struct apply_state *state) +{ + if (!state->whitespace_option && !apply_default_whitespace) + state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error); +} + +/* + * This represents one "hunk" from a patch, starting with + * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The + * patch text is pointed at by patch, and its byte length + * is stored in size. leading and trailing are the number + * of context lines. + */ +struct fragment { + unsigned long leading, trailing; + unsigned long oldpos, oldlines; + unsigned long newpos, newlines; + /* + * 'patch' is usually borrowed from buf in apply_patch(), + * but some codepaths store an allocated buffer. + */ + const char *patch; + unsigned free_patch:1, + rejected:1; + int size; + int linenr; + struct fragment *next; +}; + +/* + * When dealing with a binary patch, we reuse "leading" field + * to store the type of the binary hunk, either deflated "delta" + * or deflated "literal". + */ +#define binary_patch_method leading +#define BINARY_DELTA_DEFLATED 1 +#define BINARY_LITERAL_DEFLATED 2 + +/* + * This represents a "patch" to a file, both metainfo changes + * such as creation/deletion, filemode and content changes represented + * as a series of fragments. + */ +struct patch { + char *new_name, *old_name, *def_name; + unsigned int old_mode, new_mode; + int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ + int rejected; + unsigned ws_rule; + int lines_added, lines_deleted; + int score; + unsigned int is_toplevel_relative:1; + unsigned int inaccurate_eof:1; + unsigned int is_binary:1; + unsigned int is_copy:1; + unsigned int is_rename:1; + unsigned int recount:1; + unsigned int conflicted_threeway:1; + unsigned int direct_to_threeway:1; + struct fragment *fragments; + char *result; + size_t resultsize; + char old_sha1_prefix[41]; + char new_sha1_prefix[41]; + struct patch *next; + + /* three-way fallback result */ + struct object_id threeway_stage[3]; +}; + +static void free_fragment_list(struct fragment *list) +{ + while (list) { + struct fragment *next = list->next; + if (list->free_patch) + free((char *)list->patch); + free(list); + list = next; + } +} + +static void free_patch(struct patch *patch) +{ + free_fragment_list(patch->fragments); + free(patch->def_name); + free(patch->old_name); + free(patch->new_name); + free(patch->result); + free(patch); +} + +static void free_patch_list(struct patch *list) +{ + while (list) { + struct patch *next = list->next; + free_patch(list); + list = next; + } +} + +/* + * A line in a file, len-bytes long (includes the terminating LF, + * except for an incomplete line at the end if the file ends with + * one), and its contents hashes to 'hash'. + */ +struct line { + size_t len; + unsigned hash : 24; + unsigned flag : 8; +#define LINE_COMMON 1 +#define LINE_PATCHED 2 +}; + +/* + * This represents a "file", which is an array of "lines". + */ +struct image { + char *buf; + size_t len; + size_t nr; + size_t alloc; + struct line *line_allocated; + struct line *line; +}; + +static uint32_t hash_line(const char *cp, size_t len) +{ + size_t i; + uint32_t h; + for (i = 0, h = 0; i < len; i++) { + if (!isspace(cp[i])) { + h = h * 3 + (cp[i] & 0xff); + } + } + return h; +} + +/* + * Compare lines s1 of length n1 and s2 of length n2, ignoring + * whitespace difference. Returns 1 if they match, 0 otherwise + */ +static int fuzzy_matchlines(const char *s1, size_t n1, + const char *s2, size_t n2) +{ + const char *last1 = s1 + n1 - 1; + const char *last2 = s2 + n2 - 1; + int result = 0; + + /* ignore line endings */ + while ((*last1 == '\r') || (*last1 == '\n')) + last1--; + while ((*last2 == '\r') || (*last2 == '\n')) + last2--; + + /* skip leading whitespaces, if both begin with whitespace */ + if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) { + while (isspace(*s1) && (s1 <= last1)) + s1++; + while (isspace(*s2) && (s2 <= last2)) + s2++; + } + /* early return if both lines are empty */ + if ((s1 > last1) && (s2 > last2)) + return 1; + while (!result) { + result = *s1++ - *s2++; + /* + * Skip whitespace inside. We check for whitespace on + * both buffers because we don't want "a b" to match + * "ab" + */ + if (isspace(*s1) && isspace(*s2)) { + while (isspace(*s1) && s1 <= last1) + s1++; + while (isspace(*s2) && s2 <= last2) + s2++; + } + /* + * If we reached the end on one side only, + * lines don't match + */ + if ( + ((s2 > last2) && (s1 <= last1)) || + ((s1 > last1) && (s2 <= last2))) + return 0; + if ((s1 > last1) && (s2 > last2)) + break; + } + + return !result; +} + +static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag) +{ + ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc); + img->line_allocated[img->nr].len = len; + img->line_allocated[img->nr].hash = hash_line(bol, len); + img->line_allocated[img->nr].flag = flag; + img->nr++; +} + +/* + * "buf" has the file contents to be patched (read from various sources). + * attach it to "image" and add line-based index to it. + * "image" now owns the "buf". + */ +static void prepare_image(struct image *image, char *buf, size_t len, + int prepare_linetable) +{ + const char *cp, *ep; + + memset(image, 0, sizeof(*image)); + image->buf = buf; + image->len = len; + + if (!prepare_linetable) + return; + + ep = image->buf + image->len; + cp = image->buf; + while (cp < ep) { + const char *next; + for (next = cp; next < ep && *next != '\n'; next++) + ; + if (next < ep) + next++; + add_line_info(image, cp, next - cp, 0); + cp = next; + } + image->line = image->line_allocated; +} + +static void clear_image(struct image *image) +{ + free(image->buf); + free(image->line_allocated); + memset(image, 0, sizeof(*image)); +} + +/* fmt must contain _one_ %s and no other substitution */ +static void say_patch_name(FILE *output, const char *fmt, struct patch *patch) +{ + struct strbuf sb = STRBUF_INIT; + + if (patch->old_name && patch->new_name && + strcmp(patch->old_name, patch->new_name)) { + quote_c_style(patch->old_name, &sb, NULL, 0); + strbuf_addstr(&sb, " => "); + quote_c_style(patch->new_name, &sb, NULL, 0); + } else { + const char *n = patch->new_name; + if (!n) + n = patch->old_name; + quote_c_style(n, &sb, NULL, 0); + } + fprintf(output, fmt, sb.buf); + fputc('\n', output); + strbuf_release(&sb); +} + +#define SLOP (16) + +static int read_patch_file(struct strbuf *sb, int fd) +{ + if (strbuf_read(sb, fd, 0) < 0) + return error_errno("git apply: failed to read"); + + /* + * Make sure that we have some slop in the buffer + * so that we can do speculative "memcmp" etc, and + * see to it that it is NUL-filled. + */ + strbuf_grow(sb, SLOP); + memset(sb->buf + sb->len, 0, SLOP); + return 0; +} + +static unsigned long linelen(const char *buffer, unsigned long size) +{ + unsigned long len = 0; + while (size--) { + len++; + if (*buffer++ == '\n') + break; + } + return len; +} + +static int is_dev_null(const char *str) +{ + return skip_prefix(str, "/dev/null", &str) && isspace(*str); +} + +#define TERM_SPACE 1 +#define TERM_TAB 2 + +static int name_terminate(int c, int terminate) +{ + if (c == ' ' && !(terminate & TERM_SPACE)) + return 0; + if (c == '\t' && !(terminate & TERM_TAB)) + return 0; + + return 1; +} + +/* remove double slashes to make --index work with such filenames */ +static char *squash_slash(char *name) +{ + int i = 0, j = 0; + + if (!name) + return NULL; + + while (name[i]) { + if ((name[j++] = name[i++]) == '/') + while (name[i] == '/') + i++; + } + name[j] = '\0'; + return name; +} + +static char *find_name_gnu(struct apply_state *state, + const char *line, + const char *def, + int p_value) +{ + struct strbuf name = STRBUF_INIT; + char *cp; + + /* + * Proposed "new-style" GNU patch/diff format; see + * http://marc.info/?l=git&m=112927316408690&w=2 + */ + if (unquote_c_style(&name, line, NULL)) { + strbuf_release(&name); + return NULL; + } + + for (cp = name.buf; p_value; p_value--) { + cp = strchr(cp, '/'); + if (!cp) { + strbuf_release(&name); + return NULL; + } + cp++; + } + + strbuf_remove(&name, 0, cp - name.buf); + if (state->root.len) + strbuf_insert(&name, 0, state->root.buf, state->root.len); + return squash_slash(strbuf_detach(&name, NULL)); +} + +static size_t sane_tz_len(const char *line, size_t len) +{ + const char *tz, *p; + + if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ') + return 0; + tz = line + len - strlen(" +0500"); + + if (tz[1] != '+' && tz[1] != '-') + return 0; + + for (p = tz + 2; p != line + len; p++) + if (!isdigit(*p)) + return 0; + + return line + len - tz; +} + +static size_t tz_with_colon_len(const char *line, size_t len) +{ + const char *tz, *p; + + if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':') + return 0; + tz = line + len - strlen(" +08:00"); + + if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-')) + return 0; + p = tz + 2; + if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++)) + return 0; + + return line + len - tz; +} + +static size_t date_len(const char *line, size_t len) +{ + const char *date, *p; + + if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-') + return 0; + p = date = line + len - strlen("72-02-05"); + + if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' || + !isdigit(*p++) || !isdigit(*p++) || *p++ != '-' || + !isdigit(*p++) || !isdigit(*p++)) /* Not a date. */ + return 0; + + if (date - line >= strlen("19") && + isdigit(date[-1]) && isdigit(date[-2])) /* 4-digit year */ + date -= strlen("19"); + + return line + len - date; +} + +static size_t short_time_len(const char *line, size_t len) +{ + const char *time, *p; + + if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':') + return 0; + p = time = line + len - strlen(" 07:01:32"); + + /* Permit 1-digit hours? */ + if (*p++ != ' ' || + !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || + !isdigit(*p++) || !isdigit(*p++)) /* Not a time. */ + return 0; + + return line + len - time; +} + +static size_t fractional_time_len(const char *line, size_t len) +{ + const char *p; + size_t n; + + /* Expected format: 19:41:17.620000023 */ + if (!len || !isdigit(line[len - 1])) + return 0; + p = line + len - 1; + + /* Fractional seconds. */ + while (p > line && isdigit(*p)) + p--; + if (*p != '.') + return 0; + + /* Hours, minutes, and whole seconds. */ + n = short_time_len(line, p - line); + if (!n) + return 0; + + return line + len - p + n; +} + +static size_t trailing_spaces_len(const char *line, size_t len) +{ + const char *p; + + /* Expected format: ' ' x (1 or more) */ + if (!len || line[len - 1] != ' ') + return 0; + + p = line + len; + while (p != line) { + p--; + if (*p != ' ') + return line + len - (p + 1); + } + + /* All spaces! */ + return len; +} + +static size_t diff_timestamp_len(const char *line, size_t len) +{ + const char *end = line + len; + size_t n; + + /* + * Posix: 2010-07-05 19:41:17 + * GNU: 2010-07-05 19:41:17.620000023 -0500 + */ + + if (!isdigit(end[-1])) + return 0; + + n = sane_tz_len(line, end - line); + if (!n) + n = tz_with_colon_len(line, end - line); + end -= n; + + n = short_time_len(line, end - line); + if (!n) + n = fractional_time_len(line, end - line); + end -= n; + + n = date_len(line, end - line); + if (!n) /* No date. Too bad. */ + return 0; + end -= n; + + if (end == line) /* No space before date. */ + return 0; + if (end[-1] == '\t') { /* Success! */ + end--; + return line + len - end; + } + if (end[-1] != ' ') /* No space before date. */ + return 0; + + /* Whitespace damage. */ + end -= trailing_spaces_len(line, end - line); + return line + len - end; +} + +static char *find_name_common(struct apply_state *state, + const char *line, + const char *def, + int p_value, + const char *end, + int terminate) +{ + int len; + const char *start = NULL; + + if (p_value == 0) + start = line; + while (line != end) { + char c = *line; + + if (!end && isspace(c)) { + if (c == '\n') + break; + if (name_terminate(c, terminate)) + break; + } + line++; + if (c == '/' && !--p_value) + start = line; + } + if (!start) + return squash_slash(xstrdup_or_null(def)); + len = line - start; + if (!len) + return squash_slash(xstrdup_or_null(def)); + + /* + * Generally we prefer the shorter name, especially + * if the other one is just a variation of that with + * something else tacked on to the end (ie "file.orig" + * or "file~"). + */ + if (def) { + int deflen = strlen(def); + if (deflen < len && !strncmp(start, def, deflen)) + return squash_slash(xstrdup(def)); + } + + if (state->root.len) { + char *ret = xstrfmt("%s%.*s", state->root.buf, len, start); + return squash_slash(ret); + } + + return squash_slash(xmemdupz(start, len)); +} + +static char *find_name(struct apply_state *state, + const char *line, + char *def, + int p_value, + int terminate) +{ + if (*line == '"') { + char *name = find_name_gnu(state, line, def, p_value); + if (name) + return name; + } + + return find_name_common(state, line, def, p_value, NULL, terminate); +} + +static char *find_name_traditional(struct apply_state *state, + const char *line, + char *def, + int p_value) +{ + size_t len; + size_t date_len; + + if (*line == '"') { + char *name = find_name_gnu(state, line, def, p_value); + if (name) + return name; + } + + len = strchrnul(line, '\n') - line; + date_len = diff_timestamp_len(line, len); + if (!date_len) + return find_name_common(state, line, def, p_value, NULL, TERM_TAB); + len -= date_len; + + 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. + */ +static int guess_p_value(struct apply_state *state, const char *nameline) +{ + char *name, *cp; + int val = -1; + + if (is_dev_null(nameline)) + return -1; + name = find_name_traditional(state, nameline, NULL, 0); + if (!name) + return -1; + cp = strchr(name, '/'); + if (!cp) + val = 0; + else if (state->prefix) { + /* + * Does it begin with "a/$our-prefix" and such? Then this is + * very likely to apply to our directory. + */ + if (!strncmp(name, state->prefix, state->prefix_length)) + val = count_slashes(state->prefix); + else { + cp++; + if (!strncmp(cp, state->prefix, state->prefix_length)) + val = count_slashes(state->prefix) + 1; + } + } + free(name); + return val; +} + +/* + * Does the ---/+++ line have the POSIX timestamp after the last HT? + * GNU diff puts epoch there to signal a creation/deletion event. Is + * this such a timestamp? + */ +static int has_epoch_timestamp(const char *nameline) +{ + /* + * We are only interested in epoch timestamp; any non-zero + * fraction cannot be one, hence "(\.0+)?" in the regexp below. + * For the same reason, the date must be either 1969-12-31 or + * 1970-01-01, and the seconds part must be "00". + */ + const char stamp_regexp[] = + "^(1969-12-31|1970-01-01)" + " " + "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" + " " + "([-+][0-2][0-9]:?[0-5][0-9])\n"; + const char *timestamp = NULL, *cp, *colon; + static regex_t *stamp; + regmatch_t m[10]; + int zoneoffset; + int hourminute; + int status; + + for (cp = nameline; *cp != '\n'; cp++) { + if (*cp == '\t') + timestamp = cp + 1; + } + if (!timestamp) + return 0; + if (!stamp) { + stamp = xmalloc(sizeof(*stamp)); + if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) { + warning(_("Cannot prepare timestamp regexp %s"), + stamp_regexp); + return 0; + } + } + + status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0); + if (status) { + if (status != REG_NOMATCH) + warning(_("regexec returned %d for input: %s"), + status, timestamp); + return 0; + } + + zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10); + if (*colon == ':') + zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10); + else + zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); + if (timestamp[m[3].rm_so] == '-') + zoneoffset = -zoneoffset; + + /* + * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31 + * (west of GMT) or 1970-01-01 (east of GMT) + */ + if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) || + (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10))) + return 0; + + hourminute = (strtol(timestamp + 11, NULL, 10) * 60 + + strtol(timestamp + 14, NULL, 10) - + zoneoffset); + + return ((zoneoffset < 0 && hourminute == 1440) || + (0 <= zoneoffset && !hourminute)); +} + +/* + * Get the name etc info from the ---/+++ lines of a traditional patch header + * + * FIXME! The end-of-filename heuristics are kind of screwy. For existing + * files, we can happily check the index for a match, but for creating a + * new file we should try to match whatever "patch" does. I have no idea. + */ +static int parse_traditional_patch(struct apply_state *state, + const char *first, + const char *second, + struct patch *patch) +{ + char *name; + + first += 4; /* skip "--- " */ + second += 4; /* skip "+++ " */ + if (!state->p_value_known) { + int p, q; + p = guess_p_value(state, first); + q = guess_p_value(state, second); + if (p < 0) p = q; + if (0 <= p && p == q) { + state->p_value = p; + state->p_value_known = 1; + } + } + if (is_dev_null(first)) { + patch->is_new = 1; + patch->is_delete = 0; + name = find_name_traditional(state, second, NULL, state->p_value); + patch->new_name = name; + } else if (is_dev_null(second)) { + patch->is_new = 0; + patch->is_delete = 1; + name = find_name_traditional(state, first, NULL, state->p_value); + patch->old_name = name; + } else { + char *first_name; + first_name = find_name_traditional(state, first, NULL, state->p_value); + name = find_name_traditional(state, second, first_name, state->p_value); + free(first_name); + if (has_epoch_timestamp(first)) { + patch->is_new = 1; + patch->is_delete = 0; + patch->new_name = name; + } else if (has_epoch_timestamp(second)) { + patch->is_new = 0; + patch->is_delete = 1; + patch->old_name = name; + } else { + patch->old_name = name; + patch->new_name = xstrdup_or_null(name); + } + } + if (!name) + return error(_("unable to find filename in patch at line %d"), state->linenr); + + return 0; +} + +static int gitdiff_hdrend(struct apply_state *state, + const char *line, + struct patch *patch) +{ + return 1; +} + +/* + * We're anal about diff header consistency, to make + * sure that we don't end up having strange ambiguous + * patches floating around. + * + * As a result, gitdiff_{old|new}name() will check + * their names against any previous information, just + * to make sure.. + */ +#define DIFF_OLD_NAME 0 +#define DIFF_NEW_NAME 1 + +static int gitdiff_verify_name(struct apply_state *state, + const char *line, + int isnull, + char **name, + int side) +{ + if (!*name && !isnull) { + *name = find_name(state, line, NULL, state->p_value, TERM_TAB); + return 0; + } + + if (*name) { + int len = strlen(*name); + char *another; + if (isnull) + return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), + *name, state->linenr); + another = find_name(state, line, NULL, state->p_value, TERM_TAB); + if (!another || memcmp(another, *name, len + 1)) { + free(another); + return error((side == DIFF_NEW_NAME) ? + _("git apply: bad git-diff - inconsistent new filename on line %d") : + _("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr); + } + free(another); + } else { + /* expect "/dev/null" */ + if (memcmp("/dev/null", line, 9) || line[9] != '\n') + return error(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr); + } + + return 0; +} + +static int gitdiff_oldname(struct apply_state *state, + const char *line, + struct patch *patch) +{ + return gitdiff_verify_name(state, line, + patch->is_new, &patch->old_name, + DIFF_OLD_NAME); +} + +static int gitdiff_newname(struct apply_state *state, + const char *line, + struct patch *patch) +{ + return gitdiff_verify_name(state, line, + patch->is_delete, &patch->new_name, + DIFF_NEW_NAME); +} + +static int gitdiff_oldmode(struct apply_state *state, + const char *line, + struct patch *patch) +{ + patch->old_mode = strtoul(line, NULL, 8); + return 0; +} + +static int gitdiff_newmode(struct apply_state *state, + const char *line, + struct patch *patch) +{ + patch->new_mode = strtoul(line, NULL, 8); + return 0; +} + +static int gitdiff_delete(struct apply_state *state, + const char *line, + struct patch *patch) +{ + patch->is_delete = 1; + free(patch->old_name); + patch->old_name = xstrdup_or_null(patch->def_name); + return gitdiff_oldmode(state, line, patch); +} + +static int gitdiff_newfile(struct apply_state *state, + const char *line, + struct patch *patch) +{ + patch->is_new = 1; + free(patch->new_name); + patch->new_name = xstrdup_or_null(patch->def_name); + return gitdiff_newmode(state, line, patch); +} + +static int gitdiff_copysrc(struct apply_state *state, + const char *line, + struct patch *patch) +{ + patch->is_copy = 1; + free(patch->old_name); + patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); + return 0; +} + +static int gitdiff_copydst(struct apply_state *state, + const char *line, + struct patch *patch) +{ + patch->is_copy = 1; + free(patch->new_name); + patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); + return 0; +} + +static int gitdiff_renamesrc(struct apply_state *state, + const char *line, + struct patch *patch) +{ + patch->is_rename = 1; + free(patch->old_name); + patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); + return 0; +} + +static int gitdiff_renamedst(struct apply_state *state, + const char *line, + struct patch *patch) +{ + patch->is_rename = 1; + free(patch->new_name); + patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); + return 0; +} + +static int gitdiff_similarity(struct apply_state *state, + const char *line, + struct patch *patch) +{ + unsigned long val = strtoul(line, NULL, 10); + if (val <= 100) + patch->score = val; + return 0; +} + +static int gitdiff_dissimilarity(struct apply_state *state, + const char *line, + struct patch *patch) +{ + unsigned long val = strtoul(line, NULL, 10); + if (val <= 100) + patch->score = val; + return 0; +} + +static int gitdiff_index(struct apply_state *state, + const char *line, + struct patch *patch) +{ + /* + * index line is N hexadecimal, "..", N hexadecimal, + * and optional space with octal mode. + */ + const char *ptr, *eol; + int len; + + ptr = strchr(line, '.'); + if (!ptr || ptr[1] != '.' || 40 < ptr - line) + return 0; + len = ptr - line; + memcpy(patch->old_sha1_prefix, line, len); + patch->old_sha1_prefix[len] = 0; + + line = ptr + 2; + ptr = strchr(line, ' '); + eol = strchrnul(line, '\n'); + + if (!ptr || eol < ptr) + ptr = eol; + len = ptr - line; + + if (40 < len) + return 0; + memcpy(patch->new_sha1_prefix, line, len); + patch->new_sha1_prefix[len] = 0; + if (*ptr == ' ') + patch->old_mode = strtoul(ptr+1, NULL, 8); + return 0; +} + +/* + * This is normal for a diff that doesn't change anything: we'll fall through + * into the next diff. Tell the parser to break out. + */ +static int gitdiff_unrecognized(struct apply_state *state, + const char *line, + struct patch *patch) +{ + return 1; +} + +/* + * Skip p_value leading components from "line"; as we do not accept + * absolute paths, return NULL in that case. + */ +static const char *skip_tree_prefix(struct apply_state *state, + const char *line, + int llen) +{ + int nslash; + int i; + + if (!state->p_value) + return (llen && line[0] == '/') ? NULL : line; + + nslash = state->p_value; + for (i = 0; i < llen; i++) { + int ch = line[i]; + if (ch == '/' && --nslash <= 0) + return (i == 0) ? NULL : &line[i + 1]; + } + return NULL; +} + +/* + * This is to extract the same name that appears on "diff --git" + * line. We do not find and return anything if it is a rename + * patch, and it is OK because we will find the name elsewhere. + * We need to reliably find name only when it is mode-change only, + * creation or deletion of an empty file. In any of these cases, + * both sides are the same name under a/ and b/ respectively. + */ +static char *git_header_name(struct apply_state *state, + const char *line, + int llen) +{ + const char *name; + const char *second = NULL; + size_t len, line_len; + + line += strlen("diff --git "); + llen -= strlen("diff --git "); + + if (*line == '"') { + const char *cp; + struct strbuf first = STRBUF_INIT; + struct strbuf sp = STRBUF_INIT; + + if (unquote_c_style(&first, line, &second)) + goto free_and_fail1; + + /* strip the a/b prefix including trailing slash */ + cp = skip_tree_prefix(state, first.buf, first.len); + if (!cp) + goto free_and_fail1; + strbuf_remove(&first, 0, cp - first.buf); + + /* + * second points at one past closing dq of name. + * find the second name. + */ + while ((second < line + llen) && isspace(*second)) + second++; + + if (line + llen <= second) + goto free_and_fail1; + if (*second == '"') { + if (unquote_c_style(&sp, second, NULL)) + goto free_and_fail1; + cp = skip_tree_prefix(state, sp.buf, sp.len); + if (!cp) + goto free_and_fail1; + /* They must match, otherwise ignore */ + if (strcmp(cp, first.buf)) + goto free_and_fail1; + strbuf_release(&sp); + return strbuf_detach(&first, NULL); + } + + /* unquoted second */ + cp = skip_tree_prefix(state, second, line + llen - second); + if (!cp) + goto free_and_fail1; + if (line + llen - cp != first.len || + memcmp(first.buf, cp, first.len)) + goto free_and_fail1; + return strbuf_detach(&first, NULL); + + free_and_fail1: + strbuf_release(&first); + strbuf_release(&sp); + return NULL; + } + + /* unquoted first name */ + name = skip_tree_prefix(state, line, llen); + if (!name) + return NULL; + + /* + * since the first name is unquoted, a dq if exists must be + * the beginning of the second name. + */ + for (second = name; second < line + llen; second++) { + if (*second == '"') { + struct strbuf sp = STRBUF_INIT; + const char *np; + + if (unquote_c_style(&sp, second, NULL)) + goto free_and_fail2; + + np = skip_tree_prefix(state, sp.buf, sp.len); + if (!np) + goto free_and_fail2; + + len = sp.buf + sp.len - np; + if (len < second - name && + !strncmp(np, name, len) && + isspace(name[len])) { + /* Good */ + strbuf_remove(&sp, 0, np - sp.buf); + return strbuf_detach(&sp, NULL); + } + + free_and_fail2: + strbuf_release(&sp); + return NULL; + } + } + + /* + * Accept a name only if it shows up twice, exactly the same + * form. + */ + second = strchr(name, '\n'); + if (!second) + return NULL; + line_len = second - name; + for (len = 0 ; ; len++) { + switch (name[len]) { + default: + continue; + case '\n': + return NULL; + case '\t': case ' ': + /* + * Is this the separator between the preimage + * and the postimage pathname? Again, we are + * only interested in the case where there is + * no rename, as this is only to set def_name + * and a rename patch has the names elsewhere + * in an unambiguous form. + */ + if (!name[len + 1]) + return NULL; /* no postimage name */ + second = skip_tree_prefix(state, name + len + 1, + line_len - (len + 1)); + if (!second) + return NULL; + /* + * Does len bytes starting at "name" and "second" + * (that are separated by one HT or SP we just + * found) exactly match? + */ + if (second[len] == '\n' && !strncmp(name, second, len)) + return xmemdupz(name, len); + } + } +} + +/* Verify that we recognize the lines following a git header */ +static int parse_git_header(struct apply_state *state, + const char *line, + int len, + unsigned int size, + struct patch *patch) +{ + unsigned long offset; + + /* A git diff has explicit new/delete information, so we don't guess */ + patch->is_new = 0; + patch->is_delete = 0; + + /* + * Some things may not have the old name in the + * rest of the headers anywhere (pure mode changes, + * or removing or adding empty files), so we get + * the default name from the header. + */ + patch->def_name = git_header_name(state, line, len); + if (patch->def_name && state->root.len) { + char *s = xstrfmt("%s%s", state->root.buf, patch->def_name); + free(patch->def_name); + patch->def_name = s; + } + + line += len; + size -= len; + state->linenr++; + for (offset = len ; size > 0 ; offset += len, size -= len, line += len, state->linenr++) { + static const struct opentry { + const char *str; + int (*fn)(struct apply_state *, const char *, struct patch *); + } optable[] = { + { "@@ -", gitdiff_hdrend }, + { "--- ", gitdiff_oldname }, + { "+++ ", gitdiff_newname }, + { "old mode ", gitdiff_oldmode }, + { "new mode ", gitdiff_newmode }, + { "deleted file mode ", gitdiff_delete }, + { "new file mode ", gitdiff_newfile }, + { "copy from ", gitdiff_copysrc }, + { "copy to ", gitdiff_copydst }, + { "rename old ", gitdiff_renamesrc }, + { "rename new ", gitdiff_renamedst }, + { "rename from ", gitdiff_renamesrc }, + { "rename to ", gitdiff_renamedst }, + { "similarity index ", gitdiff_similarity }, + { "dissimilarity index ", gitdiff_dissimilarity }, + { "index ", gitdiff_index }, + { "", gitdiff_unrecognized }, + }; + int i; + + len = linelen(line, size); + if (!len || line[len-1] != '\n') + break; + for (i = 0; i < ARRAY_SIZE(optable); i++) { + const struct opentry *p = optable + i; + int oplen = strlen(p->str); + int res; + if (len < oplen || memcmp(p->str, line, oplen)) + continue; + res = p->fn(state, line + oplen, patch); + if (res < 0) + return -1; + if (res > 0) + return offset; + break; + } + } + + return offset; +} + +static int parse_num(const char *line, unsigned long *p) +{ + char *ptr; + + if (!isdigit(*line)) + return 0; + *p = strtoul(line, &ptr, 10); + return ptr - line; +} + +static int parse_range(const char *line, int len, int offset, const char *expect, + unsigned long *p1, unsigned long *p2) +{ + int digits, ex; + + if (offset < 0 || offset >= len) + return -1; + line += offset; + len -= offset; + + digits = parse_num(line, p1); + if (!digits) + return -1; + + offset += digits; + line += digits; + len -= digits; + + *p2 = 1; + if (*line == ',') { + digits = parse_num(line+1, p2); + if (!digits) + return -1; + + offset += digits+1; + line += digits+1; + len -= digits+1; + } + + ex = strlen(expect); + if (ex > len) + return -1; + if (memcmp(line, expect, ex)) + return -1; + + return offset + ex; +} + +static void recount_diff(const char *line, int size, struct fragment *fragment) +{ + int oldlines = 0, newlines = 0, ret = 0; + + if (size < 1) { + warning("recount: ignore empty hunk"); + return; + } + + for (;;) { + int len = linelen(line, size); + size -= len; + line += len; + + if (size < 1) + break; + + switch (*line) { + case ' ': case '\n': + newlines++; + /* fall through */ + case '-': + oldlines++; + continue; + case '+': + newlines++; + continue; + case '\\': + continue; + case '@': + ret = size < 3 || !starts_with(line, "@@ "); + break; + case 'd': + ret = size < 5 || !starts_with(line, "diff "); + break; + default: + ret = -1; + break; + } + if (ret) { + warning(_("recount: unexpected line: %.*s"), + (int)linelen(line, size), line); + return; + } + break; + } + fragment->oldlines = oldlines; + fragment->newlines = newlines; +} + +/* + * Parse a unified diff fragment header of the + * form "@@ -a,b +c,d @@" + */ +static int parse_fragment_header(const char *line, int len, struct fragment *fragment) +{ + int offset; + + if (!len || line[len-1] != '\n') + return -1; + + /* Figure out the number of lines in a fragment */ + offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines); + offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines); + + return offset; +} + +/* + * Find file diff header + * + * Returns: + * -1 if no header was found + * -128 in case of error + * the size of the header in bytes (called "offset") otherwise + */ +static int find_header(struct apply_state *state, + const char *line, + unsigned long size, + int *hdrsize, + struct patch *patch) +{ + unsigned long offset, len; + + patch->is_toplevel_relative = 0; + patch->is_rename = patch->is_copy = 0; + patch->is_new = patch->is_delete = -1; + patch->old_mode = patch->new_mode = 0; + patch->old_name = patch->new_name = NULL; + for (offset = 0; size > 0; offset += len, size -= len, line += len, state->linenr++) { + unsigned long nextlen; + + len = linelen(line, size); + if (!len) + break; + + /* Testing this early allows us to take a few shortcuts.. */ + if (len < 6) + continue; + + /* + * Make sure we don't find any unconnected patch fragments. + * That's a sign that we didn't find a header, and that a + * patch has become corrupted/broken up. + */ + if (!memcmp("@@ -", line, 4)) { + struct fragment dummy; + if (parse_fragment_header(line, len, &dummy) < 0) + continue; + error(_("patch fragment without header at line %d: %.*s"), + state->linenr, (int)len-1, line); + return -128; + } + + if (size < len + 6) + break; + + /* + * Git patch? It might not have a real patch, just a rename + * or mode change, so we handle that specially + */ + if (!memcmp("diff --git ", line, 11)) { + int git_hdr_len = parse_git_header(state, line, len, size, patch); + if (git_hdr_len < 0) + return -128; + if (git_hdr_len <= len) + continue; + if (!patch->old_name && !patch->new_name) { + if (!patch->def_name) { + error(Q_("git diff header lacks filename information when removing " + "%d leading pathname component (line %d)", + "git diff header lacks filename information when removing " + "%d leading pathname components (line %d)", + state->p_value), + state->p_value, state->linenr); + return -128; + } + patch->old_name = xstrdup(patch->def_name); + patch->new_name = xstrdup(patch->def_name); + } + if (!patch->is_delete && !patch->new_name) { + error("git diff header lacks filename information " + "(line %d)", state->linenr); + return -128; + } + patch->is_toplevel_relative = 1; + *hdrsize = git_hdr_len; + return offset; + } + + /* --- followed by +++ ? */ + if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) + continue; + + /* + * We only accept unified patches, so we want it to + * at least have "@@ -a,b +c,d @@\n", which is 14 chars + * minimum ("@@ -0,0 +1 @@\n" is the shortest). + */ + nextlen = linelen(line + len, size - len); + if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) + continue; + + /* Ok, we'll consider it a patch */ + if (parse_traditional_patch(state, line, line+len, patch)) + return -128; + *hdrsize = len + nextlen; + state->linenr += 2; + return offset; + } + return -1; +} + +static void record_ws_error(struct apply_state *state, + unsigned result, + const char *line, + int len, + int linenr) +{ + char *err; + + if (!result) + return; + + state->whitespace_error++; + if (state->squelch_whitespace_errors && + state->squelch_whitespace_errors < state->whitespace_error) + return; + + err = whitespace_error_string(result); + if (state->apply_verbosity > verbosity_silent) + fprintf(stderr, "%s:%d: %s.\n%.*s\n", + state->patch_input_file, linenr, err, len, line); + free(err); +} + +static void check_whitespace(struct apply_state *state, + const char *line, + int len, + unsigned ws_rule) +{ + unsigned result = ws_check(line + 1, len - 1, ws_rule); + + record_ws_error(state, result, line + 1, len - 2, state->linenr); +} + +/* + * Parse a unified diff. Note that this really needs to parse each + * fragment separately, since the only way to know the difference + * between a "---" that is part of a patch, and a "---" that starts + * the next patch is to look at the line counts.. + */ +static int parse_fragment(struct apply_state *state, + const char *line, + unsigned long size, + struct patch *patch, + struct fragment *fragment) +{ + int added, deleted; + int len = linelen(line, size), offset; + unsigned long oldlines, newlines; + unsigned long leading, trailing; + + offset = parse_fragment_header(line, len, fragment); + if (offset < 0) + return -1; + if (offset > 0 && patch->recount) + recount_diff(line + offset, size - offset, fragment); + oldlines = fragment->oldlines; + newlines = fragment->newlines; + leading = 0; + trailing = 0; + + /* Parse the thing.. */ + line += len; + size -= len; + state->linenr++; + added = deleted = 0; + for (offset = len; + 0 < size; + offset += len, size -= len, line += len, state->linenr++) { + if (!oldlines && !newlines) + break; + len = linelen(line, size); + if (!len || line[len-1] != '\n') + return -1; + switch (*line) { + default: + return -1; + case '\n': /* newer GNU diff, an empty context line */ + case ' ': + oldlines--; + newlines--; + if (!deleted && !added) + leading++; + trailing++; + if (!state->apply_in_reverse && + state->ws_error_action == correct_ws_error) + check_whitespace(state, line, len, patch->ws_rule); + break; + case '-': + if (state->apply_in_reverse && + state->ws_error_action != nowarn_ws_error) + check_whitespace(state, line, len, patch->ws_rule); + deleted++; + oldlines--; + trailing = 0; + break; + case '+': + if (!state->apply_in_reverse && + state->ws_error_action != nowarn_ws_error) + check_whitespace(state, line, len, patch->ws_rule); + added++; + newlines--; + trailing = 0; + break; + + /* + * We allow "\ No newline at end of file". Depending + * on locale settings when the patch was produced we + * don't know what this line looks like. The only + * thing we do know is that it begins with "\ ". + * Checking for 12 is just for sanity check -- any + * l10n of "\ No newline..." is at least that long. + */ + case '\\': + if (len < 12 || memcmp(line, "\\ ", 2)) + return -1; + break; + } + } + if (oldlines || newlines) + return -1; + if (!deleted && !added) + return -1; + + fragment->leading = leading; + fragment->trailing = trailing; + + /* + * If a fragment ends with an incomplete line, we failed to include + * it in the above loop because we hit oldlines == newlines == 0 + * before seeing it. + */ + if (12 < size && !memcmp(line, "\\ ", 2)) + offset += linelen(line, size); + + patch->lines_added += added; + patch->lines_deleted += deleted; + + if (0 < patch->is_new && oldlines) + return error(_("new file depends on old contents")); + if (0 < patch->is_delete && newlines) + return error(_("deleted file still has contents")); + return offset; +} + +/* + * We have seen "diff --git a/... b/..." header (or a traditional patch + * header). Read hunks that belong to this patch into fragments and hang + * them to the given patch structure. + * + * The (fragment->patch, fragment->size) pair points into the memory given + * by the caller, not a copy, when we return. + * + * Returns: + * -1 in case of error, + * the number of bytes in the patch otherwise. + */ +static int parse_single_patch(struct apply_state *state, + const char *line, + unsigned long size, + struct patch *patch) +{ + unsigned long offset = 0; + unsigned long oldlines = 0, newlines = 0, context = 0; + struct fragment **fragp = &patch->fragments; + + while (size > 4 && !memcmp(line, "@@ -", 4)) { + struct fragment *fragment; + int len; + + fragment = xcalloc(1, sizeof(*fragment)); + fragment->linenr = state->linenr; + len = parse_fragment(state, line, size, patch, fragment); + if (len <= 0) { + free(fragment); + return error(_("corrupt patch at line %d"), state->linenr); + } + fragment->patch = line; + fragment->size = len; + oldlines += fragment->oldlines; + newlines += fragment->newlines; + context += fragment->leading + fragment->trailing; + + *fragp = fragment; + fragp = &fragment->next; + + offset += len; + line += len; + size -= len; + } + + /* + * If something was removed (i.e. we have old-lines) it cannot + * be creation, and if something was added it cannot be + * deletion. However, the reverse is not true; --unified=0 + * patches that only add are not necessarily creation even + * though they do not have any old lines, and ones that only + * delete are not necessarily deletion. + * + * Unfortunately, a real creation/deletion patch do _not_ have + * any context line by definition, so we cannot safely tell it + * apart with --unified=0 insanity. At least if the patch has + * more than one hunk it is not creation or deletion. + */ + if (patch->is_new < 0 && + (oldlines || (patch->fragments && patch->fragments->next))) + patch->is_new = 0; + if (patch->is_delete < 0 && + (newlines || (patch->fragments && patch->fragments->next))) + patch->is_delete = 0; + + if (0 < patch->is_new && oldlines) + return error(_("new file %s depends on old contents"), patch->new_name); + if (0 < patch->is_delete && newlines) + return error(_("deleted file %s still has contents"), patch->old_name); + if (!patch->is_delete && !newlines && context && state->apply_verbosity > verbosity_silent) + fprintf_ln(stderr, + _("** warning: " + "file %s becomes empty but is not deleted"), + patch->new_name); + + return offset; +} + +static inline int metadata_changes(struct patch *patch) +{ + return patch->is_rename > 0 || + patch->is_copy > 0 || + patch->is_new > 0 || + patch->is_delete || + (patch->old_mode && patch->new_mode && + patch->old_mode != patch->new_mode); +} + +static char *inflate_it(const void *data, unsigned long size, + unsigned long inflated_size) +{ + git_zstream stream; + void *out; + int st; + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (unsigned char *)data; + stream.avail_in = size; + stream.next_out = out = xmalloc(inflated_size); + stream.avail_out = inflated_size; + git_inflate_init(&stream); + st = git_inflate(&stream, Z_FINISH); + git_inflate_end(&stream); + if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { + free(out); + return NULL; + } + return out; +} + +/* + * Read a binary hunk and return a new fragment; fragment->patch + * points at an allocated memory that the caller must free, so + * it is marked as "->free_patch = 1". + */ +static struct fragment *parse_binary_hunk(struct apply_state *state, + char **buf_p, + unsigned long *sz_p, + int *status_p, + int *used_p) +{ + /* + * Expect a line that begins with binary patch method ("literal" + * or "delta"), followed by the length of data before deflating. + * a sequence of 'length-byte' followed by base-85 encoded data + * should follow, terminated by a newline. + * + * Each 5-byte sequence of base-85 encodes up to 4 bytes, + * and we would limit the patch line to 66 characters, + * so one line can fit up to 13 groups that would decode + * to 52 bytes max. The length byte 'A'-'Z' corresponds + * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. + */ + int llen, used; + unsigned long size = *sz_p; + char *buffer = *buf_p; + int patch_method; + unsigned long origlen; + char *data = NULL; + int hunk_size = 0; + struct fragment *frag; + + llen = linelen(buffer, size); + used = llen; + + *status_p = 0; + + if (starts_with(buffer, "delta ")) { + patch_method = BINARY_DELTA_DEFLATED; + origlen = strtoul(buffer + 6, NULL, 10); + } + else if (starts_with(buffer, "literal ")) { + patch_method = BINARY_LITERAL_DEFLATED; + origlen = strtoul(buffer + 8, NULL, 10); + } + else + return NULL; + + state->linenr++; + buffer += llen; + while (1) { + int byte_length, max_byte_length, newsize; + llen = linelen(buffer, size); + used += llen; + state->linenr++; + if (llen == 1) { + /* consume the blank line */ + buffer++; + size--; + break; + } + /* + * Minimum line is "A00000\n" which is 7-byte long, + * and the line length must be multiple of 5 plus 2. + */ + if ((llen < 7) || (llen-2) % 5) + goto corrupt; + max_byte_length = (llen - 2) / 5 * 4; + byte_length = *buffer; + if ('A' <= byte_length && byte_length <= 'Z') + byte_length = byte_length - 'A' + 1; + else if ('a' <= byte_length && byte_length <= 'z') + byte_length = byte_length - 'a' + 27; + else + goto corrupt; + /* if the input length was not multiple of 4, we would + * have filler at the end but the filler should never + * exceed 3 bytes + */ + if (max_byte_length < byte_length || + byte_length <= max_byte_length - 4) + goto corrupt; + newsize = hunk_size + byte_length; + data = xrealloc(data, newsize); + if (decode_85(data + hunk_size, buffer + 1, byte_length)) + goto corrupt; + hunk_size = newsize; + buffer += llen; + size -= llen; + } + + frag = xcalloc(1, sizeof(*frag)); + frag->patch = inflate_it(data, hunk_size, origlen); + frag->free_patch = 1; + if (!frag->patch) + goto corrupt; + free(data); + frag->size = origlen; + *buf_p = buffer; + *sz_p = size; + *used_p = used; + frag->binary_patch_method = patch_method; + return frag; + + corrupt: + free(data); + *status_p = -1; + error(_("corrupt binary patch at line %d: %.*s"), + state->linenr-1, llen-1, buffer); + return NULL; +} + +/* + * Returns: + * -1 in case of error, + * the length of the parsed binary patch otherwise + */ +static int parse_binary(struct apply_state *state, + char *buffer, + unsigned long size, + struct patch *patch) +{ + /* + * We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "literal" or + * "delta") and the length of data before deflating; a + * sequence of 'length-byte' followed by base-85 encoded data + * follows. + * + * When a binary patch is reversible, there is another binary + * hunk in the same format, starting with patch method (either + * "literal" or "delta") with the length of data, and a sequence + * of length-byte + base-85 encoded data, terminated with another + * empty line. This data, when applied to the postimage, produces + * the preimage. + */ + struct fragment *forward; + struct fragment *reverse; + int status; + int used, used_1; + + forward = parse_binary_hunk(state, &buffer, &size, &status, &used); + if (!forward && !status) + /* there has to be one hunk (forward hunk) */ + return error(_("unrecognized binary patch at line %d"), state->linenr-1); + if (status) + /* otherwise we already gave an error message */ + return status; + + reverse = parse_binary_hunk(state, &buffer, &size, &status, &used_1); + if (reverse) + used += used_1; + else if (status) { + /* + * Not having reverse hunk is not an error, but having + * a corrupt reverse hunk is. + */ + free((void*) forward->patch); + free(forward); + return status; + } + forward->next = reverse; + patch->fragments = forward; + patch->is_binary = 1; + return used; +} + +static void prefix_one(struct apply_state *state, char **name) +{ + char *old_name = *name; + if (!old_name) + return; + *name = xstrdup(prefix_filename(state->prefix, state->prefix_length, *name)); + free(old_name); +} + +static void prefix_patch(struct apply_state *state, struct patch *p) +{ + if (!state->prefix || p->is_toplevel_relative) + return; + prefix_one(state, &p->new_name); + prefix_one(state, &p->old_name); +} + +/* + * include/exclude + */ + +static void add_name_limit(struct apply_state *state, + const char *name, + int exclude) +{ + struct string_list_item *it; + + it = string_list_append(&state->limit_by_name, name); + it->util = exclude ? NULL : (void *) 1; +} + +static int use_patch(struct apply_state *state, struct patch *p) +{ + const char *pathname = p->new_name ? p->new_name : p->old_name; + int i; + + /* Paths outside are not touched regardless of "--include" */ + if (0 < state->prefix_length) { + int pathlen = strlen(pathname); + if (pathlen <= state->prefix_length || + memcmp(state->prefix, pathname, state->prefix_length)) + return 0; + } + + /* See if it matches any of exclude/include rule */ + for (i = 0; i < state->limit_by_name.nr; i++) { + struct string_list_item *it = &state->limit_by_name.items[i]; + if (!wildmatch(it->string, pathname, 0, NULL)) + return (it->util != NULL); + } + + /* + * If we had any include, a path that does not match any rule is + * not used. Otherwise, we saw bunch of exclude rules (or none) + * and such a path is used. + */ + return !state->has_include; +} + +/* + * Read the patch text in "buffer" that extends for "size" bytes; stop + * reading after seeing a single patch (i.e. changes to a single file). + * Create fragments (i.e. patch hunks) and hang them to the given patch. + * + * Returns: + * -1 if no header was found or parse_binary() failed, + * -128 on another error, + * the number of bytes consumed otherwise, + * so that the caller can call us again for the next patch. + */ +static int parse_chunk(struct apply_state *state, char *buffer, unsigned long size, struct patch *patch) +{ + int hdrsize, patchsize; + int offset = find_header(state, buffer, size, &hdrsize, patch); + + if (offset < 0) + return offset; + + prefix_patch(state, patch); + + if (!use_patch(state, patch)) + patch->ws_rule = 0; + else + patch->ws_rule = whitespace_rule(patch->new_name + ? patch->new_name + : patch->old_name); + + patchsize = parse_single_patch(state, + buffer + offset + hdrsize, + size - offset - hdrsize, + patch); + + if (patchsize < 0) + return -128; + + if (!patchsize) { + static const char git_binary[] = "GIT binary patch\n"; + int hd = hdrsize + offset; + unsigned long llen = linelen(buffer + hd, size - hd); + + if (llen == sizeof(git_binary) - 1 && + !memcmp(git_binary, buffer + hd, llen)) { + int used; + state->linenr++; + used = parse_binary(state, buffer + hd + llen, + size - hd - llen, patch); + if (used < 0) + return -1; + if (used) + patchsize = used + llen; + else + patchsize = 0; + } + else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { + static const char *binhdr[] = { + "Binary files ", + "Files ", + NULL, + }; + int i; + for (i = 0; binhdr[i]; i++) { + int len = strlen(binhdr[i]); + if (len < size - hd && + !memcmp(binhdr[i], buffer + hd, len)) { + state->linenr++; + patch->is_binary = 1; + patchsize = llen; + break; + } + } + } + + /* Empty patch cannot be applied if it is a text patch + * without metadata change. A binary patch appears + * empty to us here. + */ + if ((state->apply || state->check) && + (!patch->is_binary && !metadata_changes(patch))) { + error(_("patch with only garbage at line %d"), state->linenr); + return -128; + } + } + + return offset + hdrsize + patchsize; +} + +#define swap(a,b) myswap((a),(b),sizeof(a)) + +#define myswap(a, b, size) do { \ + unsigned char mytmp[size]; \ + memcpy(mytmp, &a, size); \ + memcpy(&a, &b, size); \ + memcpy(&b, mytmp, size); \ +} while (0) + +static void reverse_patches(struct patch *p) +{ + for (; p; p = p->next) { + struct fragment *frag = p->fragments; + + swap(p->new_name, p->old_name); + swap(p->new_mode, p->old_mode); + swap(p->is_new, p->is_delete); + swap(p->lines_added, p->lines_deleted); + swap(p->old_sha1_prefix, p->new_sha1_prefix); + + for (; frag; frag = frag->next) { + swap(frag->newpos, frag->oldpos); + swap(frag->newlines, frag->oldlines); + } + } +} + +static const char pluses[] = +"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; +static const char minuses[]= +"----------------------------------------------------------------------"; + +static void show_stats(struct apply_state *state, struct patch *patch) +{ + struct strbuf qname = STRBUF_INIT; + char *cp = patch->new_name ? patch->new_name : patch->old_name; + int max, add, del; + + quote_c_style(cp, &qname, NULL, 0); + + /* + * "scale" the filename + */ + max = state->max_len; + if (max > 50) + max = 50; + + if (qname.len > max) { + cp = strchr(qname.buf + qname.len + 3 - max, '/'); + if (!cp) + cp = qname.buf + qname.len + 3 - max; + strbuf_splice(&qname, 0, cp - qname.buf, "...", 3); + } + + if (patch->is_binary) { + printf(" %-*s | Bin\n", max, qname.buf); + strbuf_release(&qname); + return; + } + + printf(" %-*s |", max, qname.buf); + strbuf_release(&qname); + + /* + * scale the add/delete + */ + max = max + state->max_change > 70 ? 70 - max : state->max_change; + add = patch->lines_added; + del = patch->lines_deleted; + + if (state->max_change > 0) { + int total = ((add + del) * max + state->max_change / 2) / state->max_change; + add = (add * max + state->max_change / 2) / state->max_change; + del = total - add; + } + printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted, + add, pluses, del, minuses); +} + +static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) +{ + switch (st->st_mode & S_IFMT) { + case S_IFLNK: + if (strbuf_readlink(buf, path, st->st_size) < 0) + return error(_("unable to read symlink %s"), path); + return 0; + 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); + return 0; + default: + return -1; + } +} + +/* + * Update the preimage, and the common lines in postimage, + * from buffer buf of length len. If postlen is 0 the postimage + * is updated in place, otherwise it's updated on a new buffer + * of length postlen + */ + +static void update_pre_post_images(struct image *preimage, + struct image *postimage, + char *buf, + size_t len, size_t postlen) +{ + int i, ctx, reduced; + char *new, *old, *fixed; + struct image fixed_preimage; + + /* + * Update the preimage with whitespace fixes. Note that we + * are not losing preimage->buf -- apply_one_fragment() will + * free "oldlines". + */ + prepare_image(&fixed_preimage, buf, len, 1); + assert(postlen + ? fixed_preimage.nr == preimage->nr + : fixed_preimage.nr <= preimage->nr); + for (i = 0; i < fixed_preimage.nr; i++) + fixed_preimage.line[i].flag = preimage->line[i].flag; + free(preimage->line_allocated); + *preimage = fixed_preimage; + + /* + * Adjust the common context lines in postimage. This can be + * done in-place when we are shrinking it with whitespace + * fixing, but needs a new buffer when ignoring whitespace or + * expanding leading tabs to spaces. + * + * We trust the caller to tell us if the update can be done + * in place (postlen==0) or not. + */ + old = postimage->buf; + if (postlen) + new = postimage->buf = xmalloc(postlen); + else + new = old; + fixed = preimage->buf; + + for (i = reduced = ctx = 0; i < postimage->nr; i++) { + size_t l_len = postimage->line[i].len; + if (!(postimage->line[i].flag & LINE_COMMON)) { + /* an added line -- no counterparts in preimage */ + memmove(new, old, l_len); + old += l_len; + new += l_len; + continue; + } + + /* a common context -- skip it in the original postimage */ + old += l_len; + + /* and find the corresponding one in the fixed preimage */ + while (ctx < preimage->nr && + !(preimage->line[ctx].flag & LINE_COMMON)) { + fixed += preimage->line[ctx].len; + ctx++; + } + + /* + * preimage is expected to run out, if the caller + * fixed addition of trailing blank lines. + */ + if (preimage->nr <= ctx) { + reduced++; + continue; + } + + /* and copy it in, while fixing the line length */ + l_len = preimage->line[ctx].len; + memcpy(new, fixed, l_len); + new += l_len; + fixed += l_len; + postimage->line[i].len = l_len; + ctx++; + } + + if (postlen + ? postlen < new - postimage->buf + : postimage->len < new - postimage->buf) + die("BUG: caller miscounted postlen: asked %d, orig = %d, used = %d", + (int)postlen, (int) postimage->len, (int)(new - postimage->buf)); + + /* Fix the length of the whole thing */ + postimage->len = new - postimage->buf; + postimage->nr -= reduced; +} + +static int line_by_line_fuzzy_match(struct image *img, + struct image *preimage, + struct image *postimage, + unsigned long try, + int try_lno, + int preimage_limit) +{ + int i; + size_t imgoff = 0; + size_t preoff = 0; + size_t postlen = postimage->len; + size_t extra_chars; + char *buf; + char *preimage_eof; + char *preimage_end; + struct strbuf fixed; + char *fixed_buf; + size_t fixed_len; + + for (i = 0; i < preimage_limit; i++) { + size_t prelen = preimage->line[i].len; + size_t imglen = img->line[try_lno+i].len; + + if (!fuzzy_matchlines(img->buf + try + imgoff, imglen, + preimage->buf + preoff, prelen)) + return 0; + if (preimage->line[i].flag & LINE_COMMON) + postlen += imglen - prelen; + imgoff += imglen; + preoff += prelen; + } + + /* + * Ok, the preimage matches with whitespace fuzz. + * + * imgoff now holds the true length of the target that + * matches the preimage before the end of the file. + * + * Count the number of characters in the preimage that fall + * beyond the end of the file and make sure that all of them + * are whitespace characters. (This can only happen if + * we are removing blank lines at the end of the file.) + */ + buf = preimage_eof = preimage->buf + preoff; + for ( ; i < preimage->nr; i++) + preoff += preimage->line[i].len; + preimage_end = preimage->buf + preoff; + for ( ; buf < preimage_end; buf++) + if (!isspace(*buf)) + return 0; + + /* + * Update the preimage and the common postimage context + * lines to use the same whitespace as the target. + * If whitespace is missing in the target (i.e. + * if the preimage extends beyond the end of the file), + * use the whitespace from the preimage. + */ + extra_chars = preimage_end - preimage_eof; + strbuf_init(&fixed, imgoff + extra_chars); + strbuf_add(&fixed, img->buf + try, imgoff); + strbuf_add(&fixed, preimage_eof, extra_chars); + fixed_buf = strbuf_detach(&fixed, &fixed_len); + update_pre_post_images(preimage, postimage, + fixed_buf, fixed_len, postlen); + return 1; +} + +static int match_fragment(struct apply_state *state, + struct image *img, + struct image *preimage, + struct image *postimage, + unsigned long try, + int try_lno, + unsigned ws_rule, + int match_beginning, int match_end) +{ + int i; + char *fixed_buf, *buf, *orig, *target; + struct strbuf fixed; + size_t fixed_len, postlen; + int preimage_limit; + + if (preimage->nr + try_lno <= img->nr) { + /* + * The hunk falls within the boundaries of img. + */ + preimage_limit = preimage->nr; + if (match_end && (preimage->nr + try_lno != img->nr)) + return 0; + } else if (state->ws_error_action == correct_ws_error && + (ws_rule & WS_BLANK_AT_EOF)) { + /* + * This hunk extends beyond the end of img, and we are + * removing blank lines at the end of the file. This + * many lines from the beginning of the preimage must + * match with img, and the remainder of the preimage + * must be blank. + */ + preimage_limit = img->nr - try_lno; + } else { + /* + * The hunk extends beyond the end of the img and + * we are not removing blanks at the end, so we + * should reject the hunk at this position. + */ + return 0; + } + + if (match_beginning && try_lno) + return 0; + + /* Quick hash check */ + for (i = 0; i < preimage_limit; i++) + if ((img->line[try_lno + i].flag & LINE_PATCHED) || + (preimage->line[i].hash != img->line[try_lno + i].hash)) + return 0; + + if (preimage_limit == preimage->nr) { + /* + * Do we have an exact match? If we were told to match + * at the end, size must be exactly at try+fragsize, + * otherwise try+fragsize must be still within the preimage, + * and either case, the old piece should match the preimage + * exactly. + */ + if ((match_end + ? (try + preimage->len == img->len) + : (try + preimage->len <= img->len)) && + !memcmp(img->buf + try, preimage->buf, preimage->len)) + return 1; + } else { + /* + * The preimage extends beyond the end of img, so + * there cannot be an exact match. + * + * There must be one non-blank context line that match + * a line before the end of img. + */ + char *buf_end; + + buf = preimage->buf; + buf_end = buf; + for (i = 0; i < preimage_limit; i++) + buf_end += preimage->line[i].len; + + for ( ; buf < buf_end; buf++) + if (!isspace(*buf)) + break; + if (buf == buf_end) + return 0; + } + + /* + * No exact match. If we are ignoring whitespace, run a line-by-line + * fuzzy matching. We collect all the line length information because + * we need it to adjust whitespace if we match. + */ + if (state->ws_ignore_action == ignore_ws_change) + return line_by_line_fuzzy_match(img, preimage, postimage, + try, try_lno, preimage_limit); + + if (state->ws_error_action != correct_ws_error) + return 0; + + /* + * The hunk does not apply byte-by-byte, but the hash says + * it might with whitespace fuzz. We weren't asked to + * ignore whitespace, we were asked to correct whitespace + * errors, so let's try matching after whitespace correction. + * + * While checking the preimage against the target, whitespace + * errors in both fixed, we count how large the corresponding + * postimage needs to be. The postimage prepared by + * apply_one_fragment() has whitespace errors fixed on added + * lines already, but the common lines were propagated as-is, + * which may become longer when their whitespace errors are + * fixed. + */ + + /* First count added lines in postimage */ + postlen = 0; + for (i = 0; i < postimage->nr; i++) { + if (!(postimage->line[i].flag & LINE_COMMON)) + postlen += postimage->line[i].len; + } + + /* + * The preimage may extend beyond the end of the file, + * but in this loop we will only handle the part of the + * preimage that falls within the file. + */ + strbuf_init(&fixed, preimage->len + 1); + orig = preimage->buf; + target = img->buf + try; + for (i = 0; i < preimage_limit; i++) { + size_t oldlen = preimage->line[i].len; + size_t tgtlen = img->line[try_lno + i].len; + size_t fixstart = fixed.len; + struct strbuf tgtfix; + int match; + + /* Try fixing the line in the preimage */ + ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); + + /* Try fixing the line in the target */ + strbuf_init(&tgtfix, tgtlen); + ws_fix_copy(&tgtfix, target, tgtlen, ws_rule, NULL); + + /* + * If they match, either the preimage was based on + * a version before our tree fixed whitespace breakage, + * or we are lacking a whitespace-fix patch the tree + * the preimage was based on already had (i.e. target + * has whitespace breakage, the preimage doesn't). + * In either case, we are fixing the whitespace breakages + * so we might as well take the fix together with their + * real change. + */ + match = (tgtfix.len == fixed.len - fixstart && + !memcmp(tgtfix.buf, fixed.buf + fixstart, + fixed.len - fixstart)); + + /* Add the length if this is common with the postimage */ + if (preimage->line[i].flag & LINE_COMMON) + postlen += tgtfix.len; + + strbuf_release(&tgtfix); + if (!match) + goto unmatch_exit; + + orig += oldlen; + target += tgtlen; + } + + + /* + * Now handle the lines in the preimage that falls beyond the + * end of the file (if any). They will only match if they are + * empty or only contain whitespace (if WS_BLANK_AT_EOL is + * false). + */ + for ( ; i < preimage->nr; i++) { + size_t fixstart = fixed.len; /* start of the fixed preimage */ + size_t oldlen = preimage->line[i].len; + int j; + + /* Try fixing the line in the preimage */ + ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); + + for (j = fixstart; j < fixed.len; j++) + if (!isspace(fixed.buf[j])) + goto unmatch_exit; + + orig += oldlen; + } + + /* + * Yes, the preimage is based on an older version that still + * has whitespace breakages unfixed, and fixing them makes the + * hunk match. Update the context lines in the postimage. + */ + fixed_buf = strbuf_detach(&fixed, &fixed_len); + if (postlen < postimage->len) + postlen = 0; + update_pre_post_images(preimage, postimage, + fixed_buf, fixed_len, postlen); + return 1; + + unmatch_exit: + strbuf_release(&fixed); + return 0; +} + +static int find_pos(struct apply_state *state, + struct image *img, + struct image *preimage, + struct image *postimage, + int line, + unsigned ws_rule, + int match_beginning, int match_end) +{ + int i; + unsigned long backwards, forwards, try; + int backwards_lno, forwards_lno, try_lno; + + /* + * If match_beginning or match_end is specified, there is no + * point starting from a wrong line that will never match and + * wander around and wait for a match at the specified end. + */ + if (match_beginning) + line = 0; + else if (match_end) + line = img->nr - preimage->nr; + + /* + * Because the comparison is unsigned, the following test + * will also take care of a negative line number that can + * result when match_end and preimage is larger than the target. + */ + if ((size_t) line > img->nr) + line = img->nr; + + try = 0; + for (i = 0; i < line; i++) + try += img->line[i].len; + + /* + * There's probably some smart way to do this, but I'll leave + * that to the smart and beautiful people. I'm simple and stupid. + */ + backwards = try; + backwards_lno = line; + forwards = try; + forwards_lno = line; + try_lno = line; + + for (i = 0; ; i++) { + if (match_fragment(state, img, preimage, postimage, + try, try_lno, ws_rule, + match_beginning, match_end)) + return try_lno; + + again: + if (backwards_lno == 0 && forwards_lno == img->nr) + break; + + if (i & 1) { + if (backwards_lno == 0) { + i++; + goto again; + } + backwards_lno--; + backwards -= img->line[backwards_lno].len; + try = backwards; + try_lno = backwards_lno; + } else { + if (forwards_lno == img->nr) { + i++; + goto again; + } + forwards += img->line[forwards_lno].len; + forwards_lno++; + try = forwards; + try_lno = forwards_lno; + } + + } + return -1; +} + +static void remove_first_line(struct image *img) +{ + img->buf += img->line[0].len; + img->len -= img->line[0].len; + img->line++; + img->nr--; +} + +static void remove_last_line(struct image *img) +{ + img->len -= img->line[--img->nr].len; +} + +/* + * The change from "preimage" and "postimage" has been found to + * apply at applied_pos (counts in line numbers) in "img". + * Update "img" to remove "preimage" and replace it with "postimage". + */ +static void update_image(struct apply_state *state, + struct image *img, + int applied_pos, + struct image *preimage, + struct image *postimage) +{ + /* + * remove the copy of preimage at offset in img + * and replace it with postimage + */ + int i, nr; + size_t remove_count, insert_count, applied_at = 0; + char *result; + int preimage_limit; + + /* + * If we are removing blank lines at the end of img, + * the preimage may extend beyond the end. + * If that is the case, we must be careful only to + * remove the part of the preimage that falls within + * the boundaries of img. Initialize preimage_limit + * to the number of lines in the preimage that falls + * within the boundaries. + */ + preimage_limit = preimage->nr; + if (preimage_limit > img->nr - applied_pos) + preimage_limit = img->nr - applied_pos; + + for (i = 0; i < applied_pos; i++) + applied_at += img->line[i].len; + + remove_count = 0; + for (i = 0; i < preimage_limit; i++) + remove_count += img->line[applied_pos + i].len; + insert_count = postimage->len; + + /* Adjust the contents */ + result = xmalloc(st_add3(st_sub(img->len, remove_count), insert_count, 1)); + memcpy(result, img->buf, applied_at); + memcpy(result + applied_at, postimage->buf, postimage->len); + memcpy(result + applied_at + postimage->len, + img->buf + (applied_at + remove_count), + img->len - (applied_at + remove_count)); + free(img->buf); + img->buf = result; + img->len += insert_count - remove_count; + result[img->len] = '\0'; + + /* Adjust the line table */ + nr = img->nr + postimage->nr - preimage_limit; + if (preimage_limit < postimage->nr) { + /* + * NOTE: this knows that we never call remove_first_line() + * on anything other than pre/post image. + */ + REALLOC_ARRAY(img->line, nr); + img->line_allocated = img->line; + } + if (preimage_limit != postimage->nr) + memmove(img->line + applied_pos + postimage->nr, + img->line + applied_pos + preimage_limit, + (img->nr - (applied_pos + preimage_limit)) * + sizeof(*img->line)); + memcpy(img->line + applied_pos, + postimage->line, + postimage->nr * sizeof(*img->line)); + if (!state->allow_overlap) + for (i = 0; i < postimage->nr; i++) + img->line[applied_pos + i].flag |= LINE_PATCHED; + img->nr = nr; +} + +/* + * Use the patch-hunk text in "frag" to prepare two images (preimage and + * postimage) for the hunk. Find lines that match "preimage" in "img" and + * replace the part of "img" with "postimage" text. + */ +static int apply_one_fragment(struct apply_state *state, + struct image *img, struct fragment *frag, + int inaccurate_eof, unsigned ws_rule, + int nth_fragment) +{ + int match_beginning, match_end; + const char *patch = frag->patch; + int size = frag->size; + char *old, *oldlines; + struct strbuf newlines; + int new_blank_lines_at_end = 0; + int found_new_blank_lines_at_end = 0; + int hunk_linenr = frag->linenr; + unsigned long leading, trailing; + int pos, applied_pos; + struct image preimage; + struct image postimage; + + memset(&preimage, 0, sizeof(preimage)); + memset(&postimage, 0, sizeof(postimage)); + oldlines = xmalloc(size); + strbuf_init(&newlines, size); + + old = oldlines; + while (size > 0) { + char first; + int len = linelen(patch, size); + int plen; + int added_blank_line = 0; + int is_blank_context = 0; + size_t start; + + if (!len) + break; + + /* + * "plen" is how much of the line we should use for + * the actual patch data. Normally we just remove the + * first character on the line, but if the line is + * followed by "\ No newline", then we also remove the + * last one (which is the newline, of course). + */ + plen = len - 1; + if (len < size && patch[len] == '\\') + plen--; + first = *patch; + if (state->apply_in_reverse) { + if (first == '-') + first = '+'; + else if (first == '+') + first = '-'; + } + + switch (first) { + case '\n': + /* Newer GNU diff, empty context line */ + if (plen < 0) + /* ... followed by '\No newline'; nothing */ + break; + *old++ = '\n'; + strbuf_addch(&newlines, '\n'); + add_line_info(&preimage, "\n", 1, LINE_COMMON); + add_line_info(&postimage, "\n", 1, LINE_COMMON); + is_blank_context = 1; + break; + case ' ': + if (plen && (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) + is_blank_context = 1; + case '-': + memcpy(old, patch + 1, plen); + add_line_info(&preimage, old, plen, + (first == ' ' ? LINE_COMMON : 0)); + old += plen; + if (first == '-') + break; + /* Fall-through for ' ' */ + case '+': + /* --no-add does not add new lines */ + if (first == '+' && state->no_add) + break; + + start = newlines.len; + if (first != '+' || + !state->whitespace_error || + state->ws_error_action != correct_ws_error) { + strbuf_add(&newlines, patch + 1, plen); + } + else { + ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &state->applied_after_fixing_ws); + } + add_line_info(&postimage, newlines.buf + start, newlines.len - start, + (first == '+' ? 0 : LINE_COMMON)); + if (first == '+' && + (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) + added_blank_line = 1; + break; + case '@': case '\\': + /* Ignore it, we already handled it */ + break; + default: + if (state->apply_verbosity > verbosity_normal) + error(_("invalid start of line: '%c'"), first); + applied_pos = -1; + goto out; + } + if (added_blank_line) { + if (!new_blank_lines_at_end) + found_new_blank_lines_at_end = hunk_linenr; + new_blank_lines_at_end++; + } + else if (is_blank_context) + ; + else + new_blank_lines_at_end = 0; + patch += len; + size -= len; + hunk_linenr++; + } + if (inaccurate_eof && + old > oldlines && old[-1] == '\n' && + newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') { + old--; + strbuf_setlen(&newlines, newlines.len - 1); + } + + leading = frag->leading; + trailing = frag->trailing; + + /* + * A hunk to change lines at the beginning would begin with + * @@ -1,L +N,M @@ + * but we need to be careful. -U0 that inserts before the second + * line also has this pattern. + * + * And a hunk to add to an empty file would begin with + * @@ -0,0 +N,M @@ + * + * In other words, a hunk that is (frag->oldpos <= 1) with or + * without leading context must match at the beginning. + */ + match_beginning = (!frag->oldpos || + (frag->oldpos == 1 && !state->unidiff_zero)); + + /* + * A hunk without trailing lines must match at the end. + * However, we simply cannot tell if a hunk must match end + * from the lack of trailing lines if the patch was generated + * with unidiff without any context. + */ + match_end = !state->unidiff_zero && !trailing; + + pos = frag->newpos ? (frag->newpos - 1) : 0; + preimage.buf = oldlines; + preimage.len = old - oldlines; + postimage.buf = newlines.buf; + postimage.len = newlines.len; + preimage.line = preimage.line_allocated; + postimage.line = postimage.line_allocated; + + for (;;) { + + applied_pos = find_pos(state, img, &preimage, &postimage, pos, + ws_rule, match_beginning, match_end); + + if (applied_pos >= 0) + break; + + /* Am I at my context limits? */ + if ((leading <= state->p_context) && (trailing <= state->p_context)) + break; + if (match_beginning || match_end) { + match_beginning = match_end = 0; + continue; + } + + /* + * Reduce the number of context lines; reduce both + * leading and trailing if they are equal otherwise + * just reduce the larger context. + */ + if (leading >= trailing) { + remove_first_line(&preimage); + remove_first_line(&postimage); + pos--; + leading--; + } + if (trailing > leading) { + remove_last_line(&preimage); + remove_last_line(&postimage); + trailing--; + } + } + + if (applied_pos >= 0) { + if (new_blank_lines_at_end && + preimage.nr + applied_pos >= img->nr && + (ws_rule & WS_BLANK_AT_EOF) && + state->ws_error_action != nowarn_ws_error) { + record_ws_error(state, WS_BLANK_AT_EOF, "+", 1, + found_new_blank_lines_at_end); + if (state->ws_error_action == correct_ws_error) { + while (new_blank_lines_at_end--) + remove_last_line(&postimage); + } + /* + * We would want to prevent write_out_results() + * from taking place in apply_patch() that follows + * the callchain led us here, which is: + * apply_patch->check_patch_list->check_patch-> + * apply_data->apply_fragments->apply_one_fragment + */ + if (state->ws_error_action == die_on_ws_error) + state->apply = 0; + } + + if (state->apply_verbosity > verbosity_normal && applied_pos != pos) { + int offset = applied_pos - pos; + if (state->apply_in_reverse) + offset = 0 - offset; + fprintf_ln(stderr, + Q_("Hunk #%d succeeded at %d (offset %d line).", + "Hunk #%d succeeded at %d (offset %d lines).", + offset), + nth_fragment, applied_pos + 1, offset); + } + + /* + * Warn if it was necessary to reduce the number + * of context lines. + */ + if ((leading != frag->leading || + trailing != frag->trailing) && state->apply_verbosity > verbosity_silent) + fprintf_ln(stderr, _("Context reduced to (%ld/%ld)" + " to apply fragment at %d"), + leading, trailing, applied_pos+1); + update_image(state, img, applied_pos, &preimage, &postimage); + } else { + if (state->apply_verbosity > verbosity_normal) + error(_("while searching for:\n%.*s"), + (int)(old - oldlines), oldlines); + } + +out: + free(oldlines); + strbuf_release(&newlines); + free(preimage.line_allocated); + free(postimage.line_allocated); + + return (applied_pos < 0); +} + +static int apply_binary_fragment(struct apply_state *state, + struct image *img, + struct patch *patch) +{ + struct fragment *fragment = patch->fragments; + unsigned long len; + void *dst; + + if (!fragment) + return error(_("missing binary patch data for '%s'"), + patch->new_name ? + patch->new_name : + patch->old_name); + + /* Binary patch is irreversible without the optional second hunk */ + if (state->apply_in_reverse) { + if (!fragment->next) + return error("cannot reverse-apply a binary patch " + "without the reverse hunk to '%s'", + patch->new_name + ? patch->new_name : patch->old_name); + fragment = fragment->next; + } + switch (fragment->binary_patch_method) { + case BINARY_DELTA_DEFLATED: + dst = patch_delta(img->buf, img->len, fragment->patch, + fragment->size, &len); + if (!dst) + return -1; + clear_image(img); + img->buf = dst; + img->len = len; + return 0; + case BINARY_LITERAL_DEFLATED: + clear_image(img); + img->len = fragment->size; + img->buf = xmemdupz(fragment->patch, img->len); + return 0; + } + return -1; +} + +/* + * Replace "img" with the result of applying the binary patch. + * The binary patch data itself in patch->fragment is still kept + * but the preimage prepared by the caller in "img" is freed here + * or in the helper function apply_binary_fragment() this calls. + */ +static int apply_binary(struct apply_state *state, + struct image *img, + struct patch *patch) +{ + const char *name = patch->old_name ? patch->old_name : patch->new_name; + struct object_id oid; + + /* + * For safety, we require patch index line to contain + * full 40-byte textual SHA1 for old and new, at least for now. + */ + if (strlen(patch->old_sha1_prefix) != 40 || + strlen(patch->new_sha1_prefix) != 40 || + get_oid_hex(patch->old_sha1_prefix, &oid) || + get_oid_hex(patch->new_sha1_prefix, &oid)) + return error("cannot apply binary patch to '%s' " + "without full index line", name); + + if (patch->old_name) { + /* + * See if the old one matches what the patch + * applies to. + */ + hash_sha1_file(img->buf, img->len, blob_type, oid.hash); + if (strcmp(oid_to_hex(&oid), patch->old_sha1_prefix)) + return error("the patch applies to '%s' (%s), " + "which does not match the " + "current contents.", + name, oid_to_hex(&oid)); + } + else { + /* Otherwise, the old one must be empty. */ + if (img->len) + return error("the patch applies to an empty " + "'%s' but it is not empty", name); + } + + get_oid_hex(patch->new_sha1_prefix, &oid); + if (is_null_oid(&oid)) { + clear_image(img); + return 0; /* deletion patch */ + } + + if (has_sha1_file(oid.hash)) { + /* We already have the postimage */ + enum object_type type; + unsigned long size; + char *result; + + result = read_sha1_file(oid.hash, &type, &size); + if (!result) + return error("the necessary postimage %s for " + "'%s' cannot be read", + patch->new_sha1_prefix, name); + clear_image(img); + img->buf = result; + img->len = size; + } else { + /* + * We have verified buf matches the preimage; + * apply the patch data to it, which is stored + * in the patch->fragments->{patch,size}. + */ + if (apply_binary_fragment(state, img, patch)) + return error(_("binary patch does not apply to '%s'"), + name); + + /* verify that the result matches */ + hash_sha1_file(img->buf, img->len, blob_type, oid.hash); + if (strcmp(oid_to_hex(&oid), patch->new_sha1_prefix)) + return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"), + name, patch->new_sha1_prefix, oid_to_hex(&oid)); + } + + return 0; +} + +static int apply_fragments(struct apply_state *state, struct image *img, struct patch *patch) +{ + struct fragment *frag = patch->fragments; + const char *name = patch->old_name ? patch->old_name : patch->new_name; + unsigned ws_rule = patch->ws_rule; + unsigned inaccurate_eof = patch->inaccurate_eof; + int nth = 0; + + if (patch->is_binary) + return apply_binary(state, img, patch); + + while (frag) { + nth++; + if (apply_one_fragment(state, img, frag, inaccurate_eof, ws_rule, nth)) { + error(_("patch failed: %s:%ld"), name, frag->oldpos); + if (!state->apply_with_reject) + return -1; + frag->rejected = 1; + } + frag = frag->next; + } + return 0; +} + +static int read_blob_object(struct strbuf *buf, const struct object_id *oid, unsigned mode) +{ + if (S_ISGITLINK(mode)) { + strbuf_grow(buf, 100); + strbuf_addf(buf, "Subproject commit %s\n", oid_to_hex(oid)); + } else { + enum object_type type; + unsigned long sz; + char *result; + + result = read_sha1_file(oid->hash, &type, &sz); + if (!result) + return -1; + /* XXX read_sha1_file NUL-terminates */ + strbuf_attach(buf, result, sz, sz + 1); + } + return 0; +} + +static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf) +{ + if (!ce) + return 0; + return read_blob_object(buf, &ce->oid, ce->ce_mode); +} + +static struct patch *in_fn_table(struct apply_state *state, const char *name) +{ + struct string_list_item *item; + + if (name == NULL) + return NULL; + + item = string_list_lookup(&state->fn_table, name); + if (item != NULL) + return (struct patch *)item->util; + + return NULL; +} + +/* + * item->util in the filename table records the status of the path. + * Usually it points at a patch (whose result records the contents + * of it after applying it), but it could be PATH_WAS_DELETED for a + * path that a previously applied patch has already removed, or + * PATH_TO_BE_DELETED for a path that a later patch would remove. + * + * The latter is needed to deal with a case where two paths A and B + * are swapped by first renaming A to B and then renaming B to A; + * moving A to B should not be prevented due to presence of B as we + * will remove it in a later patch. + */ +#define PATH_TO_BE_DELETED ((struct patch *) -2) +#define PATH_WAS_DELETED ((struct patch *) -1) + +static int to_be_deleted(struct patch *patch) +{ + return patch == PATH_TO_BE_DELETED; +} + +static int was_deleted(struct patch *patch) +{ + return patch == PATH_WAS_DELETED; +} + +static void add_to_fn_table(struct apply_state *state, struct patch *patch) +{ + struct string_list_item *item; + + /* + * Always add new_name unless patch is a deletion + * This should cover the cases for normal diffs, + * file creations and copies + */ + if (patch->new_name != NULL) { + item = string_list_insert(&state->fn_table, patch->new_name); + item->util = patch; + } + + /* + * store a failure on rename/deletion cases because + * later chunks shouldn't patch old names + */ + if ((patch->new_name == NULL) || (patch->is_rename)) { + item = string_list_insert(&state->fn_table, patch->old_name); + item->util = PATH_WAS_DELETED; + } +} + +static void prepare_fn_table(struct apply_state *state, struct patch *patch) +{ + /* + * store information about incoming file deletion + */ + while (patch) { + if ((patch->new_name == NULL) || (patch->is_rename)) { + struct string_list_item *item; + item = string_list_insert(&state->fn_table, patch->old_name); + item->util = PATH_TO_BE_DELETED; + } + patch = patch->next; + } +} + +static int checkout_target(struct index_state *istate, + struct cache_entry *ce, struct stat *st) +{ + struct checkout costate; + + memset(&costate, 0, sizeof(costate)); + costate.base_dir = ""; + costate.refresh_cache = 1; + costate.istate = istate; + if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st)) + return error(_("cannot checkout %s"), ce->name); + return 0; +} + +static struct patch *previous_patch(struct apply_state *state, + struct patch *patch, + int *gone) +{ + struct patch *previous; + + *gone = 0; + if (patch->is_copy || patch->is_rename) + return NULL; /* "git" patches do not depend on the order */ + + previous = in_fn_table(state, patch->old_name); + if (!previous) + return NULL; + + if (to_be_deleted(previous)) + return NULL; /* the deletion hasn't happened yet */ + + if (was_deleted(previous)) + *gone = 1; + + return previous; +} + +static int verify_index_match(const struct cache_entry *ce, struct stat *st) +{ + if (S_ISGITLINK(ce->ce_mode)) { + if (!S_ISDIR(st->st_mode)) + return -1; + return 0; + } + return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); +} + +#define SUBMODULE_PATCH_WITHOUT_INDEX 1 + +static int load_patch_target(struct apply_state *state, + struct strbuf *buf, + const struct cache_entry *ce, + struct stat *st, + const char *name, + unsigned expected_mode) +{ + if (state->cached || state->check_index) { + if (read_file_or_gitlink(ce, buf)) + return error(_("failed to read %s"), name); + } else if (name) { + if (S_ISGITLINK(expected_mode)) { + if (ce) + return read_file_or_gitlink(ce, buf); + else + return SUBMODULE_PATCH_WITHOUT_INDEX; + } else if (has_symlink_leading_path(name, strlen(name))) { + return error(_("reading from '%s' beyond a symbolic link"), name); + } else { + if (read_old_data(st, name, buf)) + return error(_("failed to read %s"), name); + } + } + return 0; +} + +/* + * We are about to apply "patch"; populate the "image" with the + * current version we have, from the working tree or from the index, + * depending on the situation e.g. --cached/--index. If we are + * applying a non-git patch that incrementally updates the tree, + * we read from the result of a previous diff. + */ +static int load_preimage(struct apply_state *state, + struct image *image, + struct patch *patch, struct stat *st, + const struct cache_entry *ce) +{ + struct strbuf buf = STRBUF_INIT; + size_t len; + char *img; + struct patch *previous; + int status; + + previous = previous_patch(state, patch, &status); + if (status) + return error(_("path %s has been renamed/deleted"), + patch->old_name); + if (previous) { + /* We have a patched copy in memory; use that. */ + strbuf_add(&buf, previous->result, previous->resultsize); + } else { + status = load_patch_target(state, &buf, ce, st, + patch->old_name, patch->old_mode); + if (status < 0) + return status; + else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) { + /* + * There is no way to apply subproject + * patch without looking at the index. + * NEEDSWORK: shouldn't this be flagged + * as an error??? + */ + free_fragment_list(patch->fragments); + patch->fragments = NULL; + } else if (status) { + return error(_("failed to read %s"), patch->old_name); + } + } + + img = strbuf_detach(&buf, &len); + prepare_image(image, img, len, !patch->is_binary); + return 0; +} + +static int three_way_merge(struct image *image, + char *path, + const struct object_id *base, + const struct object_id *ours, + const struct object_id *theirs) +{ + mmfile_t base_file, our_file, their_file; + mmbuffer_t result = { NULL }; + int status; + + read_mmblob(&base_file, base); + read_mmblob(&our_file, ours); + read_mmblob(&their_file, theirs); + status = ll_merge(&result, path, + &base_file, "base", + &our_file, "ours", + &their_file, "theirs", NULL); + free(base_file.ptr); + free(our_file.ptr); + free(their_file.ptr); + if (status < 0 || !result.ptr) { + free(result.ptr); + return -1; + } + clear_image(image); + image->buf = result.ptr; + image->len = result.size; + + return status; +} + +/* + * When directly falling back to add/add three-way merge, we read from + * the current contents of the new_name. In no cases other than that + * this function will be called. + */ +static int load_current(struct apply_state *state, + struct image *image, + struct patch *patch) +{ + struct strbuf buf = STRBUF_INIT; + int status, pos; + size_t len; + char *img; + struct stat st; + struct cache_entry *ce; + char *name = patch->new_name; + unsigned mode = patch->new_mode; + + if (!patch->is_new) + die("BUG: patch to %s is not a creation", patch->old_name); + + pos = cache_name_pos(name, strlen(name)); + if (pos < 0) + return error(_("%s: does not exist in index"), name); + ce = active_cache[pos]; + if (lstat(name, &st)) { + if (errno != ENOENT) + return error_errno("%s", name); + if (checkout_target(&the_index, ce, &st)) + return -1; + } + if (verify_index_match(ce, &st)) + return error(_("%s: does not match index"), name); + + status = load_patch_target(state, &buf, ce, &st, name, mode); + if (status < 0) + return status; + else if (status) + return -1; + img = strbuf_detach(&buf, &len); + prepare_image(image, img, len, !patch->is_binary); + return 0; +} + +static int try_threeway(struct apply_state *state, + struct image *image, + struct patch *patch, + struct stat *st, + const struct cache_entry *ce) +{ + struct object_id pre_oid, post_oid, our_oid; + struct strbuf buf = STRBUF_INIT; + size_t len; + int status; + char *img; + struct image tmp_image; + + /* No point falling back to 3-way merge in these cases */ + if (patch->is_delete || + S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode)) + return -1; + + /* Preimage the patch was prepared for */ + if (patch->is_new) + write_sha1_file("", 0, blob_type, pre_oid.hash); + else if (get_sha1(patch->old_sha1_prefix, pre_oid.hash) || + read_blob_object(&buf, &pre_oid, patch->old_mode)) + return error("repository lacks the necessary blob to fall back on 3-way merge."); + + if (state->apply_verbosity > verbosity_silent) + fprintf(stderr, "Falling back to three-way merge...\n"); + + img = strbuf_detach(&buf, &len); + prepare_image(&tmp_image, img, len, 1); + /* Apply the patch to get the post image */ + if (apply_fragments(state, &tmp_image, patch) < 0) { + clear_image(&tmp_image); + return -1; + } + /* post_oid is theirs */ + write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_oid.hash); + clear_image(&tmp_image); + + /* our_oid is ours */ + if (patch->is_new) { + if (load_current(state, &tmp_image, patch)) + return error("cannot read the current contents of '%s'", + patch->new_name); + } else { + if (load_preimage(state, &tmp_image, patch, st, ce)) + return error("cannot read the current contents of '%s'", + patch->old_name); + } + write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_oid.hash); + clear_image(&tmp_image); + + /* in-core three-way merge between post and our using pre as base */ + status = three_way_merge(image, patch->new_name, + &pre_oid, &our_oid, &post_oid); + if (status < 0) { + if (state->apply_verbosity > verbosity_silent) + fprintf(stderr, + "Failed to fall back on three-way merge...\n"); + return status; + } + + if (status) { + patch->conflicted_threeway = 1; + if (patch->is_new) + oidclr(&patch->threeway_stage[0]); + else + oidcpy(&patch->threeway_stage[0], &pre_oid); + oidcpy(&patch->threeway_stage[1], &our_oid); + oidcpy(&patch->threeway_stage[2], &post_oid); + if (state->apply_verbosity > verbosity_silent) + fprintf(stderr, + "Applied patch to '%s' with conflicts.\n", + patch->new_name); + } else { + if (state->apply_verbosity > verbosity_silent) + fprintf(stderr, + "Applied patch to '%s' cleanly.\n", + patch->new_name); + } + return 0; +} + +static int apply_data(struct apply_state *state, struct patch *patch, + struct stat *st, const struct cache_entry *ce) +{ + struct image image; + + if (load_preimage(state, &image, patch, st, ce) < 0) + return -1; + + if (patch->direct_to_threeway || + apply_fragments(state, &image, patch) < 0) { + /* Note: with --reject, apply_fragments() returns 0 */ + if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0) + return -1; + } + patch->result = image.buf; + patch->resultsize = image.len; + add_to_fn_table(state, patch); + free(image.line_allocated); + + if (0 < patch->is_delete && patch->resultsize) + return error(_("removal patch leaves file contents")); + + return 0; +} + +/* + * If "patch" that we are looking at modifies or deletes what we have, + * we would want it not to lose any local modification we have, either + * in the working tree or in the index. + * + * This also decides if a non-git patch is a creation patch or a + * modification to an existing empty file. We do not check the state + * of the current tree for a creation patch in this function; the caller + * check_patch() separately makes sure (and errors out otherwise) that + * the path the patch creates does not exist in the current tree. + */ +static int check_preimage(struct apply_state *state, + struct patch *patch, + struct cache_entry **ce, + struct stat *st) +{ + const char *old_name = patch->old_name; + struct patch *previous = NULL; + int stat_ret = 0, status; + unsigned st_mode = 0; + + if (!old_name) + return 0; + + assert(patch->is_new <= 0); + previous = previous_patch(state, patch, &status); + + if (status) + return error(_("path %s has been renamed/deleted"), old_name); + if (previous) { + st_mode = previous->new_mode; + } else if (!state->cached) { + stat_ret = lstat(old_name, st); + if (stat_ret && errno != ENOENT) + return error_errno("%s", old_name); + } + + if (state->check_index && !previous) { + int pos = cache_name_pos(old_name, strlen(old_name)); + if (pos < 0) { + if (patch->is_new < 0) + goto is_new; + return error(_("%s: does not exist in index"), old_name); + } + *ce = active_cache[pos]; + if (stat_ret < 0) { + if (checkout_target(&the_index, *ce, st)) + return -1; + } + if (!state->cached && verify_index_match(*ce, st)) + return error(_("%s: does not match index"), old_name); + if (state->cached) + st_mode = (*ce)->ce_mode; + } else if (stat_ret < 0) { + if (patch->is_new < 0) + goto is_new; + return error_errno("%s", old_name); + } + + if (!state->cached && !previous) + st_mode = ce_mode_from_stat(*ce, st->st_mode); + + if (patch->is_new < 0) + patch->is_new = 0; + if (!patch->old_mode) + patch->old_mode = st_mode; + if ((st_mode ^ patch->old_mode) & S_IFMT) + return error(_("%s: wrong type"), old_name); + if (st_mode != patch->old_mode) + warning(_("%s has type %o, expected %o"), + old_name, st_mode, patch->old_mode); + if (!patch->new_mode && !patch->is_delete) + patch->new_mode = st_mode; + return 0; + + is_new: + patch->is_new = 1; + patch->is_delete = 0; + free(patch->old_name); + patch->old_name = NULL; + return 0; +} + + +#define EXISTS_IN_INDEX 1 +#define EXISTS_IN_WORKTREE 2 + +static int check_to_create(struct apply_state *state, + const char *new_name, + int ok_if_exists) +{ + struct stat nst; + + if (state->check_index && + cache_name_pos(new_name, strlen(new_name)) >= 0 && + !ok_if_exists) + return EXISTS_IN_INDEX; + if (state->cached) + return 0; + + if (!lstat(new_name, &nst)) { + if (S_ISDIR(nst.st_mode) || ok_if_exists) + return 0; + /* + * A leading component of new_name might be a symlink + * that is going to be removed with this patch, but + * still pointing at somewhere that has the path. + * In such a case, path "new_name" does not exist as + * far as git is concerned. + */ + if (has_symlink_leading_path(new_name, strlen(new_name))) + return 0; + + return EXISTS_IN_WORKTREE; + } else if ((errno != ENOENT) && (errno != ENOTDIR)) { + return error_errno("%s", new_name); + } + return 0; +} + +static uintptr_t register_symlink_changes(struct apply_state *state, + const char *path, + uintptr_t what) +{ + struct string_list_item *ent; + + ent = string_list_lookup(&state->symlink_changes, path); + if (!ent) { + ent = string_list_insert(&state->symlink_changes, path); + ent->util = (void *)0; + } + ent->util = (void *)(what | ((uintptr_t)ent->util)); + return (uintptr_t)ent->util; +} + +static uintptr_t check_symlink_changes(struct apply_state *state, const char *path) +{ + struct string_list_item *ent; + + ent = string_list_lookup(&state->symlink_changes, path); + if (!ent) + return 0; + return (uintptr_t)ent->util; +} + +static void prepare_symlink_changes(struct apply_state *state, struct patch *patch) +{ + for ( ; patch; patch = patch->next) { + if ((patch->old_name && S_ISLNK(patch->old_mode)) && + (patch->is_rename || patch->is_delete)) + /* the symlink at patch->old_name is removed */ + register_symlink_changes(state, patch->old_name, APPLY_SYMLINK_GOES_AWAY); + + if (patch->new_name && S_ISLNK(patch->new_mode)) + /* the symlink at patch->new_name is created or remains */ + register_symlink_changes(state, patch->new_name, APPLY_SYMLINK_IN_RESULT); + } +} + +static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *name) +{ + do { + unsigned int change; + + while (--name->len && name->buf[name->len] != '/') + ; /* scan backwards */ + if (!name->len) + break; + name->buf[name->len] = '\0'; + change = check_symlink_changes(state, name->buf); + if (change & APPLY_SYMLINK_IN_RESULT) + return 1; + if (change & APPLY_SYMLINK_GOES_AWAY) + /* + * This cannot be "return 0", because we may + * see a new one created at a higher level. + */ + continue; + + /* otherwise, check the preimage */ + if (state->check_index) { + struct cache_entry *ce; + + ce = cache_file_exists(name->buf, name->len, ignore_case); + if (ce && S_ISLNK(ce->ce_mode)) + return 1; + } else { + struct stat st; + if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode)) + return 1; + } + } while (1); + return 0; +} + +static int path_is_beyond_symlink(struct apply_state *state, const char *name_) +{ + int ret; + struct strbuf name = STRBUF_INIT; + + assert(*name_ != '\0'); + strbuf_addstr(&name, name_); + ret = path_is_beyond_symlink_1(state, &name); + strbuf_release(&name); + + return ret; +} + +static int check_unsafe_path(struct patch *patch) +{ + const char *old_name = NULL; + const char *new_name = NULL; + if (patch->is_delete) + old_name = patch->old_name; + else if (!patch->is_new && !patch->is_copy) + old_name = patch->old_name; + if (!patch->is_delete) + new_name = patch->new_name; + + if (old_name && !verify_path(old_name)) + return error(_("invalid path '%s'"), old_name); + if (new_name && !verify_path(new_name)) + return error(_("invalid path '%s'"), new_name); + return 0; +} + +/* + * Check and apply the patch in-core; leave the result in patch->result + * for the caller to write it out to the final destination. + */ +static int check_patch(struct apply_state *state, struct patch *patch) +{ + struct stat st; + const char *old_name = patch->old_name; + const char *new_name = patch->new_name; + const char *name = old_name ? old_name : new_name; + struct cache_entry *ce = NULL; + struct patch *tpatch; + int ok_if_exists; + int status; + + patch->rejected = 1; /* we will drop this after we succeed */ + + status = check_preimage(state, patch, &ce, &st); + if (status) + return status; + old_name = patch->old_name; + + /* + * A type-change diff is always split into a patch to delete + * old, immediately followed by a patch to create new (see + * diff.c::run_diff()); in such a case it is Ok that the entry + * to be deleted by the previous patch is still in the working + * tree and in the index. + * + * A patch to swap-rename between A and B would first rename A + * to B and then rename B to A. While applying the first one, + * the presence of B should not stop A from getting renamed to + * B; ask to_be_deleted() about the later rename. Removal of + * B and rename from A to B is handled the same way by asking + * was_deleted(). + */ + if ((tpatch = in_fn_table(state, new_name)) && + (was_deleted(tpatch) || to_be_deleted(tpatch))) + ok_if_exists = 1; + else + ok_if_exists = 0; + + if (new_name && + ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) { + int err = check_to_create(state, new_name, ok_if_exists); + + if (err && state->threeway) { + patch->direct_to_threeway = 1; + } else switch (err) { + case 0: + break; /* happy */ + case EXISTS_IN_INDEX: + return error(_("%s: already exists in index"), new_name); + break; + case EXISTS_IN_WORKTREE: + return error(_("%s: already exists in working directory"), + new_name); + default: + return err; + } + + if (!patch->new_mode) { + if (0 < patch->is_new) + patch->new_mode = S_IFREG | 0644; + else + patch->new_mode = patch->old_mode; + } + } + + if (new_name && old_name) { + int same = !strcmp(old_name, new_name); + if (!patch->new_mode) + patch->new_mode = patch->old_mode; + if ((patch->old_mode ^ patch->new_mode) & S_IFMT) { + if (same) + return error(_("new mode (%o) of %s does not " + "match old mode (%o)"), + patch->new_mode, new_name, + patch->old_mode); + else + return error(_("new mode (%o) of %s does not " + "match old mode (%o) of %s"), + patch->new_mode, new_name, + patch->old_mode, old_name); + } + } + + if (!state->unsafe_paths && check_unsafe_path(patch)) + return -128; + + /* + * An attempt to read from or delete a path that is beyond a + * symbolic link will be prevented by load_patch_target() that + * is called at the beginning of apply_data() so we do not + * have to worry about a patch marked with "is_delete" bit + * here. We however need to make sure that the patch result + * is not deposited to a path that is beyond a symbolic link + * here. + */ + if (!patch->is_delete && path_is_beyond_symlink(state, patch->new_name)) + return error(_("affected file '%s' is beyond a symbolic link"), + patch->new_name); + + if (apply_data(state, patch, &st, ce) < 0) + return error(_("%s: patch does not apply"), name); + patch->rejected = 0; + return 0; +} + +static int check_patch_list(struct apply_state *state, struct patch *patch) +{ + int err = 0; + + prepare_symlink_changes(state, patch); + prepare_fn_table(state, patch); + while (patch) { + int res; + if (state->apply_verbosity > verbosity_normal) + say_patch_name(stderr, + _("Checking patch %s..."), patch); + res = check_patch(state, patch); + if (res == -128) + return -128; + err |= res; + patch = patch->next; + } + return err; +} + +static int read_apply_cache(struct apply_state *state) +{ + if (state->index_file) + return read_cache_from(state->index_file); + else + return read_cache(); +} + +/* This function tries to read the object name from the current index */ +static int get_current_oid(struct apply_state *state, const char *path, + struct object_id *oid) +{ + int pos; + + if (read_apply_cache(state) < 0) + return -1; + pos = cache_name_pos(path, strlen(path)); + if (pos < 0) + return -1; + oidcpy(oid, &active_cache[pos]->oid); + return 0; +} + +static int preimage_oid_in_gitlink_patch(struct patch *p, struct object_id *oid) +{ + /* + * A usable gitlink patch has only one fragment (hunk) that looks like: + * @@ -1 +1 @@ + * -Subproject commit <old sha1> + * +Subproject commit <new sha1> + * or + * @@ -1 +0,0 @@ + * -Subproject commit <old sha1> + * for a removal patch. + */ + struct fragment *hunk = p->fragments; + static const char heading[] = "-Subproject commit "; + char *preimage; + + if (/* does the patch have only one hunk? */ + hunk && !hunk->next && + /* is its preimage one line? */ + hunk->oldpos == 1 && hunk->oldlines == 1 && + /* does preimage begin with the heading? */ + (preimage = memchr(hunk->patch, '\n', hunk->size)) != NULL && + starts_with(++preimage, heading) && + /* does it record full SHA-1? */ + !get_oid_hex(preimage + sizeof(heading) - 1, oid) && + preimage[sizeof(heading) + GIT_SHA1_HEXSZ - 1] == '\n' && + /* does the abbreviated name on the index line agree with it? */ + starts_with(preimage + sizeof(heading) - 1, p->old_sha1_prefix)) + return 0; /* it all looks fine */ + + /* we may have full object name on the index line */ + return get_oid_hex(p->old_sha1_prefix, oid); +} + +/* Build an index that contains the just the files needed for a 3way merge */ +static int build_fake_ancestor(struct apply_state *state, struct patch *list) +{ + struct patch *patch; + struct index_state result = { NULL }; + static struct lock_file lock; + int res; + + /* Once we start supporting the reverse patch, it may be + * worth showing the new sha1 prefix, but until then... + */ + for (patch = list; patch; patch = patch->next) { + struct object_id oid; + struct cache_entry *ce; + const char *name; + + name = patch->old_name ? patch->old_name : patch->new_name; + if (0 < patch->is_new) + continue; + + if (S_ISGITLINK(patch->old_mode)) { + if (!preimage_oid_in_gitlink_patch(patch, &oid)) + ; /* ok, the textual part looks sane */ + else + return error("sha1 information is lacking or " + "useless for submodule %s", name); + } else if (!get_sha1_blob(patch->old_sha1_prefix, oid.hash)) { + ; /* ok */ + } else if (!patch->lines_added && !patch->lines_deleted) { + /* mode-only change: update the current */ + if (get_current_oid(state, patch->old_name, &oid)) + return error("mode change for %s, which is not " + "in current HEAD", name); + } else + return error("sha1 information is lacking or useless " + "(%s).", name); + + ce = make_cache_entry(patch->old_mode, oid.hash, name, 0, 0); + if (!ce) + return error(_("make_cache_entry failed for path '%s'"), + name); + if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD)) { + free(ce); + return error("Could not add %s to temporary index", + name); + } + } + + hold_lock_file_for_update(&lock, state->fake_ancestor, LOCK_DIE_ON_ERROR); + res = write_locked_index(&result, &lock, COMMIT_LOCK); + discard_index(&result); + + if (res) + return error("Could not write temporary index to %s", + state->fake_ancestor); + + return 0; + } + + static void stat_patch_list(struct apply_state *state, struct patch *patch) + { + int files, adds, dels; + + for (files = adds = dels = 0 ; patch ; patch = patch->next) { + files++; + adds += patch->lines_added; + dels += patch->lines_deleted; + show_stats(state, patch); + } + + print_stat_summary(stdout, files, adds, dels); + } + + static void numstat_patch_list(struct apply_state *state, + struct patch *patch) + { + for ( ; patch; patch = patch->next) { + const char *name; + name = patch->new_name ? patch->new_name : patch->old_name; + if (patch->is_binary) + printf("-\t-\t"); + else + printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); + write_name_quoted(name, stdout, state->line_termination); + } + } + + static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name) + { + if (mode) + printf(" %s mode %06o %s\n", newdelete, mode, name); + else + printf(" %s %s\n", newdelete, name); + } + + static void show_mode_change(struct patch *p, int show_name) + { + if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) { + if (show_name) + printf(" mode change %06o => %06o %s\n", + p->old_mode, p->new_mode, p->new_name); + else + printf(" mode change %06o => %06o\n", + p->old_mode, p->new_mode); + } + } + + static void show_rename_copy(struct patch *p) + { + const char *renamecopy = p->is_rename ? "rename" : "copy"; + const char *old, *new; + + /* Find common prefix */ + old = p->old_name; + new = p->new_name; + while (1) { + const char *slash_old, *slash_new; + slash_old = strchr(old, '/'); + slash_new = strchr(new, '/'); + if (!slash_old || + !slash_new || + slash_old - old != slash_new - new || + memcmp(old, new, slash_new - new)) + break; + old = slash_old + 1; + new = slash_new + 1; + } + /* p->old_name thru old is the common prefix, and old and new + * through the end of names are renames + */ + if (old != p->old_name) + printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy, + (int)(old - p->old_name), p->old_name, + old, new, p->score); + else + printf(" %s %s => %s (%d%%)\n", renamecopy, + p->old_name, p->new_name, p->score); + show_mode_change(p, 0); + } + + static void summary_patch_list(struct patch *patch) + { + struct patch *p; + + for (p = patch; p; p = p->next) { + if (p->is_new) + show_file_mode_name("create", p->new_mode, p->new_name); + else if (p->is_delete) + show_file_mode_name("delete", p->old_mode, p->old_name); + else { + if (p->is_rename || p->is_copy) + show_rename_copy(p); + else { + if (p->score) { + printf(" rewrite %s (%d%%)\n", + p->new_name, p->score); + show_mode_change(p, 0); + } + else + show_mode_change(p, 1); + } + } + } + } + + static void patch_stats(struct apply_state *state, struct patch *patch) + { + int lines = patch->lines_added + patch->lines_deleted; + + if (lines > state->max_change) + state->max_change = lines; + if (patch->old_name) { + int len = quote_c_style(patch->old_name, NULL, NULL, 0); + if (!len) + len = strlen(patch->old_name); + if (len > state->max_len) + state->max_len = len; + } + if (patch->new_name) { + int len = quote_c_style(patch->new_name, NULL, NULL, 0); + if (!len) + len = strlen(patch->new_name); + if (len > state->max_len) + state->max_len = len; + } + } + + static int remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty) + { + if (state->update_index) { + if (remove_file_from_cache(patch->old_name) < 0) + return error(_("unable to remove %s from index"), patch->old_name); + } + if (!state->cached) { + if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) { + remove_path(patch->old_name); + } + } + return 0; + } + + static int add_index_file(struct apply_state *state, + const char *path, + unsigned mode, + void *buf, + unsigned long size) + { + struct stat st; + struct cache_entry *ce; + int namelen = strlen(path); + unsigned ce_size = cache_entry_size(namelen); + + if (!state->update_index) + return 0; + + ce = xcalloc(1, ce_size); + memcpy(ce->name, path, namelen); + ce->ce_mode = create_ce_mode(mode); + ce->ce_flags = create_ce_flags(0); + ce->ce_namelen = namelen; + if (S_ISGITLINK(mode)) { + const char *s; + + if (!skip_prefix(buf, "Subproject commit ", &s) || + get_oid_hex(s, &ce->oid)) { + free(ce); + return error(_("corrupt patch for submodule %s"), path); + } + } else { + if (!state->cached) { + if (lstat(path, &st) < 0) { + free(ce); + return error_errno(_("unable to stat newly " + "created file '%s'"), + path); + } + fill_stat_cache_info(ce, &st); + } + if (write_sha1_file(buf, size, blob_type, ce->oid.hash) < 0) { + free(ce); + return error(_("unable to create backing store " + "for newly created file %s"), path); + } + } + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) { + free(ce); + return error(_("unable to add cache entry for %s"), path); + } + + return 0; +} + +/* + * Returns: + * -1 if an unrecoverable error happened + * 0 if everything went well + * 1 if a recoverable error happened + */ +static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size) +{ + int fd, res; + struct strbuf nbuf = STRBUF_INIT; + + if (S_ISGITLINK(mode)) { + struct stat st; + if (!lstat(path, &st) && S_ISDIR(st.st_mode)) + return 0; + return !!mkdir(path, 0777); + } + + if (has_symlinks && S_ISLNK(mode)) + /* Although buf:size is counted string, it also is NUL + * terminated. + */ + return !!symlink(buf, path); + + fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); + if (fd < 0) + return 1; + + if (convert_to_working_tree(path, buf, size, &nbuf)) { + size = nbuf.len; + buf = nbuf.buf; + } + + res = write_in_full(fd, buf, size) < 0; + if (res) + error_errno(_("failed to write to '%s'"), path); + strbuf_release(&nbuf); + + if (close(fd) < 0 && !res) + return error_errno(_("closing file '%s'"), path); + + return res ? -1 : 0; +} + +/* + * We optimistically assume that the directories exist, + * which is true 99% of the time anyway. If they don't, + * we create them and try again. + * + * Returns: + * -1 on error + * 0 otherwise + */ +static int create_one_file(struct apply_state *state, + char *path, + unsigned mode, + const char *buf, + unsigned long size) +{ + int res; + + if (state->cached) + return 0; + + res = try_create_file(path, mode, buf, size); + if (res < 0) + return -1; + if (!res) + return 0; + + if (errno == ENOENT) { + if (safe_create_leading_directories(path)) + return 0; + res = try_create_file(path, mode, buf, size); + if (res < 0) + return -1; + if (!res) + return 0; + } + + if (errno == EEXIST || errno == EACCES) { + /* We may be trying to create a file where a directory + * used to be. + */ + struct stat st; + if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path))) + errno = EEXIST; + } + + if (errno == EEXIST) { + unsigned int nr = getpid(); + + for (;;) { + char newpath[PATH_MAX]; + mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr); + res = try_create_file(newpath, mode, buf, size); + if (res < 0) + return -1; + if (!res) { + if (!rename(newpath, path)) + return 0; + unlink_or_warn(newpath); + break; + } + if (errno != EEXIST) + break; + ++nr; + } + } + return error_errno(_("unable to write file '%s' mode %o"), + path, mode); +} + +static int add_conflicted_stages_file(struct apply_state *state, + struct patch *patch) +{ + int stage, namelen; + unsigned ce_size, mode; + struct cache_entry *ce; + + if (!state->update_index) + return 0; + namelen = strlen(patch->new_name); + ce_size = cache_entry_size(namelen); + mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644); + + remove_file_from_cache(patch->new_name); + for (stage = 1; stage < 4; stage++) { + if (is_null_oid(&patch->threeway_stage[stage - 1])) + continue; + ce = xcalloc(1, ce_size); + memcpy(ce->name, patch->new_name, namelen); + ce->ce_mode = create_ce_mode(mode); + ce->ce_flags = create_ce_flags(stage); + ce->ce_namelen = namelen; + oidcpy(&ce->oid, &patch->threeway_stage[stage - 1]); + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) { + free(ce); + return error(_("unable to add cache entry for %s"), + patch->new_name); + } + } + + return 0; +} + +static int create_file(struct apply_state *state, struct patch *patch) +{ + char *path = patch->new_name; + unsigned mode = patch->new_mode; + unsigned long size = patch->resultsize; + char *buf = patch->result; + + if (!mode) + mode = S_IFREG | 0644; + if (create_one_file(state, path, mode, buf, size)) + return -1; + + if (patch->conflicted_threeway) + return add_conflicted_stages_file(state, patch); + else + return add_index_file(state, path, mode, buf, size); +} + +/* phase zero is to remove, phase one is to create */ +static int write_out_one_result(struct apply_state *state, + struct patch *patch, + int phase) +{ + if (patch->is_delete > 0) { + if (phase == 0) + return remove_file(state, patch, 1); + return 0; + } + if (patch->is_new > 0 || patch->is_copy) { + if (phase == 1) + return create_file(state, patch); + return 0; + } + /* + * Rename or modification boils down to the same + * thing: remove the old, write the new + */ + if (phase == 0) + return remove_file(state, patch, patch->is_rename); + if (phase == 1) + return create_file(state, patch); + return 0; +} + +static int write_out_one_reject(struct apply_state *state, struct patch *patch) +{ + FILE *rej; + char namebuf[PATH_MAX]; + struct fragment *frag; + int cnt = 0; + struct strbuf sb = STRBUF_INIT; + + for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { + if (!frag->rejected) + continue; + cnt++; + } + + if (!cnt) { + if (state->apply_verbosity > verbosity_normal) + say_patch_name(stderr, + _("Applied patch %s cleanly."), patch); + return 0; + } + + /* This should not happen, because a removal patch that leaves + * contents are marked "rejected" at the patch level. + */ + if (!patch->new_name) + die(_("internal error")); + + /* Say this even without --verbose */ + strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...", + "Applying patch %%s with %d rejects...", + cnt), + cnt); + if (state->apply_verbosity > verbosity_silent) + say_patch_name(stderr, sb.buf, patch); + strbuf_release(&sb); + + cnt = strlen(patch->new_name); + if (ARRAY_SIZE(namebuf) <= cnt + 5) { + cnt = ARRAY_SIZE(namebuf) - 5; + warning(_("truncating .rej filename to %.*s.rej"), + cnt - 1, patch->new_name); + } + memcpy(namebuf, patch->new_name, cnt); + memcpy(namebuf + cnt, ".rej", 5); + + rej = fopen(namebuf, "w"); + if (!rej) + return error_errno(_("cannot open %s"), namebuf); + + /* Normal git tools never deal with .rej, so do not pretend + * this is a git patch by saying --git or giving extended + * headers. While at it, maybe please "kompare" that wants + * the trailing TAB and some garbage at the end of line ;-). + */ + fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", + patch->new_name, patch->new_name); + for (cnt = 1, frag = patch->fragments; + frag; + cnt++, frag = frag->next) { + if (!frag->rejected) { + if (state->apply_verbosity > verbosity_silent) + fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt); + continue; + } + if (state->apply_verbosity > verbosity_silent) + fprintf_ln(stderr, _("Rejected hunk #%d."), cnt); + fprintf(rej, "%.*s", frag->size, frag->patch); + if (frag->patch[frag->size-1] != '\n') + fputc('\n', rej); + } + fclose(rej); + return -1; +} + +/* + * Returns: + * -1 if an error happened + * 0 if the patch applied cleanly + * 1 if the patch did not apply cleanly + */ +static int write_out_results(struct apply_state *state, struct patch *list) +{ + int phase; + int errs = 0; + struct patch *l; + struct string_list cpath = STRING_LIST_INIT_DUP; + + for (phase = 0; phase < 2; phase++) { + l = list; + while (l) { + if (l->rejected) + errs = 1; + else { + if (write_out_one_result(state, l, phase)) { + string_list_clear(&cpath, 0); + return -1; + } + if (phase == 1) { + if (write_out_one_reject(state, l)) + errs = 1; + if (l->conflicted_threeway) { + string_list_append(&cpath, l->new_name); + errs = 1; + } + } + } + l = l->next; + } + } + + if (cpath.nr) { + struct string_list_item *item; + + string_list_sort(&cpath); + if (state->apply_verbosity > verbosity_silent) { + for_each_string_list_item(item, &cpath) + fprintf(stderr, "U %s\n", item->string); + } + string_list_clear(&cpath, 0); + + rerere(0); + } + + return errs; +} + +/* + * Try to apply a patch. + * + * Returns: + * -128 if a bad error happened (like patch unreadable) + * -1 if patch did not apply and user cannot deal with it + * 0 if the patch applied + * 1 if the patch did not apply but user might fix it + */ +static int apply_patch(struct apply_state *state, + int fd, + const char *filename, + int options) +{ + size_t offset; + struct strbuf buf = STRBUF_INIT; /* owns the patch text */ + struct patch *list = NULL, **listp = &list; + int skipped_patch = 0; + int res = 0; + + state->patch_input_file = filename; + if (read_patch_file(&buf, fd) < 0) + return -128; + offset = 0; + while (offset < buf.len) { + struct patch *patch; + int nr; + + patch = xcalloc(1, sizeof(*patch)); + patch->inaccurate_eof = !!(options & APPLY_OPT_INACCURATE_EOF); + patch->recount = !!(options & APPLY_OPT_RECOUNT); + nr = parse_chunk(state, buf.buf + offset, buf.len - offset, patch); + if (nr < 0) { + free_patch(patch); + if (nr == -128) { + res = -128; + goto end; + } + break; + } + if (state->apply_in_reverse) + reverse_patches(patch); + if (use_patch(state, patch)) { + patch_stats(state, patch); + *listp = patch; + listp = &patch->next; + } + else { + if (state->apply_verbosity > verbosity_normal) + say_patch_name(stderr, _("Skipped patch '%s'."), patch); + free_patch(patch); + skipped_patch++; + } + offset += nr; + } + + if (!list && !skipped_patch) { + error(_("unrecognized input")); + res = -128; + goto end; + } + + if (state->whitespace_error && (state->ws_error_action == die_on_ws_error)) + state->apply = 0; + + state->update_index = state->check_index && state->apply; + if (state->update_index && state->newfd < 0) { + if (state->index_file) + state->newfd = hold_lock_file_for_update(state->lock_file, + state->index_file, + LOCK_DIE_ON_ERROR); + else + state->newfd = hold_locked_index(state->lock_file, 1); + } + + if (state->check_index && read_apply_cache(state) < 0) { + error(_("unable to read index file")); + res = -128; + goto end; + } + + if (state->check || state->apply) { + int r = check_patch_list(state, list); + if (r == -128) { + res = -128; + goto end; + } + if (r < 0 && !state->apply_with_reject) { + res = -1; + goto end; + } + } + + if (state->apply) { + int write_res = write_out_results(state, list); + if (write_res < 0) { + res = -128; + goto end; + } + if (write_res > 0) { + /* with --3way, we still need to write the index out */ + res = state->apply_with_reject ? -1 : 1; + goto end; + } + } + + if (state->fake_ancestor && + build_fake_ancestor(state, list)) { + res = -128; + goto end; + } + + if (state->diffstat && state->apply_verbosity > verbosity_silent) + stat_patch_list(state, list); + + if (state->numstat && state->apply_verbosity > verbosity_silent) + numstat_patch_list(state, list); + + if (state->summary && state->apply_verbosity > verbosity_silent) + summary_patch_list(list); + +end: + free_patch_list(list); + strbuf_release(&buf); + string_list_clear(&state->fn_table, 0); + return res; +} + +static int apply_option_parse_exclude(const struct option *opt, + const char *arg, int unset) +{ + struct apply_state *state = opt->value; + add_name_limit(state, arg, 1); + return 0; +} + +static int apply_option_parse_include(const struct option *opt, + const char *arg, int unset) +{ + struct apply_state *state = opt->value; + add_name_limit(state, arg, 0); + state->has_include = 1; + return 0; +} + +static int apply_option_parse_p(const struct option *opt, + const char *arg, + int unset) +{ + struct apply_state *state = opt->value; + state->p_value = atoi(arg); + state->p_value_known = 1; + return 0; +} + +static int apply_option_parse_space_change(const struct option *opt, + const char *arg, int unset) +{ + struct apply_state *state = opt->value; + if (unset) + state->ws_ignore_action = ignore_ws_none; + else + state->ws_ignore_action = ignore_ws_change; + return 0; +} + +static int apply_option_parse_whitespace(const struct option *opt, + const char *arg, int unset) +{ + struct apply_state *state = opt->value; + state->whitespace_option = arg; + if (parse_whitespace_option(state, arg)) + exit(1); + return 0; +} + +static int apply_option_parse_directory(const struct option *opt, + const char *arg, int unset) +{ + struct apply_state *state = opt->value; + strbuf_reset(&state->root); + strbuf_addstr(&state->root, arg); + strbuf_complete(&state->root, '/'); + return 0; +} + +int apply_all_patches(struct apply_state *state, + int argc, + const char **argv, + int options) +{ + int i; + int res; + int errs = 0; + int read_stdin = 1; + + for (i = 0; i < argc; i++) { + const char *arg = argv[i]; + int fd; + + if (!strcmp(arg, "-")) { + res = apply_patch(state, 0, "<stdin>", options); + if (res < 0) + goto end; + errs |= res; + read_stdin = 0; + continue; + } else if (0 < state->prefix_length) + arg = prefix_filename(state->prefix, + state->prefix_length, + arg); + + fd = open(arg, O_RDONLY); + if (fd < 0) { + error(_("can't open patch '%s': %s"), arg, strerror(errno)); + res = -128; + goto end; + } + read_stdin = 0; + set_default_whitespace_mode(state); + res = apply_patch(state, fd, arg, options); + close(fd); + if (res < 0) + goto end; + errs |= res; + } + set_default_whitespace_mode(state); + if (read_stdin) { + res = apply_patch(state, 0, "<stdin>", options); + if (res < 0) + goto end; + errs |= res; + } + + if (state->whitespace_error) { + if (state->squelch_whitespace_errors && + state->squelch_whitespace_errors < state->whitespace_error) { + int squelched = + state->whitespace_error - state->squelch_whitespace_errors; + warning(Q_("squelched %d whitespace error", + "squelched %d whitespace errors", + squelched), + squelched); + } + if (state->ws_error_action == die_on_ws_error) { + error(Q_("%d line adds whitespace errors.", + "%d lines add whitespace errors.", + state->whitespace_error), + state->whitespace_error); + res = -128; + goto end; + } + if (state->applied_after_fixing_ws && state->apply) + warning("%d line%s applied after" + " fixing whitespace errors.", + state->applied_after_fixing_ws, + state->applied_after_fixing_ws == 1 ? "" : "s"); + else if (state->whitespace_error) + warning(Q_("%d line adds whitespace errors.", + "%d lines add whitespace errors.", + state->whitespace_error), + state->whitespace_error); + } + + if (state->update_index) { + res = write_locked_index(&the_index, state->lock_file, COMMIT_LOCK); + if (res) { + error(_("Unable to write new index file")); + res = -128; + goto end; + } + state->newfd = -1; + } + + res = !!errs; + +end: + if (state->newfd >= 0) { + rollback_lock_file(state->lock_file); + state->newfd = -1; + } + + if (state->apply_verbosity <= verbosity_silent) { + set_error_routine(state->saved_error_routine); + set_warn_routine(state->saved_warn_routine); + } + + if (res > -1) + return res; + return (res == -1 ? 1 : 128); +} + +int apply_parse_options(int argc, const char **argv, + struct apply_state *state, + int *force_apply, int *options, + const char * const *apply_usage) +{ + struct option builtin_apply_options[] = { + { OPTION_CALLBACK, 0, "exclude", state, N_("path"), + N_("don't apply changes matching the given path"), + 0, apply_option_parse_exclude }, + { OPTION_CALLBACK, 0, "include", state, N_("path"), + N_("apply changes matching the given path"), + 0, apply_option_parse_include }, + { OPTION_CALLBACK, 'p', NULL, state, N_("num"), + N_("remove <num> leading slashes from traditional diff paths"), + 0, apply_option_parse_p }, + OPT_BOOL(0, "no-add", &state->no_add, + N_("ignore additions made by the patch")), + OPT_BOOL(0, "stat", &state->diffstat, + N_("instead of applying the patch, output diffstat for the input")), + OPT_NOOP_NOARG(0, "allow-binary-replacement"), + OPT_NOOP_NOARG(0, "binary"), + OPT_BOOL(0, "numstat", &state->numstat, + N_("show number of added and deleted lines in decimal notation")), + OPT_BOOL(0, "summary", &state->summary, + N_("instead of applying the patch, output a summary for the input")), + OPT_BOOL(0, "check", &state->check, + N_("instead of applying the patch, see if the patch is applicable")), + OPT_BOOL(0, "index", &state->check_index, + N_("make sure the patch is applicable to the current index")), + OPT_BOOL(0, "cached", &state->cached, + N_("apply a patch without touching the working tree")), + OPT_BOOL(0, "unsafe-paths", &state->unsafe_paths, + N_("accept a patch that touches outside the working area")), + OPT_BOOL(0, "apply", force_apply, + N_("also apply the patch (use with --stat/--summary/--check)")), + OPT_BOOL('3', "3way", &state->threeway, + N_( "attempt three-way merge if a patch does not apply")), + OPT_FILENAME(0, "build-fake-ancestor", &state->fake_ancestor, + N_("build a temporary index based on embedded index information")), + /* Think twice before adding "--nul" synonym to this */ + OPT_SET_INT('z', NULL, &state->line_termination, + N_("paths are separated with NUL character"), '\0'), + OPT_INTEGER('C', NULL, &state->p_context, + N_("ensure at least <n> lines of context match")), + { OPTION_CALLBACK, 0, "whitespace", state, N_("action"), + N_("detect new or modified lines that have whitespace errors"), + 0, apply_option_parse_whitespace }, + { OPTION_CALLBACK, 0, "ignore-space-change", state, NULL, + N_("ignore changes in whitespace when finding context"), + PARSE_OPT_NOARG, apply_option_parse_space_change }, + { OPTION_CALLBACK, 0, "ignore-whitespace", state, NULL, + N_("ignore changes in whitespace when finding context"), + PARSE_OPT_NOARG, apply_option_parse_space_change }, + OPT_BOOL('R', "reverse", &state->apply_in_reverse, + N_("apply the patch in reverse")), + OPT_BOOL(0, "unidiff-zero", &state->unidiff_zero, + N_("don't expect at least one line of context")), + OPT_BOOL(0, "reject", &state->apply_with_reject, + N_("leave the rejected hunks in corresponding *.rej files")), + OPT_BOOL(0, "allow-overlap", &state->allow_overlap, + N_("allow overlapping hunks")), + OPT__VERBOSE(&state->apply_verbosity, N_("be verbose")), + OPT_BIT(0, "inaccurate-eof", options, + N_("tolerate incorrectly detected missing new-line at the end of file"), + APPLY_OPT_INACCURATE_EOF), + OPT_BIT(0, "recount", options, + N_("do not trust the line counts in the hunk headers"), + APPLY_OPT_RECOUNT), + { OPTION_CALLBACK, 0, "directory", state, N_("root"), + N_("prepend <root> to all filenames"), + 0, apply_option_parse_directory }, + OPT_END() + }; + + return parse_options(argc, argv, state->prefix, builtin_apply_options, apply_usage, 0); +} diff --git a/apply.h b/apply.h new file mode 100644 index 0000000000..b3d6783d55 --- /dev/null +++ b/apply.h @@ -0,0 +1,137 @@ +#ifndef APPLY_H +#define APPLY_H + +enum apply_ws_error_action { + nowarn_ws_error, + warn_on_ws_error, + die_on_ws_error, + correct_ws_error +}; + +enum apply_ws_ignore { + ignore_ws_none, + ignore_ws_change +}; + +enum apply_verbosity { + verbosity_silent = -1, + verbosity_normal = 0, + verbosity_verbose = 1 +}; + +/* + * We need to keep track of how symlinks in the preimage are + * manipulated by the patches. A patch to add a/b/c where a/b + * is a symlink should not be allowed to affect the directory + * the symlink points at, but if the same patch removes a/b, + * it is perfectly fine, as the patch removes a/b to make room + * to create a directory a/b so that a/b/c can be created. + * + * See also "struct string_list symlink_changes" in "struct + * apply_state". + */ +#define APPLY_SYMLINK_GOES_AWAY 01 +#define APPLY_SYMLINK_IN_RESULT 02 + +struct apply_state { + const char *prefix; + int prefix_length; + + /* These are lock_file related */ + struct lock_file *lock_file; + int newfd; + + /* These control what gets looked at and modified */ + int apply; /* this is not a dry-run */ + int cached; /* apply to the index only */ + int check; /* preimage must match working tree, don't actually apply */ + int check_index; /* preimage must match the indexed version */ + int update_index; /* check_index && apply */ + + /* These control cosmetic aspect of the output */ + int diffstat; /* just show a diffstat, and don't actually apply */ + int numstat; /* just show a numeric diffstat, and don't actually apply */ + int summary; /* just report creation, deletion, etc, and don't actually apply */ + + /* These boolean parameters control how the apply is done */ + int allow_overlap; + int apply_in_reverse; + int apply_with_reject; + int no_add; + int threeway; + int unidiff_zero; + int unsafe_paths; + + /* Other non boolean parameters */ + const char *index_file; + enum apply_verbosity apply_verbosity; + const char *fake_ancestor; + const char *patch_input_file; + int line_termination; + struct strbuf root; + int p_value; + int p_value_known; + unsigned int p_context; + + /* Exclude and include path parameters */ + struct string_list limit_by_name; + int has_include; + + /* Various "current state" */ + int linenr; /* current line number */ + struct string_list symlink_changes; /* we have to track symlinks */ + + /* + * For "diff-stat" like behaviour, we keep track of the biggest change + * we've seen, and the longest filename. That allows us to do simple + * scaling. + */ + int max_change; + int max_len; + + /* + * Records filenames that have been touched, in order to handle + * the case where more than one patches touch the same file. + */ + struct string_list fn_table; + + /* + * This is to save reporting routines before using + * set_error_routine() or set_warn_routine() to install muting + * routines when in verbosity_silent mode. + */ + void (*saved_error_routine)(const char *err, va_list params); + void (*saved_warn_routine)(const char *warn, va_list params); + + /* These control whitespace errors */ + enum apply_ws_error_action ws_error_action; + enum apply_ws_ignore ws_ignore_action; + const char *whitespace_option; + int whitespace_error; + int squelch_whitespace_errors; + int applied_after_fixing_ws; +}; + +extern int apply_parse_options(int argc, const char **argv, + struct apply_state *state, + int *force_apply, int *options, + const char * const *apply_usage); +extern int init_apply_state(struct apply_state *state, + const char *prefix, + struct lock_file *lock_file); +extern void clear_apply_state(struct apply_state *state); +extern int check_apply_state(struct apply_state *state, int force_apply); + +/* + * Some aspects of the apply behavior are controlled by the following + * bits in the "options" parameter passed to apply_all_patches(). + */ +#define APPLY_OPT_INACCURATE_EOF (1<<0) /* accept inaccurate eof */ +#define APPLY_OPT_RECOUNT (1<<1) /* accept inaccurate line count */ + +extern int apply_all_patches(struct apply_state *state, + int argc, + const char **argv, + int options); + +#endif @@ -25,7 +25,7 @@ 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 unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size); +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); diff --git a/builtin/am.c b/builtin/am.c index 739b34dcf2..6981f42ce9 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -28,6 +28,8 @@ #include "rerere.h" #include "prompt.h" #include "mailinfo.h" +#include "apply.h" +#include "string-list.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -108,7 +110,7 @@ struct am_state { size_t msg_len; /* when --rebasing, records the original commit the patch came from */ - unsigned char orig_commit[GIT_SHA1_RAWSZ]; + struct object_id orig_commit; /* number of digits in patch filename */ int prec; @@ -258,38 +260,29 @@ static int read_state_file(struct strbuf *sb, const struct am_state *state, } /** - * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE - * as a newly-allocated string. VALUE must be a quoted string, and the KEY must - * match `key`. Returns NULL on failure. - * - * This is used by read_author_script() to read the GIT_AUTHOR_* variables from - * the author-script. + * Take a series of KEY='VALUE' lines where VALUE part is + * sq-quoted, and append <KEY, VALUE> at the end of the string list */ -static char *read_shell_var(FILE *fp, const char *key) +static int parse_key_value_squoted(char *buf, struct string_list *list) { - struct strbuf sb = STRBUF_INIT; - const char *str; - - if (strbuf_getline_lf(&sb, fp)) - goto fail; - - if (!skip_prefix(sb.buf, key, &str)) - goto fail; - - if (!skip_prefix(str, "=", &str)) - goto fail; - - strbuf_remove(&sb, 0, str - sb.buf); - - str = sq_dequote(sb.buf); - if (!str) - goto fail; - - return strbuf_detach(&sb, NULL); - -fail: - strbuf_release(&sb); - return NULL; + while (*buf) { + struct string_list_item *item; + char *np; + char *cp = strchr(buf, '='); + if (!cp) + return -1; + np = strchrnul(cp, '\n'); + *cp++ = '\0'; + item = string_list_append(list, buf); + + buf = np + (*np == '\n'); + *np = '\0'; + cp = sq_dequote(cp); + if (!cp) + return -1; + item->util = xstrdup(cp); + } + return 0; } /** @@ -311,44 +304,39 @@ fail: static int read_author_script(struct am_state *state) { const char *filename = am_path(state, "author-script"); - FILE *fp; + struct strbuf buf = STRBUF_INIT; + struct string_list kv = STRING_LIST_INIT_DUP; + int retval = -1; /* assume failure */ + int fd; assert(!state->author_name); assert(!state->author_email); assert(!state->author_date); - fp = fopen(filename, "r"); - if (!fp) { + fd = open(filename, O_RDONLY); + if (fd < 0) { if (errno == ENOENT) return 0; die_errno(_("could not open '%s' for reading"), filename); } + strbuf_read(&buf, fd, 0); + close(fd); + if (parse_key_value_squoted(buf.buf, &kv)) + goto finish; - state->author_name = read_shell_var(fp, "GIT_AUTHOR_NAME"); - if (!state->author_name) { - fclose(fp); - return -1; - } - - state->author_email = read_shell_var(fp, "GIT_AUTHOR_EMAIL"); - if (!state->author_email) { - fclose(fp); - return -1; - } - - state->author_date = read_shell_var(fp, "GIT_AUTHOR_DATE"); - if (!state->author_date) { - fclose(fp); - return -1; - } - - if (fgetc(fp) != EOF) { - fclose(fp); - return -1; - } - - fclose(fp); - return 0; + if (kv.nr != 3 || + strcmp(kv.items[0].string, "GIT_AUTHOR_NAME") || + strcmp(kv.items[1].string, "GIT_AUTHOR_EMAIL") || + strcmp(kv.items[2].string, "GIT_AUTHOR_DATE")) + goto finish; + state->author_name = kv.items[0].util; + state->author_email = kv.items[1].util; + state->author_date = kv.items[2].util; + retval = 0; +finish: + string_list_clear(&kv, !!retval); + strbuf_release(&buf); + return retval; } /** @@ -428,8 +416,8 @@ static void am_load(struct am_state *state) read_commit_msg(state); if (read_state_file(&sb, state, "original-commit", 1) < 0) - hashclr(state->orig_commit); - else if (get_sha1_hex(sb.buf, state->orig_commit) < 0) + oidclr(&state->orig_commit); + else if (get_oid_hex(sb.buf, &state->orig_commit) < 0) die(_("could not parse %s"), am_path(state, "original-commit")); read_state_file(&sb, state, "threeway", 1); @@ -555,14 +543,14 @@ static int copy_notes_for_rebase(const struct am_state *state) fp = xfopen(am_path(state, "rewritten"), "r"); while (!strbuf_getline_lf(&sb, fp)) { - unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ]; + struct object_id from_obj, to_obj; if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) { ret = error(invalid_line, sb.buf); goto finish; } - if (get_sha1_hex(sb.buf, from_obj)) { + if (get_oid_hex(sb.buf, &from_obj)) { ret = error(invalid_line, sb.buf); goto finish; } @@ -572,14 +560,14 @@ static int copy_notes_for_rebase(const struct am_state *state) goto finish; } - if (get_sha1_hex(sb.buf + GIT_SHA1_HEXSZ + 1, to_obj)) { + if (get_oid_hex(sb.buf + GIT_SHA1_HEXSZ + 1, &to_obj)) { ret = error(invalid_line, sb.buf); goto finish; } - if (copy_note_for_rewrite(c, from_obj, to_obj)) + if (copy_note_for_rewrite(c, from_obj.hash, to_obj.hash)) ret = error(_("Failed to copy notes from '%s' to '%s'"), - sha1_to_hex(from_obj), sha1_to_hex(to_obj)); + oid_to_hex(&from_obj), oid_to_hex(&to_obj)); } finish: @@ -985,7 +973,7 @@ static int split_mail(struct am_state *state, enum patch_format patch_format, static void am_setup(struct am_state *state, enum patch_format patch_format, const char **paths, int keep_cr) { - unsigned char curr_head[GIT_SHA1_RAWSZ]; + struct object_id curr_head; const char *str; struct strbuf sb = STRBUF_INIT; @@ -1053,10 +1041,10 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, else write_state_text(state, "applying", ""); - if (!get_sha1("HEAD", curr_head)) { - write_state_text(state, "abort-safety", sha1_to_hex(curr_head)); + if (!get_oid("HEAD", &curr_head)) { + write_state_text(state, "abort-safety", oid_to_hex(&curr_head)); if (!state->rebasing) - update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, + update_ref_oid("am", "ORIG_HEAD", &curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR); } else { write_state_text(state, "abort-safety", ""); @@ -1081,7 +1069,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, */ static void am_next(struct am_state *state) { - unsigned char head[GIT_SHA1_RAWSZ]; + struct object_id head; free(state->author_name); state->author_name = NULL; @@ -1099,11 +1087,11 @@ static void am_next(struct am_state *state) unlink(am_path(state, "author-script")); unlink(am_path(state, "final-commit")); - hashclr(state->orig_commit); + oidclr(&state->orig_commit); unlink(am_path(state, "original-commit")); - if (!get_sha1("HEAD", head)) - write_state_text(state, "abort-safety", sha1_to_hex(head)); + if (!get_oid("HEAD", &head)) + write_state_text(state, "abort-safety", oid_to_hex(&head)); else write_state_text(state, "abort-safety", ""); @@ -1145,17 +1133,17 @@ static void refresh_and_write_cache(void) */ static int index_has_changes(struct strbuf *sb) { - unsigned char head[GIT_SHA1_RAWSZ]; + struct object_id head; int i; - if (!get_sha1_tree("HEAD", head)) { + if (!get_sha1_tree("HEAD", head.hash)) { struct diff_options opt; diff_setup(&opt); DIFF_OPT_SET(&opt, EXIT_WITH_STATUS); if (!sb) DIFF_OPT_SET(&opt, QUICK); - do_diff_cache(head, &opt); + do_diff_cache(head.hash, &opt); diffcore_std(&opt); for (i = 0; sb && i < diff_queued_diff.nr; i++) { if (i) @@ -1362,7 +1350,7 @@ finish: * Sets commit_id to the commit hash where the mail was generated from. * Returns 0 on success, -1 on failure. */ -static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail) +static int get_mail_commit_oid(struct object_id *commit_id, const char *mail) { struct strbuf sb = STRBUF_INIT; FILE *fp = xfopen(mail, "r"); @@ -1374,7 +1362,7 @@ static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail) if (!skip_prefix(sb.buf, "From ", &x)) return -1; - if (get_sha1_hex(x, commit_id) < 0) + if (get_oid_hex(x, commit_id) < 0) return -1; strbuf_release(&sb); @@ -1464,12 +1452,12 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm static void write_index_patch(const struct am_state *state) { struct tree *tree; - unsigned char head[GIT_SHA1_RAWSZ]; + struct object_id head; struct rev_info rev_info; FILE *fp; - if (!get_sha1_tree("HEAD", head)) - tree = lookup_tree(head); + if (!get_sha1_tree("HEAD", head.hash)) + tree = lookup_tree(head.hash); else tree = lookup_tree(EMPTY_TREE_SHA1_BIN); @@ -1499,19 +1487,19 @@ static void write_index_patch(const struct am_state *state) static int parse_mail_rebase(struct am_state *state, const char *mail) { struct commit *commit; - unsigned char commit_sha1[GIT_SHA1_RAWSZ]; + struct object_id commit_oid; - if (get_mail_commit_sha1(commit_sha1, mail) < 0) + if (get_mail_commit_oid(&commit_oid, mail) < 0) die(_("could not parse %s"), mail); - commit = lookup_commit_or_die(commit_sha1, mail); + commit = lookup_commit_or_die(commit_oid.hash, mail); get_commit_info(state, commit); write_commit_patch(state, commit); - hashcpy(state->orig_commit, commit_sha1); - write_state_text(state, "original-commit", sha1_to_hex(commit_sha1)); + oidcpy(&state->orig_commit, &commit_oid); + write_state_text(state, "original-commit", oid_to_hex(&commit_oid)); return 0; } @@ -1522,39 +1510,59 @@ static int parse_mail_rebase(struct am_state *state, const char *mail) */ static int run_apply(const struct am_state *state, const char *index_file) { - struct child_process cp = CHILD_PROCESS_INIT; - - cp.git_cmd = 1; - - if (index_file) - argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file); + struct argv_array apply_paths = ARGV_ARRAY_INIT; + struct argv_array apply_opts = ARGV_ARRAY_INIT; + struct apply_state apply_state; + int res, opts_left; + static struct lock_file lock_file; + int force_apply = 0; + int options = 0; + + if (init_apply_state(&apply_state, NULL, &lock_file)) + die("BUG: init_apply_state() failed"); + + argv_array_push(&apply_opts, "apply"); + argv_array_pushv(&apply_opts, state->git_apply_opts.argv); + + opts_left = apply_parse_options(apply_opts.argc, apply_opts.argv, + &apply_state, &force_apply, &options, + NULL); + + if (opts_left != 0) + die("unknown option passed through to git apply"); + + if (index_file) { + apply_state.index_file = index_file; + apply_state.cached = 1; + } else + apply_state.check_index = 1; /* * If we are allowed to fall back on 3-way merge, don't give false * errors during the initial attempt. */ - if (state->threeway && !index_file) { - cp.no_stdout = 1; - cp.no_stderr = 1; - } + if (state->threeway && !index_file) + apply_state.apply_verbosity = verbosity_silent; - argv_array_push(&cp.args, "apply"); + if (check_apply_state(&apply_state, force_apply)) + die("BUG: check_apply_state() failed"); - argv_array_pushv(&cp.args, state->git_apply_opts.argv); + argv_array_push(&apply_paths, am_path(state, "patch")); - if (index_file) - argv_array_push(&cp.args, "--cached"); - else - argv_array_push(&cp.args, "--index"); + res = apply_all_patches(&apply_state, apply_paths.argc, apply_paths.argv, options); - argv_array_push(&cp.args, am_path(state, "patch")); + argv_array_clear(&apply_paths); + argv_array_clear(&apply_opts); + clear_apply_state(&apply_state); - if (run_command(&cp)) - return -1; + if (res) + return res; - /* Reload index as git-apply will have modified it. */ - discard_cache(); - read_cache_from(index_file ? index_file : get_index_file()); + if (index_file) { + /* Reload index as apply_all_patches() will have modified it. */ + discard_cache(); + read_cache_from(index_file); + } return 0; } @@ -1665,9 +1673,8 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa */ static void do_commit(const struct am_state *state) { - unsigned char tree[GIT_SHA1_RAWSZ], parent[GIT_SHA1_RAWSZ], - commit[GIT_SHA1_RAWSZ]; - unsigned char *ptr; + struct object_id tree, parent, commit; + const struct object_id *old_oid; struct commit_list *parents = NULL; const char *reflog_msg, *author; struct strbuf sb = STRBUF_INIT; @@ -1675,14 +1682,14 @@ static void do_commit(const struct am_state *state) if (run_hook_le(NULL, "pre-applypatch", NULL)) exit(1); - if (write_cache_as_tree(tree, 0, NULL)) + if (write_cache_as_tree(tree.hash, 0, NULL)) die(_("git write-tree failed to write a tree")); - if (!get_sha1_commit("HEAD", parent)) { - ptr = parent; - commit_list_insert(lookup_commit(parent), &parents); + if (!get_sha1_commit("HEAD", parent.hash)) { + old_oid = &parent; + commit_list_insert(lookup_commit(parent.hash), &parents); } else { - ptr = NULL; + old_oid = NULL; say(state, stderr, _("applying to an empty history")); } @@ -1694,7 +1701,7 @@ static void do_commit(const struct am_state *state) setenv("GIT_COMMITTER_DATE", state->ignore_date ? "" : state->author_date, 1); - if (commit_tree(state->msg, state->msg_len, tree, parents, commit, + if (commit_tree(state->msg, state->msg_len, tree.hash, parents, commit.hash, author, state->sign_commit)) die(_("failed to write commit object")); @@ -1705,14 +1712,15 @@ static void do_commit(const struct am_state *state) strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg), state->msg); - update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR); + update_ref_oid(sb.buf, "HEAD", &commit, old_oid, 0, + UPDATE_REFS_DIE_ON_ERR); if (state->rebasing) { FILE *fp = xfopen(am_path(state, "rewritten"), "a"); - assert(!is_null_sha1(state->orig_commit)); - fprintf(fp, "%s ", sha1_to_hex(state->orig_commit)); - fprintf(fp, "%s\n", sha1_to_hex(commit)); + assert(!is_null_oid(&state->orig_commit)); + fprintf(fp, "%s ", oid_to_hex(&state->orig_commit)); + fprintf(fp, "%s\n", oid_to_hex(&commit)); fclose(fp); } @@ -2033,30 +2041,30 @@ static int merge_tree(struct tree *tree) * Clean the index without touching entries that are not modified between * `head` and `remote`. */ -static int clean_index(const unsigned char *head, const unsigned char *remote) +static int clean_index(const struct object_id *head, const struct object_id *remote) { struct tree *head_tree, *remote_tree, *index_tree; - unsigned char index[GIT_SHA1_RAWSZ]; + struct object_id index; - head_tree = parse_tree_indirect(head); + head_tree = parse_tree_indirect(head->hash); if (!head_tree) - return error(_("Could not parse object '%s'."), sha1_to_hex(head)); + return error(_("Could not parse object '%s'."), oid_to_hex(head)); - remote_tree = parse_tree_indirect(remote); + remote_tree = parse_tree_indirect(remote->hash); if (!remote_tree) - return error(_("Could not parse object '%s'."), sha1_to_hex(remote)); + return error(_("Could not parse object '%s'."), oid_to_hex(remote)); read_cache_unmerged(); if (fast_forward_to(head_tree, head_tree, 1)) return -1; - if (write_cache_as_tree(index, 0, NULL)) + if (write_cache_as_tree(index.hash, 0, NULL)) return -1; - index_tree = parse_tree_indirect(index); + index_tree = parse_tree_indirect(index.hash); if (!index_tree) - return error(_("Could not parse object '%s'."), sha1_to_hex(index)); + return error(_("Could not parse object '%s'."), oid_to_hex(&index)); if (fast_forward_to(index_tree, remote_tree, 0)) return -1; @@ -2084,14 +2092,14 @@ static void am_rerere_clear(void) */ static void am_skip(struct am_state *state) { - unsigned char head[GIT_SHA1_RAWSZ]; + struct object_id head; am_rerere_clear(); - if (get_sha1("HEAD", head)) - hashcpy(head, EMPTY_TREE_SHA1_BIN); + if (get_oid("HEAD", &head)) + hashcpy(head.hash, EMPTY_TREE_SHA1_BIN); - if (clean_index(head, head)) + if (clean_index(&head, &head)) die(_("failed to clean index")); am_next(state); @@ -2109,21 +2117,21 @@ static void am_skip(struct am_state *state) static int safe_to_abort(const struct am_state *state) { struct strbuf sb = STRBUF_INIT; - unsigned char abort_safety[GIT_SHA1_RAWSZ], head[GIT_SHA1_RAWSZ]; + struct object_id abort_safety, head; if (file_exists(am_path(state, "dirtyindex"))) return 0; if (read_state_file(&sb, state, "abort-safety", 1) > 0) { - if (get_sha1_hex(sb.buf, abort_safety)) + if (get_oid_hex(sb.buf, &abort_safety)) die(_("could not parse %s"), am_path(state, "abort_safety")); } else - hashclr(abort_safety); + oidclr(&abort_safety); - if (get_sha1("HEAD", head)) - hashclr(head); + if (get_oid("HEAD", &head)) + oidclr(&head); - if (!hashcmp(head, abort_safety)) + if (!oidcmp(&head, &abort_safety)) return 1; error(_("You seem to have moved HEAD since the last 'am' failure.\n" @@ -2137,7 +2145,7 @@ static int safe_to_abort(const struct am_state *state) */ static void am_abort(struct am_state *state) { - unsigned char curr_head[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ]; + struct object_id curr_head, orig_head; int has_curr_head, has_orig_head; char *curr_branch; @@ -2148,20 +2156,20 @@ static void am_abort(struct am_state *state) am_rerere_clear(); - curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL); - has_curr_head = !is_null_sha1(curr_head); + curr_branch = resolve_refdup("HEAD", 0, curr_head.hash, NULL); + has_curr_head = !is_null_oid(&curr_head); if (!has_curr_head) - hashcpy(curr_head, EMPTY_TREE_SHA1_BIN); + hashcpy(curr_head.hash, EMPTY_TREE_SHA1_BIN); - has_orig_head = !get_sha1("ORIG_HEAD", orig_head); + has_orig_head = !get_oid("ORIG_HEAD", &orig_head); if (!has_orig_head) - hashcpy(orig_head, EMPTY_TREE_SHA1_BIN); + hashcpy(orig_head.hash, EMPTY_TREE_SHA1_BIN); - clean_index(curr_head, orig_head); + clean_index(&curr_head, &orig_head); if (has_orig_head) - update_ref("am --abort", "HEAD", orig_head, - has_curr_head ? curr_head : NULL, 0, + update_ref_oid("am --abort", "HEAD", &orig_head, + has_curr_head ? &curr_head : NULL, 0, UPDATE_REFS_DIE_ON_ERR); else if (curr_branch) delete_ref(curr_branch, NULL, REF_NODEREF); @@ -2222,7 +2230,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) int in_progress; const char * const usage[] = { - N_("git am [<options>] [(<mbox>|<Maildir>)...]"), + N_("git am [<options>] [(<mbox> | <Maildir>)...]"), N_("git am [<options>] (--continue | --skip | --abort)"), NULL }; diff --git a/builtin/apply.c b/builtin/apply.c index 1a488f9e88..81b9a61c37 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1,4793 +1,16 @@ -/* - * apply.c - * - * Copyright (C) Linus Torvalds, 2005 - * - * This applies patches on top of some (arbitrary) version of the SCM. - * - */ #include "cache.h" -#include "lockfile.h" -#include "cache-tree.h" -#include "quote.h" -#include "blob.h" -#include "delta.h" #include "builtin.h" -#include "string-list.h" -#include "dir.h" -#include "diff.h" #include "parse-options.h" -#include "xdiff-interface.h" -#include "ll-merge.h" -#include "rerere.h" - -enum ws_error_action { - nowarn_ws_error, - warn_on_ws_error, - die_on_ws_error, - correct_ws_error -}; - - -enum ws_ignore { - ignore_ws_none, - ignore_ws_change -}; - -/* - * We need to keep track of how symlinks in the preimage are - * manipulated by the patches. A patch to add a/b/c where a/b - * is a symlink should not be allowed to affect the directory - * the symlink points at, but if the same patch removes a/b, - * it is perfectly fine, as the patch removes a/b to make room - * to create a directory a/b so that a/b/c can be created. - * - * See also "struct string_list symlink_changes" in "struct - * apply_state". - */ -#define SYMLINK_GOES_AWAY 01 -#define SYMLINK_IN_RESULT 02 - -struct apply_state { - const char *prefix; - int prefix_length; - - /* These are lock_file related */ - struct lock_file *lock_file; - int newfd; - - /* These control what gets looked at and modified */ - int apply; /* this is not a dry-run */ - int cached; /* apply to the index only */ - int check; /* preimage must match working tree, don't actually apply */ - int check_index; /* preimage must match the indexed version */ - int update_index; /* check_index && apply */ - - /* These control cosmetic aspect of the output */ - int diffstat; /* just show a diffstat, and don't actually apply */ - int numstat; /* just show a numeric diffstat, and don't actually apply */ - int summary; /* just report creation, deletion, etc, and don't actually apply */ - - /* These boolean parameters control how the apply is done */ - int allow_overlap; - int apply_in_reverse; - int apply_with_reject; - int apply_verbosely; - int no_add; - int threeway; - int unidiff_zero; - int unsafe_paths; - - /* Other non boolean parameters */ - const char *fake_ancestor; - const char *patch_input_file; - int line_termination; - struct strbuf root; - int p_value; - int p_value_known; - unsigned int p_context; - - /* Exclude and include path parameters */ - struct string_list limit_by_name; - int has_include; - - /* Various "current state" */ - int linenr; /* current line number */ - struct string_list symlink_changes; /* we have to track symlinks */ - - /* - * For "diff-stat" like behaviour, we keep track of the biggest change - * we've seen, and the longest filename. That allows us to do simple - * scaling. - */ - int max_change; - int max_len; - - /* - * Records filenames that have been touched, in order to handle - * the case where more than one patches touch the same file. - */ - struct string_list fn_table; - - /* These control whitespace errors */ - enum ws_error_action ws_error_action; - enum ws_ignore ws_ignore_action; - const char *whitespace_option; - int whitespace_error; - int squelch_whitespace_errors; - int applied_after_fixing_ws; -}; +#include "lockfile.h" +#include "apply.h" static const char * const apply_usage[] = { N_("git apply [<options>] [<patch>...]"), NULL }; -static void parse_whitespace_option(struct apply_state *state, const char *option) -{ - if (!option) { - state->ws_error_action = warn_on_ws_error; - return; - } - if (!strcmp(option, "warn")) { - state->ws_error_action = warn_on_ws_error; - return; - } - if (!strcmp(option, "nowarn")) { - state->ws_error_action = nowarn_ws_error; - return; - } - if (!strcmp(option, "error")) { - state->ws_error_action = die_on_ws_error; - return; - } - if (!strcmp(option, "error-all")) { - state->ws_error_action = die_on_ws_error; - state->squelch_whitespace_errors = 0; - return; - } - if (!strcmp(option, "strip") || !strcmp(option, "fix")) { - state->ws_error_action = correct_ws_error; - return; - } - die(_("unrecognized whitespace option '%s'"), option); -} - -static void parse_ignorewhitespace_option(struct apply_state *state, - const char *option) -{ - if (!option || !strcmp(option, "no") || - !strcmp(option, "false") || !strcmp(option, "never") || - !strcmp(option, "none")) { - state->ws_ignore_action = ignore_ws_none; - return; - } - if (!strcmp(option, "change")) { - state->ws_ignore_action = ignore_ws_change; - return; - } - die(_("unrecognized whitespace ignore option '%s'"), option); -} - -static void set_default_whitespace_mode(struct apply_state *state) -{ - if (!state->whitespace_option && !apply_default_whitespace) - state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error); -} - -/* - * This represents one "hunk" from a patch, starting with - * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The - * patch text is pointed at by patch, and its byte length - * is stored in size. leading and trailing are the number - * of context lines. - */ -struct fragment { - unsigned long leading, trailing; - unsigned long oldpos, oldlines; - unsigned long newpos, newlines; - /* - * 'patch' is usually borrowed from buf in apply_patch(), - * but some codepaths store an allocated buffer. - */ - const char *patch; - unsigned free_patch:1, - rejected:1; - int size; - int linenr; - struct fragment *next; -}; - -/* - * When dealing with a binary patch, we reuse "leading" field - * to store the type of the binary hunk, either deflated "delta" - * or deflated "literal". - */ -#define binary_patch_method leading -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 - -/* - * This represents a "patch" to a file, both metainfo changes - * such as creation/deletion, filemode and content changes represented - * as a series of fragments. - */ -struct patch { - char *new_name, *old_name, *def_name; - unsigned int old_mode, new_mode; - int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ - int rejected; - unsigned ws_rule; - int lines_added, lines_deleted; - int score; - unsigned int is_toplevel_relative:1; - unsigned int inaccurate_eof:1; - unsigned int is_binary:1; - unsigned int is_copy:1; - unsigned int is_rename:1; - unsigned int recount:1; - unsigned int conflicted_threeway:1; - unsigned int direct_to_threeway:1; - struct fragment *fragments; - char *result; - size_t resultsize; - char old_sha1_prefix[41]; - char new_sha1_prefix[41]; - struct patch *next; - - /* three-way fallback result */ - struct object_id threeway_stage[3]; -}; - -static void free_fragment_list(struct fragment *list) -{ - while (list) { - struct fragment *next = list->next; - if (list->free_patch) - free((char *)list->patch); - free(list); - list = next; - } -} - -static void free_patch(struct patch *patch) -{ - free_fragment_list(patch->fragments); - free(patch->def_name); - free(patch->old_name); - free(patch->new_name); - free(patch->result); - free(patch); -} - -static void free_patch_list(struct patch *list) -{ - while (list) { - struct patch *next = list->next; - free_patch(list); - list = next; - } -} - -/* - * A line in a file, len-bytes long (includes the terminating LF, - * except for an incomplete line at the end if the file ends with - * one), and its contents hashes to 'hash'. - */ -struct line { - size_t len; - unsigned hash : 24; - unsigned flag : 8; -#define LINE_COMMON 1 -#define LINE_PATCHED 2 -}; - -/* - * This represents a "file", which is an array of "lines". - */ -struct image { - char *buf; - size_t len; - size_t nr; - size_t alloc; - struct line *line_allocated; - struct line *line; -}; - -static uint32_t hash_line(const char *cp, size_t len) -{ - size_t i; - uint32_t h; - for (i = 0, h = 0; i < len; i++) { - if (!isspace(cp[i])) { - h = h * 3 + (cp[i] & 0xff); - } - } - return h; -} - -/* - * Compare lines s1 of length n1 and s2 of length n2, ignoring - * whitespace difference. Returns 1 if they match, 0 otherwise - */ -static int fuzzy_matchlines(const char *s1, size_t n1, - const char *s2, size_t n2) -{ - const char *last1 = s1 + n1 - 1; - const char *last2 = s2 + n2 - 1; - int result = 0; - - /* ignore line endings */ - while ((*last1 == '\r') || (*last1 == '\n')) - last1--; - while ((*last2 == '\r') || (*last2 == '\n')) - last2--; - - /* skip leading whitespaces, if both begin with whitespace */ - if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) { - while (isspace(*s1) && (s1 <= last1)) - s1++; - while (isspace(*s2) && (s2 <= last2)) - s2++; - } - /* early return if both lines are empty */ - if ((s1 > last1) && (s2 > last2)) - return 1; - while (!result) { - result = *s1++ - *s2++; - /* - * Skip whitespace inside. We check for whitespace on - * both buffers because we don't want "a b" to match - * "ab" - */ - if (isspace(*s1) && isspace(*s2)) { - while (isspace(*s1) && s1 <= last1) - s1++; - while (isspace(*s2) && s2 <= last2) - s2++; - } - /* - * If we reached the end on one side only, - * lines don't match - */ - if ( - ((s2 > last2) && (s1 <= last1)) || - ((s1 > last1) && (s2 <= last2))) - return 0; - if ((s1 > last1) && (s2 > last2)) - break; - } - - return !result; -} - -static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag) -{ - ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc); - img->line_allocated[img->nr].len = len; - img->line_allocated[img->nr].hash = hash_line(bol, len); - img->line_allocated[img->nr].flag = flag; - img->nr++; -} - -/* - * "buf" has the file contents to be patched (read from various sources). - * attach it to "image" and add line-based index to it. - * "image" now owns the "buf". - */ -static void prepare_image(struct image *image, char *buf, size_t len, - int prepare_linetable) -{ - const char *cp, *ep; - - memset(image, 0, sizeof(*image)); - image->buf = buf; - image->len = len; - - if (!prepare_linetable) - return; - - ep = image->buf + image->len; - cp = image->buf; - while (cp < ep) { - const char *next; - for (next = cp; next < ep && *next != '\n'; next++) - ; - if (next < ep) - next++; - add_line_info(image, cp, next - cp, 0); - cp = next; - } - image->line = image->line_allocated; -} - -static void clear_image(struct image *image) -{ - free(image->buf); - free(image->line_allocated); - memset(image, 0, sizeof(*image)); -} - -/* fmt must contain _one_ %s and no other substitution */ -static void say_patch_name(FILE *output, const char *fmt, struct patch *patch) -{ - struct strbuf sb = STRBUF_INIT; - - if (patch->old_name && patch->new_name && - strcmp(patch->old_name, patch->new_name)) { - quote_c_style(patch->old_name, &sb, NULL, 0); - strbuf_addstr(&sb, " => "); - quote_c_style(patch->new_name, &sb, NULL, 0); - } else { - const char *n = patch->new_name; - if (!n) - n = patch->old_name; - quote_c_style(n, &sb, NULL, 0); - } - fprintf(output, fmt, sb.buf); - fputc('\n', output); - strbuf_release(&sb); -} - -#define SLOP (16) - -static void read_patch_file(struct strbuf *sb, int fd) -{ - if (strbuf_read(sb, fd, 0) < 0) - die_errno("git apply: failed to read"); - - /* - * Make sure that we have some slop in the buffer - * so that we can do speculative "memcmp" etc, and - * see to it that it is NUL-filled. - */ - strbuf_grow(sb, SLOP); - memset(sb->buf + sb->len, 0, SLOP); -} - -static unsigned long linelen(const char *buffer, unsigned long size) -{ - unsigned long len = 0; - while (size--) { - len++; - if (*buffer++ == '\n') - break; - } - return len; -} - -static int is_dev_null(const char *str) -{ - return skip_prefix(str, "/dev/null", &str) && isspace(*str); -} - -#define TERM_SPACE 1 -#define TERM_TAB 2 - -static int name_terminate(int c, int terminate) -{ - if (c == ' ' && !(terminate & TERM_SPACE)) - return 0; - if (c == '\t' && !(terminate & TERM_TAB)) - return 0; - - return 1; -} - -/* remove double slashes to make --index work with such filenames */ -static char *squash_slash(char *name) -{ - int i = 0, j = 0; - - if (!name) - return NULL; - - while (name[i]) { - if ((name[j++] = name[i++]) == '/') - while (name[i] == '/') - i++; - } - name[j] = '\0'; - return name; -} - -static char *find_name_gnu(struct apply_state *state, - const char *line, - const char *def, - int p_value) -{ - struct strbuf name = STRBUF_INIT; - char *cp; - - /* - * Proposed "new-style" GNU patch/diff format; see - * http://marc.info/?l=git&m=112927316408690&w=2 - */ - if (unquote_c_style(&name, line, NULL)) { - strbuf_release(&name); - return NULL; - } - - for (cp = name.buf; p_value; p_value--) { - cp = strchr(cp, '/'); - if (!cp) { - strbuf_release(&name); - return NULL; - } - cp++; - } - - strbuf_remove(&name, 0, cp - name.buf); - if (state->root.len) - strbuf_insert(&name, 0, state->root.buf, state->root.len); - return squash_slash(strbuf_detach(&name, NULL)); -} - -static size_t sane_tz_len(const char *line, size_t len) -{ - const char *tz, *p; - - if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ') - return 0; - tz = line + len - strlen(" +0500"); - - if (tz[1] != '+' && tz[1] != '-') - return 0; - - for (p = tz + 2; p != line + len; p++) - if (!isdigit(*p)) - return 0; - - return line + len - tz; -} - -static size_t tz_with_colon_len(const char *line, size_t len) -{ - const char *tz, *p; - - if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':') - return 0; - tz = line + len - strlen(" +08:00"); - - if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-')) - return 0; - p = tz + 2; - if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || - !isdigit(*p++) || !isdigit(*p++)) - return 0; - - return line + len - tz; -} - -static size_t date_len(const char *line, size_t len) -{ - const char *date, *p; - - if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-') - return 0; - p = date = line + len - strlen("72-02-05"); - - if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' || - !isdigit(*p++) || !isdigit(*p++) || *p++ != '-' || - !isdigit(*p++) || !isdigit(*p++)) /* Not a date. */ - return 0; - - if (date - line >= strlen("19") && - isdigit(date[-1]) && isdigit(date[-2])) /* 4-digit year */ - date -= strlen("19"); - - return line + len - date; -} - -static size_t short_time_len(const char *line, size_t len) -{ - const char *time, *p; - - if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':') - return 0; - p = time = line + len - strlen(" 07:01:32"); - - /* Permit 1-digit hours? */ - if (*p++ != ' ' || - !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || - !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' || - !isdigit(*p++) || !isdigit(*p++)) /* Not a time. */ - return 0; - - return line + len - time; -} - -static size_t fractional_time_len(const char *line, size_t len) -{ - const char *p; - size_t n; - - /* Expected format: 19:41:17.620000023 */ - if (!len || !isdigit(line[len - 1])) - return 0; - p = line + len - 1; - - /* Fractional seconds. */ - while (p > line && isdigit(*p)) - p--; - if (*p != '.') - return 0; - - /* Hours, minutes, and whole seconds. */ - n = short_time_len(line, p - line); - if (!n) - return 0; - - return line + len - p + n; -} - -static size_t trailing_spaces_len(const char *line, size_t len) -{ - const char *p; - - /* Expected format: ' ' x (1 or more) */ - if (!len || line[len - 1] != ' ') - return 0; - - p = line + len; - while (p != line) { - p--; - if (*p != ' ') - return line + len - (p + 1); - } - - /* All spaces! */ - return len; -} - -static size_t diff_timestamp_len(const char *line, size_t len) -{ - const char *end = line + len; - size_t n; - - /* - * Posix: 2010-07-05 19:41:17 - * GNU: 2010-07-05 19:41:17.620000023 -0500 - */ - - if (!isdigit(end[-1])) - return 0; - - n = sane_tz_len(line, end - line); - if (!n) - n = tz_with_colon_len(line, end - line); - end -= n; - - n = short_time_len(line, end - line); - if (!n) - n = fractional_time_len(line, end - line); - end -= n; - - n = date_len(line, end - line); - if (!n) /* No date. Too bad. */ - return 0; - end -= n; - - if (end == line) /* No space before date. */ - return 0; - if (end[-1] == '\t') { /* Success! */ - end--; - return line + len - end; - } - if (end[-1] != ' ') /* No space before date. */ - return 0; - - /* Whitespace damage. */ - end -= trailing_spaces_len(line, end - line); - return line + len - end; -} - -static char *find_name_common(struct apply_state *state, - const char *line, - const char *def, - int p_value, - const char *end, - int terminate) -{ - int len; - const char *start = NULL; - - if (p_value == 0) - start = line; - while (line != end) { - char c = *line; - - if (!end && isspace(c)) { - if (c == '\n') - break; - if (name_terminate(c, terminate)) - break; - } - line++; - if (c == '/' && !--p_value) - start = line; - } - if (!start) - return squash_slash(xstrdup_or_null(def)); - len = line - start; - if (!len) - return squash_slash(xstrdup_or_null(def)); - - /* - * Generally we prefer the shorter name, especially - * if the other one is just a variation of that with - * something else tacked on to the end (ie "file.orig" - * or "file~"). - */ - if (def) { - int deflen = strlen(def); - if (deflen < len && !strncmp(start, def, deflen)) - return squash_slash(xstrdup(def)); - } - - if (state->root.len) { - char *ret = xstrfmt("%s%.*s", state->root.buf, len, start); - return squash_slash(ret); - } - - return squash_slash(xmemdupz(start, len)); -} - -static char *find_name(struct apply_state *state, - const char *line, - char *def, - int p_value, - int terminate) -{ - if (*line == '"') { - char *name = find_name_gnu(state, line, def, p_value); - if (name) - return name; - } - - return find_name_common(state, line, def, p_value, NULL, terminate); -} - -static char *find_name_traditional(struct apply_state *state, - const char *line, - char *def, - int p_value) -{ - size_t len; - size_t date_len; - - if (*line == '"') { - char *name = find_name_gnu(state, line, def, p_value); - if (name) - return name; - } - - len = strchrnul(line, '\n') - line; - date_len = diff_timestamp_len(line, len); - if (!date_len) - return find_name_common(state, line, def, p_value, NULL, TERM_TAB); - len -= date_len; - - 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. - */ -static int guess_p_value(struct apply_state *state, const char *nameline) -{ - char *name, *cp; - int val = -1; - - if (is_dev_null(nameline)) - return -1; - name = find_name_traditional(state, nameline, NULL, 0); - if (!name) - return -1; - cp = strchr(name, '/'); - if (!cp) - val = 0; - else if (state->prefix) { - /* - * Does it begin with "a/$our-prefix" and such? Then this is - * very likely to apply to our directory. - */ - if (!strncmp(name, state->prefix, state->prefix_length)) - val = count_slashes(state->prefix); - else { - cp++; - if (!strncmp(cp, state->prefix, state->prefix_length)) - val = count_slashes(state->prefix) + 1; - } - } - free(name); - return val; -} - -/* - * Does the ---/+++ line have the POSIX timestamp after the last HT? - * GNU diff puts epoch there to signal a creation/deletion event. Is - * this such a timestamp? - */ -static int has_epoch_timestamp(const char *nameline) -{ - /* - * We are only interested in epoch timestamp; any non-zero - * fraction cannot be one, hence "(\.0+)?" in the regexp below. - * For the same reason, the date must be either 1969-12-31 or - * 1970-01-01, and the seconds part must be "00". - */ - const char stamp_regexp[] = - "^(1969-12-31|1970-01-01)" - " " - "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" - " " - "([-+][0-2][0-9]:?[0-5][0-9])\n"; - const char *timestamp = NULL, *cp, *colon; - static regex_t *stamp; - regmatch_t m[10]; - int zoneoffset; - int hourminute; - int status; - - for (cp = nameline; *cp != '\n'; cp++) { - if (*cp == '\t') - timestamp = cp + 1; - } - if (!timestamp) - return 0; - if (!stamp) { - stamp = xmalloc(sizeof(*stamp)); - if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) { - warning(_("Cannot prepare timestamp regexp %s"), - stamp_regexp); - return 0; - } - } - - status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0); - if (status) { - if (status != REG_NOMATCH) - warning(_("regexec returned %d for input: %s"), - status, timestamp); - return 0; - } - - zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10); - if (*colon == ':') - zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10); - else - zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); - if (timestamp[m[3].rm_so] == '-') - zoneoffset = -zoneoffset; - - /* - * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31 - * (west of GMT) or 1970-01-01 (east of GMT) - */ - if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) || - (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10))) - return 0; - - hourminute = (strtol(timestamp + 11, NULL, 10) * 60 + - strtol(timestamp + 14, NULL, 10) - - zoneoffset); - - return ((zoneoffset < 0 && hourminute == 1440) || - (0 <= zoneoffset && !hourminute)); -} - -/* - * Get the name etc info from the ---/+++ lines of a traditional patch header - * - * FIXME! The end-of-filename heuristics are kind of screwy. For existing - * files, we can happily check the index for a match, but for creating a - * new file we should try to match whatever "patch" does. I have no idea. - */ -static void parse_traditional_patch(struct apply_state *state, - const char *first, - const char *second, - struct patch *patch) -{ - char *name; - - first += 4; /* skip "--- " */ - second += 4; /* skip "+++ " */ - if (!state->p_value_known) { - int p, q; - p = guess_p_value(state, first); - q = guess_p_value(state, second); - if (p < 0) p = q; - if (0 <= p && p == q) { - state->p_value = p; - state->p_value_known = 1; - } - } - if (is_dev_null(first)) { - patch->is_new = 1; - patch->is_delete = 0; - name = find_name_traditional(state, second, NULL, state->p_value); - patch->new_name = name; - } else if (is_dev_null(second)) { - patch->is_new = 0; - patch->is_delete = 1; - name = find_name_traditional(state, first, NULL, state->p_value); - patch->old_name = name; - } else { - char *first_name; - first_name = find_name_traditional(state, first, NULL, state->p_value); - name = find_name_traditional(state, second, first_name, state->p_value); - free(first_name); - if (has_epoch_timestamp(first)) { - patch->is_new = 1; - patch->is_delete = 0; - patch->new_name = name; - } else if (has_epoch_timestamp(second)) { - patch->is_new = 0; - patch->is_delete = 1; - patch->old_name = name; - } else { - patch->old_name = name; - patch->new_name = xstrdup_or_null(name); - } - } - if (!name) - die(_("unable to find filename in patch at line %d"), state->linenr); -} - -static int gitdiff_hdrend(struct apply_state *state, - const char *line, - struct patch *patch) -{ - return -1; -} - -/* - * We're anal about diff header consistency, to make - * sure that we don't end up having strange ambiguous - * patches floating around. - * - * As a result, gitdiff_{old|new}name() will check - * their names against any previous information, just - * to make sure.. - */ -#define DIFF_OLD_NAME 0 -#define DIFF_NEW_NAME 1 - -static void gitdiff_verify_name(struct apply_state *state, - const char *line, - int isnull, - char **name, - int side) -{ - if (!*name && !isnull) { - *name = find_name(state, line, NULL, state->p_value, TERM_TAB); - return; - } - - if (*name) { - int len = strlen(*name); - char *another; - if (isnull) - die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), - *name, state->linenr); - another = find_name(state, line, NULL, state->p_value, TERM_TAB); - if (!another || memcmp(another, *name, len + 1)) - die((side == DIFF_NEW_NAME) ? - _("git apply: bad git-diff - inconsistent new filename on line %d") : - _("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr); - free(another); - } else { - /* expect "/dev/null" */ - if (memcmp("/dev/null", line, 9) || line[9] != '\n') - die(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr); - } -} - -static int gitdiff_oldname(struct apply_state *state, - const char *line, - struct patch *patch) -{ - gitdiff_verify_name(state, line, - patch->is_new, &patch->old_name, - DIFF_OLD_NAME); - return 0; -} - -static int gitdiff_newname(struct apply_state *state, - const char *line, - struct patch *patch) -{ - gitdiff_verify_name(state, line, - patch->is_delete, &patch->new_name, - DIFF_NEW_NAME); - return 0; -} - -static int gitdiff_oldmode(struct apply_state *state, - const char *line, - struct patch *patch) -{ - patch->old_mode = strtoul(line, NULL, 8); - return 0; -} - -static int gitdiff_newmode(struct apply_state *state, - const char *line, - struct patch *patch) -{ - patch->new_mode = strtoul(line, NULL, 8); - return 0; -} - -static int gitdiff_delete(struct apply_state *state, - const char *line, - struct patch *patch) -{ - patch->is_delete = 1; - free(patch->old_name); - patch->old_name = xstrdup_or_null(patch->def_name); - return gitdiff_oldmode(state, line, patch); -} - -static int gitdiff_newfile(struct apply_state *state, - const char *line, - struct patch *patch) -{ - patch->is_new = 1; - free(patch->new_name); - patch->new_name = xstrdup_or_null(patch->def_name); - return gitdiff_newmode(state, line, patch); -} - -static int gitdiff_copysrc(struct apply_state *state, - const char *line, - struct patch *patch) -{ - patch->is_copy = 1; - free(patch->old_name); - patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); - return 0; -} - -static int gitdiff_copydst(struct apply_state *state, - const char *line, - struct patch *patch) -{ - patch->is_copy = 1; - free(patch->new_name); - patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); - return 0; -} - -static int gitdiff_renamesrc(struct apply_state *state, - const char *line, - struct patch *patch) -{ - patch->is_rename = 1; - free(patch->old_name); - patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); - return 0; -} - -static int gitdiff_renamedst(struct apply_state *state, - const char *line, - struct patch *patch) -{ - patch->is_rename = 1; - free(patch->new_name); - patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0); - return 0; -} - -static int gitdiff_similarity(struct apply_state *state, - const char *line, - struct patch *patch) -{ - unsigned long val = strtoul(line, NULL, 10); - if (val <= 100) - patch->score = val; - return 0; -} - -static int gitdiff_dissimilarity(struct apply_state *state, - const char *line, - struct patch *patch) -{ - unsigned long val = strtoul(line, NULL, 10); - if (val <= 100) - patch->score = val; - return 0; -} - -static int gitdiff_index(struct apply_state *state, - const char *line, - struct patch *patch) -{ - /* - * index line is N hexadecimal, "..", N hexadecimal, - * and optional space with octal mode. - */ - const char *ptr, *eol; - int len; - - ptr = strchr(line, '.'); - if (!ptr || ptr[1] != '.' || 40 < ptr - line) - return 0; - len = ptr - line; - memcpy(patch->old_sha1_prefix, line, len); - patch->old_sha1_prefix[len] = 0; - - line = ptr + 2; - ptr = strchr(line, ' '); - eol = strchrnul(line, '\n'); - - if (!ptr || eol < ptr) - ptr = eol; - len = ptr - line; - - if (40 < len) - return 0; - memcpy(patch->new_sha1_prefix, line, len); - patch->new_sha1_prefix[len] = 0; - if (*ptr == ' ') - patch->old_mode = strtoul(ptr+1, NULL, 8); - return 0; -} - -/* - * This is normal for a diff that doesn't change anything: we'll fall through - * into the next diff. Tell the parser to break out. - */ -static int gitdiff_unrecognized(struct apply_state *state, - const char *line, - struct patch *patch) -{ - return -1; -} - -/* - * Skip p_value leading components from "line"; as we do not accept - * absolute paths, return NULL in that case. - */ -static const char *skip_tree_prefix(struct apply_state *state, - const char *line, - int llen) -{ - int nslash; - int i; - - if (!state->p_value) - return (llen && line[0] == '/') ? NULL : line; - - nslash = state->p_value; - for (i = 0; i < llen; i++) { - int ch = line[i]; - if (ch == '/' && --nslash <= 0) - return (i == 0) ? NULL : &line[i + 1]; - } - return NULL; -} - -/* - * This is to extract the same name that appears on "diff --git" - * line. We do not find and return anything if it is a rename - * patch, and it is OK because we will find the name elsewhere. - * We need to reliably find name only when it is mode-change only, - * creation or deletion of an empty file. In any of these cases, - * both sides are the same name under a/ and b/ respectively. - */ -static char *git_header_name(struct apply_state *state, - const char *line, - int llen) -{ - const char *name; - const char *second = NULL; - size_t len, line_len; - - line += strlen("diff --git "); - llen -= strlen("diff --git "); - - if (*line == '"') { - const char *cp; - struct strbuf first = STRBUF_INIT; - struct strbuf sp = STRBUF_INIT; - - if (unquote_c_style(&first, line, &second)) - goto free_and_fail1; - - /* strip the a/b prefix including trailing slash */ - cp = skip_tree_prefix(state, first.buf, first.len); - if (!cp) - goto free_and_fail1; - strbuf_remove(&first, 0, cp - first.buf); - - /* - * second points at one past closing dq of name. - * find the second name. - */ - while ((second < line + llen) && isspace(*second)) - second++; - - if (line + llen <= second) - goto free_and_fail1; - if (*second == '"') { - if (unquote_c_style(&sp, second, NULL)) - goto free_and_fail1; - cp = skip_tree_prefix(state, sp.buf, sp.len); - if (!cp) - goto free_and_fail1; - /* They must match, otherwise ignore */ - if (strcmp(cp, first.buf)) - goto free_and_fail1; - strbuf_release(&sp); - return strbuf_detach(&first, NULL); - } - - /* unquoted second */ - cp = skip_tree_prefix(state, second, line + llen - second); - if (!cp) - goto free_and_fail1; - if (line + llen - cp != first.len || - memcmp(first.buf, cp, first.len)) - goto free_and_fail1; - return strbuf_detach(&first, NULL); - - free_and_fail1: - strbuf_release(&first); - strbuf_release(&sp); - return NULL; - } - - /* unquoted first name */ - name = skip_tree_prefix(state, line, llen); - if (!name) - return NULL; - - /* - * since the first name is unquoted, a dq if exists must be - * the beginning of the second name. - */ - for (second = name; second < line + llen; second++) { - if (*second == '"') { - struct strbuf sp = STRBUF_INIT; - const char *np; - - if (unquote_c_style(&sp, second, NULL)) - goto free_and_fail2; - - np = skip_tree_prefix(state, sp.buf, sp.len); - if (!np) - goto free_and_fail2; - - len = sp.buf + sp.len - np; - if (len < second - name && - !strncmp(np, name, len) && - isspace(name[len])) { - /* Good */ - strbuf_remove(&sp, 0, np - sp.buf); - return strbuf_detach(&sp, NULL); - } - - free_and_fail2: - strbuf_release(&sp); - return NULL; - } - } - - /* - * Accept a name only if it shows up twice, exactly the same - * form. - */ - second = strchr(name, '\n'); - if (!second) - return NULL; - line_len = second - name; - for (len = 0 ; ; len++) { - switch (name[len]) { - default: - continue; - case '\n': - return NULL; - case '\t': case ' ': - /* - * Is this the separator between the preimage - * and the postimage pathname? Again, we are - * only interested in the case where there is - * no rename, as this is only to set def_name - * and a rename patch has the names elsewhere - * in an unambiguous form. - */ - if (!name[len + 1]) - return NULL; /* no postimage name */ - second = skip_tree_prefix(state, name + len + 1, - line_len - (len + 1)); - if (!second) - return NULL; - /* - * Does len bytes starting at "name" and "second" - * (that are separated by one HT or SP we just - * found) exactly match? - */ - if (second[len] == '\n' && !strncmp(name, second, len)) - return xmemdupz(name, len); - } - } -} - -/* Verify that we recognize the lines following a git header */ -static int parse_git_header(struct apply_state *state, - const char *line, - int len, - unsigned int size, - struct patch *patch) -{ - unsigned long offset; - - /* A git diff has explicit new/delete information, so we don't guess */ - patch->is_new = 0; - patch->is_delete = 0; - - /* - * Some things may not have the old name in the - * rest of the headers anywhere (pure mode changes, - * or removing or adding empty files), so we get - * the default name from the header. - */ - patch->def_name = git_header_name(state, line, len); - if (patch->def_name && state->root.len) { - char *s = xstrfmt("%s%s", state->root.buf, patch->def_name); - free(patch->def_name); - patch->def_name = s; - } - - line += len; - size -= len; - state->linenr++; - for (offset = len ; size > 0 ; offset += len, size -= len, line += len, state->linenr++) { - static const struct opentry { - const char *str; - int (*fn)(struct apply_state *, const char *, struct patch *); - } optable[] = { - { "@@ -", gitdiff_hdrend }, - { "--- ", gitdiff_oldname }, - { "+++ ", gitdiff_newname }, - { "old mode ", gitdiff_oldmode }, - { "new mode ", gitdiff_newmode }, - { "deleted file mode ", gitdiff_delete }, - { "new file mode ", gitdiff_newfile }, - { "copy from ", gitdiff_copysrc }, - { "copy to ", gitdiff_copydst }, - { "rename old ", gitdiff_renamesrc }, - { "rename new ", gitdiff_renamedst }, - { "rename from ", gitdiff_renamesrc }, - { "rename to ", gitdiff_renamedst }, - { "similarity index ", gitdiff_similarity }, - { "dissimilarity index ", gitdiff_dissimilarity }, - { "index ", gitdiff_index }, - { "", gitdiff_unrecognized }, - }; - int i; - - len = linelen(line, size); - if (!len || line[len-1] != '\n') - break; - for (i = 0; i < ARRAY_SIZE(optable); i++) { - const struct opentry *p = optable + i; - int oplen = strlen(p->str); - if (len < oplen || memcmp(p->str, line, oplen)) - continue; - if (p->fn(state, line + oplen, patch) < 0) - return offset; - break; - } - } - - return offset; -} - -static int parse_num(const char *line, unsigned long *p) -{ - char *ptr; - - if (!isdigit(*line)) - return 0; - *p = strtoul(line, &ptr, 10); - return ptr - line; -} - -static int parse_range(const char *line, int len, int offset, const char *expect, - unsigned long *p1, unsigned long *p2) -{ - int digits, ex; - - if (offset < 0 || offset >= len) - return -1; - line += offset; - len -= offset; - - digits = parse_num(line, p1); - if (!digits) - return -1; - - offset += digits; - line += digits; - len -= digits; - - *p2 = 1; - if (*line == ',') { - digits = parse_num(line+1, p2); - if (!digits) - return -1; - - offset += digits+1; - line += digits+1; - len -= digits+1; - } - - ex = strlen(expect); - if (ex > len) - return -1; - if (memcmp(line, expect, ex)) - return -1; - - return offset + ex; -} - -static void recount_diff(const char *line, int size, struct fragment *fragment) -{ - int oldlines = 0, newlines = 0, ret = 0; - - if (size < 1) { - warning("recount: ignore empty hunk"); - return; - } - - for (;;) { - int len = linelen(line, size); - size -= len; - line += len; - - if (size < 1) - break; - - switch (*line) { - case ' ': case '\n': - newlines++; - /* fall through */ - case '-': - oldlines++; - continue; - case '+': - newlines++; - continue; - case '\\': - continue; - case '@': - ret = size < 3 || !starts_with(line, "@@ "); - break; - case 'd': - ret = size < 5 || !starts_with(line, "diff "); - break; - default: - ret = -1; - break; - } - if (ret) { - warning(_("recount: unexpected line: %.*s"), - (int)linelen(line, size), line); - return; - } - break; - } - fragment->oldlines = oldlines; - fragment->newlines = newlines; -} - -/* - * Parse a unified diff fragment header of the - * form "@@ -a,b +c,d @@" - */ -static int parse_fragment_header(const char *line, int len, struct fragment *fragment) -{ - int offset; - - if (!len || line[len-1] != '\n') - return -1; - - /* Figure out the number of lines in a fragment */ - offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines); - offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines); - - return offset; -} - -static int find_header(struct apply_state *state, - const char *line, - unsigned long size, - int *hdrsize, - struct patch *patch) -{ - unsigned long offset, len; - - patch->is_toplevel_relative = 0; - patch->is_rename = patch->is_copy = 0; - patch->is_new = patch->is_delete = -1; - patch->old_mode = patch->new_mode = 0; - patch->old_name = patch->new_name = NULL; - for (offset = 0; size > 0; offset += len, size -= len, line += len, state->linenr++) { - unsigned long nextlen; - - len = linelen(line, size); - if (!len) - break; - - /* Testing this early allows us to take a few shortcuts.. */ - if (len < 6) - continue; - - /* - * Make sure we don't find any unconnected patch fragments. - * That's a sign that we didn't find a header, and that a - * patch has become corrupted/broken up. - */ - if (!memcmp("@@ -", line, 4)) { - struct fragment dummy; - if (parse_fragment_header(line, len, &dummy) < 0) - continue; - die(_("patch fragment without header at line %d: %.*s"), - state->linenr, (int)len-1, line); - } - - if (size < len + 6) - break; - - /* - * Git patch? It might not have a real patch, just a rename - * or mode change, so we handle that specially - */ - if (!memcmp("diff --git ", line, 11)) { - int git_hdr_len = parse_git_header(state, line, len, size, patch); - if (git_hdr_len <= len) - continue; - if (!patch->old_name && !patch->new_name) { - if (!patch->def_name) - die(Q_("git diff header lacks filename information when removing " - "%d leading pathname component (line %d)", - "git diff header lacks filename information when removing " - "%d leading pathname components (line %d)", - state->p_value), - state->p_value, state->linenr); - patch->old_name = xstrdup(patch->def_name); - patch->new_name = xstrdup(patch->def_name); - } - if (!patch->is_delete && !patch->new_name) - die("git diff header lacks filename information " - "(line %d)", state->linenr); - patch->is_toplevel_relative = 1; - *hdrsize = git_hdr_len; - return offset; - } - - /* --- followed by +++ ? */ - if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) - continue; - - /* - * We only accept unified patches, so we want it to - * at least have "@@ -a,b +c,d @@\n", which is 14 chars - * minimum ("@@ -0,0 +1 @@\n" is the shortest). - */ - nextlen = linelen(line + len, size - len); - if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) - continue; - - /* Ok, we'll consider it a patch */ - parse_traditional_patch(state, line, line+len, patch); - *hdrsize = len + nextlen; - state->linenr += 2; - return offset; - } - return -1; -} - -static void record_ws_error(struct apply_state *state, - unsigned result, - const char *line, - int len, - int linenr) -{ - char *err; - - if (!result) - return; - - state->whitespace_error++; - if (state->squelch_whitespace_errors && - state->squelch_whitespace_errors < state->whitespace_error) - return; - - err = whitespace_error_string(result); - fprintf(stderr, "%s:%d: %s.\n%.*s\n", - state->patch_input_file, linenr, err, len, line); - free(err); -} - -static void check_whitespace(struct apply_state *state, - const char *line, - int len, - unsigned ws_rule) -{ - unsigned result = ws_check(line + 1, len - 1, ws_rule); - - record_ws_error(state, result, line + 1, len - 2, state->linenr); -} - -/* - * Parse a unified diff. Note that this really needs to parse each - * fragment separately, since the only way to know the difference - * between a "---" that is part of a patch, and a "---" that starts - * the next patch is to look at the line counts.. - */ -static int parse_fragment(struct apply_state *state, - const char *line, - unsigned long size, - struct patch *patch, - struct fragment *fragment) -{ - int added, deleted; - int len = linelen(line, size), offset; - unsigned long oldlines, newlines; - unsigned long leading, trailing; - - offset = parse_fragment_header(line, len, fragment); - if (offset < 0) - return -1; - if (offset > 0 && patch->recount) - recount_diff(line + offset, size - offset, fragment); - oldlines = fragment->oldlines; - newlines = fragment->newlines; - leading = 0; - trailing = 0; - - /* Parse the thing.. */ - line += len; - size -= len; - state->linenr++; - added = deleted = 0; - for (offset = len; - 0 < size; - offset += len, size -= len, line += len, state->linenr++) { - if (!oldlines && !newlines) - break; - len = linelen(line, size); - if (!len || line[len-1] != '\n') - return -1; - switch (*line) { - default: - return -1; - case '\n': /* newer GNU diff, an empty context line */ - case ' ': - oldlines--; - newlines--; - if (!deleted && !added) - leading++; - trailing++; - if (!state->apply_in_reverse && - state->ws_error_action == correct_ws_error) - check_whitespace(state, line, len, patch->ws_rule); - break; - case '-': - if (state->apply_in_reverse && - state->ws_error_action != nowarn_ws_error) - check_whitespace(state, line, len, patch->ws_rule); - deleted++; - oldlines--; - trailing = 0; - break; - case '+': - if (!state->apply_in_reverse && - state->ws_error_action != nowarn_ws_error) - check_whitespace(state, line, len, patch->ws_rule); - added++; - newlines--; - trailing = 0; - break; - - /* - * We allow "\ No newline at end of file". Depending - * on locale settings when the patch was produced we - * don't know what this line looks like. The only - * thing we do know is that it begins with "\ ". - * Checking for 12 is just for sanity check -- any - * l10n of "\ No newline..." is at least that long. - */ - case '\\': - if (len < 12 || memcmp(line, "\\ ", 2)) - return -1; - break; - } - } - if (oldlines || newlines) - return -1; - if (!deleted && !added) - return -1; - - fragment->leading = leading; - fragment->trailing = trailing; - - /* - * If a fragment ends with an incomplete line, we failed to include - * it in the above loop because we hit oldlines == newlines == 0 - * before seeing it. - */ - if (12 < size && !memcmp(line, "\\ ", 2)) - offset += linelen(line, size); - - patch->lines_added += added; - patch->lines_deleted += deleted; - - if (0 < patch->is_new && oldlines) - return error(_("new file depends on old contents")); - if (0 < patch->is_delete && newlines) - return error(_("deleted file still has contents")); - return offset; -} - -/* - * We have seen "diff --git a/... b/..." header (or a traditional patch - * header). Read hunks that belong to this patch into fragments and hang - * them to the given patch structure. - * - * The (fragment->patch, fragment->size) pair points into the memory given - * by the caller, not a copy, when we return. - */ -static int parse_single_patch(struct apply_state *state, - const char *line, - unsigned long size, - struct patch *patch) -{ - unsigned long offset = 0; - unsigned long oldlines = 0, newlines = 0, context = 0; - struct fragment **fragp = &patch->fragments; - - while (size > 4 && !memcmp(line, "@@ -", 4)) { - struct fragment *fragment; - int len; - - fragment = xcalloc(1, sizeof(*fragment)); - fragment->linenr = state->linenr; - len = parse_fragment(state, line, size, patch, fragment); - if (len <= 0) - die(_("corrupt patch at line %d"), state->linenr); - fragment->patch = line; - fragment->size = len; - oldlines += fragment->oldlines; - newlines += fragment->newlines; - context += fragment->leading + fragment->trailing; - - *fragp = fragment; - fragp = &fragment->next; - - offset += len; - line += len; - size -= len; - } - - /* - * If something was removed (i.e. we have old-lines) it cannot - * be creation, and if something was added it cannot be - * deletion. However, the reverse is not true; --unified=0 - * patches that only add are not necessarily creation even - * though they do not have any old lines, and ones that only - * delete are not necessarily deletion. - * - * Unfortunately, a real creation/deletion patch do _not_ have - * any context line by definition, so we cannot safely tell it - * apart with --unified=0 insanity. At least if the patch has - * more than one hunk it is not creation or deletion. - */ - if (patch->is_new < 0 && - (oldlines || (patch->fragments && patch->fragments->next))) - patch->is_new = 0; - if (patch->is_delete < 0 && - (newlines || (patch->fragments && patch->fragments->next))) - patch->is_delete = 0; - - if (0 < patch->is_new && oldlines) - die(_("new file %s depends on old contents"), patch->new_name); - if (0 < patch->is_delete && newlines) - die(_("deleted file %s still has contents"), patch->old_name); - if (!patch->is_delete && !newlines && context) - fprintf_ln(stderr, - _("** warning: " - "file %s becomes empty but is not deleted"), - patch->new_name); - - return offset; -} - -static inline int metadata_changes(struct patch *patch) -{ - return patch->is_rename > 0 || - patch->is_copy > 0 || - patch->is_new > 0 || - patch->is_delete || - (patch->old_mode && patch->new_mode && - patch->old_mode != patch->new_mode); -} - -static char *inflate_it(const void *data, unsigned long size, - unsigned long inflated_size) -{ - git_zstream stream; - void *out; - int st; - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = (unsigned char *)data; - stream.avail_in = size; - stream.next_out = out = xmalloc(inflated_size); - stream.avail_out = inflated_size; - git_inflate_init(&stream); - st = git_inflate(&stream, Z_FINISH); - git_inflate_end(&stream); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - -/* - * Read a binary hunk and return a new fragment; fragment->patch - * points at an allocated memory that the caller must free, so - * it is marked as "->free_patch = 1". - */ -static struct fragment *parse_binary_hunk(struct apply_state *state, - char **buf_p, - unsigned long *sz_p, - int *status_p, - int *used_p) -{ - /* - * Expect a line that begins with binary patch method ("literal" - * or "delta"), followed by the length of data before deflating. - * a sequence of 'length-byte' followed by base-85 encoded data - * should follow, terminated by a newline. - * - * Each 5-byte sequence of base-85 encodes up to 4 bytes, - * and we would limit the patch line to 66 characters, - * so one line can fit up to 13 groups that would decode - * to 52 bytes max. The length byte 'A'-'Z' corresponds - * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. - */ - int llen, used; - unsigned long size = *sz_p; - char *buffer = *buf_p; - int patch_method; - unsigned long origlen; - char *data = NULL; - int hunk_size = 0; - struct fragment *frag; - - llen = linelen(buffer, size); - used = llen; - - *status_p = 0; - - if (starts_with(buffer, "delta ")) { - patch_method = BINARY_DELTA_DEFLATED; - origlen = strtoul(buffer + 6, NULL, 10); - } - else if (starts_with(buffer, "literal ")) { - patch_method = BINARY_LITERAL_DEFLATED; - origlen = strtoul(buffer + 8, NULL, 10); - } - else - return NULL; - - state->linenr++; - buffer += llen; - while (1) { - int byte_length, max_byte_length, newsize; - llen = linelen(buffer, size); - used += llen; - state->linenr++; - if (llen == 1) { - /* consume the blank line */ - buffer++; - size--; - break; - } - /* - * Minimum line is "A00000\n" which is 7-byte long, - * and the line length must be multiple of 5 plus 2. - */ - if ((llen < 7) || (llen-2) % 5) - goto corrupt; - max_byte_length = (llen - 2) / 5 * 4; - byte_length = *buffer; - if ('A' <= byte_length && byte_length <= 'Z') - byte_length = byte_length - 'A' + 1; - else if ('a' <= byte_length && byte_length <= 'z') - byte_length = byte_length - 'a' + 27; - else - goto corrupt; - /* if the input length was not multiple of 4, we would - * have filler at the end but the filler should never - * exceed 3 bytes - */ - if (max_byte_length < byte_length || - byte_length <= max_byte_length - 4) - goto corrupt; - newsize = hunk_size + byte_length; - data = xrealloc(data, newsize); - if (decode_85(data + hunk_size, buffer + 1, byte_length)) - goto corrupt; - hunk_size = newsize; - buffer += llen; - size -= llen; - } - - frag = xcalloc(1, sizeof(*frag)); - frag->patch = inflate_it(data, hunk_size, origlen); - frag->free_patch = 1; - if (!frag->patch) - goto corrupt; - free(data); - frag->size = origlen; - *buf_p = buffer; - *sz_p = size; - *used_p = used; - frag->binary_patch_method = patch_method; - return frag; - - corrupt: - free(data); - *status_p = -1; - error(_("corrupt binary patch at line %d: %.*s"), - state->linenr-1, llen-1, buffer); - return NULL; -} - -/* - * Returns: - * -1 in case of error, - * the length of the parsed binary patch otherwise - */ -static int parse_binary(struct apply_state *state, - char *buffer, - unsigned long size, - struct patch *patch) -{ - /* - * We have read "GIT binary patch\n"; what follows is a line - * that says the patch method (currently, either "literal" or - * "delta") and the length of data before deflating; a - * sequence of 'length-byte' followed by base-85 encoded data - * follows. - * - * When a binary patch is reversible, there is another binary - * hunk in the same format, starting with patch method (either - * "literal" or "delta") with the length of data, and a sequence - * of length-byte + base-85 encoded data, terminated with another - * empty line. This data, when applied to the postimage, produces - * the preimage. - */ - struct fragment *forward; - struct fragment *reverse; - int status; - int used, used_1; - - forward = parse_binary_hunk(state, &buffer, &size, &status, &used); - if (!forward && !status) - /* there has to be one hunk (forward hunk) */ - return error(_("unrecognized binary patch at line %d"), state->linenr-1); - if (status) - /* otherwise we already gave an error message */ - return status; - - reverse = parse_binary_hunk(state, &buffer, &size, &status, &used_1); - if (reverse) - used += used_1; - else if (status) { - /* - * Not having reverse hunk is not an error, but having - * a corrupt reverse hunk is. - */ - free((void*) forward->patch); - free(forward); - return status; - } - forward->next = reverse; - patch->fragments = forward; - patch->is_binary = 1; - return used; -} - -static void prefix_one(struct apply_state *state, char **name) -{ - char *old_name = *name; - if (!old_name) - return; - *name = xstrdup(prefix_filename(state->prefix, state->prefix_length, *name)); - free(old_name); -} - -static void prefix_patch(struct apply_state *state, struct patch *p) -{ - if (!state->prefix || p->is_toplevel_relative) - return; - prefix_one(state, &p->new_name); - prefix_one(state, &p->old_name); -} - -/* - * include/exclude - */ - -static void add_name_limit(struct apply_state *state, - const char *name, - int exclude) -{ - struct string_list_item *it; - - it = string_list_append(&state->limit_by_name, name); - it->util = exclude ? NULL : (void *) 1; -} - -static int use_patch(struct apply_state *state, struct patch *p) -{ - const char *pathname = p->new_name ? p->new_name : p->old_name; - int i; - - /* Paths outside are not touched regardless of "--include" */ - if (0 < state->prefix_length) { - int pathlen = strlen(pathname); - if (pathlen <= state->prefix_length || - memcmp(state->prefix, pathname, state->prefix_length)) - return 0; - } - - /* See if it matches any of exclude/include rule */ - for (i = 0; i < state->limit_by_name.nr; i++) { - struct string_list_item *it = &state->limit_by_name.items[i]; - if (!wildmatch(it->string, pathname, 0, NULL)) - return (it->util != NULL); - } - - /* - * If we had any include, a path that does not match any rule is - * not used. Otherwise, we saw bunch of exclude rules (or none) - * and such a path is used. - */ - return !state->has_include; -} - - -/* - * Read the patch text in "buffer" that extends for "size" bytes; stop - * reading after seeing a single patch (i.e. changes to a single file). - * Create fragments (i.e. patch hunks) and hang them to the given patch. - * Return the number of bytes consumed, so that the caller can call us - * again for the next patch. - */ -static int parse_chunk(struct apply_state *state, char *buffer, unsigned long size, struct patch *patch) -{ - int hdrsize, patchsize; - int offset = find_header(state, buffer, size, &hdrsize, patch); - - if (offset < 0) - return offset; - - prefix_patch(state, patch); - - if (!use_patch(state, patch)) - patch->ws_rule = 0; - else - patch->ws_rule = whitespace_rule(patch->new_name - ? patch->new_name - : patch->old_name); - - patchsize = parse_single_patch(state, - buffer + offset + hdrsize, - size - offset - hdrsize, - patch); - - if (!patchsize) { - static const char git_binary[] = "GIT binary patch\n"; - int hd = hdrsize + offset; - unsigned long llen = linelen(buffer + hd, size - hd); - - if (llen == sizeof(git_binary) - 1 && - !memcmp(git_binary, buffer + hd, llen)) { - int used; - state->linenr++; - used = parse_binary(state, buffer + hd + llen, - size - hd - llen, patch); - if (used < 0) - return -1; - if (used) - patchsize = used + llen; - else - patchsize = 0; - } - else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { - static const char *binhdr[] = { - "Binary files ", - "Files ", - NULL, - }; - int i; - for (i = 0; binhdr[i]; i++) { - int len = strlen(binhdr[i]); - if (len < size - hd && - !memcmp(binhdr[i], buffer + hd, len)) { - state->linenr++; - patch->is_binary = 1; - patchsize = llen; - break; - } - } - } - - /* Empty patch cannot be applied if it is a text patch - * without metadata change. A binary patch appears - * empty to us here. - */ - if ((state->apply || state->check) && - (!patch->is_binary && !metadata_changes(patch))) - die(_("patch with only garbage at line %d"), state->linenr); - } - - return offset + hdrsize + patchsize; -} - -#define swap(a,b) myswap((a),(b),sizeof(a)) - -#define myswap(a, b, size) do { \ - unsigned char mytmp[size]; \ - memcpy(mytmp, &a, size); \ - memcpy(&a, &b, size); \ - memcpy(&b, mytmp, size); \ -} while (0) - -static void reverse_patches(struct patch *p) -{ - for (; p; p = p->next) { - struct fragment *frag = p->fragments; - - swap(p->new_name, p->old_name); - swap(p->new_mode, p->old_mode); - swap(p->is_new, p->is_delete); - swap(p->lines_added, p->lines_deleted); - swap(p->old_sha1_prefix, p->new_sha1_prefix); - - for (; frag; frag = frag->next) { - swap(frag->newpos, frag->oldpos); - swap(frag->newlines, frag->oldlines); - } - } -} - -static const char pluses[] = -"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= -"----------------------------------------------------------------------"; - -static void show_stats(struct apply_state *state, struct patch *patch) -{ - struct strbuf qname = STRBUF_INIT; - char *cp = patch->new_name ? patch->new_name : patch->old_name; - int max, add, del; - - quote_c_style(cp, &qname, NULL, 0); - - /* - * "scale" the filename - */ - max = state->max_len; - if (max > 50) - max = 50; - - if (qname.len > max) { - cp = strchr(qname.buf + qname.len + 3 - max, '/'); - if (!cp) - cp = qname.buf + qname.len + 3 - max; - strbuf_splice(&qname, 0, cp - qname.buf, "...", 3); - } - - if (patch->is_binary) { - printf(" %-*s | Bin\n", max, qname.buf); - strbuf_release(&qname); - return; - } - - printf(" %-*s |", max, qname.buf); - strbuf_release(&qname); - - /* - * scale the add/delete - */ - max = max + state->max_change > 70 ? 70 - max : state->max_change; - add = patch->lines_added; - del = patch->lines_deleted; - - if (state->max_change > 0) { - int total = ((add + del) * max + state->max_change / 2) / state->max_change; - add = (add * max + state->max_change / 2) / state->max_change; - del = total - add; - } - printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted, - add, pluses, del, minuses); -} - -static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) -{ - switch (st->st_mode & S_IFMT) { - case S_IFLNK: - if (strbuf_readlink(buf, path, st->st_size) < 0) - return error(_("unable to read symlink %s"), path); - return 0; - 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); - return 0; - default: - return -1; - } -} - -/* - * Update the preimage, and the common lines in postimage, - * from buffer buf of length len. If postlen is 0 the postimage - * is updated in place, otherwise it's updated on a new buffer - * of length postlen - */ - -static void update_pre_post_images(struct image *preimage, - struct image *postimage, - char *buf, - size_t len, size_t postlen) -{ - int i, ctx, reduced; - char *new, *old, *fixed; - struct image fixed_preimage; - - /* - * Update the preimage with whitespace fixes. Note that we - * are not losing preimage->buf -- apply_one_fragment() will - * free "oldlines". - */ - prepare_image(&fixed_preimage, buf, len, 1); - assert(postlen - ? fixed_preimage.nr == preimage->nr - : fixed_preimage.nr <= preimage->nr); - for (i = 0; i < fixed_preimage.nr; i++) - fixed_preimage.line[i].flag = preimage->line[i].flag; - free(preimage->line_allocated); - *preimage = fixed_preimage; - - /* - * Adjust the common context lines in postimage. This can be - * done in-place when we are shrinking it with whitespace - * fixing, but needs a new buffer when ignoring whitespace or - * expanding leading tabs to spaces. - * - * We trust the caller to tell us if the update can be done - * in place (postlen==0) or not. - */ - old = postimage->buf; - if (postlen) - new = postimage->buf = xmalloc(postlen); - else - new = old; - fixed = preimage->buf; - - for (i = reduced = ctx = 0; i < postimage->nr; i++) { - size_t l_len = postimage->line[i].len; - if (!(postimage->line[i].flag & LINE_COMMON)) { - /* an added line -- no counterparts in preimage */ - memmove(new, old, l_len); - old += l_len; - new += l_len; - continue; - } - - /* a common context -- skip it in the original postimage */ - old += l_len; - - /* and find the corresponding one in the fixed preimage */ - while (ctx < preimage->nr && - !(preimage->line[ctx].flag & LINE_COMMON)) { - fixed += preimage->line[ctx].len; - ctx++; - } - - /* - * preimage is expected to run out, if the caller - * fixed addition of trailing blank lines. - */ - if (preimage->nr <= ctx) { - reduced++; - continue; - } - - /* and copy it in, while fixing the line length */ - l_len = preimage->line[ctx].len; - memcpy(new, fixed, l_len); - new += l_len; - fixed += l_len; - postimage->line[i].len = l_len; - ctx++; - } - - if (postlen - ? postlen < new - postimage->buf - : postimage->len < new - postimage->buf) - die("BUG: caller miscounted postlen: asked %d, orig = %d, used = %d", - (int)postlen, (int) postimage->len, (int)(new - postimage->buf)); - - /* Fix the length of the whole thing */ - postimage->len = new - postimage->buf; - postimage->nr -= reduced; -} - -static int line_by_line_fuzzy_match(struct image *img, - struct image *preimage, - struct image *postimage, - unsigned long try, - int try_lno, - int preimage_limit) -{ - int i; - size_t imgoff = 0; - size_t preoff = 0; - size_t postlen = postimage->len; - size_t extra_chars; - char *buf; - char *preimage_eof; - char *preimage_end; - struct strbuf fixed; - char *fixed_buf; - size_t fixed_len; - - for (i = 0; i < preimage_limit; i++) { - size_t prelen = preimage->line[i].len; - size_t imglen = img->line[try_lno+i].len; - - if (!fuzzy_matchlines(img->buf + try + imgoff, imglen, - preimage->buf + preoff, prelen)) - return 0; - if (preimage->line[i].flag & LINE_COMMON) - postlen += imglen - prelen; - imgoff += imglen; - preoff += prelen; - } - - /* - * Ok, the preimage matches with whitespace fuzz. - * - * imgoff now holds the true length of the target that - * matches the preimage before the end of the file. - * - * Count the number of characters in the preimage that fall - * beyond the end of the file and make sure that all of them - * are whitespace characters. (This can only happen if - * we are removing blank lines at the end of the file.) - */ - buf = preimage_eof = preimage->buf + preoff; - for ( ; i < preimage->nr; i++) - preoff += preimage->line[i].len; - preimage_end = preimage->buf + preoff; - for ( ; buf < preimage_end; buf++) - if (!isspace(*buf)) - return 0; - - /* - * Update the preimage and the common postimage context - * lines to use the same whitespace as the target. - * If whitespace is missing in the target (i.e. - * if the preimage extends beyond the end of the file), - * use the whitespace from the preimage. - */ - extra_chars = preimage_end - preimage_eof; - strbuf_init(&fixed, imgoff + extra_chars); - strbuf_add(&fixed, img->buf + try, imgoff); - strbuf_add(&fixed, preimage_eof, extra_chars); - fixed_buf = strbuf_detach(&fixed, &fixed_len); - update_pre_post_images(preimage, postimage, - fixed_buf, fixed_len, postlen); - return 1; -} - -static int match_fragment(struct apply_state *state, - struct image *img, - struct image *preimage, - struct image *postimage, - unsigned long try, - int try_lno, - unsigned ws_rule, - int match_beginning, int match_end) -{ - int i; - char *fixed_buf, *buf, *orig, *target; - struct strbuf fixed; - size_t fixed_len, postlen; - int preimage_limit; - - if (preimage->nr + try_lno <= img->nr) { - /* - * The hunk falls within the boundaries of img. - */ - preimage_limit = preimage->nr; - if (match_end && (preimage->nr + try_lno != img->nr)) - return 0; - } else if (state->ws_error_action == correct_ws_error && - (ws_rule & WS_BLANK_AT_EOF)) { - /* - * This hunk extends beyond the end of img, and we are - * removing blank lines at the end of the file. This - * many lines from the beginning of the preimage must - * match with img, and the remainder of the preimage - * must be blank. - */ - preimage_limit = img->nr - try_lno; - } else { - /* - * The hunk extends beyond the end of the img and - * we are not removing blanks at the end, so we - * should reject the hunk at this position. - */ - return 0; - } - - if (match_beginning && try_lno) - return 0; - - /* Quick hash check */ - for (i = 0; i < preimage_limit; i++) - if ((img->line[try_lno + i].flag & LINE_PATCHED) || - (preimage->line[i].hash != img->line[try_lno + i].hash)) - return 0; - - if (preimage_limit == preimage->nr) { - /* - * Do we have an exact match? If we were told to match - * at the end, size must be exactly at try+fragsize, - * otherwise try+fragsize must be still within the preimage, - * and either case, the old piece should match the preimage - * exactly. - */ - if ((match_end - ? (try + preimage->len == img->len) - : (try + preimage->len <= img->len)) && - !memcmp(img->buf + try, preimage->buf, preimage->len)) - return 1; - } else { - /* - * The preimage extends beyond the end of img, so - * there cannot be an exact match. - * - * There must be one non-blank context line that match - * a line before the end of img. - */ - char *buf_end; - - buf = preimage->buf; - buf_end = buf; - for (i = 0; i < preimage_limit; i++) - buf_end += preimage->line[i].len; - - for ( ; buf < buf_end; buf++) - if (!isspace(*buf)) - break; - if (buf == buf_end) - return 0; - } - - /* - * No exact match. If we are ignoring whitespace, run a line-by-line - * fuzzy matching. We collect all the line length information because - * we need it to adjust whitespace if we match. - */ - if (state->ws_ignore_action == ignore_ws_change) - return line_by_line_fuzzy_match(img, preimage, postimage, - try, try_lno, preimage_limit); - - if (state->ws_error_action != correct_ws_error) - return 0; - - /* - * The hunk does not apply byte-by-byte, but the hash says - * it might with whitespace fuzz. We weren't asked to - * ignore whitespace, we were asked to correct whitespace - * errors, so let's try matching after whitespace correction. - * - * While checking the preimage against the target, whitespace - * errors in both fixed, we count how large the corresponding - * postimage needs to be. The postimage prepared by - * apply_one_fragment() has whitespace errors fixed on added - * lines already, but the common lines were propagated as-is, - * which may become longer when their whitespace errors are - * fixed. - */ - - /* First count added lines in postimage */ - postlen = 0; - for (i = 0; i < postimage->nr; i++) { - if (!(postimage->line[i].flag & LINE_COMMON)) - postlen += postimage->line[i].len; - } - - /* - * The preimage may extend beyond the end of the file, - * but in this loop we will only handle the part of the - * preimage that falls within the file. - */ - strbuf_init(&fixed, preimage->len + 1); - orig = preimage->buf; - target = img->buf + try; - for (i = 0; i < preimage_limit; i++) { - size_t oldlen = preimage->line[i].len; - size_t tgtlen = img->line[try_lno + i].len; - size_t fixstart = fixed.len; - struct strbuf tgtfix; - int match; - - /* Try fixing the line in the preimage */ - ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); - - /* Try fixing the line in the target */ - strbuf_init(&tgtfix, tgtlen); - ws_fix_copy(&tgtfix, target, tgtlen, ws_rule, NULL); - - /* - * If they match, either the preimage was based on - * a version before our tree fixed whitespace breakage, - * or we are lacking a whitespace-fix patch the tree - * the preimage was based on already had (i.e. target - * has whitespace breakage, the preimage doesn't). - * In either case, we are fixing the whitespace breakages - * so we might as well take the fix together with their - * real change. - */ - match = (tgtfix.len == fixed.len - fixstart && - !memcmp(tgtfix.buf, fixed.buf + fixstart, - fixed.len - fixstart)); - - /* Add the length if this is common with the postimage */ - if (preimage->line[i].flag & LINE_COMMON) - postlen += tgtfix.len; - - strbuf_release(&tgtfix); - if (!match) - goto unmatch_exit; - - orig += oldlen; - target += tgtlen; - } - - - /* - * Now handle the lines in the preimage that falls beyond the - * end of the file (if any). They will only match if they are - * empty or only contain whitespace (if WS_BLANK_AT_EOL is - * false). - */ - for ( ; i < preimage->nr; i++) { - size_t fixstart = fixed.len; /* start of the fixed preimage */ - size_t oldlen = preimage->line[i].len; - int j; - - /* Try fixing the line in the preimage */ - ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL); - - for (j = fixstart; j < fixed.len; j++) - if (!isspace(fixed.buf[j])) - goto unmatch_exit; - - orig += oldlen; - } - - /* - * Yes, the preimage is based on an older version that still - * has whitespace breakages unfixed, and fixing them makes the - * hunk match. Update the context lines in the postimage. - */ - fixed_buf = strbuf_detach(&fixed, &fixed_len); - if (postlen < postimage->len) - postlen = 0; - update_pre_post_images(preimage, postimage, - fixed_buf, fixed_len, postlen); - return 1; - - unmatch_exit: - strbuf_release(&fixed); - return 0; -} - -static int find_pos(struct apply_state *state, - struct image *img, - struct image *preimage, - struct image *postimage, - int line, - unsigned ws_rule, - int match_beginning, int match_end) -{ - int i; - unsigned long backwards, forwards, try; - int backwards_lno, forwards_lno, try_lno; - - /* - * If match_beginning or match_end is specified, there is no - * point starting from a wrong line that will never match and - * wander around and wait for a match at the specified end. - */ - if (match_beginning) - line = 0; - else if (match_end) - line = img->nr - preimage->nr; - - /* - * Because the comparison is unsigned, the following test - * will also take care of a negative line number that can - * result when match_end and preimage is larger than the target. - */ - if ((size_t) line > img->nr) - line = img->nr; - - try = 0; - for (i = 0; i < line; i++) - try += img->line[i].len; - - /* - * There's probably some smart way to do this, but I'll leave - * that to the smart and beautiful people. I'm simple and stupid. - */ - backwards = try; - backwards_lno = line; - forwards = try; - forwards_lno = line; - try_lno = line; - - for (i = 0; ; i++) { - if (match_fragment(state, img, preimage, postimage, - try, try_lno, ws_rule, - match_beginning, match_end)) - return try_lno; - - again: - if (backwards_lno == 0 && forwards_lno == img->nr) - break; - - if (i & 1) { - if (backwards_lno == 0) { - i++; - goto again; - } - backwards_lno--; - backwards -= img->line[backwards_lno].len; - try = backwards; - try_lno = backwards_lno; - } else { - if (forwards_lno == img->nr) { - i++; - goto again; - } - forwards += img->line[forwards_lno].len; - forwards_lno++; - try = forwards; - try_lno = forwards_lno; - } - - } - return -1; -} - -static void remove_first_line(struct image *img) -{ - img->buf += img->line[0].len; - img->len -= img->line[0].len; - img->line++; - img->nr--; -} - -static void remove_last_line(struct image *img) -{ - img->len -= img->line[--img->nr].len; -} - -/* - * The change from "preimage" and "postimage" has been found to - * apply at applied_pos (counts in line numbers) in "img". - * Update "img" to remove "preimage" and replace it with "postimage". - */ -static void update_image(struct apply_state *state, - struct image *img, - int applied_pos, - struct image *preimage, - struct image *postimage) -{ - /* - * remove the copy of preimage at offset in img - * and replace it with postimage - */ - int i, nr; - size_t remove_count, insert_count, applied_at = 0; - char *result; - int preimage_limit; - - /* - * If we are removing blank lines at the end of img, - * the preimage may extend beyond the end. - * If that is the case, we must be careful only to - * remove the part of the preimage that falls within - * the boundaries of img. Initialize preimage_limit - * to the number of lines in the preimage that falls - * within the boundaries. - */ - preimage_limit = preimage->nr; - if (preimage_limit > img->nr - applied_pos) - preimage_limit = img->nr - applied_pos; - - for (i = 0; i < applied_pos; i++) - applied_at += img->line[i].len; - - remove_count = 0; - for (i = 0; i < preimage_limit; i++) - remove_count += img->line[applied_pos + i].len; - insert_count = postimage->len; - - /* Adjust the contents */ - result = xmalloc(st_add3(st_sub(img->len, remove_count), insert_count, 1)); - memcpy(result, img->buf, applied_at); - memcpy(result + applied_at, postimage->buf, postimage->len); - memcpy(result + applied_at + postimage->len, - img->buf + (applied_at + remove_count), - img->len - (applied_at + remove_count)); - free(img->buf); - img->buf = result; - img->len += insert_count - remove_count; - result[img->len] = '\0'; - - /* Adjust the line table */ - nr = img->nr + postimage->nr - preimage_limit; - if (preimage_limit < postimage->nr) { - /* - * NOTE: this knows that we never call remove_first_line() - * on anything other than pre/post image. - */ - REALLOC_ARRAY(img->line, nr); - img->line_allocated = img->line; - } - if (preimage_limit != postimage->nr) - memmove(img->line + applied_pos + postimage->nr, - img->line + applied_pos + preimage_limit, - (img->nr - (applied_pos + preimage_limit)) * - sizeof(*img->line)); - memcpy(img->line + applied_pos, - postimage->line, - postimage->nr * sizeof(*img->line)); - if (!state->allow_overlap) - for (i = 0; i < postimage->nr; i++) - img->line[applied_pos + i].flag |= LINE_PATCHED; - img->nr = nr; -} - -/* - * Use the patch-hunk text in "frag" to prepare two images (preimage and - * postimage) for the hunk. Find lines that match "preimage" in "img" and - * replace the part of "img" with "postimage" text. - */ -static int apply_one_fragment(struct apply_state *state, - struct image *img, struct fragment *frag, - int inaccurate_eof, unsigned ws_rule, - int nth_fragment) -{ - int match_beginning, match_end; - const char *patch = frag->patch; - int size = frag->size; - char *old, *oldlines; - struct strbuf newlines; - int new_blank_lines_at_end = 0; - int found_new_blank_lines_at_end = 0; - int hunk_linenr = frag->linenr; - unsigned long leading, trailing; - int pos, applied_pos; - struct image preimage; - struct image postimage; - - memset(&preimage, 0, sizeof(preimage)); - memset(&postimage, 0, sizeof(postimage)); - oldlines = xmalloc(size); - strbuf_init(&newlines, size); - - old = oldlines; - while (size > 0) { - char first; - int len = linelen(patch, size); - int plen; - int added_blank_line = 0; - int is_blank_context = 0; - size_t start; - - if (!len) - break; - - /* - * "plen" is how much of the line we should use for - * the actual patch data. Normally we just remove the - * first character on the line, but if the line is - * followed by "\ No newline", then we also remove the - * last one (which is the newline, of course). - */ - plen = len - 1; - if (len < size && patch[len] == '\\') - plen--; - first = *patch; - if (state->apply_in_reverse) { - if (first == '-') - first = '+'; - else if (first == '+') - first = '-'; - } - - switch (first) { - case '\n': - /* Newer GNU diff, empty context line */ - if (plen < 0) - /* ... followed by '\No newline'; nothing */ - break; - *old++ = '\n'; - strbuf_addch(&newlines, '\n'); - add_line_info(&preimage, "\n", 1, LINE_COMMON); - add_line_info(&postimage, "\n", 1, LINE_COMMON); - is_blank_context = 1; - break; - case ' ': - if (plen && (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) - is_blank_context = 1; - case '-': - memcpy(old, patch + 1, plen); - add_line_info(&preimage, old, plen, - (first == ' ' ? LINE_COMMON : 0)); - old += plen; - if (first == '-') - break; - /* Fall-through for ' ' */ - case '+': - /* --no-add does not add new lines */ - if (first == '+' && state->no_add) - break; - - start = newlines.len; - if (first != '+' || - !state->whitespace_error || - state->ws_error_action != correct_ws_error) { - strbuf_add(&newlines, patch + 1, plen); - } - else { - ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &state->applied_after_fixing_ws); - } - add_line_info(&postimage, newlines.buf + start, newlines.len - start, - (first == '+' ? 0 : LINE_COMMON)); - if (first == '+' && - (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) - added_blank_line = 1; - break; - case '@': case '\\': - /* Ignore it, we already handled it */ - break; - default: - if (state->apply_verbosely) - error(_("invalid start of line: '%c'"), first); - applied_pos = -1; - goto out; - } - if (added_blank_line) { - if (!new_blank_lines_at_end) - found_new_blank_lines_at_end = hunk_linenr; - new_blank_lines_at_end++; - } - else if (is_blank_context) - ; - else - new_blank_lines_at_end = 0; - patch += len; - size -= len; - hunk_linenr++; - } - if (inaccurate_eof && - old > oldlines && old[-1] == '\n' && - newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') { - old--; - strbuf_setlen(&newlines, newlines.len - 1); - } - - leading = frag->leading; - trailing = frag->trailing; - - /* - * A hunk to change lines at the beginning would begin with - * @@ -1,L +N,M @@ - * but we need to be careful. -U0 that inserts before the second - * line also has this pattern. - * - * And a hunk to add to an empty file would begin with - * @@ -0,0 +N,M @@ - * - * In other words, a hunk that is (frag->oldpos <= 1) with or - * without leading context must match at the beginning. - */ - match_beginning = (!frag->oldpos || - (frag->oldpos == 1 && !state->unidiff_zero)); - - /* - * A hunk without trailing lines must match at the end. - * However, we simply cannot tell if a hunk must match end - * from the lack of trailing lines if the patch was generated - * with unidiff without any context. - */ - match_end = !state->unidiff_zero && !trailing; - - pos = frag->newpos ? (frag->newpos - 1) : 0; - preimage.buf = oldlines; - preimage.len = old - oldlines; - postimage.buf = newlines.buf; - postimage.len = newlines.len; - preimage.line = preimage.line_allocated; - postimage.line = postimage.line_allocated; - - for (;;) { - - applied_pos = find_pos(state, img, &preimage, &postimage, pos, - ws_rule, match_beginning, match_end); - - if (applied_pos >= 0) - break; - - /* Am I at my context limits? */ - if ((leading <= state->p_context) && (trailing <= state->p_context)) - break; - if (match_beginning || match_end) { - match_beginning = match_end = 0; - continue; - } - - /* - * Reduce the number of context lines; reduce both - * leading and trailing if they are equal otherwise - * just reduce the larger context. - */ - if (leading >= trailing) { - remove_first_line(&preimage); - remove_first_line(&postimage); - pos--; - leading--; - } - if (trailing > leading) { - remove_last_line(&preimage); - remove_last_line(&postimage); - trailing--; - } - } - - if (applied_pos >= 0) { - if (new_blank_lines_at_end && - preimage.nr + applied_pos >= img->nr && - (ws_rule & WS_BLANK_AT_EOF) && - state->ws_error_action != nowarn_ws_error) { - record_ws_error(state, WS_BLANK_AT_EOF, "+", 1, - found_new_blank_lines_at_end); - if (state->ws_error_action == correct_ws_error) { - while (new_blank_lines_at_end--) - remove_last_line(&postimage); - } - /* - * We would want to prevent write_out_results() - * from taking place in apply_patch() that follows - * the callchain led us here, which is: - * apply_patch->check_patch_list->check_patch-> - * apply_data->apply_fragments->apply_one_fragment - */ - if (state->ws_error_action == die_on_ws_error) - state->apply = 0; - } - - if (state->apply_verbosely && applied_pos != pos) { - int offset = applied_pos - pos; - if (state->apply_in_reverse) - offset = 0 - offset; - fprintf_ln(stderr, - Q_("Hunk #%d succeeded at %d (offset %d line).", - "Hunk #%d succeeded at %d (offset %d lines).", - offset), - nth_fragment, applied_pos + 1, offset); - } - - /* - * Warn if it was necessary to reduce the number - * of context lines. - */ - if ((leading != frag->leading) || - (trailing != frag->trailing)) - fprintf_ln(stderr, _("Context reduced to (%ld/%ld)" - " to apply fragment at %d"), - leading, trailing, applied_pos+1); - update_image(state, img, applied_pos, &preimage, &postimage); - } else { - if (state->apply_verbosely) - error(_("while searching for:\n%.*s"), - (int)(old - oldlines), oldlines); - } - -out: - free(oldlines); - strbuf_release(&newlines); - free(preimage.line_allocated); - free(postimage.line_allocated); - - return (applied_pos < 0); -} - -static int apply_binary_fragment(struct apply_state *state, - struct image *img, - struct patch *patch) -{ - struct fragment *fragment = patch->fragments; - unsigned long len; - void *dst; - - if (!fragment) - return error(_("missing binary patch data for '%s'"), - patch->new_name ? - patch->new_name : - patch->old_name); - - /* Binary patch is irreversible without the optional second hunk */ - if (state->apply_in_reverse) { - if (!fragment->next) - return error("cannot reverse-apply a binary patch " - "without the reverse hunk to '%s'", - patch->new_name - ? patch->new_name : patch->old_name); - fragment = fragment->next; - } - switch (fragment->binary_patch_method) { - case BINARY_DELTA_DEFLATED: - dst = patch_delta(img->buf, img->len, fragment->patch, - fragment->size, &len); - if (!dst) - return -1; - clear_image(img); - img->buf = dst; - img->len = len; - return 0; - case BINARY_LITERAL_DEFLATED: - clear_image(img); - img->len = fragment->size; - img->buf = xmemdupz(fragment->patch, img->len); - return 0; - } - return -1; -} - -/* - * Replace "img" with the result of applying the binary patch. - * The binary patch data itself in patch->fragment is still kept - * but the preimage prepared by the caller in "img" is freed here - * or in the helper function apply_binary_fragment() this calls. - */ -static int apply_binary(struct apply_state *state, - struct image *img, - struct patch *patch) -{ - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned char sha1[20]; - - /* - * For safety, we require patch index line to contain - * full 40-byte textual SHA1 for old and new, at least for now. - */ - if (strlen(patch->old_sha1_prefix) != 40 || - strlen(patch->new_sha1_prefix) != 40 || - get_sha1_hex(patch->old_sha1_prefix, sha1) || - get_sha1_hex(patch->new_sha1_prefix, sha1)) - return error("cannot apply binary patch to '%s' " - "without full index line", name); - - if (patch->old_name) { - /* - * See if the old one matches what the patch - * applies to. - */ - hash_sha1_file(img->buf, img->len, blob_type, sha1); - if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) - return error("the patch applies to '%s' (%s), " - "which does not match the " - "current contents.", - name, sha1_to_hex(sha1)); - } - else { - /* Otherwise, the old one must be empty. */ - if (img->len) - return error("the patch applies to an empty " - "'%s' but it is not empty", name); - } - - get_sha1_hex(patch->new_sha1_prefix, sha1); - if (is_null_sha1(sha1)) { - clear_image(img); - return 0; /* deletion patch */ - } - - if (has_sha1_file(sha1)) { - /* We already have the postimage */ - enum object_type type; - unsigned long size; - char *result; - - result = read_sha1_file(sha1, &type, &size); - if (!result) - return error("the necessary postimage %s for " - "'%s' cannot be read", - patch->new_sha1_prefix, name); - clear_image(img); - img->buf = result; - img->len = size; - } else { - /* - * We have verified buf matches the preimage; - * apply the patch data to it, which is stored - * in the patch->fragments->{patch,size}. - */ - if (apply_binary_fragment(state, img, patch)) - return error(_("binary patch does not apply to '%s'"), - name); - - /* verify that the result matches */ - hash_sha1_file(img->buf, img->len, blob_type, sha1); - if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"), - name, patch->new_sha1_prefix, sha1_to_hex(sha1)); - } - - return 0; -} - -static int apply_fragments(struct apply_state *state, struct image *img, struct patch *patch) -{ - struct fragment *frag = patch->fragments; - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned ws_rule = patch->ws_rule; - unsigned inaccurate_eof = patch->inaccurate_eof; - int nth = 0; - - if (patch->is_binary) - return apply_binary(state, img, patch); - - while (frag) { - nth++; - if (apply_one_fragment(state, img, frag, inaccurate_eof, ws_rule, nth)) { - error(_("patch failed: %s:%ld"), name, frag->oldpos); - if (!state->apply_with_reject) - return -1; - frag->rejected = 1; - } - frag = frag->next; - } - return 0; -} - -static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode) -{ - if (S_ISGITLINK(mode)) { - strbuf_grow(buf, 100); - strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(sha1)); - } else { - enum object_type type; - unsigned long sz; - char *result; - - result = read_sha1_file(sha1, &type, &sz); - if (!result) - return -1; - /* XXX read_sha1_file NUL-terminates */ - strbuf_attach(buf, result, sz, sz + 1); - } - return 0; -} - -static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf) -{ - if (!ce) - return 0; - return read_blob_object(buf, ce->sha1, ce->ce_mode); -} - -static struct patch *in_fn_table(struct apply_state *state, const char *name) -{ - struct string_list_item *item; - - if (name == NULL) - return NULL; - - item = string_list_lookup(&state->fn_table, name); - if (item != NULL) - return (struct patch *)item->util; - - return NULL; -} - -/* - * item->util in the filename table records the status of the path. - * Usually it points at a patch (whose result records the contents - * of it after applying it), but it could be PATH_WAS_DELETED for a - * path that a previously applied patch has already removed, or - * PATH_TO_BE_DELETED for a path that a later patch would remove. - * - * The latter is needed to deal with a case where two paths A and B - * are swapped by first renaming A to B and then renaming B to A; - * moving A to B should not be prevented due to presence of B as we - * will remove it in a later patch. - */ -#define PATH_TO_BE_DELETED ((struct patch *) -2) -#define PATH_WAS_DELETED ((struct patch *) -1) - -static int to_be_deleted(struct patch *patch) -{ - return patch == PATH_TO_BE_DELETED; -} - -static int was_deleted(struct patch *patch) -{ - return patch == PATH_WAS_DELETED; -} - -static void add_to_fn_table(struct apply_state *state, struct patch *patch) -{ - struct string_list_item *item; - - /* - * Always add new_name unless patch is a deletion - * This should cover the cases for normal diffs, - * file creations and copies - */ - if (patch->new_name != NULL) { - item = string_list_insert(&state->fn_table, patch->new_name); - item->util = patch; - } - - /* - * store a failure on rename/deletion cases because - * later chunks shouldn't patch old names - */ - if ((patch->new_name == NULL) || (patch->is_rename)) { - item = string_list_insert(&state->fn_table, patch->old_name); - item->util = PATH_WAS_DELETED; - } -} - -static void prepare_fn_table(struct apply_state *state, struct patch *patch) -{ - /* - * store information about incoming file deletion - */ - while (patch) { - if ((patch->new_name == NULL) || (patch->is_rename)) { - struct string_list_item *item; - item = string_list_insert(&state->fn_table, patch->old_name); - item->util = PATH_TO_BE_DELETED; - } - patch = patch->next; - } -} - -static int checkout_target(struct index_state *istate, - struct cache_entry *ce, struct stat *st) -{ - struct checkout costate; - - memset(&costate, 0, sizeof(costate)); - costate.base_dir = ""; - costate.refresh_cache = 1; - costate.istate = istate; - if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st)) - return error(_("cannot checkout %s"), ce->name); - return 0; -} - -static struct patch *previous_patch(struct apply_state *state, - struct patch *patch, - int *gone) -{ - struct patch *previous; - - *gone = 0; - if (patch->is_copy || patch->is_rename) - return NULL; /* "git" patches do not depend on the order */ - - previous = in_fn_table(state, patch->old_name); - if (!previous) - return NULL; - - if (to_be_deleted(previous)) - return NULL; /* the deletion hasn't happened yet */ - - if (was_deleted(previous)) - *gone = 1; - - return previous; -} - -static int verify_index_match(const struct cache_entry *ce, struct stat *st) -{ - if (S_ISGITLINK(ce->ce_mode)) { - if (!S_ISDIR(st->st_mode)) - return -1; - return 0; - } - return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); -} - -#define SUBMODULE_PATCH_WITHOUT_INDEX 1 - -static int load_patch_target(struct apply_state *state, - struct strbuf *buf, - const struct cache_entry *ce, - struct stat *st, - const char *name, - unsigned expected_mode) -{ - if (state->cached || state->check_index) { - if (read_file_or_gitlink(ce, buf)) - return error(_("failed to read %s"), name); - } else if (name) { - if (S_ISGITLINK(expected_mode)) { - if (ce) - return read_file_or_gitlink(ce, buf); - else - return SUBMODULE_PATCH_WITHOUT_INDEX; - } else if (has_symlink_leading_path(name, strlen(name))) { - return error(_("reading from '%s' beyond a symbolic link"), name); - } else { - if (read_old_data(st, name, buf)) - return error(_("failed to read %s"), name); - } - } - return 0; -} - -/* - * We are about to apply "patch"; populate the "image" with the - * current version we have, from the working tree or from the index, - * depending on the situation e.g. --cached/--index. If we are - * applying a non-git patch that incrementally updates the tree, - * we read from the result of a previous diff. - */ -static int load_preimage(struct apply_state *state, - struct image *image, - struct patch *patch, struct stat *st, - const struct cache_entry *ce) -{ - struct strbuf buf = STRBUF_INIT; - size_t len; - char *img; - struct patch *previous; - int status; - - previous = previous_patch(state, patch, &status); - if (status) - return error(_("path %s has been renamed/deleted"), - patch->old_name); - if (previous) { - /* We have a patched copy in memory; use that. */ - strbuf_add(&buf, previous->result, previous->resultsize); - } else { - status = load_patch_target(state, &buf, ce, st, - patch->old_name, patch->old_mode); - if (status < 0) - return status; - else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) { - /* - * There is no way to apply subproject - * patch without looking at the index. - * NEEDSWORK: shouldn't this be flagged - * as an error??? - */ - free_fragment_list(patch->fragments); - patch->fragments = NULL; - } else if (status) { - return error(_("failed to read %s"), patch->old_name); - } - } - - img = strbuf_detach(&buf, &len); - prepare_image(image, img, len, !patch->is_binary); - return 0; -} - -static int three_way_merge(struct image *image, - char *path, - const unsigned char *base, - const unsigned char *ours, - const unsigned char *theirs) -{ - mmfile_t base_file, our_file, their_file; - mmbuffer_t result = { NULL }; - int status; - - read_mmblob(&base_file, base); - read_mmblob(&our_file, ours); - read_mmblob(&their_file, theirs); - status = ll_merge(&result, path, - &base_file, "base", - &our_file, "ours", - &their_file, "theirs", NULL); - free(base_file.ptr); - free(our_file.ptr); - free(their_file.ptr); - if (status < 0 || !result.ptr) { - free(result.ptr); - return -1; - } - clear_image(image); - image->buf = result.ptr; - image->len = result.size; - - return status; -} - -/* - * When directly falling back to add/add three-way merge, we read from - * the current contents of the new_name. In no cases other than that - * this function will be called. - */ -static int load_current(struct apply_state *state, - struct image *image, - struct patch *patch) -{ - struct strbuf buf = STRBUF_INIT; - int status, pos; - size_t len; - char *img; - struct stat st; - struct cache_entry *ce; - char *name = patch->new_name; - unsigned mode = patch->new_mode; - - if (!patch->is_new) - die("BUG: patch to %s is not a creation", patch->old_name); - - pos = cache_name_pos(name, strlen(name)); - if (pos < 0) - return error(_("%s: does not exist in index"), name); - ce = active_cache[pos]; - if (lstat(name, &st)) { - if (errno != ENOENT) - return error(_("%s: %s"), name, strerror(errno)); - if (checkout_target(&the_index, ce, &st)) - return -1; - } - if (verify_index_match(ce, &st)) - return error(_("%s: does not match index"), name); - - status = load_patch_target(state, &buf, ce, &st, name, mode); - if (status < 0) - return status; - else if (status) - return -1; - img = strbuf_detach(&buf, &len); - prepare_image(image, img, len, !patch->is_binary); - return 0; -} - -static int try_threeway(struct apply_state *state, - struct image *image, - struct patch *patch, - struct stat *st, - const struct cache_entry *ce) -{ - unsigned char pre_sha1[20], post_sha1[20], our_sha1[20]; - struct strbuf buf = STRBUF_INIT; - size_t len; - int status; - char *img; - struct image tmp_image; - - /* No point falling back to 3-way merge in these cases */ - if (patch->is_delete || - S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode)) - return -1; - - /* Preimage the patch was prepared for */ - if (patch->is_new) - write_sha1_file("", 0, blob_type, pre_sha1); - else if (get_sha1(patch->old_sha1_prefix, pre_sha1) || - read_blob_object(&buf, pre_sha1, patch->old_mode)) - return error("repository lacks the necessary blob to fall back on 3-way merge."); - - fprintf(stderr, "Falling back to three-way merge...\n"); - - img = strbuf_detach(&buf, &len); - prepare_image(&tmp_image, img, len, 1); - /* Apply the patch to get the post image */ - if (apply_fragments(state, &tmp_image, patch) < 0) { - clear_image(&tmp_image); - return -1; - } - /* post_sha1[] is theirs */ - write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_sha1); - clear_image(&tmp_image); - - /* our_sha1[] is ours */ - if (patch->is_new) { - if (load_current(state, &tmp_image, patch)) - return error("cannot read the current contents of '%s'", - patch->new_name); - } else { - if (load_preimage(state, &tmp_image, patch, st, ce)) - return error("cannot read the current contents of '%s'", - patch->old_name); - } - write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_sha1); - clear_image(&tmp_image); - - /* in-core three-way merge between post and our using pre as base */ - status = three_way_merge(image, patch->new_name, - pre_sha1, our_sha1, post_sha1); - if (status < 0) { - fprintf(stderr, "Failed to fall back on three-way merge...\n"); - return status; - } - - if (status) { - patch->conflicted_threeway = 1; - if (patch->is_new) - oidclr(&patch->threeway_stage[0]); - else - hashcpy(patch->threeway_stage[0].hash, pre_sha1); - hashcpy(patch->threeway_stage[1].hash, our_sha1); - hashcpy(patch->threeway_stage[2].hash, post_sha1); - fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name); - } else { - fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name); - } - return 0; -} - -static int apply_data(struct apply_state *state, struct patch *patch, - struct stat *st, const struct cache_entry *ce) -{ - struct image image; - - if (load_preimage(state, &image, patch, st, ce) < 0) - return -1; - - if (patch->direct_to_threeway || - apply_fragments(state, &image, patch) < 0) { - /* Note: with --reject, apply_fragments() returns 0 */ - if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0) - return -1; - } - patch->result = image.buf; - patch->resultsize = image.len; - add_to_fn_table(state, patch); - free(image.line_allocated); - - if (0 < patch->is_delete && patch->resultsize) - return error(_("removal patch leaves file contents")); - - return 0; -} - -/* - * If "patch" that we are looking at modifies or deletes what we have, - * we would want it not to lose any local modification we have, either - * in the working tree or in the index. - * - * This also decides if a non-git patch is a creation patch or a - * modification to an existing empty file. We do not check the state - * of the current tree for a creation patch in this function; the caller - * check_patch() separately makes sure (and errors out otherwise) that - * the path the patch creates does not exist in the current tree. - */ -static int check_preimage(struct apply_state *state, - struct patch *patch, - struct cache_entry **ce, - struct stat *st) -{ - const char *old_name = patch->old_name; - struct patch *previous = NULL; - int stat_ret = 0, status; - unsigned st_mode = 0; - - if (!old_name) - return 0; - - assert(patch->is_new <= 0); - previous = previous_patch(state, patch, &status); - - if (status) - return error(_("path %s has been renamed/deleted"), old_name); - if (previous) { - st_mode = previous->new_mode; - } else if (!state->cached) { - stat_ret = lstat(old_name, st); - if (stat_ret && errno != ENOENT) - return error(_("%s: %s"), old_name, strerror(errno)); - } - - if (state->check_index && !previous) { - int pos = cache_name_pos(old_name, strlen(old_name)); - if (pos < 0) { - if (patch->is_new < 0) - goto is_new; - return error(_("%s: does not exist in index"), old_name); - } - *ce = active_cache[pos]; - if (stat_ret < 0) { - if (checkout_target(&the_index, *ce, st)) - return -1; - } - if (!state->cached && verify_index_match(*ce, st)) - return error(_("%s: does not match index"), old_name); - if (state->cached) - st_mode = (*ce)->ce_mode; - } else if (stat_ret < 0) { - if (patch->is_new < 0) - goto is_new; - return error(_("%s: %s"), old_name, strerror(errno)); - } - - if (!state->cached && !previous) - st_mode = ce_mode_from_stat(*ce, st->st_mode); - - if (patch->is_new < 0) - patch->is_new = 0; - if (!patch->old_mode) - patch->old_mode = st_mode; - if ((st_mode ^ patch->old_mode) & S_IFMT) - return error(_("%s: wrong type"), old_name); - if (st_mode != patch->old_mode) - warning(_("%s has type %o, expected %o"), - old_name, st_mode, patch->old_mode); - if (!patch->new_mode && !patch->is_delete) - patch->new_mode = st_mode; - return 0; - - is_new: - patch->is_new = 1; - patch->is_delete = 0; - free(patch->old_name); - patch->old_name = NULL; - return 0; -} - - -#define EXISTS_IN_INDEX 1 -#define EXISTS_IN_WORKTREE 2 - -static int check_to_create(struct apply_state *state, - const char *new_name, - int ok_if_exists) -{ - struct stat nst; - - if (state->check_index && - cache_name_pos(new_name, strlen(new_name)) >= 0 && - !ok_if_exists) - return EXISTS_IN_INDEX; - if (state->cached) - return 0; - - if (!lstat(new_name, &nst)) { - if (S_ISDIR(nst.st_mode) || ok_if_exists) - return 0; - /* - * A leading component of new_name might be a symlink - * that is going to be removed with this patch, but - * still pointing at somewhere that has the path. - * In such a case, path "new_name" does not exist as - * far as git is concerned. - */ - if (has_symlink_leading_path(new_name, strlen(new_name))) - return 0; - - return EXISTS_IN_WORKTREE; - } else if ((errno != ENOENT) && (errno != ENOTDIR)) { - return error("%s: %s", new_name, strerror(errno)); - } - return 0; -} - -static uintptr_t register_symlink_changes(struct apply_state *state, - const char *path, - uintptr_t what) -{ - struct string_list_item *ent; - - ent = string_list_lookup(&state->symlink_changes, path); - if (!ent) { - ent = string_list_insert(&state->symlink_changes, path); - ent->util = (void *)0; - } - ent->util = (void *)(what | ((uintptr_t)ent->util)); - return (uintptr_t)ent->util; -} - -static uintptr_t check_symlink_changes(struct apply_state *state, const char *path) -{ - struct string_list_item *ent; - - ent = string_list_lookup(&state->symlink_changes, path); - if (!ent) - return 0; - return (uintptr_t)ent->util; -} - -static void prepare_symlink_changes(struct apply_state *state, struct patch *patch) -{ - for ( ; patch; patch = patch->next) { - if ((patch->old_name && S_ISLNK(patch->old_mode)) && - (patch->is_rename || patch->is_delete)) - /* the symlink at patch->old_name is removed */ - register_symlink_changes(state, patch->old_name, SYMLINK_GOES_AWAY); - - if (patch->new_name && S_ISLNK(patch->new_mode)) - /* the symlink at patch->new_name is created or remains */ - register_symlink_changes(state, patch->new_name, SYMLINK_IN_RESULT); - } -} - -static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *name) -{ - do { - unsigned int change; - - while (--name->len && name->buf[name->len] != '/') - ; /* scan backwards */ - if (!name->len) - break; - name->buf[name->len] = '\0'; - change = check_symlink_changes(state, name->buf); - if (change & SYMLINK_IN_RESULT) - return 1; - if (change & SYMLINK_GOES_AWAY) - /* - * This cannot be "return 0", because we may - * see a new one created at a higher level. - */ - continue; - - /* otherwise, check the preimage */ - if (state->check_index) { - struct cache_entry *ce; - - ce = cache_file_exists(name->buf, name->len, ignore_case); - if (ce && S_ISLNK(ce->ce_mode)) - return 1; - } else { - struct stat st; - if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode)) - return 1; - } - } while (1); - return 0; -} - -static int path_is_beyond_symlink(struct apply_state *state, const char *name_) -{ - int ret; - struct strbuf name = STRBUF_INIT; - - assert(*name_ != '\0'); - strbuf_addstr(&name, name_); - ret = path_is_beyond_symlink_1(state, &name); - strbuf_release(&name); - - return ret; -} - -static void die_on_unsafe_path(struct patch *patch) -{ - const char *old_name = NULL; - const char *new_name = NULL; - if (patch->is_delete) - old_name = patch->old_name; - else if (!patch->is_new && !patch->is_copy) - old_name = patch->old_name; - if (!patch->is_delete) - new_name = patch->new_name; - - if (old_name && !verify_path(old_name)) - die(_("invalid path '%s'"), old_name); - if (new_name && !verify_path(new_name)) - die(_("invalid path '%s'"), new_name); -} - -/* - * Check and apply the patch in-core; leave the result in patch->result - * for the caller to write it out to the final destination. - */ -static int check_patch(struct apply_state *state, struct patch *patch) -{ - struct stat st; - const char *old_name = patch->old_name; - const char *new_name = patch->new_name; - const char *name = old_name ? old_name : new_name; - struct cache_entry *ce = NULL; - struct patch *tpatch; - int ok_if_exists; - int status; - - patch->rejected = 1; /* we will drop this after we succeed */ - - status = check_preimage(state, patch, &ce, &st); - if (status) - return status; - old_name = patch->old_name; - - /* - * A type-change diff is always split into a patch to delete - * old, immediately followed by a patch to create new (see - * diff.c::run_diff()); in such a case it is Ok that the entry - * to be deleted by the previous patch is still in the working - * tree and in the index. - * - * A patch to swap-rename between A and B would first rename A - * to B and then rename B to A. While applying the first one, - * the presence of B should not stop A from getting renamed to - * B; ask to_be_deleted() about the later rename. Removal of - * B and rename from A to B is handled the same way by asking - * was_deleted(). - */ - if ((tpatch = in_fn_table(state, new_name)) && - (was_deleted(tpatch) || to_be_deleted(tpatch))) - ok_if_exists = 1; - else - ok_if_exists = 0; - - if (new_name && - ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) { - int err = check_to_create(state, new_name, ok_if_exists); - - if (err && state->threeway) { - patch->direct_to_threeway = 1; - } else switch (err) { - case 0: - break; /* happy */ - case EXISTS_IN_INDEX: - return error(_("%s: already exists in index"), new_name); - break; - case EXISTS_IN_WORKTREE: - return error(_("%s: already exists in working directory"), - new_name); - default: - return err; - } - - if (!patch->new_mode) { - if (0 < patch->is_new) - patch->new_mode = S_IFREG | 0644; - else - patch->new_mode = patch->old_mode; - } - } - - if (new_name && old_name) { - int same = !strcmp(old_name, new_name); - if (!patch->new_mode) - patch->new_mode = patch->old_mode; - if ((patch->old_mode ^ patch->new_mode) & S_IFMT) { - if (same) - return error(_("new mode (%o) of %s does not " - "match old mode (%o)"), - patch->new_mode, new_name, - patch->old_mode); - else - return error(_("new mode (%o) of %s does not " - "match old mode (%o) of %s"), - patch->new_mode, new_name, - patch->old_mode, old_name); - } - } - - if (!state->unsafe_paths) - die_on_unsafe_path(patch); - - /* - * An attempt to read from or delete a path that is beyond a - * symbolic link will be prevented by load_patch_target() that - * is called at the beginning of apply_data() so we do not - * have to worry about a patch marked with "is_delete" bit - * here. We however need to make sure that the patch result - * is not deposited to a path that is beyond a symbolic link - * here. - */ - if (!patch->is_delete && path_is_beyond_symlink(state, patch->new_name)) - return error(_("affected file '%s' is beyond a symbolic link"), - patch->new_name); - - if (apply_data(state, patch, &st, ce) < 0) - return error(_("%s: patch does not apply"), name); - patch->rejected = 0; - return 0; -} - -static int check_patch_list(struct apply_state *state, struct patch *patch) -{ - int err = 0; - - prepare_symlink_changes(state, patch); - prepare_fn_table(state, patch); - while (patch) { - if (state->apply_verbosely) - say_patch_name(stderr, - _("Checking patch %s..."), patch); - err |= check_patch(state, patch); - patch = patch->next; - } - return err; -} - -/* This function tries to read the sha1 from the current index */ -static int get_current_sha1(const char *path, unsigned char *sha1) -{ - int pos; - - if (read_cache() < 0) - return -1; - pos = cache_name_pos(path, strlen(path)); - if (pos < 0) - return -1; - hashcpy(sha1, active_cache[pos]->sha1); - return 0; -} - -static int preimage_sha1_in_gitlink_patch(struct patch *p, unsigned char sha1[20]) -{ - /* - * A usable gitlink patch has only one fragment (hunk) that looks like: - * @@ -1 +1 @@ - * -Subproject commit <old sha1> - * +Subproject commit <new sha1> - * or - * @@ -1 +0,0 @@ - * -Subproject commit <old sha1> - * for a removal patch. - */ - struct fragment *hunk = p->fragments; - static const char heading[] = "-Subproject commit "; - char *preimage; - - if (/* does the patch have only one hunk? */ - hunk && !hunk->next && - /* is its preimage one line? */ - hunk->oldpos == 1 && hunk->oldlines == 1 && - /* does preimage begin with the heading? */ - (preimage = memchr(hunk->patch, '\n', hunk->size)) != NULL && - starts_with(++preimage, heading) && - /* does it record full SHA-1? */ - !get_sha1_hex(preimage + sizeof(heading) - 1, sha1) && - preimage[sizeof(heading) + 40 - 1] == '\n' && - /* does the abbreviated name on the index line agree with it? */ - starts_with(preimage + sizeof(heading) - 1, p->old_sha1_prefix)) - return 0; /* it all looks fine */ - - /* we may have full object name on the index line */ - return get_sha1_hex(p->old_sha1_prefix, sha1); -} - -/* Build an index that contains the just the files needed for a 3way merge */ -static void build_fake_ancestor(struct patch *list, const char *filename) -{ - struct patch *patch; - struct index_state result = { NULL }; - static struct lock_file lock; - - /* Once we start supporting the reverse patch, it may be - * worth showing the new sha1 prefix, but until then... - */ - for (patch = list; patch; patch = patch->next) { - unsigned char sha1[20]; - struct cache_entry *ce; - const char *name; - - name = patch->old_name ? patch->old_name : patch->new_name; - if (0 < patch->is_new) - continue; - - if (S_ISGITLINK(patch->old_mode)) { - if (!preimage_sha1_in_gitlink_patch(patch, sha1)) - ; /* ok, the textual part looks sane */ - else - die("sha1 information is lacking or useless for submodule %s", - name); - } else if (!get_sha1_blob(patch->old_sha1_prefix, sha1)) { - ; /* ok */ - } else if (!patch->lines_added && !patch->lines_deleted) { - /* mode-only change: update the current */ - if (get_current_sha1(patch->old_name, sha1)) - die("mode change for %s, which is not " - "in current HEAD", name); - } else - die("sha1 information is lacking or useless " - "(%s).", name); - - ce = make_cache_entry(patch->old_mode, sha1, name, 0, 0); - if (!ce) - die(_("make_cache_entry failed for path '%s'"), name); - if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD)) - die ("Could not add %s to temporary index", name); - } - - hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); - if (write_locked_index(&result, &lock, COMMIT_LOCK)) - die ("Could not write temporary index to %s", filename); - - discard_index(&result); -} - -static void stat_patch_list(struct apply_state *state, struct patch *patch) -{ - int files, adds, dels; - - for (files = adds = dels = 0 ; patch ; patch = patch->next) { - files++; - adds += patch->lines_added; - dels += patch->lines_deleted; - show_stats(state, patch); - } - - print_stat_summary(stdout, files, adds, dels); -} - -static void numstat_patch_list(struct apply_state *state, - struct patch *patch) -{ - for ( ; patch; patch = patch->next) { - const char *name; - name = patch->new_name ? patch->new_name : patch->old_name; - if (patch->is_binary) - printf("-\t-\t"); - else - printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); - write_name_quoted(name, stdout, state->line_termination); - } -} - -static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name) -{ - if (mode) - printf(" %s mode %06o %s\n", newdelete, mode, name); - else - printf(" %s %s\n", newdelete, name); -} - -static void show_mode_change(struct patch *p, int show_name) -{ - if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) { - if (show_name) - printf(" mode change %06o => %06o %s\n", - p->old_mode, p->new_mode, p->new_name); - else - printf(" mode change %06o => %06o\n", - p->old_mode, p->new_mode); - } -} - -static void show_rename_copy(struct patch *p) -{ - const char *renamecopy = p->is_rename ? "rename" : "copy"; - const char *old, *new; - - /* Find common prefix */ - old = p->old_name; - new = p->new_name; - while (1) { - const char *slash_old, *slash_new; - slash_old = strchr(old, '/'); - slash_new = strchr(new, '/'); - if (!slash_old || - !slash_new || - slash_old - old != slash_new - new || - memcmp(old, new, slash_new - new)) - break; - old = slash_old + 1; - new = slash_new + 1; - } - /* p->old_name thru old is the common prefix, and old and new - * through the end of names are renames - */ - if (old != p->old_name) - printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy, - (int)(old - p->old_name), p->old_name, - old, new, p->score); - else - printf(" %s %s => %s (%d%%)\n", renamecopy, - p->old_name, p->new_name, p->score); - show_mode_change(p, 0); -} - -static void summary_patch_list(struct patch *patch) -{ - struct patch *p; - - for (p = patch; p; p = p->next) { - if (p->is_new) - show_file_mode_name("create", p->new_mode, p->new_name); - else if (p->is_delete) - show_file_mode_name("delete", p->old_mode, p->old_name); - else { - if (p->is_rename || p->is_copy) - show_rename_copy(p); - else { - if (p->score) { - printf(" rewrite %s (%d%%)\n", - p->new_name, p->score); - show_mode_change(p, 0); - } - else - show_mode_change(p, 1); - } - } - } -} - -static void patch_stats(struct apply_state *state, struct patch *patch) -{ - int lines = patch->lines_added + patch->lines_deleted; - - if (lines > state->max_change) - state->max_change = lines; - if (patch->old_name) { - int len = quote_c_style(patch->old_name, NULL, NULL, 0); - if (!len) - len = strlen(patch->old_name); - if (len > state->max_len) - state->max_len = len; - } - if (patch->new_name) { - int len = quote_c_style(patch->new_name, NULL, NULL, 0); - if (!len) - len = strlen(patch->new_name); - if (len > state->max_len) - state->max_len = len; - } -} - -static void remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty) -{ - if (state->update_index) { - if (remove_file_from_cache(patch->old_name) < 0) - die(_("unable to remove %s from index"), patch->old_name); - } - if (!state->cached) { - if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) { - remove_path(patch->old_name); - } - } -} - -static void add_index_file(struct apply_state *state, - const char *path, - unsigned mode, - void *buf, - unsigned long size) -{ - struct stat st; - struct cache_entry *ce; - int namelen = strlen(path); - unsigned ce_size = cache_entry_size(namelen); - - if (!state->update_index) - return; - - ce = xcalloc(1, ce_size); - memcpy(ce->name, path, namelen); - ce->ce_mode = create_ce_mode(mode); - ce->ce_flags = create_ce_flags(0); - ce->ce_namelen = namelen; - if (S_ISGITLINK(mode)) { - const char *s; - - if (!skip_prefix(buf, "Subproject commit ", &s) || - get_sha1_hex(s, ce->sha1)) - die(_("corrupt patch for submodule %s"), path); - } else { - if (!state->cached) { - if (lstat(path, &st) < 0) - die_errno(_("unable to stat newly created file '%s'"), - path); - fill_stat_cache_info(ce, &st); - } - if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0) - die(_("unable to create backing store for newly created file %s"), path); - } - if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) - die(_("unable to add cache entry for %s"), path); -} - -static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size) -{ - int fd; - struct strbuf nbuf = STRBUF_INIT; - - if (S_ISGITLINK(mode)) { - struct stat st; - if (!lstat(path, &st) && S_ISDIR(st.st_mode)) - return 0; - return mkdir(path, 0777); - } - - if (has_symlinks && S_ISLNK(mode)) - /* Although buf:size is counted string, it also is NUL - * terminated. - */ - return symlink(buf, path); - - fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); - if (fd < 0) - return -1; - - if (convert_to_working_tree(path, buf, size, &nbuf)) { - size = nbuf.len; - buf = nbuf.buf; - } - write_or_die(fd, buf, size); - strbuf_release(&nbuf); - - if (close(fd) < 0) - die_errno(_("closing file '%s'"), path); - return 0; -} - -/* - * We optimistically assume that the directories exist, - * which is true 99% of the time anyway. If they don't, - * we create them and try again. - */ -static void create_one_file(struct apply_state *state, - char *path, - unsigned mode, - const char *buf, - unsigned long size) -{ - if (state->cached) - return; - if (!try_create_file(path, mode, buf, size)) - return; - - if (errno == ENOENT) { - if (safe_create_leading_directories(path)) - return; - if (!try_create_file(path, mode, buf, size)) - return; - } - - if (errno == EEXIST || errno == EACCES) { - /* We may be trying to create a file where a directory - * used to be. - */ - struct stat st; - if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path))) - errno = EEXIST; - } - - if (errno == EEXIST) { - unsigned int nr = getpid(); - - for (;;) { - char newpath[PATH_MAX]; - mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr); - if (!try_create_file(newpath, mode, buf, size)) { - if (!rename(newpath, path)) - return; - unlink_or_warn(newpath); - break; - } - if (errno != EEXIST) - break; - ++nr; - } - } - die_errno(_("unable to write file '%s' mode %o"), path, mode); -} - -static void add_conflicted_stages_file(struct apply_state *state, - struct patch *patch) -{ - int stage, namelen; - unsigned ce_size, mode; - struct cache_entry *ce; - - if (!state->update_index) - return; - namelen = strlen(patch->new_name); - ce_size = cache_entry_size(namelen); - mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644); - - remove_file_from_cache(patch->new_name); - for (stage = 1; stage < 4; stage++) { - if (is_null_oid(&patch->threeway_stage[stage - 1])) - continue; - ce = xcalloc(1, ce_size); - memcpy(ce->name, patch->new_name, namelen); - ce->ce_mode = create_ce_mode(mode); - ce->ce_flags = create_ce_flags(stage); - ce->ce_namelen = namelen; - hashcpy(ce->sha1, patch->threeway_stage[stage - 1].hash); - if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) - die(_("unable to add cache entry for %s"), patch->new_name); - } -} - -static void create_file(struct apply_state *state, struct patch *patch) -{ - char *path = patch->new_name; - unsigned mode = patch->new_mode; - unsigned long size = patch->resultsize; - char *buf = patch->result; - - if (!mode) - mode = S_IFREG | 0644; - create_one_file(state, path, mode, buf, size); - - if (patch->conflicted_threeway) - add_conflicted_stages_file(state, patch); - else - add_index_file(state, path, mode, buf, size); -} - -/* phase zero is to remove, phase one is to create */ -static void write_out_one_result(struct apply_state *state, - struct patch *patch, - int phase) -{ - if (patch->is_delete > 0) { - if (phase == 0) - remove_file(state, patch, 1); - return; - } - if (patch->is_new > 0 || patch->is_copy) { - if (phase == 1) - create_file(state, patch); - return; - } - /* - * Rename or modification boils down to the same - * thing: remove the old, write the new - */ - if (phase == 0) - remove_file(state, patch, patch->is_rename); - if (phase == 1) - create_file(state, patch); -} - -static int write_out_one_reject(struct apply_state *state, struct patch *patch) -{ - FILE *rej; - char namebuf[PATH_MAX]; - struct fragment *frag; - int cnt = 0; - struct strbuf sb = STRBUF_INIT; - - for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { - if (!frag->rejected) - continue; - cnt++; - } - - if (!cnt) { - if (state->apply_verbosely) - say_patch_name(stderr, - _("Applied patch %s cleanly."), patch); - return 0; - } - - /* This should not happen, because a removal patch that leaves - * contents are marked "rejected" at the patch level. - */ - if (!patch->new_name) - die(_("internal error")); - - /* Say this even without --verbose */ - strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...", - "Applying patch %%s with %d rejects...", - cnt), - cnt); - say_patch_name(stderr, sb.buf, patch); - strbuf_release(&sb); - - cnt = strlen(patch->new_name); - if (ARRAY_SIZE(namebuf) <= cnt + 5) { - cnt = ARRAY_SIZE(namebuf) - 5; - warning(_("truncating .rej filename to %.*s.rej"), - cnt - 1, patch->new_name); - } - memcpy(namebuf, patch->new_name, cnt); - memcpy(namebuf + cnt, ".rej", 5); - - rej = fopen(namebuf, "w"); - if (!rej) - return error(_("cannot open %s: %s"), namebuf, strerror(errno)); - - /* Normal git tools never deal with .rej, so do not pretend - * this is a git patch by saying --git or giving extended - * headers. While at it, maybe please "kompare" that wants - * the trailing TAB and some garbage at the end of line ;-). - */ - fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", - patch->new_name, patch->new_name); - for (cnt = 1, frag = patch->fragments; - frag; - cnt++, frag = frag->next) { - if (!frag->rejected) { - fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt); - continue; - } - fprintf_ln(stderr, _("Rejected hunk #%d."), cnt); - fprintf(rej, "%.*s", frag->size, frag->patch); - if (frag->patch[frag->size-1] != '\n') - fputc('\n', rej); - } - fclose(rej); - return -1; -} - -static int write_out_results(struct apply_state *state, struct patch *list) -{ - int phase; - int errs = 0; - struct patch *l; - struct string_list cpath = STRING_LIST_INIT_DUP; - - for (phase = 0; phase < 2; phase++) { - l = list; - while (l) { - if (l->rejected) - errs = 1; - else { - write_out_one_result(state, l, phase); - if (phase == 1) { - if (write_out_one_reject(state, l)) - errs = 1; - if (l->conflicted_threeway) { - string_list_append(&cpath, l->new_name); - errs = 1; - } - } - } - l = l->next; - } - } - - if (cpath.nr) { - struct string_list_item *item; - - string_list_sort(&cpath); - for_each_string_list_item(item, &cpath) - fprintf(stderr, "U %s\n", item->string); - string_list_clear(&cpath, 0); - - rerere(0); - } - - return errs; -} - static struct lock_file lock_file; -#define INACCURATE_EOF (1<<0) -#define RECOUNT (1<<1) - -static int apply_patch(struct apply_state *state, - int fd, - const char *filename, - int options) -{ - size_t offset; - struct strbuf buf = STRBUF_INIT; /* owns the patch text */ - struct patch *list = NULL, **listp = &list; - int skipped_patch = 0; - - state->patch_input_file = filename; - read_patch_file(&buf, fd); - offset = 0; - while (offset < buf.len) { - struct patch *patch; - int nr; - - patch = xcalloc(1, sizeof(*patch)); - patch->inaccurate_eof = !!(options & INACCURATE_EOF); - patch->recount = !!(options & RECOUNT); - nr = parse_chunk(state, buf.buf + offset, buf.len - offset, patch); - if (nr < 0) { - free_patch(patch); - break; - } - if (state->apply_in_reverse) - reverse_patches(patch); - if (use_patch(state, patch)) { - patch_stats(state, patch); - *listp = patch; - listp = &patch->next; - } - else { - if (state->apply_verbosely) - say_patch_name(stderr, _("Skipped patch '%s'."), patch); - free_patch(patch); - skipped_patch++; - } - offset += nr; - } - - if (!list && !skipped_patch) - die(_("unrecognized input")); - - if (state->whitespace_error && (state->ws_error_action == die_on_ws_error)) - state->apply = 0; - - state->update_index = state->check_index && state->apply; - if (state->update_index && state->newfd < 0) - state->newfd = hold_locked_index(state->lock_file, 1); - - if (state->check_index) { - if (read_cache() < 0) - die(_("unable to read index file")); - } - - if ((state->check || state->apply) && - check_patch_list(state, list) < 0 && - !state->apply_with_reject) - exit(1); - - if (state->apply && write_out_results(state, list)) { - if (state->apply_with_reject) - exit(1); - /* with --3way, we still need to write the index out */ - return 1; - } - - if (state->fake_ancestor) - build_fake_ancestor(list, state->fake_ancestor); - - if (state->diffstat) - stat_patch_list(state, list); - - if (state->numstat) - numstat_patch_list(state, list); - - if (state->summary) - summary_patch_list(list); - - free_patch_list(list); - strbuf_release(&buf); - string_list_clear(&state->fn_table, 0); - return 0; -} - -static void git_apply_config(void) -{ - git_config_get_string_const("apply.whitespace", &apply_default_whitespace); - git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace); - git_config(git_default_config, NULL); -} - -static int option_parse_exclude(const struct option *opt, - const char *arg, int unset) -{ - struct apply_state *state = opt->value; - add_name_limit(state, arg, 1); - return 0; -} - -static int option_parse_include(const struct option *opt, - const char *arg, int unset) -{ - struct apply_state *state = opt->value; - add_name_limit(state, arg, 0); - state->has_include = 1; - return 0; -} - -static int option_parse_p(const struct option *opt, - const char *arg, - int unset) -{ - struct apply_state *state = opt->value; - state->p_value = atoi(arg); - state->p_value_known = 1; - return 0; -} - -static int option_parse_space_change(const struct option *opt, - const char *arg, int unset) -{ - struct apply_state *state = opt->value; - if (unset) - state->ws_ignore_action = ignore_ws_none; - else - state->ws_ignore_action = ignore_ws_change; - return 0; -} - -static int option_parse_whitespace(const struct option *opt, - const char *arg, int unset) -{ - struct apply_state *state = opt->value; - state->whitespace_option = arg; - parse_whitespace_option(state, arg); - return 0; -} - -static int option_parse_directory(const struct option *opt, - const char *arg, int unset) -{ - struct apply_state *state = opt->value; - strbuf_reset(&state->root); - strbuf_addstr(&state->root, arg); - strbuf_complete(&state->root, '/'); - return 0; -} - -static void init_apply_state(struct apply_state *state, - const char *prefix, - struct lock_file *lock_file) -{ - memset(state, 0, sizeof(*state)); - state->prefix = prefix; - state->prefix_length = state->prefix ? strlen(state->prefix) : 0; - state->lock_file = lock_file; - state->newfd = -1; - state->apply = 1; - state->line_termination = '\n'; - state->p_value = 1; - state->p_context = UINT_MAX; - state->squelch_whitespace_errors = 5; - state->ws_error_action = warn_on_ws_error; - state->ws_ignore_action = ignore_ws_none; - state->linenr = 1; - string_list_init(&state->fn_table, 0); - string_list_init(&state->limit_by_name, 0); - string_list_init(&state->symlink_changes, 0); - strbuf_init(&state->root, 0); - - git_apply_config(); - if (apply_default_whitespace) - parse_whitespace_option(state, apply_default_whitespace); - if (apply_default_ignorewhitespace) - parse_ignorewhitespace_option(state, apply_default_ignorewhitespace); -} - -static void clear_apply_state(struct apply_state *state) -{ - string_list_clear(&state->limit_by_name, 0); - string_list_clear(&state->symlink_changes, 0); - strbuf_release(&state->root); - - /* &state->fn_table is cleared at the end of apply_patch() */ -} - -static void check_apply_state(struct apply_state *state, int force_apply) -{ - int is_not_gitdir = !startup_info->have_repository; - - if (state->apply_with_reject && state->threeway) - die("--reject and --3way cannot be used together."); - if (state->cached && state->threeway) - die("--cached and --3way cannot be used together."); - if (state->threeway) { - if (is_not_gitdir) - die(_("--3way outside a repository")); - state->check_index = 1; - } - if (state->apply_with_reject) - state->apply = state->apply_verbosely = 1; - if (!force_apply && (state->diffstat || state->numstat || state->summary || state->check || state->fake_ancestor)) - state->apply = 0; - if (state->check_index && is_not_gitdir) - die(_("--index outside a repository")); - if (state->cached) { - if (is_not_gitdir) - die(_("--cached outside a repository")); - state->check_index = 1; - } - if (state->check_index) - state->unsafe_paths = 0; - if (!state->lock_file) - die("BUG: state->lock_file should not be NULL"); -} - -static int apply_all_patches(struct apply_state *state, - int argc, - const char **argv, - int options) -{ - int i; - int errs = 0; - int read_stdin = 1; - - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - int fd; - - if (!strcmp(arg, "-")) { - errs |= apply_patch(state, 0, "<stdin>", options); - read_stdin = 0; - continue; - } else if (0 < state->prefix_length) - arg = prefix_filename(state->prefix, - state->prefix_length, - arg); - - fd = open(arg, O_RDONLY); - if (fd < 0) - die_errno(_("can't open patch '%s'"), arg); - read_stdin = 0; - set_default_whitespace_mode(state); - errs |= apply_patch(state, fd, arg, options); - close(fd); - } - set_default_whitespace_mode(state); - if (read_stdin) - errs |= apply_patch(state, 0, "<stdin>", options); - - if (state->whitespace_error) { - if (state->squelch_whitespace_errors && - state->squelch_whitespace_errors < state->whitespace_error) { - int squelched = - state->whitespace_error - state->squelch_whitespace_errors; - warning(Q_("squelched %d whitespace error", - "squelched %d whitespace errors", - squelched), - squelched); - } - if (state->ws_error_action == die_on_ws_error) - die(Q_("%d line adds whitespace errors.", - "%d lines add whitespace errors.", - state->whitespace_error), - state->whitespace_error); - if (state->applied_after_fixing_ws && state->apply) - warning("%d line%s applied after" - " fixing whitespace errors.", - state->applied_after_fixing_ws, - state->applied_after_fixing_ws == 1 ? "" : "s"); - else if (state->whitespace_error) - warning(Q_("%d line adds whitespace errors.", - "%d lines add whitespace errors.", - state->whitespace_error), - state->whitespace_error); - } - - if (state->update_index) { - if (write_locked_index(&the_index, state->lock_file, COMMIT_LOCK)) - die(_("Unable to write new index file")); - state->newfd = -1; - } - - return !!errs; -} - int cmd_apply(int argc, const char **argv, const char *prefix) { int force_apply = 0; @@ -4795,81 +18,15 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int ret; struct apply_state state; - struct option builtin_apply_options[] = { - { OPTION_CALLBACK, 0, "exclude", &state, N_("path"), - N_("don't apply changes matching the given path"), - 0, option_parse_exclude }, - { OPTION_CALLBACK, 0, "include", &state, N_("path"), - N_("apply changes matching the given path"), - 0, option_parse_include }, - { OPTION_CALLBACK, 'p', NULL, &state, N_("num"), - N_("remove <num> leading slashes from traditional diff paths"), - 0, option_parse_p }, - OPT_BOOL(0, "no-add", &state.no_add, - N_("ignore additions made by the patch")), - OPT_BOOL(0, "stat", &state.diffstat, - N_("instead of applying the patch, output diffstat for the input")), - OPT_NOOP_NOARG(0, "allow-binary-replacement"), - OPT_NOOP_NOARG(0, "binary"), - OPT_BOOL(0, "numstat", &state.numstat, - N_("show number of added and deleted lines in decimal notation")), - OPT_BOOL(0, "summary", &state.summary, - N_("instead of applying the patch, output a summary for the input")), - OPT_BOOL(0, "check", &state.check, - N_("instead of applying the patch, see if the patch is applicable")), - OPT_BOOL(0, "index", &state.check_index, - N_("make sure the patch is applicable to the current index")), - OPT_BOOL(0, "cached", &state.cached, - N_("apply a patch without touching the working tree")), - OPT_BOOL(0, "unsafe-paths", &state.unsafe_paths, - N_("accept a patch that touches outside the working area")), - OPT_BOOL(0, "apply", &force_apply, - N_("also apply the patch (use with --stat/--summary/--check)")), - OPT_BOOL('3', "3way", &state.threeway, - N_( "attempt three-way merge if a patch does not apply")), - OPT_FILENAME(0, "build-fake-ancestor", &state.fake_ancestor, - N_("build a temporary index based on embedded index information")), - /* Think twice before adding "--nul" synonym to this */ - OPT_SET_INT('z', NULL, &state.line_termination, - N_("paths are separated with NUL character"), '\0'), - OPT_INTEGER('C', NULL, &state.p_context, - N_("ensure at least <n> lines of context match")), - { OPTION_CALLBACK, 0, "whitespace", &state, N_("action"), - N_("detect new or modified lines that have whitespace errors"), - 0, option_parse_whitespace }, - { OPTION_CALLBACK, 0, "ignore-space-change", &state, NULL, - N_("ignore changes in whitespace when finding context"), - PARSE_OPT_NOARG, option_parse_space_change }, - { OPTION_CALLBACK, 0, "ignore-whitespace", &state, NULL, - N_("ignore changes in whitespace when finding context"), - PARSE_OPT_NOARG, option_parse_space_change }, - OPT_BOOL('R', "reverse", &state.apply_in_reverse, - N_("apply the patch in reverse")), - OPT_BOOL(0, "unidiff-zero", &state.unidiff_zero, - N_("don't expect at least one line of context")), - OPT_BOOL(0, "reject", &state.apply_with_reject, - N_("leave the rejected hunks in corresponding *.rej files")), - OPT_BOOL(0, "allow-overlap", &state.allow_overlap, - N_("allow overlapping hunks")), - OPT__VERBOSE(&state.apply_verbosely, N_("be verbose")), - OPT_BIT(0, "inaccurate-eof", &options, - N_("tolerate incorrectly detected missing new-line at the end of file"), - INACCURATE_EOF), - OPT_BIT(0, "recount", &options, - N_("do not trust the line counts in the hunk headers"), - RECOUNT), - { OPTION_CALLBACK, 0, "directory", &state, N_("root"), - N_("prepend <root> to all filenames"), - 0, option_parse_directory }, - OPT_END() - }; - - init_apply_state(&state, prefix, &lock_file); + if (init_apply_state(&state, prefix, &lock_file)) + exit(128); - argc = parse_options(argc, argv, state.prefix, builtin_apply_options, - apply_usage, 0); + argc = apply_parse_options(argc, argv, + &state, &force_apply, &options, + apply_usage); - check_apply_state(&state, force_apply); + if (check_apply_state(&state, force_apply)) + exit(128); ret = apply_all_patches(&state, argc, argv, options); diff --git a/builtin/blame.c b/builtin/blame.c index 27aea7944c..a7bd7a6fd8 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -120,7 +120,7 @@ struct origin { */ struct blame_entry *suspects; mmfile_t file; - unsigned char blob_sha1[20]; + struct object_id blob_oid; unsigned mode; /* guilty gets set when shipping any suspects to the final * blame list instead of other commits @@ -154,8 +154,8 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, */ int textconv_object(const char *path, unsigned mode, - const unsigned char *sha1, - int sha1_valid, + const struct object_id *oid, + int oid_valid, char **buf, unsigned long *buf_size) { @@ -163,7 +163,7 @@ int textconv_object(const char *path, struct userdiff_driver *textconv; df = alloc_filespec(path); - fill_filespec(df, sha1, sha1_valid, mode); + fill_filespec(df, oid->hash, oid_valid, mode); textconv = get_textconv(df); if (!textconv) { free_filespec(df); @@ -188,15 +188,16 @@ static void fill_origin_blob(struct diff_options *opt, num_read_blob++; if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size)) + textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size)) ; else - file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size); + 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", - sha1_to_hex(o->blob_sha1), + oid_to_hex(&o->blob_oid), o->path); o->file = *file; } @@ -508,17 +509,17 @@ static struct origin *get_origin(struct scoreboard *sb, */ static int fill_blob_sha1_and_mode(struct origin *origin) { - if (!is_null_sha1(origin->blob_sha1)) + if (!is_null_oid(&origin->blob_oid)) return 0; if (get_tree_entry(origin->commit->object.oid.hash, origin->path, - origin->blob_sha1, &origin->mode)) + origin->blob_oid.hash, &origin->mode)) goto error_out; - if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB) + if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB) goto error_out; return 0; error_out: - hashclr(origin->blob_sha1); + oidclr(&origin->blob_oid); origin->mode = S_IFINVALID; return -1; } @@ -572,7 +573,7 @@ static struct origin *find_origin(struct scoreboard *sb, if (!diff_queued_diff.nr) { /* The path is the same as parent */ porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, origin->blob_sha1); + oidcpy(&porigin->blob_oid, &origin->blob_oid); porigin->mode = origin->mode; } else { /* @@ -598,7 +599,7 @@ static struct origin *find_origin(struct scoreboard *sb, p->status); case 'M': porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, p->one->oid.hash); + oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; break; case 'A': @@ -644,7 +645,7 @@ static struct origin *find_rename(struct scoreboard *sb, if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, origin->path)) { porigin = get_origin(sb, parent, p->one->path); - hashcpy(porigin->blob_sha1, p->one->oid.hash); + oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; break; } @@ -1308,7 +1309,7 @@ static void find_copy_in_parent(struct scoreboard *sb, continue; norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->oid.hash); + 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) @@ -1458,15 +1459,14 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) porigin = find(sb, p, origin); if (!porigin) continue; - if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { + 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] && - !hashcmp(sg_origin[j]->blob_sha1, - porigin->blob_sha1)) { + !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) { same = 1; break; } @@ -1941,7 +1941,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev; if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) @@ -2220,6 +2220,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) return 0; } + if (git_diff_heuristic_config(var, value, cb) < 0) + return -1; if (userdiff_config(var, value) < 0) return -1; @@ -2232,12 +2234,12 @@ static void verify_working_tree_path(struct commit *work_tree, const char *path) int pos; for (parents = work_tree->parents; parents; parents = parents->next) { - const unsigned char *commit_sha1 = parents->item->object.oid.hash; - unsigned char blob_sha1[20]; + const struct object_id *commit_oid = &parents->item->object.oid; + struct object_id blob_oid; unsigned mode; - if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) && - sha1_object_info(blob_sha1, NULL) == OBJ_BLOB) + if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) && + sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB) return; } @@ -2251,13 +2253,13 @@ static void verify_working_tree_path(struct commit *work_tree, const char *path) die("no such path '%s' in HEAD", path); } -static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1) +static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid) { struct commit *parent; - parent = lookup_commit_reference(sha1); + parent = lookup_commit_reference(oid->hash); if (!parent) - die("no such commit %s", sha1_to_hex(sha1)); + die("no such commit %s", oid_to_hex(oid)); return &commit_list_insert(parent, tail)->next; } @@ -2274,10 +2276,10 @@ static void append_merge_parents(struct commit_list **tail) } while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { - unsigned char sha1[20]; - if (line.len < 40 || get_sha1_hex(line.buf, sha1)) + 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, sha1); + tail = append_parent(tail, &oid); } close(merge_head); strbuf_release(&line); @@ -2306,7 +2308,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, struct commit *commit; struct origin *origin; struct commit_list **parent_tail, *parent; - unsigned char head_sha1[20]; + struct object_id head_oid; struct strbuf buf = STRBUF_INIT; const char *ident; time_t now; @@ -2322,10 +2324,10 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, commit->date = now; parent_tail = &commit->parents; - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) die("no such ref: HEAD"); - parent_tail = append_parent(parent_tail, head_sha1); + parent_tail = append_parent(parent_tail, &head_oid); append_merge_parents(parent_tail); verify_working_tree_path(commit, path); @@ -2366,7 +2368,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, switch (st.st_mode & S_IFMT) { case S_IFREG: if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(read_from, mode, null_sha1, 0, &buf_ptr, &buf_len)) + 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); @@ -2388,7 +2390,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, 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_sha1); + pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash); /* * Read the current index, replace the path entry with @@ -2410,7 +2412,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, } size = cache_entry_size(len); ce = xcalloc(1, size); - hashcpy(ce->sha1, origin->blob_sha1); + oidcpy(&ce->oid, &origin->blob_oid); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(0); ce->ce_namelen = len; @@ -2550,6 +2552,15 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + + /* + * The following two options are parsed by parse_revision_opt() + * and are only included here to get included in the "-h" + * output: + */ + { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental indent-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + { OPTION_LOWLEVEL_CALLBACK, 0, "compaction-heuristic", NULL, NULL, N_("Use an experimental blank-line-based heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), @@ -2596,6 +2607,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } parse_done: no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); + xdl_opts |= revs.diffopt.xdl_opts & (XDF_COMPACTION_HEURISTIC | XDF_INDENT_HEURISTIC); DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); @@ -2793,16 +2805,16 @@ parse_done: 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_sha1, 1, (char **) &sb.final_buf, + 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_sha1, &type, + 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"), - sha1_to_hex(o->blob_sha1), + oid_to_hex(&o->blob_oid), path); } num_read_blob++; diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 2dfe6265f7..94e67ebb7e 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -17,13 +17,38 @@ struct batch_options { int print_contents; int buffer_output; int all_objects; + int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */ const char *format; }; +static const char *force_path; + +static int filter_object(const char *path, unsigned mode, + const struct object_id *oid, + char **buf, unsigned long *size) +{ + enum object_type type; + + *buf = read_sha1_file(oid->hash, &type, size); + if (!*buf) + return error(_("cannot read object %s '%s'"), + oid_to_hex(oid), path); + if ((type == OBJ_BLOB) && S_ISREG(mode)) { + struct strbuf strbuf = STRBUF_INIT; + if (convert_to_working_tree(path, *buf, *size, &strbuf)) { + free(*buf); + *size = strbuf.len; + *buf = strbuf_detach(&strbuf, NULL); + } + } + + return 0; +} + static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) { - unsigned char sha1[20]; + struct object_id oid; enum object_type type; char *buf; unsigned long size; @@ -31,18 +56,24 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, struct object_info oi = {NULL}; struct strbuf sb = STRBUF_INIT; unsigned flags = LOOKUP_REPLACE_OBJECT; + const char *path = force_path; if (unknown_type) flags |= LOOKUP_UNKNOWN_OBJECT; - if (get_sha1_with_context(obj_name, 0, sha1, &obj_context)) + if (get_sha1_with_context(obj_name, 0, oid.hash, &obj_context)) die("Not a valid object name %s", obj_name); + if (!path) + path = obj_context.path; + if (obj_context.mode == S_IFINVALID) + obj_context.mode = 0100644; + buf = NULL; switch (opt) { case 't': oi.typename = &sb; - if (sha1_object_info_extended(sha1, &oi, flags) < 0) + if (sha1_object_info_extended(oid.hash, &oi, flags) < 0) die("git cat-file: could not get object info"); if (sb.len) { printf("%s\n", sb.buf); @@ -53,24 +84,34 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 's': oi.sizep = &size; - if (sha1_object_info_extended(sha1, &oi, flags) < 0) + if (sha1_object_info_extended(oid.hash, &oi, flags) < 0) die("git cat-file: could not get object info"); printf("%lu\n", size); return 0; case 'e': - return !has_sha1_file(sha1); + return !has_object_file(&oid); + + case 'w': + if (!path[0]) + die("git cat-file --filters %s: <object> must be " + "<sha1:path>", obj_name); + + if (filter_object(path, obj_context.mode, + &oid, &buf, &size)) + return -1; + break; case 'c': - if (!obj_context.path[0]) + if (!path[0]) die("git cat-file --textconv %s: <object> must be <sha1:path>", obj_name); - if (textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size)) + if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size)) break; case 'p': - type = sha1_object_info(sha1, NULL); + type = sha1_object_info(oid.hash, NULL); if (type < 0) die("Not a valid object name %s", obj_name); @@ -83,8 +124,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } if (type == OBJ_BLOB) - return stream_blob_to_fd(1, sha1, NULL, 0); - buf = read_sha1_file(sha1, &type, &size); + return stream_blob_to_fd(1, &oid, NULL, 0); + buf = read_sha1_file(oid.hash, &type, &size); if (!buf) die("Cannot read object %s", obj_name); @@ -93,19 +134,19 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 0: if (type_from_string(exp_type) == OBJ_BLOB) { - unsigned char blob_sha1[20]; - if (sha1_object_info(sha1, NULL) == OBJ_TAG) { - char *buffer = read_sha1_file(sha1, &type, &size); + struct object_id blob_oid; + if (sha1_object_info(oid.hash, NULL) == OBJ_TAG) { + char *buffer = read_sha1_file(oid.hash, &type, &size); const char *target; if (!skip_prefix(buffer, "object ", &target) || - get_sha1_hex(target, blob_sha1)) - die("%s not a valid tag", sha1_to_hex(sha1)); + get_oid_hex(target, &blob_oid)) + die("%s not a valid tag", oid_to_hex(&oid)); free(buffer); } else - hashcpy(blob_sha1, sha1); + oidcpy(&blob_oid, &oid); - if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB) - return stream_blob_to_fd(1, blob_sha1, NULL, 0); + if (sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB) + return stream_blob_to_fd(1, &blob_oid, NULL, 0); /* * we attempted to dereference a tag to a blob * and failed; there may be new dereference @@ -113,7 +154,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, * fall-back to the usual case. */ } - buf = read_object_with_reference(sha1, exp_type, &size, NULL); + buf = read_object_with_reference(oid.hash, exp_type, &size, NULL); break; default: @@ -128,12 +169,12 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } struct expand_data { - unsigned char sha1[20]; + struct object_id oid; enum object_type type; unsigned long size; off_t disk_size; const char *rest; - unsigned char delta_base_sha1[20]; + struct object_id delta_base_oid; /* * If mark_query is true, we do not expand anything, but rather @@ -176,7 +217,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, if (is_atom("objectname", atom, len)) { if (!data->mark_query) - strbuf_addstr(sb, sha1_to_hex(data->sha1)); + strbuf_addstr(sb, oid_to_hex(&data->oid)); } else if (is_atom("objecttype", atom, len)) { if (data->mark_query) data->info.typep = &data->type; @@ -199,9 +240,10 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, strbuf_addstr(sb, data->rest); } else if (is_atom("deltabase", atom, len)) { if (data->mark_query) - data->info.delta_base_sha1 = data->delta_base_sha1; + data->info.delta_base_sha1 = data->delta_base_oid.hash; else - strbuf_addstr(sb, sha1_to_hex(data->delta_base_sha1)); + strbuf_addstr(sb, + oid_to_hex(&data->delta_base_oid)); } else die("unknown format element: %.*s", len, atom); } @@ -232,28 +274,53 @@ static void batch_write(struct batch_options *opt, const void *data, int len) static void print_object_or_die(struct batch_options *opt, struct expand_data *data) { - const unsigned char *sha1 = data->sha1; + const struct object_id *oid = &data->oid; assert(data->info.typep); if (data->type == OBJ_BLOB) { if (opt->buffer_output) fflush(stdout); - if (stream_blob_to_fd(1, sha1, NULL, 0) < 0) - die("unable to stream %s to stdout", sha1_to_hex(sha1)); + if (opt->cmdmode) { + char *contents; + unsigned long size; + + if (!data->rest) + die("missing path for '%s'", oid_to_hex(oid)); + + if (opt->cmdmode == 'w') { + if (filter_object(data->rest, 0100644, oid, + &contents, &size)) + die("could not convert '%s' %s", + oid_to_hex(oid), data->rest); + } else if (opt->cmdmode == 'c') { + enum object_type type; + if (!textconv_object(data->rest, 0100644, oid, + 1, &contents, &size)) + contents = read_sha1_file(oid->hash, &type, + &size); + if (!contents) + die("could not convert '%s' %s", + oid_to_hex(oid), data->rest); + } else + die("BUG: invalid cmdmode: %c", opt->cmdmode); + batch_write(opt, contents, size); + free(contents); + } else if (stream_blob_to_fd(1, oid, NULL, 0) < 0) + die("unable to stream %s to stdout", oid_to_hex(oid)); } else { enum object_type type; unsigned long size; void *contents; - contents = read_sha1_file(sha1, &type, &size); + contents = read_sha1_file(oid->hash, &type, &size); if (!contents) - die("object %s disappeared", sha1_to_hex(sha1)); + die("object %s disappeared", oid_to_hex(oid)); if (type != data->type) - die("object %s changed type!?", sha1_to_hex(sha1)); + die("object %s changed type!?", oid_to_hex(oid)); if (data->info.sizep && size != data->size) - die("object %s changed size!?", sha1_to_hex(sha1)); + die("object %s changed size!?", oid_to_hex(oid)); batch_write(opt, contents, size); free(contents); @@ -266,8 +333,9 @@ static void batch_object_write(const char *obj_name, struct batch_options *opt, struct strbuf buf = STRBUF_INIT; if (!data->skip_object_info && - sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { - printf("%s missing\n", obj_name ? obj_name : sha1_to_hex(data->sha1)); + sha1_object_info_extended(data->oid.hash, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { + printf("%s missing\n", + obj_name ? obj_name : oid_to_hex(&data->oid)); fflush(stdout); return; } @@ -290,7 +358,7 @@ static void batch_one_object(const char *obj_name, struct batch_options *opt, int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0; enum follow_symlinks_result result; - result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx); + result = get_sha1_with_context(obj_name, flags, data->oid.hash, &ctx); if (result != FOUND) { switch (result) { case MISSING_OBJECT: @@ -336,7 +404,7 @@ struct object_cb_data { static void batch_object_cb(const unsigned char sha1[20], void *vdata) { struct object_cb_data *data = vdata; - hashcpy(data->expand->sha1, sha1); + hashcpy(data->expand->oid.hash, sha1); batch_object_write(NULL, data->opt, data->expand); } @@ -376,6 +444,8 @@ static int batch_objects(struct batch_options *opt) data.mark_query = 1; strbuf_expand(&buf, opt->format, expand_format, &data); data.mark_query = 0; + if (opt->cmdmode) + data.split_on_whitespace = 1; if (opt->all_objects) { struct object_info empty; @@ -440,8 +510,8 @@ static int batch_objects(struct batch_options *opt) } static const char * const cat_file_usage[] = { - N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"), - N_("git cat-file (--batch | --batch-check) [--follow-symlinks]"), + N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"), + N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"), NULL }; @@ -486,6 +556,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), OPT_CMDMODE(0, "textconv", &opt, N_("for blob objects, run textconv on object's content"), 'c'), + OPT_CMDMODE(0, "filters", &opt, + N_("for blob objects, run filters on object's content"), 'w'), + OPT_STRING(0, "path", &force_path, N_("blob"), + N_("use a specific path for --textconv/--filters")), OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), @@ -508,7 +582,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); if (opt) { - if (argc == 1) + if (batch.enabled && (opt == 'c' || opt == 'w')) + batch.cmdmode = opt; + else if (argc == 1) obj_name = argv[0]; else usage_with_options(cat_file_usage, options); @@ -520,14 +596,28 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) } else usage_with_options(cat_file_usage, options); } - if (batch.enabled && (opt || argc)) { - usage_with_options(cat_file_usage, options); + if (batch.enabled) { + if (batch.cmdmode != opt || argc) + usage_with_options(cat_file_usage, options); + if (batch.cmdmode && batch.all_objects) + die("--batch-all-objects cannot be combined with " + "--textconv nor with --filters"); } if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { usage_with_options(cat_file_usage, options); } + if (force_path && opt != 'c' && opt != 'w') { + error("--path=<path> needs --textconv or --filters"); + usage_with_options(cat_file_usage, options); + } + + if (force_path && batch.enabled) { + error("--path=<path> incompatible with --batch"); + usage_with_options(cat_file_usage, options); + } + if (batch.buffer_output < 0) batch.buffer_output = batch.all_objects; diff --git a/builtin/checkout.c b/builtin/checkout.c index 8672d0724f..9941abc3ac 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -76,7 +76,7 @@ static int update_some(const unsigned char *sha1, struct strbuf *base, len = base->len + strlen(pathname); ce = xcalloc(1, cache_entry_size(len)); - hashcpy(ce->sha1, sha1); + hashcpy(ce->oid.hash, sha1); memcpy(ce->name, base->buf, base->len); memcpy(ce->name + base->len, pathname, len - base->len); ce->ce_flags = create_ce_flags(0) | CE_UPDATE; @@ -92,7 +92,7 @@ static int update_some(const unsigned char *sha1, struct strbuf *base, if (pos >= 0) { struct cache_entry *old = active_cache[pos]; if (ce->ce_mode == old->ce_mode && - !hashcmp(ce->sha1, old->sha1)) { + !oidcmp(&ce->oid, &old->oid)) { old->ce_flags |= CE_UPDATE; free(ce); return 0; @@ -154,8 +154,8 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) return 0; } -static int checkout_stage(int stage, struct cache_entry *ce, int pos, - struct checkout *state) +static int checkout_stage(int stage, const struct cache_entry *ce, int pos, + const struct checkout *state) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -169,15 +169,15 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, return error(_("path '%s' does not have their version"), ce->name); } -static int checkout_merged(int pos, struct checkout *state) +static int checkout_merged(int pos, const struct checkout *state) { struct cache_entry *ce = active_cache[pos]; const char *path = ce->name; mmfile_t ancestor, ours, theirs; int status; - unsigned char sha1[20]; + struct object_id oid; mmbuffer_t result_buf; - unsigned char threeway[3][20]; + struct object_id threeway[3]; unsigned mode = 0; memset(threeway, 0, sizeof(threeway)); @@ -186,18 +186,18 @@ static int checkout_merged(int pos, struct checkout *state) stage = ce_stage(ce); if (!stage || strcmp(path, ce->name)) break; - hashcpy(threeway[stage - 1], ce->sha1); + oidcpy(&threeway[stage - 1], &ce->oid); if (stage == 2) mode = create_ce_mode(ce->ce_mode); pos++; ce = active_cache[pos]; } - if (is_null_sha1(threeway[1]) || is_null_sha1(threeway[2])) + if (is_null_oid(&threeway[1]) || is_null_oid(&threeway[2])) return error(_("path '%s' does not have necessary versions"), path); - read_mmblob(&ancestor, threeway[0]); - read_mmblob(&ours, threeway[1]); - read_mmblob(&theirs, threeway[2]); + read_mmblob(&ancestor, &threeway[0]); + read_mmblob(&ours, &threeway[1]); + read_mmblob(&theirs, &threeway[2]); /* * NEEDSWORK: re-create conflicts from merges with @@ -226,9 +226,9 @@ static int checkout_merged(int pos, struct checkout *state) * object database even when it may contain conflicts). */ if (write_sha1_file(result_buf.ptr, result_buf.size, - blob_type, sha1)) + blob_type, oid.hash)) die(_("Unable to add merge result for '%s'"), path); - ce = make_cache_entry(mode, sha1, path, 2, 0); + 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); @@ -241,7 +241,7 @@ static int checkout_paths(const struct checkout_opts *opts, int pos; struct checkout state; static char *ps_matched; - unsigned char rev[20]; + struct object_id rev; struct commit *head; int errs = 0; struct lock_file *lock_file; @@ -374,8 +374,8 @@ static int checkout_paths(const struct checkout_opts *opts, if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - read_ref_full("HEAD", 0, rev, NULL); - head = lookup_commit_reference_gently(rev, 1); + read_ref_full("HEAD", 0, rev.hash, NULL); + head = lookup_commit_reference_gently(rev.hash, 1); errs |= post_checkout_hook(head, head, 0); return errs; @@ -808,11 +808,11 @@ static int switch_branches(const struct checkout_opts *opts, int ret = 0; struct branch_info old; void *path_to_free; - unsigned char rev[20]; + struct object_id rev; int flag, writeout_error = 0; memset(&old, 0, sizeof(old)); - old.path = path_to_free = resolve_refdup("HEAD", 0, rev, &flag); - old.commit = lookup_commit_reference_gently(rev, 1); + old.path = path_to_free = resolve_refdup("HEAD", 0, rev.hash, &flag); + old.commit = lookup_commit_reference_gently(rev.hash, 1); if (!(flag & REF_ISSYMREF)) old.path = NULL; @@ -860,7 +860,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb) struct tracking_name_data { /* const */ char *src_ref; char *dst_ref; - unsigned char *dst_sha1; + struct object_id *dst_oid; int unique; }; @@ -871,7 +871,7 @@ static int check_tracking_name(struct remote *remote, void *cb_data) memset(&query, 0, sizeof(struct refspec)); query.src = cb->src_ref; if (remote_find_tracking(remote, &query) || - get_sha1(query.dst, cb->dst_sha1)) { + get_oid(query.dst, cb->dst_oid)) { free(query.dst); return 0; } @@ -884,13 +884,13 @@ static int check_tracking_name(struct remote *remote, void *cb_data) return 0; } -static const char *unique_tracking_name(const char *name, unsigned char *sha1) +static const char *unique_tracking_name(const char *name, struct object_id *oid) { struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 }; char src_ref[PATH_MAX]; snprintf(src_ref, PATH_MAX, "refs/heads/%s", name); cb_data.src_ref = src_ref; - cb_data.dst_sha1 = sha1; + cb_data.dst_oid = oid; for_each_remote(check_tracking_name, &cb_data); if (cb_data.unique) return cb_data.dst_ref; @@ -902,12 +902,12 @@ static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, struct checkout_opts *opts, - unsigned char rev[20]) + struct object_id *rev) { struct tree **source_tree = &opts->source_tree; const char **new_branch = &opts->new_branch; int argcount = 0; - unsigned char branch_rev[20]; + struct object_id branch_rev; const char *arg; int dash_dash_pos; int has_dash_dash = 0; @@ -973,7 +973,7 @@ static int parse_branchname_arg(int argc, const char **argv, if (!strcmp(arg, "-")) arg = "@{-1}"; - if (get_sha1_mb(arg, rev)) { + if (get_oid_mb(arg, rev)) { /* * Either case (3) or (4), with <something> not being * a commit, or an attempt to use case (1) with an @@ -1022,15 +1022,15 @@ static int parse_branchname_arg(int argc, const char **argv, setup_branch_path(new); if (!check_refname_format(new->path, 0) && - !read_ref(new->path, branch_rev)) - hashcpy(rev, branch_rev); + !read_ref(new->path, branch_rev.hash)) + oidcpy(rev, &branch_rev); else new->path = NULL; /* not an existing branch */ - new->commit = lookup_commit_reference_gently(rev, 1); + new->commit = lookup_commit_reference_gently(rev->hash, 1); if (!new->commit) { /* not a commit */ - *source_tree = parse_tree_indirect(rev); + *source_tree = parse_tree_indirect(rev->hash); } else { parse_commit_or_die(new->commit); *source_tree = new->commit->tree; @@ -1108,9 +1108,9 @@ static int checkout_branch(struct checkout_opts *opts, if (new->path && !opts->force_detach && !opts->new_branch && !opts->ignore_other_worktrees) { - unsigned char sha1[20]; + struct object_id oid; int flag; - char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); + char *head_ref = resolve_refdup("HEAD", 0, oid.hash, &flag); if (head_ref && (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) die_if_checked_out(new->path, 1); @@ -1118,11 +1118,11 @@ static int checkout_branch(struct checkout_opts *opts, } if (!new->commit && opts->new_branch) { - unsigned char rev[20]; + struct object_id rev; int flag; - if (!read_ref_full("HEAD", 0, rev, &flag) && - (flag & REF_ISSYMREF) && is_null_sha1(rev)) + if (!read_ref_full("HEAD", 0, rev.hash, &flag) && + (flag & REF_ISSYMREF) && is_null_oid(&rev)) return switch_unborn_to_new_branch(opts); } return switch_branches(opts, new); @@ -1232,14 +1232,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * remote branches, erroring out for invalid or ambiguous cases. */ if (argc) { - unsigned char rev[20]; + struct object_id rev; int dwim_ok = !opts.patch_mode && dwim_new_local_branch && opts.track == BRANCH_TRACK_UNSPECIFIED && !opts.new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new, &opts, rev); + &new, &opts, &rev); argv += n; argc -= n; } diff --git a/builtin/clone.c b/builtin/clone.c index f044a8c27f..404c5e8022 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -50,7 +50,8 @@ static int option_verbosity; static int option_progress = -1; static enum transport_family family; static struct string_list option_config = STRING_LIST_INIT_NODUP; -static struct string_list option_reference = STRING_LIST_INIT_NODUP; +static struct string_list option_required_reference = STRING_LIST_INIT_NODUP; +static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP; static int option_dissociate; static int max_jobs = -1; @@ -79,8 +80,10 @@ static struct option builtin_clone_options[] = { N_("number of submodules cloned in parallel")), OPT_STRING(0, "template", &option_template, N_("template-directory"), N_("directory from which templates will be used")), - OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"), + OPT_STRING_LIST(0, "reference", &option_required_reference, N_("repo"), N_("reference repository")), + OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference, + N_("repo"), N_("reference repository")), OPT_BOOL(0, "dissociate", &option_dissociate, N_("use --reference only while cloning")), OPT_STRING('o', "origin", &option_origin, N_("name"), @@ -282,50 +285,37 @@ static void strip_trailing_slashes(char *dir) static int add_one_reference(struct string_list_item *item, void *cb_data) { - char *ref_git; - const char *repo; - struct strbuf alternate = STRBUF_INIT; - - /* Beware: read_gitfile(), real_path() and mkpath() return static buffer */ - ref_git = xstrdup(real_path(item->string)); - - repo = read_gitfile(ref_git); - if (!repo) - repo = read_gitfile(mkpath("%s/.git", ref_git)); - if (repo) { - free(ref_git); - ref_git = xstrdup(repo); - } + struct strbuf err = STRBUF_INIT; + int *required = cb_data; + char *ref_git = compute_alternate_path(item->string, &err); - if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) { - char *ref_git_git = mkpathdup("%s/.git", ref_git); - free(ref_git); - ref_git = ref_git_git; - } else if (!is_directory(mkpath("%s/objects", ref_git))) { + if (!ref_git) { + if (*required) + die("%s", err.buf); + else + fprintf(stderr, + _("info: Could not add alternate for '%s': %s\n"), + item->string, err.buf); + } else { struct strbuf sb = STRBUF_INIT; - if (get_common_dir(&sb, ref_git)) - die(_("reference repository '%s' as a linked checkout is not supported yet."), - item->string); - die(_("reference repository '%s' is not a local repository."), - item->string); + strbuf_addf(&sb, "%s/objects", ref_git); + add_to_alternates_file(sb.buf); + strbuf_release(&sb); } - if (!access(mkpath("%s/shallow", ref_git), F_OK)) - die(_("reference repository '%s' is shallow"), item->string); - - if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) - die(_("reference repository '%s' is grafted"), item->string); - - strbuf_addf(&alternate, "%s/objects", ref_git); - add_to_alternates_file(alternate.buf); - strbuf_release(&alternate); + strbuf_release(&err); free(ref_git); return 0; } static void setup_reference(void) { - for_each_string_list(&option_reference, add_one_reference, NULL); + int required = 1; + for_each_string_list(&option_required_reference, + add_one_reference, &required); + required = 0; + for_each_string_list(&option_optional_reference, + add_one_reference, &required); } static void copy_alternates(struct strbuf *src, struct strbuf *dst, @@ -957,6 +947,25 @@ int cmd_clone(int argc, const char **argv, const char *prefix) else fprintf(stderr, _("Cloning into '%s'...\n"), dir); } + + if (option_recursive) { + if (option_required_reference.nr && + option_optional_reference.nr) + die(_("clone --recursive is not compatible with " + "both --reference and --reference-if-able")); + else if (option_required_reference.nr) { + string_list_append(&option_config, + "submodule.alternateLocation=superproject"); + string_list_append(&option_config, + "submodule.alternateErrorStrategy=die"); + } else if (option_optional_reference.nr) { + string_list_append(&option_config, + "submodule.alternateLocation=superproject"); + string_list_append(&option_config, + "submodule.alternateErrorStrategy=info"); + } + } + init_db(option_template, INIT_DB_QUIET); write_config(&option_config); @@ -977,7 +986,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, repo); strbuf_reset(&key); - if (option_reference.nr) + if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); fetch_pattern = value.buf; diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 8a674bc9e7..605017261c 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -40,8 +40,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) { int i, got_tree = 0; struct commit_list *parents = NULL; - unsigned char tree_sha1[20]; - unsigned char commit_sha1[20]; + struct object_id tree_oid; + struct object_id commit_oid; struct strbuf buffer = STRBUF_INIT; git_config(commit_tree_config, NULL); @@ -52,13 +52,13 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "-p")) { - unsigned char sha1[20]; + struct object_id oid; if (argc <= ++i) usage(commit_tree_usage); - if (get_sha1_commit(argv[i], sha1)) + if (get_sha1_commit(argv[i], oid.hash)) die("Not a valid object name %s", argv[i]); - assert_sha1_type(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); + assert_sha1_type(oid.hash, OBJ_COMMIT); + new_parent(lookup_commit(oid.hash), &parents); continue; } @@ -105,7 +105,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) continue; } - if (get_sha1_tree(arg, tree_sha1)) + if (get_sha1_tree(arg, tree_oid.hash)) die("Not a valid object name %s", arg); if (got_tree) die("Cannot give more than one trees"); @@ -117,13 +117,13 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) die_errno("git commit-tree: failed to read"); } - if (commit_tree(buffer.buf, buffer.len, tree_sha1, parents, - commit_sha1, NULL, sign_commit)) { + if (commit_tree(buffer.buf, buffer.len, tree_oid.hash, parents, + commit_oid.hash, NULL, sign_commit)) { strbuf_release(&buffer); return 1; } - printf("%s\n", sha1_to_hex(commit_sha1)); + printf("%s\n", oid_to_hex(&commit_oid)); strbuf_release(&buffer); return 0; } diff --git a/builtin/commit.c b/builtin/commit.c index 77e3dc8494..bb9f79b6ef 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -142,14 +142,24 @@ static int show_ignored_in_status, have_option_m; static const char *only_include_assumed; static struct strbuf message = STRBUF_INIT; -static enum status_format { - STATUS_FORMAT_NONE = 0, - STATUS_FORMAT_LONG, - STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN, +static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED; - STATUS_FORMAT_UNSPECIFIED -} status_format = STATUS_FORMAT_UNSPECIFIED; +static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset) +{ + enum wt_status_format *value = (enum wt_status_format *)opt->value; + if (unset) + *value = STATUS_FORMAT_NONE; + else if (!arg) + *value = STATUS_FORMAT_PORCELAIN; + else if (!strcmp(arg, "v1") || !strcmp(arg, "1")) + *value = STATUS_FORMAT_PORCELAIN; + else if (!strcmp(arg, "v2") || !strcmp(arg, "2")) + *value = STATUS_FORMAT_PORCELAIN_V2; + else + die("unsupported porcelain version '%s'", arg); + + return 0; +} static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -500,24 +510,13 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->fp = fp; s->nowarn = nowarn; s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; + if (!s->is_initial) + hashcpy(s->sha1_commit, sha1); + s->status_format = status_format; + s->ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(s); - - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(s); - break; - case STATUS_FORMAT_UNSPECIFIED: - die("BUG: finalize_deferred_config() should have been called"); - break; - case STATUS_FORMAT_NONE: - case STATUS_FORMAT_LONG: - wt_status_print(s); - break; - } + wt_status_print(s); return s->commitable; } @@ -1099,7 +1098,7 @@ static const char *read_commit_message(const char *name) * is not in effect here. */ static struct status_deferred_config { - enum status_format status_format; + enum wt_status_format status_format; int show_branch; } status_deferred_config = { STATUS_FORMAT_UNSPECIFIED, @@ -1109,6 +1108,7 @@ static struct status_deferred_config { static void finalize_deferred_config(struct wt_status *s) { int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN && + status_format != STATUS_FORMAT_PORCELAIN_V2 && !s->null_termination); if (s->null_termination) { @@ -1336,9 +1336,9 @@ 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_SET_INT(0, "porcelain", &status_format, - N_("machine-readable output"), - STATUS_FORMAT_PORCELAIN), + { OPTION_CALLBACK, 0, "porcelain", &status_format, + N_("version"), N_("machine-readable output"), + PARSE_OPT_OPTARG, opt_parse_porcelain }, OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), @@ -1380,7 +1380,13 @@ int cmd_status(int argc, const char **argv, const char *prefix) fd = hold_locked_index(&index_lock, 0); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; + if (!s.is_initial) + hashcpy(s.sha1_commit, sha1); + s.ignore_submodule_arg = ignore_submodule_arg; + s.status_format = status_format; + s.verbose = verbose; + wt_status_collect(&s); if (0 <= fd) @@ -1389,23 +1395,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (s.relative_paths) s.prefix = prefix; - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(&s); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(&s); - break; - case STATUS_FORMAT_UNSPECIFIED: - die("BUG: finalize_deferred_config() should have been called"); - break; - case STATUS_FORMAT_NONE: - case STATUS_FORMAT_LONG: - s.verbose = verbose; - s.ignore_submodule_arg = ignore_submodule_arg; - wt_status_print(&s); - break; - } + wt_status_print(&s); return 0; } diff --git a/builtin/diff.c b/builtin/diff.c index b7a9405d9f..7f91f6d226 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -301,20 +301,21 @@ int cmd_diff(int argc, const char **argv, const char *prefix) break; } - if (!no_index) - prefix = setup_git_directory_gently(&nongit); + prefix = setup_git_directory_gently(&nongit); - /* - * Treat git diff with at least one path outside of the - * repo the same as if the command would have been executed - * outside of a git repository. In this case it behaves - * the same way as "git diff --no-index <a> <b>", which acts - * as a colourful "diff" replacement. - */ - if (nongit || ((argc == i + 2) && - (!path_inside_repo(prefix, argv[i]) || - !path_inside_repo(prefix, argv[i + 1])))) - no_index = DIFF_NO_INDEX_IMPLICIT; + if (!no_index) { + /* + * Treat git diff with at least one path outside of the + * repo the same as if the command would have been executed + * outside of a git repository. In this case it behaves + * the same way as "git diff --no-index <a> <b>", which acts + * as a colourful "diff" replacement. + */ + if (nongit || ((argc == i + 2) && + (!path_inside_repo(prefix, argv[i]) || + !path_inside_repo(prefix, argv[i + 1])))) + no_index = DIFF_NO_INDEX_IMPLICIT; + } if (!no_index) gitmodules_config(); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index ac84e99f3a..dc2e9e420d 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -395,7 +395,7 @@ static void shortlog(const char *name, for (i = 0; i < subjects.nr; i++) if (i >= limit) - strbuf_addf(out, " ...\n"); + strbuf_addstr(out, " ...\n"); else strbuf_addf(out, " %s\n", subjects.items[i].string); diff --git a/builtin/fsck.c b/builtin/fsck.c index 2de272ea36..055dfdcf9e 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -268,7 +268,7 @@ static void check_unreachable_object(struct object *obj) if (!(f = fopen(filename, "w"))) die_errno("Could not open '%s'", filename); if (obj->type == OBJ_BLOB) { - if (stream_blob_to_fd(fileno(f), obj->oid.hash, NULL, 1)) + if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) die_errno("Could not write '%s'", filename); } else fprintf(f, "%s\n", describe_object(obj)); @@ -722,7 +722,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) mode = active_cache[i]->ce_mode; if (S_ISGITLINK(mode)) continue; - blob = lookup_blob(active_cache[i]->sha1); + blob = lookup_blob(active_cache[i]->oid.hash); if (!blob) continue; obj = &blob->object; diff --git a/builtin/gc.c b/builtin/gc.c index 332bcf7e7a..069950d0b4 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -28,7 +28,7 @@ static const char * const builtin_gc_usage[] = { static int pack_refs = 1; static int prune_reflogs = 1; -static int aggressive_depth = 250; +static int aggressive_depth = 50; static int aggressive_window = 250; static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; diff --git a/builtin/grep.c b/builtin/grep.c index ae738312aa..8887b6addb 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -398,7 +398,8 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { if (ce_stage(ce) || ce_intent_to_add(ce)) continue; - hit |= grep_sha1(opt, ce->sha1, ce->name, 0, ce->name); + hit |= grep_sha1(opt, ce->oid.hash, ce->name, 0, + ce->name); } else hit |= grep_file(opt, ce->name); diff --git a/builtin/hash-object.c b/builtin/hash-object.c index f7d3567dd0..9028e1fdcc 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -87,6 +87,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) int stdin_paths = 0; int no_filters = 0; int literally = 0; + int nongit = 0; unsigned flags = HASH_FORMAT_CHECK; const char *vpath = NULL; const struct option hash_object_options[] = { @@ -107,12 +108,14 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, NULL, hash_object_options, hash_object_usage, 0); - if (flags & HASH_WRITE_OBJECT) { + if (flags & HASH_WRITE_OBJECT) prefix = setup_git_directory(); - prefix_length = prefix ? strlen(prefix) : 0; - if (vpath && prefix) - vpath = prefix_filename(prefix, prefix_length, vpath); - } + else + prefix = setup_git_directory_gently(&nongit); + + prefix_length = prefix ? strlen(prefix) : 0; + if (vpath && prefix) + vpath = prefix_filename(prefix, prefix_length, vpath); git_config(git_default_config, NULL); diff --git a/builtin/help.c b/builtin/help.c index e8f79d7af5..49f7a07f85 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -37,8 +37,10 @@ static int show_all = 0; static int show_guides = 0; static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; +static int exclude_guides; static struct option builtin_help_options[] = { OPT_BOOL('a', "all", &show_all, N_("print all available commands")), + OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), @@ -426,10 +428,29 @@ static void list_common_guides_help(void) putchar('\n'); } +static const char *check_git_cmd(const char* cmd) +{ + char *alias; + + if (is_git_command(cmd)) + return cmd; + + alias = alias_lookup(cmd); + if (alias) { + printf_ln(_("`git %s' is aliased to `%s'"), cmd, alias); + free(alias); + exit(0); + } + + if (exclude_guides) + return help_unknown_cmd(cmd); + + return cmd; +} + int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; - char *alias; enum help_format parsed_help_format; argc = parse_options(argc, argv, prefix, builtin_help_options, @@ -469,12 +490,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) if (help_format == HELP_FORMAT_NONE) help_format = parse_help_format(DEFAULT_HELP_FORMAT); - alias = alias_lookup(argv[0]); - if (alias && !is_git_command(argv[0])) { - printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias); - free(alias); - return 0; - } + argv[0] = check_git_cmd(argv[0]); switch (help_format) { case HELP_FORMAT_NONE: diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 1d2ea583a4..4a8b4aebba 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -87,6 +87,7 @@ static struct progress *progress; static unsigned char input_buffer[4096]; static unsigned int input_offset, input_len; static off_t consumed_bytes; +static off_t max_input_size; static unsigned deepest_delta; static git_SHA_CTX input_ctx; static uint32_t input_crc32; @@ -297,6 +298,8 @@ static void use(int bytes) if (signed_add_overflows(consumed_bytes, bytes)) die(_("pack too large for current definition of off_t")); consumed_bytes += bytes; + if (max_input_size && consumed_bytes > max_input_size) + die(_("pack exceeds maximum allowed size")); } static const char *open_pack_file(const char *pack_name) @@ -1714,6 +1717,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) opts.off32_limit = strtoul(c+1, &c, 0); if (*c || opts.off32_limit & 0x80000000) die(_("bad %s"), arg); + } else if (skip_prefix(arg, "--max-input-size=", &arg)) { + max_input_size = strtoumax(arg, NULL, 10); } else usage(index_pack_usage); continue; diff --git a/builtin/init-db.c b/builtin/init-db.c index 3a45f0bcfb..72e81447ae 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -180,27 +180,30 @@ static int create_default_files(const char *template_path) char junk[2]; int reinit; int filemode; - - /* - * Create .git/refs/{heads,tags} - */ - safe_create_dir(git_path_buf(&buf, "refs"), 1); - safe_create_dir(git_path_buf(&buf, "refs/heads"), 1); - safe_create_dir(git_path_buf(&buf, "refs/tags"), 1); + struct strbuf err = STRBUF_INIT; /* Just look for `init.templatedir` */ git_config(git_init_db_config, NULL); - /* First copy the templates -- we might have the default + /* + * First copy the templates -- we might have the default * config file there, in which case we would want to read * from it after installing. + * + * Before reading that config, we also need to clear out any cached + * values (since we've just potentially changed what's available on + * disk). */ copy_templates(template_path); - + git_config_clear(); + reset_shared_repository(); git_config(git_default_config, NULL); - is_bare_repository_cfg = init_is_bare_repository; - /* reading existing config may have overwrote it */ + /* + * We must make sure command-line options continue to override any + * values we might have just re-read from the config. + */ + is_bare_repository_cfg = init_is_bare_repository; if (init_shared_repository != -1) set_shared_repository(init_shared_repository); @@ -210,12 +213,19 @@ static int create_default_files(const char *template_path) */ if (get_shared_repository()) { adjust_shared_perm(get_git_dir()); - adjust_shared_perm(git_path_buf(&buf, "refs")); - adjust_shared_perm(git_path_buf(&buf, "refs/heads")); - adjust_shared_perm(git_path_buf(&buf, "refs/tags")); } /* + * We need to create a "refs" dir in any case so that older + * versions of git can tell that this is a repository. + */ + safe_create_dir(git_path("refs"), 1); + adjust_shared_perm(git_path("refs")); + + if (refs_init_db(&err)) + die("failed to set up refs db: %s", err.buf); + + /* * Create the default symlink from ".git/HEAD" to the "master" * branch, if it does not exist yet. */ diff --git a/builtin/log.c b/builtin/log.c index 92dc34dcb0..55d20cc2d8 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -464,9 +464,9 @@ static void show_tagger(char *buf, int len, struct rev_info *rev) strbuf_release(&out); } -static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, const char *obj_name) +static int show_blob_object(const struct object_id *oid, struct rev_info *rev, const char *obj_name) { - unsigned char sha1c[20]; + struct object_id oidc; struct object_context obj_context; char *buf; unsigned long size; @@ -474,13 +474,13 @@ static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, con fflush(rev->diffopt.file); if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) || !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV)) - return stream_blob_to_fd(1, sha1, NULL, 0); + return stream_blob_to_fd(1, oid, NULL, 0); - if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context)) + if (get_sha1_with_context(obj_name, 0, 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, sha1c, 1, &buf, &size)) - return stream_blob_to_fd(1, sha1, NULL, 0); + !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) + return stream_blob_to_fd(1, oid, NULL, 0); if (!buf) die(_("git show %s: bad file"), obj_name); @@ -489,15 +489,15 @@ static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, con return 0; } -static int show_tag_object(const unsigned char *sha1, struct rev_info *rev) +static int show_tag_object(const struct object_id *oid, struct rev_info *rev) { unsigned long size; enum object_type type; - char *buf = read_sha1_file(sha1, &type, &size); + char *buf = read_sha1_file(oid->hash, &type, &size); int offset = 0; if (!buf) - return error(_("Could not read object %s"), sha1_to_hex(sha1)); + return error(_("Could not read object %s"), oid_to_hex(oid)); assert(type == OBJ_TAG); while (offset < size && buf[offset] != '\n') { @@ -574,7 +574,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) const char *name = objects[i].name; switch (o->type) { case OBJ_BLOB: - ret = show_blob_object(o->oid.hash, &rev, name); + ret = show_blob_object(&o->oid, &rev, name); break; case OBJ_TAG: { struct tag *t = (struct tag *)o; @@ -585,7 +585,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), t->tag, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - ret = show_tag_object(o->oid.hash, &rev); + ret = show_tag_object(&o->oid, &rev); rev.shown_one = 1; if (ret) break; @@ -1042,7 +1042,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, diff_flush(&opts); fprintf(rev->diffopt.file, "\n"); - print_signature(rev->diffopt.file); } static const char *clean_message_id(const char *msg_id) @@ -1112,6 +1111,11 @@ static int subject_prefix_callback(const struct option *opt, const char *arg, return 0; } +static int rfc_callback(const struct option *opt, const char *arg, int unset) +{ + return subject_prefix_callback(opt, "RFC PATCH", unset); +} + static int numbered_cmdline_opt = 0; static int numbered_callback(const struct option *opt, const char *arg, @@ -1248,11 +1252,11 @@ static struct commit *get_base_commit(const char *base_commit, if (upstream) { struct commit_list *base_list; struct commit *commit; - unsigned char sha1[20]; + struct object_id oid; - if (get_sha1(upstream, sha1)) + if (get_oid(upstream, &oid)) die(_("Failed to resolve '%s' as a valid ref."), upstream); - commit = lookup_commit_or_die(sha1, "upstream base"); + commit = lookup_commit_or_die(oid.hash, "upstream base"); base_list = get_merge_bases_many(commit, total, list); /* There should be one and only one merge base. */ if (!base_list || base_list->next) @@ -1339,15 +1343,15 @@ static void prepare_bases(struct base_tree_info *bases, * and stuff them in bases structure. */ while ((commit = get_revision(&revs)) != NULL) { - unsigned char sha1[20]; + struct object_id oid; struct object_id *patch_id; if (commit->util) continue; - if (commit_patch_id(commit, &diffopt, sha1, 0)) + if (commit_patch_id(commit, &diffopt, oid.hash, 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; - hashcpy(patch_id->hash, sha1); + oidcpy(patch_id, &oid); bases->nr_patch_id++; } } @@ -1361,7 +1365,7 @@ static void print_bases(struct base_tree_info *bases, FILE *file) return; /* Show the base commit */ - fprintf(file, "base-commit: %s\n", oid_to_hex(&bases->base_commit)); + fprintf(file, "\nbase-commit: %s\n", oid_to_hex(&bases->base_commit)); /* Show the prerequisite patches */ for (i = bases->nr_patch_id - 1; i >= 0; i--) @@ -1419,6 +1423,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")), OPT_INTEGER('v', "reroll-count", &reroll_count, N_("mark the series as Nth re-roll")), + { OPTION_CALLBACK, 0, "rfc", &rev, NULL, + N_("Use [RFC PATCH] instead of [PATCH]"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback }, { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), N_("Use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback }, @@ -1557,7 +1564,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (numbered && keep_subject) die (_("-n and -k are mutually exclusive.")); if (keep_subject && subject_prefix) - die (_("--subject-prefix and -k are mutually exclusive.")); + die (_("--subject-prefix/--rfc and -k are mutually exclusive.")); rev.preserve_subject = keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); @@ -1628,10 +1635,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) check_head = 1; if (check_head) { - unsigned char sha1[20]; + struct object_id oid; const char *ref, *v; ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - sha1, NULL); + oid.hash, NULL); if (ref && skip_prefix(ref, "refs/heads/", &v)) branch_name = xstrdup(v); else @@ -1676,16 +1683,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* nothing to do */ return 0; total = nr; - if (!keep_subject && auto_number && total > 1) - numbered = 1; - if (numbered) - rev.total = total + start_number - 1; if (cover_letter == -1) { if (config_cover_letter == COVER_AUTO) cover_letter = (total > 1); else cover_letter = (config_cover_letter == COVER_ON); } + if (!keep_subject && auto_number && (total > 1 || cover_letter)) + numbered = 1; + if (numbered) + rev.total = total + start_number - 1; if (!signature) { ; /* --no-signature inhibits all signatures */ @@ -1720,6 +1727,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) make_cover_letter(&rev, use_stdout, origin, nr, list, branch_name, quiet); print_bases(&bases, rev.diffopt.file); + print_signature(rev.diffopt.file); total++; start_number--; } @@ -1779,13 +1787,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout) rev.shown_one = 0; if (shown) { + print_bases(&bases, rev.diffopt.file); if (rev.mime_boundary) fprintf(rev.diffopt.file, "\n--%s%s--\n\n\n", mime_boundary_leader, rev.mime_boundary); else print_signature(rev.diffopt.file); - print_bases(&bases, rev.diffopt.file); } if (!use_stdout) fclose(rev.diffopt.file); @@ -1802,9 +1810,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) { - unsigned char sha1[20]; - if (get_sha1(arg, sha1) == 0) { - struct commit *commit = lookup_commit_reference(sha1); + struct object_id oid; + if (get_oid(arg, &oid) == 0) { + struct commit *commit = lookup_commit_reference(oid.hash); if (commit) { commit->object.flags |= flags; add_pending_object(revs, &commit->object, arg); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 00ea91aae6..197f153f50 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -187,7 +187,7 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce) printf("%s%06o %s %d\t", tag, ce->ce_mode, - find_unique_abbrev(ce->sha1,abbrev), + find_unique_abbrev(ce->oid.hash,abbrev), ce_stage(ce)); } write_eolinfo(ce, ce->name); diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 1c3427c36c..ce356b1da1 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -22,7 +22,7 @@ static int merge_entry(int pos, const char *path) if (strcmp(ce->name, path)) break; found++; - sha1_to_hex_r(hexbuf[stage], ce->sha1); + sha1_to_hex_r(hexbuf[stage], ce->oid.hash); xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode); arguments[stage] = hexbuf[stage]; arguments[stage + 4] = ownbuf[stage]; diff --git a/builtin/merge.c b/builtin/merge.c index 0ae099f746..a8b57c7d98 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -940,7 +940,7 @@ static void write_merge_state(struct commit_list *remoteheads) strbuf_reset(&buf); if (fast_forward == FF_NO) - strbuf_addf(&buf, "no-ff"); + strbuf_addstr(&buf, "no-ff"); write_file_buf(git_path_merge_mode(), buf.buf, buf.len); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 4a63398960..166e52c700 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -67,7 +67,8 @@ static struct packed_git *reuse_packfile; static uint32_t reuse_packfile_objects; static off_t reuse_packfile_offset; -static int use_bitmap_index = 1; +static int use_bitmap_index_default = 1; +static int use_bitmap_index = -1; static int write_bitmap_index; static uint16_t write_bitmap_options; @@ -945,13 +946,48 @@ static int have_duplicate_entry(const unsigned char *sha1, return 1; } +static int want_found_object(int exclude, struct packed_git *p) +{ + if (exclude) + return 1; + if (incremental) + return 0; + + /* + * When asked to do --local (do not include an object that appears in a + * pack we borrow from elsewhere) or --honor-pack-keep (do not include + * an object that appears in a pack marked with .keep), finding a pack + * that matches the criteria is sufficient for us to decide to omit it. + * However, even if this pack does not satisfy the criteria, we need to + * make sure no copy of this object appears in _any_ pack that makes us + * to omit the object, so we need to check all the packs. + * + * We can however first check whether these options can possible matter; + * if they do not matter we know we want the object in generated pack. + * Otherwise, we signal "-1" at the end to tell the caller that we do + * not know either way, and it needs to check more packs. + */ + if (!ignore_packed_keep && + (!local || !have_non_local_packs)) + return 1; + + if (local && !p->pack_local) + return 0; + if (ignore_packed_keep && p->pack_local && p->pack_keep) + return 0; + + /* we don't know yet; keep looking for more packs */ + return -1; +} + /* * Check whether we want the object in the pack (e.g., we do not want * objects found in non-local stores if the "--local" option was used). * - * As a side effect of this check, we will find the packed version of this - * object, if any. We therefore pass out the pack information to avoid having - * to look it up again later. + * If the caller already knows an existing pack it wants to take the object + * from, that is passed in *found_pack and *found_offset; otherwise this + * function finds if there is any pack that has the object and returns the pack + * and its offset in these variables. */ static int want_object_in_pack(const unsigned char *sha1, int exclude, @@ -959,15 +995,30 @@ static int want_object_in_pack(const unsigned char *sha1, off_t *found_offset) { struct packed_git *p; + int want; if (!exclude && local && has_loose_object_nonlocal(sha1)) return 0; - *found_pack = NULL; - *found_offset = 0; + /* + * If we already know the pack object lives in, start checks from that + * pack - in the usual case when neither --local was given nor .keep files + * are present we will determine the answer right now. + */ + if (*found_pack) { + want = want_found_object(exclude, *found_pack); + if (want != -1) + return want; + } for (p = packed_git; p; p = p->next) { - off_t offset = find_pack_entry_one(sha1, p); + off_t offset; + + if (p == *found_pack) + offset = *found_offset; + else + offset = find_pack_entry_one(sha1, p); + if (offset) { if (!*found_pack) { if (!is_pack_valid(p)) @@ -975,31 +1026,9 @@ static int want_object_in_pack(const unsigned char *sha1, *found_offset = offset; *found_pack = p; } - if (exclude) - return 1; - if (incremental) - return 0; - - /* - * When asked to do --local (do not include an - * object that appears in a pack we borrow - * from elsewhere) or --honor-pack-keep (do not - * include an object that appears in a pack marked - * with .keep), we need to make sure no copy of this - * object come from in _any_ pack that causes us to - * omit it, and need to complete this loop. When - * neither option is in effect, we know the object - * we just found is going to be packed, so break - * out of the loop to return 1 now. - */ - if (!ignore_packed_keep && - (!local || !have_non_local_packs)) - break; - - if (local && !p->pack_local) - return 0; - if (ignore_packed_keep && p->pack_local && p->pack_keep) - return 0; + want = want_found_object(exclude, p); + if (want != -1) + return want; } } @@ -1040,8 +1069,8 @@ static const char no_closure_warning[] = N_( static int add_object_entry(const unsigned char *sha1, enum object_type type, const char *name, int exclude) { - struct packed_git *found_pack; - off_t found_offset; + struct packed_git *found_pack = NULL; + off_t found_offset = 0; uint32_t index_pos; if (have_duplicate_entry(sha1, exclude, &index_pos)) @@ -1074,6 +1103,9 @@ static int add_object_entry_from_bitmap(const unsigned char *sha1, if (have_duplicate_entry(sha1, 0, &index_pos)) return 0; + if (!want_object_in_pack(sha1, 0, &pack, &offset)) + return 0; + create_object_entry(sha1, type, name_hash, 0, 0, index_pos, pack, offset); display_progress(progress_state, nr_result); @@ -2123,6 +2155,35 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, #define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p) #endif +static void add_tag_chain(const struct object_id *oid) +{ + struct tag *tag; + + /* + * We catch duplicates already in add_object_entry(), but we'd + * prefer to do this extra check to avoid having to parse the + * tag at all if we already know that it's being packed (e.g., if + * it was included via bitmaps, we would not have parsed it + * previously). + */ + if (packlist_find(&to_pack, oid->hash, NULL)) + return; + + tag = lookup_tag(oid->hash); + while (1) { + if (!tag || parse_tag(tag) || !tag->tagged) + die("unable to pack objects reachable from tag %s", + oid_to_hex(oid)); + + add_object_entry(tag->object.oid.hash, OBJ_TAG, NULL, 0); + + if (tag->tagged->type != OBJ_TAG) + return; + + tag = (struct tag *)tag->tagged; + } +} + static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data) { struct object_id peeled; @@ -2130,7 +2191,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag, if (starts_with(path, "refs/tags/") && /* is a tag? */ !peel_ref(path, peeled.hash) && /* peelable? */ packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */ - add_object_entry(oid->hash, OBJ_TAG, NULL, 0); + add_tag_chain(oid); return 0; } @@ -2244,7 +2305,7 @@ static int git_pack_config(const char *k, const char *v, void *cb) write_bitmap_options &= ~BITMAP_OPT_HASH_CACHE; } if (!strcmp(k, "pack.usebitmaps")) { - use_bitmap_index = git_config_bool(k, v); + use_bitmap_index_default = git_config_bool(k, v); return 0; } if (!strcmp(k, "pack.threads")) { @@ -2493,13 +2554,13 @@ static void loosen_unused_packed_objects(struct rev_info *revs) } /* - * This tracks any options which a reader of the pack might - * not understand, and which would therefore prevent blind reuse - * of what we have on disk. + * This tracks any options which pack-reuse code expects to be on, or which a + * reader of the pack might not understand, and which would therefore prevent + * blind reuse of what we have on disk. */ static int pack_options_allow_reuse(void) { - return allow_ofs_delta; + return pack_to_stdout && allow_ofs_delta; } static int get_object_list_from_bitmap(struct rev_info *revs) @@ -2792,7 +2853,23 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!rev_list_all || !rev_list_reflog || !rev_list_index) unpack_unreachable_expiration = 0; - if (!use_internal_rev_list || !pack_to_stdout || is_repository_shallow()) + /* + * "soft" reasons not to use bitmaps - for on-disk repack by default we want + * + * - to produce good pack (with bitmap index not-yet-packed objects are + * packed in suboptimal order). + * + * - to use more robust pack-generation codepath (avoiding possible + * bugs in bitmap code and possible bitmap index corruption). + */ + if (!pack_to_stdout) + use_bitmap_index_default = 0; + + if (use_bitmap_index < 0) + use_bitmap_index = use_bitmap_index_default; + + /* "hard" reasons not to use bitmaps; these just won't work at all */ + if (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) || is_repository_shallow()) use_bitmap_index = 0; if (pack_to_stdout || !rev_list_all) diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 8c693e7568..9bd1fd755e 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -78,7 +78,7 @@ static void debug_stage(const char *label, const struct cache_entry *ce, else printf("%06o #%d %s %.8s\n", ce->ce_mode, ce_stage(ce), ce->name, - sha1_to_hex(ce->sha1)); + oid_to_hex(&ce->oid)); } static int debug_merge(const struct cache_entry * const *stages, diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 79839d0d80..896b16f2cc 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -46,6 +46,7 @@ static int transfer_unpack_limit = -1; static int advertise_atomic_push = 1; static int advertise_push_options; static int unpack_limit = 100; +static off_t max_input_size; static int report_status; static int use_sideband; static int use_atomic; @@ -212,6 +213,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.maxinputsize") == 0) { + max_input_size = git_config_int64(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -1640,6 +1646,9 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (fsck_objects) argv_array_pushf(&child.args, "--strict%s", fsck_msg_types.buf); + if (max_input_size) + argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX, + (uintmax_t)max_input_size); child.no_stdout = 1; child.err = err_fd; child.git_cmd = 1; @@ -1668,6 +1677,9 @@ static const char *unpack(int err_fd, struct shallow_info *si) fsck_msg_types.buf); if (!reject_thin) argv_array_push(&child.args, "--fix-thin"); + if (max_input_size) + argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX, + (uintmax_t)max_input_size); child.out = -1; child.err = err_fd; child.git_cmd = 1; diff --git a/builtin/reset.c b/builtin/reset.c index 9020ec66c8..5aa86079d3 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -39,7 +39,7 @@ static inline int is_merge(void) return !access(git_path_merge_head(), F_OK); } -static int reset_index(const unsigned char *sha1, int reset_type, int quiet) +static int reset_index(const struct object_id *oid, int reset_type, int quiet) { int nr = 1; struct tree_desc desc[2]; @@ -69,22 +69,22 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet) read_cache_unmerged(); if (reset_type == KEEP) { - unsigned char head_sha1[20]; - if (get_sha1("HEAD", head_sha1)) + struct object_id head_oid; + if (get_oid("HEAD", &head_oid)) return error(_("You do not have a valid HEAD.")); - if (!fill_tree_descriptor(desc, head_sha1)) + if (!fill_tree_descriptor(desc, head_oid.hash)) return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } - if (!fill_tree_descriptor(desc + nr - 1, sha1)) - return error(_("Failed to find tree of %s."), sha1_to_hex(sha1)); + if (!fill_tree_descriptor(desc + nr - 1, oid->hash)) + return error(_("Failed to find tree of %s."), oid_to_hex(oid)); if (unpack_trees(nr, desc, &opts)) return -1; if (reset_type == MIXED || reset_type == HARD) { - tree = parse_tree_indirect(sha1); + tree = parse_tree_indirect(oid->hash); prime_cache_tree(&the_index, tree); } @@ -143,7 +143,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, } static int read_from_tree(const struct pathspec *pathspec, - unsigned char *tree_sha1, + struct object_id *tree_oid, int intent_to_add) { struct diff_options opt; @@ -154,7 +154,7 @@ static int read_from_tree(const struct pathspec *pathspec, opt.format_callback = update_index_from_diff; opt.format_callback_data = &intent_to_add; - if (do_diff_cache(tree_sha1, &opt)) + if (do_diff_cache(tree_oid->hash, &opt)) return 1; diffcore_std(&opt); diff_flush(&opt); @@ -191,7 +191,7 @@ static void parse_args(struct pathspec *pathspec, const char **rev_ret) { const char *rev = "HEAD"; - unsigned char unused[20]; + struct object_id unused; /* * Possible arguments are: * @@ -216,8 +216,8 @@ static void parse_args(struct pathspec *pathspec, * has to be unambiguous. If there is a single argument, it * can not be a tree */ - else if ((!argv[1] && !get_sha1_committish(argv[0], unused)) || - (argv[1] && !get_sha1_treeish(argv[0], unused))) { + else if ((!argv[1] && !get_sha1_committish(argv[0], unused.hash)) || + (argv[1] && !get_sha1_treeish(argv[0], unused.hash))) { /* * Ok, argv[0] looks like a commit/tree; it should not * be a filename. @@ -241,24 +241,24 @@ static void parse_args(struct pathspec *pathspec, prefix, argv); } -static int reset_refs(const char *rev, const unsigned char *sha1) +static int reset_refs(const char *rev, const struct object_id *oid) { int update_ref_status; struct strbuf msg = STRBUF_INIT; - unsigned char *orig = NULL, sha1_orig[20], - *old_orig = NULL, sha1_old_orig[20]; + struct object_id *orig = NULL, oid_orig, + *old_orig = NULL, oid_old_orig; - if (!get_sha1("ORIG_HEAD", sha1_old_orig)) - old_orig = sha1_old_orig; - if (!get_sha1("HEAD", sha1_orig)) { - orig = sha1_orig; + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; set_reflog_message(&msg, "updating ORIG_HEAD", NULL); - update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + update_ref_oid(msg.buf, "ORIG_HEAD", orig, old_orig, 0, UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) - delete_ref("ORIG_HEAD", old_orig, 0); + delete_ref("ORIG_HEAD", old_orig->hash, 0); set_reflog_message(&msg, "updating HEAD", rev); - update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, + update_ref_status = update_ref_oid(msg.buf, "HEAD", oid, orig, 0, UPDATE_REFS_MSG_ON_ERR); strbuf_release(&msg); return update_ref_status; @@ -357,15 +357,15 @@ int cmd_reset(int argc, const char **argv, const char *prefix) hold_locked_index(lock, 1); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; - if (read_from_tree(&pathspec, oid.hash, intent_to_add)) + if (read_from_tree(&pathspec, &oid, intent_to_add)) return 1; if (get_git_work_tree()) refresh_index(&the_index, flags, NULL, NULL, _("Unstaged changes after reset:")); } else { - int err = reset_index(oid.hash, reset_type, quiet); + int err = reset_index(&oid, reset_type, quiet); if (reset_type == KEEP && !err) - err = reset_index(oid.hash, MIXED, quiet); + err = reset_index(&oid, MIXED, quiet); if (err) die(_("Could not reset index file to revision '%s'."), rev); } @@ -377,7 +377,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (!pathspec.nr && !unborn) { /* Any resets without paths update HEAD to the head being * switched to, saving the previous head in ORIG_HEAD before. */ - update_ref_status = reset_refs(rev, oid.hash); + update_ref_status = reset_refs(rev, &oid); if (reset_type == HARD && !update_ref_status && !quiet) print_new_head_line(lookup_commit_reference(oid.hash)); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 0ba82b1635..8479f6ed28 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -122,48 +122,40 @@ static void show_commit(struct commit *commit, void *data) ctx.fmt = revs->commit_format; ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &buf); - if (revs->graph) { - if (buf.len) { - if (revs->commit_format != CMIT_FMT_ONELINE) - graph_show_oneline(revs->graph); - - graph_show_commit_msg(revs->graph, &buf); - - /* - * Add a newline after the commit message. - * - * Usually, this newline produces a blank - * padding line between entries, in which case - * we need to add graph padding on this line. - * - * However, the commit message may not end in a - * newline. In this case the newline simply - * ends the last line of the commit message, - * and we don't need any graph output. (This - * always happens with CMIT_FMT_ONELINE, and it - * happens with CMIT_FMT_USERFORMAT when the - * format doesn't explicitly end in a newline.) - */ - if (buf.len && buf.buf[buf.len - 1] == '\n') - graph_show_padding(revs->graph); - putchar('\n'); - } else { - /* - * If the message buffer is empty, just show - * the rest of the graph output for this - * commit. - */ - if (graph_show_remainder(revs->graph)) - putchar('\n'); - if (revs->commit_format == CMIT_FMT_ONELINE) - putchar('\n'); - } + if (buf.len) { + if (revs->commit_format != CMIT_FMT_ONELINE) + graph_show_oneline(revs->graph); + + graph_show_commit_msg(revs->graph, stdout, &buf); + + /* + * Add a newline after the commit message. + * + * Usually, this newline produces a blank + * padding line between entries, in which case + * we need to add graph padding on this line. + * + * However, the commit message may not end in a + * newline. In this case the newline simply + * ends the last line of the commit message, + * and we don't need any graph output. (This + * always happens with CMIT_FMT_ONELINE, and it + * happens with CMIT_FMT_USERFORMAT when the + * format doesn't explicitly end in a newline.) + */ + if (buf.len && buf.buf[buf.len - 1] == '\n') + graph_show_padding(revs->graph); + putchar('\n'); } else { - if (revs->commit_format != CMIT_FMT_USERFORMAT || - buf.len) { - fwrite(buf.buf, 1, buf.len, stdout); - putchar(info->hdr_termination); - } + /* + * If the message buffer is empty, just show + * the rest of the graph output for this + * commit. + */ + if (graph_show_remainder(revs->graph)) + putchar('\n'); + if (revs->commit_format == CMIT_FMT_ONELINE) + putchar('\n'); } strbuf_release(&buf); } else { diff --git a/builtin/rm.c b/builtin/rm.c index b2fee3e90a..3f3e24eb36 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -107,7 +107,7 @@ static int check_submodules_use_gitfiles(void) return errs; } -static int check_local_mod(unsigned char *head, int index_only) +static int check_local_mod(struct object_id *head, int index_only) { /* * Items in list are already sorted in the cache order, @@ -123,13 +123,13 @@ static int check_local_mod(unsigned char *head, int index_only) struct string_list files_submodule = STRING_LIST_INIT_NODUP; struct string_list files_local = STRING_LIST_INIT_NODUP; - no_head = is_null_sha1(head); + no_head = is_null_oid(head); for (i = 0; i < list.nr; i++) { struct stat st; int pos; const struct cache_entry *ce; const char *name = list.entry[i].name; - unsigned char sha1[20]; + struct object_id oid; unsigned mode; int local_changes = 0; int staged_changes = 0; @@ -197,9 +197,9 @@ static int check_local_mod(unsigned char *head, int index_only) * way as changed from the HEAD. */ if (no_head - || get_tree_entry(head, name, sha1, &mode) + || get_tree_entry(head->hash, name, oid.hash, &mode) || ce->ce_mode != create_ce_mode(mode) - || hashcmp(ce->sha1, sha1)) + || oidcmp(&ce->oid, &oid)) staged_changes = 1; /* @@ -351,10 +351,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) * report no changes unless forced. */ if (!force) { - unsigned char sha1[20]; - if (get_sha1("HEAD", sha1)) - hashclr(sha1); - if (check_local_mod(sha1, index_only)) + struct object_id oid; + if (get_oid("HEAD", &oid)) + oidclr(&oid); + if (check_local_mod(&oid, index_only)) exit(1); } else if (!index_only) { if (check_submodules_use_gitfiles()) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index e79790f0bd..432794b130 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -296,7 +296,8 @@ static int module_list(int argc, const char **argv, const char *prefix) if (ce_stage(ce)) printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1)); else - printf("%06o %s %d\t", ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce)); + printf("%06o %s %d\t", ce->ce_mode, + oid_to_hex(&ce->oid), ce_stage(ce)); utf8_fprintf(stdout, "%s\n", ce->name); } @@ -442,7 +443,7 @@ static int module_name(int argc, const char **argv, const char *prefix) } static int clone_submodule(const char *path, const char *gitdir, const char *url, - const char *depth, const char *reference, int quiet) + const char *depth, struct string_list *reference, int quiet) { struct child_process cp = CHILD_PROCESS_INIT; @@ -452,8 +453,12 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url argv_array_push(&cp.args, "--quiet"); if (depth && *depth) argv_array_pushl(&cp.args, "--depth", depth, NULL); - if (reference && *reference) - argv_array_pushl(&cp.args, "--reference", reference, NULL); + if (reference->nr) { + struct string_list_item *item; + for_each_string_list_item(item, reference) + argv_array_pushl(&cp.args, "--reference", + item->string, NULL); + } if (gitdir && *gitdir) argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); @@ -467,15 +472,114 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url return run_command(&cp); } +struct submodule_alternate_setup { + const char *submodule_name; + enum SUBMODULE_ALTERNATE_ERROR_MODE { + SUBMODULE_ALTERNATE_ERROR_DIE, + SUBMODULE_ALTERNATE_ERROR_INFO, + SUBMODULE_ALTERNATE_ERROR_IGNORE + } error_mode; + struct string_list *reference; +}; +#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ + SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } + +static int add_possible_reference_from_superproject( + struct alternate_object_database *alt, void *sas_cb) +{ + struct submodule_alternate_setup *sas = sas_cb; + + /* directory name, minus trailing slash */ + size_t namelen = alt->name - alt->base - 1; + struct strbuf name = STRBUF_INIT; + strbuf_add(&name, alt->base, namelen); + + /* + * If the alternate object store is another repository, try the + * standard layout with .git/modules/<name>/objects + */ + if (ends_with(name.buf, ".git/objects")) { + char *sm_alternate; + struct strbuf sb = STRBUF_INIT; + struct strbuf err = STRBUF_INIT; + strbuf_add(&sb, name.buf, name.len - strlen("objects")); + /* + * We need to end the new path with '/' to mark it as a dir, + * otherwise a submodule name containing '/' will be broken + * as the last part of a missing submodule reference would + * be taken as a file name. + */ + strbuf_addf(&sb, "modules/%s/", sas->submodule_name); + + sm_alternate = compute_alternate_path(sb.buf, &err); + if (sm_alternate) { + string_list_append(sas->reference, xstrdup(sb.buf)); + free(sm_alternate); + } else { + switch (sas->error_mode) { + case SUBMODULE_ALTERNATE_ERROR_DIE: + die(_("submodule '%s' cannot add alternate: %s"), + sas->submodule_name, err.buf); + case SUBMODULE_ALTERNATE_ERROR_INFO: + fprintf(stderr, _("submodule '%s' cannot add alternate: %s"), + sas->submodule_name, err.buf); + case SUBMODULE_ALTERNATE_ERROR_IGNORE: + ; /* nothing */ + } + } + strbuf_release(&sb); + } + + strbuf_release(&name); + return 0; +} + +static void prepare_possible_alternates(const char *sm_name, + struct string_list *reference) +{ + char *sm_alternate = NULL, *error_strategy = NULL; + struct submodule_alternate_setup sas = SUBMODULE_ALTERNATE_SETUP_INIT; + + git_config_get_string("submodule.alternateLocation", &sm_alternate); + if (!sm_alternate) + return; + + git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); + + if (!error_strategy) + error_strategy = xstrdup("die"); + + sas.submodule_name = sm_name; + sas.reference = reference; + if (!strcmp(error_strategy, "die")) + sas.error_mode = SUBMODULE_ALTERNATE_ERROR_DIE; + else if (!strcmp(error_strategy, "info")) + sas.error_mode = SUBMODULE_ALTERNATE_ERROR_INFO; + else if (!strcmp(error_strategy, "ignore")) + sas.error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE; + else + die(_("Value '%s' for submodule.alternateErrorStrategy is not recognized"), error_strategy); + + if (!strcmp(sm_alternate, "superproject")) + foreach_alt_odb(add_possible_reference_from_superproject, &sas); + else if (!strcmp(sm_alternate, "no")) + ; /* do nothing */ + else + die(_("Value '%s' for submodule.alternateLocation is not recognized"), sm_alternate); + + free(sm_alternate); + free(error_strategy); +} + static int module_clone(int argc, const char **argv, const char *prefix) { - const char *name = NULL, *url = NULL; - const char *reference = NULL, *depth = NULL; + const char *name = NULL, *url = NULL, *depth = NULL; int quiet = 0; FILE *submodule_dot_git; char *p, *path = NULL, *sm_gitdir; struct strbuf rel_path = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; + struct string_list reference = STRING_LIST_INIT_NODUP; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &prefix, @@ -490,8 +594,8 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT_STRING(0, "url", &url, N_("string"), N_("url where to clone the submodule from")), - OPT_STRING(0, "reference", &reference, - N_("string"), + OPT_STRING_LIST(0, "reference", &reference, + N_("repo"), N_("reference repository")), OPT_STRING(0, "depth", &depth, N_("string"), @@ -527,7 +631,10 @@ static int module_clone(int argc, const char **argv, const char *prefix) if (!file_exists(sm_gitdir)) { if (safe_create_leading_directories_const(sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); - if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet)) + + prepare_possible_alternates(name, &reference); + + if (clone_submodule(path, sm_gitdir, url, depth, &reference, quiet)) die(_("clone of '%s' into submodule path '%s' failed"), url, path); } else { @@ -579,7 +686,7 @@ struct submodule_update_clone { /* configuration parameters which are passed on to the children */ int quiet; int recommend_shallow; - const char *reference; + struct string_list references; const char *depth; const char *recursive_prefix; const char *prefix; @@ -595,7 +702,8 @@ struct submodule_update_clone { int failed_clones_nr, failed_clones_alloc; }; #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ - SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, NULL, NULL, NULL, NULL, \ + SUBMODULE_UPDATE_STRATEGY_INIT, 0, -1, STRING_LIST_INIT_DUP, \ + NULL, NULL, NULL, \ STRING_LIST_INIT_DUP, 0, NULL, 0, 0} @@ -683,7 +791,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, strbuf_reset(&sb); strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode, - sha1_to_hex(ce->sha1), ce_stage(ce), + oid_to_hex(&ce->oid), ce_stage(ce), needs_cloning, ce->name); string_list_append(&suc->projectlines, sb.buf); @@ -705,8 +813,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, argv_array_pushl(&child->args, "--path", sub->path, NULL); argv_array_pushl(&child->args, "--name", sub->name, NULL); argv_array_pushl(&child->args, "--url", url, NULL); - if (suc->reference) - argv_array_push(&child->args, suc->reference); + if (suc->references.nr) { + struct string_list_item *item; + for_each_string_list_item(item, &suc->references) + argv_array_pushl(&child->args, "--reference", item->string, NULL); + } if (suc->depth) argv_array_push(&child->args, suc->depth); @@ -749,8 +860,9 @@ static int update_clone_get_next_task(struct child_process *child, ce = suc->failed_clones[index]; if (!prepare_to_clone_next_submodule(ce, child, suc, err)) { suc->current ++; - strbuf_addf(err, "BUG: submodule considered for cloning," - "doesn't need cloning any more?\n"); + strbuf_addstr(err, "BUG: submodule considered for " + "cloning, doesn't need cloning " + "any more?\n"); return 0; } p = xmalloc(sizeof(*p)); @@ -829,7 +941,7 @@ static int update_clone(int argc, const char **argv, const char *prefix) OPT_STRING(0, "update", &update, N_("string"), N_("rebase, merge, checkout or none")), - OPT_STRING(0, "reference", &suc.reference, N_("repo"), + OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"), N_("reference repository")), OPT_STRING(0, "depth", &suc.depth, "<depth>", N_("Create a shallow clone truncated to the " diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 9c29a64e43..96eed94468 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -56,6 +56,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) ret = check_symref(argv[0], 1, 0, 0); if (ret) die("Cannot delete %s, not a symbolic ref", argv[0]); + if (!strcmp(argv[0], "HEAD")) + die("deleting '%s' is not allowed", argv[0]); return delete_ref(argv[0], NULL, REF_NODEREF); } diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 172470bf24..4532aa0831 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -19,6 +19,7 @@ static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] static unsigned char buffer[4096]; static unsigned int offset, len; static off_t consumed_bytes; +static off_t max_input_size; static git_SHA_CTX ctx; static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; @@ -87,6 +88,8 @@ static void use(int bytes) if (signed_add_overflows(consumed_bytes, bytes)) die("pack too large for current definition of off_t"); consumed_bytes += bytes; + if (max_input_size && consumed_bytes > max_input_size) + die(_("pack exceeds maximum allowed size")); } static void *get_data(unsigned long size) @@ -550,6 +553,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) len = sizeof(*hdr); continue; } + if (skip_prefix(arg, "--max-input-size=", &arg)) { + max_input_size = strtoumax(arg, NULL, 10); + continue; + } usage(unpack_usage); } diff --git a/builtin/update-index.c b/builtin/update-index.c index 7a17ce14ea..73f6b3e1be 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -275,7 +275,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(ce->sha1, path, st, + if (index_path(ce->oid.hash, path, st, info_only ? 0 : HASH_WRITE_OBJECT)) { free(ce); return -1; @@ -312,7 +312,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len */ static int process_directory(const char *path, int len, struct stat *st) { - unsigned char sha1[20]; + struct object_id oid; int pos = cache_name_pos(path, len); /* Exact match: file or existing gitlink */ @@ -321,7 +321,7 @@ static int process_directory(const char *path, int len, struct stat *st) if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ - if (resolve_gitlink_ref(path, "HEAD", sha1) < 0) + if (resolve_gitlink_ref(path, "HEAD", oid.hash) < 0) return 0; return add_one_path(ce, path, len, st); @@ -347,7 +347,7 @@ static int process_directory(const char *path, int len, struct stat *st) } /* No match - should we add it as a gitlink? */ - if (!resolve_gitlink_ref(path, "HEAD", sha1)) + if (!resolve_gitlink_ref(path, "HEAD", oid.hash)) return add_one_path(NULL, path, len, st); /* Error out. */ @@ -390,7 +390,7 @@ static int process_path(const char *path) return add_one_path(ce, path, len, &st); } -static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, +static int add_cacheinfo(unsigned int mode, const struct object_id *oid, const char *path, int stage) { int size, len, option; @@ -403,7 +403,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, size = cache_entry_size(len); ce = xcalloc(1, size); - hashcpy(ce->sha1, sha1); + oidcpy(&ce->oid, oid); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = len; @@ -487,7 +487,7 @@ static void read_index_info(int nul_term_line) while (getline_fn(&buf, stdin) != EOF) { char *ptr, *tab; char *path_name; - unsigned char sha1[20]; + struct object_id oid; unsigned int mode; unsigned long ul; int stage; @@ -516,7 +516,7 @@ static void read_index_info(int nul_term_line) mode = ul; tab = strchr(ptr, '\t'); - if (!tab || tab - ptr < 41) + if (!tab || tab - ptr < GIT_SHA1_HEXSZ + 1) goto bad_line; if (tab[-2] == ' ' && '0' <= tab[-1] && tab[-1] <= '3') { @@ -529,7 +529,8 @@ static void read_index_info(int nul_term_line) ptr = tab + 1; /* point at the head of path */ } - if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ') + if (get_oid_hex(tab - GIT_SHA1_HEXSZ, &oid) || + tab[-(GIT_SHA1_HEXSZ + 1)] != ' ') goto bad_line; path_name = ptr; @@ -557,8 +558,8 @@ static void read_index_info(int nul_term_line) * ptr[-1] points at tab, * ptr[-41] is at the beginning of sha1 */ - ptr[-42] = ptr[-1] = 0; - if (add_cacheinfo(mode, sha1, path_name, stage)) + ptr[-(GIT_SHA1_HEXSZ + 2)] = ptr[-1] = 0; + if (add_cacheinfo(mode, &oid, path_name, stage)) die("git update-index: unable to update %s", path_name); } @@ -576,19 +577,19 @@ static const char * const update_index_usage[] = { NULL }; -static unsigned char head_sha1[20]; -static unsigned char merge_head_sha1[20]; +static struct object_id head_oid; +static struct object_id merge_head_oid; static struct cache_entry *read_one_ent(const char *which, - unsigned char *ent, const char *path, + struct object_id *ent, const char *path, int namelen, int stage) { unsigned mode; - unsigned char sha1[20]; + struct object_id oid; int size; struct cache_entry *ce; - if (get_tree_entry(ent, path, sha1, &mode)) { + if (get_tree_entry(ent->hash, path, oid.hash, &mode)) { if (which) error("%s: not in %s branch.", path, which); return NULL; @@ -601,7 +602,7 @@ static struct cache_entry *read_one_ent(const char *which, size = cache_entry_size(namelen); ce = xcalloc(1, size); - hashcpy(ce->sha1, sha1); + oidcpy(&ce->oid, &oid); memcpy(ce->name, path, namelen); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = namelen; @@ -651,14 +652,14 @@ static int unresolve_one(const char *path) * stuff HEAD version in stage #2, * stuff MERGE_HEAD version in stage #3. */ - ce_2 = read_one_ent("our", head_sha1, path, namelen, 2); - ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3); + ce_2 = read_one_ent("our", &head_oid, path, namelen, 2); + ce_3 = read_one_ent("their", &merge_head_oid, path, namelen, 3); if (!ce_2 || !ce_3) { ret = -1; goto free_return; } - if (!hashcmp(ce_2->sha1, ce_3->sha1) && + if (!oidcmp(&ce_2->oid, &ce_3->oid) && ce_2->ce_mode == ce_3->ce_mode) { fprintf(stderr, "%s: identical in both, skipping.\n", path); @@ -683,9 +684,9 @@ static int unresolve_one(const char *path) static void read_head_pointers(void) { - if (read_ref("HEAD", head_sha1)) + if (read_ref("HEAD", head_oid.hash)) die("No HEAD -- no initial commit yet?"); - if (read_ref("MERGE_HEAD", merge_head_sha1)) { + if (read_ref("MERGE_HEAD", merge_head_oid.hash)) { fprintf(stderr, "Not in the middle of a merge.\n"); exit(0); } @@ -725,7 +726,7 @@ static int do_reupdate(int ac, const char **av, PATHSPEC_PREFER_CWD, prefix, av + 1); - if (read_ref("HEAD", head_sha1)) + if (read_ref("HEAD", head_oid.hash)) /* If there is no HEAD, that means it is an initial * commit. Update everything in the index. */ @@ -740,10 +741,10 @@ static int do_reupdate(int ac, const char **av, if (ce_stage(ce) || !ce_path_match(ce, &pathspec, NULL)) continue; if (has_head) - old = read_one_ent(NULL, head_sha1, + old = read_one_ent(NULL, &head_oid, ce->name, ce_namelen(ce), 0); if (old && ce->ce_mode == old->ce_mode && - !hashcmp(ce->sha1, old->sha1)) { + !oidcmp(&ce->oid, &old->oid)) { free(old); continue; /* unchanged */ } @@ -807,7 +808,7 @@ static int resolve_undo_clear_callback(const struct option *opt, static int parse_new_style_cacheinfo(const char *arg, unsigned int *mode, - unsigned char sha1[], + struct object_id *oid, const char **path) { unsigned long ul; @@ -822,21 +823,21 @@ static int parse_new_style_cacheinfo(const char *arg, return -1; /* not a new-style cacheinfo */ *mode = ul; endp++; - if (get_sha1_hex(endp, sha1) || endp[40] != ',') + if (get_oid_hex(endp, oid) || endp[GIT_SHA1_HEXSZ] != ',') return -1; - *path = endp + 41; + *path = endp + GIT_SHA1_HEXSZ + 1; return 0; } static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) { - unsigned char sha1[20]; + struct object_id oid; unsigned int mode; const char *path; - if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, sha1, &path)) { - if (add_cacheinfo(mode, sha1, path, 0)) + if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) { + if (add_cacheinfo(mode, &oid, path, 0)) die("git update-index: --cacheinfo cannot add %s", path); ctx->argv++; ctx->argc--; @@ -845,8 +846,8 @@ static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, if (ctx->argc <= 3) return error("option 'cacheinfo' expects <mode>,<sha1>,<path>"); if (strtoul_ui(*++ctx->argv, 8, &mode) || - get_sha1_hex(*++ctx->argv, sha1) || - add_cacheinfo(mode, sha1, *++ctx->argv, 0)) + get_oid_hex(*++ctx->argv, &oid) || + add_cacheinfo(mode, &oid, *++ctx->argv, 0)) die("git update-index: --cacheinfo cannot add %s", *ctx->argv); ctx->argc -= 3; return 0; diff --git a/cache-tree.c b/cache-tree.c index f28b1f45a4..345ea35963 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -168,7 +168,7 @@ static int verify_cache(struct cache_entry **cache, break; } fprintf(stderr, "%s: unmerged (%s)\n", - ce->name, sha1_to_hex(ce->sha1)); + ce->name, oid_to_hex(&ce->oid)); } } if (funny) @@ -349,7 +349,7 @@ static int update_one(struct cache_tree *it, } } else { - sha1 = ce->sha1; + sha1 = ce->oid.hash; mode = ce->ce_mode; entlen = pathlen - baselen; i++; @@ -173,7 +173,7 @@ struct cache_entry { unsigned int ce_flags; unsigned int ce_namelen; unsigned int index; /* for link extension */ - unsigned char sha1[20]; + struct object_id oid; char name[FLEX_ARRAY]; /* more */ }; @@ -453,6 +453,12 @@ static inline enum object_type object_type(unsigned int mode) */ extern const char * const local_repo_env[]; +/* + * Returns true iff we have a configured git repository (either via + * setup_git_directory, or in the environment via $GIT_DIR). + */ +int have_git_dir(void); + extern int is_bare_repository_cfg; extern int is_bare_repository(void); extern int is_inside_git_dir(void); @@ -665,8 +671,15 @@ extern size_t delta_base_cache_limit; extern unsigned long big_file_threshold; extern unsigned long pack_size_limit_cfg; +/* + * Accessors for the core.sharedrepository config which lazy-load the value + * from the config (if not already set). The "reset" function can be + * used to unset "set" or cached value, meaning that the value will be loaded + * fresh from the config file on the next call to get_shared_repository(). + */ void set_shared_repository(int value); int get_shared_repository(void); +void reset_shared_repository(void); /* * Do replace refs need to be checked this run? This variable is @@ -819,8 +832,8 @@ extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...) __attribute__((format (printf, 2, 3))); extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); -extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path, - const char *fmt, ...) +extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path, + const char *fmt, ...) __attribute__((format (printf, 3, 4))); extern char *git_pathdup(const char *fmt, ...) __attribute__((format (printf, 1, 2))); @@ -953,22 +966,39 @@ static inline void oidclr(struct object_id *oid) #define EMPTY_TREE_SHA1_BIN_LITERAL \ "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \ "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04" -#define EMPTY_TREE_SHA1_BIN \ - ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL) +extern const struct object_id empty_tree_oid; +#define EMPTY_TREE_SHA1_BIN (empty_tree_oid.hash) #define EMPTY_BLOB_SHA1_HEX \ "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" #define EMPTY_BLOB_SHA1_BIN_LITERAL \ "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \ "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91" -#define EMPTY_BLOB_SHA1_BIN \ - ((const unsigned char *) EMPTY_BLOB_SHA1_BIN_LITERAL) +extern const struct object_id empty_blob_oid; +#define EMPTY_BLOB_SHA1_BIN (empty_blob_oid.hash) + static inline int is_empty_blob_sha1(const unsigned char *sha1) { return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN); } +static inline int is_empty_blob_oid(const struct object_id *oid) +{ + return !hashcmp(oid->hash, EMPTY_BLOB_SHA1_BIN); +} + +static inline int is_empty_tree_sha1(const unsigned char *sha1) +{ + return !hashcmp(sha1, EMPTY_TREE_SHA1_BIN); +} + +static inline int is_empty_tree_oid(const struct object_id *oid) +{ + return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN); +} + + int git_mkstemp(char *path, size_t n, const char *template); /* set default permissions by passing mode arguments to open(2) */ @@ -1139,6 +1169,16 @@ static inline unsigned int hexval(unsigned char c) return hexval_table[c]; } +/* + * Convert two consecutive hexadecimal digits into a char. Return a + * negative value on error. Don't run over the end of short strings. + */ +static inline int hex2chr(const char *s) +{ + int val = hexval(s[0]); + return (val < 0) ? val : (val << 4) | hexval(s[1]); +} + /* Convert to/from hex/sha1 representation */ #define MINIMUM_ABBREV minimum_abbrev #define DEFAULT_ABBREV default_abbrev @@ -1204,7 +1244,7 @@ extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */ extern int interpret_branch_name(const char *str, int len, struct strbuf *); -extern int get_sha1_mb(const char *str, unsigned char *sha1); +extern int get_oid_mb(const char *str, struct object_id *oid); extern int validate_headref(const char *ref); @@ -1344,6 +1384,7 @@ extern struct alternate_object_database { } *alt_odb_list; extern void prepare_alt_odb(void); extern void read_info_alternates(const char * relative_base, int depth); +extern char *compute_alternate_path(const char *path, struct strbuf *err); extern void add_to_alternates_file(const char *reference); typedef int alt_odb_fn(struct alternate_object_database *, void *); extern int foreach_alt_odb(alt_odb_fn, void*); @@ -1785,7 +1826,6 @@ extern void write_file(const char *path, const char *fmt, ...); /* pager.c */ extern void setup_pager(void); -extern const char *pager_program; extern int pager_in_use(void); extern int pager_use_color; extern int term_columns(void); @@ -215,7 +215,7 @@ int color_parse_mem(const char *value, int value_len, char *dst) /* [fg [bg]] [attr]... */ while (len > 0) { const char *word = ptr; - struct color c; + struct color c = { COLOR_UNSPECIFIED }; int val, wordlen = 0; while (len > 0 && !isspace(word[wordlen])) { diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c index 2d4ef59013..1cc31c3502 100644 --- a/compat/nedmalloc/nedmalloc.c +++ b/compat/nedmalloc/nedmalloc.c @@ -948,22 +948,6 @@ void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void ** return ret; } -#ifdef OVERRIDE_STRDUP -/* - * This implementation is purely there to override the libc version, to - * avoid a crash due to allocation and free on different 'heaps'. - */ -char *strdup(const char *s1) -{ - size_t len = strlen(s1) + 1; - char *s2 = malloc(len); - - if (s2) - memcpy(s2, s1, len); - return s2; -} -#endif - #if defined(__cplusplus) } #endif diff --git a/compat/strdup.c b/compat/strdup.c new file mode 100644 index 0000000000..f3fb978eb3 --- /dev/null +++ b/compat/strdup.c @@ -0,0 +1,11 @@ +#include "../git-compat-util.h" + +char *gitstrdup(const char *s1) +{ + size_t len = strlen(s1) + 1; + char *s2 = malloc(len); + + if (s2) + memcpy(s2, s1, len); + return s2; +} @@ -927,9 +927,6 @@ static int git_default_core_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "core.pager")) - return git_config_string(&pager_program, var, value); - if (!strcmp(var, "core.editor")) return git_config_string(&editor_program, var, value); @@ -1289,7 +1286,7 @@ static int do_git_config_sequence(config_fn_t fn, void *data) int ret = 0; char *xdg_config = xdg_config_home("config"); char *user_config = expand_user_path("~/.gitconfig"); - char *repo_config = git_pathdup("config"); + char *repo_config = have_git_dir() ? git_pathdup("config") : NULL; current_parsing_scope = CONFIG_SCOPE_SYSTEM; if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) @@ -43,9 +43,9 @@ int check_ref_type(const struct ref *ref, int flags) return check_ref(ref->name, flags); } -static void die_initial_contact(int got_at_least_one_head) +static void die_initial_contact(int unexpected) { - if (got_at_least_one_head) + if (unexpected) die(_("The remote end hung up upon initial contact")); else die(_("Could not read from remote repository.\n\n" @@ -115,10 +115,18 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, struct sha1_array *shallow_points) { struct ref **orig_list = list; - int got_at_least_one_head = 0; + + /* + * A hang-up after seeing some response from the other end + * means that it is unexpected, as we know the other end is + * willing to talk to us. A hang-up before seeing any + * response does not necessarily mean an ACL problem, though. + */ + int saw_response; + int got_dummy_ref_with_capabilities_declaration = 0; *list = NULL; - for (;;) { + for (saw_response = 0; ; saw_response = 1) { struct ref *ref; struct object_id old_oid; char *name; @@ -131,7 +139,7 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, PACKET_READ_GENTLE_ON_EOF | PACKET_READ_CHOMP_NEWLINE); if (len < 0) - die_initial_contact(got_at_least_one_head); + die_initial_contact(saw_response); if (!len) break; @@ -165,13 +173,25 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, continue; } + if (!strcmp(name, "capabilities^{}")) { + if (saw_response) + die("protocol error: unexpected capabilities^{}"); + if (got_dummy_ref_with_capabilities_declaration) + die("protocol error: multiple capabilities^{}"); + got_dummy_ref_with_capabilities_declaration = 1; + continue; + } + if (!check_ref(name, flags)) continue; + + if (got_dummy_ref_with_capabilities_declaration) + die("protocol error: unexpected ref after capabilities^{}"); + ref = alloc_ref(buffer + GIT_SHA1_HEXSZ + 1); oidcpy(&ref->old_oid, &old_oid); *list = ref; list = &ref->next; - got_at_least_one_head = 1; } annotate_refs_with_symref_info(*orig_list); diff --git a/contrib/coccinelle/object_id.cocci b/contrib/coccinelle/object_id.cocci index 8ccdbb5666..0307624a03 100644 --- a/contrib/coccinelle/object_id.cocci +++ b/contrib/coccinelle/object_id.cocci @@ -23,16 +23,16 @@ expression E1; + oid_to_hex(E1) @@ -expression E1; +expression E1, E2; @@ -- sha1_to_hex_r(E1.hash) -+ oid_to_hex_r(&E1) +- sha1_to_hex_r(E1, E2.hash) ++ oid_to_hex_r(E1, &E2) @@ -expression E1; +expression E1, E2; @@ -- sha1_to_hex_r(E1->hash) -+ oid_to_hex_r(E1) +- sha1_to_hex_r(E1, E2->hash) ++ oid_to_hex_r(E1, E2) @@ expression E1; diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci new file mode 100644 index 0000000000..7932d48cdf --- /dev/null +++ b/contrib/coccinelle/strbuf.cocci @@ -0,0 +1,5 @@ +@@ +expression E1, E2; +@@ +- strbuf_addf(E1, E2); ++ strbuf_addstr(E1, E2); diff --git a/contrib/diff-highlight/Makefile b/contrib/diff-highlight/Makefile new file mode 100644 index 0000000000..9018724524 --- /dev/null +++ b/contrib/diff-highlight/Makefile @@ -0,0 +1,5 @@ +# nothing to build +all: + +test: + $(MAKE) -C t diff --git a/contrib/diff-highlight/diff-highlight b/contrib/diff-highlight/diff-highlight index ffefc31a98..81bd8040e3 100755 --- a/contrib/diff-highlight/diff-highlight +++ b/contrib/diff-highlight/diff-highlight @@ -21,6 +21,10 @@ my $RESET = "\x1b[m"; my $COLOR = qr/\x1b\[[0-9;]*m/; my $BORING = qr/$COLOR|\s/; +# The patch portion of git log -p --graph should only ever have preceding | and +# not / or \ as merge history only shows up on the commit line. +my $GRAPH = qr/$COLOR?\|$COLOR?\s+/; + my @removed; my @added; my $in_hunk; @@ -32,12 +36,12 @@ $SIG{PIPE} = 'DEFAULT'; while (<>) { if (!$in_hunk) { print; - $in_hunk = /^$COLOR*\@/; + $in_hunk = /^$GRAPH*$COLOR*\@\@ /; } - elsif (/^$COLOR*-/) { + elsif (/^$GRAPH*$COLOR*-/) { push @removed, $_; } - elsif (/^$COLOR*\+/) { + elsif (/^$GRAPH*$COLOR*\+/) { push @added, $_; } else { @@ -46,7 +50,7 @@ while (<>) { @added = (); print; - $in_hunk = /^$COLOR*[\@ ]/; + $in_hunk = /^$GRAPH*$COLOR*[\@ ]/; } # Most of the time there is enough output to keep things streaming, @@ -163,6 +167,9 @@ sub highlight_pair { } } +# we split either by $COLOR or by character. This has the side effect of +# leaving in graph cruft. It works because the graph cruft does not contain "-" +# or "+" sub split_line { local $_ = shift; return utf8::decode($_) ? @@ -211,8 +218,8 @@ sub is_pair_interesting { my $suffix_a = join('', @$a[($sa+1)..$#$a]); my $suffix_b = join('', @$b[($sb+1)..$#$b]); - return $prefix_a !~ /^$COLOR*-$BORING*$/ || - $prefix_b !~ /^$COLOR*\+$BORING*$/ || + return $prefix_a !~ /^$GRAPH*$COLOR*-$BORING*$/ || + $prefix_b !~ /^$GRAPH*$COLOR*\+$BORING*$/ || $suffix_a !~ /^$BORING*$/ || $suffix_b !~ /^$BORING*$/; } diff --git a/contrib/diff-highlight/t/.gitignore b/contrib/diff-highlight/t/.gitignore new file mode 100644 index 0000000000..7dcbb232cd --- /dev/null +++ b/contrib/diff-highlight/t/.gitignore @@ -0,0 +1,2 @@ +/trash directory* +/test-results diff --git a/contrib/diff-highlight/t/Makefile b/contrib/diff-highlight/t/Makefile new file mode 100644 index 0000000000..5ff5275496 --- /dev/null +++ b/contrib/diff-highlight/t/Makefile @@ -0,0 +1,22 @@ +-include ../../../config.mak.autogen +-include ../../../config.mak + +# copied from ../../t/Makefile +SHELL_PATH ?= $(SHELL) +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) + +all: test +test: $(T) + +.PHONY: help clean all test $(T) + +help: + @echo 'Run "$(MAKE) test" to launch test scripts' + @echo 'Run "$(MAKE) clean" to remove trash folders' + +$(T): + @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) + +clean: + $(RM) -r 'trash directory'.* diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh new file mode 100755 index 0000000000..3b43dbed74 --- /dev/null +++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh @@ -0,0 +1,296 @@ +#!/bin/sh + +test_description='Test diff-highlight' + +CURR_DIR=$(pwd) +TEST_OUTPUT_DIRECTORY=$(pwd) +TEST_DIRECTORY="$CURR_DIR"/../../../t +DIFF_HIGHLIGHT="$CURR_DIR"/../diff-highlight + +CW="$(printf "\033[7m")" # white +CR="$(printf "\033[27m")" # reset + +. "$TEST_DIRECTORY"/test-lib.sh + +if ! test_have_prereq PERL +then + skip_all='skipping diff-highlight tests; perl not available' + test_done +fi + +# dh_test is a test helper function which takes 3 file names as parameters. The +# first 2 files are used to generate diff and commit output, which is then +# piped through diff-highlight. The 3rd file should contain the expected output +# of diff-highlight (minus the diff/commit header, ie. everything after and +# including the first @@ line). +dh_test () { + a="$1" b="$2" && + + cat >patch.exp && + + { + cat "$a" >file && + git add file && + git commit -m "Add a file" && + + cat "$b" >file && + git diff file >diff.raw && + git commit -a -m "Update a file" && + git show >commit.raw + } >/dev/null && + + "$DIFF_HIGHLIGHT" <diff.raw | test_strip_patch_header >diff.act && + "$DIFF_HIGHLIGHT" <commit.raw | test_strip_patch_header >commit.act && + test_cmp patch.exp diff.act && + test_cmp patch.exp commit.act +} + +test_strip_patch_header () { + sed -n '/^@@/,$p' $* +} + +# dh_test_setup_history generates a contrived graph such that we have at least +# 1 nesting (E) and 2 nestings (F). +# +# A branch +# / +# D---E---F master +# +# git log --all --graph +# * commit +# | A +# | * commit +# | | F +# | * commit +# |/ +# | E +# * commit +# D +# +dh_test_setup_history () { + echo "file1" >file1 && + echo "file2" >file2 && + echo "file3" >file3 && + + cat file1 >file && + git add file && + git commit -m "D" && + + git checkout -b branch && + cat file2 >file && + git commit -a -m "A" && + + git checkout master && + cat file2 >file && + git commit -a -m "E" && + + cat file3 >file && + git commit -a -m "F" +} + +left_trim () { + "$PERL_PATH" -pe 's/^\s+//' +} + +trim_graph () { + # graphs start with * or | + # followed by a space or / or \ + "$PERL_PATH" -pe 's@^((\*|\|)( |/|\\))+@@' +} + +test_expect_success 'diff-highlight highlights the beginning of a line' ' + cat >a <<-\EOF && + aaa + bbb + ccc + EOF + + cat >b <<-\EOF && + aaa + 0bb + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -${CW}b${CR}bb + +${CW}0${CR}bb + ccc + EOF +' + +test_expect_success 'diff-highlight highlights the end of a line' ' + cat >a <<-\EOF && + aaa + bbb + ccc + EOF + + cat >b <<-\EOF && + aaa + bb0 + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -bb${CW}b${CR} + +bb${CW}0${CR} + ccc + EOF +' + +test_expect_success 'diff-highlight highlights the middle of a line' ' + cat >a <<-\EOF && + aaa + bbb + ccc + EOF + + cat >b <<-\EOF && + aaa + b0b + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -b${CW}b${CR}b + +b${CW}0${CR}b + ccc + EOF +' + +test_expect_success 'diff-highlight does not highlight whole line' ' + cat >a <<-\EOF && + aaa + bbb + ccc + EOF + + cat >b <<-\EOF && + aaa + 000 + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -bbb + +000 + ccc + EOF +' + +test_expect_failure 'diff-highlight highlights mismatched hunk size' ' + cat >a <<-\EOF && + aaa + bbb + EOF + + cat >b <<-\EOF && + aaa + b0b + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -b${CW}b${CR}b + +b${CW}0${CR}b + +ccc + EOF +' + +# These two code points share the same leading byte in UTF-8 representation; +# a naive byte-wise diff would highlight only the second byte. +# +# - U+00f3 ("o" with acute) +o_accent=$(printf '\303\263') +# - U+00f8 ("o" with stroke) +o_stroke=$(printf '\303\270') + +test_expect_success 'diff-highlight treats multibyte utf-8 as a unit' ' + echo "unic${o_accent}de" >a && + echo "unic${o_stroke}de" >b && + dh_test a b <<-EOF + @@ -1 +1 @@ + -unic${CW}${o_accent}${CR}de + +unic${CW}${o_stroke}${CR}de + EOF +' + +# Unlike the UTF-8 above, these are combining code points which are meant +# to modify the character preceding them: +# +# - U+0301 (combining acute accent) +combine_accent=$(printf '\314\201') +# - U+0302 (combining circumflex) +combine_circum=$(printf '\314\202') + +test_expect_failure 'diff-highlight treats combining code points as a unit' ' + echo "unico${combine_accent}de" >a && + echo "unico${combine_circum}de" >b && + dh_test a b <<-EOF + @@ -1 +1 @@ + -unic${CW}o${combine_accent}${CR}de + +unic${CW}o${combine_circum}${CR}de + EOF +' + +test_expect_success 'diff-highlight works with the --graph option' ' + dh_test_setup_history && + + # topo-order so that the order of the commits is the same as with --graph + # trim graph elements so we can do a diff + # trim leading space because our trim_graph is not perfect + git log --branches -p --topo-order | + "$DIFF_HIGHLIGHT" | left_trim >graph.exp && + git log --branches -p --graph | + "$DIFF_HIGHLIGHT" | trim_graph | left_trim >graph.act && + test_cmp graph.exp graph.act +' + +# Most combined diffs won't meet diff-highlight's line-number filter. So we +# create one here where one side drops a line and the other modifies it. That +# should result in a diff like: +# +# - modified content +# ++resolved content +# +# which naively looks like one side added "+resolved". +test_expect_success 'diff-highlight ignores combined diffs' ' + echo "content" >file && + git add file && + git commit -m base && + + >file && + git commit -am master && + + git checkout -b other HEAD^ && + echo "modified content" >file && + git commit -am other && + + test_must_fail git merge master && + echo "resolved content" >file && + git commit -am resolved && + + cat >expect <<-\EOF && + --- a/file + +++ b/file + @@@ -1,1 -1,0 +1,1 @@@ + - modified content + ++resolved content + EOF + + git show -c | "$DIFF_HIGHLIGHT" >actual.raw && + sed -n "/^---/,\$p" <actual.raw >actual && + test_cmp expect actual +' + +test_done diff --git a/diff-lib.c b/diff-lib.c index bc49c708c1..3007c8524c 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -155,7 +155,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) if (2 <= stage) { int mode = nce->ce_mode; num_compare_stages++; - hashcpy(dpath->parent[stage-2].oid.hash, nce->sha1); + oidcpy(&dpath->parent[stage - 2].oid, + &nce->oid); dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode); dpath->parent[stage-2].status = DIFF_STATUS_MODIFIED; @@ -209,7 +210,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } diff_addremove(&revs->diffopt, '-', ce->ce_mode, - ce->sha1, !is_null_sha1(ce->sha1), + ce->oid.hash, + !is_null_oid(&ce->oid), ce->name, 0); continue; } @@ -225,8 +227,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } oldmode = ce->ce_mode; - old_sha1 = ce->sha1; - new_sha1 = changed ? null_sha1 : ce->sha1; + old_sha1 = ce->oid.hash; + new_sha1 = changed ? null_sha1 : ce->oid.hash; diff_change(&revs->diffopt, oldmode, newmode, old_sha1, new_sha1, !is_null_sha1(old_sha1), @@ -261,7 +263,7 @@ static int get_stat_data(const struct cache_entry *ce, int cached, int match_missing, unsigned *dirty_submodule, struct diff_options *diffopt) { - const unsigned char *sha1 = ce->sha1; + const unsigned char *sha1 = ce->oid.hash; unsigned int mode = ce->ce_mode; if (!cached && !ce_uptodate(ce)) { @@ -324,12 +326,13 @@ static int show_modified(struct rev_info *revs, &dirty_submodule, &revs->diffopt) < 0) { if (report_missing) diff_index_show_file(revs, "-", old, - old->sha1, 1, old->ce_mode, 0); + old->oid.hash, 1, old->ce_mode, + 0); return -1; } if (revs->combine_merges && !cached && - (hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) { + (hashcmp(sha1, old->oid.hash) || oidcmp(&old->oid, &new->oid))) { struct combine_diff_path *p; int pathlen = ce_namelen(new); @@ -343,22 +346,22 @@ static int show_modified(struct rev_info *revs, memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent)); p->parent[0].status = DIFF_STATUS_MODIFIED; p->parent[0].mode = new->ce_mode; - hashcpy(p->parent[0].oid.hash, new->sha1); + oidcpy(&p->parent[0].oid, &new->oid); p->parent[1].status = DIFF_STATUS_MODIFIED; p->parent[1].mode = old->ce_mode; - hashcpy(p->parent[1].oid.hash, old->sha1); + oidcpy(&p->parent[1].oid, &old->oid); show_combined_diff(p, 2, revs->dense_combined_merges, revs); free(p); return 0; } oldmode = old->ce_mode; - if (mode == oldmode && !hashcmp(sha1, old->sha1) && !dirty_submodule && + if (mode == oldmode && !hashcmp(sha1, old->oid.hash) && !dirty_submodule && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) return 0; diff_change(&revs->diffopt, oldmode, mode, - old->sha1, sha1, 1, !is_null_sha1(sha1), + old->oid.hash, sha1, 1, !is_null_sha1(sha1), old->name, 0, dirty_submodule); return 0; } @@ -392,7 +395,8 @@ 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->sha1, 1, tree->ce_mode); + fill_filespec(pair->one, tree->oid.hash, 1, + tree->ce_mode); return; } @@ -408,7 +412,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, * Something removed from the tree? */ if (!idx) { - diff_index_show_file(revs, "-", tree, tree->sha1, 1, tree->ce_mode, 0); + diff_index_show_file(revs, "-", tree, tree->oid.hash, 1, + tree->ce_mode, 0); return; } diff --git a/diff-no-index.c b/diff-no-index.c index 1f8999b9ca..f420786039 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -281,6 +281,9 @@ void diff_no_index(struct rev_info *revs, DIFF_OPT_SET(&revs->diffopt, NO_INDEX); + DIFF_OPT_SET(&revs->diffopt, RELATIVE_NAME); + revs->diffopt.prefix = prefix; + revs->max_count = -2; diff_setup_done(&revs->diffopt); @@ -18,6 +18,7 @@ #include "ll-merge.h" #include "string-list.h" #include "argv-array.h" +#include "graph.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -26,6 +27,7 @@ #endif static int diff_detect_rename_default; +static int diff_indent_heuristic; /* experimental */ static int diff_compaction_heuristic; /* experimental */ static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; @@ -136,9 +138,11 @@ static int parse_dirstat_params(struct diff_options *options, const char *params static int parse_submodule_params(struct diff_options *options, const char *value) { if (!strcmp(value, "log")) - DIFF_OPT_SET(options, SUBMODULE_LOG); + options->submodule_format = DIFF_SUBMODULE_LOG; else if (!strcmp(value, "short")) - DIFF_OPT_CLR(options, SUBMODULE_LOG); + options->submodule_format = DIFF_SUBMODULE_SHORT; + else if (!strcmp(value, "diff")) + options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF; else return -1; return 0; @@ -179,6 +183,21 @@ void init_diff_ui_defaults(void) diff_detect_rename_default = 1; } +int git_diff_heuristic_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "diff.indentheuristic")) { + diff_indent_heuristic = git_config_bool(var, value); + if (diff_indent_heuristic) + diff_compaction_heuristic = 0; + } + if (!strcmp(var, "diff.compactionheuristic")) { + diff_compaction_heuristic = git_config_bool(var, value); + if (diff_compaction_heuristic) + diff_indent_heuristic = 0; + } + return 0; +} + int git_diff_ui_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { @@ -195,10 +214,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_detect_rename_default = git_config_rename(var, value); return 0; } - if (!strcmp(var, "diff.compactionheuristic")) { - diff_compaction_heuristic = git_config_bool(var, value); - return 0; - } if (!strcmp(var, "diff.autorefreshindex")) { diff_auto_refresh_index = git_config_bool(var, value); return 0; @@ -239,6 +254,8 @@ 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 (git_color_config(var, value, cb) < 0) return -1; @@ -359,7 +376,6 @@ struct emit_callback { const char **label_path; struct diff_words_data *diff_words; struct diff_options *opt; - int *found_changesp; struct strbuf *header; }; @@ -727,7 +743,6 @@ static void emit_rewrite_diff(const char *name_a, memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.color_diff = want_color(o->use_color); - ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b); ecbdata.opt = o; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { @@ -1221,12 +1236,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) struct diff_options *o = ecbdata->opt; const char *line_prefix = diff_line_prefix(o); + o->found_changes = 1; + if (ecbdata->header) { - fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf); + fprintf(o->file, "%s", ecbdata->header->buf); strbuf_reset(ecbdata->header); ecbdata->header = NULL; } - *(ecbdata->found_changesp) = 1; if (ecbdata->label_path[0]) { const char *name_a_tab, *name_b_tab; @@ -1234,9 +1250,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : ""; name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : ""; - fprintf(ecbdata->opt->file, "%s%s--- %s%s%s\n", + fprintf(o->file, "%s%s--- %s%s%s\n", line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab); - fprintf(ecbdata->opt->file, "%s%s+++ %s%s%s\n", + fprintf(o->file, "%s%s+++ %s%s%s\n", line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab); ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } @@ -1254,15 +1270,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) find_lno(line, ecbdata); emit_hunk_header(ecbdata, line, len); if (line[len-1] != '\n') - putc('\n', ecbdata->opt->file); - return; - } - - if (len < 1) { - emit_line(ecbdata->opt, reset, reset, line, len); - if (ecbdata->diff_words - && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) - fputs("~\n", ecbdata->opt->file); + putc('\n', o->file); return; } @@ -1287,8 +1295,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) } diff_words_flush(ecbdata); if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) { - emit_line(ecbdata->opt, context, reset, line, len); - fputs("~\n", ecbdata->opt->file); + emit_line(o, context, reset, line, len); + fputs("~\n", o->file); } else { /* * Skip the prefix character, if any. With @@ -1299,7 +1307,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) line++; len--; } - emit_line(ecbdata->opt, context, reset, line, len); + emit_line(o, context, reset, line, len); } return; } @@ -1321,8 +1329,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) default: /* incomplete line at the end */ ecbdata->lno_in_preimage++; - emit_line(ecbdata->opt, - diff_get_color(ecbdata->color_diff, DIFF_CONTEXT), + emit_line(o, diff_get_color(ecbdata->color_diff, DIFF_CONTEXT), reset, line, len); break; } @@ -1630,7 +1637,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) */ if (options->stat_width == -1) - width = term_columns() - options->output_prefix_length; + width = term_columns() - strlen(line_prefix); else width = options->stat_width ? options->stat_width : 80; number_width = decimal_width(max_change) > number_width ? @@ -2304,17 +2311,37 @@ static void builtin_diff(const char *name_a, struct strbuf header = STRBUF_INIT; const char *line_prefix = diff_line_prefix(o); - if (DIFF_OPT_TST(o, SUBMODULE_LOG) && - (!one->mode || S_ISGITLINK(one->mode)) && - (!two->mode || S_ISGITLINK(two->mode))) { + diff_set_mnemonic_prefix(o, "a/", "b/"); + if (DIFF_OPT_TST(o, REVERSE_DIFF)) { + a_prefix = o->b_prefix; + b_prefix = o->a_prefix; + } else { + a_prefix = o->a_prefix; + b_prefix = o->b_prefix; + } + + if (o->submodule_format == DIFF_SUBMODULE_LOG && + (!one->mode || S_ISGITLINK(one->mode)) && + (!two->mode || S_ISGITLINK(two->mode))) { const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); show_submodule_summary(o->file, one->path ? one->path : two->path, line_prefix, - one->oid.hash, two->oid.hash, + &one->oid, &two->oid, two->dirty_submodule, meta, del, add, reset); return; + } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF && + (!one->mode || S_ISGITLINK(one->mode)) && + (!two->mode || S_ISGITLINK(two->mode))) { + const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); + const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); + show_submodule_inline_diff(o->file, one->path ? one->path : two->path, + line_prefix, + &one->oid, &two->oid, + two->dirty_submodule, + meta, del, add, reset, o); + return; } if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) { @@ -2322,15 +2349,6 @@ static void builtin_diff(const char *name_a, textconv_two = get_textconv(two); } - diff_set_mnemonic_prefix(o, "a/", "b/"); - if (DIFF_OPT_TST(o, REVERSE_DIFF)) { - a_prefix = o->b_prefix; - b_prefix = o->a_prefix; - } else { - a_prefix = o->a_prefix; - b_prefix = o->b_prefix; - } - /* Never use a non-valid filename anywhere if at all possible */ name_a = DIFF_FILE_VALID(one) ? name_a : name_b; name_b = DIFF_FILE_VALID(two) ? name_b : name_a; @@ -2443,7 +2461,6 @@ static void builtin_diff(const char *name_a, memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.label_path = lbl; ecbdata.color_diff = want_color(o->use_color); - ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b); if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); @@ -2705,7 +2722,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->sha1) || !S_ISREG(ce->ce_mode)) + if (hashcmp(sha1, ce->oid.hash) || !S_ISREG(ce->ce_mode)) return 0; /* @@ -3298,7 +3315,9 @@ void diff_setup(struct diff_options *options) options->use_color = diff_use_color_default; options->detect_rename = diff_detect_rename_default; options->xdl_opts |= diff_algorithm; - if (diff_compaction_heuristic) + if (diff_indent_heuristic) + DIFF_XDL_SET(options, INDENT_HEURISTIC); + else if (diff_compaction_heuristic) DIFF_XDL_SET(options, COMPACTION_HEURISTIC); options->orderfile = diff_order_file_cfg; @@ -3820,9 +3839,15 @@ int diff_opt_parse(struct diff_options *options, DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); else if (!strcmp(arg, "--ignore-blank-lines")) DIFF_XDL_SET(options, IGNORE_BLANK_LINES); - else if (!strcmp(arg, "--compaction-heuristic")) + else if (!strcmp(arg, "--indent-heuristic")) { + DIFF_XDL_SET(options, INDENT_HEURISTIC); + DIFF_XDL_CLR(options, COMPACTION_HEURISTIC); + } else if (!strcmp(arg, "--no-indent-heuristic")) + DIFF_XDL_CLR(options, INDENT_HEURISTIC); + else if (!strcmp(arg, "--compaction-heuristic")) { DIFF_XDL_SET(options, COMPACTION_HEURISTIC); - else if (!strcmp(arg, "--no-compaction-heuristic")) + DIFF_XDL_CLR(options, INDENT_HEURISTIC); + } else if (!strcmp(arg, "--no-compaction-heuristic")) DIFF_XDL_CLR(options, COMPACTION_HEURISTIC); else if (!strcmp(arg, "--patience")) options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); @@ -3920,7 +3945,7 @@ int diff_opt_parse(struct diff_options *options, DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG); handle_ignore_submodules_arg(options, arg); } else if (!strcmp(arg, "--submodule")) - DIFF_OPT_SET(options, SUBMODULE_LOG); + options->submodule_format = DIFF_SUBMODULE_LOG; else if (skip_prefix(arg, "--submodule=", &arg)) return parse_submodule_opt(options, arg); else if (skip_prefix(arg, "--ws-error-highlight=", &arg)) @@ -3971,6 +3996,12 @@ int diff_opt_parse(struct diff_options *options, options->a_prefix = optarg; return argcount; } + else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) { + options->line_prefix = optarg; + options->line_prefix_length = strlen(options->line_prefix); + graph_setup_line_prefix(options); + return argcount; + } else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) { options->b_prefix = optarg; return argcount; @@ -83,7 +83,6 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_OPT_DIRSTAT_BY_FILE (1 << 20) #define DIFF_OPT_ALLOW_TEXTCONV (1 << 21) #define DIFF_OPT_DIFF_FROM_CONTENTS (1 << 22) -#define DIFF_OPT_SUBMODULE_LOG (1 << 23) #define DIFF_OPT_DIRTY_SUBMODULES (1 << 24) #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) @@ -110,11 +109,19 @@ enum diff_words_type { DIFF_WORDS_COLOR }; +enum diff_submodule_format { + DIFF_SUBMODULE_SHORT = 0, + DIFF_SUBMODULE_LOG, + DIFF_SUBMODULE_INLINE_DIFF +}; + struct diff_options { const char *orderfile; const char *pickaxe; const char *single_follow; const char *a_prefix, *b_prefix; + const char *line_prefix; + size_t line_prefix_length; unsigned flags; unsigned touched_flags; @@ -155,6 +162,7 @@ struct diff_options { int stat_count; const char *word_regex; enum diff_words_type word_diff; + enum diff_submodule_format submodule_format; /* this is set by diffcore for DIFF_FORMAT_PATCH */ int found_changes; @@ -174,7 +182,6 @@ struct diff_options { diff_format_fn_t format_callback; void *format_callback_data; diff_prefix_fn_t output_prefix; - int output_prefix_length; void *output_prefix_data; int diff_path_counter; @@ -266,6 +273,7 @@ extern int parse_long_opt(const char *opt, const char **argv, const char **optarg); extern int git_diff_basic_config(const char *var, const char *value, void *cb); +extern int git_diff_heuristic_config(const char *var, const char *value, void *cb); extern void init_diff_ui_defaults(void); extern int git_diff_ui_config(const char *var, const char *value, void *cb); extern void diff_setup(struct diff_options *); @@ -525,7 +525,7 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size, return NULL; if (!ce_skip_worktree(active_cache[pos])) return NULL; - data = read_sha1_file(active_cache[pos]->sha1, &type, &sz); + data = read_sha1_file(active_cache[pos]->oid.hash, &type, &sz); if (!data || type != OBJ_BLOB) { free(data); return NULL; @@ -533,7 +533,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]->sha1); + hashcpy(sha1_stat->sha1, active_cache[pos]->oid.hash); } return data; } @@ -713,7 +713,8 @@ static int add_excludes(const char *fname, const char *base, int baselen, !ce_stage(active_cache[pos]) && ce_uptodate(active_cache[pos]) && !would_convert_to_git(fname)) - hashcpy(sha1_stat->sha1, active_cache[pos]->sha1); + hashcpy(sha1_stat->sha1, + active_cache[pos]->oid.hash); else hash_sha1_file(buf, size, "blob", sha1_stat->sha1); fill_stat_data(&sha1_stat->stat, &st); @@ -82,7 +82,7 @@ static int create_file(const char *path, unsigned int mode) static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size) { enum object_type type; - void *new = read_sha1_file(ce->sha1, &type, size); + void *new = read_sha1_file(ce->oid.hash, &type, size); if (new) { if (type == OBJ_BLOB) @@ -127,7 +127,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path, if (fd < 0) return -1; - result |= stream_blob_to_fd(fd, ce->sha1, filter, 1); + result |= stream_blob_to_fd(fd, &ce->oid, filter, 1); *fstat_done = fstat_output(fd, state, statbuf); result |= close(fd); @@ -148,7 +148,8 @@ static int write_entry(struct cache_entry *ce, struct stat st; if (ce_mode_s_ifmt == S_IFREG) { - struct stream_filter *filter = get_stream_filter(ce->name, ce->sha1); + struct stream_filter *filter = get_stream_filter(ce->name, + ce->oid.hash); if (filter && !streaming_write_entry(ce, path, filter, state, to_tempfile, @@ -162,7 +163,7 @@ static int write_entry(struct cache_entry *ce, new = read_blob_entry(ce, &size); if (!new) return error("unable to read sha1 file of %s (%s)", - path, sha1_to_hex(ce->sha1)); + path, oid_to_hex(&ce->oid)); if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) { ret = symlink(new, path); diff --git a/environment.c b/environment.c index ca72464a98..cd5aa57179 100644 --- a/environment.c +++ b/environment.c @@ -40,7 +40,6 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 96 * 1024 * 1024; unsigned long big_file_threshold = 512 * 1024 * 1024; -const char *pager_program; int pager_use_color = 1; const char *editor_program; const char *askpass_program; @@ -196,6 +195,13 @@ int is_bare_repository(void) return is_bare_repository_cfg && !get_git_work_tree(); } +int have_git_dir(void) +{ + return startup_info->have_repository + || git_dir + || getenv(GIT_DIR_ENVIRONMENT); +} + const char *get_git_dir(void) { if (!git_dir) @@ -345,3 +351,8 @@ int get_shared_repository(void) } return the_shared_repository; } + +void reset_shared_repository(void) +{ + need_shared_repository_from_config = 1; +} diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 642cce1ac6..ee3d812695 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -45,6 +45,7 @@ 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_compaction_heuristic = $repo->config_bool('diff.compactionheuristic'); my $diff_filter = $repo->config('interactive.difffilter'); @@ -750,7 +751,9 @@ sub parse_diff { if (defined $diff_algorithm) { splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}"; } - if ($diff_compaction_heuristic) { + if ($diff_indent_heuristic) { + splice @diff_cmd, 1, 0, "--indent-heuristic"; + } elsif ($diff_compaction_heuristic) { splice @diff_cmd, 1, 0, "--compaction-heuristic"; } if (defined $patch_mode_revision) { diff --git a/git-compat-util.h b/git-compat-util.h index db89ba7748..37cce0746f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -436,10 +436,14 @@ static inline int const_error(void) return -1; } #define error(...) (error(__VA_ARGS__), const_error()) +#define error_errno(...) (error_errno(__VA_ARGS__), const_error()) #endif extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); extern void set_error_routine(void (*routine)(const char *err, va_list params)); +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 *); @@ -663,6 +667,14 @@ void *gitmemmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen); #endif +#ifdef OVERRIDE_STRDUP +#ifdef strdup +#undef strdup +#endif +#define strdup gitstrdup +char *gitstrdup(const char *s); +#endif + #ifdef NO_GETPAGESIZE #define getpagesize() sysconf(_SC_PAGESIZE) #endif diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 864b687057..01d2cc280b 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -369,6 +369,9 @@ A rescan will be automatically started now. # -- Create the commit. # set cmd [list commit-tree $tree_id] + if {[is_config_true commit.gpgsign]} { + lappend cmd -S + } foreach p [concat $PARENT $MERGE_HEAD] { lappend cmd -p $p } diff --git a/git-gui/po/ja.po b/git-gui/po/ja.po index 9aff249d5f..208651c1af 100644 --- a/git-gui/po/ja.po +++ b/git-gui/po/ja.po @@ -1,47 +1,50 @@ # Translation of git-gui to Japanese # Copyright (C) 2007 Shawn Pearce # This file is distributed under the same license as the git-gui package. +# # ã—らã„ã— ãªãªã“ <nanako3@bluebottle.com>, 2007. +# Satoshi Yasushima <s.yasushima@gmail.com>, 2016. # msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-01-26 15:47-0800\n" -"PO-Revision-Date: 2010-02-02 19:03+0900\n" -"Last-Translator: ã—らã„ã— ãªãªã“ <nanako3@lavabit.com>\n" +"POT-Creation-Date: 2016-05-27 17:52+0900\n" +"PO-Revision-Date: 2016-06-22 12:50+0900\n" +"Last-Translator: Satoshi Yasushima <s.yasushima@gmail.com>\n" "Language-Team: Japanese\n" +"Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: git-gui.sh:41 git-gui.sh:793 git-gui.sh:807 git-gui.sh:820 git-gui.sh:903 -#: git-gui.sh:922 -msgid "git-gui: fatal error" -msgstr "git-gui: 致命的ãªã‚¨ãƒ©ãƒ¼" - -#: git-gui.sh:743 +#: git-gui.sh:861 #, tcl-format msgid "Invalid font specified in %s:" msgstr "%s ã«ç„¡åŠ¹ãªãƒ•ã‚©ãƒ³ãƒˆãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã™:" -#: git-gui.sh:779 +#: git-gui.sh:915 msgid "Main Font" msgstr "主フォント" -#: git-gui.sh:780 +#: git-gui.sh:916 msgid "Diff/Console Font" msgstr "diff/コンソール・フォント" -#: git-gui.sh:794 +#: git-gui.sh:931 git-gui.sh:945 git-gui.sh:958 git-gui.sh:1048 git-gui.sh:1067 +#: git-gui.sh:3125 +msgid "git-gui: fatal error" +msgstr "git-gui: 致命的ãªã‚¨ãƒ©ãƒ¼" + +#: git-gui.sh:932 msgid "Cannot find git in PATH." msgstr "PATH ä¸ã« git ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“" -#: git-gui.sh:821 +#: git-gui.sh:959 msgid "Cannot parse Git version string:" msgstr "Git ãƒãƒ¼ã‚¸ãƒ§ãƒ³åãŒç†è§£ã§ãã¾ã›ã‚“:" -#: git-gui.sh:839 +#: git-gui.sh:984 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -60,475 +63,502 @@ msgstr "" "\n" "'%s' ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³ 1.5.0 ã¨æ€ã£ã¦è‰¯ã„ã§ã™ã‹ï¼Ÿ\n" -#: git-gui.sh:1128 +#: git-gui.sh:1281 msgid "Git directory not found:" msgstr "Git ディレクトリãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“:" -#: git-gui.sh:1146 +#: git-gui.sh:1315 msgid "Cannot move to top of working directory:" msgstr "作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®æœ€ä¸Šä½ã«ç§»å‹•ã§ãã¾ã›ã‚“" -#: git-gui.sh:1154 +#: git-gui.sh:1323 msgid "Cannot use bare repository:" msgstr "裸ã®ãƒªãƒã‚¸ãƒˆãƒªã¯ä½¿ãˆã¾ã›ã‚“:" -#: git-gui.sh:1162 +#: git-gui.sh:1331 msgid "No working directory" msgstr "作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãŒã‚ã‚Šã¾ã›ã‚“" -#: git-gui.sh:1334 lib/checkout_op.tcl:306 +#: git-gui.sh:1503 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "ファイル状態を更新ã—ã¦ã„ã¾ã™â€¦" -#: git-gui.sh:1390 +#: git-gui.sh:1563 msgid "Scanning for modified files ..." msgstr "変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’スã‚ャンã—ã¦ã„ã¾ã™â€¦" -#: git-gui.sh:1454 +#: git-gui.sh:1639 msgid "Calling prepare-commit-msg hook..." msgstr "prepare-commit-msg フックを実行ä¸ãƒ»ãƒ»ãƒ»" -#: git-gui.sh:1471 +#: git-gui.sh:1656 msgid "Commit declined by prepare-commit-msg hook." msgstr "prepare-commit-msg フックãŒã‚³ãƒŸãƒƒãƒˆã‚’æ‹’å¦ã—ã¾ã—ãŸ" -#: git-gui.sh:1629 lib/browser.tcl:246 +#: git-gui.sh:1814 lib/browser.tcl:252 msgid "Ready." msgstr "準備完了" -#: git-gui.sh:1787 +#: git-gui.sh:1978 #, tcl-format -msgid "Displaying only %s of %s files." -msgstr "全体ã§%s個ã®å†…ã®%sファイルã ã‘表示ã—ã¦ã„ã¾ã™" +msgid "" +"Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files." +msgstr "" +"表示å¯èƒ½ãªé™ç•Œ (gui.maxfilesdisplayed = %s) ã«é”ã—ãŸã‚ã€å…¨ä½“ã§%s個ã®ãƒ•ã‚¡ã‚¤ãƒ«" +"を表示ã§ãã¾ã›ã‚“" -#: git-gui.sh:1913 +#: git-gui.sh:2101 msgid "Unmodified" msgstr "変更無ã—" -#: git-gui.sh:1915 +#: git-gui.sh:2103 msgid "Modified, not staged" msgstr "変更ã‚ã‚Šã€ã‚³ãƒŸãƒƒãƒˆæœªäºˆå®š" -#: git-gui.sh:1916 git-gui.sh:1924 +#: git-gui.sh:2104 git-gui.sh:2116 msgid "Staged for commit" msgstr "コミット予定済" -#: git-gui.sh:1917 git-gui.sh:1925 +#: git-gui.sh:2105 git-gui.sh:2117 msgid "Portions staged for commit" msgstr "部分的ã«ã‚³ãƒŸãƒƒãƒˆäºˆå®šæ¸ˆ" -#: git-gui.sh:1918 git-gui.sh:1926 +#: git-gui.sh:2106 git-gui.sh:2118 msgid "Staged for commit, missing" msgstr "コミット予定済ã€ãƒ•ã‚¡ã‚¤ãƒ«ç„¡ã—" -#: git-gui.sh:1920 +#: git-gui.sh:2108 msgid "File type changed, not staged" msgstr "ファイル型変更ã€ã‚³ãƒŸãƒƒãƒˆæœªäºˆå®š" -#: git-gui.sh:1921 +#: git-gui.sh:2109 git-gui.sh:2110 +msgid "File type changed, old type staged for commit" +msgstr "ファイル型変更ã€æ—§åž‹ã‚³ãƒŸãƒƒãƒˆäºˆå®šæ¸ˆ" + +#: git-gui.sh:2111 msgid "File type changed, staged" msgstr "ファイル型変更ã€ã‚³ãƒŸãƒƒãƒˆäºˆå®šæ¸ˆ" -#: git-gui.sh:1923 +#: git-gui.sh:2112 +msgid "File type change staged, modification not staged" +msgstr "ファイル型変更コミット予定済ã€å¤‰æ›´ã‚³ãƒŸãƒƒãƒˆæœªäºˆå®š" + +#: git-gui.sh:2113 +msgid "File type change staged, file missing" +msgstr "ファイル型変更コミット予定済ã€ãƒ•ã‚¡ã‚¤ãƒ«ç„¡ã—" + +#: git-gui.sh:2115 msgid "Untracked, not staged" msgstr "管ç†å¤–ã€ã‚³ãƒŸãƒƒãƒˆæœªäºˆå®š" -#: git-gui.sh:1928 +#: git-gui.sh:2120 msgid "Missing" msgstr "ファイル無ã—" -#: git-gui.sh:1929 +#: git-gui.sh:2121 msgid "Staged for removal" msgstr "削除予定済" -#: git-gui.sh:1930 +#: git-gui.sh:2122 msgid "Staged for removal, still present" msgstr "削除予定済ã€ãƒ•ã‚¡ã‚¤ãƒ«æœªå‰Šé™¤" -#: git-gui.sh:1932 git-gui.sh:1933 git-gui.sh:1934 git-gui.sh:1935 -#: git-gui.sh:1936 git-gui.sh:1937 +#: git-gui.sh:2124 git-gui.sh:2125 git-gui.sh:2126 git-gui.sh:2127 +#: git-gui.sh:2128 git-gui.sh:2129 msgid "Requires merge resolution" msgstr "è¦ãƒžãƒ¼ã‚¸è§£æ±º" -#: git-gui.sh:1972 +#: git-gui.sh:2164 msgid "Starting gitk... please wait..." msgstr "gitk ã‚’èµ·å‹•ä¸â€¦ãŠå¾…ã¡ä¸‹ã•ã„…" -#: git-gui.sh:1984 +#: git-gui.sh:2176 msgid "Couldn't find gitk in PATH" msgstr "PATH ä¸ã« gitk ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“" -#: git-gui.sh:2043 +#: git-gui.sh:2235 msgid "Couldn't find git gui in PATH" msgstr "PATH ä¸ã« git gui ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“" -#: git-gui.sh:2455 lib/choose_repository.tcl:36 +#: git-gui.sh:2654 lib/choose_repository.tcl:41 msgid "Repository" msgstr "リãƒã‚¸ãƒˆãƒª" -#: git-gui.sh:2456 +#: git-gui.sh:2655 msgid "Edit" msgstr "編集" -#: git-gui.sh:2458 lib/choose_rev.tcl:561 +#: git-gui.sh:2657 lib/choose_rev.tcl:567 msgid "Branch" msgstr "ブランãƒ" -#: git-gui.sh:2461 lib/choose_rev.tcl:548 +#: git-gui.sh:2660 lib/choose_rev.tcl:554 msgid "Commit@@noun" msgstr "コミット" -#: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:2663 lib/merge.tcl:123 lib/merge.tcl:152 lib/merge.tcl:170 msgid "Merge" msgstr "マージ" -#: git-gui.sh:2465 lib/choose_rev.tcl:557 +#: git-gui.sh:2664 lib/choose_rev.tcl:563 msgid "Remote" msgstr "リモート" -#: git-gui.sh:2468 +#: git-gui.sh:2667 msgid "Tools" msgstr "ツール" -#: git-gui.sh:2477 +#: git-gui.sh:2676 msgid "Explore Working Copy" msgstr "ワーã‚ングコピーをブラウズ" -#: git-gui.sh:2483 +#: git-gui.sh:2682 +msgid "Git Bash" +msgstr "" + +#: git-gui.sh:2692 msgid "Browse Current Branch's Files" msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る" -#: git-gui.sh:2487 +#: git-gui.sh:2696 msgid "Browse Branch Files..." msgstr "ブランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る…" -#: git-gui.sh:2492 +#: git-gui.sh:2701 msgid "Visualize Current Branch's History" msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®å±¥æ´ã‚’見る" -#: git-gui.sh:2496 +#: git-gui.sh:2705 msgid "Visualize All Branch History" msgstr "å…¨ã¦ã®ãƒ–ランãƒã®å±¥æ´ã‚’見る" -#: git-gui.sh:2503 +#: git-gui.sh:2712 #, tcl-format msgid "Browse %s's Files" msgstr "ブランム%s ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る" -#: git-gui.sh:2505 +#: git-gui.sh:2714 #, tcl-format msgid "Visualize %s's History" msgstr "ブランム%s ã®å±¥æ´ã‚’見る" -#: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2719 lib/database.tcl:40 lib/database.tcl:66 msgid "Database Statistics" msgstr "データベース統計" -#: git-gui.sh:2513 lib/database.tcl:34 +#: git-gui.sh:2722 lib/database.tcl:33 msgid "Compress Database" msgstr "データベース圧縮" -#: git-gui.sh:2516 +#: git-gui.sh:2725 msgid "Verify Database" msgstr "データベース検証" -#: git-gui.sh:2523 git-gui.sh:2527 git-gui.sh:2531 lib/shortcut.tcl:8 +#: git-gui.sh:2732 git-gui.sh:2736 git-gui.sh:2740 lib/shortcut.tcl:8 #: lib/shortcut.tcl:40 lib/shortcut.tcl:72 msgid "Create Desktop Icon" msgstr "デスクトップ・アイコンを作る" -#: git-gui.sh:2539 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 +#: git-gui.sh:2748 lib/choose_repository.tcl:193 lib/choose_repository.tcl:201 msgid "Quit" msgstr "終了" -#: git-gui.sh:2547 +#: git-gui.sh:2756 msgid "Undo" msgstr "å…ƒã«æˆ»ã™" -#: git-gui.sh:2550 +#: git-gui.sh:2759 msgid "Redo" msgstr "ã‚„ã‚Šç›´ã—" -#: git-gui.sh:2554 git-gui.sh:3109 +#: git-gui.sh:2763 git-gui.sh:3368 msgid "Cut" msgstr "切りå–ã‚Š" -#: git-gui.sh:2557 git-gui.sh:3112 git-gui.sh:3186 git-gui.sh:3259 +#: git-gui.sh:2766 git-gui.sh:3371 git-gui.sh:3445 git-gui.sh:3530 #: lib/console.tcl:69 msgid "Copy" msgstr "コピー" -#: git-gui.sh:2560 git-gui.sh:3115 +#: git-gui.sh:2769 git-gui.sh:3374 msgid "Paste" msgstr "貼り付ã‘" -#: git-gui.sh:2563 git-gui.sh:3118 lib/branch_delete.tcl:26 -#: lib/remote_branch_delete.tcl:38 +#: git-gui.sh:2772 git-gui.sh:3377 lib/remote_branch_delete.tcl:39 +#: lib/branch_delete.tcl:28 msgid "Delete" msgstr "削除" -#: git-gui.sh:2567 git-gui.sh:3122 git-gui.sh:3263 lib/console.tcl:71 +#: git-gui.sh:2776 git-gui.sh:3381 git-gui.sh:3534 lib/console.tcl:71 msgid "Select All" msgstr "å…¨ã¦é¸æŠž" -#: git-gui.sh:2576 +#: git-gui.sh:2785 msgid "Create..." msgstr "作æˆâ€¦" -#: git-gui.sh:2582 +#: git-gui.sh:2791 msgid "Checkout..." msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" -#: git-gui.sh:2588 +#: git-gui.sh:2797 msgid "Rename..." msgstr "åå‰å¤‰æ›´â€¦" -#: git-gui.sh:2593 +#: git-gui.sh:2802 msgid "Delete..." msgstr "削除…" -#: git-gui.sh:2598 +#: git-gui.sh:2807 msgid "Reset..." msgstr "リセット…" -#: git-gui.sh:2608 +#: git-gui.sh:2817 msgid "Done" msgstr "完了" -#: git-gui.sh:2610 +#: git-gui.sh:2819 msgid "Commit@@verb" msgstr "コミット" -#: git-gui.sh:2619 git-gui.sh:3050 +#: git-gui.sh:2828 git-gui.sh:3309 msgid "New Commit" msgstr "æ–°è¦ã‚³ãƒŸãƒƒãƒˆ" -#: git-gui.sh:2627 git-gui.sh:3057 +#: git-gui.sh:2836 git-gui.sh:3316 msgid "Amend Last Commit" msgstr "最新コミットを訂æ£" -#: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2846 git-gui.sh:3270 lib/remote_branch_delete.tcl:101 msgid "Rescan" msgstr "å†ã‚¹ã‚ャン" -#: git-gui.sh:2643 +#: git-gui.sh:2852 msgid "Stage To Commit" msgstr "コミット予定ã™ã‚‹" -#: git-gui.sh:2649 +#: git-gui.sh:2858 msgid "Stage Changed Files To Commit" msgstr "変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’コミット予定" -#: git-gui.sh:2655 +#: git-gui.sh:2864 msgid "Unstage From Commit" msgstr "コミットã‹ã‚‰é™ã‚ã™" -#: git-gui.sh:2661 lib/index.tcl:412 +#: git-gui.sh:2870 lib/index.tcl:442 msgid "Revert Changes" msgstr "変更を元ã«æˆ»ã™" -#: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 +#: git-gui.sh:2878 git-gui.sh:3581 git-gui.sh:3612 msgid "Show Less Context" msgstr "文脈を少ãªã" -#: git-gui.sh:2673 git-gui.sh:3314 git-gui.sh:3345 +#: git-gui.sh:2882 git-gui.sh:3585 git-gui.sh:3616 msgid "Show More Context" msgstr "文脈を多ã" -#: git-gui.sh:2680 git-gui.sh:3024 git-gui.sh:3133 +#: git-gui.sh:2889 git-gui.sh:3283 git-gui.sh:3392 msgid "Sign Off" msgstr "ç½²å" -#: git-gui.sh:2696 +#: git-gui.sh:2905 msgid "Local Merge..." msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒžãƒ¼ã‚¸â€¦" -#: git-gui.sh:2701 +#: git-gui.sh:2910 msgid "Abort Merge..." msgstr "マージä¸æ¢â€¦" -#: git-gui.sh:2713 git-gui.sh:2741 +#: git-gui.sh:2922 git-gui.sh:2950 msgid "Add..." msgstr "è¿½åŠ " -#: git-gui.sh:2717 +#: git-gui.sh:2926 msgid "Push..." msgstr "プッシュ…" -#: git-gui.sh:2721 +#: git-gui.sh:2930 msgid "Delete Branch..." msgstr "ブランãƒå‰Šé™¤..." -#: git-gui.sh:2731 git-gui.sh:3292 +#: git-gui.sh:2940 git-gui.sh:3563 msgid "Options..." msgstr "オプション…" -#: git-gui.sh:2742 +#: git-gui.sh:2951 msgid "Remove..." msgstr "削除..." -#: git-gui.sh:2751 lib/choose_repository.tcl:50 +#: git-gui.sh:2960 lib/choose_repository.tcl:55 msgid "Help" msgstr "ヘルプ" -#: git-gui.sh:2755 git-gui.sh:2759 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 +#: git-gui.sh:2964 git-gui.sh:2968 lib/about.tcl:14 +#: lib/choose_repository.tcl:49 lib/choose_repository.tcl:58 #, tcl-format msgid "About %s" msgstr "%s ã«ã¤ã„ã¦" -#: git-gui.sh:2783 +#: git-gui.sh:2992 msgid "Online Documentation" msgstr "オンライン・ドã‚ュメント" -#: git-gui.sh:2786 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +#: git-gui.sh:2995 lib/choose_repository.tcl:52 lib/choose_repository.tcl:61 msgid "Show SSH Key" msgstr "SSH ã‚ーを表示" -#: git-gui.sh:2893 +#: git-gui.sh:3014 git-gui.sh:3146 +msgid "Usage" +msgstr "使ã„æ–¹" + +#: git-gui.sh:3095 lib/blame.tcl:573 +msgid "Error" +msgstr "エラー" + +#: git-gui.sh:3126 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "" "致命的: パス %s ㌠stat ã§ãã¾ã›ã‚“。ãã®ã‚ˆã†ãªãƒ•ã‚¡ã‚¤ãƒ«ã‚„ディレクトリã¯ã‚ã‚Šã¾" "ã›ã‚“" -#: git-gui.sh:2926 +#: git-gui.sh:3159 msgid "Current Branch:" msgstr "ç¾åœ¨ã®ãƒ–ランãƒ" -#: git-gui.sh:2947 +#: git-gui.sh:3185 msgid "Staged Changes (Will Commit)" msgstr "ステージングã•ã‚ŒãŸï¼ˆã‚³ãƒŸãƒƒãƒˆäºˆå®šæ¸ˆã®ï¼‰å¤‰æ›´" -#: git-gui.sh:2967 +#: git-gui.sh:3205 msgid "Unstaged Changes" msgstr "コミット予定ã«å…¥ã£ã¦ã„ãªã„変更" -#: git-gui.sh:3017 +#: git-gui.sh:3276 msgid "Stage Changed" msgstr "変更をコミット予定ã«å…¥ã‚Œã‚‹" -#: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 +#: git-gui.sh:3295 lib/transport.tcl:137 lib/transport.tcl:229 msgid "Push" msgstr "プッシュ" -#: git-gui.sh:3071 +#: git-gui.sh:3330 msgid "Initial Commit Message:" msgstr "最åˆã®ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:" -#: git-gui.sh:3072 +#: git-gui.sh:3331 msgid "Amended Commit Message:" msgstr "訂æ£ã—ãŸã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:" -#: git-gui.sh:3073 +#: git-gui.sh:3332 msgid "Amended Initial Commit Message:" msgstr "訂æ£ã—ãŸæœ€åˆã®ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:" -#: git-gui.sh:3074 +#: git-gui.sh:3333 msgid "Amended Merge Commit Message:" msgstr "訂æ£ã—ãŸãƒžãƒ¼ã‚¸ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸:" -#: git-gui.sh:3075 +#: git-gui.sh:3334 msgid "Merge Commit Message:" msgstr "マージコミットメッセージ:" -#: git-gui.sh:3076 +#: git-gui.sh:3335 msgid "Commit Message:" msgstr "コミットメッセージ:" -#: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 +#: git-gui.sh:3384 git-gui.sh:3538 lib/console.tcl:73 msgid "Copy All" msgstr "å…¨ã¦ã‚³ãƒ”ー" -#: git-gui.sh:3149 lib/blame.tcl:104 +#: git-gui.sh:3408 lib/blame.tcl:105 msgid "File:" msgstr "ファイル:" -#: git-gui.sh:3255 +#: git-gui.sh:3526 msgid "Refresh" msgstr "å†èªã¿è¾¼ã¿" -#: git-gui.sh:3276 +#: git-gui.sh:3547 msgid "Decrease Font Size" msgstr "フォントをå°ã•ã" -#: git-gui.sh:3280 +#: git-gui.sh:3551 msgid "Increase Font Size" msgstr "フォントを大ãã" -#: git-gui.sh:3288 lib/blame.tcl:281 +#: git-gui.sh:3559 lib/blame.tcl:294 msgid "Encoding" msgstr "エンコーディング" -#: git-gui.sh:3299 +#: git-gui.sh:3570 msgid "Apply/Reverse Hunk" msgstr "パッãƒã‚’é©ç”¨/å–り消ã™" -#: git-gui.sh:3304 +#: git-gui.sh:3575 msgid "Apply/Reverse Line" msgstr "パッãƒè¡Œã‚’é©ç”¨/å–り消ã™" -#: git-gui.sh:3323 +#: git-gui.sh:3594 msgid "Run Merge Tool" msgstr "マージツールを起動" -#: git-gui.sh:3328 +#: git-gui.sh:3599 msgid "Use Remote Version" msgstr "リモートã®æ–¹ã‚’採用" -#: git-gui.sh:3332 +#: git-gui.sh:3603 msgid "Use Local Version" msgstr "ãƒãƒ¼ã‚«ãƒ«ã®æ–¹ã‚’採用" -#: git-gui.sh:3336 +#: git-gui.sh:3607 msgid "Revert To Base" msgstr "ベース版を採用" -#: git-gui.sh:3354 +#: git-gui.sh:3625 msgid "Visualize These Changes In The Submodule" msgstr "サブモジュール内ã®ã“れらã®å¤‰æ›´ã‚’見る" -#: git-gui.sh:3358 +#: git-gui.sh:3629 msgid "Visualize Current Branch History In The Submodule" msgstr "サブモジュール内ã§ç¾åœ¨ã®ãƒ–ランãƒã®å±¥æ´ã‚’見る" -#: git-gui.sh:3362 +#: git-gui.sh:3633 msgid "Visualize All Branch History In The Submodule" msgstr "サブモジュール内ã§å…¨ã¦ã®ãƒ–ランãƒã®å±¥æ´ã‚’見る" -#: git-gui.sh:3367 +#: git-gui.sh:3638 msgid "Start git gui In The Submodule" msgstr "サブモジュール内ã§git guiã‚’èµ·å‹•ã™ã‚‹" -#: git-gui.sh:3389 +#: git-gui.sh:3673 msgid "Unstage Hunk From Commit" msgstr "パッãƒã‚’コミット予定ã‹ã‚‰å¤–ã™" -#: git-gui.sh:3391 +#: git-gui.sh:3675 msgid "Unstage Lines From Commit" msgstr "コミット予定ã‹ã‚‰è¡Œã‚’外ã™" -#: git-gui.sh:3393 +#: git-gui.sh:3677 msgid "Unstage Line From Commit" msgstr "コミット予定ã‹ã‚‰è¡Œã‚’外ã™" -#: git-gui.sh:3396 +#: git-gui.sh:3680 msgid "Stage Hunk For Commit" msgstr "パッãƒã‚’コミット予定ã«åŠ ãˆã‚‹" -#: git-gui.sh:3398 +#: git-gui.sh:3682 msgid "Stage Lines For Commit" msgstr "パッãƒè¡Œã‚’コミット予定ã«åŠ ãˆã‚‹" -#: git-gui.sh:3400 +#: git-gui.sh:3684 msgid "Stage Line For Commit" msgstr "パッãƒè¡Œã‚’コミット予定ã«åŠ ãˆã‚‹" -#: git-gui.sh:3424 +#: git-gui.sh:3709 msgid "Initializing..." msgstr "åˆæœŸåŒ–ã—ã¦ã„ã¾ã™â€¦" -#: git-gui.sh:3541 +#: git-gui.sh:3852 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -543,7 +573,7 @@ msgstr "" "以下ã®ç’°å¢ƒå¤‰æ•°ã¯ %s ãŒèµ·å‹•ã™ã‚‹ Git サブプãƒã‚»ã‚¹ã«ã‚ˆã£ã¦ç„¡è¦–ã•ã‚Œã‚‹ã§ã—ょã†:\n" "\n" -#: git-gui.sh:3570 +#: git-gui.sh:3881 msgid "" "\n" "This is due to a known issue with the\n" @@ -553,7 +583,7 @@ msgstr "" "ã“れ㯠Cygwin ã§é…布ã•ã‚Œã¦ã„ã‚‹ Tcl ãƒã‚¤ãƒŠãƒªã«\n" "é–¢ã—ã¦ã®æ—¢çŸ¥ã®å•é¡Œã«ã‚ˆã‚Šã¾ã™" -#: git-gui.sh:3575 +#: git-gui.sh:3886 #, tcl-format msgid "" "\n" @@ -568,249 +598,294 @@ msgstr "" "個人的㪠~/.gitconfig ファイル内㧠user.name 㨠user.email ã®å€¤ã‚’è¨å®š\n" "ã™ã‚‹ã®ãŒã€%s ã®è‰¯ã„代用ã¨ãªã‚Šã¾ã™\n" -#: lib/about.tcl:26 -msgid "git-gui - a graphical user interface for Git." -msgstr "Git ã®ã‚°ãƒ©ãƒ•ã‚£ã‚«ãƒ«UI git-gui" - -#: lib/blame.tcl:72 -msgid "File Viewer" -msgstr "ファイルピューワ" - -#: lib/blame.tcl:78 -msgid "Commit:" -msgstr "コミット:" - -#: lib/blame.tcl:271 -msgid "Copy Commit" -msgstr "コミットをコピー" - -#: lib/blame.tcl:275 -msgid "Find Text..." -msgstr "テã‚ストを検索" - -#: lib/blame.tcl:284 -msgid "Do Full Copy Detection" -msgstr "コピー検知" - -#: lib/blame.tcl:288 -msgid "Show History Context" -msgstr "文脈を見ã›ã‚‹" +#: lib/merge.tcl:13 +msgid "" +"Cannot merge while amending.\n" +"\n" +"You must finish amending this commit before starting any type of merge.\n" +msgstr "" +"訂æ£ä¸ã«ã¯ãƒžãƒ¼ã‚¸ã§ãã¾ã›ã‚“。\n" +"\n" +"訂æ£å‡¦ç†ã‚’完了ã™ã‚‹ã¾ã§ã¯æ–°ãŸã«ãƒžãƒ¼ã‚¸ã‚’開始ã§ãã¾ã›ã‚“。\n" -#: lib/blame.tcl:291 -msgid "Blame Parent Commit" -msgstr "親コミットを註釈" +#: lib/merge.tcl:27 +msgid "" +"Last scanned state does not match repository state.\n" +"\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before a merge can be performed.\n" +"\n" +"The rescan will be automatically started now.\n" +msgstr "" +"最後ã«ã‚¹ã‚ャンã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n" +"\n" +"最後ã«ã‚¹ã‚ャンã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プãƒã‚°ãƒ©ãƒ ãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚マー" +"ジを開始ã™ã‚‹å‰ã«ã€å†ã‚¹ã‚ャンãŒå¿…è¦ã§ã™ã€‚\n" +"\n" +"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" -#: lib/blame.tcl:450 +#: lib/merge.tcl:45 #, tcl-format -msgid "Reading %s..." -msgstr "%s ã‚’èªã‚“ã§ã„ã¾ã™â€¦" - -#: lib/blame.tcl:557 -msgid "Loading copy/move tracking annotations..." -msgstr "コピー・移動追跡データをèªã‚“ã§ã„ã¾ã™â€¦" - -#: lib/blame.tcl:577 -msgid "lines annotated" -msgstr "行を注釈ã—ã¾ã—ãŸ" +msgid "" +"You are in the middle of a conflicted merge.\n" +"\n" +"File %s has merge conflicts.\n" +"\n" +"You must resolve them, stage the file, and commit to complete the current " +"merge. Only then can you begin another merge.\n" +msgstr "" +"è¡çªã®ã‚ã£ãŸãƒžãƒ¼ã‚¸ã®é€”ä¸ã§ã™ã€‚\n" +"\n" +"ファイル %s ã«ã¯ãƒžãƒ¼ã‚¸ä¸ã®è¡çªãŒæ®‹ã£ã¦ã„ã¾ã™ã€‚\n" +"\n" +"ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®è¡çªã‚’解決ã—ã€ã‚³ãƒŸãƒƒãƒˆäºˆå®šã«åŠ ãˆã¦ã€ã‚³ãƒŸãƒƒãƒˆã™ã‚‹ã“ã¨ã§ãƒžãƒ¼ã‚¸ã‚’" +"完了ã—ã¾ã™ã€‚ãã†ã‚„ã£ã¦å§‹ã‚ã¦ã€æ–°ãŸãªãƒžãƒ¼ã‚¸ã‚’開始ã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚\n" -#: lib/blame.tcl:769 -msgid "Loading original location annotations..." -msgstr "å…ƒä½ç½®è¡Œã®æ³¨é‡ˆãƒ‡ãƒ¼ã‚¿ã‚’èªã‚“ã§ã„ã¾ã™â€¦" +#: lib/merge.tcl:55 +#, tcl-format +msgid "" +"You are in the middle of a change.\n" +"\n" +"File %s is modified.\n" +"\n" +"You should complete the current commit before starting a merge. Doing so " +"will help you abort a failed merge, should the need arise.\n" +msgstr "" +"変更ã®é€”ä¸ã§ã™ã€‚\n" +"\n" +"ファイル %s ã¯å¤‰æ›´ä¸ã§ã™ã€‚\n" +"\n" +"ç¾åœ¨ã®ã‚³ãƒŸãƒƒãƒˆã‚’完了ã—ã¦ã‹ã‚‰ãƒžãƒ¼ã‚¸ã‚’開始ã—ã¦ä¸‹ã•ã„。ãã†ã™ã‚‹æ–¹ãŒãƒžãƒ¼ã‚¸ã«å¤±æ•—" +"ã—ãŸã¨ãã®å›žå¾©ãŒæ¥½ã§ã™ã€‚\n" -#: lib/blame.tcl:772 -msgid "Annotation complete." -msgstr "注釈完了ã—ã¾ã—ãŸ" +#: lib/merge.tcl:108 +#, tcl-format +msgid "%s of %s" +msgstr "%s ã® %s ブランãƒ" -#: lib/blame.tcl:802 -msgid "Busy" -msgstr "実行ä¸" +#: lib/merge.tcl:122 +#, tcl-format +msgid "Merging %s and %s..." +msgstr "%s 㨠%s をマージä¸ãƒ»ãƒ»ãƒ»" -#: lib/blame.tcl:803 -msgid "Annotation process is already running." -msgstr "ã™ã§ã« blame プãƒã‚»ã‚¹ã‚’実行ä¸ã§ã™ã€‚" +#: lib/merge.tcl:133 +msgid "Merge completed successfully." +msgstr "マージãŒå®Œäº†ã—ã¾ã—ãŸ" -#: lib/blame.tcl:842 -msgid "Running thorough copy detection..." -msgstr "コピー検知を実行ä¸â€¦" +#: lib/merge.tcl:135 +msgid "Merge failed. Conflict resolution is required." +msgstr "マージãŒå¤±æ•—ã—ã¾ã—ãŸã€‚è¡çªã®è§£æ±ºãŒå¿…è¦ã§ã™ã€‚" -#: lib/blame.tcl:910 -msgid "Loading annotation..." -msgstr "注釈をèªã¿è¾¼ã‚“ã§ã„ã¾ã™â€¦" +#: lib/merge.tcl:160 +#, tcl-format +msgid "Merge Into %s" +msgstr "%s ã«ãƒžãƒ¼ã‚¸" -#: lib/blame.tcl:963 -msgid "Author:" -msgstr "作者:" +#: lib/merge.tcl:166 lib/checkout_op.tcl:567 lib/tools_dlg.tcl:336 +msgid "Visualize" +msgstr "å¯è¦–化" -#: lib/blame.tcl:967 -msgid "Committer:" -msgstr "コミット者:" +#: lib/merge.tcl:174 lib/remote_branch_delete.tcl:43 lib/branch_delete.tcl:34 +#: lib/checkout_op.tcl:579 lib/branch_rename.tcl:32 lib/tools_dlg.tcl:41 +#: lib/tools_dlg.tcl:202 lib/tools_dlg.tcl:345 lib/option.tcl:127 +#: lib/transport.tcl:141 lib/choose_font.tcl:45 lib/branch_checkout.tcl:30 +#: lib/browser.tcl:292 lib/remote_add.tcl:34 lib/branch_create.tcl:37 +msgid "Cancel" +msgstr "ä¸æ¢" -#: lib/blame.tcl:972 -msgid "Original File:" -msgstr "元ファイル" +#: lib/merge.tcl:179 +msgid "Revision To Merge" +msgstr "マージã™ã‚‹ãƒªãƒ“ジョン" -#: lib/blame.tcl:1020 -msgid "Cannot find HEAD commit:" -msgstr "HEAD コミットãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“" +#: lib/merge.tcl:214 +msgid "" +"Cannot abort while amending.\n" +"\n" +"You must finish amending this commit.\n" +msgstr "" +"訂æ£ä¸ã«ã¯ä¸æ¢ã§ãã¾ã›ã‚“。\n" +"\n" +"ã¾ãšä»Šã®ã‚³ãƒŸãƒƒãƒˆè¨‚æ£ã‚’完了ã•ã›ã¦ä¸‹ã•ã„。\n" -#: lib/blame.tcl:1075 -msgid "Cannot find parent commit:" -msgstr "親コミットãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“:" +#: lib/merge.tcl:224 +msgid "" +"Abort merge?\n" +"\n" +"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with aborting the current merge?" +msgstr "" +"マージをä¸æ–ã—ã¾ã™ã‹ï¼Ÿ\n" +"\n" +"ç¾åœ¨ã®ãƒžãƒ¼ã‚¸ã‚’ä¸æ–ã™ã‚‹ã¨ã€ã‚³ãƒŸãƒƒãƒˆã—ã¦ã„ãªã„å…¨ã¦ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚\n" +"\n" +"マージをä¸æ–ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ" -#: lib/blame.tcl:1090 -msgid "Unable to display parent" -msgstr "親を表示ã§ãã¾ã›ã‚“" +#: lib/merge.tcl:230 +msgid "" +"Reset changes?\n" +"\n" +"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"\n" +"Continue with resetting the current changes?" +msgstr "" +"変更点をリセットã—ã¾ã™ã‹ï¼Ÿ\n" +"\n" +"変更点をリセットã™ã‚‹ã¨ã€ã‚³ãƒŸãƒƒãƒˆã—ã¦ã„ãªã„å…¨ã¦ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚\n" +"\n" +"リセットã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ" -#: lib/blame.tcl:1091 lib/diff.tcl:320 -msgid "Error loading diff:" -msgstr "diff ã‚’èªã‚€éš›ã®ã‚¨ãƒ©ãƒ¼ã§ã™:" +#: lib/merge.tcl:241 +msgid "Aborting" +msgstr "ä¸æ–ã—ã¦ã„ã¾ã™" -#: lib/blame.tcl:1231 -msgid "Originally By:" -msgstr "原作者:" +#: lib/merge.tcl:241 +msgid "files reset" +msgstr "リセットã—ãŸãƒ•ã‚¡ã‚¤ãƒ«" -#: lib/blame.tcl:1237 -msgid "In File:" -msgstr "ファイル:" +#: lib/merge.tcl:269 +msgid "Abort failed." +msgstr "ä¸æ–ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" -#: lib/blame.tcl:1242 -msgid "Copied Or Moved Here By:" -msgstr "複写・移動者:" +#: lib/merge.tcl:271 +msgid "Abort completed. Ready." +msgstr "ä¸æ–完了。" -#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 -msgid "Checkout Branch" -msgstr "ブランãƒã‚’ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" +#: lib/error.tcl:20 lib/error.tcl:116 +msgid "error" +msgstr "エラー" -#: lib/branch_checkout.tcl:23 -msgid "Checkout" -msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" +#: lib/error.tcl:36 +msgid "warning" +msgstr "è¦å‘Š" -#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 -#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282 -#: lib/checkout_op.tcl:579 lib/choose_font.tcl:43 lib/merge.tcl:172 -#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 -#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 -#: lib/transport.tcl:108 -msgid "Cancel" -msgstr "ä¸æ¢" +#: lib/error.tcl:96 +msgid "You must correct the above errors before committing." +msgstr "コミットã™ã‚‹å‰ã«ã€ä»¥ä¸Šã®ã‚¨ãƒ©ãƒ¼ã‚’ä¿®æ£ã—ã¦ä¸‹ã•ã„" -#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328 -msgid "Revision" -msgstr "リビジョン" +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Git ã‹ã‚‰å‡ºãŸç„¡åŠ¹ãªæ—¥ä»˜: %s" -#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280 -msgid "Options" -msgstr "オプション" +#: lib/encoding.tcl:443 +msgid "Default" +msgstr "デフォールト" -#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 -msgid "Fetch Tracking Branch" -msgstr "トラッã‚ング・ブランãƒã‚’フェッãƒ" +#: lib/encoding.tcl:448 +#, tcl-format +msgid "System (%s)" +msgstr "システム(%s)" -#: lib/branch_checkout.tcl:44 -msgid "Detach From Local Branch" -msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒã‹ã‚‰å‰Šé™¤" +#: lib/encoding.tcl:459 lib/encoding.tcl:465 +msgid "Other" +msgstr "ãã®ä»–" -#: lib/branch_create.tcl:22 -msgid "Create Branch" -msgstr "ブランãƒã‚’作æˆ" +#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 +msgid "Delete Branch Remotely" +msgstr "リモートブランãƒå‰Šé™¤" -#: lib/branch_create.tcl:27 -msgid "Create New Branch" -msgstr "ブランãƒã‚’æ–°è¦ä½œæˆ" +#: lib/remote_branch_delete.tcl:48 +msgid "From Repository" +msgstr "å…ƒã®ãƒªãƒã‚¸ãƒˆãƒª" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 -msgid "Create" -msgstr "作æˆ" +#: lib/remote_branch_delete.tcl:51 lib/transport.tcl:165 +msgid "Remote:" +msgstr "リモート:" -#: lib/branch_create.tcl:40 -msgid "Branch Name" -msgstr "ブランãƒå" +#: lib/remote_branch_delete.tcl:72 lib/transport.tcl:187 +msgid "Arbitrary Location:" +msgstr "ä»»æ„ã®ä½ç½®:" -#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50 -msgid "Name:" -msgstr "åå‰:" +#: lib/remote_branch_delete.tcl:88 +msgid "Branches" +msgstr "ブランãƒ" -#: lib/branch_create.tcl:58 -msgid "Match Tracking Branch Name" -msgstr "トラッã‚ング・ブランãƒåã‚’åˆã‚ã›ã‚‹" +#: lib/remote_branch_delete.tcl:110 +msgid "Delete Only If" +msgstr "æ¡ä»¶ä»˜ã§å‰Šé™¤" -#: lib/branch_create.tcl:66 -msgid "Starting Revision" -msgstr "åˆæœŸãƒªãƒ“ジョン" +#: lib/remote_branch_delete.tcl:112 +msgid "Merged Into:" +msgstr "マージ先:" -#: lib/branch_create.tcl:72 -msgid "Update Existing Branch:" -msgstr "æ—¢å˜ã®ãƒ–ランãƒã‚’æ›´æ–°:" +#: lib/remote_branch_delete.tcl:120 lib/branch_delete.tcl:53 +msgid "Always (Do not perform merge checks)" +msgstr "ç„¡æ¡ä»¶ï¼ˆãƒžãƒ¼ã‚¸æ¤œæŸ»ã‚’ã—ãªã„)" -#: lib/branch_create.tcl:75 -msgid "No" -msgstr "ã„ã„ãˆ" +#: lib/remote_branch_delete.tcl:153 +msgid "A branch is required for 'Merged Into'." +msgstr "'マージ先' ã«ã¯ãƒ–ランãƒãŒå¿…è¦ã§ã™ã€‚" -#: lib/branch_create.tcl:80 -msgid "Fast Forward Only" -msgstr "æ—©é€ã‚Šã®ã¿" +#: lib/remote_branch_delete.tcl:185 +#, tcl-format +msgid "" +"The following branches are not completely merged into %s:\n" +"\n" +" - %s" +msgstr "" +"以下ã®ãƒ–ランãƒã¯ %s ã«å®Œå…¨ã«ãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã„ã¾ã›ã‚“:\n" +"\n" +" - %s" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 -msgid "Reset" -msgstr "リセット" +#: lib/remote_branch_delete.tcl:190 +#, tcl-format +msgid "" +"One or more of the merge tests failed because you have not fetched the " +"necessary commits. Try fetching from %s first." +msgstr "" +"å¿…è¦ãªã‚³ãƒŸãƒƒãƒˆãŒä¸è¶³ã—ã¦ã„ã‚‹ãŸã‚ã«ã€ãƒžãƒ¼ã‚¸æ¤œæŸ»ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚ã¾ãš %s ã‹ã‚‰" +"フェッãƒã—ã¦ä¸‹ã•ã„。" -#: lib/branch_create.tcl:97 -msgid "Checkout After Creation" -msgstr "作æˆã—ã¦ã™ããƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" +#: lib/remote_branch_delete.tcl:208 +msgid "Please select one or more branches to delete." +msgstr "削除ã™ã‚‹ãƒ–ランãƒã‚’é¸æŠžã—ã¦ä¸‹ã•ã„。" -#: lib/branch_create.tcl:131 -msgid "Please select a tracking branch." -msgstr "トラッã‚ング・ブランãƒã‚’é¸æŠžã—ã¦ä¸‹ã•ã„。" +#: lib/remote_branch_delete.tcl:218 lib/branch_delete.tcl:115 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"削除ã—ãŸãƒ–ランãƒã‚’回復ã™ã‚‹ã®ã¯å›°é›£ã§ã™ã€‚\n" +"\n" +"é¸æŠžã—ãŸãƒ–ランãƒã‚’削除ã—ã¦è‰¯ã„ã§ã™ã‹ï¼Ÿ" -#: lib/branch_create.tcl:140 +#: lib/remote_branch_delete.tcl:227 #, tcl-format -msgid "Tracking branch %s is not a branch in the remote repository." -msgstr "トラッã‚ング・ブランム%s ã¯é 隔リãƒã‚¸ãƒˆãƒªã®ãƒ–ランãƒã§ã¯ã‚ã‚Šã¾ã›ã‚“。" +msgid "Deleting branches from %s" +msgstr "%s ã‹ã‚‰ãƒ–ランãƒã‚’削除ã—ã¦ã„ã¾ã™ã€‚" -#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 -msgid "Please supply a branch name." -msgstr "ブランãƒåを指定ã—ã¦ä¸‹ã•ã„。" +#: lib/remote_branch_delete.tcl:300 +msgid "No repository selected." +msgstr "リãƒã‚¸ãƒˆãƒªãŒé¸æŠžã•ã‚Œã¦ã„ã¾ã›ã‚“。" -#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 +#: lib/remote_branch_delete.tcl:305 #, tcl-format -msgid "'%s' is not an acceptable branch name." -msgstr "'%s' ã¯ãƒ–ランãƒåã«ä½¿ãˆã¾ã›ã‚“。" +msgid "Scanning %s..." +msgstr "%s をスã‚ャンã—ã¦ã„ã¾ã™â€¦" -#: lib/branch_delete.tcl:15 +#: lib/branch_delete.tcl:16 msgid "Delete Branch" msgstr "ブランãƒå‰Šé™¤" -#: lib/branch_delete.tcl:20 +#: lib/branch_delete.tcl:21 msgid "Delete Local Branch" msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒã‚’削除" -#: lib/branch_delete.tcl:37 +#: lib/branch_delete.tcl:39 msgid "Local Branches" msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒ" -#: lib/branch_delete.tcl:52 +#: lib/branch_delete.tcl:51 msgid "Delete Only If Merged Into" msgstr "マージ済ã¿ã®æ™‚ã®ã¿å‰Šé™¤" -#: lib/branch_delete.tcl:54 lib/remote_branch_delete.tcl:119 -msgid "Always (Do not perform merge checks)" -msgstr "ç„¡æ¡ä»¶ï¼ˆãƒžãƒ¼ã‚¸æ¤œæŸ»ã‚’ã—ãªã„)" - #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" msgstr "以下ã®ãƒ–ランãƒã¯ %s ã«å®Œå…¨ã«ãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã„ã¾ã›ã‚“:" -#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:217 -msgid "" -"Recovering deleted branches is difficult.\n" -"\n" -"Delete the selected branches?" -msgstr "" -"削除ã—ãŸãƒ–ランãƒã‚’回復ã™ã‚‹ã®ã¯å›°é›£ã§ã™ã€‚\n" -"\n" -"é¸æŠžã—ãŸãƒ–ランãƒã‚’削除ã—ã¦è‰¯ã„ã§ã™ã‹ï¼Ÿ" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -820,62 +895,63 @@ msgstr "" "以下ã®ãƒ–ランãƒã‚’削除ã§ãã¾ã›ã‚“:\n" "%s" -#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 -msgid "Rename Branch" -msgstr "ブランãƒã®åå‰å¤‰æ›´" +#: lib/choose_rev.tcl:52 +msgid "This Detached Checkout" +msgstr "分離ã•ã‚ŒãŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" -#: lib/branch_rename.tcl:26 -msgid "Rename" -msgstr "åå‰å¤‰æ›´" +#: lib/choose_rev.tcl:60 +msgid "Revision Expression:" +msgstr "リビジョンå¼:" -#: lib/branch_rename.tcl:36 -msgid "Branch:" -msgstr "ブランãƒ:" +#: lib/choose_rev.tcl:72 +msgid "Local Branch" +msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒ" -#: lib/branch_rename.tcl:39 -msgid "New Name:" -msgstr "æ–°ã—ã„åå‰:" +#: lib/choose_rev.tcl:77 +msgid "Tracking Branch" +msgstr "トラッã‚ング・ブランãƒ" -#: lib/branch_rename.tcl:75 -msgid "Please select a branch to rename." -msgstr "åå‰ã‚’変更ã™ã‚‹ãƒ–ランãƒã‚’é¸ã‚“ã§ä¸‹ã•ã„。" +#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544 +msgid "Tag" +msgstr "ã‚¿ã‚°" -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 +#: lib/choose_rev.tcl:321 #, tcl-format -msgid "Branch '%s' already exists." -msgstr "'%s'ã¨ã„ã†ãƒ–ランãƒã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" +msgid "Invalid revision: %s" +msgstr "無効ãªãƒªãƒ“ジョン: %s" -#: lib/branch_rename.tcl:117 -#, tcl-format -msgid "Failed to rename '%s'." -msgstr "'%s'ã®åå‰å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" +#: lib/choose_rev.tcl:342 +msgid "No revision selected." +msgstr "リビジョンãŒæœªé¸æŠžã§ã™ã€‚" -#: lib/browser.tcl:17 -msgid "Starting..." -msgstr "èµ·å‹•ä¸â€¦" +#: lib/choose_rev.tcl:350 +msgid "Revision expression is empty." +msgstr "リビジョンå¼ãŒç©ºã§ã™ã€‚" -#: lib/browser.tcl:26 -msgid "File Browser" -msgstr "ファイル・ブラウザ" +#: lib/choose_rev.tcl:537 +msgid "Updated" +msgstr "æ›´æ–°ã—ã¾ã—ãŸ" -#: lib/browser.tcl:126 lib/browser.tcl:143 -#, tcl-format -msgid "Loading %s..." -msgstr "%s ã‚’ãƒãƒ¼ãƒ‰ä¸â€¦" +#: lib/choose_rev.tcl:565 +msgid "URL" +msgstr "URL" -#: lib/browser.tcl:187 -msgid "[Up To Parent]" -msgstr "[上ä½ãƒ•ã‚©ãƒ«ãƒ€ã¸]" +#: lib/console.tcl:59 +msgid "Working... please wait..." +msgstr "実行ä¸â€¦ãŠå¾…ã¡ä¸‹ã•ã„…" -#: lib/browser.tcl:267 lib/browser.tcl:273 -msgid "Browse Branch Files" -msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る" +#: lib/console.tcl:81 lib/checkout_op.tcl:146 lib/database.tcl:30 +#: lib/sshkey.tcl:55 +msgid "Close" +msgstr "é–‰ã˜ã‚‹" -#: lib/browser.tcl:278 lib/choose_repository.tcl:398 -#: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 -#: lib/choose_repository.tcl:1028 -msgid "Browse" -msgstr "ブラウズ" +#: lib/console.tcl:186 +msgid "Success" +msgstr "æˆåŠŸ" + +#: lib/console.tcl:200 +msgid "Error: Command Failed" +msgstr "エラー: コマンドãŒå¤±æ•—ã—ã¾ã—ãŸ" #: lib/checkout_op.tcl:85 #, tcl-format @@ -887,11 +963,6 @@ msgstr "%s ã‹ã‚‰ %s をフェッãƒã—ã¦ã„ã¾ã™" msgid "fatal: Cannot resolve %s" msgstr "致命的エラー: %s を解決ã§ãã¾ã›ã‚“" -#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:31 -#: lib/sshkey.tcl:53 -msgid "Close" -msgstr "é–‰ã˜ã‚‹" - #: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." @@ -902,6 +973,11 @@ msgstr "ブランãƒ'%s'ã¯å˜åœ¨ã—ã¾ã›ã‚“。" msgid "Failed to configure simplified git-pull for '%s'." msgstr "'%s' ã«ç°¡æ˜“ git-pull ã‚’è¨å®šã§ãã¾ã›ã‚“ã§ã—ãŸ" +#: lib/checkout_op.tcl:202 lib/branch_rename.tcl:102 +#, tcl-format +msgid "Branch '%s' already exists." +msgstr "'%s'ã¨ã„ã†ãƒ–ランãƒã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" + #: lib/checkout_op.tcl:229 #, tcl-format msgid "" @@ -999,9 +1075,9 @@ msgstr "失ãªã‚ã‚ŒãŸã‚³ãƒŸãƒƒãƒˆã‚’回復ã™ã‚‹ã®ã¯ç°¡å˜ã§ã¯ã‚ã‚Šã¾ã› msgid "Reset '%s'?" msgstr "'%s' をリセットã—ã¾ã™ã‹ï¼Ÿ" -#: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 -msgid "Visualize" -msgstr "å¯è¦–化" +#: lib/checkout_op.tcl:571 lib/branch_create.tcl:85 +msgid "Reset" +msgstr "リセット" #: lib/checkout_op.tcl:635 #, tcl-format @@ -1019,509 +1095,838 @@ msgstr "" "ã¾ã—ãŸãŒã€ Git ã®å†…部データを更新ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚\n" "èµ·ã“ã‚‹ã¯ãšã®ãªã„エラーã§ã™ã€‚ã‚ãらã‚㦠%s を終了ã—ã¾ã™ã€‚" -#: lib/choose_font.tcl:39 -msgid "Select" -msgstr "é¸æŠž" +#: lib/blame.tcl:73 +msgid "File Viewer" +msgstr "ファイルピューワ" -#: lib/choose_font.tcl:53 -msgid "Font Family" -msgstr "フォント・ファミリー" +#: lib/blame.tcl:79 +msgid "Commit:" +msgstr "コミット:" -#: lib/choose_font.tcl:74 -msgid "Font Size" -msgstr "フォントã®å¤§ãã•" +#: lib/blame.tcl:280 +msgid "Copy Commit" +msgstr "コミットをコピー" -#: lib/choose_font.tcl:91 -msgid "Font Example" -msgstr "フォント・サンプル" +#: lib/blame.tcl:284 +msgid "Find Text..." +msgstr "テã‚ストを検索" -#: lib/choose_font.tcl:103 -msgid "" -"This is example text.\n" -"If you like this text, it can be your font." -msgstr "" -"ã“ã‚Œã¯ã‚µãƒ³ãƒ—ル文ã§ã™ã€‚\n" -"ã“ã®ãƒ•ã‚©ãƒ³ãƒˆãŒæ°—ã«å…¥ã‚Œã°ãŠä½¿ã„ã«ãªã‚Œã¾ã™ã€‚" +#: lib/blame.tcl:288 +msgid "Goto Line..." +msgstr "指定行ã«ç§»å‹•â€¦" + +#: lib/blame.tcl:297 +msgid "Do Full Copy Detection" +msgstr "コピー検知" + +#: lib/blame.tcl:301 +msgid "Show History Context" +msgstr "文脈を見ã›ã‚‹" + +#: lib/blame.tcl:304 +msgid "Blame Parent Commit" +msgstr "親コミットを注釈" + +#: lib/blame.tcl:466 +#, tcl-format +msgid "Reading %s..." +msgstr "%s ã‚’èªã‚“ã§ã„ã¾ã™â€¦" + +#: lib/blame.tcl:594 +msgid "Loading copy/move tracking annotations..." +msgstr "コピー・移動追跡データをèªã‚“ã§ã„ã¾ã™â€¦" + +#: lib/blame.tcl:614 +msgid "lines annotated" +msgstr "行を注釈ã—ã¾ã—ãŸ" + +#: lib/blame.tcl:806 +msgid "Loading original location annotations..." +msgstr "å…ƒä½ç½®è¡Œã®æ³¨é‡ˆãƒ‡ãƒ¼ã‚¿ã‚’èªã‚“ã§ã„ã¾ã™â€¦" + +#: lib/blame.tcl:809 +msgid "Annotation complete." +msgstr "注釈完了ã—ã¾ã—ãŸ" + +#: lib/blame.tcl:839 +msgid "Busy" +msgstr "実行ä¸" + +#: lib/blame.tcl:840 +msgid "Annotation process is already running." +msgstr "ã™ã§ã« blame プãƒã‚»ã‚¹ã‚’実行ä¸ã§ã™ã€‚" + +#: lib/blame.tcl:879 +msgid "Running thorough copy detection..." +msgstr "コピー検知を実行ä¸â€¦" + +#: lib/blame.tcl:947 +msgid "Loading annotation..." +msgstr "注釈をèªã¿è¾¼ã‚“ã§ã„ã¾ã™â€¦" -#: lib/choose_repository.tcl:28 +#: lib/blame.tcl:1000 +msgid "Author:" +msgstr "作者:" + +#: lib/blame.tcl:1004 +msgid "Committer:" +msgstr "コミット者:" + +#: lib/blame.tcl:1009 +msgid "Original File:" +msgstr "元ファイル" + +#: lib/blame.tcl:1057 +msgid "Cannot find HEAD commit:" +msgstr "HEAD コミットãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“" + +#: lib/blame.tcl:1112 +msgid "Cannot find parent commit:" +msgstr "親コミットãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“:" + +#: lib/blame.tcl:1127 +msgid "Unable to display parent" +msgstr "親を表示ã§ãã¾ã›ã‚“" + +#: lib/blame.tcl:1128 lib/diff.tcl:356 +msgid "Error loading diff:" +msgstr "diff ã‚’èªã‚€éš›ã®ã‚¨ãƒ©ãƒ¼ã§ã™:" + +#: lib/blame.tcl:1269 +msgid "Originally By:" +msgstr "原作者:" + +#: lib/blame.tcl:1275 +msgid "In File:" +msgstr "ファイル:" + +#: lib/blame.tcl:1280 +msgid "Copied Or Moved Here By:" +msgstr "複写・移動者:" + +#: lib/about.tcl:26 +msgid "git-gui - a graphical user interface for Git." +msgstr "Git ã®ã‚°ãƒ©ãƒ•ã‚£ã‚«ãƒ«UI git-gui" + +#: lib/choose_repository.tcl:33 msgid "Git Gui" msgstr "Git GUI" -#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:386 +#: lib/choose_repository.tcl:92 lib/choose_repository.tcl:412 msgid "Create New Repository" msgstr "æ–°ã—ã„リãƒã‚¸ãƒˆãƒªã‚’作る" -#: lib/choose_repository.tcl:93 +#: lib/choose_repository.tcl:98 msgid "New..." msgstr "æ–°è¦â€¦" -#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 +#: lib/choose_repository.tcl:105 lib/choose_repository.tcl:496 msgid "Clone Existing Repository" msgstr "æ—¢å˜ãƒªãƒã‚¸ãƒˆãƒªã‚’複製ã™ã‚‹" -#: lib/choose_repository.tcl:106 +#: lib/choose_repository.tcl:116 msgid "Clone..." msgstr "複製…" -#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 +#: lib/choose_repository.tcl:123 lib/choose_repository.tcl:1064 msgid "Open Existing Repository" msgstr "æ—¢å˜ãƒªãƒã‚¸ãƒˆãƒªã‚’é–‹ã" -#: lib/choose_repository.tcl:119 +#: lib/choose_repository.tcl:129 msgid "Open..." msgstr "é–‹ã…" -#: lib/choose_repository.tcl:132 +#: lib/choose_repository.tcl:142 msgid "Recent Repositories" msgstr "最近使ã£ãŸãƒªãƒã‚¸ãƒˆãƒª" -#: lib/choose_repository.tcl:138 +#: lib/choose_repository.tcl:148 msgid "Open Recent Repository:" msgstr "最近使ã£ãŸãƒªãƒã‚¸ãƒˆãƒªã‚’é–‹ã" -#: lib/choose_repository.tcl:306 lib/choose_repository.tcl:313 -#: lib/choose_repository.tcl:320 +#: lib/choose_repository.tcl:316 lib/choose_repository.tcl:323 +#: lib/choose_repository.tcl:330 #, tcl-format msgid "Failed to create repository %s:" msgstr "リãƒã‚¸ãƒˆãƒª %s を作製ã§ãã¾ã›ã‚“:" -#: lib/choose_repository.tcl:391 +#: lib/choose_repository.tcl:407 lib/branch_create.tcl:33 +msgid "Create" +msgstr "作æˆ" + +#: lib/choose_repository.tcl:417 msgid "Directory:" msgstr "ディレクトリ:" -#: lib/choose_repository.tcl:423 lib/choose_repository.tcl:550 -#: lib/choose_repository.tcl:1052 +#: lib/choose_repository.tcl:422 lib/choose_repository.tcl:509 +#: lib/choose_repository.tcl:518 lib/choose_repository.tcl:1074 +#: lib/browser.tcl:288 +msgid "Browse" +msgstr "ブラウズ" + +#: lib/choose_repository.tcl:447 lib/choose_repository.tcl:573 +#: lib/choose_repository.tcl:1098 msgid "Git Repository" msgstr "GIT リãƒã‚¸ãƒˆãƒª" -#: lib/choose_repository.tcl:448 +#: lib/choose_repository.tcl:472 #, tcl-format msgid "Directory %s already exists." msgstr "ディレクトリ '%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" -#: lib/choose_repository.tcl:452 +#: lib/choose_repository.tcl:476 #, tcl-format msgid "File %s already exists." msgstr "ファイル '%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" -#: lib/choose_repository.tcl:466 +#: lib/choose_repository.tcl:491 msgid "Clone" msgstr "複製" -#: lib/choose_repository.tcl:479 +#: lib/choose_repository.tcl:504 msgid "Source Location:" msgstr "ソースã®ä½ç½®" -#: lib/choose_repository.tcl:490 +#: lib/choose_repository.tcl:513 msgid "Target Directory:" msgstr "先ディレクトリ:" -#: lib/choose_repository.tcl:502 +#: lib/choose_repository.tcl:523 msgid "Clone Type:" msgstr "複製方å¼:" -#: lib/choose_repository.tcl:508 +#: lib/choose_repository.tcl:528 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "標準(高速・ä¸å†—長度・ãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯)" -#: lib/choose_repository.tcl:514 +#: lib/choose_repository.tcl:533 msgid "Full Copy (Slower, Redundant Backup)" msgstr "全複写(低速・冗長ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—)" -#: lib/choose_repository.tcl:520 +#: lib/choose_repository.tcl:538 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "共有(最高速・éžæŽ¨å¥¨ãƒ»ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ç„¡ã—)" -#: lib/choose_repository.tcl:556 lib/choose_repository.tcl:603 -#: lib/choose_repository.tcl:749 lib/choose_repository.tcl:819 -#: lib/choose_repository.tcl:1058 lib/choose_repository.tcl:1066 +#: lib/choose_repository.tcl:545 +msgid "Recursively clone submodules too" +msgstr "サブモジュールもå†å¸°çš„ã«è¤‡è£½ã™ã‚‹" + +#: lib/choose_repository.tcl:579 lib/choose_repository.tcl:626 +#: lib/choose_repository.tcl:772 lib/choose_repository.tcl:842 +#: lib/choose_repository.tcl:1104 lib/choose_repository.tcl:1112 #, tcl-format msgid "Not a Git repository: %s" msgstr "Git リãƒã‚¸ãƒˆãƒªã§ã¯ã‚ã‚Šã¾ã›ã‚“: %s" -#: lib/choose_repository.tcl:592 +#: lib/choose_repository.tcl:615 msgid "Standard only available for local repository." msgstr "標準方å¼ã¯åŒä¸€è¨ˆç®—機上ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã®ã¿ä½¿ãˆã¾ã™ã€‚" -#: lib/choose_repository.tcl:596 +#: lib/choose_repository.tcl:619 msgid "Shared only available for local repository." msgstr "共有方å¼ã¯åŒä¸€è¨ˆç®—機上ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã®ã¿ä½¿ãˆã¾ã™ã€‚" -#: lib/choose_repository.tcl:617 +#: lib/choose_repository.tcl:640 #, tcl-format msgid "Location %s already exists." msgstr "'%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" -#: lib/choose_repository.tcl:628 +#: lib/choose_repository.tcl:651 msgid "Failed to configure origin" msgstr "origin ã‚’è¨å®šã§ãã¾ã›ã‚“ã§ã—ãŸ" -#: lib/choose_repository.tcl:640 +#: lib/choose_repository.tcl:663 msgid "Counting objects" msgstr "オブジェクトを数ãˆã¦ã„ã¾ã™" -#: lib/choose_repository.tcl:641 +#: lib/choose_repository.tcl:664 msgid "buckets" msgstr "ãƒã‚±ãƒ„" -#: lib/choose_repository.tcl:665 +#: lib/choose_repository.tcl:688 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "objects/info/alternates を複写ã§ãã¾ã›ã‚“: %s" -#: lib/choose_repository.tcl:701 +#: lib/choose_repository.tcl:724 #, tcl-format msgid "Nothing to clone from %s." msgstr "%s ã‹ã‚‰è¤‡è£½ã™ã‚‹å†…容ã¯ã‚ã‚Šã¾ã›ã‚“" -#: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 -#: lib/choose_repository.tcl:929 +#: lib/choose_repository.tcl:726 lib/choose_repository.tcl:940 +#: lib/choose_repository.tcl:952 msgid "The 'master' branch has not been initialized." msgstr "'master' ブランãƒãŒåˆæœŸåŒ–ã•ã‚Œã¦ã„ã¾ã›ã‚“" -#: lib/choose_repository.tcl:716 +#: lib/choose_repository.tcl:739 msgid "Hardlinks are unavailable. Falling back to copying." msgstr "ãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯ãŒä½œã‚Œãªã„ã®ã§ã€ã‚³ãƒ”ーã—ã¾ã™" -#: lib/choose_repository.tcl:728 +#: lib/choose_repository.tcl:751 #, tcl-format msgid "Cloning from %s" msgstr "%s ã‹ã‚‰è¤‡è£½ã—ã¦ã„ã¾ã™" -#: lib/choose_repository.tcl:759 +#: lib/choose_repository.tcl:782 msgid "Copying objects" msgstr "オブジェクトを複写ã—ã¦ã„ã¾ã™" -#: lib/choose_repository.tcl:760 +#: lib/choose_repository.tcl:783 msgid "KiB" msgstr "KiB" -#: lib/choose_repository.tcl:784 +#: lib/choose_repository.tcl:807 #, tcl-format msgid "Unable to copy object: %s" msgstr "オブジェクトを複写ã§ãã¾ã›ã‚“: %s" -#: lib/choose_repository.tcl:794 +#: lib/choose_repository.tcl:817 msgid "Linking objects" msgstr "オブジェクトを連çµã—ã¦ã„ã¾ã™" -#: lib/choose_repository.tcl:795 +#: lib/choose_repository.tcl:818 msgid "objects" msgstr "オブジェクト" -#: lib/choose_repository.tcl:803 +#: lib/choose_repository.tcl:826 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "オブジェクトをãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯ã§ãã¾ã›ã‚“: %s" -#: lib/choose_repository.tcl:858 +#: lib/choose_repository.tcl:881 msgid "Cannot fetch branches and objects. See console output for details." msgstr "ブランãƒã‚„オブジェクトをå–å¾—ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„" -#: lib/choose_repository.tcl:869 +#: lib/choose_repository.tcl:892 msgid "Cannot fetch tags. See console output for details." msgstr "ã‚¿ã‚°ã‚’å–å¾—ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„" -#: lib/choose_repository.tcl:893 +#: lib/choose_repository.tcl:916 msgid "Cannot determine HEAD. See console output for details." msgstr "HEAD を確定ã§ãã¾ã›ã‚“。コンソール出力を見ã¦ä¸‹ã•ã„" -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:925 #, tcl-format msgid "Unable to cleanup %s" msgstr "%s を掃除ã§ãã¾ã›ã‚“" -#: lib/choose_repository.tcl:908 +#: lib/choose_repository.tcl:931 msgid "Clone failed." msgstr "複写ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" -#: lib/choose_repository.tcl:915 +#: lib/choose_repository.tcl:938 msgid "No default branch obtained." msgstr "デフォールト・ブランãƒãŒå–å¾—ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ" -#: lib/choose_repository.tcl:926 +#: lib/choose_repository.tcl:949 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "%s をコミットã¨ã—ã¦è§£é‡ˆã§ãã¾ã›ã‚“" -#: lib/choose_repository.tcl:938 +#: lib/choose_repository.tcl:961 msgid "Creating working directory" msgstr "作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’作æˆã—ã¦ã„ã¾ã™" -#: lib/choose_repository.tcl:939 lib/index.tcl:67 lib/index.tcl:130 -#: lib/index.tcl:198 +#: lib/choose_repository.tcl:962 lib/index.tcl:70 lib/index.tcl:136 +#: lib/index.tcl:207 msgid "files" msgstr "ファイル" -#: lib/choose_repository.tcl:968 +#: lib/choose_repository.tcl:981 +msgid "Cannot clone submodules." +msgstr "サブモジュールãŒè¤‡è£½ã§ãã¾ã›ã‚“。" + +#: lib/choose_repository.tcl:990 +msgid "Cloning submodules" +msgstr "サブモジュールを複製ã—ã¦ã„ã¾ã™" + +#: lib/choose_repository.tcl:1015 msgid "Initial file checkout failed." msgstr "åˆæœŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã«å¤±æ•—ã—ã¾ã—ãŸ" -#: lib/choose_repository.tcl:1011 +#: lib/choose_repository.tcl:1059 msgid "Open" msgstr "é–‹ã" -#: lib/choose_repository.tcl:1021 +#: lib/choose_repository.tcl:1069 msgid "Repository:" msgstr "リãƒã‚¸ãƒˆãƒª:" -#: lib/choose_repository.tcl:1072 +#: lib/choose_repository.tcl:1118 #, tcl-format msgid "Failed to open repository %s:" msgstr "リãƒã‚¸ãƒˆãƒª %s ã‚’é–‹ã‘ã¾ã›ã‚“:" -#: lib/choose_rev.tcl:53 -msgid "This Detached Checkout" -msgstr "分離ã•ã‚ŒãŸãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" +#: lib/branch_rename.tcl:15 lib/branch_rename.tcl:23 +msgid "Rename Branch" +msgstr "ブランãƒã®åå‰å¤‰æ›´" -#: lib/choose_rev.tcl:60 -msgid "Revision Expression:" -msgstr "リビジョンå¼:" +#: lib/branch_rename.tcl:28 +msgid "Rename" +msgstr "åå‰å¤‰æ›´" -#: lib/choose_rev.tcl:74 -msgid "Local Branch" -msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒ" +#: lib/branch_rename.tcl:38 +msgid "Branch:" +msgstr "ブランãƒ:" -#: lib/choose_rev.tcl:79 -msgid "Tracking Branch" -msgstr "トラッã‚ング・ブランãƒ" +#: lib/branch_rename.tcl:46 +msgid "New Name:" +msgstr "æ–°ã—ã„åå‰:" -#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538 -msgid "Tag" -msgstr "ã‚¿ã‚°" +#: lib/branch_rename.tcl:81 +msgid "Please select a branch to rename." +msgstr "åå‰ã‚’変更ã™ã‚‹ãƒ–ランãƒã‚’é¸ã‚“ã§ä¸‹ã•ã„。" + +#: lib/branch_rename.tcl:92 lib/branch_create.tcl:154 +msgid "Please supply a branch name." +msgstr "ブランãƒåを指定ã—ã¦ä¸‹ã•ã„。" -#: lib/choose_rev.tcl:317 +#: lib/branch_rename.tcl:112 lib/branch_create.tcl:165 #, tcl-format -msgid "Invalid revision: %s" -msgstr "無効ãªãƒªãƒ“ジョン: %s" +msgid "'%s' is not an acceptable branch name." +msgstr "'%s' ã¯ãƒ–ランãƒåã«ä½¿ãˆã¾ã›ã‚“。" -#: lib/choose_rev.tcl:338 -msgid "No revision selected." -msgstr "リビジョンãŒæœªé¸æŠžã§ã™ã€‚" +#: lib/branch_rename.tcl:123 +#, tcl-format +msgid "Failed to rename '%s'." +msgstr "'%s'ã®åå‰å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" -#: lib/choose_rev.tcl:346 -msgid "Revision expression is empty." -msgstr "リビジョンå¼ãŒç©ºã§ã™ã€‚" +#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 +msgid "Cannot write shortcut:" +msgstr "ショートカットãŒæ›¸ã‘ã¾ã›ã‚“:" -#: lib/choose_rev.tcl:531 -msgid "Updated" -msgstr "æ›´æ–°ã—ã¾ã—ãŸ" +#: lib/shortcut.tcl:137 +msgid "Cannot write icon:" +msgstr "アイコンãŒæ›¸ã‘ã¾ã›ã‚“:" -#: lib/choose_rev.tcl:559 -msgid "URL" -msgstr "URL" +#: lib/search.tcl:48 +msgid "Find:" +msgstr "検索:" -#: lib/commit.tcl:9 -msgid "" -"There is nothing to amend.\n" -"\n" -"You are about to create the initial commit. There is no commit before this " -"to amend.\n" -msgstr "" -"訂æ£ã™ã‚‹ã‚³ãƒŸãƒƒãƒˆãŒãã‚‚ãã‚‚ã‚ã‚Šã¾ã›ã‚“。\n" -"\n" -"ã“ã‚Œã‹ã‚‰ä½œã‚‹ã®ã¯æœ€åˆã®ã‚³ãƒŸãƒƒãƒˆã§ã™ã€‚ãã®å‰ã«ã¯ã¾ã 訂æ£ã™ã‚‹ã‚ˆã†ãªã‚³ãƒŸãƒƒãƒˆã¯ã‚" -"ã‚Šã¾ã›ã‚“。\n" +#: lib/search.tcl:50 +msgid "Next" +msgstr "次" -#: lib/commit.tcl:18 +#: lib/search.tcl:51 +msgid "Prev" +msgstr "å‰" + +#: lib/search.tcl:52 +msgid "RegExp" +msgstr "æ£è¦è¡¨ç¾" + +#: lib/search.tcl:54 +msgid "Case" +msgstr "大文å—å°æ–‡å—を区別" + +#: lib/status_bar.tcl:87 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%1$s ... %4$*i %6$s ä¸ã® %2$*i (%7$3i%%)" + +#: lib/tools_dlg.tcl:22 +msgid "Add Tool" +msgstr "ツールã®è¿½åŠ " + +#: lib/tools_dlg.tcl:28 +msgid "Add New Tool Command" +msgstr "æ–°è¦ãƒ„ールコマンドã®è¿½åŠ " + +#: lib/tools_dlg.tcl:34 +msgid "Add globally" +msgstr "全体ã«è¿½åŠ " + +#: lib/tools_dlg.tcl:37 lib/remote_add.tcl:30 +msgid "Add" +msgstr "è¿½åŠ " + +#: lib/tools_dlg.tcl:46 +msgid "Tool Details" +msgstr "ツールã®è©³ç´°" + +#: lib/tools_dlg.tcl:49 +msgid "Use '/' separators to create a submenu tree:" +msgstr "'/' ã§ã‚µãƒ–メニューを区切りã¾ã™:" + +#: lib/tools_dlg.tcl:51 lib/remote_add.tcl:41 lib/branch_create.tcl:44 +msgid "Name:" +msgstr "åå‰:" + +#: lib/tools_dlg.tcl:60 +msgid "Command:" +msgstr "コマンド:" + +#: lib/tools_dlg.tcl:71 +msgid "Show a dialog before running" +msgstr "èµ·å‹•ã™ã‚‹å‰ã«ãƒ€ã‚¤ã‚¢ãƒã‚°ã‚’表示" + +#: lib/tools_dlg.tcl:77 +msgid "Ask the user to select a revision (sets $REVISION)" +msgstr "ユーザã«ã‚³ãƒŸãƒƒãƒˆã‚’一ã¤é¸ã°ã›ã‚‹ ($REVISION ã«ã‚»ãƒƒãƒˆã—ã¾ã™)" + +#: lib/tools_dlg.tcl:82 +msgid "Ask the user for additional arguments (sets $ARGS)" +msgstr "ユーザã«ä»–ã®å¼•æ•°ã‚’è¿½åŠ ã•ã›ã‚‹ ($ARGS ã«ã‚»ãƒƒãƒˆã—ã¾ã™)" + +#: lib/tools_dlg.tcl:89 +msgid "Don't show the command output window" +msgstr "コマンドã‹ã‚‰ã®å‡ºåŠ›ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã‚’見ã›ãªã„" + +#: lib/tools_dlg.tcl:94 +msgid "Run only if a diff is selected ($FILENAME not empty)" +msgstr "パッãƒãŒé¸ã°ã‚Œã¦ã„ã‚‹ã¨ãã ã‘å‹•ã‹ã™($FILENAME ãŒç©ºã§ãªã„)" + +#: lib/tools_dlg.tcl:118 +msgid "Please supply a name for the tool." +msgstr "ツールåを指定ã—ã¦ä¸‹ã•ã„。" + +#: lib/tools_dlg.tcl:126 +#, tcl-format +msgid "Tool '%s' already exists." +msgstr "ツール '%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" + +#: lib/tools_dlg.tcl:148 +#, tcl-format msgid "" -"Cannot amend while merging.\n" -"\n" -"You are currently in the middle of a merge that has not been fully " -"completed. You cannot amend the prior commit unless you first abort the " -"current merge activity.\n" +"Could not add tool:\n" +"%s" msgstr "" -"マージä¸ã«ã‚³ãƒŸãƒƒãƒˆã®è¨‚æ£ã¯ã§ãã¾ã›ã‚“。\n" -"\n" -"ç¾åœ¨ã¯ã¾ã マージã®é€”ä¸ã§ã™ã€‚å…ˆã«ã“ã®ãƒžãƒ¼ã‚¸ã‚’ä¸æ¢ã—ãªã„ã¨ã€å‰ã®ã‚³ãƒŸãƒƒãƒˆã®è¨‚æ£" -"ã¯ã§ãã¾ã›ã‚“\n" +"ãƒ„ãƒ¼ãƒ«ã‚’è¿½åŠ ã§ãã¾ã›ã‚“:\n" +"%s" -#: lib/commit.tcl:48 -msgid "Error loading commit data for amend:" -msgstr "訂æ£ã™ã‚‹ã‚³ãƒŸãƒƒãƒˆã®ãƒ‡ãƒ¼ã‚¿ã‚’èªã‚ã¾ã›ã‚“:" +#: lib/tools_dlg.tcl:187 +msgid "Remove Tool" +msgstr "ツールã®å‰Šé™¤" -#: lib/commit.tcl:75 -msgid "Unable to obtain your identity:" -msgstr "ユーザã®æ£ä½“を確èªã§ãã¾ã›ã‚“:" +#: lib/tools_dlg.tcl:193 +msgid "Remove Tool Commands" +msgstr "ツールコマンドã®å‰Šé™¤" -#: lib/commit.tcl:80 -msgid "Invalid GIT_COMMITTER_IDENT:" -msgstr "GIT_COMMITTER_IDENT ãŒç„¡åŠ¹ã§ã™:" +#: lib/tools_dlg.tcl:198 +msgid "Remove" +msgstr "削除" -#: lib/commit.tcl:129 +#: lib/tools_dlg.tcl:231 +msgid "(Blue denotes repository-local tools)" +msgstr "(é’色ã¯ãƒãƒ¼ã‚«ãƒ«ãƒ¬ãƒã‚¸ãƒˆãƒªã®ãƒ„ールã§ã™)" + +#: lib/tools_dlg.tcl:292 #, tcl-format -msgid "warning: Tcl does not support encoding '%s'." -msgstr "è¦å‘Š: Tcl ã¯ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚° '%s' をサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“" +msgid "Run Command: %s" +msgstr "コマンドを起動: %s" -#: lib/commit.tcl:149 +#: lib/tools_dlg.tcl:306 +msgid "Arguments" +msgstr "引数" + +#: lib/tools_dlg.tcl:321 lib/branch_checkout.tcl:35 lib/browser.tcl:297 +msgid "Revision" +msgstr "リビジョン" + +#: lib/tools_dlg.tcl:341 +msgid "OK" +msgstr "OK" + +#: lib/tools.tcl:75 +#, tcl-format +msgid "Running %s requires a selected file." +msgstr "ファイルをé¸æŠžã—ã¦ã‹ã‚‰ %s ã‚’èµ·å‹•ã—ã¦ãã ã•ã„。" + +#: lib/tools.tcl:91 +#, tcl-format +msgid "Are you sure you want to run %1$s on file \"%2$s\"?" +msgstr "本当ã«ãƒ•ã‚¡ã‚¤ãƒ« \"%2$s\"㧠%1$s ã‚’èµ·å‹•ã—ã¾ã™ã‹?" + +#: lib/tools.tcl:95 +#, tcl-format +msgid "Are you sure you want to run %s?" +msgstr "本当㫠%s ã‚’èµ·å‹•ã—ã¾ã™ã‹?" + +#: lib/tools.tcl:116 +#, tcl-format +msgid "Tool: %s" +msgstr "ツール: %s" + +#: lib/tools.tcl:117 +#, tcl-format +msgid "Running: %s" +msgstr "実行ä¸: %s" + +#: lib/tools.tcl:155 +#, tcl-format +msgid "Tool completed successfully: %s" +msgstr "ツールãŒå®Œäº†ã—ã¾ã—ãŸ: %s" + +#: lib/tools.tcl:157 +#, tcl-format +msgid "Tool failed: %s" +msgstr "ツールãŒå¤±æ•—ã—ã¾ã—ãŸ: %s" + +#: lib/mergetool.tcl:8 +msgid "Force resolution to the base version?" +msgstr "共通ã®ç‰ˆã‚’使ã„ã¾ã™ã‹?" + +#: lib/mergetool.tcl:9 +msgid "Force resolution to this branch?" +msgstr "自分ã®å´ã®ç‰ˆã‚’使ã„ã¾ã™ã‹?" + +#: lib/mergetool.tcl:10 +msgid "Force resolution to the other branch?" +msgstr "相手制ã®ç‰ˆã‚’使ã„ã¾ã™ã‹?" + +#: lib/mergetool.tcl:14 +#, tcl-format msgid "" -"Last scanned state does not match repository state.\n" +"Note that the diff shows only conflicting changes.\n" "\n" -"Another Git program has modified this repository since the last scan. A " -"rescan must be performed before another commit can be created.\n" +"%s will be overwritten.\n" "\n" -"The rescan will be automatically started now.\n" +"This operation can be undone only by restarting the merge." msgstr "" -"最後ã«ã‚¹ã‚ャンã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n" +"競åˆã™ã‚‹å¤‰æ›´ç‚¹ã ã‘ãŒè¡¨ç¤ºã•ã‚Œã¦ã„ã‚‹ã“ã¨ã«æ³¨æ„ã—ã¦ãã ã•ã„。\n" "\n" -"最後ã«ã‚¹ã‚ャンã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プãƒã‚°ãƒ©ãƒ ãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚æ–°ã—" -"ãコミットã™ã‚‹å‰ã«ã€å†ã‚¹ã‚ャンãŒå¿…è¦ã§ã™ã€‚\n" +"%s ã¯ä¸Šæ›¸ãã•ã‚Œã¾ã™ã€‚\n" "\n" -"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" +"ã‚„ã‚Šç›´ã™ã«ã¯ãƒžãƒ¼ã‚¸å…¨ä½“ã‚’ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。" -#: lib/commit.tcl:172 +#: lib/mergetool.tcl:45 #, tcl-format -msgid "" -"Unmerged files cannot be committed.\n" -"\n" -"File %s has merge conflicts. You must resolve them and stage the file " -"before committing.\n" +msgid "File %s seems to have unresolved conflicts, still stage?" msgstr "" -"マージã—ã¦ã„ãªã„ファイルã¯ã‚³ãƒŸãƒƒãƒˆã§ãã¾ã›ã‚“。\n" -"\n" -"ファイル %s ã«ã¯ãƒžãƒ¼ã‚¸è¡çªãŒæ®‹ã£ã¦ã„ã¾ã™ã€‚ã¾ãšè§£æ±ºã—ã¦ã‚³ãƒŸãƒƒãƒˆäºˆå®šã«åŠ ãˆã‚‹å¿…" -"è¦ãŒã‚ã‚Šã¾ã™ã€‚\n" +"ファイル %s ã«ã¯è§£æ±ºã—ã¦ã„ãªã„競åˆéƒ¨åˆ†ãŒã¾ã ã‚るよã†ã§ã™ãŒã€ã„ã„ã§ã™ã‹?" + +#: lib/mergetool.tcl:60 +#, tcl-format +msgid "Adding resolution for %s" +msgstr "%s ã¸ã®è§£æ±ºã‚’ステージã—ã¾ã™" + +#: lib/mergetool.tcl:141 +msgid "Cannot resolve deletion or link conflicts using a tool" +msgstr "ツールã§ã¯å‰Šé™¤ã‚„リンク競åˆã¯æ‰±ãˆã¾ã›ã‚“" -#: lib/commit.tcl:180 +#: lib/mergetool.tcl:146 +msgid "Conflict file does not exist" +msgstr "競åˆãƒ•ã‚¡ã‚¤ãƒ«ã¯å˜åœ¨ã—ã¾ã›ã‚“。" + +#: lib/mergetool.tcl:246 #, tcl-format -msgid "" -"Unknown file state %s detected.\n" -"\n" -"File %s cannot be committed by this program.\n" -msgstr "" -"ä¸æ˜Žãªãƒ•ã‚¡ã‚¤ãƒ«çŠ¶æ…‹ %s ã§ã™ã€‚\n" -"\n" -"ファイル %s ã¯æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã§ã¯ã‚³ãƒŸãƒƒãƒˆã§ãã¾ã›ã‚“。\n" +msgid "Not a GUI merge tool: '%s'" +msgstr "GUI マージツールã§ã¯ã‚ã‚Šã¾ã›ã‚“: %s" -#: lib/commit.tcl:188 +#: lib/mergetool.tcl:275 +#, tcl-format +msgid "Unsupported merge tool '%s'" +msgstr "マージツール '%s' ã¯ã‚µãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“" + +#: lib/mergetool.tcl:310 +msgid "Merge tool is already running, terminate it?" +msgstr "マージツールã¯ã™ã§ã«èµ·å‹•ã—ã¦ã„ã¾ã™ã€‚終了ã—ã¾ã™ã‹?" + +#: lib/mergetool.tcl:330 +#, tcl-format msgid "" -"No changes to commit.\n" -"\n" -"You must stage at least 1 file before you can commit.\n" +"Error retrieving versions:\n" +"%s" msgstr "" -"コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n" -"\n" -"最低一ã¤ã®å¤‰æ›´ã‚’コミット予定ã«åŠ ãˆã¦ã‹ã‚‰ã‚³ãƒŸãƒƒãƒˆã—ã¦ä¸‹ã•ã„。\n" +"版ã®å–り出ã—時ã«ã‚¨ãƒ©ãƒ¼ãŒå‡ºã¾ã—ãŸ:\n" +"%s" -#: lib/commit.tcl:203 +#: lib/mergetool.tcl:350 +#, tcl-format msgid "" -"Please supply a commit message.\n" -"\n" -"A good commit message has the following format:\n" +"Could not start the merge tool:\n" "\n" -"- First line: Describe in one sentence what you did.\n" -"- Second line: Blank\n" -"- Remaining lines: Describe why this change is good.\n" +"%s" msgstr "" -"コミット・メッセージを入力ã—ã¦ä¸‹ã•ã„。\n" -"\n" -"æ£ã—ã„コミット・メッセージã¯:\n" +"マージツールãŒèµ·å‹•ã§ãã¾ã›ã‚“:\n" "\n" -"- 第1行: 何をã—ãŸã‹ã€ã‚’1行ã§è¦ç´„。\n" -"- 第2行: 空白\n" -"- 残りã®è¡Œ: ãªãœã€ã“ã®å¤‰æ›´ãŒè‰¯ã„変更ã‹ã€ã®èª¬æ˜Žã€‚\n" - -#: lib/commit.tcl:234 -msgid "Calling pre-commit hook..." -msgstr "コミットå‰ãƒ•ãƒƒã‚¯ã‚’実行ä¸ãƒ»ãƒ»ãƒ»" +"%s" -#: lib/commit.tcl:249 -msgid "Commit declined by pre-commit hook." -msgstr "コミットå‰ãƒ•ãƒƒã‚¯ãŒã‚³ãƒŸãƒƒãƒˆã‚’æ‹’å¦ã—ã¾ã—ãŸ" +#: lib/mergetool.tcl:354 +msgid "Running merge tool..." +msgstr "マージツールを実行ã—ã¦ã„ã¾ã™..." -#: lib/commit.tcl:272 -msgid "Calling commit-msg hook..." -msgstr "コミット・メッセージ・フックを実行ä¸ãƒ»ãƒ»ãƒ»" +#: lib/mergetool.tcl:382 lib/mergetool.tcl:390 +msgid "Merge tool failed." +msgstr "マージツールãŒå¤±æ•—ã—ã¾ã—ãŸã€‚" -#: lib/commit.tcl:287 -msgid "Commit declined by commit-msg hook." -msgstr "コミット・メッセージ・フックãŒã‚³ãƒŸãƒƒãƒˆã‚’æ‹’å¦ã—ã¾ã—ãŸ" +#: lib/option.tcl:11 +#, tcl-format +msgid "Invalid global encoding '%s'" +msgstr "全体エンコーディング㫠無効㪠%s ãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã™" -#: lib/commit.tcl:300 -msgid "Committing changes..." -msgstr "変更点をコミットä¸ãƒ»ãƒ»ãƒ»" +#: lib/option.tcl:19 +#, tcl-format +msgid "Invalid repo encoding '%s'" +msgstr "リãƒã‚¸ãƒˆãƒªã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚°ã« 無効㪠%s ãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã™" -#: lib/commit.tcl:316 -msgid "write-tree failed:" -msgstr "write-tree ãŒå¤±æ•—ã—ã¾ã—ãŸ:" +#: lib/option.tcl:119 +msgid "Restore Defaults" +msgstr "既定値ã«æˆ»ã™" -#: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 -msgid "Commit failed." -msgstr "コミットã«å¤±æ•—ã—ã¾ã—ãŸã€‚" +#: lib/option.tcl:123 +msgid "Save" +msgstr "ä¿å˜" -#: lib/commit.tcl:334 +#: lib/option.tcl:133 #, tcl-format -msgid "Commit %s appears to be corrupt" -msgstr "コミット %s ã¯å£Šã‚Œã¦ã„ã¾ã™" +msgid "%s Repository" +msgstr "%s リãƒã‚¸ãƒˆãƒª" -#: lib/commit.tcl:339 -msgid "" -"No changes to commit.\n" -"\n" -"No files were modified by this commit and it was not a merge commit.\n" -"\n" -"A rescan will be automatically started now.\n" -msgstr "" -"コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n" -"\n" -"マージã§ãªãã€ã¾ãŸã€ä¸€ã¤ã‚‚変更点ãŒã‚ã‚Šã¾ã›ã‚“。\n" -"\n" -"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" +#: lib/option.tcl:134 +msgid "Global (All Repositories)" +msgstr "大域(全ã¦ã®ãƒªãƒã‚¸ãƒˆãƒªï¼‰" -#: lib/commit.tcl:346 -msgid "No changes to commit." -msgstr "コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。" +#: lib/option.tcl:140 +msgid "User Name" +msgstr "ユーザå" -#: lib/commit.tcl:360 -msgid "commit-tree failed:" -msgstr "commit-tree ãŒå¤±æ•—ã—ã¾ã—ãŸ:" +#: lib/option.tcl:141 +msgid "Email Address" +msgstr "é›»åメールアドレス" -#: lib/commit.tcl:381 -msgid "update-ref failed:" -msgstr "update-ref ãŒå¤±æ•—ã—ã¾ã—ãŸ:" +#: lib/option.tcl:143 +msgid "Summarize Merge Commits" +msgstr "マージコミットã®è¦ç´„" + +#: lib/option.tcl:144 +msgid "Merge Verbosity" +msgstr "マージã®å†—長度" + +#: lib/option.tcl:145 +msgid "Show Diffstat After Merge" +msgstr "マージ後㫠diffstat を表示" + +#: lib/option.tcl:146 +msgid "Use Merge Tool" +msgstr "マージツールを使用" + +#: lib/option.tcl:148 +msgid "Trust File Modification Timestamps" +msgstr "ãƒ•ã‚¡ã‚¤ãƒ«å¤‰æ›´æ™‚åˆ»ã‚’ä¿¡é ¼ã™ã‚‹" + +#: lib/option.tcl:149 +msgid "Prune Tracking Branches During Fetch" +msgstr "フェッãƒä¸ã«ãƒˆãƒ©ãƒƒã‚ングブランãƒã‚’刈る" -#: lib/commit.tcl:469 +#: lib/option.tcl:150 +msgid "Match Tracking Branches" +msgstr "トラッã‚ングブランãƒã‚’åˆã‚ã›ã‚‹" + +#: lib/option.tcl:151 +msgid "Use Textconv For Diffs and Blames" +msgstr "diff ã¨æ³¨é‡ˆã« textconv を使ã†" + +#: lib/option.tcl:152 +msgid "Blame Copy Only On Changed Files" +msgstr "変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã®ã¿ã‚³ãƒ”ー検知を行ãªã†" + +#: lib/option.tcl:153 +msgid "Maximum Length of Recent Repositories List" +msgstr "最近使ã£ãŸãƒªãƒã‚¸ãƒˆãƒªä¸€è¦§ã®ä¸Šé™" + +#: lib/option.tcl:154 +msgid "Minimum Letters To Blame Copy On" +msgstr "コピーを検知ã™ã‚‹æœ€å°‘æ–‡å—æ•°" + +#: lib/option.tcl:155 +msgid "Blame History Context Radius (days)" +msgstr "注釈ã™ã‚‹å±¥æ´åŠå¾„(日数)" + +#: lib/option.tcl:156 +msgid "Number of Diff Context Lines" +msgstr "diff ã®æ–‡è„ˆè¡Œæ•°" + +#: lib/option.tcl:157 +msgid "Additional Diff Parameters" +msgstr "diff ã®è¿½åŠ 引数" + +#: lib/option.tcl:158 +msgid "Commit Message Text Width" +msgstr "コミットメッセージã®ãƒ†ã‚スト幅" + +#: lib/option.tcl:159 +msgid "New Branch Name Template" +msgstr "æ–°ã—ã„ブランãƒåã®ãƒ†ãƒ³ãƒ—レート" + +#: lib/option.tcl:160 +msgid "Default File Contents Encoding" +msgstr "ファイル内容ã®ãƒ‡ãƒ•ã‚©ãƒ¼ãƒ«ãƒˆã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚°" + +#: lib/option.tcl:161 +msgid "Warn before committing to a detached head" +msgstr "分離 HEAD ã®ã‚³ãƒŸãƒƒãƒˆå‰ã«è¦å‘Šã™ã‚‹" + +#: lib/option.tcl:162 +msgid "Staging of untracked files" +msgstr "管ç†å¤–ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’コミット予定ã™ã‚‹" + +#: lib/option.tcl:163 +msgid "Show untracked files" +msgstr "管ç†å¤–ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’表示ã™ã‚‹" + +#: lib/option.tcl:164 +msgid "Tab spacing" +msgstr "タブ幅" + +#: lib/option.tcl:210 +msgid "Change" +msgstr "変更" + +#: lib/option.tcl:254 +msgid "Spelling Dictionary:" +msgstr "スペルãƒã‚§ãƒƒã‚¯è¾žæ›¸" + +#: lib/option.tcl:284 +msgid "Change Font" +msgstr "フォントを変更" + +#: lib/option.tcl:288 #, tcl-format -msgid "Created commit %s: %s" -msgstr "コミット %s を作æˆã—ã¾ã—ãŸ: %s" +msgid "Choose %s" +msgstr "%s ã‚’é¸æŠž" -#: lib/console.tcl:59 -msgid "Working... please wait..." -msgstr "実行ä¸â€¦ãŠå¾…ã¡ä¸‹ã•ã„…" +#: lib/option.tcl:294 +msgid "pt." +msgstr "ãƒã‚¤ãƒ³ãƒˆ" -#: lib/console.tcl:186 -msgid "Success" -msgstr "æˆåŠŸ" +#: lib/option.tcl:308 +msgid "Preferences" +msgstr "è¨å®š" -#: lib/console.tcl:200 -msgid "Error: Command Failed" -msgstr "エラー: コマンドãŒå¤±æ•—ã—ã¾ã—ãŸ" +#: lib/option.tcl:310 lib/branch_checkout.tcl:39 lib/branch_create.tcl:69 +msgid "Options" +msgstr "オプション" -#: lib/database.tcl:43 +#: lib/option.tcl:345 +msgid "Failed to completely save options:" +msgstr "完全ã«ã‚ªãƒ—ションをä¿å˜ã§ãã¾ã›ã‚“:" + +#: lib/database.tcl:42 msgid "Number of loose objects" msgstr "ã°ã‚‰ã°ã‚‰ãªã‚ªãƒ–ジェクトã®æ•°" -#: lib/database.tcl:44 +#: lib/database.tcl:43 msgid "Disk space used by loose objects" msgstr "ã°ã‚‰ã°ã‚‰ãªã‚ªãƒ–ジェクトã®ä½¿ç”¨ã™ã‚‹ãƒ‡ã‚£ã‚¹ã‚¯é‡" -#: lib/database.tcl:45 +#: lib/database.tcl:44 msgid "Number of packed objects" msgstr "パックã•ã‚ŒãŸã‚ªãƒ–ジェクトã®æ•°" -#: lib/database.tcl:46 +#: lib/database.tcl:45 msgid "Number of packs" msgstr "パックã®æ•°" -#: lib/database.tcl:47 +#: lib/database.tcl:46 msgid "Disk space used by packed objects" msgstr "パックã•ã‚ŒãŸã‚ªãƒ–ジェクトã®ä½¿ç”¨ã™ã‚‹ãƒ‡ã‚£ã‚¹ã‚¯é‡" -#: lib/database.tcl:48 +#: lib/database.tcl:47 msgid "Packed objects waiting for pruning" msgstr "パックã«å˜åœ¨ã™ã‚‹ã®ã§æ¨ã¦ã¦è‰¯ã„オブジェクトã®æ•°" -#: lib/database.tcl:49 +#: lib/database.tcl:48 msgid "Garbage files" msgstr "ゴミファイル" @@ -1549,12 +1954,132 @@ msgstr "" "\n" "データベースを圧縮ã—ã¾ã™ã‹ï¼Ÿ" -#: lib/date.tcl:25 +#: lib/transport.tcl:6 lib/remote_add.tcl:132 #, tcl-format -msgid "Invalid date from Git: %s" -msgstr "Git ã‹ã‚‰å‡ºãŸç„¡åŠ¹ãªæ—¥ä»˜: %s" +msgid "fetch %s" +msgstr "%s ã‚’å–å¾—" -#: lib/diff.tcl:64 +#: lib/transport.tcl:7 +#, tcl-format +msgid "Fetching new changes from %s" +msgstr "%s ã‹ã‚‰æ–°ã—ã„変更をフェッãƒã—ã¦ã„ã¾ã™" + +#: lib/transport.tcl:18 +#, tcl-format +msgid "remote prune %s" +msgstr "リモート刈込 %s" + +#: lib/transport.tcl:19 +#, tcl-format +msgid "Pruning tracking branches deleted from %s" +msgstr "%s ã‹ã‚‰å‰Šé™¤ã•ã‚ŒãŸãƒˆãƒ©ãƒƒã‚ング・ブランãƒã‚’刈ã£ã¦ã„ã¾ã™" + +#: lib/transport.tcl:25 +msgid "fetch all remotes" +msgstr "ã™ã¹ã¦ã®ãƒªãƒ¢ãƒ¼ãƒˆã‚’å–å¾—" + +#: lib/transport.tcl:26 +msgid "Fetching new changes from all remotes" +msgstr "ã™ã¹ã¦ã®ãƒªãƒ¢ãƒ¼ãƒˆã‹ã‚‰æ–°ã—ã„変更をフェッãƒã—ã¦ã„ã¾ã™" + +#: lib/transport.tcl:40 +msgid "remote prune all remotes" +msgstr "リモート刈込 ã™ã¹ã¦ã®ãƒªãƒ¢ãƒ¼ãƒˆ" + +#: lib/transport.tcl:41 +msgid "Pruning tracking branches deleted from all remotes" +msgstr "ã™ã¹ã¦ã®ãƒªãƒ¢ãƒ¼ãƒˆã‹ã‚‰å‰Šé™¤ã•ã‚ŒãŸãƒˆãƒ©ãƒƒã‚ング・ブランãƒã‚’刈ã£ã¦ã„ã¾ã™" + +#: lib/transport.tcl:54 lib/transport.tcl:92 lib/transport.tcl:110 +#: lib/remote_add.tcl:162 +#, tcl-format +msgid "push %s" +msgstr "%s をプッシュ" + +#: lib/transport.tcl:55 +#, tcl-format +msgid "Pushing changes to %s" +msgstr "%s ã¸å¤‰æ›´ã‚’プッシュã—ã¦ã„ã¾ã™" + +#: lib/transport.tcl:93 +#, tcl-format +msgid "Mirroring to %s" +msgstr "%s ã¸ãƒŸãƒ©ãƒ¼ã—ã¦ã„ã¾ã™" + +#: lib/transport.tcl:111 +#, tcl-format +msgid "Pushing %s %s to %s" +msgstr "%3$s 㸠%1$s %2$s をプッシュã—ã¦ã„ã¾ã™" + +#: lib/transport.tcl:132 +msgid "Push Branches" +msgstr "ブランãƒã‚’プッシュ" + +#: lib/transport.tcl:147 +msgid "Source Branches" +msgstr "å…ƒã®ãƒ–ランãƒ" + +#: lib/transport.tcl:162 +msgid "Destination Repository" +msgstr "é€ã‚Šå…ˆãƒªãƒã‚¸ãƒˆãƒª" + +#: lib/transport.tcl:205 +msgid "Transfer Options" +msgstr "通信オプション" + +#: lib/transport.tcl:207 +msgid "Force overwrite existing branch (may discard changes)" +msgstr "æ—¢å˜ãƒ–ランãƒã‚’上書ã(å¤‰æ›´ã‚’ç ´æ£„ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™)" + +#: lib/transport.tcl:211 +msgid "Use thin pack (for slow network connections)" +msgstr "Thin Pack を使ã†ï¼ˆé…ã„ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯æŽ¥ç¶šï¼‰" + +#: lib/transport.tcl:215 +msgid "Include tags" +msgstr "ã‚¿ã‚°ã‚’å«ã‚ã‚‹" + +#: lib/choose_font.tcl:41 +msgid "Select" +msgstr "é¸æŠž" + +#: lib/choose_font.tcl:55 +msgid "Font Family" +msgstr "フォント・ファミリー" + +#: lib/choose_font.tcl:76 +msgid "Font Size" +msgstr "フォントã®å¤§ãã•" + +#: lib/choose_font.tcl:93 +msgid "Font Example" +msgstr "フォント・サンプル" + +#: lib/choose_font.tcl:105 +msgid "" +"This is example text.\n" +"If you like this text, it can be your font." +msgstr "" +"ã“ã‚Œã¯ã‚µãƒ³ãƒ—ル文ã§ã™ã€‚\n" +"ã“ã®ãƒ•ã‚©ãƒ³ãƒˆãŒæ°—ã«å…¥ã‚Œã°ãŠä½¿ã„ã«ãªã‚Œã¾ã™ã€‚" + +#: lib/remote.tcl:200 +msgid "Push to" +msgstr "プッシュ先" + +#: lib/remote.tcl:218 +msgid "Remove Remote" +msgstr "リモートを削除" + +#: lib/remote.tcl:223 +msgid "Prune from" +msgstr "ã‹ã‚‰åˆˆè¾¼ã‚€â€¦" + +#: lib/remote.tcl:228 +msgid "Fetch from" +msgstr "å–å¾—å…ƒ" + +#: lib/diff.tcl:77 #, tcl-format msgid "" "No differences detected.\n" @@ -1576,12 +2101,12 @@ msgstr "" "\n" "åŒæ§˜ãªçŠ¶æ…‹ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’探ã™ãŸã‚ã«ã€è‡ªå‹•çš„ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚" -#: lib/diff.tcl:104 +#: lib/diff.tcl:117 #, tcl-format msgid "Loading diff of %s..." msgstr "%s ã®å¤‰æ›´ç‚¹ã‚’ãƒãƒ¼ãƒ‰ä¸â€¦" -#: lib/diff.tcl:125 +#: lib/diff.tcl:140 msgid "" "LOCAL: deleted\n" "REMOTE:\n" @@ -1589,7 +2114,7 @@ msgstr "" "LOCAL: 削除\n" "Remote:\n" -#: lib/diff.tcl:130 +#: lib/diff.tcl:145 msgid "" "REMOTE: deleted\n" "LOCAL:\n" @@ -1597,32 +2122,32 @@ msgstr "" "REMOTE: 削除\n" "LOCAL:\n" -#: lib/diff.tcl:137 +#: lib/diff.tcl:152 msgid "LOCAL:\n" msgstr "LOCAL:\n" -#: lib/diff.tcl:140 +#: lib/diff.tcl:155 msgid "REMOTE:\n" msgstr "REMOTE\n" -#: lib/diff.tcl:202 lib/diff.tcl:319 +#: lib/diff.tcl:217 lib/diff.tcl:355 #, tcl-format msgid "Unable to display %s" msgstr "%s を表示ã§ãã¾ã›ã‚“" -#: lib/diff.tcl:203 +#: lib/diff.tcl:218 msgid "Error loading file:" msgstr "ファイルをèªã‚€éš›ã®ã‚¨ãƒ©ãƒ¼ã§ã™:" -#: lib/diff.tcl:210 +#: lib/diff.tcl:225 msgid "Git Repository (subproject)" msgstr "Git リãƒã‚¸ãƒˆãƒª(サブプãƒã‚¸ã‚§ã‚¯ãƒˆ)" -#: lib/diff.tcl:222 +#: lib/diff.tcl:237 msgid "* Binary file (not showing content)." msgstr "* ãƒã‚¤ãƒŠãƒªãƒ•ã‚¡ã‚¤ãƒ«(内容ã¯è¡¨ç¤ºã—ã¾ã›ã‚“)" -#: lib/diff.tcl:227 +#: lib/diff.tcl:242 #, tcl-format msgid "" "* Untracked file is %d bytes.\n" @@ -1631,7 +2156,7 @@ msgstr "" "* 管ç†å¤–ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®å¤§ãã•ã¯ %d ãƒã‚¤ãƒˆã§ã™ã€‚\n" "* 最åˆã® %d ãƒã‚¤ãƒˆã ã‘表示ã—ã¦ã„ã¾ã™ã€‚\n" -#: lib/diff.tcl:233 +#: lib/diff.tcl:248 #, tcl-format msgid "" "\n" @@ -1643,56 +2168,47 @@ msgstr "" "* %s ã¯ç®¡ç†å¤–ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ã“ã“ã§åˆ‡ã‚ŠãŠã¨ã—ã¾ã—ãŸã€‚\n" "* 全体を見るã«ã¯å¤–部エディタを使ã£ã¦ãã ã•ã„。\n" -#: lib/diff.tcl:482 +#: lib/diff.tcl:578 msgid "Failed to unstage selected hunk." msgstr "é¸æŠžã•ã‚ŒãŸãƒ‘ッãƒã‚’コミット予定ã‹ã‚‰å¤–ã›ã¾ã›ã‚“。" -#: lib/diff.tcl:489 +#: lib/diff.tcl:585 msgid "Failed to stage selected hunk." msgstr "é¸æŠžã•ã‚ŒãŸãƒ‘ッãƒã‚’コミット予定ã«åŠ ãˆã‚‰ã‚Œã¾ã›ã‚“。" -#: lib/diff.tcl:568 +#: lib/diff.tcl:664 msgid "Failed to unstage selected line." msgstr "é¸æŠžã•ã‚ŒãŸãƒ‘ッãƒè¡Œã‚’コミット予定ã‹ã‚‰å¤–ã›ã¾ã›ã‚“。" -#: lib/diff.tcl:576 +#: lib/diff.tcl:672 msgid "Failed to stage selected line." msgstr "é¸æŠžã•ã‚ŒãŸãƒ‘ッãƒè¡Œã‚’コミット予定ã«åŠ ãˆã‚‰ã‚Œã¾ã›ã‚“。" -#: lib/encoding.tcl:443 -msgid "Default" -msgstr "デフォールト" - -#: lib/encoding.tcl:448 -#, tcl-format -msgid "System (%s)" -msgstr "システム(%s)" - -#: lib/encoding.tcl:459 lib/encoding.tcl:465 -msgid "Other" -msgstr "ãã®ä»–" +#: lib/branch_checkout.tcl:16 lib/branch_checkout.tcl:21 +msgid "Checkout Branch" +msgstr "ブランãƒã‚’ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" -#: lib/error.tcl:20 lib/error.tcl:114 -msgid "error" -msgstr "エラー" +#: lib/branch_checkout.tcl:26 +msgid "Checkout" +msgstr "ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" -#: lib/error.tcl:36 -msgid "warning" -msgstr "è¦å‘Š" +#: lib/branch_checkout.tcl:42 lib/branch_create.tcl:92 +msgid "Fetch Tracking Branch" +msgstr "トラッã‚ング・ブランãƒã‚’フェッãƒ" -#: lib/error.tcl:94 -msgid "You must correct the above errors before committing." -msgstr "コミットã™ã‚‹å‰ã«ã€ä»¥ä¸Šã®ã‚¨ãƒ©ãƒ¼ã‚’ä¿®æ£ã—ã¦ä¸‹ã•ã„" +#: lib/branch_checkout.tcl:47 +msgid "Detach From Local Branch" +msgstr "ãƒãƒ¼ã‚«ãƒ«ãƒ»ãƒ–ランãƒã‹ã‚‰å‰Šé™¤" #: lib/index.tcl:6 msgid "Unable to unlock the index." msgstr "インデックスをãƒãƒƒã‚¯ã§ãã¾ã›ã‚“" -#: lib/index.tcl:15 +#: lib/index.tcl:17 msgid "Index Error" msgstr "索引エラー" -#: lib/index.tcl:17 +#: lib/index.tcl:19 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." @@ -1700,433 +2216,330 @@ msgstr "" "GIT インデックスã®æ›´æ–°ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚git-gui ã¨åŒæœŸã‚’ã¨ã‚‹ãŸã‚ã«å†ã‚¹ã‚ャンã—" "ã¾ã™ã€‚" -#: lib/index.tcl:28 +#: lib/index.tcl:30 msgid "Continue" msgstr "続行" -#: lib/index.tcl:31 +#: lib/index.tcl:33 msgid "Unlock Index" msgstr "インデックスã®ãƒãƒƒã‚¯è§£é™¤" -#: lib/index.tcl:289 +#: lib/index.tcl:298 #, tcl-format msgid "Unstaging %s from commit" msgstr "コミットã‹ã‚‰ '%s' ã‚’é™ã‚ã™" -#: lib/index.tcl:328 +#: lib/index.tcl:337 msgid "Ready to commit." msgstr "コミット準備完了" -#: lib/index.tcl:341 +#: lib/index.tcl:350 #, tcl-format msgid "Adding %s" msgstr "コミット㫠%s ã‚’åŠ ãˆã¦ã„ã¾ã™" -#: lib/index.tcl:398 +#: lib/index.tcl:380 +#, tcl-format +msgid "Stage %d untracked files?" +msgstr "管ç†å¤–ã® %d ファイルをコミット予定ã¨ã—ã¾ã™ã‹ï¼Ÿ" + +#: lib/index.tcl:428 #, tcl-format msgid "Revert changes in file %s?" msgstr "ファイル %s ã«ã—ãŸå¤‰æ›´ã‚’å…ƒã«æˆ»ã—ã¾ã™ã‹ï¼Ÿ" -#: lib/index.tcl:400 +#: lib/index.tcl:430 #, tcl-format msgid "Revert changes in these %i files?" msgstr "ã“れら %i 個ã®ãƒ•ã‚¡ã‚¤ãƒ«ã«ã—ãŸå¤‰æ›´ã‚’å…ƒã«æˆ»ã—ã¾ã™ã‹ï¼Ÿ" -#: lib/index.tcl:408 +#: lib/index.tcl:438 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "変更を元ã«æˆ»ã™ã¨ã‚³ãƒŸãƒƒãƒˆäºˆå®šã—ã¦ã„ãªã„変更ã¯å…¨ã¦å¤±ã‚ã‚Œã¾ã™ã€‚" -#: lib/index.tcl:411 +#: lib/index.tcl:441 msgid "Do Nothing" msgstr "何もã—ãªã„" -#: lib/index.tcl:429 +#: lib/index.tcl:459 msgid "Reverting selected files" msgstr "é¸æŠžã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã«ã—ãŸå¤‰æ›´ã‚’å…ƒã«æˆ»ã—ã¾ã™" -#: lib/index.tcl:433 +#: lib/index.tcl:463 #, tcl-format msgid "Reverting %s" msgstr "%s ã«ã—ãŸå¤‰æ›´ã‚’å…ƒã«æˆ»ã—ã¾ã™" -#: lib/merge.tcl:13 +#: lib/sshkey.tcl:31 +msgid "No keys found." +msgstr "ã‚ーãŒã‚ã‚Šã¾ã›ã‚“。" + +#: lib/sshkey.tcl:34 +#, tcl-format +msgid "Found a public key in: %s" +msgstr "公開éµãŒã‚ã‚Šã¾ã—ãŸ: %s" + +#: lib/sshkey.tcl:40 +msgid "Generate Key" +msgstr "éµã‚’生æˆ" + +#: lib/sshkey.tcl:58 +msgid "Copy To Clipboard" +msgstr "クリップボードã«ã‚³ãƒ”ー" + +#: lib/sshkey.tcl:72 +msgid "Your OpenSSH Public Key" +msgstr "ã‚ãªãŸã® OpenSSH 公開éµ" + +#: lib/sshkey.tcl:80 +msgid "Generating..." +msgstr "生æˆä¸..." + +#: lib/sshkey.tcl:86 +#, tcl-format msgid "" -"Cannot merge while amending.\n" +"Could not start ssh-keygen:\n" "\n" -"You must finish amending this commit before starting any type of merge.\n" +"%s" msgstr "" -"訂æ£ä¸ã«ã¯ãƒžãƒ¼ã‚¸ã§ãã¾ã›ã‚“。\n" +"ssh-keygen ã‚’èµ·å‹•ã§ãã¾ã›ã‚“:\n" "\n" -"訂æ£å‡¦ç†ã‚’完了ã™ã‚‹ã¾ã§ã¯æ–°ãŸã«ãƒžãƒ¼ã‚¸ã‚’開始ã§ãã¾ã›ã‚“。\n" +"%s" -#: lib/merge.tcl:27 +#: lib/sshkey.tcl:113 +msgid "Generation failed." +msgstr "生æˆã«å¤±æ•—ã—ã¾ã—ãŸã€‚" + +#: lib/sshkey.tcl:120 +msgid "Generation succeeded, but no keys found." +msgstr "生æˆã«ã¯æˆåŠŸã—ã¾ã—ãŸãŒã€éµãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。" + +#: lib/sshkey.tcl:123 +#, tcl-format +msgid "Your key is in: %s" +msgstr "ã‚ãªãŸã®éµã¯ %s ã«ã‚ã‚Šã¾ã™" + +#: lib/commit.tcl:9 msgid "" -"Last scanned state does not match repository state.\n" -"\n" -"Another Git program has modified this repository since the last scan. A " -"rescan must be performed before a merge can be performed.\n" +"There is nothing to amend.\n" "\n" -"The rescan will be automatically started now.\n" +"You are about to create the initial commit. There is no commit before this " +"to amend.\n" msgstr "" -"最後ã«ã‚¹ã‚ャンã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n" -"\n" -"最後ã«ã‚¹ã‚ャンã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プãƒã‚°ãƒ©ãƒ ãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚マー" -"ジを開始ã™ã‚‹å‰ã«ã€å†ã‚¹ã‚ャンãŒå¿…è¦ã§ã™ã€‚\n" +"訂æ£ã™ã‚‹ã‚³ãƒŸãƒƒãƒˆãŒãã‚‚ãã‚‚ã‚ã‚Šã¾ã›ã‚“。\n" "\n" -"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" +"ã“ã‚Œã‹ã‚‰ä½œã‚‹ã®ã¯æœ€åˆã®ã‚³ãƒŸãƒƒãƒˆã§ã™ã€‚ãã®å‰ã«ã¯ã¾ã 訂æ£ã™ã‚‹ã‚ˆã†ãªã‚³ãƒŸãƒƒãƒˆã¯ã‚" +"ã‚Šã¾ã›ã‚“。\n" -#: lib/merge.tcl:45 -#, tcl-format +#: lib/commit.tcl:18 msgid "" -"You are in the middle of a conflicted merge.\n" -"\n" -"File %s has merge conflicts.\n" +"Cannot amend while merging.\n" "\n" -"You must resolve them, stage the file, and commit to complete the current " -"merge. Only then can you begin another merge.\n" +"You are currently in the middle of a merge that has not been fully " +"completed. You cannot amend the prior commit unless you first abort the " +"current merge activity.\n" msgstr "" -"è¡çªã®ã‚ã£ãŸãƒžãƒ¼ã‚¸ã®é€”ä¸ã§ã™ã€‚\n" -"\n" -"ファイル %s ã«ã¯ãƒžãƒ¼ã‚¸ä¸ã®è¡çªãŒæ®‹ã£ã¦ã„ã¾ã™ã€‚\n" +"マージä¸ã«ã‚³ãƒŸãƒƒãƒˆã®è¨‚æ£ã¯ã§ãã¾ã›ã‚“。\n" "\n" -"ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®è¡çªã‚’解決ã—ã€ã‚³ãƒŸãƒƒãƒˆäºˆå®šã«åŠ ãˆã¦ã€ã‚³ãƒŸãƒƒãƒˆã™ã‚‹ã“ã¨ã§ãƒžãƒ¼ã‚¸ã‚’" -"完了ã—ã¾ã™ã€‚ãã†ã‚„ã£ã¦å§‹ã‚ã¦ã€æ–°ãŸãªãƒžãƒ¼ã‚¸ã‚’開始ã§ãるよã†ã«ãªã‚Šã¾ã™ã€‚\n" +"ç¾åœ¨ã¯ã¾ã マージã®é€”ä¸ã§ã™ã€‚å…ˆã«ã“ã®ãƒžãƒ¼ã‚¸ã‚’ä¸æ¢ã—ãªã„ã¨ã€å‰ã®ã‚³ãƒŸãƒƒãƒˆã®è¨‚æ£" +"ã¯ã§ãã¾ã›ã‚“\n" -#: lib/merge.tcl:55 +#: lib/commit.tcl:48 +msgid "Error loading commit data for amend:" +msgstr "訂æ£ã™ã‚‹ã‚³ãƒŸãƒƒãƒˆã®ãƒ‡ãƒ¼ã‚¿ã‚’èªã‚ã¾ã›ã‚“:" + +#: lib/commit.tcl:75 +msgid "Unable to obtain your identity:" +msgstr "ユーザã®æ£ä½“を確èªã§ãã¾ã›ã‚“:" + +#: lib/commit.tcl:80 +msgid "Invalid GIT_COMMITTER_IDENT:" +msgstr "GIT_COMMITTER_IDENT ãŒç„¡åŠ¹ã§ã™:" + +#: lib/commit.tcl:129 #, tcl-format +msgid "warning: Tcl does not support encoding '%s'." +msgstr "è¦å‘Š: Tcl ã¯ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚° '%s' をサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“" + +#: lib/commit.tcl:149 msgid "" -"You are in the middle of a change.\n" +"Last scanned state does not match repository state.\n" "\n" -"File %s is modified.\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before another commit can be created.\n" "\n" -"You should complete the current commit before starting a merge. Doing so " -"will help you abort a failed merge, should the need arise.\n" +"The rescan will be automatically started now.\n" msgstr "" -"変更ã®é€”ä¸ã§ã™ã€‚\n" +"最後ã«ã‚¹ã‚ャンã—ãŸçŠ¶æ…‹ã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã¨åˆè‡´ã—ã¾ã›ã‚“。\n" "\n" -"ファイル %s ã¯å¤‰æ›´ä¸ã§ã™ã€‚\n" +"最後ã«ã‚¹ã‚ャンã—ã¦ä»¥å¾Œã€åˆ¥ã® Git プãƒã‚°ãƒ©ãƒ ãŒãƒªãƒã‚¸ãƒˆãƒªã‚’変更ã—ã¦ã„ã¾ã™ã€‚æ–°ã—" +"ãコミットã™ã‚‹å‰ã«ã€å†ã‚¹ã‚ャンãŒå¿…è¦ã§ã™ã€‚\n" "\n" -"ç¾åœ¨ã®ã‚³ãƒŸãƒƒãƒˆã‚’完了ã—ã¦ã‹ã‚‰ãƒžãƒ¼ã‚¸ã‚’開始ã—ã¦ä¸‹ã•ã„。ãã†ã™ã‚‹æ–¹ãŒãƒžãƒ¼ã‚¸ã«å¤±æ•—" -"ã—ãŸã¨ãã®å›žå¾©ãŒæ¥½ã§ã™ã€‚\n" - -#: lib/merge.tcl:107 -#, tcl-format -msgid "%s of %s" -msgstr "%s ã® %s ブランãƒ" +"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" -#: lib/merge.tcl:120 +#: lib/commit.tcl:173 #, tcl-format -msgid "Merging %s and %s..." -msgstr "%s 㨠%s をマージä¸ãƒ»ãƒ»ãƒ»" - -#: lib/merge.tcl:131 -msgid "Merge completed successfully." -msgstr "マージãŒå®Œäº†ã—ã¾ã—ãŸ" - -#: lib/merge.tcl:133 -msgid "Merge failed. Conflict resolution is required." -msgstr "マージãŒå¤±æ•—ã—ã¾ã—ãŸã€‚è¡çªã®è§£æ±ºãŒå¿…è¦ã§ã™ã€‚" +msgid "" +"Unmerged files cannot be committed.\n" +"\n" +"File %s has merge conflicts. You must resolve them and stage the file " +"before committing.\n" +msgstr "" +"マージã—ã¦ã„ãªã„ファイルã¯ã‚³ãƒŸãƒƒãƒˆã§ãã¾ã›ã‚“。\n" +"\n" +"ファイル %s ã«ã¯ãƒžãƒ¼ã‚¸è¡çªãŒæ®‹ã£ã¦ã„ã¾ã™ã€‚ã¾ãšè§£æ±ºã—ã¦ã‚³ãƒŸãƒƒãƒˆäºˆå®šã«åŠ ãˆã‚‹å¿…" +"è¦ãŒã‚ã‚Šã¾ã™ã€‚\n" -#: lib/merge.tcl:158 +#: lib/commit.tcl:181 #, tcl-format -msgid "Merge Into %s" -msgstr "%s ã«ãƒžãƒ¼ã‚¸" - -#: lib/merge.tcl:177 -msgid "Revision To Merge" -msgstr "マージã™ã‚‹ãƒªãƒ“ジョン" - -#: lib/merge.tcl:212 msgid "" -"Cannot abort while amending.\n" +"Unknown file state %s detected.\n" "\n" -"You must finish amending this commit.\n" +"File %s cannot be committed by this program.\n" msgstr "" -"訂æ£ä¸ã«ã¯ä¸æ¢ã§ãã¾ã›ã‚“。\n" +"ä¸æ˜Žãªãƒ•ã‚¡ã‚¤ãƒ«çŠ¶æ…‹ %s ã§ã™ã€‚\n" "\n" -"ã¾ãšä»Šã®ã‚³ãƒŸãƒƒãƒˆè¨‚æ£ã‚’完了ã•ã›ã¦ä¸‹ã•ã„。\n" +"ファイル %s ã¯æœ¬ãƒ—ãƒã‚°ãƒ©ãƒ ã§ã¯ã‚³ãƒŸãƒƒãƒˆã§ãã¾ã›ã‚“。\n" -#: lib/merge.tcl:222 +#: lib/commit.tcl:189 msgid "" -"Abort merge?\n" -"\n" -"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n" +"No changes to commit.\n" "\n" -"Continue with aborting the current merge?" +"You must stage at least 1 file before you can commit.\n" msgstr "" -"マージをä¸æ–ã—ã¾ã™ã‹ï¼Ÿ\n" -"\n" -"ç¾åœ¨ã®ãƒžãƒ¼ã‚¸ã‚’ä¸æ–ã™ã‚‹ã¨ã€ã‚³ãƒŸãƒƒãƒˆã—ã¦ã„ãªã„å…¨ã¦ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚\n" +"コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n" "\n" -"マージをä¸æ–ã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ" +"最低一ã¤ã®å¤‰æ›´ã‚’コミット予定ã«åŠ ãˆã¦ã‹ã‚‰ã‚³ãƒŸãƒƒãƒˆã—ã¦ä¸‹ã•ã„。\n" -#: lib/merge.tcl:228 +#: lib/commit.tcl:204 msgid "" -"Reset changes?\n" +"Please supply a commit message.\n" "\n" -"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" +"A good commit message has the following format:\n" "\n" -"Continue with resetting the current changes?" +"- First line: Describe in one sentence what you did.\n" +"- Second line: Blank\n" +"- Remaining lines: Describe why this change is good.\n" msgstr "" -"変更点をリセットã—ã¾ã™ã‹ï¼Ÿ\n" +"コミット・メッセージを入力ã—ã¦ä¸‹ã•ã„。\n" "\n" -"変更点をリセットã™ã‚‹ã¨ã€ã‚³ãƒŸãƒƒãƒˆã—ã¦ã„ãªã„å…¨ã¦ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚\n" +"æ£ã—ã„コミット・メッセージã¯:\n" "\n" -"リセットã—ã¦ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ" - -#: lib/merge.tcl:239 -msgid "Aborting" -msgstr "ä¸æ–ã—ã¦ã„ã¾ã™" - -#: lib/merge.tcl:239 -msgid "files reset" -msgstr "リセットã—ãŸãƒ•ã‚¡ã‚¤ãƒ«" - -#: lib/merge.tcl:267 -msgid "Abort failed." -msgstr "ä¸æ–ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" - -#: lib/merge.tcl:269 -msgid "Abort completed. Ready." -msgstr "ä¸æ–完了。" - -#: lib/mergetool.tcl:8 -msgid "Force resolution to the base version?" -msgstr "共通ã®ç‰ˆã‚’使ã„ã¾ã™ã‹?" +"- 第1行: 何をã—ãŸã‹ã€ã‚’1行ã§è¦ç´„。\n" +"- 第2行: 空白\n" +"- 残りã®è¡Œ: ãªãœã€ã“ã®å¤‰æ›´ãŒè‰¯ã„変更ã‹ã€ã®èª¬æ˜Žã€‚\n" -#: lib/mergetool.tcl:9 -msgid "Force resolution to this branch?" -msgstr "自分ã®å´ã®ç‰ˆã‚’使ã„ã¾ã™ã‹?" +#: lib/commit.tcl:235 +msgid "Calling pre-commit hook..." +msgstr "コミットå‰ãƒ•ãƒƒã‚¯ã‚’実行ä¸ãƒ»ãƒ»ãƒ»" -#: lib/mergetool.tcl:10 -msgid "Force resolution to the other branch?" -msgstr "相手制ã®ç‰ˆã‚’使ã„ã¾ã™ã‹?" +#: lib/commit.tcl:250 +msgid "Commit declined by pre-commit hook." +msgstr "コミットå‰ãƒ•ãƒƒã‚¯ãŒã‚³ãƒŸãƒƒãƒˆã‚’æ‹’å¦ã—ã¾ã—ãŸ" -#: lib/mergetool.tcl:14 -#, tcl-format +#: lib/commit.tcl:269 msgid "" -"Note that the diff shows only conflicting changes.\n" -"\n" -"%s will be overwritten.\n" -"\n" -"This operation can be undone only by restarting the merge." +"You are about to commit on a detached head. This is a potentially dangerous " +"thing to do because if you switch to another branch you will lose your " +"changes and it can be difficult to retrieve them later from the reflog. You " +"should probably cancel this commit and create a new branch to continue.\n" +" \n" +" Do you really want to proceed with your Commit?" msgstr "" -"競åˆã™ã‚‹å¤‰æ›´ç‚¹ã ã‘ãŒè¡¨ç¤ºã•ã‚Œã¦ã„ã‚‹ã“ã¨ã«æ³¨æ„ã—ã¦ãã ã•ã„。\n" +"分離 HEAD ã§ã®å¤‰æ›´ã‚’コミットã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚" +"ã“ã‚Œã¯æ½œåœ¨çš„ã«å±é™ºãªè¡Œç‚ºã§ã€ç†ç”±ã¯åˆ¥ã®ãƒ–ランãƒã¸ã®åˆ‡ã‚Šæ›¿ãˆã§" +"変更ãŒæ¶ˆå¤±ã—ã€reflog ã‹ã‚‰ã®äº‹å¾Œå¾©æ—§ã‚‚困難ã¨ãªã‚‹ãŸã‚ã§ã™ã€‚" +"ãŠãらãã“ã®ã‚³ãƒŸãƒƒãƒˆã¯ã‚ャンセルã—æ–°ã—ã作æˆã—ãŸãƒ–ランãƒã§" +"è¡Œã†ã¹ãã§ã™ã€‚\n" "\n" -"%s ã¯ä¸Šæ›¸ãã•ã‚Œã¾ã™ã€‚\n" -"\n" -"ã‚„ã‚Šç›´ã™ã«ã¯ãƒžãƒ¼ã‚¸å…¨ä½“ã‚’ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。" +" 本当ã«ã‚³ãƒŸãƒƒãƒˆã‚’続行ã—ã¾ã™ã‹ï¼Ÿ" -#: lib/mergetool.tcl:45 -#, tcl-format -msgid "File %s seems to have unresolved conflicts, still stage?" -msgstr "" -"ファイル %s ã«ã¯è§£æ±ºã—ã¦ã„ãªã„競åˆéƒ¨åˆ†ãŒã¾ã ã‚るよã†ã§ã™ãŒã€ã„ã„ã§ã™ã‹?" - -#: lib/mergetool.tcl:60 -#, tcl-format -msgid "Adding resolution for %s" -msgstr "%s ã¸ã®è§£æ±ºã‚’ステージã—ã¾ã™" - -#: lib/mergetool.tcl:141 -msgid "Cannot resolve deletion or link conflicts using a tool" -msgstr "ツールã§ã¯å‰Šé™¤ã‚„リンク競åˆã¯æ‰±ãˆã¾ã›ã‚“" +#: lib/commit.tcl:290 +msgid "Calling commit-msg hook..." +msgstr "コミット・メッセージ・フックを実行ä¸ãƒ»ãƒ»ãƒ»" -#: lib/mergetool.tcl:146 -msgid "Conflict file does not exist" -msgstr "競åˆãƒ•ã‚¡ã‚¤ãƒ«ã¯å˜åœ¨ã—ã¾ã›ã‚“。" +#: lib/commit.tcl:305 +msgid "Commit declined by commit-msg hook." +msgstr "コミット・メッセージ・フックãŒã‚³ãƒŸãƒƒãƒˆã‚’æ‹’å¦ã—ã¾ã—ãŸ" -#: lib/mergetool.tcl:264 -#, tcl-format -msgid "Not a GUI merge tool: '%s'" -msgstr "GUI マージツールã§ã¯ã‚ã‚Šã¾ã›ã‚“: %s" +#: lib/commit.tcl:318 +msgid "Committing changes..." +msgstr "変更点をコミットä¸ãƒ»ãƒ»ãƒ»" -#: lib/mergetool.tcl:268 -#, tcl-format -msgid "Unsupported merge tool '%s'" -msgstr "マージツール '%s' ã¯ã‚µãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“" +#: lib/commit.tcl:334 +msgid "write-tree failed:" +msgstr "write-tree ãŒå¤±æ•—ã—ã¾ã—ãŸ:" -#: lib/mergetool.tcl:303 -msgid "Merge tool is already running, terminate it?" -msgstr "マージツールã¯ã™ã§ã«èµ·å‹•ã—ã¦ã„ã¾ã™ã€‚終了ã—ã¾ã™ã‹?" +#: lib/commit.tcl:335 lib/commit.tcl:379 lib/commit.tcl:400 +msgid "Commit failed." +msgstr "コミットã«å¤±æ•—ã—ã¾ã—ãŸã€‚" -#: lib/mergetool.tcl:323 +#: lib/commit.tcl:352 #, tcl-format -msgid "" -"Error retrieving versions:\n" -"%s" -msgstr "" -"版ã®å–り出ã—時ã«ã‚¨ãƒ©ãƒ¼ãŒå‡ºã¾ã—ãŸ:\n" -"%s" +msgid "Commit %s appears to be corrupt" +msgstr "コミット %s ã¯å£Šã‚Œã¦ã„ã¾ã™" -#: lib/mergetool.tcl:343 -#, tcl-format +#: lib/commit.tcl:357 msgid "" -"Could not start the merge tool:\n" +"No changes to commit.\n" "\n" -"%s" +"No files were modified by this commit and it was not a merge commit.\n" +"\n" +"A rescan will be automatically started now.\n" msgstr "" -"マージツールãŒèµ·å‹•ã§ãã¾ã›ã‚“:\n" +"コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。\n" "\n" -"%s" - -#: lib/mergetool.tcl:347 -msgid "Running merge tool..." -msgstr "マージツールを実行ã—ã¦ã„ã¾ã™..." - -#: lib/mergetool.tcl:375 lib/mergetool.tcl:383 -msgid "Merge tool failed." -msgstr "マージツールãŒå¤±æ•—ã—ã¾ã—ãŸã€‚" - -#: lib/option.tcl:11 -#, tcl-format -msgid "Invalid global encoding '%s'" -msgstr "全体エンコーディング㫠無効㪠%s ãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã™" +"マージã§ãªãã€ã¾ãŸã€ä¸€ã¤ã‚‚変更点ãŒã‚ã‚Šã¾ã›ã‚“。\n" +"\n" +"自動的ã«å†ã‚¹ã‚ャンを開始ã—ã¾ã™ã€‚\n" -#: lib/option.tcl:19 -#, tcl-format -msgid "Invalid repo encoding '%s'" -msgstr "リãƒã‚¸ãƒˆãƒªã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚°ã« 無効㪠%s ãŒæŒ‡å®šã•ã‚Œã¦ã„ã¾ã™" +#: lib/commit.tcl:364 +msgid "No changes to commit." +msgstr "コミットã™ã‚‹å¤‰æ›´ãŒã‚ã‚Šã¾ã›ã‚“。" -#: lib/option.tcl:117 -msgid "Restore Defaults" -msgstr "既定値ã«æˆ»ã™" +#: lib/commit.tcl:378 +msgid "commit-tree failed:" +msgstr "commit-tree ãŒå¤±æ•—ã—ã¾ã—ãŸ:" -#: lib/option.tcl:121 -msgid "Save" -msgstr "ä¿å˜" +#: lib/commit.tcl:399 +msgid "update-ref failed:" +msgstr "update-ref ãŒå¤±æ•—ã—ã¾ã—ãŸ:" -#: lib/option.tcl:131 +#: lib/commit.tcl:492 #, tcl-format -msgid "%s Repository" -msgstr "%s リãƒã‚¸ãƒˆãƒª" - -#: lib/option.tcl:132 -msgid "Global (All Repositories)" -msgstr "大域(全ã¦ã®ãƒªãƒã‚¸ãƒˆãƒªï¼‰" - -#: lib/option.tcl:138 -msgid "User Name" -msgstr "ユーザå" - -#: lib/option.tcl:139 -msgid "Email Address" -msgstr "é›»åメールアドレス" - -#: lib/option.tcl:141 -msgid "Summarize Merge Commits" -msgstr "マージコミットã®è¦ç´„" - -#: lib/option.tcl:142 -msgid "Merge Verbosity" -msgstr "マージã®å†—長度" - -#: lib/option.tcl:143 -msgid "Show Diffstat After Merge" -msgstr "マージ後㫠diffstat を表示" - -#: lib/option.tcl:144 -msgid "Use Merge Tool" -msgstr "マージツールを使用" - -#: lib/option.tcl:146 -msgid "Trust File Modification Timestamps" -msgstr "ãƒ•ã‚¡ã‚¤ãƒ«å¤‰æ›´æ™‚åˆ»ã‚’ä¿¡é ¼ã™ã‚‹" - -#: lib/option.tcl:147 -msgid "Prune Tracking Branches During Fetch" -msgstr "フェッãƒä¸ã«ãƒˆãƒ©ãƒƒã‚ングブランãƒã‚’刈る" - -#: lib/option.tcl:148 -msgid "Match Tracking Branches" -msgstr "トラッã‚ングブランãƒã‚’åˆã‚ã›ã‚‹" - -#: lib/option.tcl:149 -msgid "Blame Copy Only On Changed Files" -msgstr "変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã®ã¿ã‚³ãƒ”ー検知を行ãªã†" - -#: lib/option.tcl:150 -msgid "Minimum Letters To Blame Copy On" -msgstr "コピーを検知ã™ã‚‹æœ€å°‘æ–‡å—æ•°" - -#: lib/option.tcl:151 -msgid "Blame History Context Radius (days)" -msgstr "註釈ã™ã‚‹å±¥æ´åŠå¾„(日数)" - -#: lib/option.tcl:152 -msgid "Number of Diff Context Lines" -msgstr "diff ã®æ–‡è„ˆè¡Œæ•°" - -#: lib/option.tcl:153 -msgid "Commit Message Text Width" -msgstr "コミットメッセージã®ãƒ†ã‚スト幅" - -#: lib/option.tcl:154 -msgid "New Branch Name Template" -msgstr "æ–°ã—ã„ブランãƒåã®ãƒ†ãƒ³ãƒ—レート" - -#: lib/option.tcl:155 -msgid "Default File Contents Encoding" -msgstr "ファイル内容ã®ãƒ‡ãƒ•ã‚©ãƒ¼ãƒ«ãƒˆã‚¨ãƒ³ã‚³ãƒ¼ãƒ‡ã‚£ãƒ³ã‚°" - -#: lib/option.tcl:203 -msgid "Change" -msgstr "変更" +msgid "Created commit %s: %s" +msgstr "コミット %s を作æˆã—ã¾ã—ãŸ: %s" -#: lib/option.tcl:230 -msgid "Spelling Dictionary:" -msgstr "スペルãƒã‚§ãƒƒã‚¯è¾žæ›¸" +#: lib/browser.tcl:17 +msgid "Starting..." +msgstr "èµ·å‹•ä¸â€¦" -#: lib/option.tcl:254 -msgid "Change Font" -msgstr "フォントを変更" +#: lib/browser.tcl:27 +msgid "File Browser" +msgstr "ファイル・ブラウザ" -#: lib/option.tcl:258 +#: lib/browser.tcl:132 lib/browser.tcl:149 #, tcl-format -msgid "Choose %s" -msgstr "%s ã‚’é¸æŠž" - -#: lib/option.tcl:264 -msgid "pt." -msgstr "ãƒã‚¤ãƒ³ãƒˆ" - -#: lib/option.tcl:278 -msgid "Preferences" -msgstr "è¨å®š" - -#: lib/option.tcl:314 -msgid "Failed to completely save options:" -msgstr "完全ã«ã‚ªãƒ—ションをä¿å˜ã§ãã¾ã›ã‚“:" - -#: lib/remote.tcl:163 -msgid "Remove Remote" -msgstr "リモートを削除" - -#: lib/remote.tcl:168 -msgid "Prune from" -msgstr "ã‹ã‚‰åˆˆè¾¼ã‚€â€¦" +msgid "Loading %s..." +msgstr "%s ã‚’ãƒãƒ¼ãƒ‰ä¸â€¦" -#: lib/remote.tcl:173 -msgid "Fetch from" -msgstr "å–å¾—å…ƒ" +#: lib/browser.tcl:193 +msgid "[Up To Parent]" +msgstr "[上ä½ãƒ•ã‚©ãƒ«ãƒ€ã¸]" -#: lib/remote.tcl:215 -msgid "Push to" -msgstr "プッシュ先" +#: lib/browser.tcl:275 lib/browser.tcl:282 +msgid "Browse Branch Files" +msgstr "ç¾åœ¨ã®ãƒ–ランãƒã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’見る" -#: lib/remote_add.tcl:19 +#: lib/remote_add.tcl:20 msgid "Add Remote" msgstr "ãƒªãƒ¢ãƒ¼ãƒˆã‚’è¿½åŠ " -#: lib/remote_add.tcl:24 +#: lib/remote_add.tcl:25 msgid "Add New Remote" msgstr "リモートを新è¦ã«è¿½åŠ " -#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36 -msgid "Add" -msgstr "è¿½åŠ " - -#: lib/remote_add.tcl:37 +#: lib/remote_add.tcl:39 msgid "Remote Details" msgstr "リモートã®è©³ç´°" @@ -2134,155 +2547,104 @@ msgstr "リモートã®è©³ç´°" msgid "Location:" msgstr "å ´æ‰€:" -#: lib/remote_add.tcl:62 +#: lib/remote_add.tcl:60 msgid "Further Action" msgstr "ãã®ä»–ã®å‹•ä½œ" -#: lib/remote_add.tcl:65 +#: lib/remote_add.tcl:63 msgid "Fetch Immediately" msgstr "å³åº§ã«å–å¾—" -#: lib/remote_add.tcl:71 +#: lib/remote_add.tcl:69 msgid "Initialize Remote Repository and Push" msgstr "リモートレãƒã‚¸ãƒˆãƒªã‚’åˆæœŸåŒ–ã—ã¦ãƒ—ッシュ" -#: lib/remote_add.tcl:77 +#: lib/remote_add.tcl:75 msgid "Do Nothing Else Now" msgstr "何もã—ãªã„" -#: lib/remote_add.tcl:101 +#: lib/remote_add.tcl:100 msgid "Please supply a remote name." msgstr "リモートåを指定ã—ã¦ä¸‹ã•ã„。" -#: lib/remote_add.tcl:114 +#: lib/remote_add.tcl:113 #, tcl-format msgid "'%s' is not an acceptable remote name." msgstr "'%s' ã¯ãƒªãƒ¢ãƒ¼ãƒˆåã«ä½¿ãˆã¾ã›ã‚“。" -#: lib/remote_add.tcl:125 +#: lib/remote_add.tcl:124 #, tcl-format msgid "Failed to add remote '%s' of location '%s'." msgstr "å ´æ‰€ '%2$s' ã®ãƒªãƒ¢ãƒ¼ãƒˆ '%1$s'ã®åå‰å¤‰æ›´ã«å¤±æ•—ã—ã¾ã—ãŸã€‚" -#: lib/remote_add.tcl:133 lib/transport.tcl:6 -#, tcl-format -msgid "fetch %s" -msgstr "%s ã‚’å–å¾—" - -#: lib/remote_add.tcl:134 +#: lib/remote_add.tcl:133 #, tcl-format msgid "Fetching the %s" msgstr "%s ã‹ã‚‰ãƒ•ã‚§ãƒƒãƒã—ã¦ã„ã¾ã™" -#: lib/remote_add.tcl:157 +#: lib/remote_add.tcl:156 #, tcl-format msgid "Do not know how to initialize repository at location '%s'." msgstr "リãƒã‚¸ãƒˆãƒª '%s' ã‚’åˆæœŸåŒ–ã§ãã¾ã›ã‚“。" -#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63 -#: lib/transport.tcl:81 -#, tcl-format -msgid "push %s" -msgstr "%s をプッシュ" - -#: lib/remote_add.tcl:164 +#: lib/remote_add.tcl:163 #, tcl-format msgid "Setting up the %s (at %s)" msgstr "%2$s ã«ã‚ã‚‹ %1$s をセットアップã—ã¾ã™" -#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 -msgid "Delete Branch Remotely" -msgstr "é éš”ã§ãƒ–ランãƒå‰Šé™¤" - -#: lib/remote_branch_delete.tcl:47 -msgid "From Repository" -msgstr "å…ƒã®ãƒªãƒã‚¸ãƒˆãƒª" +#: lib/line.tcl:17 +msgid "Goto Line:" +msgstr "行番å·" -#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134 -msgid "Remote:" -msgstr "リモート:" +#: lib/line.tcl:23 +msgid "Go" +msgstr "移動" -#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149 -msgid "Arbitrary Location:" -msgstr "ä»»æ„ã®ä½ç½®:" +#: lib/branch_create.tcl:23 +msgid "Create Branch" +msgstr "ブランãƒã‚’作æˆ" -#: lib/remote_branch_delete.tcl:84 -msgid "Branches" -msgstr "ブランãƒ" +#: lib/branch_create.tcl:28 +msgid "Create New Branch" +msgstr "ブランãƒã‚’æ–°è¦ä½œæˆ" -#: lib/remote_branch_delete.tcl:109 -msgid "Delete Only If" -msgstr "æ¡ä»¶ä»˜ã§å‰Šé™¤" +#: lib/branch_create.tcl:42 +msgid "Branch Name" +msgstr "ブランãƒå" -#: lib/remote_branch_delete.tcl:111 -msgid "Merged Into:" -msgstr "マージ先:" +#: lib/branch_create.tcl:57 +msgid "Match Tracking Branch Name" +msgstr "トラッã‚ング・ブランãƒåã‚’åˆã‚ã›ã‚‹" -#: lib/remote_branch_delete.tcl:152 -msgid "A branch is required for 'Merged Into'." -msgstr "'マージ先' ã«ã¯ãƒ–ランãƒãŒå¿…è¦ã§ã™ã€‚" +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "åˆæœŸãƒªãƒ“ジョン" -#: lib/remote_branch_delete.tcl:184 -#, tcl-format -msgid "" -"The following branches are not completely merged into %s:\n" -"\n" -" - %s" -msgstr "" -"以下ã®ãƒ–ランãƒã¯ %s ã«å®Œå…¨ã«ãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã„ã¾ã›ã‚“:\n" -"\n" -" - %s" +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "æ—¢å˜ã®ãƒ–ランãƒã‚’æ›´æ–°:" -#: lib/remote_branch_delete.tcl:189 -#, tcl-format -msgid "" -"One or more of the merge tests failed because you have not fetched the " -"necessary commits. Try fetching from %s first." -msgstr "" -"å¿…è¦ãªã‚³ãƒŸãƒƒãƒˆãŒä¸è¶³ã—ã¦ã„ã‚‹ãŸã‚ã«ã€ãƒžãƒ¼ã‚¸æ¤œæŸ»ãŒå¤±æ•—ã—ã¾ã—ãŸã€‚ã¾ãš %s ã‹ã‚‰" -"フェッãƒã—ã¦ä¸‹ã•ã„。" +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "ã„ã„ãˆ" -#: lib/remote_branch_delete.tcl:207 -msgid "Please select one or more branches to delete." -msgstr "削除ã™ã‚‹ãƒ–ランãƒã‚’é¸æŠžã—ã¦ä¸‹ã•ã„。" +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "æ—©é€ã‚Šã®ã¿" -#: lib/remote_branch_delete.tcl:226 -#, tcl-format -msgid "Deleting branches from %s" -msgstr "%s ã‹ã‚‰ãƒ–ランãƒã‚’削除ã—ã¦ã„ã¾ã™ã€‚" +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "作æˆã—ã¦ã™ããƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ" -#: lib/remote_branch_delete.tcl:292 -msgid "No repository selected." -msgstr "リãƒã‚¸ãƒˆãƒªãŒé¸æŠžã•ã‚Œã¦ã„ã¾ã›ã‚“。" +#: lib/branch_create.tcl:132 +msgid "Please select a tracking branch." +msgstr "トラッã‚ング・ブランãƒã‚’é¸æŠžã—ã¦ä¸‹ã•ã„。" -#: lib/remote_branch_delete.tcl:297 +#: lib/branch_create.tcl:141 #, tcl-format -msgid "Scanning %s..." -msgstr "%s をスã‚ャンã—ã¦ã„ã¾ã™â€¦" - -#: lib/search.tcl:21 -msgid "Find:" -msgstr "検索:" - -#: lib/search.tcl:23 -msgid "Next" -msgstr "次" - -#: lib/search.tcl:24 -msgid "Prev" -msgstr "å‰" - -#: lib/search.tcl:25 -msgid "Case-Sensitive" -msgstr "大文å—å°æ–‡å—を区別" - -#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 -msgid "Cannot write shortcut:" -msgstr "ショートカットãŒæ›¸ã‘ã¾ã›ã‚“:" - -#: lib/shortcut.tcl:137 -msgid "Cannot write icon:" -msgstr "アイコンãŒæ›¸ã‘ã¾ã›ã‚“:" +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "" +"トラッã‚ング・ブランム%s ã¯ãƒªãƒ¢ãƒ¼ãƒˆãƒªãƒã‚¸ãƒˆãƒªã®ãƒ–ランãƒã§ã¯ã‚ã‚Šã¾ã›ã‚“。" #: lib/spellcheck.tcl:57 msgid "Unsupported spell checker" @@ -2320,236 +2682,3 @@ msgstr "スペルãƒã‚§ãƒƒã‚«ãƒ¼ãŒäºˆæƒ³å¤–ã® EOF ã‚’è¿”ã—ã¾ã—ãŸ" #: lib/spellcheck.tcl:392 msgid "Spell Checker Failed" msgstr "スペルãƒã‚§ãƒƒã‚¯å¤±æ•—" - -#: lib/sshkey.tcl:31 -msgid "No keys found." -msgstr "ã‚ーãŒã‚ã‚Šã¾ã›ã‚“。" - -#: lib/sshkey.tcl:34 -#, tcl-format -msgid "Found a public key in: %s" -msgstr "公開éµãŒã‚ã‚Šã¾ã—ãŸ: %s" - -#: lib/sshkey.tcl:40 -msgid "Generate Key" -msgstr "éµã‚’生æˆ" - -#: lib/sshkey.tcl:56 -msgid "Copy To Clipboard" -msgstr "クリップボードã«ã‚³ãƒ”ー" - -#: lib/sshkey.tcl:70 -msgid "Your OpenSSH Public Key" -msgstr "ã‚ãªãŸã® OpenSSH 公開éµ" - -#: lib/sshkey.tcl:78 -msgid "Generating..." -msgstr "生æˆä¸..." - -#: lib/sshkey.tcl:84 -#, tcl-format -msgid "" -"Could not start ssh-keygen:\n" -"\n" -"%s" -msgstr "" -"ssh-keygen ã‚’èµ·å‹•ã§ãã¾ã›ã‚“:\n" -"\n" -"%s" - -#: lib/sshkey.tcl:111 -msgid "Generation failed." -msgstr "生æˆã«å¤±æ•—ã—ã¾ã—ãŸã€‚" - -#: lib/sshkey.tcl:118 -msgid "Generation succeeded, but no keys found." -msgstr "生æˆã«ã¯æˆåŠŸã—ã¾ã—ãŸãŒã€éµãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。" - -#: lib/sshkey.tcl:121 -#, tcl-format -msgid "Your key is in: %s" -msgstr "ã‚ãªãŸã®éµã¯ %s ã«ã‚ã‚Šã¾ã™" - -#: lib/status_bar.tcl:83 -#, tcl-format -msgid "%s ... %*i of %*i %s (%3i%%)" -msgstr "%1$s ... %4$*i %6$s ä¸ã® %2$*i (%7$3i%%)" - -#: lib/tools.tcl:75 -#, tcl-format -msgid "Running %s requires a selected file." -msgstr "ファイルをé¸æŠžã—ã¦ã‹ã‚‰ %s ã‚’èµ·å‹•ã—ã¦ãã ã•ã„。" - -#: lib/tools.tcl:90 -#, tcl-format -msgid "Are you sure you want to run %s?" -msgstr "本当㫠%s ã‚’èµ·å‹•ã—ã¾ã™ã‹?" - -#: lib/tools.tcl:110 -#, tcl-format -msgid "Tool: %s" -msgstr "ツール: %s" - -#: lib/tools.tcl:111 -#, tcl-format -msgid "Running: %s" -msgstr "実行ä¸: %s" - -#: lib/tools.tcl:149 -#, tcl-format -msgid "Tool completed successfully: %s" -msgstr "ツールãŒå®Œäº†ã—ã¾ã—ãŸ: %s" - -#: lib/tools.tcl:151 -#, tcl-format -msgid "Tool failed: %s" -msgstr "ツールãŒå¤±æ•—ã—ã¾ã—ãŸ: %s" - -#: lib/tools_dlg.tcl:22 -msgid "Add Tool" -msgstr "ツールã®è¿½åŠ " - -#: lib/tools_dlg.tcl:28 -msgid "Add New Tool Command" -msgstr "æ–°è¦ãƒ„ールコマンドã®è¿½åŠ " - -#: lib/tools_dlg.tcl:33 -msgid "Add globally" -msgstr "全体ã«è¿½åŠ " - -#: lib/tools_dlg.tcl:45 -msgid "Tool Details" -msgstr "ツールã®è©³ç´°" - -#: lib/tools_dlg.tcl:48 -msgid "Use '/' separators to create a submenu tree:" -msgstr "'/' ã§ã‚µãƒ–メニューを区切りã¾ã™:" - -#: lib/tools_dlg.tcl:61 -msgid "Command:" -msgstr "コマンド:" - -#: lib/tools_dlg.tcl:74 -msgid "Show a dialog before running" -msgstr "èµ·å‹•ã™ã‚‹å‰ã«ãƒ€ã‚¤ã‚¢ãƒã‚°ã‚’表示" - -#: lib/tools_dlg.tcl:80 -msgid "Ask the user to select a revision (sets $REVISION)" -msgstr "ユーザã«ã‚³ãƒŸãƒƒãƒˆã‚’一ã¤é¸ã°ã›ã‚‹ ($REVISION ã«ã‚»ãƒƒãƒˆã—ã¾ã™)" - -#: lib/tools_dlg.tcl:85 -msgid "Ask the user for additional arguments (sets $ARGS)" -msgstr "ユーザã«ä»–ã®å¼•æ•°ã‚’è¿½åŠ ã•ã›ã‚‹ ($ARGS ã«ã‚»ãƒƒãƒˆã—ã¾ã™)" - -#: lib/tools_dlg.tcl:92 -msgid "Don't show the command output window" -msgstr "コマンドã‹ã‚‰ã®å‡ºåŠ›ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã‚’見ã›ãªã„" - -#: lib/tools_dlg.tcl:97 -msgid "Run only if a diff is selected ($FILENAME not empty)" -msgstr "パッãƒãŒé¸ã°ã‚Œã¦ã„ã‚‹ã¨ãã ã‘å‹•ã‹ã™($FILENAME ãŒç©ºã§ãªã„)" - -#: lib/tools_dlg.tcl:121 -msgid "Please supply a name for the tool." -msgstr "ツールåを指定ã—ã¦ä¸‹ã•ã„。" - -#: lib/tools_dlg.tcl:129 -#, tcl-format -msgid "Tool '%s' already exists." -msgstr "ツール '%s' ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" - -#: lib/tools_dlg.tcl:151 -#, tcl-format -msgid "" -"Could not add tool:\n" -"%s" -msgstr "" -"ãƒ„ãƒ¼ãƒ«ã‚’è¿½åŠ ã§ãã¾ã›ã‚“:\n" -"%s" - -#: lib/tools_dlg.tcl:190 -msgid "Remove Tool" -msgstr "ツールã®å‰Šé™¤" - -#: lib/tools_dlg.tcl:196 -msgid "Remove Tool Commands" -msgstr "ツールコマンドã®å‰Šé™¤" - -#: lib/tools_dlg.tcl:200 -msgid "Remove" -msgstr "削除" - -#: lib/tools_dlg.tcl:236 -msgid "(Blue denotes repository-local tools)" -msgstr "(é’色ã¯ãƒãƒ¼ã‚«ãƒ«ãƒ¬ãƒã‚¸ãƒˆãƒªã®ãƒ„ールã§ã™)" - -#: lib/tools_dlg.tcl:297 -#, tcl-format -msgid "Run Command: %s" -msgstr "コマンドを起動: %s" - -#: lib/tools_dlg.tcl:311 -msgid "Arguments" -msgstr "引数" - -#: lib/tools_dlg.tcl:348 -msgid "OK" -msgstr "OK" - -#: lib/transport.tcl:7 -#, tcl-format -msgid "Fetching new changes from %s" -msgstr "%s ã‹ã‚‰æ–°ã—ã„変更をフェッãƒã—ã¦ã„ã¾ã™" - -#: lib/transport.tcl:18 -#, tcl-format -msgid "remote prune %s" -msgstr "é 隔刈込 %s" - -#: lib/transport.tcl:19 -#, tcl-format -msgid "Pruning tracking branches deleted from %s" -msgstr "%s ã‹ã‚‰å‰Šé™¤ã•ã‚ŒãŸãƒˆãƒ©ãƒƒã‚ング・ブランãƒã‚’刈ã£ã¦ã„ã¾ã™" - -#: lib/transport.tcl:26 -#, tcl-format -msgid "Pushing changes to %s" -msgstr "%s ã¸å¤‰æ›´ã‚’プッシュã—ã¦ã„ã¾ã™" - -#: lib/transport.tcl:64 -#, tcl-format -msgid "Mirroring to %s" -msgstr "%s ã¸ãƒŸãƒ©ãƒ¼ã—ã¦ã„ã¾ã™" - -#: lib/transport.tcl:82 -#, tcl-format -msgid "Pushing %s %s to %s" -msgstr "%3$s 㸠%1$s %2$s をプッシュã—ã¦ã„ã¾ã™" - -#: lib/transport.tcl:100 -msgid "Push Branches" -msgstr "ブランãƒã‚’プッシュ" - -#: lib/transport.tcl:114 -msgid "Source Branches" -msgstr "å…ƒã®ãƒ–ランãƒ" - -#: lib/transport.tcl:131 -msgid "Destination Repository" -msgstr "é€ã‚Šå…ˆãƒªãƒã‚¸ãƒˆãƒª" - -#: lib/transport.tcl:169 -msgid "Transfer Options" -msgstr "通信オプション" - -#: lib/transport.tcl:171 -msgid "Force overwrite existing branch (may discard changes)" -msgstr "æ—¢å˜ãƒ–ランãƒã‚’上書ã(å¤‰æ›´ã‚’ç ´æ£„ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™)" - -#: lib/transport.tcl:175 -msgid "Use thin pack (for slow network connections)" -msgstr "Thin Pack を使ã†ï¼ˆé…ã„ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯æŽ¥ç¶šï¼‰" - -#: lib/transport.tcl:179 -msgid "Include tags" -msgstr "ã‚¿ã‚°ã‚’å«ã‚ã‚‹" diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh index 308eafd1d3..bcf0d92ec2 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -30,7 +30,7 @@ do esac done -# Reject if this is not an Octopus -- resolve should be used instead. +# Reject if this is not an octopus -- resolve should be used instead. case "$remotes" in ?*' '?*) ;; @@ -59,7 +59,7 @@ do # conflicts. Last round failed and we still had # a head to merge. gettextln "Automated merge did not work." - gettextln "Should not be doing an Octopus." + gettextln "Should not be doing an octopus." exit 2 esac diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 7e558b068c..5e474e4a85 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -1082,7 +1082,7 @@ If they are meant to go into a new commit, run: git commit \$gpg_sign_opt_quoted -In both case, once you're done, continue with: +In both cases, once you're done, continue with: git rebase --continue ")" @@ -1141,9 +1141,6 @@ To continue rebase after editing, run: ;; esac -git var GIT_COMMITTER_IDENT >/dev/null || - die "$(gettext "You need to set your committer info first")" - comment_for_reflog start if test ! -z "$switch_to" diff --git a/git-submodule.sh b/git-submodule.sh index b57f87de65..a1cc71b521 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -576,7 +576,7 @@ cmd_update() ${wt_prefix:+--prefix "$wt_prefix"} \ ${prefix:+--recursive-prefix "$prefix"} \ ${update:+--update "$update"} \ - ${reference:+--reference "$reference"} \ + ${reference:+"$reference"} \ ${depth:+--depth "$depth"} \ ${recommend_shallow:+"$recommend_shallow"} \ ${jobs:+$jobs} \ @@ -444,7 +444,7 @@ static struct cmd_struct commands[] = { { "pack-objects", cmd_pack_objects, RUN_SETUP }, { "pack-redundant", cmd_pack_redundant, RUN_SETUP }, { "pack-refs", cmd_pack_refs, RUN_SETUP }, - { "patch-id", cmd_patch_id }, + { "patch-id", cmd_patch_id, RUN_SETUP_GENTLY }, { "pickaxe", cmd_blame, RUN_SETUP }, { "prune", cmd_prune, RUN_SETUP }, { "prune-packed", cmd_prune_packed, RUN_SETUP }, @@ -522,21 +522,34 @@ static void strip_extension(const char **argv) static void handle_builtin(int argc, const char **argv) { + struct argv_array args = ARGV_ARRAY_INIT; const char *cmd; struct cmd_struct *builtin; strip_extension(argv); cmd = argv[0]; - /* Turn "git cmd --help" into "git help cmd" */ + /* Turn "git cmd --help" into "git help --exclude-guides cmd" */ if (argc > 1 && !strcmp(argv[1], "--help")) { + int i; + argv[1] = argv[0]; argv[0] = cmd = "help"; + + for (i = 0; i < argc; i++) { + argv_array_push(&args, argv[i]); + if (!i) + argv_array_push(&args, "--exclude-guides"); + } + + argc++; + argv = args.argv; } builtin = get_builtin(cmd); if (builtin) exit(run_builtin(builtin, argc, argv)); + argv_array_clear(&args); } static void execv_dashed_external(const char **argv) @@ -2,7 +2,6 @@ #include "commit.h" #include "color.h" #include "graph.h" -#include "diff.h" #include "revision.h" /* Internal API */ @@ -28,8 +27,15 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb); * responsible for printing this line's graph (perhaps via * graph_show_commit() or graph_show_oneline()) before calling * graph_show_strbuf(). + * + * Note that unlike some other graph display functions, you must pass the file + * handle directly. It is assumed that this is the same file handle as the + * file specified by the graph diff options. This is necessary so that + * graph_show_strbuf can be called even with a NULL graph. */ -static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb); +static void graph_show_strbuf(struct git_graph *graph, + FILE *file, + struct strbuf const *sb); /* * TODO: @@ -59,6 +65,17 @@ enum graph_state { GRAPH_COLLAPSING }; +static void graph_show_line_prefix(const struct diff_options *diffopt) +{ + if (!diffopt || !diffopt->line_prefix) + return; + + fwrite(diffopt->line_prefix, + sizeof(char), + diffopt->line_prefix_length, + diffopt->file); +} + static const char **column_colors; static unsigned short column_colors_max; @@ -195,14 +212,28 @@ static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void static struct strbuf msgbuf = STRBUF_INIT; assert(opt); - assert(graph); - opt->output_prefix_length = graph->width; strbuf_reset(&msgbuf); - graph_padding_line(graph, &msgbuf); + if (opt->line_prefix) + strbuf_add(&msgbuf, opt->line_prefix, + opt->line_prefix_length); + if (graph) + graph_padding_line(graph, &msgbuf); return &msgbuf; } +static const struct diff_options *default_diffopt; + +void graph_setup_line_prefix(struct diff_options *diffopt) +{ + default_diffopt = diffopt; + + /* setup an output prefix callback if necessary */ + if (diffopt && !diffopt->output_prefix) + diffopt->output_prefix = diff_output_prefix_callback; +} + + struct git_graph *graph_init(struct rev_info *opt) { struct git_graph *graph = xmalloc(sizeof(struct git_graph)); @@ -245,7 +276,6 @@ struct git_graph *graph_init(struct rev_info *opt) */ opt->diffopt.output_prefix = diff_output_prefix_callback; opt->diffopt.output_prefix_data = graph; - opt->diffopt.output_prefix_length = 0; return graph; } @@ -1185,6 +1215,8 @@ void graph_show_commit(struct git_graph *graph) struct strbuf msgbuf = STRBUF_INIT; int shown_commit_line = 0; + graph_show_line_prefix(default_diffopt); + if (!graph) return; @@ -1202,8 +1234,10 @@ void graph_show_commit(struct git_graph *graph) shown_commit_line = graph_next_line(graph, &msgbuf); fwrite(msgbuf.buf, sizeof(char), msgbuf.len, graph->revs->diffopt.file); - if (!shown_commit_line) + if (!shown_commit_line) { putc('\n', graph->revs->diffopt.file); + graph_show_line_prefix(&graph->revs->diffopt); + } strbuf_setlen(&msgbuf, 0); } @@ -1214,6 +1248,8 @@ void graph_show_oneline(struct git_graph *graph) { struct strbuf msgbuf = STRBUF_INIT; + graph_show_line_prefix(default_diffopt); + if (!graph) return; @@ -1226,6 +1262,8 @@ void graph_show_padding(struct git_graph *graph) { struct strbuf msgbuf = STRBUF_INIT; + graph_show_line_prefix(default_diffopt); + if (!graph) return; @@ -1239,6 +1277,8 @@ int graph_show_remainder(struct git_graph *graph) struct strbuf msgbuf = STRBUF_INIT; int shown = 0; + graph_show_line_prefix(default_diffopt); + if (!graph) return 0; @@ -1252,27 +1292,24 @@ int graph_show_remainder(struct git_graph *graph) strbuf_setlen(&msgbuf, 0); shown = 1; - if (!graph_is_commit_finished(graph)) + if (!graph_is_commit_finished(graph)) { putc('\n', graph->revs->diffopt.file); - else + graph_show_line_prefix(&graph->revs->diffopt); + } else { break; + } } strbuf_release(&msgbuf); return shown; } - -static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb) +static void graph_show_strbuf(struct git_graph *graph, + FILE *file, + struct strbuf const *sb) { char *p; - if (!graph) { - fwrite(sb->buf, sizeof(char), sb->len, - graph->revs->diffopt.file); - return; - } - /* * Print the strbuf line by line, * and display the graph info before each line but the first. @@ -1287,7 +1324,7 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb) } else { len = (sb->buf + sb->len) - p; } - fwrite(p, sizeof(char), len, graph->revs->diffopt.file); + fwrite(p, sizeof(char), len, file); if (next_p && *next_p != '\0') graph_show_oneline(graph); p = next_p; @@ -1295,29 +1332,20 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb) } void graph_show_commit_msg(struct git_graph *graph, + FILE *file, struct strbuf const *sb) { int newline_terminated; - if (!graph) { - /* - * If there's no graph, just print the message buffer. - * - * The message buffer for CMIT_FMT_ONELINE and - * CMIT_FMT_USERFORMAT are already missing a terminating - * newline. All of the other formats should have it. - */ - fwrite(sb->buf, sizeof(char), sb->len, - graph->revs->diffopt.file); - return; - } - - newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n'); - /* * Show the commit message */ - graph_show_strbuf(graph, sb); + graph_show_strbuf(graph, file, sb); + + if (!graph) + return; + + newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n'); /* * If there is more output needed for this commit, show it now @@ -1329,7 +1357,7 @@ void graph_show_commit_msg(struct git_graph *graph, * new line. */ if (!newline_terminated) - putc('\n', graph->revs->diffopt.file); + putc('\n', file); graph_show_remainder(graph); @@ -1337,6 +1365,6 @@ void graph_show_commit_msg(struct git_graph *graph, * If sb ends with a newline, our output should too. */ if (newline_terminated) - putc('\n', graph->revs->diffopt.file); + putc('\n', file); } } @@ -1,10 +1,23 @@ #ifndef GRAPH_H #define GRAPH_H +#include "diff.h" /* A graph is a pointer to this opaque structure */ struct git_graph; /* + * Called to setup global display of line_prefix diff option. + * + * Passed a diff_options structure which indicates the line_prefix and the + * file to output the prefix to. This is sort of a hack used so that the + * line_prefix will be honored by all flows which also honor "--graph" + * regardless of whether a graph has actually been setup. The normal graph + * flow will honor the exact diff_options passed, but a NULL graph will cause + * display of a line_prefix to stdout. + */ +void graph_setup_line_prefix(struct diff_options *diffopt); + +/* * Set up a custom scheme for column colors. * * The default column color scheme inserts ANSI color escapes to colorize @@ -113,7 +126,14 @@ int graph_show_remainder(struct git_graph *graph); * missing a terminating newline (including if it is empty), the output * printed by graph_show_commit_msg() will also be missing a terminating * newline. + * + * Note that unlike some other graph display functions, you must pass the file + * handle directly. It is assumed that this is the same file handle as the + * file specified by the graph diff options. This is necessary so that + * graph_show_commit_msg can be called even with a NULL graph. */ -void graph_show_commit_msg(struct git_graph *graph, struct strbuf const *sb); +void graph_show_commit_msg(struct git_graph *graph, + FILE *file, + struct strbuf const *sb); #endif /* GRAPH_H */ @@ -39,16 +39,8 @@ int get_sha1_hex(const char *hex, unsigned char *sha1) { int i; for (i = 0; i < GIT_SHA1_RAWSZ; i++) { - unsigned int val; - /* - * hex[1]=='\0' is caught when val is checked below, - * but if hex[0] is NUL we have to avoid reading - * past the end of the string: - */ - if (!hex[0]) - return -1; - val = (hexval(hex[0]) << 4) | hexval(hex[1]); - if (val & ~0xff) + int val = hex2chr(hex); + if (val < 0) return -1; *sha1++ = val; hex += 2; @@ -201,6 +201,13 @@ static void finish_active_slot(struct active_request_slot *slot) slot->callback_func(slot->callback_data); } +static void xmulti_remove_handle(struct active_request_slot *slot) +{ +#ifdef USE_CURL_MULTI + curl_multi_remove_handle(curlm, slot->curl); +#endif +} + #ifdef USE_CURL_MULTI static void process_curl_messages(void) { @@ -216,7 +223,7 @@ static void process_curl_messages(void) slot->curl != curl_message->easy_handle) slot = slot->next; if (slot != NULL) { - curl_multi_remove_handle(curlm, slot->curl); + xmulti_remove_handle(slot); slot->curl_result = curl_result; finish_active_slot(slot); } else { @@ -723,7 +730,7 @@ static CURL *get_curl_handle(void) * precedence here, as in CURL. */ if (!curl_http_proxy) { - if (!strcmp(http_auth.protocol, "https")) { + if (http_auth.protocol && !strcmp(http_auth.protocol, "https")) { var_override(&curl_http_proxy, getenv("HTTPS_PROXY")); var_override(&curl_http_proxy, getenv("https_proxy")); } else { @@ -881,9 +888,7 @@ void http_cleanup(void) while (slot != NULL) { struct active_request_slot *next = slot->next; if (slot->curl != NULL) { -#ifdef USE_CURL_MULTI - curl_multi_remove_handle(curlm, slot->curl); -#endif + xmulti_remove_handle(slot); curl_easy_cleanup(slot->curl); } free(slot); @@ -1022,6 +1027,8 @@ int start_active_slot(struct active_request_slot *slot) if (curlm_result != CURLM_OK && curlm_result != CURLM_CALL_MULTI_PERFORM) { + warning("curl_multi_add_handle failed: %s", + curl_multi_strerror(curlm_result)); active_requests--; slot->in_use = 0; return 0; @@ -1161,13 +1168,13 @@ void run_active_slot(struct active_request_slot *slot) static void release_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); - if (slot->curl && curl_session_count > min_curl_sessions) { -#ifdef USE_CURL_MULTI - curl_multi_remove_handle(curlm, slot->curl); -#endif - curl_easy_cleanup(slot->curl); - slot->curl = NULL; - curl_session_count--; + if (slot->curl) { + xmulti_remove_handle(slot); + if (curl_session_count > min_curl_sessions) { + curl_easy_cleanup(slot->curl); + slot->curl = NULL; + curl_session_count--; + } } #ifdef USE_CURL_MULTI fill_active_slots(); diff --git a/log-tree.c b/log-tree.c index bfb735c845..8c2415747a 100644 --- a/log-tree.c +++ b/log-tree.c @@ -715,10 +715,7 @@ void show_log(struct rev_info *opt) else opt->missing_newline = 0; - if (opt->graph) - graph_show_commit_msg(opt->graph, &msgbuf); - else - fwrite(msgbuf.buf, sizeof(char), msgbuf.len, opt->diffopt.file); + graph_show_commit_msg(opt->graph, opt->diffopt.file, &msgbuf); if (opt->use_terminator && !commit_format_is_empty(opt->commit_format)) { if (!opt->missing_newline) graph_show_padding(opt->graph); diff --git a/merge-recursive.c b/merge-recursive.c index e34912683c..5200d5ccf8 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -206,7 +206,7 @@ static void output_commit_title(struct merge_options *o, struct commit *commit) find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV)); if (parse_commit(commit) != 0) - strbuf_addf(&o->obuf, _("(bad commit)\n")); + strbuf_addstr(&o->obuf, _("(bad commit)\n")); else { const char *title; const char *msg = get_commit_buffer(commit, NULL); @@ -382,7 +382,7 @@ static struct string_list *get_unmerged(void) } e = item->util; e->stages[ce_stage(ce)].mode = ce->ce_mode; - hashcpy(e->stages[ce_stage(ce)].oid.hash, ce->sha1); + oidcpy(&e->stages[ce_stage(ce)].oid, &ce->oid); } return unmerged; @@ -910,9 +910,9 @@ static int merge_3way(struct merge_options *o, name2 = mkpathdup("%s", branch2); } - read_mmblob(&orig, one->oid.hash); - read_mmblob(&src1, a->oid.hash); - read_mmblob(&src2, b->oid.hash); + read_mmblob(&orig, &one->oid); + read_mmblob(&src1, &a->oid); + read_mmblob(&src2, &b->oid); merge_status = ll_merge(result_buf, a->path, &orig, base_name, &src1, name1, &src2, name2, &ll_opts); @@ -57,7 +57,8 @@ int checkout_fast_forward(const unsigned char *head, refresh_cache(REFRESH_QUIET); - hold_locked_index(lock_file, 1); + if (hold_locked_index(lock_file, 0) < 0) + return -1; memset(&trees, 0, sizeof(trees)); memset(&opts, 0, sizeof(opts)); @@ -90,7 +91,9 @@ int checkout_fast_forward(const unsigned char *head, } if (unpack_trees(nr_trees, t, &opts)) return -1; - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) - die(_("unable to write new index file")); + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) { + rollback_lock_file(lock_file); + return error(_("unable to write new index file")); + } return 0; } diff --git a/notes-merge.c b/notes-merge.c index 3bbeb863f8..5998605acc 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -12,7 +12,7 @@ #include "notes-utils.h" struct notes_merge_pair { - unsigned char obj[20], base[20], local[20], remote[20]; + struct object_id obj, base, local, remote; }; void init_notes_merge_options(struct notes_merge_options *o) @@ -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); + cmp = hashcmp(obj, list[i].obj.hash); if (!cmp) /* obj belongs @ i */ break; else if (cmp < 0 && prev_cmp <= 0) /* obj belongs < i */ @@ -108,9 +108,10 @@ static struct notes_merge_pair *find_notes_merge_pair_pos( return list + i; } -static unsigned char uninitialized[20] = +static struct object_id uninitialized = { "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \ - "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +}; static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, const unsigned char *base, @@ -149,25 +150,25 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, mp = find_notes_merge_pair_pos(changes, len, obj, 1, &occupied); if (occupied) { /* We've found an addition/deletion pair */ - assert(!hashcmp(mp->obj, obj)); + assert(!hashcmp(mp->obj.hash, obj)); if (is_null_oid(&p->one->oid)) { /* addition */ - assert(is_null_sha1(mp->remote)); - hashcpy(mp->remote, p->two->oid.hash); + assert(is_null_oid(&mp->remote)); + oidcpy(&mp->remote, &p->two->oid); } else if (is_null_oid(&p->two->oid)) { /* deletion */ - assert(is_null_sha1(mp->base)); - hashcpy(mp->base, p->one->oid.hash); + assert(is_null_oid(&mp->base)); + oidcpy(&mp->base, &p->one->oid); } else assert(!"Invalid existing change recorded"); } else { - hashcpy(mp->obj, obj); - hashcpy(mp->base, p->one->oid.hash); - hashcpy(mp->local, uninitialized); - hashcpy(mp->remote, p->two->oid.hash); + hashcpy(mp->obj.hash, obj); + oidcpy(&mp->base, &p->one->oid); + oidcpy(&mp->local, &uninitialized); + oidcpy(&mp->remote, &p->two->oid); len++; } trace_printf("\t\tStored remote change for %s: %.7s -> %.7s\n", - sha1_to_hex(mp->obj), sha1_to_hex(mp->base), - sha1_to_hex(mp->remote)); + oid_to_hex(&mp->obj), oid_to_hex(&mp->base), + oid_to_hex(&mp->remote)); } diff_flush(&opt); clear_pathspec(&opt.pathspec); @@ -216,7 +217,7 @@ static void diff_tree_local(struct notes_merge_options *o, continue; } - assert(!hashcmp(mp->obj, obj)); + assert(!hashcmp(mp->obj.hash, obj)); if (is_null_oid(&p->two->oid)) { /* deletion */ /* * Either this is a true deletion (1), or it is part @@ -227,8 +228,8 @@ static void diff_tree_local(struct notes_merge_options *o, * (3) mp->local is uninitialized; set it to null_sha1 * (will be overwritten by following addition) */ - if (!hashcmp(mp->local, uninitialized)) - hashclr(mp->local); + if (!oidcmp(&mp->local, &uninitialized)) + oidclr(&mp->local); } else if (is_null_oid(&p->one->oid)) { /* addition */ /* * Either this is a true addition (1), or it is part @@ -238,22 +239,22 @@ static void diff_tree_local(struct notes_merge_options *o, * (2) mp->local is uninitialized; set to p->two->sha1 * (3) mp->local is null_sha1; set to p->two->sha1 */ - assert(is_null_sha1(mp->local) || - !hashcmp(mp->local, uninitialized)); - hashcpy(mp->local, p->two->oid.hash); + assert(is_null_oid(&mp->local) || + !oidcmp(&mp->local, &uninitialized)); + oidcpy(&mp->local, &p->two->oid); } else { /* modification */ /* * This is a true modification. p->one->sha1 shall * match mp->base, and mp->local shall be uninitialized. * Set mp->local to p->two->sha1. */ - assert(!hashcmp(p->one->oid.hash, mp->base)); - assert(!hashcmp(mp->local, uninitialized)); - hashcpy(mp->local, p->two->oid.hash); + assert(!oidcmp(&p->one->oid, &mp->base)); + assert(!oidcmp(&mp->local, &uninitialized)); + oidcpy(&mp->local, &p->two->oid); } trace_printf("\t\tStored local change for %s: %.7s -> %.7s\n", - sha1_to_hex(mp->obj), sha1_to_hex(mp->base), - sha1_to_hex(mp->local)); + oid_to_hex(&mp->obj), oid_to_hex(&mp->base), + oid_to_hex(&mp->local)); } diff_flush(&opt); clear_pathspec(&opt.pathspec); @@ -343,11 +344,11 @@ static int ll_merge_in_worktree(struct notes_merge_options *o, mmfile_t base, local, remote; int status; - read_mmblob(&base, p->base); - read_mmblob(&local, p->local); - read_mmblob(&remote, p->remote); + read_mmblob(&base, &p->base); + read_mmblob(&local, &p->local); + read_mmblob(&remote, &p->remote); - status = ll_merge(&result_buf, sha1_to_hex(p->obj), &base, NULL, + status = ll_merge(&result_buf, oid_to_hex(&p->obj), &base, NULL, &local, o->local_ref, &remote, o->remote_ref, NULL); free(base.ptr); @@ -357,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, result_buf.ptr, result_buf.size); + write_buf_to_worktree(p->obj.hash, result_buf.ptr, result_buf.size); free(result_buf.ptr); return status; @@ -372,51 +373,52 @@ static int merge_one_change_manual(struct notes_merge_options *o, trace_printf("\t\t\tmerge_one_change_manual(obj = %.7s, base = %.7s, " "local = %.7s, remote = %.7s)\n", - sha1_to_hex(p->obj), sha1_to_hex(p->base), - sha1_to_hex(p->local), sha1_to_hex(p->remote)); + oid_to_hex(&p->obj), oid_to_hex(&p->base), + oid_to_hex(&p->local), oid_to_hex(&p->remote)); /* add "Conflicts:" section to commit message first time through */ if (!o->has_worktree) strbuf_addstr(&(o->commit_msg), "\n\nConflicts:\n"); - strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj)); + strbuf_addf(&(o->commit_msg), "\t%s\n", oid_to_hex(&p->obj)); if (o->verbosity >= 2) - printf("Auto-merging notes for %s\n", sha1_to_hex(p->obj)); + printf("Auto-merging notes for %s\n", oid_to_hex(&p->obj)); check_notes_merge_worktree(o); - if (is_null_sha1(p->local)) { + if (is_null_oid(&p->local)) { /* D/F conflict, checkout p->remote */ - assert(!is_null_sha1(p->remote)); + assert(!is_null_oid(&p->remote)); if (o->verbosity >= 1) printf("CONFLICT (delete/modify): Notes for object %s " "deleted in %s and modified in %s. Version from %s " "left in tree.\n", - sha1_to_hex(p->obj), lref, rref, rref); - write_note_to_worktree(p->obj, p->remote); - } else if (is_null_sha1(p->remote)) { + oid_to_hex(&p->obj), lref, rref, rref); + write_note_to_worktree(p->obj.hash, p->remote.hash); + } else if (is_null_oid(&p->remote)) { /* D/F conflict, checkout p->local */ - assert(!is_null_sha1(p->local)); + assert(!is_null_oid(&p->local)); if (o->verbosity >= 1) printf("CONFLICT (delete/modify): Notes for object %s " "deleted in %s and modified in %s. Version from %s " "left in tree.\n", - sha1_to_hex(p->obj), rref, lref, lref); - write_note_to_worktree(p->obj, p->local); + oid_to_hex(&p->obj), rref, lref, lref); + write_note_to_worktree(p->obj.hash, p->local.hash); } else { /* "regular" conflict, checkout result of ll_merge() */ const char *reason = "content"; - if (is_null_sha1(p->base)) + if (is_null_oid(&p->base)) reason = "add/add"; - assert(!is_null_sha1(p->local)); - assert(!is_null_sha1(p->remote)); + assert(!is_null_oid(&p->local)); + assert(!is_null_oid(&p->remote)); if (o->verbosity >= 1) printf("CONFLICT (%s): Merge conflict in notes for " - "object %s\n", reason, sha1_to_hex(p->obj)); + "object %s\n", reason, + oid_to_hex(&p->obj)); ll_merge_in_worktree(o, p); } trace_printf("\t\t\tremoving from partial merge result\n"); - remove_note(t, p->obj); + remove_note(t, p->obj.hash); return 1; } @@ -435,29 +437,29 @@ static int merge_one_change(struct notes_merge_options *o, case NOTES_MERGE_RESOLVE_OURS: if (o->verbosity >= 2) printf("Using local notes for %s\n", - sha1_to_hex(p->obj)); + oid_to_hex(&p->obj)); /* nothing to do */ return 0; case NOTES_MERGE_RESOLVE_THEIRS: if (o->verbosity >= 2) printf("Using remote notes for %s\n", - sha1_to_hex(p->obj)); - if (add_note(t, p->obj, p->remote, combine_notes_overwrite)) + oid_to_hex(&p->obj)); + if (add_note(t, p->obj.hash, p->remote.hash, 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", - sha1_to_hex(p->obj)); - if (add_note(t, p->obj, p->remote, combine_notes_concatenate)) + oid_to_hex(&p->obj)); + if (add_note(t, p->obj.hash, p->remote.hash, combine_notes_concatenate)) die("failed to concatenate notes " "(combine_notes_concatenate)"); return 0; case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ: if (o->verbosity >= 2) printf("Concatenating unique lines in local and remote " - "notes for %s\n", sha1_to_hex(p->obj)); - if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq)) + "notes for %s\n", oid_to_hex(&p->obj)); + if (add_note(t, p->obj.hash, p->remote.hash, combine_notes_cat_sort_uniq)) die("failed to concatenate notes " "(combine_notes_cat_sort_uniq)"); return 0; @@ -475,20 +477,21 @@ static int merge_changes(struct notes_merge_options *o, for (i = 0; i < *num_changes; i++) { struct notes_merge_pair *p = changes + i; trace_printf("\t\t%.7s: %.7s -> %.7s/%.7s\n", - sha1_to_hex(p->obj), sha1_to_hex(p->base), - sha1_to_hex(p->local), sha1_to_hex(p->remote)); + oid_to_hex(&p->obj), oid_to_hex(&p->base), + oid_to_hex(&p->local), + oid_to_hex(&p->remote)); - if (!hashcmp(p->base, p->remote)) { + if (!oidcmp(&p->base, &p->remote)) { /* no remote change; nothing to do */ trace_printf("\t\t\tskipping (no remote change)\n"); - } else if (!hashcmp(p->local, p->remote)) { + } else if (!oidcmp(&p->local, &p->remote)) { /* same change in local and remote; nothing to do */ trace_printf("\t\t\tskipping (local == remote)\n"); - } else if (!hashcmp(p->local, uninitialized) || - !hashcmp(p->local, p->base)) { + } else if (!oidcmp(&p->local, &uninitialized) || + !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, p->remote, + if (add_note(t, p->obj.hash, p->remote.hash, combine_notes_overwrite)) die("BUG: combine_notes_overwrite failed"); } else { @@ -993,7 +993,7 @@ const char *default_notes_ref(void) void init_notes(struct notes_tree *t, const char *notes_ref, combine_notes_fn combine_notes, int flags) { - unsigned char sha1[20], object_sha1[20]; + struct object_id oid, object_oid; unsigned mode; struct leaf_node root_tree; @@ -1017,16 +1017,16 @@ void init_notes(struct notes_tree *t, const char *notes_ref, t->dirty = 0; if (flags & NOTES_INIT_EMPTY || !notes_ref || - get_sha1_treeish(notes_ref, object_sha1)) + get_sha1_treeish(notes_ref, object_oid.hash)) return; - if (flags & NOTES_INIT_WRITABLE && read_ref(notes_ref, object_sha1)) + if (flags & NOTES_INIT_WRITABLE && read_ref(notes_ref, object_oid.hash)) die("Cannot use notes ref %s", notes_ref); - if (get_tree_entry(object_sha1, "", sha1, &mode)) + if (get_tree_entry(object_oid.hash, "", oid.hash, &mode)) die("Failed to read notes tree referenced by %s (%s)", - notes_ref, sha1_to_hex(object_sha1)); + notes_ref, oid_to_hex(&object_oid)); hashclr(root_tree.key_sha1); - hashcpy(root_tree.val_sha1, sha1); + hashcpy(root_tree.val_sha1, oid.hash); load_subtree(t, &root_tree, t->root, 0); } @@ -6,12 +6,8 @@ #define DEFAULT_PAGER "less" #endif -/* - * This is split up from the rest of git so that we can do - * something different on Windows. - */ - static struct child_process pager_process = CHILD_PROCESS_INIT; +static const char *pager_program; static void wait_for_pager(int in_signal) { @@ -40,6 +36,44 @@ static void wait_for_pager_signal(int signo) raise(signo); } +static int core_pager_config(const char *var, const char *value, void *data) +{ + if (!strcmp(var, "core.pager")) + return git_config_string(&pager_program, var, value); + return 0; +} + +static void read_early_config(config_fn_t cb, void *data) +{ + git_config_with_options(cb, data, NULL, 1); + + /* + * Note that this is a really dirty hack that does the wrong thing in + * many cases. The crux of the problem is that we cannot run + * setup_git_directory() early on in git's setup, so we have no idea if + * we are in a repository or not, and therefore are not sure whether + * and how to read repository-local config. + * + * So if we _aren't_ in a repository (or we are but we would reject its + * core.repositoryformatversion), we'll read whatever is in .git/config + * blindly. Similarly, if we _are_ in a repository, but not at the + * root, we'll fail to find .git/config (because it's really + * ../.git/config, etc). See t7006 for a complete set of failures. + * + * However, we have historically provided this hack because it does + * work some of the time (namely when you are at the top-level of a + * valid repository), and would rarely make things worse (i.e., you do + * not generally have a .git/config file sitting around). + */ + if (!startup_info->have_repository) { + struct git_config_source repo_config; + + memset(&repo_config, 0, sizeof(repo_config)); + repo_config.file = ".git/config"; + git_config_with_options(cb, data, &repo_config, 1); + } +} + const char *git_pager(int stdout_is_tty) { const char *pager; @@ -50,7 +84,7 @@ const char *git_pager(int stdout_is_tty) pager = getenv("GIT_PAGER"); if (!pager) { if (!pager_program) - git_config(git_default_config, NULL); + read_early_config(core_pager_config, NULL); pager = pager_program; } if (!pager) @@ -180,23 +214,42 @@ int decimal_width(uintmax_t number) return width; } -/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */ -int check_pager_config(const char *cmd) +struct pager_command_config_data { + const char *cmd; + int want; + char *value; +}; + +static int pager_command_config(const char *var, const char *value, void *vdata) { - int want = -1; - struct strbuf key = STRBUF_INIT; - const char *value = NULL; - strbuf_addf(&key, "pager.%s", cmd); - if (git_config_key_is_valid(key.buf) && - !git_config_get_value(key.buf, &value)) { - int b = git_config_maybe_bool(key.buf, value); + struct pager_command_config_data *data = vdata; + const char *cmd; + + if (skip_prefix(var, "pager.", &cmd) && !strcmp(cmd, data->cmd)) { + int b = git_config_maybe_bool(var, value); if (b >= 0) - want = b; + data->want = b; else { - want = 1; - pager_program = xstrdup(value); + data->want = 1; + data->value = xstrdup(value); } } - strbuf_release(&key); - return want; + + return 0; +} + +/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */ +int check_pager_config(const char *cmd) +{ + struct pager_command_config_data data; + + data.cmd = cmd; + data.want = -1; + data.value = NULL; + + read_early_config(pager_command_config, &data); + + if (data.value) + pager_program = data.value; + return data.want; } diff --git a/parse-options-cb.c b/parse-options-cb.c index 9667bc75a0..b5d920914e 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -159,6 +159,18 @@ int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset) } /** + * Report that the option is unknown, so that other code can handle + * it. This can be used as a callback together with + * OPTION_LOWLEVEL_CALLBACK to allow an option to be documented in the + * "-h" output even if it's not being handled directly by + * parse_options(). + */ +int parse_opt_unknown_cb(const struct option *opt, const char *arg, int unset) +{ + return -2; +} + +/** * Recreates the command-line option in the strbuf. */ static int recreate_opt(struct strbuf *sb, const struct option *opt, diff --git a/parse-options.h b/parse-options.h index 78f8384c56..dcd8a0926c 100644 --- a/parse-options.h +++ b/parse-options.h @@ -228,6 +228,7 @@ extern int parse_opt_commits(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); extern int parse_opt_string_list(const struct option *, const char *, int); extern int parse_opt_noop_cb(const struct option *, const char *, int); +extern int parse_opt_unknown_cb(const struct option *, const char *, int); extern int parse_opt_passthru(const struct option *, const char *, int); extern int parse_opt_passthru_argv(const struct option *, const char *, int); diff --git a/patch-ids.c b/patch-ids.c index 082412aca6..ce285c2e0c 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -4,9 +4,18 @@ #include "sha1-lookup.h" #include "patch-ids.h" +static int patch_id_defined(struct commit *commit) +{ + /* must be 0 or 1 parents */ + return !commit->parents || !commit->parents->next; +} + int commit_patch_id(struct commit *commit, struct diff_options *options, unsigned char *sha1, 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); @@ -45,6 +54,7 @@ int init_patch_ids(struct patch_ids *ids) { memset(ids, 0, sizeof(*ids)); diff_setup(&ids->diffopts); + ids->diffopts.detect_rename = 0; DIFF_OPT_SET(&ids->diffopts, RECURSIVE); diff_setup_done(&ids->diffopts); hashmap_init(&ids->patches, (hashmap_cmp_fn)patch_id_cmp, 256); @@ -76,6 +86,9 @@ struct patch_id *has_commit_patch_id(struct commit *commit, { struct patch_id patch; + if (!patch_id_defined(commit)) + return NULL; + memset(&patch, 0, sizeof(patch)); if (init_patch_id_entry(&patch, commit, ids)) return NULL; @@ -88,6 +101,9 @@ struct patch_id *add_commit_patch_id(struct commit *commit, { struct patch_id *key = xcalloc(1, sizeof(*key)); + if (!patch_id_defined(commit)) + return NULL; + if (init_patch_id_entry(key, commit, ids)) { free(key); return NULL; @@ -6,6 +6,7 @@ #include "string-list.h" #include "dir.h" #include "worktree.h" +#include "submodule-config.h" static int get_st_mode_bits(const char *path, int *mode) { @@ -468,12 +469,16 @@ const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...) return pathname->buf; } -static void do_submodule_path(struct strbuf *buf, const char *path, - const char *fmt, va_list args) +/* Returns 0 on success, negative on failure. */ +#define SUBMODULE_PATH_ERR_NOT_CONFIGURED -1 +static int do_submodule_path(struct strbuf *buf, const char *path, + const char *fmt, va_list args) { const char *git_dir; struct strbuf git_submodule_common_dir = STRBUF_INIT; struct strbuf git_submodule_dir = STRBUF_INIT; + const struct submodule *sub; + int err = 0; strbuf_addstr(buf, path); strbuf_complete(buf, '/'); @@ -484,6 +489,17 @@ static void do_submodule_path(struct strbuf *buf, const char *path, strbuf_reset(buf); strbuf_addstr(buf, git_dir); } + if (!is_git_directory(buf->buf)) { + gitmodules_config(); + sub = submodule_from_path(null_sha1, path); + if (!sub) { + err = SUBMODULE_PATH_ERR_NOT_CONFIGURED; + goto cleanup; + } + strbuf_reset(buf); + strbuf_git_path(buf, "%s/%s", "modules", sub->name); + } + strbuf_addch(buf, '/'); strbuf_addbuf(&git_submodule_dir, buf); @@ -494,27 +510,38 @@ static void do_submodule_path(struct strbuf *buf, const char *path, strbuf_cleanup_path(buf); +cleanup: strbuf_release(&git_submodule_dir); strbuf_release(&git_submodule_common_dir); + + return err; } char *git_pathdup_submodule(const char *path, const char *fmt, ...) { + int err; va_list args; struct strbuf buf = STRBUF_INIT; va_start(args, fmt); - do_submodule_path(&buf, path, fmt, args); + err = do_submodule_path(&buf, path, fmt, args); va_end(args); + if (err) { + strbuf_release(&buf); + return NULL; + } return strbuf_detach(&buf, NULL); } -void strbuf_git_path_submodule(struct strbuf *buf, const char *path, - const char *fmt, ...) +int strbuf_git_path_submodule(struct strbuf *buf, const char *path, + const char *fmt, ...) { + int err; va_list args; va_start(args, fmt); - do_submodule_path(buf, path, fmt, args); + err = do_submodule_path(buf, path, fmt, args); va_end(args); + + return err; } static void do_git_common_path(struct strbuf *buf, diff --git a/pathspec.h b/pathspec.h index 4a80f6fc96..59809e4793 100644 --- a/pathspec.h +++ b/pathspec.h @@ -96,7 +96,5 @@ static inline int ps_strcmp(const struct pathspec_item *item, 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 const char *check_path_for_gitlink(const char *path); -extern void die_if_path_beyond_symlink(const char *path, const char *prefix); #endif /* PATHSPEC_H */ diff --git a/pkt-line.c b/pkt-line.c index 62fdb37079..30489c60b1 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -172,27 +172,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size, static int packet_length(const char *linelen) { - int n; - int len = 0; - - for (n = 0; n < 4; n++) { - unsigned char c = linelen[n]; - len <<= 4; - if (c >= '0' && c <= '9') { - len += c - '0'; - continue; - } - if (c >= 'a' && c <= 'f') { - len += c - 'a' + 10; - continue; - } - if (c >= 'A' && c <= 'F') { - len += c - 'A' + 10; - continue; - } - return -1; - } - return len; + int val = hex2chr(linelen); + return (val < 0) ? val : (val << 8) | hex2chr(linelen + 2); } int packet_read(int fd, char **src_buf, size_t *src_len, @@ -37,8 +37,9 @@ Repository: https://github.com/changwoo/git-l10n-ko/ Leader: Changwoo Ryu <cwryu@debian.org> Language: pt_PT (Portuguese - Portugal) -Repository: https://github.com/marcomsousa/git-l10n-pt_PT/ -Leader: Marco Sousa <marcomsousa AT gmail.com> +Repository: https://github.com/vascool/git-po-pt/ +Leader: Vasco Almeida <vascomalmeida@sapo.pt> +Members: Marco Sousa <marcomsousa AT gmail.com> Language: ru (Russian) Repository: https://github.com/DJm00n/git-po-ru/ diff --git a/po/pt_PT.po b/po/pt_PT.po index 13610a0aa9..c8658dfd25 100644 --- a/po/pt_PT.po +++ b/po/pt_PT.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2016-08-15 22:43+0800\n" -"PO-Revision-Date: 2016-08-24 14:07+0000\n" +"POT-Creation-Date: 2016-08-27 23:21+0800\n" +"PO-Revision-Date: 2016-09-03 12:00+0000\n" "Last-Translator: Vasco Almeida <vascomalmeida@sapo.pt>\n" "Language-Team: Portuguese\n" "Language: pt\n" @@ -27,7 +27,8 @@ msgstr "dica: %.*s\n" #: advice.c:83 msgid "Cherry-picking is not possible because you have unmerged files." -msgstr "Não é possÃvel efetuar cherry-pick porque tem ficheiros não integrados." +msgstr "" +"Não é possÃvel efetuar cherry-pick porque tem ficheiros não integrados." #: advice.c:85 msgid "Committing is not possible because you have unmerged files." @@ -614,145 +615,98 @@ msgstr "linha de configuração %d incorreta na linha de comandos %s" msgid "bad config line %d in %s" msgstr "linha de configuração %d incorreta em %s" -#: config.c:660 -#, c-format -msgid "bad numeric config value '%s' for '%s': out of range" -msgstr "valor numérico '%s' da configuração '%s' incorreto: fora de intervalo" +#: config.c:655 +msgid "out of range" +msgstr "fora de intervalo" + +#: config.c:655 +msgid "invalid unit" +msgstr "unidade inválida" #: config.c:661 #, c-format -msgid "bad numeric config value '%s' for '%s': invalid unit" -msgstr "valor numérico '%s' da configuração '%s' incorreto: unidade inválida" +msgid "bad numeric config value '%s' for '%s': %s" +msgstr "valor numérico '%s' da configuração '%s' incorreto: %s" -#: config.c:667 +#: config.c:666 #, c-format -msgid "bad numeric config value '%s' for '%s' in blob %s: out of range" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto no blob %s: fora de " -"intervalo" +msgid "bad numeric config value '%s' for '%s' in blob %s: %s" +msgstr "valor numérico '%s' da configuração '%s' incorreto no blob %s: %s" -#: config.c:668 +#: config.c:669 #, c-format -msgid "bad numeric config value '%s' for '%s' in blob %s: invalid unit" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto no blob %s: unidade " -"inválida" +msgid "bad numeric config value '%s' for '%s' in file %s: %s" +msgstr "valor numérico '%s' da configuração '%s' incorreto no ficheiro %s: %s" #: config.c:672 #, c-format -msgid "bad numeric config value '%s' for '%s' in file %s: out of range" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto no ficheiro %s: fora de " -"intervalo" - -#: config.c:673 -#, c-format -msgid "bad numeric config value '%s' for '%s' in file %s: invalid unit" +msgid "bad numeric config value '%s' for '%s' in standard input: %s" msgstr "" -"valor numérico '%s' da configuração '%s' incorreto no ficheiro %s: unidade " -"inválida" +"valor numérico '%s' da configuração '%s' incorreto na entrada padrão: %s" -#: config.c:677 +#: config.c:675 #, c-format -msgid "bad numeric config value '%s' for '%s' in standard input: out of range" +msgid "bad numeric config value '%s' for '%s' in submodule-blob %s: %s" msgstr "" -"valor numérico '%s' da configuração '%s' incorreto na entrada padrão: fora " -"de intervalo" +"valor numérico '%s' da configuração '%s' incorreto no submódulo-blob %s: %s" #: config.c:678 #, c-format -msgid "bad numeric config value '%s' for '%s' in standard input: invalid unit" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto na entrada padrão: " -"unidade inválida" - -#: config.c:682 -#, c-format -msgid "" -"bad numeric config value '%s' for '%s' in submodule-blob %s: out of range" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto no submódulo-blob %s: " -"fora de intervalo" - -#: config.c:683 -#, c-format -msgid "" -"bad numeric config value '%s' for '%s' in submodule-blob %s: invalid unit" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto no submódulo-blob %s: " -"unidade inválida" - -#: config.c:687 -#, c-format -msgid "bad numeric config value '%s' for '%s' in command line %s: out of range" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto na linha de comandos %s: " -"fora de intervalo" - -#: config.c:688 -#, c-format -msgid "bad numeric config value '%s' for '%s' in command line %s: invalid unit" +msgid "bad numeric config value '%s' for '%s' in command line %s: %s" msgstr "" "valor numérico '%s' da configuração '%s' incorreto na linha de comandos %s: " -"unidade inválida" - -#: config.c:692 -#, c-format -msgid "bad numeric config value '%s' for '%s' in %s: out of range" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto em %s: fora de intervalo" +"%s" -#: config.c:693 +#: config.c:681 #, c-format -msgid "bad numeric config value '%s' for '%s' in %s: invalid unit" -msgstr "" -"valor numérico '%s' da configuração '%s' incorreto em %s: unidade inválida" +msgid "bad numeric config value '%s' for '%s' in %s: %s" +msgstr "valor numérico '%s' da configuração '%s' incorreto em %s: %s" -#: config.c:780 +#: config.c:768 #, c-format msgid "failed to expand user dir in: '%s'" msgstr "falha ao expandir diretório de utilizador em: '%s'" -#: config.c:861 config.c:872 +#: config.c:849 config.c:860 #, c-format msgid "bad zlib compression level %d" msgstr "nÃvel de compressão zlib %d incorreto" -#: config.c:990 +#: config.c:978 #, c-format msgid "invalid mode for object creation: %s" msgstr "modo inválido para criação de objeto: %s" -#: config.c:1324 +#: config.c:1312 msgid "unable to parse command-line config" msgstr "não é possÃvel analisar configuração de linha de comandos" -#: config.c:1374 +#: config.c:1362 msgid "unknown error occurred while reading the configuration files" msgstr "" "ocorreu um erro desconhecido durante a leitura dos ficheiros de configuração" -#: config.c:1728 +#: config.c:1716 #, c-format msgid "unable to parse '%s' from command-line config" msgstr "não é possÃvel analisar '%s' da configuração de linha de comandos" -#: config.c:1730 +#: config.c:1718 #, c-format msgid "bad config variable '%s' in file '%s' at line %d" msgstr "variável de configuração '%s' incorreta no ficheiro '%s' na linha %d" -#: config.c:1789 +#: config.c:1777 #, c-format msgid "%s has multiple values" msgstr "%s tem múltiplos valores" -#: config.c:2323 +#: config.c:2311 #, c-format msgid "could not set '%s' to '%s'" msgstr "não foi possÃvel definir '%s' como '%s'" -#: config.c:2325 +#: config.c:2313 #, c-format msgid "could not unset '%s'" msgstr "não foi possÃvel reiniciar '%s'" @@ -1030,73 +984,73 @@ msgstr "falha ao ler a cache" msgid "unable to write new index file" msgstr "não foi possÃvel escrever novo ficheiro de Ãndice" -#: merge-recursive.c:212 +#: merge-recursive.c:209 msgid "(bad commit)\n" msgstr "(commit incorreto)\n" -#: merge-recursive.c:234 +#: merge-recursive.c:231 #, c-format msgid "addinfo_cache failed for path '%s'" msgstr "addinfo_cache falhou no caminho '%s'" -#: merge-recursive.c:304 +#: merge-recursive.c:301 msgid "error building trees" msgstr "erro ao construir árvores" -#: merge-recursive.c:723 +#: merge-recursive.c:720 #, c-format msgid "failed to create path '%s'%s" msgstr "falha ao criar o caminho '%s'%s" -#: merge-recursive.c:734 +#: merge-recursive.c:731 #, c-format msgid "Removing %s to make room for subdirectory\n" msgstr "A remover %s para criar espaço para o subdiretório\n" -#: merge-recursive.c:748 merge-recursive.c:767 +#: merge-recursive.c:745 merge-recursive.c:764 msgid ": perhaps a D/F conflict?" msgstr ": talvez um conflito D/F?" -#: merge-recursive.c:757 +#: merge-recursive.c:754 #, c-format msgid "refusing to lose untracked file at '%s'" msgstr "perda de ficheiro não controlado em '%s' recusada" -#: merge-recursive.c:799 +#: merge-recursive.c:796 #, c-format msgid "cannot read object %s '%s'" msgstr "não é possÃvel ler o objeto %s: '%s'" -#: merge-recursive.c:801 +#: merge-recursive.c:798 #, c-format msgid "blob expected for %s '%s'" msgstr "blob para %s '%s' esperado" -#: merge-recursive.c:825 +#: merge-recursive.c:822 #, c-format msgid "failed to open '%s': %s" msgstr "falha ao abrir '%s': %s" -#: merge-recursive.c:836 +#: merge-recursive.c:833 #, c-format msgid "failed to symlink '%s': %s" msgstr "falha ao criar a ligação simbólica '%s': %s" -#: merge-recursive.c:841 +#: merge-recursive.c:838 #, c-format msgid "do not know what to do with %06o %s '%s'" msgstr "não sei o que fazer com %06o %s '%s'" -#: merge-recursive.c:981 +#: merge-recursive.c:978 msgid "Failed to execute internal merge" msgstr "Falha ao executar integração interna" -#: merge-recursive.c:985 +#: merge-recursive.c:982 #, c-format msgid "Unable to add %s to database" msgstr "Não é possÃvel adicionar %s à base de dados" -#: merge-recursive.c:1084 merge-recursive.c:1098 +#: merge-recursive.c:1081 merge-recursive.c:1095 #, c-format msgid "" "CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s left " @@ -1105,7 +1059,7 @@ msgstr "" "CONFLITO (%s/eliminar): %s eliminado em %s e %s em %s. Versão de %s de %s " "deixada na árvore." -#: merge-recursive.c:1090 merge-recursive.c:1103 +#: merge-recursive.c:1087 merge-recursive.c:1100 #, c-format msgid "" "CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s left " @@ -1114,160 +1068,160 @@ msgstr "" "CONFLITO (%s/eliminar): %s eliminado em %s e %s em %s. Versão de %s de %s " "deixada na árvore em %s." -#: merge-recursive.c:1146 +#: merge-recursive.c:1143 msgid "rename" msgstr "mudar nome" -#: merge-recursive.c:1146 +#: merge-recursive.c:1143 msgid "renamed" msgstr "nome mudado" -#: merge-recursive.c:1203 +#: merge-recursive.c:1200 #, c-format msgid "%s is a directory in %s adding as %s instead" msgstr "%s é um diretório em %s adicionando %s no seu lugar" -#: merge-recursive.c:1228 +#: merge-recursive.c:1225 #, c-format msgid "" "CONFLICT (rename/rename): Rename \"%s\"->\"%s\" in branch \"%s\" rename \"%s" "\"->\"%s\" in \"%s\"%s" msgstr "" -"CONFLITO (mudar nome/mudar nome): Mudar o nome \"%s\"->\"%s\" no ramo \"%s\" mudar " -"o nome \"%s\"->\"%s\" em \"%s\"%s" +"CONFLITO (mudar nome/mudar nome): Mudar o nome \"%s\"->\"%s\" no ramo \"%s\" " +"mudar o nome \"%s\"->\"%s\" em \"%s\"%s" -#: merge-recursive.c:1233 +#: merge-recursive.c:1230 msgid " (left unresolved)" msgstr " (por resolver)" -#: merge-recursive.c:1295 +#: merge-recursive.c:1292 #, c-format msgid "CONFLICT (rename/rename): Rename %s->%s in %s. Rename %s->%s in %s" msgstr "" -"CONFLITO (mudar nome/mudar nome): Mudar de nome %s->%s em %s. Mudar o nome %" -"s->%s em %s" +"CONFLITO (mudar nome/mudar nome): Mudar de nome %s->%s em %s. Mudar o nome " +"%s->%s em %s" -#: merge-recursive.c:1328 +#: merge-recursive.c:1325 #, c-format msgid "Renaming %s to %s and %s to %s instead" msgstr "A mudar o nome de %s para %s e de %s para %s, em alternativa" -#: merge-recursive.c:1534 +#: merge-recursive.c:1531 #, c-format msgid "CONFLICT (rename/add): Rename %s->%s in %s. %s added in %s" msgstr "" "CONFLITO (mudar nome/adicionar): Mudar o nome %s->%s em %s. %s adicionado em " "%s" -#: merge-recursive.c:1549 +#: merge-recursive.c:1546 #, c-format msgid "Adding merged %s" msgstr "A adicionar %s integrado" -#: merge-recursive.c:1556 merge-recursive.c:1769 +#: merge-recursive.c:1553 merge-recursive.c:1766 #, c-format msgid "Adding as %s instead" msgstr "A adicionar como %s, em alternativa." -#: merge-recursive.c:1613 +#: merge-recursive.c:1610 #, c-format msgid "cannot read object %s" msgstr "não foi possÃvel ler o objeto %s" -#: merge-recursive.c:1616 +#: merge-recursive.c:1613 #, c-format msgid "object %s is not a blob" msgstr "o objeto %s não é um blob" -#: merge-recursive.c:1669 +#: merge-recursive.c:1666 msgid "modify" msgstr "modificar" -#: merge-recursive.c:1669 +#: merge-recursive.c:1666 msgid "modified" msgstr "modificado" -#: merge-recursive.c:1679 +#: merge-recursive.c:1676 msgid "content" msgstr "conteúdo" -#: merge-recursive.c:1686 +#: merge-recursive.c:1683 msgid "add/add" msgstr "adicionar/adicionar" -#: merge-recursive.c:1721 +#: merge-recursive.c:1718 #, c-format msgid "Skipped %s (merged same as existing)" msgstr "%s ignorado (integrado é igual ao existente)" -#: merge-recursive.c:1735 +#: merge-recursive.c:1732 #, c-format msgid "Auto-merging %s" msgstr "A integrar automaticamente %s" -#: merge-recursive.c:1739 git-submodule.sh:919 +#: merge-recursive.c:1736 git-submodule.sh:919 msgid "submodule" msgstr "submódulo" -#: merge-recursive.c:1740 +#: merge-recursive.c:1737 #, c-format msgid "CONFLICT (%s): Merge conflict in %s" msgstr "CONFLITO (%s): conflito de integração em %s" -#: merge-recursive.c:1834 +#: merge-recursive.c:1831 #, c-format msgid "Removing %s" msgstr "A remover %s" -#: merge-recursive.c:1860 +#: merge-recursive.c:1857 msgid "file/directory" msgstr "ficheiro/diretório" -#: merge-recursive.c:1866 +#: merge-recursive.c:1863 msgid "directory/file" msgstr "diretório/ficheiro" -#: merge-recursive.c:1871 +#: merge-recursive.c:1868 #, c-format msgid "CONFLICT (%s): There is a directory with name %s in %s. Adding %s as %s" msgstr "" "CONFLITO (%s): Há um diretório designado %s em %s. A adicionar %s como %s" -#: merge-recursive.c:1880 +#: merge-recursive.c:1877 #, c-format msgid "Adding %s" msgstr "A adicionar %s" -#: merge-recursive.c:1917 +#: merge-recursive.c:1914 msgid "Already up-to-date!" msgstr "Já está atualizado!" -#: merge-recursive.c:1926 +#: merge-recursive.c:1923 #, c-format msgid "merging of trees %s and %s failed" msgstr "falha ao integrar as árvores %s e %s" -#: merge-recursive.c:2009 +#: merge-recursive.c:2006 msgid "Merging:" msgstr "A integrar:" -#: merge-recursive.c:2022 +#: merge-recursive.c:2019 #, c-format msgid "found %u common ancestor:" msgid_plural "found %u common ancestors:" msgstr[0] "%u antecessor comum encontrado:" msgstr[1] "%u antecessores comuns encontrados:" -#: merge-recursive.c:2061 +#: merge-recursive.c:2058 msgid "merge returned no commit" msgstr "a integração não retornou nenhum commit" -#: merge-recursive.c:2124 +#: merge-recursive.c:2121 #, c-format msgid "Could not parse object '%s'" msgstr "Não foi possÃvel analisar o objeto '%s'" -#: merge-recursive.c:2138 builtin/merge.c:641 builtin/merge.c:788 +#: merge-recursive.c:2135 builtin/merge.c:641 builtin/merge.c:788 msgid "Unable to write index." msgstr "Não é possÃvel escrever no Ãndice." @@ -1314,7 +1268,7 @@ msgstr "utilização: %s" msgid " or: %s" msgstr " ou: %s" -#: parse-options.c:598 +#: parse-options.c:597 #, c-format msgid " %s" msgstr " %s" @@ -1328,7 +1282,7 @@ msgstr "-NUM" msgid "malformed object name '%s'" msgstr "nome do objeto malformado '%s'" -#: path.c:796 +#: path.c:798 #, c-format msgid "Could not make %s writable by group" msgstr "Não foi possÃvel dar permissão de escrita ao grupo a %s" @@ -1878,7 +1832,7 @@ msgid "git %s: failed to refresh the index" msgstr "git %s: falha ao atualizar o Ãndice" #: sequencer.c:705 -msgid "Cannot revert during a another revert." +msgid "Cannot revert during another revert." msgstr "Não é possÃvel reverter durante outra reversão." #: sequencer.c:706 @@ -2309,8 +2263,8 @@ msgid "" "Your local changes to the following files would be overwritten by %s:\n" "%%sPlease commit your changes or stash them before you %s." msgstr "" -"As suas alterações locais nos seguintes ficheiros seriam substituÃdas por %" -"s:\n" +"As suas alterações locais nos seguintes ficheiros seriam substituÃdas por " +"%s:\n" "%%sSubmeta as suas alterações ou esconda-as antes de efetuar %s." #: unpack-trees.c:76 @@ -3120,7 +3074,7 @@ msgstr "ser verboso" msgid "interactive picking" msgstr "seleção interativa" -#: builtin/add.c:257 builtin/checkout.c:1156 builtin/reset.c:286 +#: builtin/add.c:257 builtin/checkout.c:1157 builtin/reset.c:286 msgid "select hunks interactively" msgstr "selecionar blocos interativamente" @@ -4680,7 +4634,8 @@ msgstr "não faz sentido criar 'HEAD' manualmente" #: builtin/branch.c:846 msgid "-a and -r options to 'git branch' do not make sense with a branch name" -msgstr "as opções -a e -r de 'git branch' não fazem sentido com um nome de ramo" +msgstr "" +"as opções -a e -r de 'git branch' não fazem sentido com um nome de ramo" #: builtin/branch.c:849 #, c-format @@ -4801,7 +4756,7 @@ msgstr "ler os nomes dos ficheiros da entrada padrão" msgid "terminate input and output records by a NUL character" msgstr "terminar registos da entrada e da saÃda com um carácter NUL" -#: builtin/check-ignore.c:18 builtin/checkout.c:1137 builtin/gc.c:325 +#: builtin/check-ignore.c:18 builtin/checkout.c:1138 builtin/gc.c:325 msgid "suppress progress reporting" msgstr "suprimir informação de progresso" @@ -4976,45 +4931,45 @@ msgstr "primeiro deve resolver o Ãndice atual" msgid "Can not do reflog for '%s': %s\n" msgstr "Não é possÃvel efetuar reflog de '%s': %s\n" -#: builtin/checkout.c:663 +#: builtin/checkout.c:664 msgid "HEAD is now at" msgstr "HEAD está agora em" -#: builtin/checkout.c:667 builtin/clone.c:661 +#: builtin/checkout.c:668 builtin/clone.c:661 msgid "unable to update HEAD" msgstr "não foi possÃvel atualizar HEAD" -#: builtin/checkout.c:671 +#: builtin/checkout.c:672 #, c-format msgid "Reset branch '%s'\n" msgstr "Repor ramo '%s'\n" -#: builtin/checkout.c:674 +#: builtin/checkout.c:675 #, c-format msgid "Already on '%s'\n" msgstr "Já em '%s'\n" -#: builtin/checkout.c:678 +#: builtin/checkout.c:679 #, c-format msgid "Switched to and reset branch '%s'\n" msgstr "Mudou para e repôs o ramo '%s'\n" -#: builtin/checkout.c:680 builtin/checkout.c:1069 +#: builtin/checkout.c:681 builtin/checkout.c:1070 #, c-format msgid "Switched to a new branch '%s'\n" msgstr "Mudou para o novo ramo '%s'\n" -#: builtin/checkout.c:682 +#: builtin/checkout.c:683 #, c-format msgid "Switched to branch '%s'\n" msgstr "Mudou para o ramo '%s'\n" -#: builtin/checkout.c:733 +#: builtin/checkout.c:734 #, c-format msgid " ... and %d more.\n" msgstr " ... e mais %d.\n" -#: builtin/checkout.c:739 +#: builtin/checkout.c:740 #, c-format msgid "" "Warning: you are leaving %d commit behind, not connected to\n" @@ -5035,7 +4990,7 @@ msgstr[1] "" "\n" "%s\n" -#: builtin/checkout.c:758 +#: builtin/checkout.c:759 #, c-format msgid "" "If you want to keep it by creating a new branch, this may be a good time\n" @@ -5062,151 +5017,151 @@ msgstr[1] "" " git branch <nome-do-novo-ramo> %s\n" "\n" -#: builtin/checkout.c:794 +#: builtin/checkout.c:795 msgid "internal error in revision walk" msgstr "erro interno durante o curso de revisões" -#: builtin/checkout.c:798 +#: builtin/checkout.c:799 msgid "Previous HEAD position was" msgstr "A posição anterior de HEAD era" -#: builtin/checkout.c:825 builtin/checkout.c:1064 +#: builtin/checkout.c:826 builtin/checkout.c:1065 msgid "You are on a branch yet to be born" msgstr "Está num ramo ainda não criado" -#: builtin/checkout.c:970 +#: builtin/checkout.c:971 #, c-format msgid "only one reference expected, %d given." msgstr "esperava-se apenas uma referência, %d fornecidas." -#: builtin/checkout.c:1010 builtin/worktree.c:214 +#: builtin/checkout.c:1011 builtin/worktree.c:214 #, c-format msgid "invalid reference: %s" msgstr "referência inválida: %s" -#: builtin/checkout.c:1039 +#: builtin/checkout.c:1040 #, c-format msgid "reference is not a tree: %s" msgstr "a referência não é uma árvore: %s" -#: builtin/checkout.c:1078 +#: builtin/checkout.c:1079 msgid "paths cannot be used with switching branches" msgstr "não podem ser usados caminhos ao mudar de ramo" -#: builtin/checkout.c:1081 builtin/checkout.c:1085 +#: builtin/checkout.c:1082 builtin/checkout.c:1086 #, c-format msgid "'%s' cannot be used with switching branches" msgstr "'%s' não pode ser usado ao mudar de ramo" -#: builtin/checkout.c:1089 builtin/checkout.c:1092 builtin/checkout.c:1097 -#: builtin/checkout.c:1100 +#: builtin/checkout.c:1090 builtin/checkout.c:1093 builtin/checkout.c:1098 +#: builtin/checkout.c:1101 #, c-format msgid "'%s' cannot be used with '%s'" msgstr "'%s' não pode ser usado com '%s'" -#: builtin/checkout.c:1105 +#: builtin/checkout.c:1106 #, c-format msgid "Cannot switch branch to a non-commit '%s'" msgstr "Não é possÃvel mudar de ramo para '%s', visto que não é um commit" -#: builtin/checkout.c:1138 builtin/checkout.c:1140 builtin/clone.c:88 +#: builtin/checkout.c:1139 builtin/checkout.c:1141 builtin/clone.c:88 #: builtin/remote.c:165 builtin/remote.c:167 builtin/worktree.c:324 #: builtin/worktree.c:326 msgid "branch" msgstr "ramo" -#: builtin/checkout.c:1139 +#: builtin/checkout.c:1140 msgid "create and checkout a new branch" msgstr "criar e extrair um novo ramo" -#: builtin/checkout.c:1141 +#: builtin/checkout.c:1142 msgid "create/reset and checkout a branch" msgstr "criar/repor e extrair um ramo" -#: builtin/checkout.c:1142 +#: builtin/checkout.c:1143 msgid "create reflog for new branch" msgstr "criar reflog do novo ramo" -#: builtin/checkout.c:1143 builtin/worktree.c:328 +#: builtin/checkout.c:1144 builtin/worktree.c:328 msgid "detach HEAD at named commit" msgstr "destacar HEAD no commit indicado" -#: builtin/checkout.c:1144 +#: builtin/checkout.c:1145 msgid "set upstream info for new branch" msgstr "definir a informação do ramo a montante do novo ramo" -#: builtin/checkout.c:1146 +#: builtin/checkout.c:1147 msgid "new-branch" msgstr "novo-ramo" -#: builtin/checkout.c:1146 +#: builtin/checkout.c:1147 msgid "new unparented branch" msgstr "novo ramo sem pai" -#: builtin/checkout.c:1147 +#: builtin/checkout.c:1148 msgid "checkout our version for unmerged files" msgstr "extrair a nossa versão dos ficheiros não integrados" -#: builtin/checkout.c:1149 +#: builtin/checkout.c:1150 msgid "checkout their version for unmerged files" msgstr "extrair a versão deles dos ficheiros não integrados" -#: builtin/checkout.c:1151 +#: builtin/checkout.c:1152 msgid "force checkout (throw away local modifications)" msgstr "forçar extração (descartar modificações locais)" -#: builtin/checkout.c:1152 +#: builtin/checkout.c:1153 msgid "perform a 3-way merge with the new branch" msgstr "realizar uma integração com 3 pontos com o novo ramo" -#: builtin/checkout.c:1153 builtin/merge.c:231 +#: builtin/checkout.c:1154 builtin/merge.c:231 msgid "update ignored files (default)" msgstr "atualizar ficheiros ignorados (predefinição)" -#: builtin/checkout.c:1154 builtin/log.c:1459 parse-options.h:250 +#: builtin/checkout.c:1155 builtin/log.c:1459 parse-options.h:250 msgid "style" msgstr "estilo" -#: builtin/checkout.c:1155 +#: builtin/checkout.c:1156 msgid "conflict style (merge or diff3)" msgstr "estilo de conflito (merge ou diff3)" -#: builtin/checkout.c:1158 +#: builtin/checkout.c:1159 msgid "do not limit pathspecs to sparse entries only" msgstr "não restringir especificadores de caminho a entradas esparsas" -#: builtin/checkout.c:1160 +#: builtin/checkout.c:1161 msgid "second guess 'git checkout <no-such-branch>'" msgstr "tentar adivinhar 'git checkout <ramo-inexistente>'" -#: builtin/checkout.c:1162 +#: builtin/checkout.c:1163 msgid "do not check if another worktree is holding the given ref" msgstr "" "não verificar se outra árvore de trabalho contém a referência fornecida" -#: builtin/checkout.c:1163 builtin/clone.c:60 builtin/fetch.c:117 +#: builtin/checkout.c:1164 builtin/clone.c:60 builtin/fetch.c:117 #: builtin/merge.c:228 builtin/pull.c:116 builtin/push.c:536 #: builtin/send-pack.c:168 msgid "force progress reporting" msgstr "forçar informação de progresso" -#: builtin/checkout.c:1194 +#: builtin/checkout.c:1195 msgid "-b, -B and --orphan are mutually exclusive" msgstr "-b, -B e --orphan são mutuamente exclusivos" -#: builtin/checkout.c:1211 +#: builtin/checkout.c:1212 msgid "--track needs a branch name" msgstr "--track requer um nome dum ramo" -#: builtin/checkout.c:1216 +#: builtin/checkout.c:1217 msgid "Missing branch name; try -b" msgstr "Falta um nome dum ramo; tente -b" -#: builtin/checkout.c:1252 +#: builtin/checkout.c:1253 msgid "invalid path specification" msgstr "especificação de caminho inválida" -#: builtin/checkout.c:1259 +#: builtin/checkout.c:1260 #, c-format msgid "" "Cannot update paths and switch to branch '%s' at the same time.\n" @@ -5216,12 +5171,12 @@ msgstr "" "tempo.\n" "Pretendia extrair '%s' o qual não pode ser resolvido como commit?" -#: builtin/checkout.c:1264 +#: builtin/checkout.c:1265 #, c-format msgid "git checkout: --detach does not take a path argument '%s'" msgstr "git checkout: --detach não aceita um caminho como argumento '%s'" -#: builtin/checkout.c:1268 +#: builtin/checkout.c:1269 msgid "" "git checkout: --ours/--theirs, --force and --merge are incompatible when\n" "checking out of the index." @@ -5626,7 +5581,7 @@ msgid "cannot unlink temporary alternates file" msgstr "" "não é possÃvel invocar unlink sobre o ficheiro sobressalente temporário" -#: builtin/clone.c:863 builtin/receive-pack.c:1857 +#: builtin/clone.c:863 builtin/receive-pack.c:1855 msgid "Too many arguments." msgstr "Demasiados argumentos." @@ -6788,7 +6743,8 @@ msgstr "número de submódulos obtidos em paralelo" #: builtin/fetch.c:108 builtin/pull.c:187 msgid "prune remote-tracking branches no longer on remote" -msgstr "eliminar os ramos de monitorização remotos que já não existem no remoto" +msgstr "" +"eliminar os ramos de monitorização remotos que já não existem no remoto" #: builtin/fetch.c:109 builtin/pull.c:190 msgid "on-demand" @@ -7586,49 +7542,49 @@ msgstr "nenhum visualizador de manual atendeu o pedido" msgid "no info viewer handled the request" msgstr "nenhum visualizador de info atendeu o pedido" -#: builtin/help.c:408 +#: builtin/help.c:401 msgid "Defining attributes per path" msgstr "Definir atributos por caminho" -#: builtin/help.c:409 +#: builtin/help.c:402 msgid "Everyday Git With 20 Commands Or So" msgstr "Diário Git com mais ou menos 20 Comandos" -#: builtin/help.c:410 +#: builtin/help.c:403 msgid "A Git glossary" msgstr "Um Glossário do Git" -#: builtin/help.c:411 +#: builtin/help.c:404 msgid "Specifies intentionally untracked files to ignore" msgstr "" "Especificar ficheiros não controlados para serem intencionalmente ignorados" -#: builtin/help.c:412 +#: builtin/help.c:405 msgid "Defining submodule properties" msgstr "Definir propriedades de submódulos" -#: builtin/help.c:413 +#: builtin/help.c:406 msgid "Specifying revisions and ranges for Git" msgstr "Especificar revisões e intervalos do Git" -#: builtin/help.c:414 +#: builtin/help.c:407 msgid "A tutorial introduction to Git (for version 1.5.1 or newer)" msgstr "Uma introdução ao Git (versão 1.5.1 ou mais recente)" -#: builtin/help.c:415 +#: builtin/help.c:408 msgid "An overview of recommended workflows with Git" msgstr "Uma descrição geral dos fluxos de trabalho recomendados para o Git" -#: builtin/help.c:427 +#: builtin/help.c:420 msgid "The common Git guides are:\n" msgstr "Os guias comuns do Git:\n" -#: builtin/help.c:448 builtin/help.c:465 +#: builtin/help.c:441 builtin/help.c:458 #, c-format msgid "usage: %s%s" msgstr "utilização: %s%s" -#: builtin/help.c:481 +#: builtin/help.c:474 #, c-format msgid "`git %s' is aliased to `%s'" msgstr "'git %s' é um alias de '%s'" @@ -9066,7 +9022,8 @@ msgstr "O diretório %s está no Ãndice porém nenhum submódulo?" #: builtin/mv.c:72 builtin/rm.c:317 msgid "Please stage your changes to .gitmodules or stash them to proceed" -msgstr "Prepare as suas alterações em .gitmodules ou esconda-as para prosseguir" +msgstr "" +"Prepare as suas alterações em .gitmodules ou esconda-as para prosseguir" #: builtin/mv.c:90 #, c-format @@ -9503,7 +9460,8 @@ msgid "" "Automatic notes merge failed. Fix conflicts in %s and commit the result with " "'git notes merge --commit', or abort the merge with 'git notes merge --" "abort'.\n" -msgstr "A integração automática de notas falhou. Corrija os conflitos em %s e " +msgstr "" +"A integração automática de notas falhou. Corrija os conflitos em %s e " "submeta o resultado com 'git notes merge --commit' ou aborte a integração " "com 'git notes merge --abort'.\n" @@ -10326,11 +10284,11 @@ msgstr "depurar unpack-trees" msgid "git receive-pack <git-dir>" msgstr "git receive-pack <git-dir>" -#: builtin/receive-pack.c:1845 +#: builtin/receive-pack.c:1843 msgid "quiet" msgstr "silencioso" -#: builtin/receive-pack.c:1859 +#: builtin/receive-pack.c:1857 msgid "You must specify a directory." msgstr "Deve especificar um diretório." @@ -11400,7 +11358,8 @@ msgstr "imprimir estado do programa auxiliar remoto" #: builtin/shortlog.c:13 msgid "git shortlog [<options>] [<revision-range>] [[--] [<path>...]]" -msgstr "git shortlog [<opções>] [<intervalo-de-revisões>] [[--] [<caminho>...]]" +msgstr "" +"git shortlog [<opções>] [<intervalo-de-revisões>] [[--] [<caminho>...]]" #: builtin/shortlog.c:242 msgid "sort output according to the number of commits per author" @@ -12222,8 +12181,7 @@ msgstr "eliminar a referência" #: builtin/update-ref.c:365 msgid "update <refname> not the one it points to" msgstr "" -"atualizar a referência <nome-da-referência>, não a referência que esta " -"aponta" +"atualizar a referência <nome-da-referência>, não a referência que esta aponta" #: builtin/update-ref.c:366 msgid "stdin has NUL-terminated arguments" @@ -12597,7 +12555,8 @@ msgstr "HEAD incorreta - é necessário uma HEAD" #, sh-format msgid "" "Checking out '$start_head' failed. Try 'git bisect reset <valid-branch>'." -msgstr "Falha ao extrair '$start_head'. Tente 'git bisect reset <ramo-válido>'." +msgstr "" +"Falha ao extrair '$start_head'. Tente 'git bisect reset <ramo-válido>'." #: git-bisect.sh:177 msgid "won't bisect on cg-seek'ed tree" @@ -12813,8 +12772,8 @@ msgid "" msgstr "" "Depois de resolver este problema, execute \"git rebase --continue\".\n" "Se prefere ignorar este patch, execute \"git rebase --skip\".\n" -"Para extrair o ramo original e interromper o rebase, execute \"git rebase " -"--abort\"." +"Para extrair o ramo original e interromper o rebase, execute \"git rebase --" +"abort\"." #: git-rebase.sh:156 git-rebase.sh:395 #, sh-format @@ -13477,167 +13436,43 @@ msgstr "Erro ao refazer integração $sha1" msgid "Could not pick $sha1" msgstr "Não foi possÃvel apanhar $sha1" -#: git-rebase--interactive.sh:408 git-rebase--interactive.sh:474 -msgid "This is the 1st commit message:" -msgstr "Esta é a 1ª mensagem de commit:" - -#: git-rebase--interactive.sh:409 -msgid "This is the 2nd commit message:" -msgstr "Esta é a 2ª mensagem de commit:" - -#: git-rebase--interactive.sh:410 -msgid "This is the 3rd commit message:" -msgstr "Esta é a 3ª mensagem de commit:" - -#: git-rebase--interactive.sh:411 -msgid "This is the 4th commit message:" -msgstr "Esta é a 4ª mensagem de commit:" +#: git-rebase--interactive.sh:407 +#, sh-format +msgid "This is the commit message #${n}:" +msgstr "Esta é a mensagem de commit nº${n}:" #: git-rebase--interactive.sh:412 -msgid "This is the 5th commit message:" -msgstr "Esta é a 5ª mensagem de commit:" - -#: git-rebase--interactive.sh:413 -msgid "This is the 6th commit message:" -msgstr "Esta é a 6ª mensagem de commit:" - -#: git-rebase--interactive.sh:414 -msgid "This is the 7th commit message:" -msgstr "Esta é a 7ª mensagem de commit:" - -#: git-rebase--interactive.sh:415 -msgid "This is the 8th commit message:" -msgstr "Esta é a 8ª mensagem de commit:" - -#: git-rebase--interactive.sh:416 -msgid "This is the 9th commit message:" -msgstr "Esta é a 9ª mensagem de commit:" - -#: git-rebase--interactive.sh:417 -msgid "This is the 10th commit message:" -msgstr "Esta é a 10ª mensagem de commit:" - -#. TRANSLATORS: if the language you are translating into -#. doesn't allow you to compose a sentence in this fashion, -#. consider translating as if this and the following few strings -#. were "This is the commit message ${n}:" -#: git-rebase--interactive.sh:422 #, sh-format -msgid "This is the ${n}th commit message:" -msgstr "Esta é a ${n}ª mensagem de commit:" +msgid "The commit message #${n} will be skipped:" +msgstr "A mensagem de commit nº${n} será ignorada:" #: git-rebase--interactive.sh:423 #, sh-format -msgid "This is the ${n}st commit message:" -msgstr "Está é a ${n}ª mensagem de commit:" - -#: git-rebase--interactive.sh:424 -#, sh-format -msgid "This is the ${n}nd commit message:" -msgstr "Esta é a ${n}ª mensagem de commit:" - -#: git-rebase--interactive.sh:425 -#, sh-format -msgid "This is the ${n}rd commit message:" -msgstr "Esta é a ${n}ª mensagem de commit:" - -#: git-rebase--interactive.sh:426 -#, sh-format -msgid "This is the commit message ${n}:" -msgstr "Esta é a mensagem de commit ${n}:" - -#: git-rebase--interactive.sh:432 -msgid "The 1st commit message will be skipped:" -msgstr "A 1ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:433 -msgid "The 2nd commit message will be skipped:" -msgstr "A 2ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:434 -msgid "The 3rd commit message will be skipped:" -msgstr "A 3ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:435 -msgid "The 4th commit message will be skipped:" -msgstr "A 4ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:436 -msgid "The 5th commit message will be skipped:" -msgstr "A 5ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:437 -msgid "The 6th commit message will be skipped:" -msgstr "A 6ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:438 -msgid "The 7th commit message will be skipped:" -msgstr "A 7ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:439 -msgid "The 8th commit message will be skipped:" -msgstr "A 8ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:440 -msgid "The 9th commit message will be skipped:" -msgstr "A 9ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:441 -msgid "The 10th commit message will be skipped:" -msgstr "A 10ª mensagem de commit será ignorada:" - -#. TRANSLATORS: if the language you are translating into -#. doesn't allow you to compose a sentence in this fashion, -#. consider translating as if this and the following few strings -#. were "The commit message ${n} will be skipped:" -#: git-rebase--interactive.sh:446 -#, sh-format -msgid "The ${n}th commit message will be skipped:" -msgstr "A ${n}ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:447 -#, sh-format -msgid "The ${n}st commit message will be skipped:" -msgstr "A ${n}ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:448 -#, sh-format -msgid "The ${n}nd commit message will be skipped:" -msgstr "A ${n}ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:449 -#, sh-format -msgid "The ${n}rd commit message will be skipped:" -msgstr "A ${n}ª mensagem de commit será ignorada:" - -#: git-rebase--interactive.sh:450 -#, sh-format -msgid "The commit message ${n} will be skipped:" -msgstr "A mensagem de commit ${n} será ignorada:" - -#: git-rebase--interactive.sh:462 -#, sh-format msgid "This is a combination of $count commit." msgid_plural "This is a combination of $count commits." msgstr[0] "Isto é a combinação de $count commit." msgstr[1] "Isto é a combinação de $count commits." -#: git-rebase--interactive.sh:470 +#: git-rebase--interactive.sh:431 #, sh-format msgid "Cannot write $fixup_msg" msgstr "Não é possÃvel escrever $fixup_msg" -#: git-rebase--interactive.sh:473 +#: git-rebase--interactive.sh:434 msgid "This is a combination of 2 commits." msgstr "Isto é a combinação de 2 commits." -#: git-rebase--interactive.sh:514 git-rebase--interactive.sh:557 -#: git-rebase--interactive.sh:560 +#: git-rebase--interactive.sh:435 +msgid "This is the 1st commit message:" +msgstr "Esta é a 1ª mensagem de commit:" + +#: git-rebase--interactive.sh:475 git-rebase--interactive.sh:518 +#: git-rebase--interactive.sh:521 #, sh-format msgid "Could not apply $sha1... $rest" msgstr "Não foi possÃvel aplicar $sha1... $rest" -#: git-rebase--interactive.sh:588 +#: git-rebase--interactive.sh:549 #, sh-format msgid "" "Could not amend commit after successfully picking $sha1... $rest\n" @@ -13654,31 +13489,31 @@ msgstr "" "poder\n" "reformular a mensagem do commit." -#: git-rebase--interactive.sh:603 +#: git-rebase--interactive.sh:564 #, sh-format msgid "Stopped at $sha1_abbrev... $rest" msgstr "Parou em $sha1_abbrev... $rest" -#: git-rebase--interactive.sh:618 +#: git-rebase--interactive.sh:579 #, sh-format msgid "Cannot '$squash_style' without a previous commit" msgstr "Não é possÃvel efetuar '$squash_style' sem um commit anterior" -#: git-rebase--interactive.sh:660 +#: git-rebase--interactive.sh:621 #, sh-format msgid "Executing: $rest" msgstr "A executar: $rest" -#: git-rebase--interactive.sh:668 +#: git-rebase--interactive.sh:629 #, sh-format msgid "Execution failed: $rest" msgstr "Falha ao executar: $rest" -#: git-rebase--interactive.sh:670 +#: git-rebase--interactive.sh:631 msgid "and made changes to the index and/or the working tree" msgstr "e fez alterações ao Ãndice e/ou à árvore de trabalho" -#: git-rebase--interactive.sh:672 +#: git-rebase--interactive.sh:633 msgid "" "You can fix the problem, and then run\n" "\n" @@ -13689,7 +13524,7 @@ msgstr "" "\tgit rebase --continue" #. TRANSLATORS: after these lines is a command to be issued by the user -#: git-rebase--interactive.sh:685 +#: git-rebase--interactive.sh:646 #, sh-format msgid "" "Execution succeeded: $rest\n" @@ -13704,25 +13539,25 @@ msgstr "" "\n" "\tgit rebase --continue" -#: git-rebase--interactive.sh:696 +#: git-rebase--interactive.sh:657 #, sh-format msgid "Unknown command: $command $sha1 $rest" msgstr "Comando desconhecido: $command $sha1 $rest" -#: git-rebase--interactive.sh:697 +#: git-rebase--interactive.sh:658 msgid "Please fix this using 'git rebase --edit-todo'." msgstr "Corrija-o usando 'git rebase --edit-todo'." -#: git-rebase--interactive.sh:732 +#: git-rebase--interactive.sh:693 #, sh-format msgid "Successfully rebased and updated $head_name." msgstr "$head_name rebaseado e atualizado com sucesso." -#: git-rebase--interactive.sh:779 +#: git-rebase--interactive.sh:740 msgid "Could not skip unnecessary pick commands" msgstr "Não foi possÃvel saltar comandos pick desnecessários" -#: git-rebase--interactive.sh:937 +#: git-rebase--interactive.sh:898 #, sh-format msgid "" "Warning: the SHA-1 is missing or isn't a commit in the following line:\n" @@ -13731,7 +13566,7 @@ msgstr "" "Aviso: falta o SHA-1 ou a seguinte linha não tem um commit:\n" " - $line" -#: git-rebase--interactive.sh:970 +#: git-rebase--interactive.sh:931 #, sh-format msgid "" "Warning: the command isn't recognized in the following line:\n" @@ -13740,11 +13575,11 @@ msgstr "" "Aviso: o comando não é reconhecido na seguinte linha:\n" " - $line" -#: git-rebase--interactive.sh:1009 +#: git-rebase--interactive.sh:970 msgid "could not detach HEAD" msgstr "não foi possÃvel destacar HEAD" -#: git-rebase--interactive.sh:1047 +#: git-rebase--interactive.sh:1008 msgid "" "Warning: some commits may have been dropped accidentally.\n" "Dropped commits (newer to older):" @@ -13752,7 +13587,7 @@ msgstr "" "Aviso: alguns commits podem ter sido descartados acidentalmente.\n" "Commits descartados (mais novo para o mais velho):" -#: git-rebase--interactive.sh:1055 +#: git-rebase--interactive.sh:1016 msgid "" "To avoid this message, use \"drop\" to explicitly remove a commit.\n" "\n" @@ -13760,12 +13595,13 @@ msgid "" "warnings.\n" "The possible behaviours are: ignore, warn, error." msgstr "" -"Para evitar esta mensagem, use \"drop\" para remover um commit explicitamente.\n" +"Para evitar esta mensagem, use \"drop\" para remover um commit " +"explicitamente.\n" "\n" "Use 'git config rebase.missingCommitsCheck' para mudar o nÃvel de avisos.\n" "O comportamentos possÃveis são: ignore, warn, error." -#: git-rebase--interactive.sh:1066 +#: git-rebase--interactive.sh:1027 #, sh-format msgid "" "Unrecognized setting $check_level for option rebase.missingCommitsCheck. " @@ -13774,19 +13610,19 @@ msgstr "" "Definição $check_level desconhecida da opção rebase.missingCommitsCheck. " "Ignorado." -#: git-rebase--interactive.sh:1083 +#: git-rebase--interactive.sh:1044 msgid "You can fix this with 'git rebase --edit-todo'." msgstr "Pode corrÃ-lo com 'git rebase --edit-todo'." -#: git-rebase--interactive.sh:1084 +#: git-rebase--interactive.sh:1045 msgid "Or you can abort the rebase with 'git rebase --abort'." msgstr "Ou pode abortar o rebase com 'git rebase --abort'." -#: git-rebase--interactive.sh:1108 +#: git-rebase--interactive.sh:1069 msgid "Could not remove CHERRY_PICK_HEAD" msgstr "Não foi possÃvel remover CHERRY_PICK_HEAD" -#: git-rebase--interactive.sh:1113 +#: git-rebase--interactive.sh:1074 #, sh-format msgid "" "You have staged changes in your working tree.\n" @@ -13817,11 +13653,11 @@ msgstr "" "\n" " git rebase --continue\n" -#: git-rebase--interactive.sh:1130 +#: git-rebase--interactive.sh:1091 msgid "Error trying to find the author identity to amend commit" msgstr "Erro ao tentar encontrar a identidade do autor para emendar o commit" -#: git-rebase--interactive.sh:1135 +#: git-rebase--interactive.sh:1096 msgid "" "You have uncommitted changes in your working tree. Please commit them\n" "first and then run 'git rebase --continue' again." @@ -13829,11 +13665,11 @@ msgstr "" "Tem alterações por submeter na árvore de trabalho. Submeta-as primeiro\n" "e execute 'git rebase --continue' de novo." -#: git-rebase--interactive.sh:1140 git-rebase--interactive.sh:1144 +#: git-rebase--interactive.sh:1101 git-rebase--interactive.sh:1105 msgid "Could not commit staged changes." msgstr "Não foi possÃvel submeter as alterações preparadas." -#: git-rebase--interactive.sh:1168 +#: git-rebase--interactive.sh:1129 msgid "" "\n" "You are editing the todo file of an ongoing interactive rebase.\n" @@ -13847,44 +13683,44 @@ msgstr "" " git rebase --continue\n" "\n" -#: git-rebase--interactive.sh:1176 git-rebase--interactive.sh:1337 +#: git-rebase--interactive.sh:1137 git-rebase--interactive.sh:1298 msgid "Could not execute editor" msgstr "Não foi possÃvel executar o editor" -#: git-rebase--interactive.sh:1184 +#: git-rebase--interactive.sh:1145 msgid "You need to set your committer info first" msgstr "Primeiro tem de definir a sua informação de committer" -#: git-rebase--interactive.sh:1192 +#: git-rebase--interactive.sh:1153 #, sh-format msgid "Could not checkout $switch_to" msgstr "Não foi possÃvel extrair $switch_to" -#: git-rebase--interactive.sh:1197 +#: git-rebase--interactive.sh:1158 msgid "No HEAD?" msgstr "Sem HEAD?" -#: git-rebase--interactive.sh:1198 +#: git-rebase--interactive.sh:1159 #, sh-format msgid "Could not create temporary $state_dir" msgstr "Não foi possÃvel criar $state_dir temporário" -#: git-rebase--interactive.sh:1200 +#: git-rebase--interactive.sh:1161 msgid "Could not mark as interactive" msgstr "Não foi possÃvel marcar como interativo" -#: git-rebase--interactive.sh:1210 git-rebase--interactive.sh:1215 +#: git-rebase--interactive.sh:1171 git-rebase--interactive.sh:1176 msgid "Could not init rewritten commits" msgstr "Não foi possÃvel inicializar commits reescritos" -#: git-rebase--interactive.sh:1315 +#: git-rebase--interactive.sh:1276 #, sh-format msgid "Rebase $shortrevisions onto $shortonto ($todocount command)" msgid_plural "Rebase $shortrevisions onto $shortonto ($todocount commands)" msgstr[0] "Rebase $shortrevisions sobre $shortonto ($todocount comando)" msgstr[1] "Rebase $shortrevisions sobre $shortonto ($todocount comandos)" -#: git-rebase--interactive.sh:1320 +#: git-rebase--interactive.sh:1281 msgid "" "\n" "However, if you remove everything, the rebase will be aborted.\n" @@ -13894,7 +13730,7 @@ msgstr "" "No entanto, se remover tudo, o rebase será abortado.\n" "\n" -#: git-rebase--interactive.sh:1327 +#: git-rebase--interactive.sh:1288 msgid "Note that empty commits are commented out" msgstr "Note que commits vazios são comentados" @@ -13946,6 +13782,123 @@ msgstr "" msgid "Unable to determine absolute path of git directory" msgstr "Não é possÃvel determinar o caminho absoluto do diretório git" +#~ msgid "bad numeric config value '%s' for '%s': invalid unit" +#~ msgstr "" +#~ "valor numérico '%s' da configuração '%s' incorreto: unidade inválida" + +#~ msgid "bad numeric config value '%s' for '%s' in blob %s: invalid unit" +#~ msgstr "" +#~ "valor numérico '%s' da configuração '%s' incorreto no blob %s: unidade " +#~ "inválida" + +#~ msgid "bad numeric config value '%s' for '%s' in file %s: invalid unit" +#~ msgstr "" +#~ "valor numérico '%s' da configuração '%s' incorreto no ficheiro %s: " +#~ "unidade inválida" + +#~ msgid "" +#~ "bad numeric config value '%s' for '%s' in standard input: invalid unit" +#~ msgstr "" +#~ "valor numérico '%s' da configuração '%s' incorreto na entrada padrão: " +#~ "unidade inválida" + +#~ msgid "" +#~ "bad numeric config value '%s' for '%s' in submodule-blob %s: invalid unit" +#~ msgstr "" +#~ "valor numérico '%s' da configuração '%s' incorreto no submódulo-blob %s: " +#~ "unidade inválida" + +#~ msgid "" +#~ "bad numeric config value '%s' for '%s' in command line %s: invalid unit" +#~ msgstr "" +#~ "valor numérico '%s' da configuração '%s' incorreto na linha de comandos " +#~ "%s: unidade inválida" + +#~ msgid "bad numeric config value '%s' for '%s' in %s: invalid unit" +#~ msgstr "" +#~ "valor numérico '%s' da configuração '%s' incorreto em %s: unidade inválida" + +#~ msgid "This is the 2nd commit message:" +#~ msgstr "Esta é a 2ª mensagem de commit:" + +#~ msgid "This is the 3rd commit message:" +#~ msgstr "Esta é a 3ª mensagem de commit:" + +#~ msgid "This is the 4th commit message:" +#~ msgstr "Esta é a 4ª mensagem de commit:" + +#~ msgid "This is the 5th commit message:" +#~ msgstr "Esta é a 5ª mensagem de commit:" + +#~ msgid "This is the 6th commit message:" +#~ msgstr "Esta é a 6ª mensagem de commit:" + +#~ msgid "This is the 7th commit message:" +#~ msgstr "Esta é a 7ª mensagem de commit:" + +#~ msgid "This is the 8th commit message:" +#~ msgstr "Esta é a 8ª mensagem de commit:" + +#~ msgid "This is the 9th commit message:" +#~ msgstr "Esta é a 9ª mensagem de commit:" + +#~ msgid "This is the 10th commit message:" +#~ msgstr "Esta é a 10ª mensagem de commit:" + +#~ msgid "This is the ${n}th commit message:" +#~ msgstr "Esta é a ${n}ª mensagem de commit:" + +#~ msgid "This is the ${n}st commit message:" +#~ msgstr "Está é a ${n}ª mensagem de commit:" + +#~ msgid "This is the ${n}nd commit message:" +#~ msgstr "Esta é a ${n}ª mensagem de commit:" + +#~ msgid "This is the ${n}rd commit message:" +#~ msgstr "Esta é a ${n}ª mensagem de commit:" + +#~ msgid "The 1st commit message will be skipped:" +#~ msgstr "A 1ª mensagem de commit será ignorada:" + +#~ msgid "The 2nd commit message will be skipped:" +#~ msgstr "A 2ª mensagem de commit será ignorada:" + +#~ msgid "The 3rd commit message will be skipped:" +#~ msgstr "A 3ª mensagem de commit será ignorada:" + +#~ msgid "The 4th commit message will be skipped:" +#~ msgstr "A 4ª mensagem de commit será ignorada:" + +#~ msgid "The 5th commit message will be skipped:" +#~ msgstr "A 5ª mensagem de commit será ignorada:" + +#~ msgid "The 6th commit message will be skipped:" +#~ msgstr "A 6ª mensagem de commit será ignorada:" + +#~ msgid "The 7th commit message will be skipped:" +#~ msgstr "A 7ª mensagem de commit será ignorada:" + +#~ msgid "The 8th commit message will be skipped:" +#~ msgstr "A 8ª mensagem de commit será ignorada:" + +#~ msgid "The 9th commit message will be skipped:" +#~ msgstr "A 9ª mensagem de commit será ignorada:" + +#~ msgid "The 10th commit message will be skipped:" +#~ msgstr "A 10ª mensagem de commit será ignorada:" + +#~ msgid "The ${n}th commit message will be skipped:" +#~ msgstr "A ${n}ª mensagem de commit será ignorada:" + +#~ msgid "The ${n}st commit message will be skipped:" +#~ msgstr "A ${n}ª mensagem de commit será ignorada:" + +#~ msgid "The ${n}nd commit message will be skipped:" +#~ msgstr "A ${n}ª mensagem de commit será ignorada:" + +#~ msgid "The ${n}rd commit message will be skipped:" +#~ msgstr "A ${n}ª mensagem de commit será ignorada:" + #~ msgid "could not run gpg." #~ msgstr "não foi possÃvel executar gpg." @@ -14034,7 +13987,8 @@ msgstr "Não é possÃvel determinar o caminho absoluto do diretório git" #~ "use the '--force' option. If the local git directory is not the correct " #~ "repo" #~ msgstr "" -#~ "use a opção '--force'. Se o diretório git local não é o repositório correto" +#~ "use a opção '--force'. Se o diretório git local não é o repositório " +#~ "correto" #~ msgid "" #~ "or you are unsure what this means choose another name with the '--name' " @@ -14140,7 +14094,8 @@ msgstr "Não é possÃvel determinar o caminho absoluto do diretório git" #~ "Quando push.default está definido como 'matching', o Git publica\n" #~ "os ramos locais nos ramos remotos que já existam com o mesmo nome.\n" #~ "\n" -#~ "Desde a versão 2.0, o Git assume o comportamento mais conservativo 'simple',\n" +#~ "Desde a versão 2.0, o Git assume o comportamento mais conservativo " +#~ "'simple',\n" #~ "publicando só o ramo atual no ramo remoto correspondente, que é usado\n" #~ "para atualizar o ramo atual com 'git pull'.\n" #~ "Consulte 'git help config' e procure por 'push.default' para mais " diff --git a/po/zh_CN.po b/po/zh_CN.po index ac7f5b4cb1..dc312227e0 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -136,7 +136,7 @@ msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" "POT-Creation-Date: 2016-08-27 23:21+0800\n" -"PO-Revision-Date: 2016-08-31 00:11+0800\n" +"PO-Revision-Date: 2016-09-05 23:36+0800\n" "Last-Translator: Jiang Xin <worldhello.net@gmail.com>\n" "Language-Team: GitHub <https://github.com/jiangxin/git/>\n" "Language: zh_CN\n" @@ -319,7 +319,7 @@ msgstr "远程 git-upload-archive 命令的路径" #: archive.c:461 msgid "Unexpected option --remote" -msgstr "æ„外的选项 --remote" +msgstr "未知å‚æ•° --remote" #: archive.c:463 msgid "Option --exec can only be used together with --remote" @@ -419,7 +419,7 @@ msgstr "" #: bisect.c:798 #, c-format msgid "Bisecting: a merge base must be tested\n" -msgstr "二分查找ä¸ï¼šåˆå¹¶åŸºçº¿å¿…é¡»è¦æµ‹è¯•\n" +msgstr "二分查找ä¸ï¼šåˆå¹¶åŸºçº¿å¿…须是ç»è¿‡æµ‹è¯•çš„\n" #: bisect.c:849 #, c-format @@ -467,8 +467,8 @@ msgstr[1] "(大概 %d æ¥ï¼‰" #, c-format msgid "Bisecting: %d revision left to test after this %s\n" msgid_plural "Bisecting: %d revisions left to test after this %s\n" -msgstr[0] "二分查找ä¸ï¼šåœ¨ %2$s 之åŽè¿˜å‰©ä¸‹ %1$d 个版本\n" -msgstr[1] "二分查找ä¸ï¼šåœ¨ %2$s 之åŽè¿˜å‰©ä¸‹ %1$d 个版本\n" +msgstr[0] "二分查找ä¸ï¼šåœ¨æ¤ä¹‹åŽï¼Œè¿˜å‰© %d 个版本待测试 %s\n" +msgstr[1] "二分查找ä¸ï¼šåœ¨æ¤ä¹‹åŽï¼Œè¿˜å‰© %d 个版本待测试 %s\n" #: branch.c:53 #, c-format @@ -720,7 +720,7 @@ msgstr "æ ‡å‡†è¾“å…¥ä¸é”™è¯¯çš„é…置行 %d" #: config.c:528 #, c-format msgid "bad config line %d in submodule-blob %s" -msgstr "åæ¨¡ç»„æ•°æ® %2$s ä¸é”™è¯¯çš„é…置行 %1$d" +msgstr "å模组数æ®å¯¹è±¡ %2$s ä¸é”™è¯¯çš„é…置行 %1$d" #: config.c:532 #, c-format @@ -743,37 +743,37 @@ msgstr "æ— æ•ˆçš„å•ä½" #: config.c:661 #, c-format msgid "bad numeric config value '%s' for '%s': %s" -msgstr "é…ç½®å˜é‡ '%2$s' çš„æ•°å—å–值 '%1$s' 设置错误: %3$s" +msgstr "é…ç½®å˜é‡ '%2$s' çš„æ•°å—å–值 '%1$s' 设置错误:%3$s" #: config.c:666 #, c-format msgid "bad numeric config value '%s' for '%s' in blob %s: %s" -msgstr "æ•°æ®å¯¹è±¡ %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s': %4$s" +msgstr "æ•°æ®å¯¹è±¡ %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s':%4$s" #: config.c:669 #, c-format msgid "bad numeric config value '%s' for '%s' in file %s: %s" -msgstr "文件 %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s': %4$s" +msgstr "文件 %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s':%4$s" #: config.c:672 #, c-format msgid "bad numeric config value '%s' for '%s' in standard input: %s" -msgstr "æ ‡å‡†è¾“å…¥ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s': %3$s" +msgstr "æ ‡å‡†è¾“å…¥ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s':%3$s" #: config.c:675 #, c-format msgid "bad numeric config value '%s' for '%s' in submodule-blob %s: %s" -msgstr "åæ¨¡ç»„æ•°æ® %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s': %4$s" +msgstr "åæ¨¡ç»„æ•°æ® %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s':%4$s" #: config.c:678 #, c-format msgid "bad numeric config value '%s' for '%s' in command line %s: %s" -msgstr "命令行 %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s': %4$s" +msgstr "命令行 %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s':%4$s" #: config.c:681 #, c-format msgid "bad numeric config value '%s' for '%s' in %s: %s" -msgstr "在 %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s': %4$s" +msgstr "在 %3$s ä¸é…ç½®å˜é‡ '%2$s' 错误的å–值 '%1$s':%4$s" #: config.c:768 #, c-format @@ -949,7 +949,7 @@ msgstr "外部 diff 退出,åœæ¢åœ¨ %s" #: diff.c:3415 msgid "--follow requires exactly one pathspec" -msgstr "--follow å‚æ•°åŽåªè·Ÿä¸€ä¸ª pathspec" +msgstr "--follow 明确è¦æ±‚åªè·Ÿä¸€ä¸ªè·¯å¾„è§„æ ¼" #: diff.c:3578 #, c-format @@ -1033,7 +1033,7 @@ msgid "" "WARNING: You called a Git command named '%s', which does not exist.\n" "Continuing under the assumption that you meant '%s'" msgstr "" -"è¦å‘Šï¼šæ‚¨è¿è¡Œä¸€ä¸ªä¸å˜åœ¨çš„ Git 命令 '%s'。继ç»æ‰§è¡Œå‡å®šæ‚¨è¦è¦è¿è¡Œçš„\n" +"è¦å‘Šï¼šæ‚¨è¿è¡Œä¸€ä¸ªä¸å˜åœ¨çš„ Git 命令 '%s'。继ç»æ‰§è¡Œå‡å®šæ‚¨è¦è¿è¡Œçš„\n" "是 '%s'" #: help.c:393 @@ -2059,7 +2059,7 @@ msgid "" "Use '--' to separate paths from revisions, like this:\n" "'git <command> [<revision>...] -- [<file>...]'" msgstr "" -"有æ§ä¹‰çš„å‚æ•° '%s':工作区ä¸æœªçŸ¥çš„版本或路径。\n" +"有æ§ä¹‰çš„å‚æ•° '%s':未知的版本或路径ä¸å˜åœ¨äºŽå·¥ä½œåŒºä¸ã€‚\n" "使用 '--' æ¥åˆ†éš”版本和路径,例如:\n" "'git <command> [<revision>...] -- [<file>...]'" @@ -2070,7 +2070,7 @@ msgid "" "Use '--' to separate paths from revisions, like this:\n" "'git <command> [<revision>...] -- [<file>...]'" msgstr "" -"有æ§ä¹‰çš„å‚æ•° '%s':两者å‡ä¸ºç‰ˆæœ¬å’Œæ–‡ä»¶\n" +"有æ§ä¹‰çš„å‚æ•° '%s':å¯åŒæ—¶æ˜¯ç‰ˆæœ¬å’Œæ–‡ä»¶\n" "使用 '--' æ¥åˆ†éš”版本和路径,例如:\n" "'git <command> [<revision>...] -- [<file>...]'" @@ -2107,7 +2107,7 @@ msgid "" "Not a git repository (or any parent up to mount point %s)\n" "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)." msgstr "" -"ä¸æ˜¯ä¸€ä¸ª git 仓库(或者任何å‘上递归到挂载点 %s)\n" +"ä¸æ˜¯ä¸€ä¸ª git 仓库(或者å‘上递归至挂载点 %s 的任何祖先目录)\n" "åœæ¢åœ¨æ–‡ä»¶ç³»ç»Ÿè¾¹ç•Œï¼ˆæœªè®¾ç½® GIT_DISCOVERY_ACROSS_FILESYSTEM)。" #: setup.c:927 @@ -2121,7 +2121,7 @@ msgid "" "Problem with core.sharedRepository filemode value (0%.3o).\n" "The owner of files must always have read and write permissions." msgstr "" -"å‚æ•° core.sharedRepository 的文件æƒé™å€¼æœ‰é”™(0%.3o)。\n" +"å‚æ•° core.sharedRepository 的文件æƒé™å€¼æœ‰é”™ï¼ˆ0%.3o)。\n" "文件属主必须始终拥有读写æƒé™ã€‚" #: sha1_file.c:1046 @@ -2260,7 +2260,7 @@ msgstr "ä¼ è¾“ï¼šæ— æ•ˆçš„æ·±åº¦é€‰é¡¹ '%s'" msgid "" "The following submodule paths contain changes that can\n" "not be found on any remote:\n" -msgstr "如下的å模组路径的修改在任何远程æºä¸éƒ½æ‰¾ä¸åˆ°ï¼š\n" +msgstr "下列å模组路径所包å«çš„修改在任何远程æºä¸éƒ½æ‰¾ä¸åˆ°ï¼š\n" #: transport.c:775 #, c-format @@ -2286,7 +2286,7 @@ msgstr "" "\n" "\tgit push\n" "\n" -"æ¥æŽ¨é€åˆ°è¿œç¨‹æºã€‚\n" +"以推é€è‡³è¿œç¨‹ã€‚\n" "\n" #: transport.c:783 @@ -6815,7 +6815,7 @@ msgstr "[已拒ç»]" #: builtin/fetch.c:610 msgid "can't fetch in current branch" -msgstr "ä¸èƒ½èŽ·å–到当å‰åˆ†æ”¯" +msgstr "当å‰åˆ†æ”¯ä¸‹ä¸èƒ½æ‰§è¡ŒèŽ·å–æ“作" #: builtin/fetch.c:619 msgid "[tag update]" @@ -7922,12 +7922,12 @@ msgstr "é‡æ–°åˆå§‹åŒ–å·²å˜åœ¨çš„ Git 仓库于 %s%s\n" #: builtin/init-db.c:407 #, c-format msgid "Initialized empty shared Git repository in %s%s\n" -msgstr "åˆå§‹åŒ–空的共享 Git 仓库于 %s%s\n" +msgstr "å·²åˆå§‹åŒ–空的共享 Git 仓库于 %s%s\n" #: builtin/init-db.c:408 #, c-format msgid "Initialized empty Git repository in %s%s\n" -msgstr "åˆå§‹åŒ–空的 Git 仓库于 %s%s\n" +msgstr "å·²åˆå§‹åŒ–空的 Git 仓库于 %s%s\n" #: builtin/init-db.c:455 msgid "" @@ -8718,7 +8718,7 @@ msgstr "对于 %s 没有æ¥è‡ª %s 的远程跟踪分支" #: builtin/merge.c:960 #, c-format msgid "Bad value '%s' in environment '%s'" -msgstr "环境 '%2$s' åçš„å–值 '%1$s'" +msgstr "环境 '%2$s' ä¸å˜åœ¨åçš„å–值 '%1$s'" #: builtin/merge.c:1034 #, c-format @@ -9378,7 +9378,7 @@ msgstr "ä¸èƒ½æ··ç”¨ --commitã€--abort 或 -s/--strategy" #: builtin/notes.c:797 msgid "Must specify a notes ref to merge" -msgstr "您必须指定一个注解引用æ¥åˆå¹¶" +msgstr "必须指定一个注解引用æ¥åˆå¹¶" #: builtin/notes.c:821 #, c-format @@ -10091,7 +10091,7 @@ msgstr "server-specific" #: builtin/push.c:546 msgid "option to transmit" -msgstr "ä¼ è¾“çš„é€‰é¡¹" +msgstr "ä¼ è¾“é€‰é¡¹" #: builtin/push.c:560 msgid "--delete is incompatible with --all, --mirror and --tags" @@ -11566,7 +11566,7 @@ msgstr "并å‘任务" #: builtin/submodule--helper.c:840 msgid "whether the initial clone should follow the shallow recommendation" -msgstr "是å¦åˆå§‹å…‹éš†åº”该éµå®ˆæŽ¨è的浅克隆选项" +msgstr "åˆå§‹å…‹éš†æ˜¯å¦åº”该éµå®ˆæŽ¨è的浅克隆选项" #: builtin/submodule--helper.c:841 msgid "don't print cloning progress" @@ -12422,17 +12422,17 @@ msgstr "åçš„ bisect_write å‚数:$state" #: git-bisect.sh:262 #, sh-format msgid "Bad rev input: $arg" -msgstr "输入å的版本:$arg" +msgstr "å的输入版本:$arg" #: git-bisect.sh:281 #, sh-format msgid "Bad rev input: $bisected_head" -msgstr "输入å的版本:$bisected_head" +msgstr "å的输入版本:$bisected_head" #: git-bisect.sh:290 #, sh-format msgid "Bad rev input: $rev" -msgstr "输入å的版本:$rev" +msgstr "å的输入版本:$rev" #: git-bisect.sh:299 #, sh-format @@ -12579,7 +12579,7 @@ msgstr "错误:您对下列文件的本地修改将被åˆå¹¶æ“作覆盖" #: git-merge-octopus.sh:61 msgid "Automated merge did not work." -msgstr "自动åˆå¹¶ä¸å·¥ä½œ" +msgstr "自动åˆå¹¶æœªç”Ÿæ•ˆã€‚" #: git-merge-octopus.sh:62 msgid "Should not be doing an Octopus." @@ -12607,7 +12607,7 @@ msgstr "å°è¯•å’Œ $pretty_name 的简å•åˆå¹¶" #: git-merge-octopus.sh:102 msgid "Simple merge did not work, trying automatic merge." -msgstr "简å•åˆå¹¶ä¸è¡Œï¼Œå°è¯•è‡ªåŠ¨åˆå¹¶ã€‚" +msgstr "简å•åˆå¹¶æœªç”Ÿæ•ˆï¼Œå°è¯•è‡ªåŠ¨åˆå¹¶ã€‚" #: git-rebase.sh:56 msgid "" @@ -12881,7 +12881,7 @@ msgstr "索引的进度没有被æ¢å¤ã€‚" #: git-stash.sh:551 msgid "The stash is kept in case you need it again." -msgstr "进度ä¿å˜ä»¥å…您å†éœ€è¦ã€‚" +msgstr "æš‚å˜è¢«ä¿ç•™ä»¥å¤‡æ‚¨å†æ¬¡éœ€è¦ã€‚" #: git-stash.sh:560 #, sh-format @@ -13003,7 +13003,7 @@ msgid "" "(use 'rm -rf' if you really want to remove it including all of its history)" msgstr "" "å模组工作区 '$displaypath' 包å«ä¸€ä¸ª .git 目录\n" -"(使用 'rm -rf' å‘½ä»¤å¦‚æžœæ‚¨çœŸçš„æƒ³åˆ é™¤å®ƒåŠå…¶å…¨éƒ¨åŽ†å²ï¼‰" +"ï¼ˆå¦‚æžœæ‚¨çœŸçš„æƒ³åˆ é™¤å®ƒåŠå…¶å…¨éƒ¨åŽ†å²ï¼Œä½¿ç”¨ 'rm -rf' 命令)" #: git-submodule.sh:447 #, sh-format @@ -13047,7 +13047,7 @@ msgstr "æ— æ³•åœ¨å模组路径 '$sm_path' ä¸èŽ·å–" msgid "" "Unable to find current ${remote_name}/${branch} revision in submodule path " "'$sm_path'" -msgstr "æ— æ³•åœ¨å模组路径 '$sm_path' ä¸æ‰¾åˆ°å½“å‰ ${remote_name}/${branch} 版本" +msgstr "æ— æ³•åœ¨å模组路径 '$sm_path' ä¸æ‰¾åˆ°å½“å‰ç‰ˆæœ¬ ${remote_name}/${branch}" #: git-submodule.sh:645 #, sh-format @@ -13181,7 +13181,7 @@ msgstr "" " x, exec = 使用 shell è¿è¡Œå‘½ä»¤ï¼ˆæ¤è¡Œå‰©ä½™éƒ¨åˆ†ï¼‰\n" " d, drop = åˆ é™¤æ交\n" "\n" -"这些行å¯ä»¥é‡æ–°æŽ’åºï¼Œä»Žä¸Šè‡³ä¸‹åœ°æ‰§è¡Œã€‚\n" +"这些行å¯ä»¥è¢«é‡æ–°æŽ’åºï¼›å®ƒä»¬ä¼šè¢«ä»Žä¸Šè‡³ä¸‹åœ°æ‰§è¡Œã€‚\n" "\n" #: git-rebase--interactive.sh:162 @@ -13194,7 +13194,7 @@ msgstr "\nä¸è¦åˆ 除任æ„一行。使用 'drop' 显å¼åœ°åˆ 除一个æ交〠msgid "" "\n" "If you remove a line here THAT COMMIT WILL BE LOST.\n" -msgstr "\nå¦‚æžœæ‚¨åœ¨è¿™é‡Œåˆ é™¤ä¸€è¡Œï¼Œè¿™ä¸ªæ交将会丢失。\n" +msgstr "\nå¦‚æžœæ‚¨åœ¨è¿™é‡Œåˆ é™¤ä¸€è¡Œï¼Œå¯¹åº”çš„æ交将会丢失。\n" #: git-rebase--interactive.sh:202 #, sh-format @@ -13211,7 +13211,7 @@ msgstr "" "\n" "\tgit commit --amend $gpg_sign_opt_quoted\n" "\n" -"一旦您对å˜æ›´æ»¡æ„,执行\n" +"当您对å˜æ›´æ„Ÿåˆ°æ»¡æ„,执行\n" "\n" "\tgit rebase --continue" @@ -13262,12 +13262,12 @@ msgstr "ä¸èƒ½æ‹£é€‰ $sha1" #: git-rebase--interactive.sh:407 #, sh-format msgid "This is the commit message #${n}:" -msgstr "这是æäº¤ä¿¡æ¯ #${n}:" +msgstr "这是æ交说明 #${n}:" #: git-rebase--interactive.sh:412 #, sh-format msgid "The commit message #${n} will be skipped:" -msgstr "æäº¤ä¿¡æ¯ #${n} 将被跳过:" +msgstr "æ交说明 #${n} 将被跳过:" #: git-rebase--interactive.sh:423 #, sh-format @@ -13316,7 +13316,7 @@ msgstr "åœæ¢åœ¨ $sha1_abbrev... $rest" #: git-rebase--interactive.sh:579 #, sh-format msgid "Cannot '$squash_style' without a previous commit" -msgstr "ä¸èƒ½åœ¨æ²¡æœ‰å‰ä¸€ä¸ªæ交的情况下 '$squash_style'" +msgstr "没有父æ交的情况下ä¸èƒ½ '$squash_style'" #: git-rebase--interactive.sh:621 #, sh-format @@ -13370,7 +13370,7 @@ msgstr "è¦ä¿®æ”¹è¯·ä½¿ç”¨å‘½ä»¤ 'git rebase --edit-todo'。" #: git-rebase--interactive.sh:693 #, sh-format msgid "Successfully rebased and updated $head_name." -msgstr "æˆåŠŸå˜åŸºå¹¶æ›´æ–° $head_name." +msgstr "æˆåŠŸå˜åŸºå¹¶æ›´æ–° $head_name。" #: git-rebase--interactive.sh:740 msgid "Could not skip unnecessary pick commands" @@ -13412,17 +13412,17 @@ msgid "" "warnings.\n" "The possible behaviours are: ignore, warn, error." msgstr "" -"为é¿å…è¿™æ¡ä¿¡æ¯ï¼Œä½¿ç”¨ drop 指令显å¼åœ°åˆ 除一个æ交。\n" +"为é¿å…è¿™æ¡ä¿¡æ¯ï¼Œä½¿ç”¨ \"drop\" 指令显å¼åœ°åˆ 除一个æ交。\n" "\n" -"使用 'git config rebase.missingCommitsCheck' æ¥ä¿®æ”¹è¿™ä¸ªçº§åˆ«çš„è¦å‘Šã€‚\n" -"å¯ä»¥ä½¿ç”¨ï¼šignoreã€warnã€error。" +"使用 'git config rebase.missingCommitsCheck' æ¥ä¿®æ”¹è¦å‘Šçº§åˆ«ã€‚\n" +"å¯é€‰å€¼æœ‰ï¼šignoreã€warnã€error。" #: git-rebase--interactive.sh:1027 #, sh-format msgid "" "Unrecognized setting $check_level for option rebase.missingCommitsCheck. " "Ignoring." -msgstr "选项 rebase.missingCommitsCheck ä¸æ— 法识别的设置 $check_level。忽略。" +msgstr "选项 rebase.missingCommitsCheck 的值 $check_level æ— æ³•è¢«è¯†åˆ«ã€‚å·²å¿½ç•¥ã€‚" #: git-rebase--interactive.sh:1044 msgid "You can fix this with 'git rebase --edit-todo'." @@ -13461,19 +13461,19 @@ msgstr "" "\n" " git commit $gpg_sign_opt_quoted\n" "\n" -"两ç§æƒ…况下,一旦执行完毕,继ç»æ‰§è¡Œï¼š\n" +"æ— è®ºå“ªç§æƒ…况,当您完æˆæ交,继ç»æ‰§è¡Œï¼š\n" "\n" " git rebase --continue\n" #: git-rebase--interactive.sh:1091 msgid "Error trying to find the author identity to amend commit" -msgstr "当在修补æ交ä¸æŸ¥æ‰¾ä½œè€…ä¿¡æ¯æ—¶é‡åˆ°é”™è¯¯" +msgstr "在修补æ交ä¸æŸ¥æ‰¾ä½œè€…ä¿¡æ¯æ—¶é‡åˆ°é”™è¯¯" #: git-rebase--interactive.sh:1096 msgid "" "You have uncommitted changes in your working tree. Please commit them\n" "first and then run 'git rebase --continue' again." -msgstr "您的工作区ä¸æœ‰æœªæ交的å˜æ›´ã€‚请先æ交然åŽå†è¿è¡Œ 'git rebase --continue'。" +msgstr "您的工作区ä¸æœ‰æœªæ交的å˜æ›´ã€‚请先æ交然åŽå†æ¬¡è¿è¡Œ 'git rebase --continue'。" #: git-rebase--interactive.sh:1101 git-rebase--interactive.sh:1105 msgid "Could not commit staged changes." @@ -13488,7 +13488,7 @@ msgid "" "\n" msgstr "" "\n" -"您æ£åœ¨ä¿®æ”¹è¿è¡Œä¸çš„交互å¼å˜åŸºçš„ todo 文件。编辑结æŸåŽç»§ç»å˜åŸºï¼Œ\n" +"您æ£åœ¨ä¿®æ”¹è¿è¡Œä¸çš„交互å¼å˜åŸºçš„ todo 文件。若è¦åœ¨ç¼–辑结æŸåŽç»§ç»å˜åŸºï¼Œ\n" "请执行:\n" " git rebase --continue\n" "\n" @@ -13549,7 +13549,7 @@ msgstr "用法:$dashless $USAGE" #: git-sh-setup.sh:190 #, sh-format msgid "Cannot chdir to $cdup, the toplevel of the working tree" -msgstr "ä¸èƒ½æ›´æ¢ç›®å½•åˆ° $cdup,工作区的顶级目录" +msgstr "ä¸èƒ½åˆ‡æ¢ç›®å½•åˆ° $cdup,工作区的顶级目录" #: git-sh-setup.sh:199 git-sh-setup.sh:206 #, sh-format @@ -1065,13 +1065,15 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ const struct commit *commit = c->commit; const char *msg = c->message; struct commit_list *p; - int h1, h2; + int ch; /* these are independent of the commit */ switch (placeholder[0]) { case 'C': if (starts_with(placeholder + 1, "(auto)")) { c->auto_color = want_color(c->pretty_ctx->color); + if (c->auto_color) + strbuf_addstr(sb, GIT_COLOR_RESET); return 7; /* consumed 7 bytes, "C(auto)" */ } else { int ret = parse_color(sb, placeholder, c); @@ -1089,14 +1091,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ return 1; case 'x': /* %x00 == NUL, %x0a == LF, etc. */ - if (0 <= (h1 = hexval_table[0xff & placeholder[1]]) && - h1 <= 16 && - 0 <= (h2 = hexval_table[0xff & placeholder[2]]) && - h2 <= 16) { - strbuf_addch(sb, (h1<<4)|h2); - return 3; - } else + ch = hex2chr(placeholder + 1); + if (ch < 0) return 0; + strbuf_addch(sb, ch); + return 3; case 'w': if (placeholder[1] == '(') { unsigned long width = 0, indent1 = 0, indent2 = 0; diff --git a/read-cache.c b/read-cache.c index 491e52d120..31eddec5f7 100644 --- a/read-cache.c +++ b/read-cache.c @@ -161,7 +161,7 @@ static int ce_compare_data(const struct cache_entry *ce, struct stat *st) if (fd >= 0) { unsigned char sha1[20]; if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name, 0)) - match = hashcmp(sha1, ce->sha1); + match = hashcmp(sha1, ce->oid.hash); /* index_fd() closed the file descriptor already */ } return match; @@ -178,7 +178,7 @@ static int ce_compare_link(const struct cache_entry *ce, size_t expected_size) if (strbuf_readlink(&sb, ce->name, expected_size)) return -1; - buffer = read_sha1_file(ce->sha1, &type, &size); + buffer = read_sha1_file(ce->oid.hash, &type, &size); if (buffer) { if (size == sb.len) match = memcmp(buffer, sb.buf, size); @@ -202,7 +202,7 @@ static int ce_compare_gitlink(const struct cache_entry *ce) */ if (resolve_gitlink_ref(ce->name, "HEAD", sha1) < 0) return 0; - return hashcmp(sha1, ce->sha1); + return hashcmp(sha1, ce->oid.hash); } static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st) @@ -262,7 +262,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st) /* Racily smudged entry? */ if (!ce->ce_stat_data.sd_size) { - if (!is_empty_blob_sha1(ce->sha1)) + if (!is_empty_blob_sha1(ce->oid.hash)) changed |= DATA_CHANGED; } @@ -624,7 +624,7 @@ void set_object_name_for_intent_to_add_entry(struct cache_entry *ce) unsigned char sha1[20]; if (write_sha1_file("", 0, blob_type, sha1)) die("cannot create an empty blob in the object database"); - hashcpy(ce->sha1, sha1); + hashcpy(ce->oid.hash, sha1); } int add_to_index(struct index_state *istate, const char *path, struct stat *st, int flags, int force_mode) @@ -691,7 +691,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, return 0; } if (!intent_only) { - if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT)) { + if (index_path(ce->oid.hash, path, st, HASH_WRITE_OBJECT)) { free(ce); return error("unable to index file %s", path); } @@ -705,7 +705,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, /* It was suspected to be racily clean, but it turns out to be Ok */ was_same = (alias && !ce_stage(alias) && - !hashcmp(alias->sha1, ce->sha1) && + !oidcmp(&alias->oid, &ce->oid) && ce->ce_mode == alias->ce_mode); if (pretend) @@ -744,7 +744,7 @@ struct cache_entry *make_cache_entry(unsigned int mode, size = cache_entry_size(len); ce = xcalloc(1, size); - hashcpy(ce->sha1, sha1); + hashcpy(ce->oid.hash, sha1); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = len; @@ -1424,7 +1424,7 @@ static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *on ce->ce_flags = flags & ~CE_NAMEMASK; ce->ce_namelen = len; ce->index = 0; - hashcpy(ce->sha1, ondisk->sha1); + hashcpy(ce->oid.hash, ondisk->sha1); memcpy(ce->name, name, len); ce->name[len] = '\0'; return ce; @@ -1849,7 +1849,7 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk, ondisk->uid = htonl(ce->ce_stat_data.sd_uid); ondisk->gid = htonl(ce->ce_stat_data.sd_gid); ondisk->size = htonl(ce->ce_stat_data.sd_size); - hashcpy(ondisk->sha1, ce->sha1); + hashcpy(ondisk->sha1, ce->oid.hash); flags = ce->ce_flags & ~CE_NAMEMASK; flags |= (ce_namelen(ce) >= CE_NAMEMASK ? CE_NAMEMASK : ce_namelen(ce)); @@ -2038,7 +2038,7 @@ static int do_write_index(struct index_state *istate, int newfd, continue; if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce)) ce_smudge_racily_clean_entry(ce); - if (is_null_sha1(ce->sha1)) { + if (is_null_oid(&ce->oid)) { static const char msg[] = "cache entry has null sha1: %s"; static int allow = -1; @@ -2285,7 +2285,7 @@ void *read_blob_data_from_index(struct index_state *istate, const char *path, un } if (pos < 0) return NULL; - data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); + data = read_sha1_file(istate->cache[pos]->oid.hash, &type, &sz); if (!data || type != OBJ_BLOB) { free(data); return NULL; diff --git a/ref-filter.c b/ref-filter.c index bc551a752c..9adbb8af3e 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1576,24 +1576,6 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs); } -static int hex1(char ch) -{ - if ('0' <= ch && ch <= '9') - return ch - '0'; - else if ('a' <= ch && ch <= 'f') - return ch - 'a' + 10; - else if ('A' <= ch && ch <= 'F') - return ch - 'A' + 10; - return -1; -} -static int hex2(const char *cp) -{ - if (cp[0] && cp[1]) - return (hex1(cp[0]) << 4) | hex1(cp[1]); - else - return -1; -} - static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state) { struct strbuf *s = &state->stack->output; @@ -1603,7 +1585,7 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting if (cp[1] == '%') cp++; else { - int ch = hex2(cp + 1); + int ch = hex2chr(cp + 1); if (0 <= ch) { strbuf_addch(s, ch); cp += 3; @@ -10,6 +10,25 @@ #include "tag.h" /* + * List of all available backends + */ +static struct ref_storage_be *refs_backends = &refs_be_files; + +static struct ref_storage_be *find_ref_storage_backend(const char *name) +{ + struct ref_storage_be *be; + for (be = refs_backends; be; be = be->next) + if (!strcmp(be->name, name)) + return be; + return NULL; +} + +int ref_storage_backend_exists(const char *name) +{ + return find_ref_storage_backend(name) != NULL; +} + +/* * How to handle various characters in refnames: * 0: An acceptable character for refs * 1: End-of-component @@ -858,6 +877,14 @@ int ref_transaction_verify(struct ref_transaction *transaction, flags, NULL, err); } +int update_ref_oid(const char *msg, const char *refname, + const struct object_id *new_oid, const struct object_id *old_oid, + unsigned int flags, enum action_on_err onerr) +{ + return update_ref(msg, refname, new_oid ? new_oid->hash : NULL, + old_oid ? old_oid->hash : NULL, flags, onerr); +} + int update_ref(const char *msg, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, unsigned int flags, enum action_on_err onerr) @@ -1081,20 +1108,20 @@ const char *find_descendant_ref(const char *dirname, return NULL; } -int rename_ref_available(const char *oldname, const char *newname) +int rename_ref_available(const char *old_refname, const char *new_refname) { struct string_list skip = STRING_LIST_INIT_NODUP; struct strbuf err = STRBUF_INIT; - int ret; + int ok; - string_list_insert(&skip, oldname); - ret = !verify_refname_available(newname, NULL, &skip, &err); - if (!ret) + string_list_insert(&skip, old_refname); + ok = !verify_refname_available(new_refname, NULL, &skip, &err); + if (!ok) error("%s", err.buf); string_list_clear(&skip, 0); strbuf_release(&err); - return ret; + return ok; } int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) @@ -1132,9 +1159,13 @@ int head_ref(each_ref_fn fn, void *cb_data) static int do_for_each_ref(const char *submodule, const char *prefix, each_ref_fn fn, int trim, int flags, void *cb_data) { + struct ref_store *refs = get_ref_store(submodule); struct ref_iterator *iter; - iter = files_ref_iterator_begin(submodule, prefix, flags); + if (!refs) + return 0; + + iter = refs->be->iterator_begin(refs, prefix, flags); iter = prefix_ref_iterator_begin(iter, prefix, trim); return do_for_each_ref_iterator(iter, fn, cb_data); @@ -1193,8 +1224,10 @@ int for_each_rawref(each_ref_fn fn, void *cb_data) } /* This function needs to return a meaningful errno on failure */ -const char *resolve_ref_unsafe(const char *refname, int resolve_flags, - unsigned char *sha1, int *flags) +static const char *resolve_ref_recursively(struct ref_store *refs, + const char *refname, + int resolve_flags, + unsigned char *sha1, int *flags) { static struct strbuf sb_refname = STRBUF_INIT; int unused_flags; @@ -1226,7 +1259,8 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) { unsigned int read_flags = 0; - if (read_raw_ref(refname, sha1, &sb_refname, &read_flags)) { + if (refs->be->read_raw_ref(refs, refname, + sha1, &sb_refname, &read_flags)) { *flags |= read_flags; if (errno != ENOENT || (resolve_flags & RESOLVE_REF_READING)) return NULL; @@ -1265,3 +1299,266 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, errno = ELOOP; return NULL; } + +/* backend functions */ +int refs_init_db(struct strbuf *err) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->init_db(refs, err); +} + +const char *resolve_ref_unsafe(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags) +{ + return resolve_ref_recursively(get_ref_store(NULL), refname, + resolve_flags, sha1, flags); +} + +int resolve_gitlink_ref(const char *submodule, const char *refname, + unsigned char *sha1) +{ + size_t len = strlen(submodule); + struct ref_store *refs; + int flags; + + while (len && submodule[len - 1] == '/') + len--; + + if (!len) + return -1; + + if (submodule[len]) { + /* We need to strip off one or more trailing slashes */ + char *stripped = xmemdupz(submodule, len); + + refs = get_ref_store(stripped); + free(stripped); + } else { + refs = get_ref_store(submodule); + } + + if (!refs) + return -1; + + if (!resolve_ref_recursively(refs, refname, 0, sha1, &flags) || + is_null_sha1(sha1)) + return -1; + return 0; +} + +/* A pointer to the ref_store for the main repository: */ +static struct ref_store *main_ref_store; + +/* A linked list of ref_stores for submodules: */ +static struct ref_store *submodule_ref_stores; + +void base_ref_store_init(struct ref_store *refs, + const struct ref_storage_be *be, + const char *submodule) +{ + refs->be = be; + if (!submodule) { + if (main_ref_store) + die("BUG: main_ref_store initialized twice"); + + refs->submodule = ""; + refs->next = NULL; + main_ref_store = refs; + } else { + if (lookup_ref_store(submodule)) + die("BUG: ref_store for submodule '%s' initialized twice", + submodule); + + refs->submodule = xstrdup(submodule); + refs->next = submodule_ref_stores; + submodule_ref_stores = refs; + } +} + +struct ref_store *ref_store_init(const char *submodule) +{ + const char *be_name = "files"; + struct ref_storage_be *be = find_ref_storage_backend(be_name); + + if (!be) + die("BUG: reference backend %s is unknown", be_name); + + if (!submodule || !*submodule) + return be->init(NULL); + else + return be->init(submodule); +} + +struct ref_store *lookup_ref_store(const char *submodule) +{ + struct ref_store *refs; + + if (!submodule || !*submodule) + return main_ref_store; + + for (refs = submodule_ref_stores; refs; refs = refs->next) { + if (!strcmp(submodule, refs->submodule)) + return refs; + } + + return NULL; +} + +struct ref_store *get_ref_store(const char *submodule) +{ + struct ref_store *refs; + + if (!submodule || !*submodule) { + refs = lookup_ref_store(NULL); + + if (!refs) + refs = ref_store_init(NULL); + } else { + refs = lookup_ref_store(submodule); + + if (!refs) { + struct strbuf submodule_sb = STRBUF_INIT; + + strbuf_addstr(&submodule_sb, submodule); + if (is_nonbare_repository_dir(&submodule_sb)) + refs = ref_store_init(submodule); + strbuf_release(&submodule_sb); + } + } + + return refs; +} + +void assert_main_repository(struct ref_store *refs, const char *caller) +{ + if (*refs->submodule) + die("BUG: %s called for a submodule", caller); +} + +/* backend functions */ +int pack_refs(unsigned int flags) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->pack_refs(refs, flags); +} + +int peel_ref(const char *refname, unsigned char *sha1) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->peel_ref(refs, refname, sha1); +} + +int create_symref(const char *ref_target, const char *refs_heads_master, + const char *logmsg) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->create_symref(refs, ref_target, refs_heads_master, + logmsg); +} + +int ref_transaction_commit(struct ref_transaction *transaction, + struct strbuf *err) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->transaction_commit(refs, transaction, err); +} + +int verify_refname_available(const char *refname, + const struct string_list *extra, + const struct string_list *skip, + struct strbuf *err) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->verify_refname_available(refs, refname, extra, skip, err); +} + +int for_each_reflog(each_ref_fn fn, void *cb_data) +{ + struct ref_store *refs = get_ref_store(NULL); + struct ref_iterator *iter; + + iter = refs->be->reflog_iterator_begin(refs); + + return do_for_each_ref_iterator(iter, fn, cb_data); +} + +int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, + void *cb_data) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->for_each_reflog_ent_reverse(refs, refname, + fn, cb_data); +} + +int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, + void *cb_data) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->for_each_reflog_ent(refs, refname, fn, cb_data); +} + +int reflog_exists(const char *refname) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->reflog_exists(refs, refname); +} + +int safe_create_reflog(const char *refname, int force_create, + struct strbuf *err) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->create_reflog(refs, refname, force_create, err); +} + +int delete_reflog(const char *refname) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->delete_reflog(refs, refname); +} + +int reflog_expire(const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->reflog_expire(refs, refname, sha1, flags, + prepare_fn, should_prune_fn, + cleanup_fn, policy_cb_data); +} + +int initial_ref_transaction_commit(struct ref_transaction *transaction, + struct strbuf *err) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->initial_transaction_commit(refs, transaction, err); +} + +int delete_refs(struct string_list *refnames, unsigned int flags) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->delete_refs(refs, refnames, flags); +} + +int rename_ref(const char *oldref, const char *newref, const char *logmsg) +{ + struct ref_store *refs = get_ref_store(NULL); + + return refs->be->rename_ref(refs, oldref, newref, logmsg); +} @@ -66,6 +66,8 @@ int ref_exists(const char *refname); int is_branch(const char *refname); +extern int refs_init_db(struct strbuf *err); + /* * If refname is a non-symbolic reference that refers to a tag object, * and the tag can be (recursively) dereferenced to a non-tag object, @@ -77,11 +79,12 @@ int is_branch(const char *refname); int peel_ref(const char *refname, unsigned char *sha1); /** - * Resolve refname in the nested "gitlink" repository that is located - * at path. If the resolution is successful, return 0 and set sha1 to - * the name of the object; otherwise, return a non-zero value. + * Resolve refname in the nested "gitlink" repository in the specified + * submodule (which must be non-NULL). If the resolution is + * successful, return 0 and set sha1 to the name of the object; + * otherwise, return a non-zero value. */ -int resolve_gitlink_ref(const char *path, const char *refname, +int resolve_gitlink_ref(const char *submodule, const char *refname, unsigned char *sha1); /* @@ -477,6 +480,9 @@ void ref_transaction_free(struct ref_transaction *transaction); int update_ref(const char *msg, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, unsigned int flags, enum action_on_err onerr); +int update_ref_oid(const char *msg, const char *refname, + const struct object_id *new_oid, const struct object_id *old_oid, + unsigned int flags, enum action_on_err onerr); int parse_hide_refs_config(const char *var, const char *value, const char *); @@ -544,4 +550,6 @@ int reflog_expire(const char *refname, const unsigned char *sha1, reflog_expiry_cleanup_fn cleanup_fn, void *policy_cb_data); +int ref_storage_backend_exists(const char *name); + #endif /* REFS_H */ diff --git a/refs/files-backend.c b/refs/files-backend.c index 12290d2496..0709f60b8e 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -39,7 +39,7 @@ struct ref_value { struct object_id peeled; }; -struct ref_cache; +struct files_ref_store; /* * Information used (along with the information in ref_entry) to @@ -78,8 +78,8 @@ struct ref_dir { */ int sorted; - /* A pointer to the ref_cache that contains this ref_dir. */ - struct ref_cache *ref_cache; + /* A pointer to the files_ref_store that contains this ref_dir. */ + struct files_ref_store *ref_store; struct ref_entry **entries; }; @@ -161,7 +161,7 @@ struct ref_entry { static void read_loose_refs(const char *dirname, struct ref_dir *dir); static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len); -static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, +static struct ref_entry *create_dir_entry(struct files_ref_store *ref_store, const char *dirname, size_t len, int incomplete); static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry); @@ -183,7 +183,7 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry) int pos = search_ref_dir(dir, "refs/bisect/", 12); if (pos < 0) { struct ref_entry *child_entry; - child_entry = create_dir_entry(dir->ref_cache, + child_entry = create_dir_entry(dir->ref_store, "refs/bisect/", 12, 1); add_entry_to_dir(dir, child_entry); @@ -261,13 +261,13 @@ static void clear_ref_dir(struct ref_dir *dir) * dirname is the name of the directory with a trailing slash (e.g., * "refs/heads/") or "" for the top-level directory. */ -static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, +static struct ref_entry *create_dir_entry(struct files_ref_store *ref_store, const char *dirname, size_t len, int incomplete) { struct ref_entry *direntry; FLEX_ALLOC_MEM(direntry, name, dirname, len); - direntry->u.subdir.ref_cache = ref_cache; + direntry->u.subdir.ref_store = ref_store; direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0); return direntry; } @@ -343,7 +343,7 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir, * therefore, create an empty record for it but mark * the record complete. */ - entry = create_dir_entry(dir->ref_cache, subdirname, len, 0); + entry = create_dir_entry(dir->ref_store, subdirname, len, 0); add_entry_to_dir(dir, entry); } else { entry = dir->entries[entry_index]; @@ -887,9 +887,9 @@ struct packed_ref_cache { /* * Count of references to the data structure in this instance, - * including the pointer from ref_cache::packed if any. The - * data will not be freed as long as the reference count is - * nonzero. + * including the pointer from files_ref_store::packed if any. + * The data will not be freed as long as the reference count + * is nonzero. */ unsigned int referrers; @@ -910,17 +910,11 @@ struct packed_ref_cache { * Future: need to be in "struct repository" * when doing a full libification. */ -static struct ref_cache { - struct ref_cache *next; +struct files_ref_store { + struct ref_store base; struct ref_entry *loose; struct packed_ref_cache *packed; - /* - * The submodule name, or "" for the main repo. We allocate - * length 1 rather than FLEX_ARRAY so that the main ref_cache - * is initialized correctly. - */ - char name[1]; -} ref_cache, *submodule_ref_caches; +}; /* Lock used for the main packed-refs file: */ static struct lock_file packlock; @@ -949,7 +943,7 @@ static int release_packed_ref_cache(struct packed_ref_cache *packed_refs) } } -static void clear_packed_ref_cache(struct ref_cache *refs) +static void clear_packed_ref_cache(struct files_ref_store *refs) { if (refs->packed) { struct packed_ref_cache *packed_refs = refs->packed; @@ -961,7 +955,7 @@ static void clear_packed_ref_cache(struct ref_cache *refs) } } -static void clear_loose_ref_cache(struct ref_cache *refs) +static void clear_loose_ref_cache(struct files_ref_store *refs) { if (refs->loose) { free_ref_entry(refs->loose); @@ -973,53 +967,34 @@ static void clear_loose_ref_cache(struct ref_cache *refs) * Create a new submodule ref cache and add it to the internal * set of caches. */ -static struct ref_cache *create_ref_cache(const char *submodule) -{ - struct ref_cache *refs; - if (!submodule) - submodule = ""; - FLEX_ALLOC_STR(refs, name, submodule); - refs->next = submodule_ref_caches; - submodule_ref_caches = refs; - return refs; -} - -static struct ref_cache *lookup_ref_cache(const char *submodule) +static struct ref_store *files_ref_store_create(const char *submodule) { - struct ref_cache *refs; + struct files_ref_store *refs = xcalloc(1, sizeof(*refs)); + struct ref_store *ref_store = (struct ref_store *)refs; - if (!submodule || !*submodule) - return &ref_cache; + base_ref_store_init(ref_store, &refs_be_files, submodule); - for (refs = submodule_ref_caches; refs; refs = refs->next) - if (!strcmp(submodule, refs->name)) - return refs; - return NULL; + return ref_store; } /* - * Return a pointer to a ref_cache for the specified submodule. For - * the main repository, use submodule==NULL; such a call cannot fail. - * For a submodule, the submodule must exist and be a nonbare - * repository, otherwise return NULL. - * - * The returned structure will be allocated and initialized but not - * necessarily populated; it should not be freed. + * Downcast ref_store to files_ref_store. Die if ref_store is not a + * files_ref_store. If submodule_allowed is not true, then also die if + * files_ref_store is for a submodule (i.e., not for the main + * repository). caller is used in any necessary error messages. */ -static struct ref_cache *get_ref_cache(const char *submodule) +static struct files_ref_store *files_downcast( + struct ref_store *ref_store, int submodule_allowed, + const char *caller) { - struct ref_cache *refs = lookup_ref_cache(submodule); - - if (!refs) { - struct strbuf submodule_sb = STRBUF_INIT; + if (ref_store->be != &refs_be_files) + die("BUG: ref_store is type \"%s\" not \"files\" in %s", + ref_store->be->name, caller); - strbuf_addstr(&submodule_sb, submodule); - if (is_nonbare_repository_dir(&submodule_sb)) - refs = create_ref_cache(submodule); - strbuf_release(&submodule_sb); - } + if (!submodule_allowed) + assert_main_repository(ref_store, caller); - return refs; + return (struct files_ref_store *)ref_store; } /* The length of a peeled reference line in packed-refs, including EOL: */ @@ -1151,15 +1126,16 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir) } /* - * Get the packed_ref_cache for the specified ref_cache, creating it - * if necessary. + * Get the packed_ref_cache for the specified files_ref_store, + * creating it if necessary. */ -static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs) +static struct packed_ref_cache *get_packed_ref_cache(struct files_ref_store *refs) { char *packed_refs_file; - if (*refs->name) - packed_refs_file = git_pathdup_submodule(refs->name, "packed-refs"); + if (*refs->base.submodule) + packed_refs_file = git_pathdup_submodule(refs->base.submodule, + "packed-refs"); else packed_refs_file = git_pathdup("packed-refs"); @@ -1189,7 +1165,7 @@ static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_ca return get_ref_dir(packed_ref_cache->root); } -static struct ref_dir *get_packed_refs(struct ref_cache *refs) +static struct ref_dir *get_packed_refs(struct files_ref_store *refs) { return get_packed_ref_dir(get_packed_ref_cache(refs)); } @@ -1200,10 +1176,10 @@ static struct ref_dir *get_packed_refs(struct ref_cache *refs) * lock_packed_refs()). To actually write the packed-refs file, call * commit_packed_refs(). */ -static void add_packed_ref(const char *refname, const unsigned char *sha1) +static void add_packed_ref(struct files_ref_store *refs, + const char *refname, const unsigned char *sha1) { - struct packed_ref_cache *packed_ref_cache = - get_packed_ref_cache(&ref_cache); + struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs); if (!packed_ref_cache->lock) die("internal error: packed refs not locked"); @@ -1218,20 +1194,26 @@ static void add_packed_ref(const char *refname, const unsigned char *sha1) */ static void read_loose_refs(const char *dirname, struct ref_dir *dir) { - struct ref_cache *refs = dir->ref_cache; + struct files_ref_store *refs = dir->ref_store; DIR *d; struct dirent *de; int dirnamelen = strlen(dirname); struct strbuf refname; struct strbuf path = STRBUF_INIT; size_t path_baselen; + int err = 0; - if (*refs->name) - strbuf_git_path_submodule(&path, refs->name, "%s", dirname); + if (*refs->base.submodule) + err = strbuf_git_path_submodule(&path, refs->base.submodule, "%s", dirname); else strbuf_git_path(&path, "%s", dirname); path_baselen = path.len; + if (err) { + strbuf_release(&path); + return; + } + d = opendir(path.buf); if (!d) { strbuf_release(&path); @@ -1262,10 +1244,10 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) } else { int read_ok; - if (*refs->name) { + if (*refs->base.submodule) { hashclr(sha1); flag = 0; - read_ok = !resolve_gitlink_ref(refs->name, + read_ok = !resolve_gitlink_ref(refs->base.submodule, refname.buf, sha1); } else { read_ok = !read_ref_full(refname.buf, @@ -1306,7 +1288,7 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir) closedir(d); } -static struct ref_dir *get_loose_refs(struct ref_cache *refs) +static struct ref_dir *get_loose_refs(struct files_ref_store *refs) { if (!refs->loose) { /* @@ -1324,105 +1306,22 @@ static struct ref_dir *get_loose_refs(struct ref_cache *refs) return get_ref_dir(refs->loose); } -#define MAXREFLEN (1024) - -/* - * Called by resolve_gitlink_ref_recursive() after it failed to read - * from the loose refs in ref_cache refs. Find <refname> in the - * packed-refs file for the submodule. - */ -static int resolve_gitlink_packed_ref(struct ref_cache *refs, - const char *refname, unsigned char *sha1) -{ - struct ref_entry *ref; - struct ref_dir *dir = get_packed_refs(refs); - - ref = find_ref(dir, refname); - if (ref == NULL) - return -1; - - hashcpy(sha1, ref->u.value.oid.hash); - return 0; -} - -static int resolve_gitlink_ref_recursive(struct ref_cache *refs, - const char *refname, unsigned char *sha1, - int recursion) -{ - int fd, len; - char buffer[128], *p; - char *path; - - if (recursion > SYMREF_MAXDEPTH || strlen(refname) > MAXREFLEN) - return -1; - path = *refs->name - ? git_pathdup_submodule(refs->name, "%s", refname) - : git_pathdup("%s", refname); - fd = open(path, O_RDONLY); - free(path); - if (fd < 0) - return resolve_gitlink_packed_ref(refs, refname, sha1); - - len = read(fd, buffer, sizeof(buffer)-1); - close(fd); - if (len < 0) - return -1; - while (len && isspace(buffer[len-1])) - len--; - buffer[len] = 0; - - /* Was it a detached head or an old-fashioned symlink? */ - if (!get_sha1_hex(buffer, sha1)) - return 0; - - /* Symref? */ - if (strncmp(buffer, "ref:", 4)) - return -1; - p = buffer + 4; - while (isspace(*p)) - p++; - - return resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1); -} - -int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1) -{ - int len = strlen(path), retval; - struct strbuf submodule = STRBUF_INIT; - struct ref_cache *refs; - - while (len && path[len-1] == '/') - len--; - if (!len) - return -1; - - strbuf_add(&submodule, path, len); - refs = get_ref_cache(submodule.buf); - if (!refs) { - strbuf_release(&submodule); - return -1; - } - strbuf_release(&submodule); - - retval = resolve_gitlink_ref_recursive(refs, refname, sha1, 0); - return retval; -} - /* * Return the ref_entry for the given refname from the packed * references. If it does not exist, return NULL. */ -static struct ref_entry *get_packed_ref(const char *refname) +static struct ref_entry *get_packed_ref(struct files_ref_store *refs, + const char *refname) { - return find_ref(get_packed_refs(&ref_cache), refname); + return find_ref(get_packed_refs(refs), refname); } /* * A loose ref file doesn't exist; check for a packed ref. */ -static int resolve_missing_loose_ref(const char *refname, - unsigned char *sha1, - unsigned int *flags) +static int resolve_packed_ref(struct files_ref_store *refs, + const char *refname, + unsigned char *sha1, unsigned int *flags) { struct ref_entry *entry; @@ -1430,7 +1329,7 @@ static int resolve_missing_loose_ref(const char *refname, * The loose reference file does not exist; check for a packed * reference. */ - entry = get_packed_ref(refname); + entry = get_packed_ref(refs, refname); if (entry) { hashcpy(sha1, entry->u.value.oid.hash); *flags |= REF_ISPACKED; @@ -1440,9 +1339,12 @@ static int resolve_missing_loose_ref(const char *refname, return -1; } -int read_raw_ref(const char *refname, unsigned char *sha1, - struct strbuf *referent, unsigned int *type) +static int files_read_raw_ref(struct ref_store *ref_store, + const char *refname, unsigned char *sha1, + struct strbuf *referent, unsigned int *type) { + struct files_ref_store *refs = + files_downcast(ref_store, 1, "read_raw_ref"); struct strbuf sb_contents = STRBUF_INIT; struct strbuf sb_path = STRBUF_INIT; const char *path; @@ -1454,7 +1356,12 @@ int read_raw_ref(const char *refname, unsigned char *sha1, *type = 0; strbuf_reset(&sb_path); - strbuf_git_path(&sb_path, "%s", refname); + + if (*refs->base.submodule) + strbuf_git_path_submodule(&sb_path, refs->base.submodule, "%s", refname); + else + strbuf_git_path(&sb_path, "%s", refname); + path = sb_path.buf; stat_ref: @@ -1471,7 +1378,7 @@ stat_ref: if (lstat(path, &st) < 0) { if (errno != ENOENT) goto out; - if (resolve_missing_loose_ref(refname, sha1, type)) { + if (resolve_packed_ref(refs, refname, sha1, type)) { errno = ENOENT; goto out; } @@ -1505,7 +1412,7 @@ stat_ref: * ref is supposed to be, there could still be a * packed ref: */ - if (resolve_missing_loose_ref(refname, sha1, type)) { + if (resolve_packed_ref(refs, refname, sha1, type)) { errno = EISDIR; goto out; } @@ -1606,7 +1513,8 @@ static void unlock_ref(struct ref_lock *lock) * avoided, namely if we were successfully able to read the ref * - Generate informative error messages in the case of failure */ -static int lock_raw_ref(const char *refname, int mustexist, +static int lock_raw_ref(struct files_ref_store *refs, + const char *refname, int mustexist, const struct string_list *extras, const struct string_list *skip, struct ref_lock **lock_p, @@ -1620,6 +1528,8 @@ static int lock_raw_ref(const char *refname, int mustexist, int ret = TRANSACTION_GENERIC_ERROR; assert(err); + assert_main_repository(&refs->base, "lock_raw_ref"); + *type = 0; /* First lock the file so it can't change out from under us. */ @@ -1703,7 +1613,8 @@ retry: * fear that its value will change. */ - if (read_raw_ref(refname, lock->old_oid.hash, referent, type)) { + if (files_read_raw_ref(&refs->base, refname, + lock->old_oid.hash, referent, type)) { if (errno == ENOENT) { if (mustexist) { /* Garden variety missing reference. */ @@ -1746,7 +1657,7 @@ retry: REMOVE_DIR_EMPTY_ONLY)) { if (verify_refname_available_dir( refname, extras, skip, - get_loose_refs(&ref_cache), + get_loose_refs(refs), err)) { /* * The error message set by @@ -1785,7 +1696,7 @@ retry: */ if (verify_refname_available_dir( refname, extras, skip, - get_packed_refs(&ref_cache), + get_packed_refs(refs), err)) { goto error_return; } @@ -1838,8 +1749,10 @@ static enum peel_status peel_entry(struct ref_entry *entry, int repeel) return status; } -int peel_ref(const char *refname, unsigned char *sha1) +static int files_peel_ref(struct ref_store *ref_store, + const char *refname, unsigned char *sha1) { + struct files_ref_store *refs = files_downcast(ref_store, 0, "peel_ref"); int flag; unsigned char base[20]; @@ -1864,7 +1777,7 @@ int peel_ref(const char *refname, unsigned char *sha1) * have REF_KNOWS_PEELED. */ if (flag & REF_ISPACKED) { - struct ref_entry *r = get_packed_ref(refname); + struct ref_entry *r = get_packed_ref(refs, refname); if (r) { if (peel_entry(r, 0)) return -1; @@ -1891,6 +1804,10 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator) int ok; while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) { + if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && + ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE) + continue; + if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && !ref_resolves_to_object(iter->iter0->refname, iter->iter0->oid, @@ -1939,11 +1856,12 @@ static struct ref_iterator_vtable files_ref_iterator_vtable = { files_ref_iterator_abort }; -struct ref_iterator *files_ref_iterator_begin( - const char *submodule, +static struct ref_iterator *files_ref_iterator_begin( + struct ref_store *ref_store, const char *prefix, unsigned int flags) { - struct ref_cache *refs = get_ref_cache(submodule); + struct files_ref_store *refs = + files_downcast(ref_store, 1, "ref_iterator_begin"); struct ref_dir *loose_dir, *packed_dir; struct ref_iterator *loose_iter, *packed_iter; struct files_ref_iterator *iter; @@ -2059,7 +1977,8 @@ static int remove_empty_directories(struct strbuf *path) * Locks a ref returning the lock on success and NULL on failure. * On failure errno is set to something meaningful. */ -static struct ref_lock *lock_ref_sha1_basic(const char *refname, +static struct ref_lock *lock_ref_sha1_basic(struct files_ref_store *refs, + const char *refname, const unsigned char *old_sha1, const struct string_list *extras, const struct string_list *skip, @@ -2075,6 +1994,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, int attempts_remaining = 3; int resolved; + assert_main_repository(&refs->base, "lock_ref_sha1_basic"); assert(err); lock = xcalloc(1, sizeof(struct ref_lock)); @@ -2096,8 +2016,9 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, */ if (remove_empty_directories(&ref_file)) { last_errno = errno; - if (!verify_refname_available_dir(refname, extras, skip, - get_loose_refs(&ref_cache), err)) + if (!verify_refname_available_dir( + refname, extras, skip, + get_loose_refs(refs), err)) strbuf_addf(err, "there are still refs under '%s'", refname); goto error_return; @@ -2108,8 +2029,9 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, if (!resolved) { last_errno = errno; if (last_errno != ENOTDIR || - !verify_refname_available_dir(refname, extras, skip, - get_loose_refs(&ref_cache), err)) + !verify_refname_available_dir( + refname, extras, skip, + get_loose_refs(refs), err)) strbuf_addf(err, "unable to resolve reference '%s': %s", refname, strerror(last_errno)); @@ -2124,7 +2046,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, */ if (is_null_oid(&lock->old_oid) && verify_refname_available_dir(refname, extras, skip, - get_packed_refs(&ref_cache), err)) { + get_packed_refs(refs), + err)) { last_errno = ENOTDIR; goto error_return; } @@ -2211,13 +2134,14 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data) * hold_lock_file_for_update(). Return 0 on success. On errors, set * errno appropriately and return a nonzero value. */ -static int lock_packed_refs(int flags) +static int lock_packed_refs(struct files_ref_store *refs, int flags) { static int timeout_configured = 0; static int timeout_value = 1000; - struct packed_ref_cache *packed_ref_cache; + assert_main_repository(&refs->base, "lock_packed_refs"); + if (!timeout_configured) { git_config_get_int("core.packedrefstimeout", &timeout_value); timeout_configured = 1; @@ -2233,7 +2157,7 @@ static int lock_packed_refs(int flags) * this will automatically invalidate the cache and re-read * the packed-refs file. */ - packed_ref_cache = get_packed_ref_cache(&ref_cache); + packed_ref_cache = get_packed_ref_cache(refs); packed_ref_cache->lock = &packlock; /* Increment the reference count to prevent it from being freed: */ acquire_packed_ref_cache(packed_ref_cache); @@ -2246,14 +2170,16 @@ static int lock_packed_refs(int flags) * lock_packed_refs()). Return zero on success. On errors, set errno * and return a nonzero value */ -static int commit_packed_refs(void) +static int commit_packed_refs(struct files_ref_store *refs) { struct packed_ref_cache *packed_ref_cache = - get_packed_ref_cache(&ref_cache); + get_packed_ref_cache(refs); int error = 0; int save_errno = 0; FILE *out; + assert_main_repository(&refs->base, "commit_packed_refs"); + if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); @@ -2280,17 +2206,19 @@ static int commit_packed_refs(void) * in-memory packed reference cache. (The packed-refs file will be * read anew if it is needed again after this function is called.) */ -static void rollback_packed_refs(void) +static void rollback_packed_refs(struct files_ref_store *refs) { struct packed_ref_cache *packed_ref_cache = - get_packed_ref_cache(&ref_cache); + get_packed_ref_cache(refs); + + assert_main_repository(&refs->base, "rollback_packed_refs"); if (!packed_ref_cache->lock) die("internal error: packed-refs not locked"); rollback_lock_file(packed_ref_cache->lock); packed_ref_cache->lock = NULL; release_packed_ref_cache(packed_ref_cache); - clear_packed_ref_cache(&ref_cache); + clear_packed_ref_cache(refs); } struct ref_to_prune { @@ -2421,20 +2349,22 @@ static void prune_refs(struct ref_to_prune *r) } } -int pack_refs(unsigned int flags) +static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) { + struct files_ref_store *refs = + files_downcast(ref_store, 0, "pack_refs"); struct pack_refs_cb_data cbdata; memset(&cbdata, 0, sizeof(cbdata)); cbdata.flags = flags; - lock_packed_refs(LOCK_DIE_ON_ERROR); - cbdata.packed_refs = get_packed_refs(&ref_cache); + lock_packed_refs(refs, LOCK_DIE_ON_ERROR); + cbdata.packed_refs = get_packed_refs(refs); - do_for_each_entry_in_dir(get_loose_refs(&ref_cache), 0, + do_for_each_entry_in_dir(get_loose_refs(refs), 0, pack_if_possible_fn, &cbdata); - if (commit_packed_refs()) + if (commit_packed_refs(refs)) die_errno("unable to overwrite old ref-pack file"); prune_refs(cbdata.ref_to_prune); @@ -2448,17 +2378,19 @@ int pack_refs(unsigned int flags) * * The refs in 'refnames' needn't be sorted. `err` must not be NULL. */ -static int repack_without_refs(struct string_list *refnames, struct strbuf *err) +static int repack_without_refs(struct files_ref_store *refs, + struct string_list *refnames, struct strbuf *err) { struct ref_dir *packed; struct string_list_item *refname; int ret, needs_repacking = 0, removed = 0; + assert_main_repository(&refs->base, "repack_without_refs"); assert(err); /* Look for a packed ref */ for_each_string_list_item(refname, refnames) { - if (get_packed_ref(refname->string)) { + if (get_packed_ref(refs, refname->string)) { needs_repacking = 1; break; } @@ -2468,11 +2400,11 @@ static int repack_without_refs(struct string_list *refnames, struct strbuf *err) if (!needs_repacking) return 0; /* no refname exists in packed refs */ - if (lock_packed_refs(0)) { + if (lock_packed_refs(refs, 0)) { unable_to_lock_message(git_path("packed-refs"), errno, err); return -1; } - packed = get_packed_refs(&ref_cache); + packed = get_packed_refs(refs); /* Remove refnames from the cache */ for_each_string_list_item(refname, refnames) @@ -2483,12 +2415,12 @@ static int repack_without_refs(struct string_list *refnames, struct strbuf *err) * All packed entries disappeared while we were * acquiring the lock. */ - rollback_packed_refs(); + rollback_packed_refs(refs); return 0; } /* Write what remains */ - ret = commit_packed_refs(); + ret = commit_packed_refs(refs); if (ret) strbuf_addf(err, "unable to overwrite old ref-pack file: %s", strerror(errno)); @@ -2513,15 +2445,18 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) return 0; } -int delete_refs(struct string_list *refnames, unsigned int flags) +static int files_delete_refs(struct ref_store *ref_store, + struct string_list *refnames, unsigned int flags) { + struct files_ref_store *refs = + files_downcast(ref_store, 0, "delete_refs"); struct strbuf err = STRBUF_INIT; int i, result = 0; if (!refnames->nr) return 0; - result = repack_without_refs(refnames, &err); + result = repack_without_refs(refs, refnames, &err); if (result) { /* * If we failed to rewrite the packed-refs file, then @@ -2612,13 +2547,16 @@ out: return ret; } -int verify_refname_available(const char *newname, - const struct string_list *extras, - const struct string_list *skip, - struct strbuf *err) +static int files_verify_refname_available(struct ref_store *ref_store, + const char *newname, + const struct string_list *extras, + const struct string_list *skip, + struct strbuf *err) { - struct ref_dir *packed_refs = get_packed_refs(&ref_cache); - struct ref_dir *loose_refs = get_loose_refs(&ref_cache); + struct files_ref_store *refs = + files_downcast(ref_store, 1, "verify_refname_available"); + struct ref_dir *packed_refs = get_packed_refs(refs); + struct ref_dir *loose_refs = get_loose_refs(refs); if (verify_refname_available_dir(newname, extras, skip, packed_refs, err) || @@ -2631,12 +2569,17 @@ int verify_refname_available(const char *newname, static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1, struct strbuf *err); -static int commit_ref_update(struct ref_lock *lock, +static int commit_ref_update(struct files_ref_store *refs, + struct ref_lock *lock, const unsigned char *sha1, const char *logmsg, struct strbuf *err); -int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) +static int files_rename_ref(struct ref_store *ref_store, + const char *oldrefname, const char *newrefname, + const char *logmsg) { + struct files_ref_store *refs = + files_downcast(ref_store, 0, "rename_ref"); unsigned char sha1[20], orig_sha1[20]; int flag = 0, logmoved = 0; struct ref_lock *lock; @@ -2699,8 +2642,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms logmoved = log; - lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, REF_NODEREF, - NULL, &err); + lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL, + REF_NODEREF, NULL, &err); if (!lock) { error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf); strbuf_release(&err); @@ -2709,7 +2652,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms hashcpy(lock->old_oid.hash, orig_sha1); if (write_ref_to_lockfile(lock, orig_sha1, &err) || - commit_ref_update(lock, orig_sha1, logmsg, &err)) { + commit_ref_update(refs, lock, orig_sha1, logmsg, &err)) { error("unable to write current sha1 into %s: %s", newrefname, err.buf); strbuf_release(&err); goto rollback; @@ -2718,8 +2661,8 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms return 0; rollback: - lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, REF_NODEREF, - NULL, &err); + lock = lock_ref_sha1_basic(refs, oldrefname, NULL, NULL, NULL, + REF_NODEREF, NULL, &err); if (!lock) { error("unable to lock %s for rollback: %s", oldrefname, err.buf); strbuf_release(&err); @@ -2729,7 +2672,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms flag = log_all_ref_updates; log_all_ref_updates = 0; if (write_ref_to_lockfile(lock, orig_sha1, &err) || - commit_ref_update(lock, orig_sha1, NULL, &err)) { + commit_ref_update(refs, lock, orig_sha1, NULL, &err)) { error("unable to write current sha1 into %s: %s", oldrefname, err.buf); strbuf_release(&err); } @@ -2832,11 +2775,16 @@ static int log_ref_setup(const char *refname, struct strbuf *logfile, struct str } -int safe_create_reflog(const char *refname, int force_create, struct strbuf *err) +static int files_create_reflog(struct ref_store *ref_store, + const char *refname, int force_create, + struct strbuf *err) { int ret; struct strbuf sb = STRBUF_INIT; + /* Check validity (but we don't need the result): */ + files_downcast(ref_store, 0, "create_reflog"); + ret = log_ref_setup(refname, &sb, err, force_create); strbuf_release(&sb); return ret; @@ -2965,11 +2913,14 @@ static int write_ref_to_lockfile(struct ref_lock *lock, * to the loose reference lockfile. Also update the reflogs if * necessary, using the specified lockmsg (which can be NULL). */ -static int commit_ref_update(struct ref_lock *lock, +static int commit_ref_update(struct files_ref_store *refs, + struct ref_lock *lock, const unsigned char *sha1, const char *logmsg, struct strbuf *err) { - clear_loose_ref_cache(&ref_cache); + assert_main_repository(&refs->base, "commit_ref_update"); + + clear_loose_ref_cache(refs); if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) { char *old_msg = strbuf_detach(err, NULL); strbuf_addf(err, "cannot update the ref '%s': %s", @@ -3068,13 +3019,18 @@ static int create_symref_locked(struct ref_lock *lock, const char *refname, return 0; } -int create_symref(const char *refname, const char *target, const char *logmsg) +static int files_create_symref(struct ref_store *ref_store, + const char *refname, const char *target, + const char *logmsg) { + struct files_ref_store *refs = + files_downcast(ref_store, 0, "create_symref"); struct strbuf err = STRBUF_INIT; struct ref_lock *lock; int ret; - lock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, REF_NODEREF, NULL, + lock = lock_ref_sha1_basic(refs, refname, NULL, + NULL, NULL, REF_NODEREF, NULL, &err); if (!lock) { error("%s", err.buf); @@ -3122,16 +3078,24 @@ int set_worktree_head_symref(const char *gitdir, const char *target) return ret; } -int reflog_exists(const char *refname) +static int files_reflog_exists(struct ref_store *ref_store, + const char *refname) { struct stat st; + /* Check validity (but we don't need the result): */ + files_downcast(ref_store, 0, "reflog_exists"); + return !lstat(git_path("logs/%s", refname), &st) && S_ISREG(st.st_mode); } -int delete_reflog(const char *refname) +static int files_delete_reflog(struct ref_store *ref_store, + const char *refname) { + /* Check validity (but we don't need the result): */ + files_downcast(ref_store, 0, "delete_reflog"); + return remove_path(git_path("logs/%s", refname)); } @@ -3174,13 +3138,19 @@ static char *find_beginning_of_line(char *bob, char *scan) return scan; } -int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data) +static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data) { struct strbuf sb = STRBUF_INIT; FILE *logfp; long pos; int ret = 0, at_tail = 1; + /* Check validity (but we don't need the result): */ + files_downcast(ref_store, 0, "for_each_reflog_ent_reverse"); + logfp = fopen(git_path("logs/%s", refname), "r"); if (!logfp) return -1; @@ -3276,12 +3246,17 @@ int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void return ret; } -int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data) +static int files_for_each_reflog_ent(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, void *cb_data) { FILE *logfp; struct strbuf sb = STRBUF_INIT; int ret = 0; + /* Check validity (but we don't need the result): */ + files_downcast(ref_store, 0, "for_each_reflog_ent"); + logfp = fopen(git_path("logs/%s", refname), "r"); if (!logfp) return -1; @@ -3360,22 +3335,19 @@ static struct ref_iterator_vtable files_reflog_iterator_vtable = { files_reflog_iterator_abort }; -struct ref_iterator *files_reflog_iterator_begin(void) +static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store) { struct files_reflog_iterator *iter = xcalloc(1, sizeof(*iter)); struct ref_iterator *ref_iterator = &iter->base; + /* Check validity (but we don't need the result): */ + files_downcast(ref_store, 0, "reflog_iterator_begin"); + base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable); iter->dir_iterator = dir_iterator_begin(git_path("logs")); return ref_iterator; } -int for_each_reflog(each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref_iterator(files_reflog_iterator_begin(), - fn, cb_data); -} - static int ref_update_reject_duplicates(struct string_list *refnames, struct strbuf *err) { @@ -3448,7 +3420,8 @@ static int split_head_update(struct ref_update *update, * Note that the new update will itself be subject to splitting when * the iteration gets to it. */ -static int split_symref_update(struct ref_update *update, +static int split_symref_update(struct files_ref_store *refs, + struct ref_update *update, const char *referent, struct ref_transaction *transaction, struct string_list *affected_refnames, @@ -3562,7 +3535,8 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid, * - If it is an update of head_ref, add a corresponding REF_LOG_ONLY * update of HEAD. */ -static int lock_ref_for_update(struct ref_update *update, +static int lock_ref_for_update(struct files_ref_store *refs, + struct ref_update *update, struct ref_transaction *transaction, const char *head_ref, struct string_list *affected_refnames, @@ -3574,6 +3548,8 @@ static int lock_ref_for_update(struct ref_update *update, int ret; struct ref_lock *lock; + assert_main_repository(&refs->base, "lock_ref_for_update"); + if ((update->flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1)) update->flags |= REF_DELETING; @@ -3584,11 +3560,10 @@ static int lock_ref_for_update(struct ref_update *update, return ret; } - ret = lock_raw_ref(update->refname, mustexist, + ret = lock_raw_ref(refs, update->refname, mustexist, affected_refnames, NULL, - &update->lock, &referent, + &lock, &referent, &update->type, err); - if (ret) { char *reason; @@ -3599,7 +3574,7 @@ static int lock_ref_for_update(struct ref_update *update, return ret; } - lock = update->lock; + update->backend_data = lock; if (update->type & REF_ISSYMREF) { if (update->flags & REF_NODEREF) { @@ -3627,7 +3602,8 @@ static int lock_ref_for_update(struct ref_update *update, * of processing the split-off update, so we * don't have to do it here. */ - ret = split_symref_update(update, referent.buf, transaction, + ret = split_symref_update(refs, update, + referent.buf, transaction, affected_refnames, err); if (ret) return ret; @@ -3646,7 +3622,8 @@ static int lock_ref_for_update(struct ref_update *update, for (parent_update = update->parent_update; parent_update; parent_update = parent_update->parent_update) { - oidcpy(&parent_update->lock->old_oid, &lock->old_oid); + struct ref_lock *parent_lock = parent_update->backend_data; + oidcpy(&parent_lock->old_oid, &lock->old_oid); } } @@ -3667,7 +3644,7 @@ static int lock_ref_for_update(struct ref_update *update, * The lock was freed upon failure of * write_ref_to_lockfile(): */ - update->lock = NULL; + update->backend_data = NULL; strbuf_addf(err, "cannot update ref '%s': %s", update->refname, write_err); @@ -3692,9 +3669,12 @@ static int lock_ref_for_update(struct ref_update *update, return 0; } -int ref_transaction_commit(struct ref_transaction *transaction, - struct strbuf *err) +static int files_transaction_commit(struct ref_store *ref_store, + struct ref_transaction *transaction, + struct strbuf *err) { + struct files_ref_store *refs = + files_downcast(ref_store, 0, "ref_transaction_commit"); int ret = 0, i; struct string_list refs_to_delete = STRING_LIST_INIT_NODUP; struct string_list_item *ref_to_delete; @@ -3773,8 +3753,8 @@ int ref_transaction_commit(struct ref_transaction *transaction, for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; - ret = lock_ref_for_update(update, transaction, head_ref, - &affected_refnames, err); + ret = lock_ref_for_update(refs, update, transaction, + head_ref, &affected_refnames, err); if (ret) goto cleanup; } @@ -3782,7 +3762,7 @@ int ref_transaction_commit(struct ref_transaction *transaction, /* Perform updates first so live commits remain referenced */ for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; - struct ref_lock *lock = update->lock; + struct ref_lock *lock = update->backend_data; if (update->flags & REF_NEEDS_COMMIT || update->flags & REF_LOG_ONLY) { @@ -3795,17 +3775,17 @@ int ref_transaction_commit(struct ref_transaction *transaction, lock->ref_name, old_msg); free(old_msg); unlock_ref(lock); - update->lock = NULL; + update->backend_data = NULL; ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } } if (update->flags & REF_NEEDS_COMMIT) { - clear_loose_ref_cache(&ref_cache); + clear_loose_ref_cache(refs); if (commit_ref(lock)) { strbuf_addf(err, "couldn't set '%s'", lock->ref_name); unlock_ref(lock); - update->lock = NULL; + update->backend_data = NULL; ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } @@ -3814,34 +3794,35 @@ int ref_transaction_commit(struct ref_transaction *transaction, /* Perform deletes now that updates are safely completed */ for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; + struct ref_lock *lock = update->backend_data; if (update->flags & REF_DELETING && !(update->flags & REF_LOG_ONLY)) { - if (delete_ref_loose(update->lock, update->type, err)) { + if (delete_ref_loose(lock, update->type, err)) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } if (!(update->flags & REF_ISPRUNING)) string_list_append(&refs_to_delete, - update->lock->ref_name); + lock->ref_name); } } - if (repack_without_refs(&refs_to_delete, err)) { + if (repack_without_refs(refs, &refs_to_delete, err)) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } for_each_string_list_item(ref_to_delete, &refs_to_delete) unlink_or_warn(git_path("logs/%s", ref_to_delete->string)); - clear_loose_ref_cache(&ref_cache); + clear_loose_ref_cache(refs); cleanup: transaction->state = REF_TRANSACTION_CLOSED; for (i = 0; i < transaction->nr; i++) - if (transaction->updates[i]->lock) - unlock_ref(transaction->updates[i]->lock); + if (transaction->updates[i]->backend_data) + unlock_ref(transaction->updates[i]->backend_data); string_list_clear(&refs_to_delete, 0); free(head_ref); string_list_clear(&affected_refnames, 0); @@ -3857,9 +3838,12 @@ static int ref_present(const char *refname, return string_list_has_string(affected_refnames, refname); } -int initial_ref_transaction_commit(struct ref_transaction *transaction, - struct strbuf *err) +static int files_initial_transaction_commit(struct ref_store *ref_store, + struct ref_transaction *transaction, + struct strbuf *err) { + struct files_ref_store *refs = + files_downcast(ref_store, 0, "initial_ref_transaction_commit"); int ret = 0, i; struct string_list affected_refnames = STRING_LIST_INIT_NODUP; @@ -3907,7 +3891,7 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction, } } - if (lock_packed_refs(0)) { + if (lock_packed_refs(refs, 0)) { strbuf_addf(err, "unable to lock packed-refs file: %s", strerror(errno)); ret = TRANSACTION_GENERIC_ERROR; @@ -3919,10 +3903,10 @@ int initial_ref_transaction_commit(struct ref_transaction *transaction, if ((update->flags & REF_HAVE_NEW) && !is_null_sha1(update->new_sha1)) - add_packed_ref(update->refname, update->new_sha1); + add_packed_ref(refs, update->refname, update->new_sha1); } - if (commit_packed_refs()) { + if (commit_packed_refs(refs)) { strbuf_addf(err, "unable to commit packed-refs file: %s", strerror(errno)); ret = TRANSACTION_GENERIC_ERROR; @@ -3972,13 +3956,16 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, return 0; } -int reflog_expire(const char *refname, const unsigned char *sha1, - unsigned int flags, - reflog_expiry_prepare_fn prepare_fn, - reflog_expiry_should_prune_fn should_prune_fn, - reflog_expiry_cleanup_fn cleanup_fn, - void *policy_cb_data) +static int files_reflog_expire(struct ref_store *ref_store, + const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data) { + struct files_ref_store *refs = + files_downcast(ref_store, 0, "reflog_expire"); static struct lock_file reflog_lock; struct expire_reflog_cb cb; struct ref_lock *lock; @@ -3997,7 +3984,8 @@ int reflog_expire(const char *refname, const unsigned char *sha1, * reference itself, plus we might need to update the * reference if --updateref was specified: */ - lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, REF_NODEREF, + lock = lock_ref_sha1_basic(refs, refname, sha1, + NULL, NULL, REF_NODEREF, &type, &err); if (!lock) { error("cannot lock ref '%s': %s", refname, err.buf); @@ -4077,3 +4065,47 @@ int reflog_expire(const char *refname, const unsigned char *sha1, unlock_ref(lock); return -1; } + +static int files_init_db(struct ref_store *ref_store, struct strbuf *err) +{ + /* Check validity (but we don't need the result): */ + files_downcast(ref_store, 0, "init_db"); + + /* + * Create .git/refs/{heads,tags} + */ + safe_create_dir(git_path("refs/heads"), 1); + safe_create_dir(git_path("refs/tags"), 1); + if (get_shared_repository()) { + adjust_shared_perm(git_path("refs/heads")); + adjust_shared_perm(git_path("refs/tags")); + } + return 0; +} + +struct ref_storage_be refs_be_files = { + NULL, + "files", + files_ref_store_create, + files_init_db, + files_transaction_commit, + files_initial_transaction_commit, + + files_pack_refs, + files_peel_ref, + files_create_symref, + files_delete_refs, + files_rename_ref, + + files_ref_iterator_begin, + files_read_raw_ref, + files_verify_refname_available, + + files_reflog_iterator_begin, + files_for_each_reflog_ent, + files_for_each_reflog_ent_reverse, + files_reflog_exists, + files_create_reflog, + files_delete_reflog, + files_reflog_expire +}; diff --git a/refs/refs-internal.h b/refs/refs-internal.h index efe584701b..708b26082a 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -162,7 +162,7 @@ struct ref_update { */ unsigned int flags; - struct ref_lock *lock; + void *backend_data; unsigned int type; char *msg; @@ -240,7 +240,17 @@ const char *find_descendant_ref(const char *dirname, const struct string_list *extras, const struct string_list *skip); -int rename_ref_available(const char *oldname, const char *newname); +/* + * Check whether an attempt to rename old_refname to new_refname would + * cause a D/F conflict with any existing reference (other than + * possibly old_refname). If there would be a conflict, emit an error + * message and return false; otherwise, return true. + * + * Note that this function is not safe against all races with other + * processes (though rename_ref() catches some races that might get by + * this check). + */ +int rename_ref_available(const char *old_refname, const char *new_refname); /* We allow "recursive" symbolic refs. Only within reason, though */ #define SYMREF_MAXDEPTH 5 @@ -394,23 +404,6 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, const char *prefix, int trim); -/* - * Iterate over the packed and loose references in the specified - * submodule that are within find_containing_dir(prefix). If prefix is - * NULL or the empty string, iterate over all references in the - * submodule. - */ -struct ref_iterator *files_ref_iterator_begin(const char *submodule, - const char *prefix, - unsigned int flags); - -/* - * Iterate over the references in the main ref_store that have a - * reflog. The paths within a directory are iterated over in arbitrary - * order. - */ -struct ref_iterator *files_reflog_iterator_begin(void); - /* Internal implementation of reference iteration: */ /* @@ -475,8 +468,85 @@ int do_for_each_ref_iterator(struct ref_iterator *iter, each_ref_fn fn, void *cb_data); /* - * Read the specified reference from the filesystem or packed refs - * file, non-recursively. Set type to describe the reference, and: + * Only include per-worktree refs in a do_for_each_ref*() iteration. + * Normally this will be used with a files ref_store, since that's + * where all reference backends will presumably store their + * per-worktree refs. + */ +#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02 + +struct ref_store; + +/* refs backends */ + +/* + * Initialize the ref_store for the specified submodule, or for the + * main repository if submodule == NULL. These functions should call + * base_ref_store_init() to initialize the shared part of the + * ref_store and to record the ref_store for later lookup. + */ +typedef struct ref_store *ref_store_init_fn(const char *submodule); + +typedef int ref_init_db_fn(struct ref_store *refs, struct strbuf *err); + +typedef int ref_transaction_commit_fn(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err); + +typedef int pack_refs_fn(struct ref_store *ref_store, unsigned int flags); +typedef int peel_ref_fn(struct ref_store *ref_store, + const char *refname, unsigned char *sha1); +typedef int create_symref_fn(struct ref_store *ref_store, + const char *ref_target, + const char *refs_heads_master, + const char *logmsg); +typedef int delete_refs_fn(struct ref_store *ref_store, + struct string_list *refnames, unsigned int flags); +typedef int rename_ref_fn(struct ref_store *ref_store, + const char *oldref, const char *newref, + const char *logmsg); + +/* + * Iterate over the references in the specified ref_store that are + * within find_containing_dir(prefix). If prefix is NULL or the empty + * string, iterate over all references in the submodule. + */ +typedef struct ref_iterator *ref_iterator_begin_fn( + struct ref_store *ref_store, + const char *prefix, unsigned int flags); + +/* reflog functions */ + +/* + * Iterate over the references in the specified ref_store that have a + * reflog. The refs are iterated over in arbitrary order. + */ +typedef struct ref_iterator *reflog_iterator_begin_fn( + struct ref_store *ref_store); + +typedef int for_each_reflog_ent_fn(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data); +typedef int for_each_reflog_ent_reverse_fn(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data); +typedef int reflog_exists_fn(struct ref_store *ref_store, const char *refname); +typedef int create_reflog_fn(struct ref_store *ref_store, const char *refname, + int force_create, struct strbuf *err); +typedef int delete_reflog_fn(struct ref_store *ref_store, const char *refname); +typedef int reflog_expire_fn(struct ref_store *ref_store, + const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data); + +/* + * Read a reference from the specified reference store, non-recursively. + * Set type to describe the reference, and: * * - If refname is the name of a normal reference, fill in sha1 * (leaving referent unchanged). @@ -512,7 +582,111 @@ int do_for_each_ref_iterator(struct ref_iterator *iter, * - in all other cases, referent will be untouched, and therefore * refname will still be valid and unchanged. */ -int read_raw_ref(const char *refname, unsigned char *sha1, - struct strbuf *referent, unsigned int *type); +typedef int read_raw_ref_fn(struct ref_store *ref_store, + const char *refname, unsigned char *sha1, + struct strbuf *referent, unsigned int *type); + +typedef int verify_refname_available_fn(struct ref_store *ref_store, + const char *newname, + const struct string_list *extras, + const struct string_list *skip, + struct strbuf *err); + +struct ref_storage_be { + struct ref_storage_be *next; + const char *name; + ref_store_init_fn *init; + ref_init_db_fn *init_db; + ref_transaction_commit_fn *transaction_commit; + ref_transaction_commit_fn *initial_transaction_commit; + + pack_refs_fn *pack_refs; + peel_ref_fn *peel_ref; + create_symref_fn *create_symref; + delete_refs_fn *delete_refs; + rename_ref_fn *rename_ref; + + ref_iterator_begin_fn *iterator_begin; + read_raw_ref_fn *read_raw_ref; + verify_refname_available_fn *verify_refname_available; + + reflog_iterator_begin_fn *reflog_iterator_begin; + for_each_reflog_ent_fn *for_each_reflog_ent; + for_each_reflog_ent_reverse_fn *for_each_reflog_ent_reverse; + reflog_exists_fn *reflog_exists; + create_reflog_fn *create_reflog; + delete_reflog_fn *delete_reflog; + reflog_expire_fn *reflog_expire; +}; + +extern struct ref_storage_be refs_be_files; + +/* + * A representation of the reference store for the main repository or + * a submodule. The ref_store instances for submodules are kept in a + * linked list. + */ +struct ref_store { + /* The backend describing this ref_store's storage scheme: */ + const struct ref_storage_be *be; + + /* + * The name of the submodule represented by this object, or + * the empty string if it represents the main repository's + * reference store: + */ + const char *submodule; + + /* + * Submodule reference store instances are stored in a linked + * list using this pointer. + */ + struct ref_store *next; +}; + +/* + * Fill in the generic part of refs for the specified submodule and + * add it to our collection of reference stores. + */ +void base_ref_store_init(struct ref_store *refs, + const struct ref_storage_be *be, + const char *submodule); + +/* + * Create, record, and return a ref_store instance for the specified + * submodule (or the main repository if submodule is NULL). + * + * For backwards compatibility, submodule=="" is treated the same as + * submodule==NULL. + */ +struct ref_store *ref_store_init(const char *submodule); + +/* + * Return the ref_store instance for the specified submodule (or the + * main repository if submodule is NULL). If that ref_store hasn't + * been initialized yet, return NULL. + * + * For backwards compatibility, submodule=="" is treated the same as + * submodule==NULL. + */ +struct ref_store *lookup_ref_store(const char *submodule); + +/* + * Return the ref_store instance for the specified submodule. For the + * main repository, use submodule==NULL; such a call cannot fail. For + * a submodule, the submodule must exist and be a nonbare repository, + * otherwise return NULL. If the requested reference store has not yet + * been initialized, initialize it first. + * + * For backwards compatibility, submodule=="" is treated the same as + * submodule==NULL. + */ +struct ref_store *get_ref_store(const char *submodule); + +/* + * Die if refs is for a submodule (i.e., not for the main repository). + * caller is used in any necessary error messages. + */ +void assert_main_repository(struct ref_store *refs, const char *caller); #endif /* REFS_REFS_INTERNAL_H */ @@ -2073,7 +2073,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) _("Your branch is based on '%s', but the upstream is gone.\n"), base); if (advice_status_hints) - strbuf_addf(sb, + strbuf_addstr(sb, _(" (use \"git branch --unset-upstream\" to fixup)\n")); } else if (!ours && !theirs) { strbuf_addf(sb, @@ -2086,7 +2086,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) ours), base, ours); if (advice_status_hints) - strbuf_addf(sb, + strbuf_addstr(sb, _(" (use \"git push\" to publish your local commits)\n")); } else if (!ours) { strbuf_addf(sb, @@ -2097,7 +2097,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) theirs), base, theirs); if (advice_status_hints) - strbuf_addf(sb, + strbuf_addstr(sb, _(" (use \"git pull\" to update your local branch)\n")); } else { strbuf_addf(sb, @@ -2110,7 +2110,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) ours + theirs), base, ours, theirs); if (advice_status_hints) - strbuf_addf(sb, + strbuf_addstr(sb, _(" (use \"git pull\" to merge the remote branch into yours)\n")); } free(base); @@ -980,7 +980,8 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu break; i = ce_stage(ce) - 1; if (!mmfile[i].ptr) { - mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); + mmfile[i].ptr = read_sha1_file(ce->oid.hash, &type, + &size); mmfile[i].size = size; } } diff --git a/resolve-undo.c b/resolve-undo.c index 468a2eb92c..b40f3173d3 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -24,7 +24,7 @@ void record_resolve_undo(struct index_state *istate, struct cache_entry *ce) if (!lost->util) lost->util = xcalloc(1, sizeof(*ui)); ui = lost->util; - hashcpy(ui->sha1[stage - 1], ce->sha1); + hashcpy(ui->sha1[stage - 1], ce->oid.hash); ui->mode[stage - 1] = ce->ce_mode; } diff --git a/revision.c b/revision.c index 8a29cb03c5..969b3d149f 100644 --- a/revision.c +++ b/revision.c @@ -1275,7 +1275,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned flags) if (S_ISGITLINK(ce->ce_mode)) continue; - blob = lookup_blob(ce->sha1); + blob = lookup_blob(ce->oid.hash); if (!blob) die("unable to add index blob to traversal"); add_pending_object_with_path(revs, &blob->object, "", diff --git a/sequencer.c b/sequencer.c index 3804fa931d..eec8a60d6b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -180,17 +180,20 @@ static void print_advice(int show_hint, struct replay_opts *opts) } } -static void write_message(struct strbuf *msgbuf, const char *filename) +static int write_message(struct strbuf *msgbuf, const char *filename) { static struct lock_file msg_file; - int msg_fd = hold_lock_file_for_update(&msg_file, filename, - LOCK_DIE_ON_ERROR); + int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0); + if (msg_fd < 0) + return error_errno(_("Could not lock '%s'"), filename); if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - die_errno(_("Could not write to %s"), filename); + return error_errno(_("Could not write to %s"), filename); strbuf_release(msgbuf); if (commit_lock_file(&msg_file) < 0) - die(_("Error wrapping up %s."), filename); + return error(_("Error wrapping up %s."), filename); + + return 0; } static struct tree *empty_tree(void) @@ -223,7 +226,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, read_cache(); if (checkout_fast_forward(from, to, 1)) - exit(128); /* the callee should have complained already */ + return -1; /* the callee should have complained already */ strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts)); @@ -300,7 +303,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" or "cherry-pick" */ - die(_("%s: Unable to write new index file"), action_name(opts)); + return error(_("%s: Unable to write new index file"), + action_name(opts)); rollback_lock_file(&index_lock); if (opts->signoff) @@ -460,7 +464,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * to work on. */ if (write_cache_as_tree(head, 0, NULL)) - die (_("Your index file is unmerged.")); + return error(_("Your index file is unmerged.")); } else { unborn = get_sha1("HEAD", head); if (unborn) @@ -564,16 +568,16 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) head, &msgbuf, opts); if (res < 0) return res; - write_message(&msgbuf, git_path_merge_msg()); + res |= write_message(&msgbuf, git_path_merge_msg()); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; - write_message(&msgbuf, git_path_merge_msg()); + res = write_message(&msgbuf, git_path_merge_msg()); commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, + res |= try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); @@ -585,12 +589,14 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) - update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); - if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) - update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); + if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1) && + update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + res = -1; + if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1) && + update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + res = -1; if (res) { error(opts->action == REPLAY_REVERT @@ -617,7 +623,7 @@ leave: return res; } -static void prepare_revs(struct replay_opts *opts) +static int prepare_revs(struct replay_opts *opts) { /* * picking (but not reverting) ranges (but not individual revisions) @@ -627,24 +633,32 @@ static void prepare_revs(struct replay_opts *opts) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); + return error(_("revision walk setup failed")); if (!opts->revs->commits) - die(_("empty commit set passed")); + return error(_("empty commit set passed")); + return 0; } -static void read_and_refresh_cache(struct replay_opts *opts) +static int read_and_refresh_cache(struct replay_opts *opts) { static struct lock_file index_lock; int index_fd = hold_locked_index(&index_lock, 0); - if (read_index_preload(&the_index, NULL) < 0) - die(_("git %s: failed to read the index"), action_name(opts)); + if (read_index_preload(&the_index, NULL) < 0) { + rollback_lock_file(&index_lock); + return error(_("git %s: failed to read the index"), + action_name(opts)); + } refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed && index_fd >= 0) { - if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - die(_("git %s: failed to refresh the index"), action_name(opts)); + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) { + rollback_lock_file(&index_lock); + return error(_("git %s: failed to refresh the index"), + action_name(opts)); + } } rollback_lock_file(&index_lock); + return 0; } static int format_todo(struct strbuf *buf, struct commit_list *todo_list, @@ -738,7 +752,7 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list, return 0; } -static void read_populate_todo(struct commit_list **todo_list, +static int read_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; @@ -746,18 +760,21 @@ static void read_populate_todo(struct commit_list **todo_list, fd = open(git_path_todo_file(), O_RDONLY); if (fd < 0) - die_errno(_("Could not open %s"), git_path_todo_file()); + return error_errno(_("Could not open %s"), + git_path_todo_file()); if (strbuf_read(&buf, fd, 0) < 0) { close(fd); strbuf_release(&buf); - die(_("Could not read %s."), git_path_todo_file()); + return error(_("Could not read %s."), git_path_todo_file()); } close(fd); res = parse_insn_buffer(buf.buf, todo_list, opts); strbuf_release(&buf); if (res) - die(_("Unusable instruction sheet: %s"), git_path_todo_file()); + return error(_("Unusable instruction sheet: %s"), + git_path_todo_file()); + return 0; } static int populate_opts_cb(const char *key, const char *value, void *data) @@ -795,25 +812,35 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } -static void read_populate_opts(struct replay_opts **opts_ptr) +static int read_populate_opts(struct replay_opts **opts) { if (!file_exists(git_path_opts_file())) - return; - if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts_ptr) < 0) - die(_("Malformed options sheet: %s"), git_path_opts_file()); + return 0; + /* + * The function git_parse_source(), called from git_config_from_file(), + * may die() in case of a syntactically incorrect file. We do not care + * about this case, though, because we wrote that file ourselves, so we + * are pretty certain that it is syntactically correct. + */ + if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts) < 0) + return error(_("Malformed options sheet: %s"), + git_path_opts_file()); + return 0; } -static void walk_revs_populate_todo(struct commit_list **todo_list, +static int walk_revs_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { struct commit *commit; struct commit_list **next; - prepare_revs(opts); + if (prepare_revs(opts)) + return -1; next = todo_list; while ((commit = get_revision(opts->revs))) next = commit_list_append(commit, next); + return 0; } static int create_seq_dir(void) @@ -824,23 +851,33 @@ static int create_seq_dir(void) return -1; } else if (mkdir(git_path_seq_dir(), 0777) < 0) - die_errno(_("Could not create sequencer directory %s"), - git_path_seq_dir()); + return error_errno(_("Could not create sequencer directory %s"), + git_path_seq_dir()); return 0; } -static void save_head(const char *head) +static int save_head(const char *head) { static struct lock_file head_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0); + if (fd < 0) { + rollback_lock_file(&head_lock); + return error_errno(_("Could not lock HEAD")); + } strbuf_addf(&buf, "%s\n", head); - if (write_in_full(fd, buf.buf, buf.len) < 0) - die_errno(_("Could not write to %s"), git_path_head_file()); - if (commit_lock_file(&head_lock) < 0) - die(_("Error wrapping up %s."), git_path_head_file()); + if (write_in_full(fd, buf.buf, buf.len) < 0) { + rollback_lock_file(&head_lock); + return error_errno(_("Could not write to %s"), + git_path_head_file()); + } + if (commit_lock_file(&head_lock) < 0) { + rollback_lock_file(&head_lock); + return error(_("Error wrapping up %s."), git_path_head_file()); + } + return 0; } static int reset_for_rollback(const unsigned char *sha1) @@ -910,57 +947,66 @@ fail: return -1; } -static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) +static int save_todo(struct commit_list *todo_list, struct replay_opts *opts) { static struct lock_file todo_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), LOCK_DIE_ON_ERROR); - if (format_todo(&buf, todo_list, opts) < 0) - die(_("Could not format %s."), git_path_todo_file()); + fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), 0); + if (fd < 0) + return error_errno(_("Could not lock '%s'"), + git_path_todo_file()); + if (format_todo(&buf, todo_list, opts) < 0) { + strbuf_release(&buf); + return error(_("Could not format %s."), git_path_todo_file()); + } if (write_in_full(fd, buf.buf, buf.len) < 0) { strbuf_release(&buf); - die_errno(_("Could not write to %s"), git_path_todo_file()); + return error_errno(_("Could not write to %s"), + git_path_todo_file()); } if (commit_lock_file(&todo_lock) < 0) { strbuf_release(&buf); - die(_("Error wrapping up %s."), git_path_todo_file()); + return error(_("Error wrapping up %s."), git_path_todo_file()); } strbuf_release(&buf); + return 0; } -static void save_opts(struct replay_opts *opts) +static int save_opts(struct replay_opts *opts) { const char *opts_file = git_path_opts_file(); + int res = 0; if (opts->no_commit) - git_config_set_in_file(opts_file, "options.no-commit", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.no-commit", "true"); if (opts->edit) - git_config_set_in_file(opts_file, "options.edit", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.edit", "true"); if (opts->signoff) - git_config_set_in_file(opts_file, "options.signoff", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.signoff", "true"); if (opts->record_origin) - git_config_set_in_file(opts_file, "options.record-origin", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.record-origin", "true"); if (opts->allow_ff) - git_config_set_in_file(opts_file, "options.allow-ff", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.allow-ff", "true"); if (opts->mainline) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%d", opts->mainline); - git_config_set_in_file(opts_file, "options.mainline", buf.buf); + res |= git_config_set_in_file_gently(opts_file, "options.mainline", buf.buf); strbuf_release(&buf); } if (opts->strategy) - git_config_set_in_file(opts_file, "options.strategy", opts->strategy); + res |= git_config_set_in_file_gently(opts_file, "options.strategy", opts->strategy); if (opts->gpg_sign) - git_config_set_in_file(opts_file, "options.gpg-sign", opts->gpg_sign); + res |= git_config_set_in_file_gently(opts_file, "options.gpg-sign", opts->gpg_sign); if (opts->xopts) { int i; for (i = 0; i < opts->xopts_nr; i++) - git_config_set_multivar_in_file(opts_file, + res |= git_config_set_multivar_in_file_gently(opts_file, "options.strategy-option", opts->xopts[i], "^$", 0); } + return res; } static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) @@ -972,10 +1018,12 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || opts->edit)); - read_and_refresh_cache(opts); + if (read_and_refresh_cache(opts)) + return -1; for (cur = todo_list; cur; cur = cur->next) { - save_todo(cur, opts); + if (save_todo(cur, opts)) + return -1; res = do_pick_commit(cur->item, opts); if (res) return res; @@ -1005,8 +1053,9 @@ static int sequencer_continue(struct replay_opts *opts) if (!file_exists(git_path_todo_file())) return continue_single_pick(); - read_populate_opts(&opts); - read_populate_todo(&todo_list, opts); + if (read_populate_opts(&opts) || + read_populate_todo(&todo_list, opts)) + return -1; /* Verify that the conflict has been resolved */ if (file_exists(git_path_cherry_pick_head()) || @@ -1036,7 +1085,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (opts->subcommand == REPLAY_NONE) assert(opts->revs); - read_and_refresh_cache(opts); + if (read_and_refresh_cache(opts)) + return -1; /* * Decide what to do depending on the arguments; a fresh @@ -1063,10 +1113,11 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (!get_sha1(name, sha1)) { if (!lookup_commit_reference_gently(sha1, 1)) { enum object_type type = sha1_object_info(sha1, NULL); - die(_("%s: can't cherry-pick a %s"), name, typename(type)); + return error(_("%s: can't cherry-pick a %s"), + name, typename(type)); } } else - die(_("%s: bad revision"), name); + return error(_("%s: bad revision"), name); } /* @@ -1082,10 +1133,10 @@ int sequencer_pick_revisions(struct replay_opts *opts) !opts->revs->cmdline.rev->flags) { struct commit *cmit; if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); + return error(_("revision walk setup failed")); cmit = get_revision(opts->revs); if (!cmit || get_revision(opts->revs)) - die("BUG: expected exactly one commit from walk"); + return error("BUG: expected exactly one commit from walk"); return single_pick(cmit, opts); } @@ -1095,13 +1146,15 @@ int sequencer_pick_revisions(struct replay_opts *opts) * progress */ - walk_revs_populate_todo(&todo_list, opts); - if (create_seq_dir() < 0) + if (walk_revs_populate_todo(&todo_list, opts) || + create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT)) return error(_("Can't revert as initial commit")); - save_head(sha1_to_hex(sha1)); - save_opts(opts); + if (save_head(sha1_to_hex(sha1))) + return -1; + if (save_opts(opts)) + return -1; return pick_commits(todo_list, opts); } diff --git a/sha1_file.c b/sha1_file.c index 3045aeabda..b9c1fa3f1a 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -24,6 +24,8 @@ #include "streaming.h" #include "dir.h" #include "mru.h" +#include "list.h" +#include "mergesort.h" #ifndef O_NOATIME #if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) @@ -38,6 +40,12 @@ static inline uintmax_t sz_fmt(size_t s) { return s; } const unsigned char null_sha1[20]; const struct object_id null_oid; +const struct object_id empty_tree_oid = { + EMPTY_TREE_SHA1_BIN_LITERAL +}; +const struct object_id empty_blob_oid = { + EMPTY_BLOB_SHA1_BIN_LITERAL +}; /* * This is meant to hold a *small* number of objects that you would @@ -418,6 +426,82 @@ void add_to_alternates_file(const char *reference) free(alts); } +/* + * Compute the exact path an alternate is at and returns it. In case of + * error NULL is returned and the human readable error is added to `err` + * `path` may be relative and should point to $GITDIR. + * `err` must not be null. + */ +char *compute_alternate_path(const char *path, struct strbuf *err) +{ + char *ref_git = NULL; + const char *repo, *ref_git_s; + int seen_error = 0; + + ref_git_s = real_path_if_valid(path); + if (!ref_git_s) { + seen_error = 1; + strbuf_addf(err, _("path '%s' does not exist"), path); + goto out; + } else + /* + * Beware: read_gitfile(), real_path() and mkpath() + * return static buffer + */ + ref_git = xstrdup(ref_git_s); + + repo = read_gitfile(ref_git); + if (!repo) + repo = read_gitfile(mkpath("%s/.git", ref_git)); + if (repo) { + free(ref_git); + ref_git = xstrdup(repo); + } + + if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) { + char *ref_git_git = mkpathdup("%s/.git", ref_git); + free(ref_git); + ref_git = ref_git_git; + } else if (!is_directory(mkpath("%s/objects", ref_git))) { + struct strbuf sb = STRBUF_INIT; + seen_error = 1; + if (get_common_dir(&sb, ref_git)) { + strbuf_addf(err, + _("reference repository '%s' as a linked " + "checkout is not supported yet."), + path); + goto out; + } + + strbuf_addf(err, _("reference repository '%s' is not a " + "local repository."), path); + goto out; + } + + if (!access(mkpath("%s/shallow", ref_git), F_OK)) { + strbuf_addf(err, _("reference repository '%s' is shallow"), + path); + seen_error = 1; + goto out; + } + + if (!access(mkpath("%s/info/grafts", ref_git), F_OK)) { + strbuf_addf(err, + _("reference repository '%s' is grafted"), + path); + seen_error = 1; + goto out; + } + +out: + if (seen_error) { + free(ref_git); + ref_git = NULL; + } + + return ref_git; +} + int foreach_alt_odb(alt_odb_fn fn, void *cb) { struct alternate_object_database *ent; @@ -1297,10 +1381,20 @@ static void prepare_packed_git_one(char *objdir, int local) strbuf_release(&path); } +static void *get_next_packed_git(const void *p) +{ + return ((const struct packed_git *)p)->next; +} + +static void set_next_packed_git(void *p, void *next) +{ + ((struct packed_git *)p)->next = next; +} + static int sort_pack(const void *a_, const void *b_) { - struct packed_git *a = *((struct packed_git **)a_); - struct packed_git *b = *((struct packed_git **)b_); + const struct packed_git *a = a_; + const struct packed_git *b = b_; int st; /* @@ -1327,28 +1421,8 @@ static int sort_pack(const void *a_, const void *b_) static void rearrange_packed_git(void) { - struct packed_git **ary, *p; - int i, n; - - for (n = 0, p = packed_git; p; p = p->next) - n++; - if (n < 2) - return; - - /* prepare an array of packed_git for easier sorting */ - ary = xcalloc(n, sizeof(struct packed_git *)); - for (n = 0, p = packed_git; p; p = p->next) - ary[n++] = p; - - qsort(ary, n, sizeof(struct packed_git *), sort_pack); - - /* link them back again */ - for (i = 0; i < n - 1; i++) - ary[i]->next = ary[i + 1]; - ary[n - 1]->next = NULL; - packed_git = ary[0]; - - free(ary); + packed_git = llist_mergesort(packed_git, get_next_packed_git, + set_next_packed_git, sort_pack); } static void prepare_packed_git_mru(void) @@ -2073,136 +2147,142 @@ static void *unpack_compressed_entry(struct packed_git *p, return buffer; } -#define MAX_DELTA_CACHE (256) - +static struct hashmap delta_base_cache; static size_t delta_base_cached; -static struct delta_base_cache_lru_list { - struct delta_base_cache_lru_list *prev; - struct delta_base_cache_lru_list *next; -} delta_base_cache_lru = { &delta_base_cache_lru, &delta_base_cache_lru }; +static LIST_HEAD(delta_base_cache_lru); -static struct delta_base_cache_entry { - struct delta_base_cache_lru_list lru; - void *data; +struct delta_base_cache_key { struct packed_git *p; off_t base_offset; +}; + +struct delta_base_cache_entry { + struct hashmap hash; + struct delta_base_cache_key key; + struct list_head lru; + void *data; unsigned long size; enum object_type type; -} delta_base_cache[MAX_DELTA_CACHE]; +}; -static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset) +static unsigned int pack_entry_hash(struct packed_git *p, off_t base_offset) { - unsigned long hash; + unsigned int hash; - hash = (unsigned long)(intptr_t)p + (unsigned long)base_offset; + hash = (unsigned int)(intptr_t)p + (unsigned int)base_offset; hash += (hash >> 8) + (hash >> 16); - return hash % MAX_DELTA_CACHE; + return hash; } static struct delta_base_cache_entry * get_delta_base_cache_entry(struct packed_git *p, off_t base_offset) { - unsigned long hash = pack_entry_hash(p, base_offset); - return delta_base_cache + hash; + struct hashmap_entry entry; + struct delta_base_cache_key key; + + if (!delta_base_cache.cmpfn) + return NULL; + + hashmap_entry_init(&entry, pack_entry_hash(p, base_offset)); + key.p = p; + key.base_offset = base_offset; + return hashmap_get(&delta_base_cache, &entry, &key); +} + +static int delta_base_cache_key_eq(const struct delta_base_cache_key *a, + const struct delta_base_cache_key *b) +{ + return a->p == b->p && a->base_offset == b->base_offset; } -static int eq_delta_base_cache_entry(struct delta_base_cache_entry *ent, - struct packed_git *p, off_t base_offset) +static int delta_base_cache_hash_cmp(const void *va, const void *vb, + const void *vkey) { - return (ent->data && ent->p == p && ent->base_offset == base_offset); + const struct delta_base_cache_entry *a = va, *b = vb; + const struct delta_base_cache_key *key = vkey; + if (key) + return !delta_base_cache_key_eq(&a->key, key); + else + return !delta_base_cache_key_eq(&a->key, &b->key); } static int in_delta_base_cache(struct packed_git *p, off_t base_offset) { - struct delta_base_cache_entry *ent; - ent = get_delta_base_cache_entry(p, base_offset); - return eq_delta_base_cache_entry(ent, p, base_offset); + return !!get_delta_base_cache_entry(p, base_offset); } -static void clear_delta_base_cache_entry(struct delta_base_cache_entry *ent) +/* + * Remove the entry from the cache, but do _not_ free the associated + * entry data. The caller takes ownership of the "data" buffer, and + * should copy out any fields it wants before detaching. + */ +static void detach_delta_base_cache_entry(struct delta_base_cache_entry *ent) { - ent->data = NULL; - ent->lru.next->prev = ent->lru.prev; - ent->lru.prev->next = ent->lru.next; + hashmap_remove(&delta_base_cache, ent, &ent->key); + list_del(&ent->lru); delta_base_cached -= ent->size; + free(ent); } static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset, - unsigned long *base_size, enum object_type *type, int keep_cache) + unsigned long *base_size, enum object_type *type) { struct delta_base_cache_entry *ent; - void *ret; ent = get_delta_base_cache_entry(p, base_offset); - - if (!eq_delta_base_cache_entry(ent, p, base_offset)) + if (!ent) return unpack_entry(p, base_offset, type, base_size); - ret = ent->data; - - if (!keep_cache) - clear_delta_base_cache_entry(ent); - else - ret = xmemdupz(ent->data, ent->size); *type = ent->type; *base_size = ent->size; - return ret; + return xmemdupz(ent->data, ent->size); } static inline void release_delta_base_cache(struct delta_base_cache_entry *ent) { - if (ent->data) { - free(ent->data); - ent->data = NULL; - ent->lru.next->prev = ent->lru.prev; - ent->lru.prev->next = ent->lru.next; - delta_base_cached -= ent->size; - } + free(ent->data); + detach_delta_base_cache_entry(ent); } void clear_delta_base_cache(void) { - unsigned long p; - for (p = 0; p < MAX_DELTA_CACHE; p++) - release_delta_base_cache(&delta_base_cache[p]); + struct hashmap_iter iter; + struct delta_base_cache_entry *entry; + for (entry = hashmap_iter_first(&delta_base_cache, &iter); + entry; + entry = hashmap_iter_next(&iter)) { + release_delta_base_cache(entry); + } } static void add_delta_base_cache(struct packed_git *p, off_t base_offset, void *base, unsigned long base_size, enum object_type type) { - unsigned long hash = pack_entry_hash(p, base_offset); - struct delta_base_cache_entry *ent = delta_base_cache + hash; - struct delta_base_cache_lru_list *lru; + struct delta_base_cache_entry *ent = xmalloc(sizeof(*ent)); + struct list_head *lru, *tmp; - release_delta_base_cache(ent); delta_base_cached += base_size; - for (lru = delta_base_cache_lru.next; - delta_base_cached > delta_base_cache_limit - && lru != &delta_base_cache_lru; - lru = lru->next) { - struct delta_base_cache_entry *f = (void *)lru; - if (f->type == OBJ_BLOB) - release_delta_base_cache(f); - } - for (lru = delta_base_cache_lru.next; - delta_base_cached > delta_base_cache_limit - && lru != &delta_base_cache_lru; - lru = lru->next) { - struct delta_base_cache_entry *f = (void *)lru; + list_for_each_safe(lru, tmp, &delta_base_cache_lru) { + struct delta_base_cache_entry *f = + list_entry(lru, struct delta_base_cache_entry, lru); + if (delta_base_cached <= delta_base_cache_limit) + break; release_delta_base_cache(f); } - ent->p = p; - ent->base_offset = base_offset; + ent->key.p = p; + ent->key.base_offset = base_offset; ent->type = type; ent->data = base; ent->size = base_size; - ent->lru.next = &delta_base_cache_lru; - ent->lru.prev = delta_base_cache_lru.prev; - delta_base_cache_lru.prev->next = &ent->lru; - delta_base_cache_lru.prev = &ent->lru; + list_add_tail(&ent->lru, &delta_base_cache_lru); + + if (!delta_base_cache.cmpfn) + hashmap_init(&delta_base_cache, delta_base_cache_hash_cmp, 0); + hashmap_entry_init(ent, pack_entry_hash(p, base_offset)); + hashmap_add(&delta_base_cache, ent); } static void *read_object(const unsigned char *sha1, enum object_type *type, @@ -2246,11 +2326,11 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, struct delta_base_cache_entry *ent; ent = get_delta_base_cache_entry(p, curpos); - if (eq_delta_base_cache_entry(ent, p, curpos)) { + if (ent) { type = ent->type; data = ent->data; size = ent->size; - clear_delta_base_cache_entry(ent); + detach_delta_base_cache_entry(ent); base_from_cache = 1; break; } @@ -2755,7 +2835,7 @@ static void *read_packed_sha1(const unsigned char *sha1, if (!find_pack_entry(sha1, &e)) return NULL; - data = cache_or_unpack_entry(e.p, e.offset, size, type, 1); + data = cache_or_unpack_entry(e.p, e.offset, size, type); if (!data) { /* * We're probably in deep shit, but let's try to fetch diff --git a/sha1_name.c b/sha1_name.c index ca7ddd6f2c..faf873cf7f 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -995,35 +995,35 @@ static int interpret_nth_prior_checkout(const char *name, int namelen, return retval; } -int get_sha1_mb(const char *name, unsigned char *sha1) +int get_oid_mb(const char *name, struct object_id *oid) { struct commit *one, *two; struct commit_list *mbs; - unsigned char sha1_tmp[20]; + struct object_id oid_tmp; const char *dots; int st; dots = strstr(name, "..."); if (!dots) - return get_sha1(name, sha1); + return get_oid(name, oid); if (dots == name) - st = get_sha1("HEAD", sha1_tmp); + st = get_oid("HEAD", &oid_tmp); else { struct strbuf sb; strbuf_init(&sb, dots - name); strbuf_add(&sb, name, dots - name); - st = get_sha1_committish(sb.buf, sha1_tmp); + st = get_sha1_committish(sb.buf, oid_tmp.hash); strbuf_release(&sb); } if (st) return st; - one = lookup_commit_reference_gently(sha1_tmp, 0); + one = lookup_commit_reference_gently(oid_tmp.hash, 0); if (!one) return -1; - if (get_sha1_committish(dots[3] ? (dots + 3) : "HEAD", sha1_tmp)) + if (get_sha1_committish(dots[3] ? (dots + 3) : "HEAD", oid_tmp.hash)) return -1; - two = lookup_commit_reference_gently(sha1_tmp, 0); + two = lookup_commit_reference_gently(oid_tmp.hash, 0); if (!two) return -1; mbs = get_merge_bases(one, two); @@ -1031,7 +1031,7 @@ int get_sha1_mb(const char *name, unsigned char *sha1) st = -1; else { st = 0; - hashcpy(sha1, mbs->item->object.oid.hash); + oidcpy(oid, &mbs->item->object.oid); } free_commit_list(mbs); return st; @@ -1435,7 +1435,7 @@ static int get_sha1_with_context_1(const char *name, memcmp(ce->name, cp, namelen)) break; if (ce_stage(ce) == stage) { - hashcpy(sha1, ce->sha1); + hashcpy(sha1, ce->oid.hash); oc->mode = ce->ce_mode; free(new_path); return 0; @@ -187,7 +187,7 @@ void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len) void strbuf_remove(struct strbuf *sb, size_t pos, size_t len) { - strbuf_splice(sb, pos, len, NULL, 0); + strbuf_splice(sb, pos, len, "", 0); } void strbuf_add(struct strbuf *sb, const void *data, size_t len) diff --git a/streaming.c b/streaming.c index 811fcc24d2..3c48f049d3 100644 --- a/streaming.c +++ b/streaming.c @@ -497,7 +497,7 @@ static open_method_decl(incore) * Users of streaming interface ****************************************************************/ -int stream_blob_to_fd(int fd, unsigned const char *sha1, struct stream_filter *filter, +int stream_blob_to_fd(int fd, const struct object_id *oid, struct stream_filter *filter, int can_seek) { struct git_istream *st; @@ -506,7 +506,7 @@ int stream_blob_to_fd(int fd, unsigned const char *sha1, struct stream_filter *f ssize_t kept = 0; int result = -1; - st = open_istream(sha1, &type, &sz, filter); + st = open_istream(oid->hash, &type, &sz, filter); if (!st) { if (filter) free_stream_filter(filter); diff --git a/streaming.h b/streaming.h index 1d05c2a465..73c1d156b3 100644 --- a/streaming.h +++ b/streaming.h @@ -12,6 +12,6 @@ extern struct git_istream *open_istream(const unsigned char *, enum object_type extern int close_istream(struct git_istream *); extern ssize_t read_istream(struct git_istream *, void *, size_t); -extern int stream_blob_to_fd(int fd, const unsigned char *, struct stream_filter *, int can_seek); +extern int stream_blob_to_fd(int fd, const struct object_id *, struct stream_filter *, int can_seek); #endif /* STREAMING_H */ diff --git a/submodule.c b/submodule.c index 1b5cdfb7e7..0ef2ff4321 100644 --- a/submodule.c +++ b/submodule.c @@ -127,7 +127,9 @@ static int add_submodule_odb(const char *path) int ret = 0; size_t alloc; - strbuf_git_path_submodule(&objects_directory, path, "objects/"); + ret = strbuf_git_path_submodule(&objects_directory, path, "objects/"); + if (ret) + goto done; if (!is_directory(objects_directory.buf)) { ret = -1; goto done; @@ -278,9 +280,9 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, static int prepare_submodule_summary(struct rev_info *rev, const char *path, struct commit *left, struct commit *right, - int *fast_forward, int *fast_backward) + struct commit_list *merge_bases) { - struct commit_list *merge_bases, *list; + struct commit_list *list; init_revisions(rev, NULL); setup_revisions(0, NULL, rev, NULL); @@ -289,13 +291,6 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path, left->object.flags |= SYMMETRIC_LEFT; add_pending_object(rev, &left->object, path); add_pending_object(rev, &right->object, path); - merge_bases = get_merge_bases(left, right); - if (merge_bases) { - if (merge_bases->item == left) - *fast_forward = 1; - else if (merge_bases->item == right) - *fast_backward = 1; - } for (list = merge_bases; list; list = list->next) { list->item->object.flags |= UNINTERESTING; add_pending_object(rev, &list->item->object, @@ -333,31 +328,23 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, strbuf_release(&sb); } -void show_submodule_summary(FILE *f, const char *path, +/* Helper function to display the submodule header line prior to the full + * summary output. If it can locate the submodule objects directory it will + * attempt to lookup both the left and right commits and put them into the + * left and right pointers. + */ +static void show_submodule_header(FILE *f, const char *path, const char *line_prefix, - unsigned char one[20], unsigned char two[20], + struct object_id *one, struct object_id *two, unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset) + const char *reset, + struct commit **left, struct commit **right, + struct commit_list **merge_bases) { - struct rev_info rev; - struct commit *left = NULL, *right = NULL; const char *message = NULL; struct strbuf sb = STRBUF_INIT; int fast_forward = 0, fast_backward = 0; - if (is_null_sha1(two)) - message = "(submodule deleted)"; - else if (add_submodule_odb(path)) - message = "(not checked out)"; - else if (is_null_sha1(one)) - message = "(new submodule)"; - else if (!(left = lookup_commit_reference(one)) || - !(right = lookup_commit_reference(two))) - message = "(commits not present)"; - else if (prepare_submodule_summary(&rev, path, left, right, - &fast_forward, &fast_backward)) - message = "(revision walker failed)"; - if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) fprintf(f, "%sSubmodule %s contains untracked content\n", line_prefix, path); @@ -365,30 +352,163 @@ void show_submodule_summary(FILE *f, const char *path, fprintf(f, "%sSubmodule %s contains modified content\n", line_prefix, path); - if (!hashcmp(one, two)) { + if (is_null_oid(one)) + message = "(new submodule)"; + else if (is_null_oid(two)) + message = "(submodule deleted)"; + + if (add_submodule_odb(path)) { + if (!message) + message = "(not initialized)"; + goto output_header; + } + + /* + * Attempt to lookup the commit references, and determine if this is + * a fast forward or fast backwards update. + */ + *left = lookup_commit_reference(one->hash); + *right = lookup_commit_reference(two->hash); + + /* + * Warn about missing commits in the submodule project, but only if + * they aren't null. + */ + if ((!is_null_oid(one) && !*left) || + (!is_null_oid(two) && !*right)) + message = "(commits not present)"; + + *merge_bases = get_merge_bases(*left, *right); + if (*merge_bases) { + if ((*merge_bases)->item == *left) + fast_forward = 1; + else if ((*merge_bases)->item == *right) + fast_backward = 1; + } + + if (!oidcmp(one, two)) { strbuf_release(&sb); return; } +output_header: strbuf_addf(&sb, "%s%sSubmodule %s %s..", line_prefix, meta, path, - find_unique_abbrev(one, DEFAULT_ABBREV)); + find_unique_abbrev(one->hash, DEFAULT_ABBREV)); if (!fast_backward && !fast_forward) strbuf_addch(&sb, '.'); - strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV)); + strbuf_addf(&sb, "%s", find_unique_abbrev(two->hash, DEFAULT_ABBREV)); if (message) strbuf_addf(&sb, " %s%s\n", message, reset); else strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset); fwrite(sb.buf, sb.len, 1, f); - if (!message) /* only NULL if we succeeded in setting up the walk */ - print_submodule_summary(&rev, f, line_prefix, del, add, reset); + strbuf_release(&sb); +} + +void show_submodule_summary(FILE *f, const char *path, + const char *line_prefix, + struct object_id *one, struct object_id *two, + unsigned dirty_submodule, const char *meta, + const char *del, const char *add, const char *reset) +{ + struct rev_info rev; + struct commit *left = NULL, *right = NULL; + struct commit_list *merge_bases = NULL; + + show_submodule_header(f, path, line_prefix, one, two, dirty_submodule, + meta, reset, &left, &right, &merge_bases); + + /* + * If we don't have both a left and a right pointer, there is no + * reason to try and display a summary. The header line should contain + * all the information the user needs. + */ + if (!left || !right) + goto out; + + /* Treat revision walker failure the same as missing commits */ + if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) { + fprintf(f, "%s(revision walker failed)\n", line_prefix); + goto out; + } + + print_submodule_summary(&rev, f, line_prefix, del, add, reset); + +out: + if (merge_bases) + free_commit_list(merge_bases); + clear_commit_marks(left, ~0); + clear_commit_marks(right, ~0); +} + +void show_submodule_inline_diff(FILE *f, const char *path, + const char *line_prefix, + struct object_id *one, struct object_id *two, + unsigned dirty_submodule, const char *meta, + const char *del, const char *add, const char *reset, + const struct diff_options *o) +{ + const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid; + struct commit *left = NULL, *right = NULL; + struct commit_list *merge_bases = NULL; + struct strbuf submodule_dir = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + + show_submodule_header(f, path, line_prefix, one, two, dirty_submodule, + meta, reset, &left, &right, &merge_bases); + + /* We need a valid left and right commit to display a difference */ + if (!(left || is_null_oid(one)) || + !(right || is_null_oid(two))) + goto done; + + if (left) + old = one; + if (right) + new = two; + + fflush(f); + cp.git_cmd = 1; + cp.dir = path; + cp.out = dup(fileno(f)); + cp.no_stdin = 1; + + /* TODO: other options may need to be passed here. */ + argv_array_push(&cp.args, "diff"); + 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/", + o->b_prefix, path); + argv_array_pushf(&cp.args, "--dst-prefix=%s%s/", + o->a_prefix, path); + } else { + argv_array_pushf(&cp.args, "--src-prefix=%s%s/", + o->a_prefix, path); + argv_array_pushf(&cp.args, "--dst-prefix=%s%s/", + o->b_prefix, path); + } + argv_array_push(&cp.args, oid_to_hex(old)); + /* + * If the submodule has modified content, we will diff against the + * work tree, under the assumption that the user has asked for the + * diff format and wishes to actually see all differences even if they + * haven't yet been committed to the submodule yet. + */ + if (!(dirty_submodule & DIRTY_SUBMODULE_MODIFIED)) + argv_array_push(&cp.args, oid_to_hex(new)); + + if (run_command(&cp)) + fprintf(f, "(diff failed)\n"); + +done: + strbuf_release(&submodule_dir); + if (merge_bases) + free_commit_list(merge_bases); if (left) clear_commit_marks(left, ~0); if (right) clear_commit_marks(right, ~0); - - strbuf_release(&sb); } void set_config_fetch_recurse_submodules(int value) @@ -1160,4 +1280,5 @@ void prepare_submodule_repo_env(struct argv_array *out) if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) argv_array_push(out, *var); } + argv_array_push(out, "GIT_DIR=.git"); } diff --git a/submodule.h b/submodule.h index 2af9390998..d9e197a948 100644 --- a/submodule.h +++ b/submodule.h @@ -43,9 +43,15 @@ const char *submodule_strategy_to_string(const struct submodule_update_strategy void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, - unsigned char one[20], unsigned char two[20], + struct object_id *one, struct object_id *two, unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset); +void show_submodule_inline_diff(FILE *f, const char *path, + const char *line_prefix, + struct object_id *one, struct object_id *two, + unsigned dirty_submodule, const char *meta, + const char *del, const char *add, const char *reset, + const struct diff_options *opt); void set_config_fetch_recurse_submodules(int value); void check_for_new_submodule_commits(unsigned char new_sha1[20]); int fetch_populated_submodules(const struct argv_array *options, diff --git a/t/helper/test-config.c b/t/helper/test-config.c index 3c6d08cd09..83a4f2ab86 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c @@ -72,6 +72,9 @@ int cmd_main(int argc, const char **argv) const char *v; const struct string_list *strptr; struct config_set cs; + + setup_git_directory(); + git_configset_init(&cs); if (argc < 2) { diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c index d1689248b4..e44430b699 100644 --- a/t/helper/test-dump-split-index.c +++ b/t/helper/test-dump-split-index.c @@ -23,7 +23,7 @@ int cmd_main(int ac, const char **av) for (i = 0; i < the_index.cache_nr; i++) { struct cache_entry *ce = the_index.cache[i]; printf("%06o %s %d\t%s\n", ce->ce_mode, - sha1_to_hex(ce->sha1), ce_stage(ce), ce->name); + oid_to_hex(&ce->oid), ce_stage(ce), ce->name); } printf("replacements:"); if (si->replace_bitmap) diff --git a/t/perf/p0003-delta-base-cache.sh b/t/perf/p0003-delta-base-cache.sh new file mode 100755 index 0000000000..62369eaaf0 --- /dev/null +++ b/t/perf/p0003-delta-base-cache.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='Test operations that emphasize the delta base cache. + +We look at both "log --raw", which should put only trees into the delta cache, +and "log -Sfoo --raw", which should look at both trees and blobs. + +Any effects will be emphasized if the test repository is fully packed (loose +objects obviously do not use the delta base cache at all). It is also +emphasized if the pack has long delta chains (e.g., as produced by "gc +--aggressive"), though cache is still quite noticeable even with the default +depth of 50. + +The setting of core.deltaBaseCacheLimit in the source repository is also +relevant (depending on the size of your test repo), so be sure it is consistent +between runs. +' +. ./perf-lib.sh + +test_perf_large_repo + +# puts mostly trees into the delta base cache +test_perf 'log --raw' ' + git log --raw >/dev/null +' + +test_perf 'log -S' ' + git log --raw -Sfoo >/dev/null +' + +test_done diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index de2a224a36..bb91dbb173 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -32,6 +32,14 @@ test_perf 'simulated fetch' ' } | git pack-objects --revs --stdout >/dev/null ' +test_perf 'pack to file' ' + git pack-objects --all pack1 </dev/null >/dev/null +' + +test_perf 'pack to file (bitmap)' ' + git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null +' + test_expect_success 'create partial bitmap state' ' # pick a commit to represent the repo tip in the past cutoff=$(git rev-list HEAD~100 -1) && @@ -53,8 +61,12 @@ test_expect_success 'create partial bitmap state' ' git update-ref HEAD $orig_tip ' -test_perf 'partial bitmap' ' +test_perf 'clone (partial bitmap)' ' git pack-objects --stdout --all </dev/null >/dev/null ' +test_perf 'pack to file (partial bitmap)' ' + git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null +' + test_done diff --git a/t/perf/run b/t/perf/run index cfd70129bb..e8adedadfd 100755 --- a/t/perf/run +++ b/t/perf/run @@ -30,7 +30,13 @@ unpack_git_rev () { } build_git_rev () { rev=$1 - cp ../../config.mak build/$rev/config.mak + for config in config.mak config.mak.autogen config.status + do + if test -e "../../$config" + then + cp "../../$config" "build/$rev/" + fi + done (cd build/$rev && make $GIT_PERF_MAKE_OPTS) || die "failed to build revision '$mydir'" } diff --git a/t/t0001-init.sh b/t/t0001-init.sh index a6fdd5ef3a..8ffbbea4d6 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -384,4 +384,13 @@ test_expect_success MINGW 'bare git dir not hidden' ' ! is_hidden newdir ' +test_expect_success 'remote init from does not use config from cwd' ' + rm -rf newdir && + test_config core.logallrefupdates true && + git init newdir && + echo true >expect && + git -C newdir config --bool core.logallrefupdates >actual && + test_cmp expect actual +' + test_done diff --git a/t/t0012-help.sh b/t/t0012-help.sh new file mode 100755 index 0000000000..8faba2e8bc --- /dev/null +++ b/t/t0012-help.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +test_description='help' + +. ./test-lib.sh + +configure_help () { + test_config help.format html && + + # Unless the path has "://" in it, Git tries to make sure + # the documentation directory locally exists. Avoid it as + # we are only interested in seeing an attempt to correctly + # invoke a help browser in this test. + test_config help.htmlpath test://html && + + # Name a custom browser + test_config browser.test.cmd ./test-browser && + test_config help.browser test +} + +test_expect_success "setup" ' + # Just write out which page gets requested + write_script test-browser <<-\EOF + echo "$*" >test-browser.log + EOF +' + +test_expect_success "works for commands and guides by default" ' + configure_help && + git help status && + echo "test://html/git-status.html" >expect && + test_cmp expect test-browser.log && + git help revisions && + echo "test://html/gitrevisions.html" >expect && + test_cmp expect test-browser.log +' + +test_expect_success "--exclude-guides does not work for guides" ' + >test-browser.log && + test_must_fail git help --exclude-guides revisions && + test_must_be_empty test-browser.log +' + +test_expect_success "--help does not work for guides" " + cat <<-EOF >expect && + git: 'revisions' is not a git command. See 'git --help'. + EOF + test_must_fail git revisions --help 2>actual && + test_i18ncmp expect actual +" + +test_done diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 7d2baa15bb..acca9ac562 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -101,7 +101,7 @@ test_expect_success 'git hash-object --stdin file1 <file0 first operates on file test "$obname1" = "$obname1new" ' -test_expect_success 'check that appropriate filter is invoke when --path is used' ' +test_expect_success 'set up crlf tests' ' echo fooQ | tr Q "\\015" >file0 && cp file0 file1 && echo "file0 -crlf" >.gitattributes && @@ -109,7 +109,10 @@ test_expect_success 'check that appropriate filter is invoke when --path is used git config core.autocrlf true && file0_sha=$(git hash-object file0) && file1_sha=$(git hash-object file1) && - test "$file0_sha" != "$file1_sha" && + test "$file0_sha" != "$file1_sha" +' + +test_expect_success 'check that appropriate filter is invoke when --path is used' ' path1_sha=$(git hash-object --path=file1 file0) && path0_sha=$(git hash-object --path=file0 file1) && test "$file0_sha" = "$path0_sha" && @@ -117,38 +120,30 @@ test_expect_success 'check that appropriate filter is invoke when --path is used path1_sha=$(cat file0 | git hash-object --path=file1 --stdin) && path0_sha=$(cat file1 | git hash-object --path=file0 --stdin) && test "$file0_sha" = "$path0_sha" && - test "$file1_sha" = "$path1_sha" && - git config --unset core.autocrlf + test "$file1_sha" = "$path1_sha" +' + +test_expect_success 'gitattributes also work in a subdirectory' ' + mkdir subdir && + ( + cd subdir && + subdir_sha0=$(git hash-object ../file0) && + subdir_sha1=$(git hash-object ../file1) && + test "$file0_sha" = "$subdir_sha0" && + test "$file1_sha" = "$subdir_sha1" + ) ' test_expect_success 'check that --no-filters option works' ' - echo fooQ | tr Q "\\015" >file0 && - cp file0 file1 && - echo "file0 -crlf" >.gitattributes && - echo "file1 crlf" >>.gitattributes && - git config core.autocrlf true && - file0_sha=$(git hash-object file0) && - file1_sha=$(git hash-object file1) && - test "$file0_sha" != "$file1_sha" && nofilters_file1=$(git hash-object --no-filters file1) && test "$file0_sha" = "$nofilters_file1" && nofilters_file1=$(cat file1 | git hash-object --stdin) && - test "$file0_sha" = "$nofilters_file1" && - git config --unset core.autocrlf + test "$file0_sha" = "$nofilters_file1" ' test_expect_success 'check that --no-filters option works with --stdin-paths' ' - echo fooQ | tr Q "\\015" >file0 && - cp file0 file1 && - echo "file0 -crlf" >.gitattributes && - echo "file1 crlf" >>.gitattributes && - git config core.autocrlf true && - file0_sha=$(git hash-object file0) && - file1_sha=$(git hash-object file1) && - test "$file0_sha" != "$file1_sha" && nofilters_file1=$(echo "file1" | git hash-object --stdin-paths --no-filters) && - test "$file0_sha" = "$nofilters_file1" && - git config --unset core.autocrlf + test "$file0_sha" = "$nofilters_file1" ' pop_repo diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index ac10875408..1312004f8c 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -172,4 +172,45 @@ test_expect_success POSIXPERM 'forced modes' ' }" actual)" ' +test_expect_success POSIXPERM 'remote init does not use config from cwd' ' + git config core.sharedrepository 0666 && + umask 0022 && + git init --bare child.git && + echo "-rw-r--r--" >expect && + modebits child.git/config >actual && + test_cmp expect actual +' + +test_expect_success POSIXPERM 're-init respects core.sharedrepository (local)' ' + git config core.sharedrepository 0666 && + umask 0022 && + echo whatever >templates/foo && + git init --template=templates && + echo "-rw-rw-rw-" >expect && + modebits .git/foo >actual && + test_cmp expect actual +' + +test_expect_success POSIXPERM 're-init respects core.sharedrepository (remote)' ' + rm -rf child.git && + umask 0022 && + git init --bare --shared=0666 child.git && + test_path_is_missing child.git/foo && + git init --bare --template=../templates child.git && + echo "-rw-rw-rw-" >expect && + modebits child.git/foo >actual && + test_cmp expect actual +' + +test_expect_success POSIXPERM 'template can set core.sharedrepository' ' + rm -rf child.git && + umask 0022 && + git config core.sharedrepository 0666 && + cp .git/config templates/config && + git init --bare --template=../templates child.git && + echo "-rw-rw-rw-" >expect && + modebits child.git/HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 9bcd34969f..ce4cff13bb 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -25,46 +25,26 @@ test_expect_success 'setup' ' test_expect_success 'gitdir selection on normal repos' ' echo 0 >expect && git config core.repositoryformatversion >actual && - ( - cd test && - git config core.repositoryformatversion >../actual2 - ) && + git -C test config core.repositoryformatversion >actual2 && test_cmp expect actual && test_cmp expect actual2 ' test_expect_success 'gitdir selection on unsupported repo' ' # Make sure it would stop at test2, not trash - echo 99 >expect && - ( - cd test2 && - git config core.repositoryformatversion >../actual - ) && - test_cmp expect actual + test_expect_code 1 git -C test2 config core.repositoryformatversion >actual ' test_expect_success 'gitdir not required mode' ' git apply --stat test.patch && - ( - cd test && - git apply --stat ../test.patch - ) && - ( - cd test2 && - git apply --stat ../test.patch - ) + git -C test apply --stat ../test.patch && + git -C test2 apply --stat ../test.patch ' test_expect_success 'gitdir required mode' ' git apply --check --index test.patch && - ( - cd test && - git apply --check --index ../test.patch - ) && - ( - cd test2 && - test_must_fail git apply --check --index ../test.patch - ) + git -C test apply --check --index ../test.patch && + test_must_fail git -C test2 apply --check --index ../test.patch ' check_allow () { diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index ca3fa406c3..eec3e90f9c 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -33,18 +33,25 @@ test_expect_success 'symbolic-ref refuses bare sha1' ' ' reset_to_sane -test_expect_success 'symbolic-ref deletes HEAD' ' - git symbolic-ref -d HEAD && +test_expect_success 'HEAD cannot be removed' ' + test_must_fail git symbolic-ref -d HEAD +' + +reset_to_sane + +test_expect_success 'symbolic-ref can be deleted' ' + git symbolic-ref NOTHEAD refs/heads/foo && + git symbolic-ref -d NOTHEAD && test_path_is_file .git/refs/heads/foo && - test_path_is_missing .git/HEAD + test_path_is_missing .git/NOTHEAD ' reset_to_sane -test_expect_success 'symbolic-ref deletes dangling HEAD' ' - git symbolic-ref HEAD refs/heads/missing && - git symbolic-ref -d HEAD && +test_expect_success 'symbolic-ref can delete dangling symref' ' + git symbolic-ref NOTHEAD refs/heads/missing && + git symbolic-ref -d NOTHEAD && test_path_is_missing .git/refs/heads/missing && - test_path_is_missing .git/HEAD + test_path_is_missing .git/NOTHEAD ' reset_to_sane diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index 643d729157..0a8af76aab 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -68,7 +68,7 @@ test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' ' sed -e "s/-CIT/xCIT/" <output >broken && test_must_fail git apply --stat --summary broken 2>detected && detected=$(cat detected) && - detected=$(expr "$detected" : "fatal.*at line \\([0-9]*\\)\$") && + detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") && detected=$(sed -ne "${detected}p" broken) && test "$detected" = xCIT ' @@ -77,7 +77,7 @@ test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' ' git diff --binary | sed -e "s/-CIT/xCIT/" >broken && test_must_fail git apply --stat --summary broken 2>detected && detected=$(cat detected) && - detected=$(expr "$detected" : "fatal.*at line \\([0-9]*\\)\$") && + detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") && detected=$(sed -ne "${detected}p" broken) && test "$detected" = xCIT ' diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 94ef5000e7..566817e2ef 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -306,6 +306,8 @@ diff --no-index --name-status dir2 dir diff --no-index --name-status -- dir2 dir diff --no-index dir dir3 diff master master^ side +# Can't use spaces... +diff --line-prefix=abc master master^ side diff --dirstat master~1 master~2 diff --dirstat initial rearrange diff --dirstat-by-file initial rearrange @@ -325,6 +327,10 @@ test_expect_success 'diff --cached -- file on unborn branch' ' git diff --cached -- file0 >result && test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" result ' +test_expect_success 'diff --line-prefix with spaces' ' + git diff --line-prefix="| | | " --cached -- file0 >result && + test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--line-prefix_--cached_--_file0" result +' test_expect_success 'diff-tree --stdin with log formatting' ' cat >expect <<-\EOF && diff --git a/t/t4013/diff.diff_--line-prefix=abc_master_master^_side b/t/t4013/diff.diff_--line-prefix=abc_master_master^_side new file mode 100644 index 0000000000..99f91e7f0e --- /dev/null +++ b/t/t4013/diff.diff_--line-prefix=abc_master_master^_side @@ -0,0 +1,29 @@ +$ git diff --line-prefix=abc master master^ side +abcdiff --cc dir/sub +abcindex cead32e,7289e35..992913c +abc--- a/dir/sub +abc+++ b/dir/sub +abc@@@ -1,6 -1,4 +1,8 @@@ +abc A +abc B +abc +C +abc +D +abc +E +abc +F +abc+ 1 +abc+ 2 +abcdiff --cc file0 +abcindex b414108,f4615da..10a8a9f +abc--- a/file0 +abc+++ b/file0 +abc@@@ -1,6 -1,6 +1,9 @@@ +abc 1 +abc 2 +abc 3 +abc +4 +abc +5 +abc +6 +abc+ A +abc+ B +abc+ C +$ diff --git a/t/t4013/diff.diff_--line-prefix_--cached_--_file0 b/t/t4013/diff.diff_--line-prefix_--cached_--_file0 new file mode 100644 index 0000000000..f41ba4d36a --- /dev/null +++ b/t/t4013/diff.diff_--line-prefix_--cached_--_file0 @@ -0,0 +1,15 @@ +| | | diff --git a/file0 b/file0 +| | | new file mode 100644 +| | | index 0000000..10a8a9f +| | | --- /dev/null +| | | +++ b/file0 +| | | @@ -0,0 +1,9 @@ +| | | +1 +| | | +2 +| | | +3 +| | | +4 +| | | +5 +| | | +6 +| | | +A +| | | +B +| | | +C diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index b0579dd452..ba4902df2b 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -754,9 +754,22 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' ' git format-patch --ignore-if-in-upstream HEAD ' +git_version="$(git --version | sed "s/.* //")" + +signature() { + printf "%s\n%s\n\n" "-- " "${1:-$git_version}" +} + +test_expect_success 'format-patch default signature' ' + git format-patch --stdout -1 | tail -n 3 >output && + signature >expect && + test_cmp expect output +' + test_expect_success 'format-patch --signature' ' - git format-patch --stdout --signature="my sig" -1 >output && - grep "my sig" output + git format-patch --stdout --signature="my sig" -1 | tail -n 3 >output && + signature "my sig" >expect && + test_cmp expect output ' test_expect_success 'format-patch with format.signature config' ' @@ -1073,6 +1086,15 @@ test_expect_success 'empty subject prefix does not have extra space' ' test_cmp expect actual ' +test_expect_success '--rfc' ' + cat >expect <<-\EOF && + Subject: [RFC PATCH 1/1] header with . in it + EOF + git format-patch -n -1 --stdout --rfc >patch && + grep ^Subject: patch >actual && + test_cmp expect actual +' + test_expect_success '--from=ident notices bogus ident' ' test_must_fail git format-patch -1 --stdout --from=foo >patch ' @@ -1502,12 +1524,12 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' ' test_expect_success 'format-patch --base' ' git checkout side && - git format-patch --stdout --base=HEAD~3 -1 >patch && - grep "^base-commit:" patch >actual && - grep "^prerequisite-patch-id:" patch >>actual && - echo "base-commit: $(git rev-parse HEAD~3)" >expected && + git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual && + echo >expected && + echo "base-commit: $(git rev-parse HEAD~3)" >>expected && echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected && echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected && + signature >> expected && test_cmp expected actual ' @@ -1605,6 +1627,14 @@ test_expect_success 'format-patch --base overrides format.useAutoBase' ' test_cmp expected actual ' +test_expect_success 'format-patch --base with --attach' ' + git format-patch --attach=mimemime --stdout --base=HEAD~ -1 >patch && + sed -n -e "/^base-commit:/s/.*/1/p" -e "/^---*mimemime--$/s/.*/2/p" \ + patch >actual && + test_write_lines 1 2 >expect && + test_cmp expect actual +' + test_expect_success 'format-patch --pretty=mboxrd' ' sp=" " && cat >msg <<-INPUT_END && diff --git a/t/t4021-format-patch-numbered.sh b/t/t4021-format-patch-numbered.sh index 886494b58f..9be65fd444 100755 --- a/t/t4021-format-patch-numbered.sh +++ b/t/t4021-format-patch-numbered.sh @@ -36,6 +36,11 @@ test_no_numbered() { test_num_no_numbered $1 2 } +test_single_cover_letter_numbered() { + grep "^Subject: \[PATCH 0/1\]" $1 && + grep "^Subject: \[PATCH 1/1\]" $1 +} + test_single_numbered() { grep "^Subject: \[PATCH 1/1\]" $1 } @@ -121,4 +126,16 @@ test_expect_success '--start-number && --numbered' ' grep "^Subject: \[PATCH 3/3\]" patch8 ' +test_expect_success 'single patch with cover-letter defaults to numbers' ' + git format-patch --cover-letter --stdout HEAD~1 >patch9.single && + test_single_cover_letter_numbered patch9.single +' + +test_expect_success 'Use --no-numbered and --cover-letter single patch' ' + git format-patch --no-numbered --stdout --cover-letter HEAD~1 >patch10 && + test_no_numbered patch10 +' + + + test_done diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh index b79b87790b..6154acb456 100755 --- a/t/t4051-diff-function-context.sh +++ b/t/t4051-diff-function-context.sh @@ -67,6 +67,15 @@ test_expect_success 'setup' ' commit_and_tag long_common_tail file.c && git checkout initial && + cat "$dir/hello.c" "$dir/dummy.c" >file.c && + commit_and_tag hello_dummy file.c && + + # 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 && + commit_and_tag changed_hello_dummy file.c && + + git checkout initial && grep -v "delete me from hello" <file.c >file.c.new && mv file.c.new file.c && cat "$dir/appended1.c" >>file.c && @@ -179,4 +188,20 @@ test_expect_success ' context does not include other functions' ' test $(grep -c "^[ +-].*Begin" changed_hello_appended.diff) -le 2 ' +check_diff changed_hello_dummy 'changed two consecutive functions' + +test_expect_success ' context includes begin' ' + grep "^ .*Begin of hello" changed_hello_dummy.diff && + grep "^ .*Begin of dummy" changed_hello_dummy.diff +' + +test_expect_success ' context includes end' ' + grep "^ .*End of hello" changed_hello_dummy.diff && + grep "^ .*End of dummy" changed_hello_dummy.diff +' + +test_expect_success ' overlapping hunks are merged' ' + test $(grep -c "^@@" changed_hello_dummy.diff) -eq 1 +' + test_done diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh index 6eb83211b5..453e6c35eb 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh @@ -89,4 +89,42 @@ test_expect_success 'turning a file into a directory' ' ) ' +test_expect_success 'diff from repo subdir shows real paths (explicit)' ' + echo "diff --git a/../../non/git/a b/../../non/git/b" >expect && + test_expect_code 1 \ + git -C repo/sub \ + diff --no-index ../../non/git/a ../../non/git/b >actual && + head -n 1 <actual >actual.head && + test_cmp expect actual.head +' + +test_expect_success 'diff from repo subdir shows real paths (implicit)' ' + echo "diff --git a/../../non/git/a b/../../non/git/b" >expect && + test_expect_code 1 \ + git -C repo/sub \ + diff ../../non/git/a ../../non/git/b >actual && + head -n 1 <actual >actual.head && + test_cmp expect actual.head +' + +test_expect_success 'diff --no-index from repo subdir respects config (explicit)' ' + echo "diff --git ../../non/git/a ../../non/git/b" >expect && + test_config -C repo diff.noprefix true && + test_expect_code 1 \ + git -C repo/sub \ + diff --no-index ../../non/git/a ../../non/git/b >actual && + head -n 1 <actual >actual.head && + test_cmp expect actual.head +' + +test_expect_success 'diff --no-index from repo subdir respects config (implicit)' ' + echo "diff --git ../../non/git/a ../../non/git/b" >expect && + test_config -C repo diff.noprefix true && + test_expect_code 1 \ + git -C repo/sub \ + diff ../../non/git/a ../../non/git/b >actual && + head -n 1 <actual >actual.head && + test_cmp expect actual.head +' + test_done diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh new file mode 100755 index 0000000000..cd70fd5192 --- /dev/null +++ b/t/t4059-diff-submodule-not-initialized.sh @@ -0,0 +1,127 @@ +#!/bin/sh +# +# Copyright (c) 2016 Jacob Keller, based on t4041 by Jens Lehmann +# + +test_description='Test for submodule diff on non-checked out submodule + +This test tries to verify that add_submodule_odb works when the submodule was +initialized previously but the checkout has since been removed. +' + +. ./test-lib.sh + +# Tested non-UTF-8 encoding +test_encoding="ISO8859-1" + +# String "added" in German (translated with Google Translate), encoded in UTF-8, +# used in sample commit log messages in add_file() function below. +added=$(printf "hinzugef\303\274gt") + +add_file () { + ( + cd "$1" && + shift && + for name + do + echo "$name" >"$name" && + git add "$name" && + test_tick && + # "git commit -m" would break MinGW, as Windows refuse to pass + # $test_encoding encoded parameter to git. + echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | + git -c "i18n.commitEncoding=$test_encoding" commit -F - + done >/dev/null && + git rev-parse --short --verify HEAD + ) +} + +commit_file () { + test_tick && + git commit "$@" -m "Commit $*" >/dev/null +} + +test_expect_success 'setup - submodules' ' + test_create_repo sm2 && + add_file . foo && + add_file sm2 foo1 foo2 && + smhead1=$(git -C sm2 rev-parse --short --verify HEAD) +' + +test_expect_success 'setup - git submodule add' ' + git submodule add ./sm2 sm1 && + commit_file sm1 .gitmodules && + git diff-tree -p --no-commit-id --submodule=log HEAD -- sm1 >actual && + cat >expected <<-EOF && + Submodule sm1 0000000...$smhead1 (new submodule) + EOF + test_cmp expected actual +' + +test_expect_success 'submodule directory removed' ' + rm -rf sm1 && + git diff-tree -p --no-commit-id --submodule=log HEAD -- sm1 >actual && + cat >expected <<-EOF && + Submodule sm1 0000000...$smhead1 (new submodule) + EOF + test_cmp expected actual +' + +test_expect_success 'setup - submodule multiple commits' ' + git submodule update --checkout sm1 && + smhead2=$(add_file sm1 foo3 foo4) && + commit_file sm1 && + git diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $smhead1..$smhead2: + > Add foo4 ($added foo4) + > Add foo3 ($added foo3) + EOF + test_cmp expected actual +' + +test_expect_success 'submodule removed multiple commits' ' + rm -rf sm1 && + git diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $smhead1..$smhead2: + > Add foo4 ($added foo4) + > Add foo3 ($added foo3) + EOF + test_cmp expected actual +' + +test_expect_success 'submodule not initialized in new clone' ' + git clone . sm3 && + git -C sm3 diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $smhead1...$smhead2 (not initialized) + EOF + test_cmp expected actual +' + +test_expect_success 'setup submodule moved' ' + git submodule update --checkout sm1 && + git mv sm1 sm4 && + commit_file sm4 && + git diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm4 0000000...$smhead2 (new submodule) + EOF + test_cmp expected actual +' + +test_expect_success 'submodule moved then removed' ' + smhead3=$(add_file sm4 foo6 foo7) && + commit_file sm4 && + rm -rf sm4 && + git diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm4 $smhead2..$smhead3: + > Add foo7 ($added foo7) + > Add foo6 ($added foo6) + EOF + test_cmp expected actual +' + +test_done diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh new file mode 100755 index 0000000000..7e23b55ea4 --- /dev/null +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -0,0 +1,749 @@ +#!/bin/sh +# +# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin +# Copyright (c) 2011 Alexey Shumkin (+ non-UTF-8 commit encoding tests) +# Copyright (c) 2016 Jacob Keller (copy + convert to --submodule=diff) +# + +test_description='Support for diff format verbose submodule difference in git diff + +This test tries to verify the sanity of --submodule=diff option of git diff. +' + +. ./test-lib.sh + +# Tested non-UTF-8 encoding +test_encoding="ISO8859-1" + +# String "added" in German (translated with Google Translate), encoded in UTF-8, +# used in sample commit log messages in add_file() function below. +added=$(printf "hinzugef\303\274gt") + +add_file () { + ( + cd "$1" && + shift && + for name + do + echo "$name" >"$name" && + git add "$name" && + test_tick && + # "git commit -m" would break MinGW, as Windows refuse to pass + # $test_encoding encoded parameter to git. + echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | + git -c "i18n.commitEncoding=$test_encoding" commit -F - + done >/dev/null && + git rev-parse --short --verify HEAD + ) +} + +commit_file () { + test_tick && + git commit "$@" -m "Commit $*" >/dev/null +} + +test_expect_success 'setup repository' ' + test_create_repo sm1 && + add_file . foo && + head1=$(add_file sm1 foo1 foo2) && + fullhead1=$(git -C sm1 rev-parse --verify HEAD) +' + +test_expect_success 'added submodule' ' + git add sm1 && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 0000000...$head1 (new submodule) + diff --git a/sm1/foo1 b/sm1/foo1 + new file mode 100644 + index 0000000..1715acd + --- /dev/null + +++ b/sm1/foo1 + @@ -0,0 +1 @@ + +foo1 + diff --git a/sm1/foo2 b/sm1/foo2 + new file mode 100644 + index 0000000..54b060e + --- /dev/null + +++ b/sm1/foo2 + @@ -0,0 +1 @@ + +foo2 + EOF + test_cmp expected actual +' + +test_expect_success 'added submodule, set diff.submodule' ' + test_config diff.submodule log && + git add sm1 && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 0000000...$head1 (new submodule) + diff --git a/sm1/foo1 b/sm1/foo1 + new file mode 100644 + index 0000000..1715acd + --- /dev/null + +++ b/sm1/foo1 + @@ -0,0 +1 @@ + +foo1 + diff --git a/sm1/foo2 b/sm1/foo2 + new file mode 100644 + index 0000000..54b060e + --- /dev/null + +++ b/sm1/foo2 + @@ -0,0 +1 @@ + +foo2 + EOF + test_cmp expected actual +' + +test_expect_success '--submodule=short overrides diff.submodule' ' + test_config diff.submodule log && + git add sm1 && + git diff --submodule=short --cached >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + new file mode 160000 + index 0000000..$head1 + --- /dev/null + +++ b/sm1 + @@ -0,0 +1 @@ + +Subproject commit $fullhead1 + EOF + test_cmp expected actual +' + +test_expect_success 'diff.submodule does not affect plumbing' ' + test_config diff.submodule log && + git diff-index -p HEAD >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + new file mode 160000 + index 0000000..$head1 + --- /dev/null + +++ b/sm1 + @@ -0,0 +1 @@ + +Subproject commit $fullhead1 + EOF + test_cmp expected actual +' + +commit_file sm1 && +head2=$(add_file sm1 foo3) + +test_expect_success 'modified submodule(forward)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head1..$head2: + diff --git a/sm1/foo3 b/sm1/foo3 + new file mode 100644 + index 0000000..c1ec6c6 + --- /dev/null + +++ b/sm1/foo3 + @@ -0,0 +1 @@ + +foo3 + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule(forward)' ' + git diff --submodule=diff >actual && + cat >expected <<-EOF && + Submodule sm1 $head1..$head2: + diff --git a/sm1/foo3 b/sm1/foo3 + new file mode 100644 + index 0000000..c1ec6c6 + --- /dev/null + +++ b/sm1/foo3 + @@ -0,0 +1 @@ + +foo3 + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule(forward) --submodule' ' + git diff --submodule >actual && + cat >expected <<-EOF && + Submodule sm1 $head1..$head2: + > Add foo3 ($added foo3) + EOF + test_cmp expected actual +' + +fullhead2=$(cd sm1; git rev-parse --verify HEAD) +test_expect_success 'modified submodule(forward) --submodule=short' ' + git diff --submodule=short >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + index $head1..$head2 160000 + --- a/sm1 + +++ b/sm1 + @@ -1 +1 @@ + -Subproject commit $fullhead1 + +Subproject commit $fullhead2 + EOF + test_cmp expected actual +' + +commit_file sm1 && +head3=$( + cd sm1 && + git reset --hard HEAD~2 >/dev/null && + git rev-parse --short --verify HEAD +) + +test_expect_success 'modified submodule(backward)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head2..$head3 (rewind): + diff --git a/sm1/foo2 b/sm1/foo2 + deleted file mode 100644 + index 54b060e..0000000 + --- a/sm1/foo2 + +++ /dev/null + @@ -1 +0,0 @@ + -foo2 + diff --git a/sm1/foo3 b/sm1/foo3 + deleted file mode 100644 + index c1ec6c6..0000000 + --- a/sm1/foo3 + +++ /dev/null + @@ -1 +0,0 @@ + -foo3 + EOF + test_cmp expected actual +' + +head4=$(add_file sm1 foo4 foo5) +test_expect_success 'modified submodule(backward and forward)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head2...$head4: + diff --git a/sm1/foo2 b/sm1/foo2 + deleted file mode 100644 + index 54b060e..0000000 + --- a/sm1/foo2 + +++ /dev/null + @@ -1 +0,0 @@ + -foo2 + diff --git a/sm1/foo3 b/sm1/foo3 + deleted file mode 100644 + index c1ec6c6..0000000 + --- a/sm1/foo3 + +++ /dev/null + @@ -1 +0,0 @@ + -foo3 + diff --git a/sm1/foo4 b/sm1/foo4 + new file mode 100644 + index 0000000..a0016db + --- /dev/null + +++ b/sm1/foo4 + @@ -0,0 +1 @@ + +foo4 + diff --git a/sm1/foo5 b/sm1/foo5 + new file mode 100644 + index 0000000..d6f2413 + --- /dev/null + +++ b/sm1/foo5 + @@ -0,0 +1 @@ + +foo5 + EOF + test_cmp expected actual +' + +commit_file sm1 && +mv sm1 sm1-bak && +echo sm1 >sm1 && +head5=$(git hash-object sm1 | cut -c1-7) && +git add sm1 && +rm -f sm1 && +mv sm1-bak sm1 + +test_expect_success 'typechanged submodule(submodule->blob), --cached' ' + git diff --submodule=diff --cached >actual && + cat >expected <<-EOF && + Submodule sm1 $head4...0000000 (submodule deleted) + diff --git a/sm1/foo1 b/sm1/foo1 + deleted file mode 100644 + index 1715acd..0000000 + --- a/sm1/foo1 + +++ /dev/null + @@ -1 +0,0 @@ + -foo1 + diff --git a/sm1/foo4 b/sm1/foo4 + deleted file mode 100644 + index a0016db..0000000 + --- a/sm1/foo4 + +++ /dev/null + @@ -1 +0,0 @@ + -foo4 + diff --git a/sm1/foo5 b/sm1/foo5 + deleted file mode 100644 + index d6f2413..0000000 + --- a/sm1/foo5 + +++ /dev/null + @@ -1 +0,0 @@ + -foo5 + diff --git a/sm1 b/sm1 + new file mode 100644 + index 0000000..9da5fb8 + --- /dev/null + +++ b/sm1 + @@ -0,0 +1 @@ + +sm1 + EOF + test_cmp expected actual +' + +test_expect_success 'typechanged submodule(submodule->blob)' ' + git diff --submodule=diff >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + deleted file mode 100644 + index 9da5fb8..0000000 + --- a/sm1 + +++ /dev/null + @@ -1 +0,0 @@ + -sm1 + Submodule sm1 0000000...$head4 (new submodule) + diff --git a/sm1/foo1 b/sm1/foo1 + new file mode 100644 + index 0000000..1715acd + --- /dev/null + +++ b/sm1/foo1 + @@ -0,0 +1 @@ + +foo1 + diff --git a/sm1/foo4 b/sm1/foo4 + new file mode 100644 + index 0000000..a0016db + --- /dev/null + +++ b/sm1/foo4 + @@ -0,0 +1 @@ + +foo4 + diff --git a/sm1/foo5 b/sm1/foo5 + new file mode 100644 + index 0000000..d6f2413 + --- /dev/null + +++ b/sm1/foo5 + @@ -0,0 +1 @@ + +foo5 + EOF + test_cmp expected actual +' + +rm -rf sm1 && +git checkout-index sm1 +test_expect_success 'typechanged submodule(submodule->blob)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head4...0000000 (submodule deleted) + diff --git a/sm1 b/sm1 + new file mode 100644 + index 0000000..9da5fb8 + --- /dev/null + +++ b/sm1 + @@ -0,0 +1 @@ + +sm1 + EOF + test_cmp expected actual +' + +rm -f sm1 && +test_create_repo sm1 && +head6=$(add_file sm1 foo6 foo7) +fullhead6=$(cd sm1; git rev-parse --verify HEAD) +test_expect_success 'nonexistent commit' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head4...$head6 (commits not present) + EOF + test_cmp expected actual +' + +commit_file +test_expect_success 'typechanged submodule(blob->submodule)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + deleted file mode 100644 + index 9da5fb8..0000000 + --- a/sm1 + +++ /dev/null + @@ -1 +0,0 @@ + -sm1 + Submodule sm1 0000000...$head6 (new submodule) + diff --git a/sm1/foo6 b/sm1/foo6 + new file mode 100644 + index 0000000..462398b + --- /dev/null + +++ b/sm1/foo6 + @@ -0,0 +1 @@ + +foo6 + diff --git a/sm1/foo7 b/sm1/foo7 + new file mode 100644 + index 0000000..6e9262c + --- /dev/null + +++ b/sm1/foo7 + @@ -0,0 +1 @@ + +foo7 + EOF + test_cmp expected actual +' + +commit_file sm1 && +test_expect_success 'submodule is up to date' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + EOF + test_cmp expected actual +' + +test_expect_success 'submodule contains untracked content' ' + echo new > sm1/new-file && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains untracked content + EOF + test_cmp expected actual +' + +test_expect_success 'submodule contains untracked content (untracked ignored)' ' + git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains untracked content (dirty ignored)' ' + git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains untracked content (all ignored)' ' + git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains untracked and modified content' ' + echo new > sm1/foo6 && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains untracked content + Submodule sm1 contains modified content + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +# NOT OK +test_expect_success 'submodule contains untracked and modified content (untracked ignored)' ' + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains modified content + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'submodule contains untracked and modified content (dirty ignored)' ' + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains untracked and modified content (all ignored)' ' + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains modified content' ' + rm -f sm1/new-file && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains modified content + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +(cd sm1; git commit -mchange foo6 >/dev/null) && +head8=$(cd sm1; git rev-parse --short --verify HEAD) && +test_expect_success 'submodule is modified' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9..$head8: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked content' ' + echo new > sm1/new-file && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains untracked content + Submodule sm1 17243c9..$head8: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked content (untracked ignored)' ' + git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9..$head8: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked content (dirty ignored)' ' + git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked content (all ignored)' ' + git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'modified submodule contains untracked and modified content' ' + echo modification >> sm1/foo6 && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains untracked content + Submodule sm1 contains modified content + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..dfda541 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1,2 @@ + -foo6 + +new + +modification + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked and modified content (untracked ignored)' ' + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains modified content + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..e20e2d9 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1,3 @@ + -foo6 + +new + +modification + +modification + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked and modified content (dirty ignored)' ' + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked and modified content (all ignored)' ' + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=diff HEAD >actual && + ! test -s actual +' + +# NOT OK +test_expect_success 'modified submodule contains modified content' ' + rm -f sm1/new-file && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains modified content + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..ac466ca 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1,5 @@ + -foo6 + +new + +modification + +modification + +modification + +modification + EOF + test_cmp expected actual +' + +rm -rf sm1 +test_expect_success 'deleted submodule' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9...0000000 (submodule deleted) + EOF + test_cmp expected actual +' + +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 && + cat >expected <<-EOF && + Submodule sm1 17243c9...0000000 (submodule deleted) + Submodule sm2 0000000...a5a65c9 (new submodule) + diff --git a/sm2/foo8 b/sm2/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/foo9 b/sm2/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + test_cmp expected actual +' + +test_expect_success 'path filter' ' + git diff-index -p --submodule=diff HEAD sm2 >actual && + cat >expected <<-EOF && + Submodule sm2 0000000...a5a65c9 (new submodule) + diff --git a/sm2/foo8 b/sm2/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/foo9 b/sm2/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + test_cmp expected actual +' + +commit_file sm2 +test_expect_success 'given commit' ' + git diff-index -p --submodule=diff HEAD^ >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9...0000000 (submodule deleted) + Submodule sm2 0000000...a5a65c9 (new submodule) + diff --git a/sm2/foo8 b/sm2/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/foo9 b/sm2/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + test_cmp expected actual +' + +test_expect_success 'setup .git file for sm2' ' + (cd sm2 && + REAL="$(pwd)/../.real" && + mv .git "$REAL" + echo "gitdir: $REAL" >.git) +' + +test_expect_success 'diff --submodule=diff with .git file' ' + git diff --submodule=diff HEAD^ >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9...0000000 (submodule deleted) + Submodule sm2 0000000...a5a65c9 (new submodule) + diff --git a/sm2/foo8 b/sm2/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/foo9 b/sm2/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + test_cmp expected actual +' + +test_done diff --git a/t/t4061-diff-indent.sh b/t/t4061-diff-indent.sh new file mode 100755 index 0000000000..556450609b --- /dev/null +++ b/t/t4061-diff-indent.sh @@ -0,0 +1,216 @@ +#!/bin/sh + +test_description='Test diff indent heuristic. + +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh + +# Compare two diff outputs. Ignore "index" lines, because we don't +# care about SHA-1s or file modes. +compare_diff () { + sed -e "/^index /d" <"$1" >.tmp-1 + sed -e "/^index /d" <"$2" >.tmp-2 + test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 +} + +# Compare blame output using the expectation for a diff as reference. +# Only look for the lines coming from non-boundary commits. +compare_blame () { + sed -n -e "1,4d" -e "s/^\+//p" <"$1" >.tmp-1 + sed -ne "s/^[^^][^)]*) *//p" <"$2" >.tmp-2 + test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 +} + +test_expect_success 'prepare' ' + cat <<-\EOF >spaces.txt && + 1 + 2 + a + + b + 3 + 4 + EOF + + cat <<-\EOF >functions.c && + 1 + 2 + /* function */ + foo() { + foo + } + + 3 + 4 + EOF + + git add spaces.txt functions.c && + test_tick && + git commit -m initial && + git branch old && + + cat <<-\EOF >spaces.txt && + 1 + 2 + a + + b + a + + b + 3 + 4 + EOF + + cat <<-\EOF >functions.c && + 1 + 2 + /* function */ + bar() { + foo + } + + /* function */ + foo() { + foo + } + + 3 + 4 + EOF + + git add spaces.txt functions.c && + test_tick && + git commit -m initial && + git branch new && + + tr "_" " " <<-\EOF >spaces-expect && + diff --git a/spaces.txt b/spaces.txt + --- a/spaces.txt + +++ b/spaces.txt + @@ -3,5 +3,8 @@ + a + _ + b + +a + + + +b + 3 + 4 + EOF + + tr "_" " " <<-\EOF >spaces-compacted-expect && + diff --git a/spaces.txt b/spaces.txt + --- a/spaces.txt + +++ b/spaces.txt + @@ -2,6 +2,9 @@ + 2 + a + _ + +b + +a + + + b + 3 + 4 + EOF + + tr "_" " " <<-\EOF >functions-expect && + diff --git a/functions.c b/functions.c + --- a/functions.c + +++ b/functions.c + @@ -1,6 +1,11 @@ + 1 + 2 + /* function */ + +bar() { + + foo + +} + + + +/* function */ + foo() { + foo + } + EOF + + tr "_" " " <<-\EOF >functions-compacted-expect + diff --git a/functions.c b/functions.c + --- a/functions.c + +++ b/functions.c + @@ -1,5 +1,10 @@ + 1 + 2 + +/* function */ + +bar() { + + foo + +} + + + /* function */ + foo() { + foo + EOF +' + +test_expect_success 'diff: ugly spaces' ' + git diff old new -- spaces.txt >out && + compare_diff spaces-expect out +' + +test_expect_success 'diff: nice spaces with --indent-heuristic' ' + git 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' ' + 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 +' + +test_expect_success 'diff: --indent-heuristic with --histogram' ' + git diff --indent-heuristic --histogram old new -- spaces.txt >out-compacted4 && + compare_diff spaces-compacted-expect out-compacted4 +' + +test_expect_success 'diff: ugly functions' ' + git diff old new -- functions.c >out && + compare_diff functions-expect out +' + +test_expect_success 'diff: nice functions with --indent-heuristic' ' + git diff --indent-heuristic old new -- functions.c >out-compacted && + 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 +' + +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' ' + 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: --no-indent-heuristic overrides config' ' + git -c diff.indentHeuristic=true blame --no-indent-heuristic old..new -- spaces.txt >out-blame2 && + git blame old..new -- spaces.txt >out-blame && + compare_blame spaces-expect out-blame2 +' + +test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index e2db47c36e..1ccbd5948a 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -188,6 +188,16 @@ test_expect_success 'git log --no-walk=sorted <commits> sorts by commit time' ' ' cat > expect << EOF +=== 804a787 sixth +=== 394ef78 fifth +=== 5d31159 fourth +EOF +test_expect_success 'git log --line-prefix="=== " --no-walk <commits> sorts by commit time' ' + git log --line-prefix="=== " --no-walk --oneline 5d31159 804a787 394ef78 > actual && + test_cmp expect actual +' + +cat > expect << EOF 5d31159 fourth 804a787 sixth 394ef78 fifth @@ -284,6 +294,21 @@ test_expect_success 'simple log --graph' ' test_cmp expect actual ' +cat > expect <<EOF +123 * Second +123 * sixth +123 * fifth +123 * fourth +123 * third +123 * second +123 * initial +EOF + +test_expect_success 'simple log --graph --line-prefix="123 "' ' + git log --graph --line-prefix="123 " --pretty=tformat:%s >actual && + test_cmp expect actual +' + test_expect_success 'set up merge history' ' git checkout -b side HEAD~4 && test_commit side-1 1 1 && @@ -313,6 +338,27 @@ test_expect_success 'log --graph with merge' ' test_cmp expect actual ' +cat > expect <<\EOF +| | | * Merge branch 'side' +| | | |\ +| | | | * side-2 +| | | | * side-1 +| | | * | Second +| | | * | sixth +| | | * | fifth +| | | * | fourth +| | | |/ +| | | * third +| | | * second +| | | * initial +EOF + +test_expect_success 'log --graph --line-prefix="| | | " with merge' ' + git log --line-prefix="| | | " --graph --date-order --pretty=tformat:%s | + sed "s/ *\$//" >actual && + test_cmp expect actual +' + test_expect_success 'log --raw --graph -m with merge' ' git log --raw --graph --oneline -m master | head -n 500 >actual && grep "initial" actual @@ -867,6 +913,283 @@ test_expect_success 'log --graph with diff and stats' ' test_i18ncmp expect actual.sanitized ' +cat >expect <<\EOF +*** * commit COMMIT_OBJECT_NAME +*** |\ Merge: MERGE_PARENTS +*** | | Author: A U Thor <author@example.com> +*** | | +*** | | Merge HEADS DESCRIPTION +*** | | +*** | * commit COMMIT_OBJECT_NAME +*** | | Author: A U Thor <author@example.com> +*** | | +*** | | reach +*** | | --- +*** | | reach.t | 1 + +*** | | 1 file changed, 1 insertion(+) +*** | | +*** | | diff --git a/reach.t b/reach.t +*** | | new file mode 100644 +*** | | index 0000000..10c9591 +*** | | --- /dev/null +*** | | +++ b/reach.t +*** | | @@ -0,0 +1 @@ +*** | | +reach +*** | | +*** | \ +*** *-. \ commit COMMIT_OBJECT_NAME +*** |\ \ \ Merge: MERGE_PARENTS +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | Merge HEADS DESCRIPTION +*** | | | | +*** | | * | commit COMMIT_OBJECT_NAME +*** | | |/ Author: A U Thor <author@example.com> +*** | | | +*** | | | octopus-b +*** | | | --- +*** | | | octopus-b.t | 1 + +*** | | | 1 file changed, 1 insertion(+) +*** | | | +*** | | | diff --git a/octopus-b.t b/octopus-b.t +*** | | | new file mode 100644 +*** | | | index 0000000..d5fcad0 +*** | | | --- /dev/null +*** | | | +++ b/octopus-b.t +*** | | | @@ -0,0 +1 @@ +*** | | | +octopus-b +*** | | | +*** | * | commit COMMIT_OBJECT_NAME +*** | |/ Author: A U Thor <author@example.com> +*** | | +*** | | octopus-a +*** | | --- +*** | | octopus-a.t | 1 + +*** | | 1 file changed, 1 insertion(+) +*** | | +*** | | diff --git a/octopus-a.t b/octopus-a.t +*** | | new file mode 100644 +*** | | index 0000000..11ee015 +*** | | --- /dev/null +*** | | +++ b/octopus-a.t +*** | | @@ -0,0 +1 @@ +*** | | +octopus-a +*** | | +*** * | commit COMMIT_OBJECT_NAME +*** |/ Author: A U Thor <author@example.com> +*** | +*** | seventh +*** | --- +*** | seventh.t | 1 + +*** | 1 file changed, 1 insertion(+) +*** | +*** | diff --git a/seventh.t b/seventh.t +*** | new file mode 100644 +*** | index 0000000..9744ffc +*** | --- /dev/null +*** | +++ b/seventh.t +*** | @@ -0,0 +1 @@ +*** | +seventh +*** | +*** * commit COMMIT_OBJECT_NAME +*** |\ Merge: MERGE_PARENTS +*** | | Author: A U Thor <author@example.com> +*** | | +*** | | Merge branch 'tangle' +*** | | +*** | * commit COMMIT_OBJECT_NAME +*** | |\ Merge: MERGE_PARENTS +*** | | | Author: A U Thor <author@example.com> +*** | | | +*** | | | Merge branch 'side' (early part) into tangle +*** | | | +*** | * | commit COMMIT_OBJECT_NAME +*** | |\ \ Merge: MERGE_PARENTS +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | Merge branch 'master' (early part) into tangle +*** | | | | +*** | * | | commit COMMIT_OBJECT_NAME +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | tangle-a +*** | | | | --- +*** | | | | tangle-a | 1 + +*** | | | | 1 file changed, 1 insertion(+) +*** | | | | +*** | | | | diff --git a/tangle-a b/tangle-a +*** | | | | new file mode 100644 +*** | | | | index 0000000..7898192 +*** | | | | --- /dev/null +*** | | | | +++ b/tangle-a +*** | | | | @@ -0,0 +1 @@ +*** | | | | +a +*** | | | | +*** * | | | commit COMMIT_OBJECT_NAME +*** |\ \ \ \ Merge: MERGE_PARENTS +*** | | | | | Author: A U Thor <author@example.com> +*** | | | | | +*** | | | | | Merge branch 'side' +*** | | | | | +*** | * | | | commit COMMIT_OBJECT_NAME +*** | | |_|/ Author: A U Thor <author@example.com> +*** | |/| | +*** | | | | side-2 +*** | | | | --- +*** | | | | 2 | 1 + +*** | | | | 1 file changed, 1 insertion(+) +*** | | | | +*** | | | | diff --git a/2 b/2 +*** | | | | new file mode 100644 +*** | | | | index 0000000..0cfbf08 +*** | | | | --- /dev/null +*** | | | | +++ b/2 +*** | | | | @@ -0,0 +1 @@ +*** | | | | +2 +*** | | | | +*** | * | | commit COMMIT_OBJECT_NAME +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | side-1 +*** | | | | --- +*** | | | | 1 | 1 + +*** | | | | 1 file changed, 1 insertion(+) +*** | | | | +*** | | | | diff --git a/1 b/1 +*** | | | | new file mode 100644 +*** | | | | index 0000000..d00491f +*** | | | | --- /dev/null +*** | | | | +++ b/1 +*** | | | | @@ -0,0 +1 @@ +*** | | | | +1 +*** | | | | +*** * | | | commit COMMIT_OBJECT_NAME +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | Second +*** | | | | --- +*** | | | | one | 1 + +*** | | | | 1 file changed, 1 insertion(+) +*** | | | | +*** | | | | diff --git a/one b/one +*** | | | | new file mode 100644 +*** | | | | index 0000000..9a33383 +*** | | | | --- /dev/null +*** | | | | +++ b/one +*** | | | | @@ -0,0 +1 @@ +*** | | | | +case +*** | | | | +*** * | | | commit COMMIT_OBJECT_NAME +*** | |_|/ Author: A U Thor <author@example.com> +*** |/| | +*** | | | sixth +*** | | | --- +*** | | | a/two | 1 - +*** | | | 1 file changed, 1 deletion(-) +*** | | | +*** | | | diff --git a/a/two b/a/two +*** | | | deleted file mode 100644 +*** | | | index 9245af5..0000000 +*** | | | --- a/a/two +*** | | | +++ /dev/null +*** | | | @@ -1 +0,0 @@ +*** | | | -ni +*** | | | +*** * | | commit COMMIT_OBJECT_NAME +*** | | | Author: A U Thor <author@example.com> +*** | | | +*** | | | fifth +*** | | | --- +*** | | | a/two | 1 + +*** | | | 1 file changed, 1 insertion(+) +*** | | | +*** | | | diff --git a/a/two b/a/two +*** | | | new file mode 100644 +*** | | | index 0000000..9245af5 +*** | | | --- /dev/null +*** | | | +++ b/a/two +*** | | | @@ -0,0 +1 @@ +*** | | | +ni +*** | | | +*** * | | commit COMMIT_OBJECT_NAME +*** |/ / Author: A U Thor <author@example.com> +*** | | +*** | | fourth +*** | | --- +*** | | ein | 1 + +*** | | 1 file changed, 1 insertion(+) +*** | | +*** | | diff --git a/ein b/ein +*** | | new file mode 100644 +*** | | index 0000000..9d7e69f +*** | | --- /dev/null +*** | | +++ b/ein +*** | | @@ -0,0 +1 @@ +*** | | +ichi +*** | | +*** * | commit COMMIT_OBJECT_NAME +*** |/ Author: A U Thor <author@example.com> +*** | +*** | third +*** | --- +*** | ichi | 1 + +*** | one | 1 - +*** | 2 files changed, 1 insertion(+), 1 deletion(-) +*** | +*** | diff --git a/ichi b/ichi +*** | new file mode 100644 +*** | index 0000000..9d7e69f +*** | --- /dev/null +*** | +++ b/ichi +*** | @@ -0,0 +1 @@ +*** | +ichi +*** | diff --git a/one b/one +*** | deleted file mode 100644 +*** | index 9d7e69f..0000000 +*** | --- a/one +*** | +++ /dev/null +*** | @@ -1 +0,0 @@ +*** | -ichi +*** | +*** * commit COMMIT_OBJECT_NAME +*** | Author: A U Thor <author@example.com> +*** | +*** | second +*** | --- +*** | one | 2 +- +*** | 1 file changed, 1 insertion(+), 1 deletion(-) +*** | +*** | diff --git a/one b/one +*** | index 5626abf..9d7e69f 100644 +*** | --- a/one +*** | +++ b/one +*** | @@ -1 +1 @@ +*** | -one +*** | +ichi +*** | +*** * commit COMMIT_OBJECT_NAME +*** Author: A U Thor <author@example.com> +*** +*** initial +*** --- +*** one | 1 + +*** 1 file changed, 1 insertion(+) +*** +*** diff --git a/one b/one +*** new file mode 100644 +*** index 0000000..5626abf +*** --- /dev/null +*** +++ b/one +*** @@ -0,0 +1 @@ +*** +one +EOF + +test_expect_success 'log --line-prefix="*** " --graph with diff and stats' ' + git log --line-prefix="*** " --no-renames --graph --pretty=short --stat -p >actual && + sanitize_output >actual.sanitized <actual && + test_i18ncmp expect actual.sanitized +' + test_expect_success 'dotdot is a parent directory' ' mkdir -p a/b && ( echo sixth && echo fifth ) >expect && diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index 84a809690e..0288c17ec6 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -143,6 +143,20 @@ test_expect_success 'patch-id supports git-format-patch MIME output' ' test_cmp patch-id_master patch-id_same ' +test_expect_success 'patch-id respects config from subdir' ' + test_config patchid.stable true && + mkdir subdir && + + # copy these because test_patch_id() looks for them in + # the current directory + cp bar-then-foo foo-then-bar subdir && + + ( + cd subdir && + test_patch_id irrelevant patchid.stable=true + ) +' + cat >nonl <<\EOF diff --git i/a w/a index e69de29..2e65efe 100644 diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index 85716dd6ec..9bd7dd2ba1 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -29,7 +29,7 @@ test_expect_success 'try to apply corrupted patch' ' ' test_expect_success 'compare diagnostic; ensure file is still here' ' - echo "fatal: git diff header lacks filename information (line 4)" >expected && + echo "error: git diff header lacks filename information (line 4)" >expected && test_path_is_file f && test_cmp expected actual ' diff --git a/t/t5305-include-tag.sh b/t/t5305-include-tag.sh index f314ad5079..a5eca210b8 100755 --- a/t/t5305-include-tag.sh +++ b/t/t5305-include-tag.sh @@ -25,58 +25,94 @@ test_expect_success setup ' } >obj-list ' -rm -rf clone.git test_expect_success 'pack without --include-tag' ' - packname_1=$(git pack-objects \ + packname=$(git pack-objects \ --window=0 \ - test-1 <obj-list) + test-no-include <obj-list) ' test_expect_success 'unpack objects' ' - ( - GIT_DIR=clone.git && - export GIT_DIR && - git init && - git unpack-objects -n <test-1-${packname_1}.pack && - git unpack-objects <test-1-${packname_1}.pack - ) + rm -rf clone.git && + git init clone.git && + git -C clone.git unpack-objects <test-no-include-${packname}.pack ' test_expect_success 'check unpacked result (have commit, no tag)' ' git rev-list --objects $commit >list.expect && - ( - test_must_fail env GIT_DIR=clone.git git cat-file -e $tag && - git rev-list --objects $commit - ) >list.actual && + test_must_fail git -C clone.git cat-file -e $tag && + git -C clone.git rev-list --objects $commit >list.actual && test_cmp list.expect list.actual ' -rm -rf clone.git test_expect_success 'pack with --include-tag' ' - packname_1=$(git pack-objects \ + packname=$(git pack-objects \ --window=0 \ --include-tag \ - test-2 <obj-list) + test-include <obj-list) ' test_expect_success 'unpack objects' ' - ( - GIT_DIR=clone.git && - export GIT_DIR && - git init && - git unpack-objects -n <test-2-${packname_1}.pack && - git unpack-objects <test-2-${packname_1}.pack - ) + rm -rf clone.git && + git init clone.git && + git -C clone.git unpack-objects <test-include-${packname}.pack ' test_expect_success 'check unpacked result (have commit, have tag)' ' git rev-list --objects mytag >list.expect && - ( - GIT_DIR=clone.git && - export GIT_DIR && - git rev-list --objects $tag - ) >list.actual && + git -C clone.git rev-list --objects $tag >list.actual && test_cmp list.expect list.actual ' +# A tag of a tag, where the "inner" tag is not otherwise +# reachable, and a full peel points to a commit reachable from HEAD. +test_expect_success 'create hidden inner tag' ' + test_commit commit && + git tag -m inner inner HEAD && + git tag -m outer outer inner && + git tag -d inner +' + +test_expect_success 'pack explicit outer tag' ' + packname=$( + { + echo HEAD && + echo outer + } | + git pack-objects --revs test-hidden-explicit + ) +' + +test_expect_success 'unpack objects' ' + rm -rf clone.git && + git init clone.git && + git -C clone.git unpack-objects <test-hidden-explicit-${packname}.pack +' + +test_expect_success 'check unpacked result (have all objects)' ' + git -C clone.git rev-list --objects $(git rev-parse outer HEAD) +' + +test_expect_success 'pack implied outer tag' ' + packname=$( + echo HEAD | + git pack-objects --revs --include-tag test-hidden-implied + ) +' + +test_expect_success 'unpack objects' ' + rm -rf clone.git && + git init clone.git && + git -C clone.git unpack-objects <test-hidden-implied-${packname}.pack +' + +test_expect_success 'check unpacked result (have all objects)' ' + git -C clone.git rev-list --objects $(git rev-parse outer HEAD) +' + +test_expect_success 'single-branch clone can transfer tag' ' + rm -rf clone.git && + git clone --no-local --single-branch -b master . clone.git && + git -C clone.git fsck +' + test_done diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 3893afd687..b4c7a6ff6b 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -7,6 +7,18 @@ objpath () { echo ".git/objects/$(echo "$1" | sed -e 's|\(..\)|\1/|')" } +# show objects present in pack ($1 should be associated *.idx) +list_packed_objects () { + git show-index <"$1" | cut -d' ' -f2 +} + +# has_any pattern-file content-file +# tests whether content-file has any entry from pattern-file with entries being +# whole lines. +has_any () { + grep -Ff "$1" "$2" +} + test_expect_success 'setup repo with moderate-sized history' ' for i in $(test_seq 1 10); do test_commit $i @@ -16,6 +28,7 @@ test_expect_success 'setup repo with moderate-sized history' ' test_commit side-$i done && git checkout master && + bitmaptip=$(git rev-parse master) && blob=$(echo tagged-blob | git hash-object -w --stdin) && git tag tagged-blob $blob && git config repack.writebitmaps true && @@ -118,6 +131,83 @@ test_expect_success 'incremental repack can disable bitmaps' ' git repack -d --no-write-bitmap-index ' +test_expect_success 'pack-objects respects --local (non-local loose)' ' + git init --bare alt.git && + echo $(pwd)/alt.git/objects >.git/objects/info/alternates && + echo content1 >file1 && + # non-local loose object which is not present in bitmapped pack + altblob=$(GIT_DIR=alt.git git hash-object -w file1) && + # non-local loose object which is also present in bitmapped pack + git cat-file blob $blob | GIT_DIR=alt.git git hash-object -w --stdin && + git add file1 && + test_tick && + git commit -m commit_file1 && + echo HEAD | git pack-objects --local --stdout --revs >1.pack && + git index-pack 1.pack && + list_packed_objects 1.idx >1.objects && + printf "%s\n" "$altblob" "$blob" >nonlocal-loose && + ! has_any nonlocal-loose 1.objects +' + +test_expect_success 'pack-objects respects --honor-pack-keep (local non-bitmapped pack)' ' + echo content2 >file2 && + blob2=$(git hash-object -w file2) && + git add file2 && + test_tick && + git commit -m commit_file2 && + printf "%s\n" "$blob2" "$bitmaptip" >keepobjects && + pack2=$(git pack-objects pack2 <keepobjects) && + mv pack2-$pack2.* .git/objects/pack/ && + >.git/objects/pack/pack2-$pack2.keep && + rm $(objpath $blob2) && + echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >2a.pack && + git index-pack 2a.pack && + list_packed_objects 2a.idx >2a.objects && + ! has_any keepobjects 2a.objects +' + +test_expect_success 'pack-objects respects --local (non-local pack)' ' + mv .git/objects/pack/pack2-$pack2.* alt.git/objects/pack/ && + echo HEAD | git pack-objects --local --stdout --revs >2b.pack && + git index-pack 2b.pack && + list_packed_objects 2b.idx >2b.objects && + ! has_any keepobjects 2b.objects +' + +test_expect_success 'pack-objects respects --honor-pack-keep (local bitmapped pack)' ' + ls .git/objects/pack/ | grep bitmap >output && + test_line_count = 1 output && + packbitmap=$(basename $(cat output) .bitmap) && + list_packed_objects .git/objects/pack/$packbitmap.idx >packbitmap.objects && + test_when_finished "rm -f .git/objects/pack/$packbitmap.keep" && + >.git/objects/pack/$packbitmap.keep && + echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >3a.pack && + git index-pack 3a.pack && + list_packed_objects 3a.idx >3a.objects && + ! has_any packbitmap.objects 3a.objects +' + +test_expect_success 'pack-objects respects --local (non-local bitmapped pack)' ' + mv .git/objects/pack/$packbitmap.* alt.git/objects/pack/ && + test_when_finished "mv alt.git/objects/pack/$packbitmap.* .git/objects/pack/" && + echo HEAD | git pack-objects --local --stdout --revs >3b.pack && + git index-pack 3b.pack && + list_packed_objects 3b.idx >3b.objects && + ! has_any packbitmap.objects 3b.objects +' + +test_expect_success 'pack-objects to file can use bitmap' ' + # make sure we still have 1 bitmap index from previous tests + ls .git/objects/pack/ | grep bitmap >output && + test_line_count = 1 output && + # verify equivalent packs are generated with/without using bitmap index + packasha1=$(git pack-objects --no-use-bitmap-index --all packa </dev/null) && + packbsha1=$(git pack-objects --use-bitmap-index --all packb </dev/null) && + list_packed_objects <packa-$packasha1.idx >packa.objects && + list_packed_objects <packb-$packbsha1.idx >packb.objects && + test_cmp packa.objects packb.objects +' + test_expect_success 'full repack, reusing previous bitmaps' ' git repack -ad && ls .git/objects/pack/ | grep bitmap >output && @@ -143,6 +233,20 @@ test_expect_success 'create objects for missing-HAVE tests' ' EOF ' +test_expect_success 'pack-objects respects --incremental' ' + cat >revs2 <<-EOF && + HEAD + $commit + EOF + git pack-objects --incremental --stdout --revs <revs2 >4.pack && + git index-pack 4.pack && + list_packed_objects 4.idx >4.objects && + test_line_count = 4 4.objects && + git rev-list --objects $commit >revlist && + cut -d" " -f1 revlist |sort >objects && + test_cmp 4.objects objects +' + test_expect_success 'pack with missing blob' ' rm $(objpath $blob) && git pack-objects --stdout --revs <revs >/dev/null @@ -158,10 +262,6 @@ test_expect_success 'pack with missing parent' ' git pack-objects --stdout --revs <revs >/dev/null ' -test_lazy_prereq JGIT ' - type jgit -' - test_expect_success JGIT 'we can read jgit bitmaps' ' git clone . compat-jgit && ( diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index c23434ba29..55fc83fc06 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -207,5 +207,45 @@ test_expect_success 'ls-remote --symref omits filtered-out matches' ' test_cmp expect actual ' +test_lazy_prereq GIT_DAEMON ' + test_tristate GIT_TEST_GIT_DAEMON && + test "$GIT_TEST_GIT_DAEMON" != false +' + +# This test spawns a daemon, so run it only if the user would be OK with +# testing with git-daemon. +test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' ' + JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} && + JGIT_DAEMON_PID= && + git init --bare empty.git && + >empty.git/git-daemon-export-ok && + mkfifo jgit_daemon_output && + { + jgit daemon --port="$JGIT_DAEMON_PORT" . >jgit_daemon_output & + JGIT_DAEMON_PID=$! + } && + test_when_finished kill "$JGIT_DAEMON_PID" && + { + read line && + case $line in + Exporting*) + ;; + *) + echo "Expected: Exporting" && + false;; + esac && + read line && + case $line in + "Listening on"*) + ;; + *) + echo "Expected: Listening on" && + false;; + esac + } <jgit_daemon_output && + # --exit-code asks the command to exit with 2 when no + # matching refs are found. + test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git +' test_done diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 954d0e43f5..f3b0a8d30a 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -485,4 +485,39 @@ test_expect_success 'fetching submodules respects parallel settings' ' ) ' +test_expect_success 'fetching submodule into a broken repository' ' + # Prepare src and src/sub nested in it + git init src && + ( + cd src && + git init sub && + git -C sub commit --allow-empty -m "initial in sub" && + git submodule add -- ./sub sub && + git commit -m "initial in top" + ) && + + # Clone the old-fashoned way + git clone src dst && + git -C dst clone ../src/sub sub && + + # Make sure that old-fashoned layout is still supported + git -C dst status && + + # "diff" would find no change + git -C dst diff --exit-code && + + # Recursive-fetch works fine + git -C dst fetch --recurse-submodules && + + # Break the receiving submodule + rm -f dst/sub/.git/HEAD && + + # NOTE: without the fix the following tests will recurse forever! + # They should terminate with an error. + + test_must_fail git -C dst status && + test_must_fail git -C dst diff && + test_must_fail git -C dst fetch --recurse-submodules +' + test_done diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh index 4840c71f02..d38bf32470 100755 --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@ -74,7 +74,7 @@ test_expect_success 'push to remote repository (standard)' ' test_tick && git commit -m path2 && HEAD=$(git rev-parse --verify HEAD) && - GIT_CURL_VERBOSE=1 git push -v -v 2>err && + GIT_TRACE_CURL=true git push -v -v 2>err && ! grep "Expect: 100-continue" err && grep "POST git-receive-pack ([0-9]* bytes)" err && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && diff --git a/t/t5546-receive-limits.sh b/t/t5546-receive-limits.sh new file mode 100755 index 0000000000..10cb0be2b7 --- /dev/null +++ b/t/t5546-receive-limits.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='check receive input limits' +. ./test-lib.sh + +# Let's run tests with different unpack limits: 1 and 10000 +# When the limit is 1, `git receive-pack` will call `git index-pack`. +# When the limit is 10000, `git receive-pack` will call `git unpack-objects`. + +test_pack_input_limit () { + case "$1" in + index) unpack_limit=1 ;; + unpack) unpack_limit=10000 ;; + esac + + test_expect_success 'prepare destination repository' ' + rm -fr dest && + git --bare init dest + ' + + test_expect_success "set unpacklimit to $unpack_limit" ' + git --git-dir=dest config receive.unpacklimit "$unpack_limit" + ' + + test_expect_success 'setting receive.maxInputSize to 512 rejects push' ' + git --git-dir=dest config receive.maxInputSize 512 && + test_must_fail git push dest HEAD + ' + + test_expect_success 'bumping limit to 4k allows push' ' + git --git-dir=dest config receive.maxInputSize 4k && + git push dest HEAD + ' + + test_expect_success 'prepare destination repository (again)' ' + rm -fr dest && + git --bare init dest + ' + + test_expect_success 'lifting the limit allows push' ' + git --git-dir=dest config receive.maxInputSize 0 && + git push dest HEAD + ' +} + +test_expect_success "create known-size (1024 bytes) commit" ' + test-genrandom foo 1024 >one-k && + git add one-k && + test_commit one-k +' + +test_pack_input_limit index +test_pack_input_limit unpack + +test_done diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 3484b6f0f3..7641417b4a 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -263,15 +263,15 @@ check_language () { >expect ;; ?*) - echo "Accept-Language: $1" >expect + echo "=> Send header: Accept-Language: $1" >expect ;; esac && - GIT_CURL_VERBOSE=1 \ + GIT_TRACE_CURL=true \ LANGUAGE=$2 \ git ls-remote "$HTTPD_URL/dumb/repo.git" >output 2>&1 && tr -d '\015' <output | sort -u | - sed -ne '/^Accept-Language:/ p' >actual && + sed -ne '/^=> Send header: Accept-Language:/ p' >actual && test_cmp expect actual } @@ -295,8 +295,16 @@ ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90" \ ' test_expect_success 'git client does not send an empty Accept-Language' ' - GIT_CURL_VERBOSE=1 LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr && - ! grep "^Accept-Language:" stderr + GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr && + ! grep "^=> Send header: Accept-Language:" stderr +' + +test_expect_success 'remote-http complains cleanly about malformed urls' ' + # do not actually issue "list" or other commands, as we do not + # want to rely on what curl would actually do with such a broken + # URL. This is just about making sure we do not segfault during + # initialization. + test_must_fail git remote-http http::/example.com/repo.git ' stop_httpd diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 2f375eb94d..1ec5b2747a 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -43,12 +43,21 @@ cat >exp <<EOF < Content-Type: application/x-git-upload-pack-result EOF test_expect_success 'clone http repository' ' - GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && + GIT_TRACE_CURL=true git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && test_cmp file clone/file && tr '\''\015'\'' Q <err | sed -e " s/Q\$// /^[*] /d + /^== Info:/d + /^=> Send header, /d + /^=> Send header:$/d + /^<= Recv header, /d + /^<= Recv header:$/d + s/=> Send header: // + s/= Recv header:// + /^<= Recv data/d + /^=> Send data/d /^$/d /^< $/d @@ -261,9 +270,9 @@ test_expect_success CMDLINE_LIMIT \ ' test_expect_success 'large fetch-pack requests can be split across POSTs' ' - GIT_CURL_VERBOSE=1 git -c http.postbuffer=65536 \ + GIT_TRACE_CURL=true git -c http.postbuffer=65536 \ clone --bare "$HTTPD_URL/smart/repo.git" split.git 2>err && - grep "^> POST" err >posts && + grep "^=> Send header: POST" err >posts && test_line_count = 2 posts ' diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index a1dcdb81d7..f6020cd2aa 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -225,7 +225,7 @@ test_expect_success '%C(auto,...) respects --color=auto (stdout not tty)' ' test_expect_success '%C(auto) respects --color' ' git log --color --format="%C(auto)%H" -1 >actual && - printf "\\033[33m%s\\033[m\\n" $(git rev-parse HEAD) >expect && + printf "\\033[m\\033[33m%s\\033[m\\n" $(git rev-parse HEAD) >expect && test_cmp expect actual ' diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh index dd8f88d187..7a6e33e673 100755 --- a/t/t6026-merge-attr.sh +++ b/t/t6026-merge-attr.sh @@ -185,7 +185,9 @@ test_expect_success 'custom merge does not lock index' ' git reset --hard anchor && write_script sleep-one-second.sh <<-\EOF && sleep 1 & + echo $! >sleep.pid EOF + test_when_finished "kill \$(cat sleep.pid)" && test_write_lines >.gitattributes \ "* merge=ours" "text merge=sleep-one-second" && diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 4d17363a92..53cf42fac1 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -232,4 +232,25 @@ test_expect_success 'status --branch with detached HEAD' ' test_i18ncmp expected actual ' +## Duplicate the above test and verify --porcelain=v1 arg parsing. +test_expect_success 'status --porcelain=v1 --branch with detached HEAD' ' + git reset --hard && + git checkout master^0 && + git status --branch --porcelain=v1 >actual && + cat >expected <<-EOF && + ## HEAD (no branch) + ?? .gitconfig + ?? actual + ?? expect + ?? expected + ?? mdconflict/ + EOF + test_i18ncmp expected actual +' + +## Verify parser error on invalid --porcelain argument. +test_expect_success 'status --porcelain=bogus' ' + test_must_fail git status --porcelain=bogus +' + test_done diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh new file mode 100755 index 0000000000..3012a4d7c0 --- /dev/null +++ b/t/t7064-wtstatus-pv2.sh @@ -0,0 +1,593 @@ +#!/bin/sh + +test_description='git status --porcelain=v2 + +This test exercises porcelain V2 output for git status.' + + +. ./test-lib.sh + + +test_expect_success setup ' + test_tick && + git config core.autocrlf false && + echo x >file_x && + echo y >file_y && + echo z >file_z && + mkdir dir1 && + echo a >dir1/file_a && + echo b >dir1/file_b +' + +test_expect_success 'before initial commit, nothing added, only untracked' ' + cat >expect <<-EOF && + # branch.oid (initial) + # branch.head master + ? actual + ? dir1/ + ? expect + ? file_x + ? file_y + ? file_z + EOF + + git status --porcelain=v2 --branch --untracked-files=normal >actual && + test_cmp expect actual +' + +test_expect_success 'before initial commit, things added' ' + git add file_x file_y file_z dir1 && + OID_A=$(git hash-object -t blob -- dir1/file_a) && + OID_B=$(git hash-object -t blob -- dir1/file_b) && + OID_X=$(git hash-object -t blob -- file_x) && + OID_Y=$(git hash-object -t blob -- file_y) && + OID_Z=$(git hash-object -t blob -- file_z) && + + cat >expect <<-EOF && + # branch.oid (initial) + # branch.head master + 1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $_z40 $OID_X file_x + 1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y + 1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'before initial commit, things added (-z)' ' + lf_to_nul >expect <<-EOF && + # branch.oid (initial) + # branch.head master + 1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $_z40 $OID_X file_x + 1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y + 1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z + ? actual + ? expect + EOF + + git status -z --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'make first commit, comfirm HEAD oid and branch' ' + git commit -m initial && + H0=$(git rev-parse HEAD) && + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit, create unstaged changes' ' + echo x >>file_x && + OID_X1=$(git hash-object -t blob -- file_x) && + rm file_z && + H0=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 .M N... 100644 100644 100644 $OID_X $OID_X file_x + 1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit but omit untracked files and branch' ' + cat >expect <<-EOF && + 1 .M N... 100644 100644 100644 $OID_X $OID_X file_x + 1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z + EOF + + git status --porcelain=v2 --untracked-files=no >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit, stage existing changes' ' + git add file_x && + git rm file_z && + H0=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'rename causes 2 path lines' ' + git mv file_y renamed_y && + H0=$(git rev-parse HEAD) && + + q_to_tab >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'rename causes 2 path lines (-z)' ' + H0=$(git rev-parse HEAD) && + + ## Lines use NUL path separator and line terminator, so double transform here. + q_to_nul <<-EOF | lf_to_nul >expect && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all -z >actual && + test_cmp expect actual +' + +test_expect_success 'make second commit, confirm clean and new HEAD oid' ' + git commit -m second && + H1=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H1 + # branch.head master + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'confirm ignored files are not printed' ' + test_when_finished "rm -f x.ign .gitignore" && + echo x.ign >.gitignore && + echo "ignore me" >x.ign && + + cat >expect <<-EOF && + ? .gitignore + ? actual + ? expect + EOF + + git status --porcelain=v2 --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'ignored files are printed with --ignored' ' + test_when_finished "rm -f x.ign .gitignore" && + echo x.ign >.gitignore && + echo "ignore me" >x.ign && + + cat >expect <<-EOF && + ? .gitignore + ? actual + ? expect + ! x.ign + EOF + + git status --porcelain=v2 --ignored --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'create and commit permanent ignore file' ' + cat >.gitignore <<-EOF && + actual* + expect* + EOF + + git add .gitignore && + git commit -m ignore_trash && + H1=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H1 + # branch.head master + EOF + + git status --porcelain=v2 --branch >actual && + test_cmp expect actual +' + +test_expect_success 'verify --intent-to-add output' ' + test_when_finished "git rm -f intent1.add intent2.add" && + touch intent1.add && + echo test >intent2.add && + + git add --intent-to-add intent1.add intent2.add && + + cat >expect <<-EOF && + 1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent1.add + 1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent2.add + EOF + + git status --porcelain=v2 >actual && + test_cmp expect actual +' + +test_expect_success 'verify AA (add-add) conflict' ' + test_when_finished "git reset --hard" && + + git branch AA_A master && + git checkout AA_A && + echo "Branch AA_A" >conflict.txt && + OID_AA_A=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch aa_a" && + + git branch AA_B master && + git checkout AA_B && + echo "Branch AA_B" >conflict.txt && + OID_AA_B=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch aa_b" && + + git branch AA_M AA_B && + git checkout AA_M && + test_must_fail git merge AA_A && + + HM=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HM + # branch.head AA_M + u AA N... 000000 100644 100644 100644 $_z40 $OID_AA_B $OID_AA_A conflict.txt + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'verify UU (edit-edit) conflict' ' + test_when_finished "git reset --hard" && + + git branch UU_ANC master && + git checkout UU_ANC && + echo "Ancestor" >conflict.txt && + OID_UU_ANC=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "UU_ANC" && + + git branch UU_A UU_ANC && + git checkout UU_A && + echo "Branch UU_A" >conflict.txt && + OID_UU_A=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch uu_a" && + + git branch UU_B UU_ANC && + git checkout UU_B && + echo "Branch UU_B" >conflict.txt && + OID_UU_B=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch uu_b" && + + git branch UU_M UU_B && + git checkout UU_M && + test_must_fail git merge UU_A && + + HM=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HM + # branch.head UU_M + u UU N... 100644 100644 100644 100644 $OID_UU_ANC $OID_UU_B $OID_UU_A conflict.txt + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'verify upstream fields in branch header' ' + git checkout master && + test_when_finished "rm -rf sub_repo" && + git clone . sub_repo && + ( + ## Confirm local master tracks remote master. + cd sub_repo && + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + ## Test ahead/behind. + echo xyz >file_xyz && + git add file_xyz && + git commit -m xyz && + + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + ## Repeat the above but without --branch. + cat >expect <<-EOF && + EOF + + git status --porcelain=v2 --untracked-files=all >actual && + test_cmp expect actual && + + ## Test upstream-gone case. Fake this by pointing origin/master at + ## a non-existing commit. + OLD=$(git rev-parse origin/master) && + NEW=$_z40 && + mv .git/packed-refs .git/old-packed-refs && + sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs && + + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'create and add submodule, submodule appears clean (A. S...)' ' + git checkout master && + git clone . sub_repo && + git clone . super_repo && + ( cd super_repo && + git submodule add ../sub_repo sub1 && + + ## Confirm stage/add of clean submodule. + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 A. S... 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'untracked changes in added submodule (AM S..U)' ' + ( cd super_repo && + ## create untracked file in the submodule. + ( cd sub1 && + echo "xxxx" >file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S..U 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged changes in added submodule (AM S.M.)' ' + ( cd super_repo && + ## stage the changes in the submodule. + ( cd sub1 && + git add file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged and unstaged changes in added (AM S.M.)' ' + ( cd super_repo && + ( cd sub1 && + ## make additional unstaged changes (on the same file) in the submodule. + ## This does not cause us to get S.MU (because the submodule does not report + ## a "?" line for the unstaged changes). + echo "more changes" >>file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged and untracked changes in added submodule (AM S.MU)' ' + ( cd super_repo && + ( cd sub1 && + ## stage new changes in tracked file. + git add file_in_sub && + ## create new untracked file. + echo "yyyy" >>another_file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.MU 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'commit within the submodule appears as new commit in super (AM SC..)' ' + ( cd super_repo && + ( cd sub1 && + ## Make a new commit in the submodule. + git add file_in_sub && + rm -f another_file_in_sub && + git commit -m "new commit" + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM SC.. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'stage submodule in super and commit' ' + ( cd super_repo && + ## Stage the new submodule commit in the super. + git add sub1 && + ## Commit the super so that the sub no longer appears as added. + git commit -m "super commit" && + + HSUP=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'make unstaged changes in existing submodule (.M S.M.)' ' + ( cd super_repo && + ( cd sub1 && + echo "zzzz" >>file_in_sub + ) && + + HSUP=$(git rev-parse HEAD) && + HSUB=$(cd sub1 && git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + 1 .M S.M. 160000 160000 160000 $HSUB $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index eaea19b8f2..1c1e289ffd 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -8,74 +8,121 @@ test_description='test clone --reference' base_dir=$(pwd) -U=$base_dir/UPLOAD_LOG - -test_expect_success 'preparing first repository' \ -'test_create_repo A && cd A && -echo first > file1 && -git add file1 && -git commit -m A-initial' - -cd "$base_dir" - -test_expect_success 'preparing second repository' \ -'git clone A B && cd B && -echo second > file2 && -git add file2 && -git commit -m B-addition && -git repack -a -d && -git prune' - -cd "$base_dir" - -test_expect_success 'preparing superproject' \ -'test_create_repo super && cd super && -echo file > file && -git add file && -git commit -m B-super-initial' - -cd "$base_dir" - -test_expect_success 'submodule add --reference' \ -'cd super && git submodule add --reference ../B "file://$base_dir/A" sub && -git commit -m B-super-added' - -cd "$base_dir" - -test_expect_success 'after add: existence of info/alternates' \ -'test_line_count = 1 super/.git/modules/sub/objects/info/alternates' - -cd "$base_dir" - -test_expect_success 'that reference gets used with add' \ -'cd super/sub && -echo "0 objects, 0 kilobytes" > expected && -git count-objects > current && -diff expected current' - -cd "$base_dir" - -test_expect_success 'cloning superproject' \ -'git clone super super-clone' - -cd "$base_dir" - -test_expect_success 'update with reference' \ -'cd super-clone && git submodule update --init --reference ../B' - -cd "$base_dir" - -test_expect_success 'after update: existence of info/alternates' \ -'test_line_count = 1 super-clone/.git/modules/sub/objects/info/alternates' - -cd "$base_dir" - -test_expect_success 'that reference gets used with update' \ -'cd super-clone/sub && -echo "0 objects, 0 kilobytes" > expected && -git count-objects > current && -diff expected current' - -cd "$base_dir" +test_alternate_is_used () { + alternates_file="$1" && + working_dir="$2" && + test_line_count = 1 "$alternates_file" && + echo "0 objects, 0 kilobytes" >expect && + git -C "$working_dir" count-objects >actual && + test_cmp expect actual +} + +test_expect_success 'preparing first repository' ' + test_create_repo A && + ( + cd A && + echo first >file1 && + git add file1 && + git commit -m A-initial + ) +' + +test_expect_success 'preparing second repository' ' + git clone A B && + ( + cd B && + echo second >file2 && + git add file2 && + git commit -m B-addition && + git repack -a -d && + git prune + ) +' + +test_expect_success 'preparing superproject' ' + test_create_repo super && + ( + cd super && + echo file >file && + git add file && + git commit -m B-super-initial + ) +' + +test_expect_success 'submodule add --reference uses alternates' ' + ( + cd super && + git submodule add --reference ../B "file://$base_dir/A" sub && + git commit -m B-super-added && + git repack -ad + ) && + test_alternate_is_used super/.git/modules/sub/objects/info/alternates super/sub +' + +test_expect_success 'that reference gets used with add' ' + ( + cd super/sub && + echo "0 objects, 0 kilobytes" >expected && + git count-objects >current && + diff expected current + ) +' + +# The tests up to this point, and repositories created by them +# (A, B, super and super/sub), are about setting up the stage +# for subsequent tests and meant to be kept throughout the +# remainder of the test. +# Tests from here on, if they create their own test repository, +# are expected to clean after themselves. + +test_expect_success 'updating superproject keeps alternates' ' + test_when_finished "rm -rf super-clone" && + git clone super super-clone && + git -C super-clone submodule update --init --reference ../B && + test_alternate_is_used super-clone/.git/modules/sub/objects/info/alternates super-clone/sub +' + +test_expect_success 'submodules use alternates when cloning a superproject' ' + test_when_finished "rm -rf super-clone" && + git clone --reference super --recursive super super-clone && + ( + cd super-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # test submodule has correct setup + test_alternate_is_used .git/modules/sub/objects/info/alternates sub + ) +' + +test_expect_success 'missing submodule alternate fails clone and submodule update' ' + test_when_finished "rm -rf super-clone" && + git clone super super2 && + test_must_fail git clone --recursive --reference super2 super2 super-clone && + ( + cd super-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # update of the submodule succeeds + test_must_fail git submodule update --init && + # and we have no alternates: + test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub && + test_must_fail test_path_is_file sub/file1 + ) +' + +test_expect_success 'ignoring missing submodule alternates passes clone and submodule update' ' + test_when_finished "rm -rf super-clone" && + git clone --reference-if-able super2 --recursive super2 super-clone && + ( + cd super-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # update of the submodule succeeds + git submodule update --init && + # and we have no alternates: + test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub && + test_path_is_file sub/file1 + ) +' test_done diff --git a/t/t7517-per-repo-email.sh b/t/t7517-per-repo-email.sh index 337e6e30c3..2a22fa7588 100755 --- a/t/t7517-per-repo-email.sh +++ b/t/t7517-per-repo-email.sh @@ -36,4 +36,51 @@ test_expect_success 'succeeds cloning if global email is not set' ' git clone . clone ' +test_expect_success 'set up rebase scenarios' ' + # temporarily enable an actual ident for this setup + test_config user.email foo@example.com && + test_commit new && + git branch side-without-commit HEAD^ && + git checkout -b side-with-commit HEAD^ && + test_commit side +' + +test_expect_success 'fast-forward rebase does not care about ident' ' + git checkout -B tmp side-without-commit && + git rebase master +' + +test_expect_success 'non-fast-forward rebase refuses to write commits' ' + test_when_finished "git rebase --abort || true" && + git checkout -B tmp side-with-commit && + test_must_fail git rebase master +' + +test_expect_success 'fast-forward rebase does not care about ident (interactive)' ' + git checkout -B tmp side-without-commit && + git rebase -i master +' + +test_expect_success 'non-fast-forward rebase refuses to write commits (interactive)' ' + test_when_finished "git rebase --abort || true" && + git checkout -B tmp side-with-commit && + test_must_fail git rebase -i master +' + +test_expect_success 'noop interactive rebase does not care about ident' ' + git checkout -B tmp side-with-commit && + git rebase -i HEAD^ +' + +test_expect_success 'fast-forward rebase does not care about ident (preserve)' ' + git checkout -B tmp side-without-commit && + git rebase -p master +' + +test_expect_success 'non-fast-forward rebase refuses to write commits (preserve)' ' + test_when_finished "git rebase --abort || true" && + git checkout -B tmp side-with-commit && + test_must_fail git rebase -p master +' + test_done diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh new file mode 100755 index 0000000000..d8242e467e --- /dev/null +++ b/t/t8010-cat-file-filters.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +test_description='git cat-file filters support' +. ./test-lib.sh + +test_expect_success 'setup ' ' + echo "*.txt eol=crlf diff=txt" >.gitattributes && + echo "hello" | append_cr >world.txt && + git add .gitattributes world.txt && + test_tick && + git commit -m "Initial commit" +' + +has_cr () { + tr '\015' Q <"$1" | grep Q >/dev/null +} + +test_expect_success 'no filters with `git show`' ' + git show HEAD:world.txt >actual && + ! has_cr actual + +' + +test_expect_success 'no filters with cat-file' ' + git cat-file blob HEAD:world.txt >actual && + ! has_cr actual +' + +test_expect_success 'cat-file --filters converts to worktree version' ' + git cat-file --filters HEAD:world.txt >actual && + has_cr actual +' + +test_expect_success 'cat-file --filters --path=<path> works' ' + sha1=$(git rev-parse -q --verify HEAD:world.txt) && + git cat-file --filters --path=world.txt $sha1 >actual && + has_cr actual +' + +test_expect_success 'cat-file --textconv --path=<path> works' ' + sha1=$(git rev-parse -q --verify HEAD:world.txt) && + test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" && + git cat-file --textconv --path=hello.txt $sha1 >rot13 && + test uryyb = "$(cat rot13 | remove_cr)" +' + +test_expect_success '--path=<path> complains without --textconv/--filters' ' + sha1=$(git rev-parse -q --verify HEAD:world.txt) && + test_must_fail git cat-file --path=hello.txt blob $sha1 >actual 2>err && + test ! -s actual && + grep "path.*needs.*filters" err +' + +test_expect_success 'cat-file --textconv --batch works' ' + sha1=$(git rev-parse -q --verify HEAD:world.txt) && + test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" && + printf "%s hello.txt\n%s hello\n" $sha1 $sha1 | + git cat-file --textconv --batch >actual && + printf "%s blob 6\nuryyb\r\n\n%s blob 6\nhello\n\n" \ + $sha1 $sha1 >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 0db4469c89..97c9b32c2e 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -177,7 +177,7 @@ test_expect_success 'prompt - interactive rebase' ' git checkout b1 && test_when_finished "git checkout master" && git rebase -i HEAD^ && - test_when_finished "git rebase --abort" + test_when_finished "git rebase --abort" && __git_ps1 >"$actual" && test_cmp expected "$actual" ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 4f7eadb596..fdaeb3a96b 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -81,6 +81,10 @@ test_decode_color () { ' } +lf_to_nul () { + perl -pe 'y/\012/\000/' +} + nul_to_q () { perl -pe 'y/\000/Q/' } diff --git a/t/test-lib.sh b/t/test-lib.sh index d731d66e36..11562bde10 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -89,6 +89,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' UNZIP PERF_ CURL_VERBOSE + TRACE_CURL )); my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); print join("\n", @vars); @@ -687,9 +688,9 @@ test_done () { test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" mkdir -p "$test_results_dir" base=${0##*/} - test_results_path="$test_results_dir/${base%.sh}-$$.counts" + test_results_path="$test_results_dir/${base%.sh}.counts" - cat >>"$test_results_path" <<-EOF + cat >"$test_results_path" <<-EOF total $test_count success $test_success fixed $test_fixed @@ -1072,6 +1073,10 @@ test_lazy_prereq NOT_ROOT ' test "$uid" != 0 ' +test_lazy_prereq JGIT ' + type jgit +' + # SANITY is about "can you correctly predict what the filesystem would # do by only looking at the permission bits of the files and # directories?" A typical example of !SANITY is running the test diff --git a/transport.c b/transport.c index cf8de6e888..94d6dc3725 100644 --- a/transport.c +++ b/transport.c @@ -771,7 +771,7 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing) fprintf(stderr, _("The following submodule paths contain changes that can\n" "not be found on any remote:\n")); for (i = 0; i < needs_pushing->nr; i++) - printf(" %s\n", needs_pushing->items[i].string); + fprintf(stderr, " %s\n", needs_pushing->items[i].string); fprintf(stderr, _("\nPlease try\n\n" " git push --recurse-submodules=on-demand\n\n" "or cd to the path and use\n\n" @@ -26,7 +26,7 @@ static int read_one_entry_opt(const unsigned char *sha1, const char *base, int b ce->ce_namelen = baselen + len; memcpy(ce->name, base, baselen); memcpy(ce->name + baselen, pathname, len+1); - hashcpy(ce->sha1, sha1); + hashcpy(ce->oid.hash, sha1); return add_cache_entry(ce, opt); } diff --git a/unpack-trees.c b/unpack-trees.c index 11c37fbc58..3db3f02577 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -123,9 +123,9 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] = _("Cannot update sparse checkout: the following entries are not up-to-date:\n%s"); msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] = - _("The following Working tree files would be overwritten by sparse checkout update:\n%s"); + _("The following working tree files would be overwritten by sparse checkout update:\n%s"); msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] = - _("The following Working tree files would be removed by sparse checkout update:\n%s"); + _("The following working tree files would be removed by sparse checkout update:\n%s"); opts->show_all_errors = 1; /* rejected paths may not have a static buffer */ @@ -218,8 +218,8 @@ static void unlink_entry(const struct cache_entry *ce) schedule_dir_for_removal(ce->name, ce_namelen(ce)); } -static struct checkout state; -static int check_updates(struct unpack_trees_options *o) +static int check_updates(struct unpack_trees_options *o, + const struct checkout *state) { unsigned cnt = 0, total = 0; struct progress *progress = NULL; @@ -264,7 +264,7 @@ static int check_updates(struct unpack_trees_options *o) display_progress(progress, ++cnt); ce->ce_flags &= ~CE_UPDATE; if (o->update && !o->dry_run) { - errs |= checkout_entry(ce, &state, NULL); + errs |= checkout_entry(ce, state, NULL); } } } @@ -625,7 +625,7 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, con ce->ce_mode = create_ce_mode(n->mode); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = len; - hashcpy(ce->sha1, n->oid->hash); + oidcpy(&ce->oid, n->oid); make_traverse_path(ce->name, info, n); return ce; @@ -1094,6 +1094,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options int i, ret; static struct cache_entry *dfc; struct exclude_list el; + struct checkout state; if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); @@ -1239,7 +1240,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } o->src_index = NULL; - ret = check_updates(o) ? (-2) : 0; + ret = check_updates(o, &state) ? (-2) : 0; if (o->dst_index) { if (!ret) { if (!o->result.cache_tree) @@ -1287,7 +1288,7 @@ static int same(const struct cache_entry *a, const struct cache_entry *b) if ((a->ce_flags | b->ce_flags) & CE_CONFLICTED) return 0; return a->ce_mode == b->ce_mode && - !hashcmp(a->sha1, b->sha1); + !oidcmp(&a->oid, &b->oid); } @@ -1393,7 +1394,7 @@ static int verify_clean_subdirectory(const struct cache_entry *ce, /* If we are not going to update the submodule, then * we don't care. */ - if (!hashcmp(sha1, ce->sha1)) + if (!hashcmp(sha1, ce->oid.hash)) return 0; return verify_clean_submodule(ce, error_type, o); } @@ -1665,7 +1666,7 @@ static void show_stage_entry(FILE *o, fprintf(o, "%s%06o %s %d\t%s\n", label, ce->ce_mode, - sha1_to_hex(ce->sha1), + oid_to_hex(&ce->oid), ce_stage(ce), ce->name); } @@ -29,25 +29,6 @@ int is_url(const char *url) return (url[0] == ':' && url[1] == '/' && url[2] == '/'); } -static int url_decode_char(const char *q) -{ - int i; - unsigned char val = 0; - for (i = 0; i < 2; i++) { - unsigned char c = *q++; - val <<= 4; - if (c >= '0' && c <= '9') - val += c - '0'; - else if (c >= 'a' && c <= 'f') - val += c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - val += c - 'A' + 10; - else - return -1; - } - return val; -} - static char *url_decode_internal(const char **query, int len, const char *stop_at, struct strbuf *out, int decode_plus) @@ -66,7 +47,7 @@ static char *url_decode_internal(const char **query, int len, } if (c == '%') { - int val = url_decode_char(q + 1); + int val = hex2chr(q + 1); if (0 <= val) { strbuf_addch(out, val); q += 3; @@ -70,6 +70,21 @@ void set_error_routine(void (*routine)(const char *err, va_list params)) error_routine = routine; } +void (*get_error_routine(void))(const char *err, va_list params) +{ + return error_routine; +} + +void set_warn_routine(void (*routine)(const char *warn, va_list params)) +{ + warn_routine = routine; +} + +void (*get_warn_routine(void))(const char *warn, va_list params) +{ + return warn_routine; +} + void set_die_is_recursing_routine(int (*routine)(void)) { die_is_recursing = routine; @@ -148,6 +163,7 @@ void NORETURN die_errno(const char *fmt, ...) va_end(params); } +#undef error_errno int error_errno(const char *fmt, ...) { char buf[1024]; diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c index bd0f2c2b86..97cba39cdf 100644 --- a/vcs-svn/fast_export.c +++ b/vcs-svn/fast_export.c @@ -73,7 +73,7 @@ void fast_export_begin_note(uint32_t revision, const char *author, static int firstnote = 1; size_t loglen = strlen(log); printf("commit %s\n", note_ref); - printf("committer %s <%s@%s> %ld +0000\n", author, author, "local", timestamp); + printf("committer %s <%s@%s> %lu +0000\n", author, author, "local", timestamp); printf("data %"PRIuMAX"\n", (uintmax_t)loglen); fwrite(log, loglen, 1, stdout); if (firstnote) { @@ -107,7 +107,7 @@ void fast_export_begin_commit(uint32_t revision, const char *author, } printf("commit %s\n", local_ref); printf("mark :%"PRIu32"\n", revision); - printf("committer %s <%s@%s> %ld +0000\n", + printf("committer %s <%s@%s> %lu +0000\n", *author ? author : "nobody", *author ? author : "nobody", *uuid ? uuid : "local", timestamp); diff --git a/wt-status.c b/wt-status.c index 6225a2d89f..9628c1d5d7 100644 --- a/wt-status.c +++ b/wt-status.c @@ -139,7 +139,7 @@ void wt_status_prepare(struct wt_status *s) s->display_comment_prefix = 0; } -static void wt_status_print_unmerged_header(struct wt_status *s) +static void wt_longstatus_print_unmerged_header(struct wt_status *s) { int i; int del_mod_conflict = 0; @@ -191,7 +191,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s) status_printf_ln(s, c, "%s", ""); } -static void wt_status_print_cached_header(struct wt_status *s) +static void wt_longstatus_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); @@ -207,9 +207,9 @@ static void wt_status_print_cached_header(struct wt_status *s) status_printf_ln(s, c, "%s", ""); } -static void wt_status_print_dirty_header(struct wt_status *s, - int has_deleted, - int has_dirty_submodules) +static void wt_longstatus_print_dirty_header(struct wt_status *s, + int has_deleted, + int has_dirty_submodules) { const char *c = color(WT_STATUS_HEADER, s); @@ -226,9 +226,9 @@ static void wt_status_print_dirty_header(struct wt_status *s, status_printf_ln(s, c, "%s", ""); } -static void wt_status_print_other_header(struct wt_status *s, - const char *what, - const char *how) +static void wt_longstatus_print_other_header(struct wt_status *s, + const char *what, + const char *how) { const char *c = color(WT_STATUS_HEADER, s); status_printf_ln(s, c, "%s:", what); @@ -238,7 +238,7 @@ static void wt_status_print_other_header(struct wt_status *s, status_printf_ln(s, c, "%s", ""); } -static void wt_status_print_trailer(struct wt_status *s) +static void wt_longstatus_print_trailer(struct wt_status *s) { status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", ""); } @@ -304,8 +304,8 @@ static int maxwidth(const char *(*label)(int), int minval, int maxval) return result; } -static void wt_status_print_unmerged_data(struct wt_status *s, - struct string_list_item *it) +static void wt_longstatus_print_unmerged_data(struct wt_status *s, + struct string_list_item *it) { const char *c = color(WT_STATUS_UNMERGED, s); struct wt_status_change_data *d = it->util; @@ -331,9 +331,9 @@ static void wt_status_print_unmerged_data(struct wt_status *s, strbuf_release(&onebuf); } -static void wt_status_print_change_data(struct wt_status *s, - int change_type, - struct string_list_item *it) +static void wt_longstatus_print_change_data(struct wt_status *s, + int change_type, + struct string_list_item *it) { struct wt_status_change_data *d = it->util; const char *c = color(change_type, s); @@ -367,18 +367,18 @@ static void wt_status_print_change_data(struct wt_status *s, if (d->new_submodule_commits || d->dirty_submodule) { strbuf_addstr(&extra, " ("); if (d->new_submodule_commits) - strbuf_addf(&extra, _("new commits, ")); + strbuf_addstr(&extra, _("new commits, ")); if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) - strbuf_addf(&extra, _("modified content, ")); + strbuf_addstr(&extra, _("modified content, ")); if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) - strbuf_addf(&extra, _("untracked content, ")); + strbuf_addstr(&extra, _("untracked content, ")); strbuf_setlen(&extra, extra.len - 2); strbuf_addch(&extra, ')'); } status = d->worktree_status; break; default: - die("BUG: unhandled change_type %d in wt_status_print_change_data", + die("BUG: unhandled change_type %d in wt_longstatus_print_change_data", change_type); } @@ -434,6 +434,31 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, if (S_ISGITLINK(p->two->mode)) d->new_submodule_commits = !!oidcmp(&p->one->oid, &p->two->oid); + + switch (p->status) { + case DIFF_STATUS_ADDED: + die("BUG: worktree status add???"); + break; + + case DIFF_STATUS_DELETED: + d->mode_index = p->one->mode; + oidcpy(&d->oid_index, &p->one->oid); + /* mode_worktree is zero for a delete. */ + break; + + case DIFF_STATUS_MODIFIED: + case DIFF_STATUS_TYPE_CHANGED: + case DIFF_STATUS_UNMERGED: + d->mode_index = p->one->mode; + d->mode_worktree = p->two->mode; + oidcpy(&d->oid_index, &p->one->oid); + break; + + case DIFF_STATUS_UNKNOWN: + die("BUG: worktree status unknown???"); + break; + } + } } @@ -479,12 +504,36 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q, if (!d->index_status) d->index_status = p->status; switch (p->status) { + case DIFF_STATUS_ADDED: + /* Leave {mode,oid}_head zero for an add. */ + d->mode_index = p->two->mode; + oidcpy(&d->oid_index, &p->two->oid); + break; + case DIFF_STATUS_DELETED: + d->mode_head = p->one->mode; + oidcpy(&d->oid_head, &p->one->oid); + /* Leave {mode,oid}_index zero for a delete. */ + break; + case DIFF_STATUS_COPIED: case DIFF_STATUS_RENAMED: d->head_path = xstrdup(p->one->path); + d->score = p->score * 100 / MAX_SCORE; + /* fallthru */ + case DIFF_STATUS_MODIFIED: + case DIFF_STATUS_TYPE_CHANGED: + d->mode_head = p->one->mode; + d->mode_index = p->two->mode; + oidcpy(&d->oid_head, &p->one->oid); + oidcpy(&d->oid_index, &p->two->oid); break; case DIFF_STATUS_UNMERGED: d->stagemask = unmerged_mask(p->two->path); + /* + * Don't bother setting {mode,oid}_{head,index} since the print + * code will output the stage values directly and not use the + * values in these fields. + */ break; } } @@ -565,9 +614,17 @@ static void wt_status_collect_changes_initial(struct wt_status *s) if (ce_stage(ce)) { d->index_status = DIFF_STATUS_UNMERGED; d->stagemask |= (1 << (ce_stage(ce) - 1)); - } - else + /* + * Don't bother setting {mode,oid}_{head,index} since the print + * code will output the stage values directly and not use the + * values in these fields. + */ + } else { d->index_status = DIFF_STATUS_ADDED; + /* Leave {mode,oid}_head zero for adds. */ + d->mode_index = ce->ce_mode; + hashcpy(d->oid_index.hash, ce->oid.hash); + } } } @@ -627,7 +684,7 @@ void wt_status_collect(struct wt_status *s) wt_status_collect_untracked(s); } -static void wt_status_print_unmerged(struct wt_status *s) +static void wt_longstatus_print_unmerged(struct wt_status *s) { int shown_header = 0; int i; @@ -640,17 +697,17 @@ static void wt_status_print_unmerged(struct wt_status *s) if (!d->stagemask) continue; if (!shown_header) { - wt_status_print_unmerged_header(s); + wt_longstatus_print_unmerged_header(s); shown_header = 1; } - wt_status_print_unmerged_data(s, it); + wt_longstatus_print_unmerged_data(s, it); } if (shown_header) - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } -static void wt_status_print_updated(struct wt_status *s) +static void wt_longstatus_print_updated(struct wt_status *s) { int shown_header = 0; int i; @@ -664,14 +721,14 @@ static void wt_status_print_updated(struct wt_status *s) d->index_status == DIFF_STATUS_UNMERGED) continue; if (!shown_header) { - wt_status_print_cached_header(s); + wt_longstatus_print_cached_header(s); s->commitable = 1; shown_header = 1; } - wt_status_print_change_data(s, WT_STATUS_UPDATED, it); + wt_longstatus_print_change_data(s, WT_STATUS_UPDATED, it); } if (shown_header) - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } /* @@ -703,7 +760,7 @@ static int wt_status_check_worktree_changes(struct wt_status *s, return changes; } -static void wt_status_print_changed(struct wt_status *s) +static void wt_longstatus_print_changed(struct wt_status *s) { int i, dirty_submodules; int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules); @@ -711,7 +768,7 @@ static void wt_status_print_changed(struct wt_status *s) if (!worktree_changes) return; - wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules); + wt_longstatus_print_dirty_header(s, worktree_changes < 0, dirty_submodules); for (i = 0; i < s->change.nr; i++) { struct wt_status_change_data *d; @@ -721,12 +778,12 @@ static void wt_status_print_changed(struct wt_status *s) if (!d->worktree_status || d->worktree_status == DIFF_STATUS_UNMERGED) continue; - wt_status_print_change_data(s, WT_STATUS_CHANGED, it); + wt_longstatus_print_change_data(s, WT_STATUS_CHANGED, it); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } -static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted) +static void wt_longstatus_print_submodule_summary(struct wt_status *s, int uncommitted) { struct child_process sm_summary = CHILD_PROCESS_INIT; struct strbuf cmd_stdout = STRBUF_INIT; @@ -772,10 +829,10 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt strbuf_release(&summary); } -static void wt_status_print_other(struct wt_status *s, - struct string_list *l, - const char *what, - const char *how) +static void wt_longstatus_print_other(struct wt_status *s, + struct string_list *l, + const char *what, + const char *how) { int i; struct strbuf buf = STRBUF_INIT; @@ -785,7 +842,7 @@ static void wt_status_print_other(struct wt_status *s, if (!l->nr) return; - wt_status_print_other_header(s, what, how); + wt_longstatus_print_other_header(s, what, how); for (i = 0; i < l->nr; i++) { struct string_list_item *it; @@ -845,7 +902,7 @@ void wt_status_add_cut_line(FILE *fp) strbuf_release(&buf); } -static void wt_status_print_verbose(struct wt_status *s) +static void wt_longstatus_print_verbose(struct wt_status *s) { struct rev_info rev; struct setup_revision_opt opt; @@ -878,7 +935,7 @@ static void wt_status_print_verbose(struct wt_status *s) if (s->verbose > 1 && s->commitable) { /* print_updated() printed a header, so do we */ if (s->fp != stdout) - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); status_printf_ln(s, c, _("Changes to be committed:")); rev.diffopt.a_prefix = "c/"; rev.diffopt.b_prefix = "i/"; @@ -896,7 +953,7 @@ static void wt_status_print_verbose(struct wt_status *s) } } -static void wt_status_print_tracking(struct wt_status *s) +static void wt_longstatus_print_tracking(struct wt_status *s) { struct strbuf sb = STRBUF_INIT; const char *cp, *ep, *branch_name; @@ -962,7 +1019,7 @@ static void show_merge_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git commit\" to conclude merge)")); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static void show_am_in_progress(struct wt_status *s, @@ -983,7 +1040,7 @@ static void show_am_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git am --abort\" to restore the original branch)")); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static char *read_line_from_git_path(const char *filename) @@ -1207,7 +1264,7 @@ static void show_rebase_in_progress(struct wt_status *s, _(" (use \"git rebase --continue\" once you are satisfied with your changes)")); } } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static void show_cherry_pick_in_progress(struct wt_status *s, @@ -1226,7 +1283,7 @@ static void show_cherry_pick_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)")); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static void show_revert_in_progress(struct wt_status *s, @@ -1245,7 +1302,7 @@ static void show_revert_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git revert --abort\" to cancel the revert operation)")); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static void show_bisect_in_progress(struct wt_status *s, @@ -1262,7 +1319,7 @@ static void show_bisect_in_progress(struct wt_status *s, if (s->hints) status_printf_ln(s, color, _(" (use \"git bisect reset\" to get back to the original branch)")); - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } /* @@ -1432,8 +1489,8 @@ void wt_status_get_state(struct wt_status_state *state, wt_status_get_detached_from(state); } -static void wt_status_print_state(struct wt_status *s, - struct wt_status_state *state) +static void wt_longstatus_print_state(struct wt_status *s, + struct wt_status_state *state) { const char *state_color = color(WT_STATUS_HEADER, s); if (state->merge_in_progress) @@ -1450,7 +1507,7 @@ static void wt_status_print_state(struct wt_status *s, show_bisect_in_progress(s, state, state_color); } -void wt_status_print(struct wt_status *s) +static void wt_longstatus_print(struct wt_status *s) { const char *branch_color = color(WT_STATUS_ONBRANCH, s); const char *branch_status_color = color(WT_STATUS_HEADER, s); @@ -1487,10 +1544,10 @@ void wt_status_print(struct wt_status *s) status_printf_more(s, branch_status_color, "%s", on_what); status_printf_more(s, branch_color, "%s\n", branch_name); if (!s->is_initial) - wt_status_print_tracking(s); + wt_longstatus_print_tracking(s); } - wt_status_print_state(s, &state); + wt_longstatus_print_state(s, &state); free(state.branch); free(state.onto); free(state.detached_from); @@ -1501,19 +1558,19 @@ void wt_status_print(struct wt_status *s) status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", ""); } - wt_status_print_updated(s); - wt_status_print_unmerged(s); - wt_status_print_changed(s); + wt_longstatus_print_updated(s); + wt_longstatus_print_unmerged(s); + wt_longstatus_print_changed(s); if (s->submodule_summary && (!s->ignore_submodule_arg || strcmp(s->ignore_submodule_arg, "all"))) { - wt_status_print_submodule_summary(s, 0); /* staged */ - wt_status_print_submodule_summary(s, 1); /* unstaged */ + wt_longstatus_print_submodule_summary(s, 0); /* staged */ + wt_longstatus_print_submodule_summary(s, 1); /* unstaged */ } if (s->show_untracked_files) { - wt_status_print_other(s, &s->untracked, _("Untracked files"), "add"); + wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add"); if (s->show_ignored_files) - wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f"); + wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f"); if (advice_status_u_option && 2000 < s->untracked_in_ms) { status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); status_printf_ln(s, GIT_COLOR_NORMAL, @@ -1528,7 +1585,7 @@ void wt_status_print(struct wt_status *s) ? _(" (use -u option to show untracked files)") : ""); if (s->verbose) - wt_status_print_verbose(s); + wt_longstatus_print_verbose(s); if (!s->commitable) { if (s->amend) status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes")); @@ -1717,7 +1774,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) fputc(s->null_termination ? '\0' : '\n', s->fp); } -void wt_shortstatus_print(struct wt_status *s) +static void wt_shortstatus_print(struct wt_status *s) { int i; @@ -1749,7 +1806,7 @@ void wt_shortstatus_print(struct wt_status *s) } } -void wt_porcelain_print(struct wt_status *s) +static void wt_porcelain_print(struct wt_status *s) { s->use_color = 0; s->relative_paths = 0; @@ -1757,3 +1814,398 @@ void wt_porcelain_print(struct wt_status *s) s->no_gettext = 1; wt_shortstatus_print(s); } + +/* + * Print branch information for porcelain v2 output. These lines + * are printed when the '--branch' parameter is given. + * + * # branch.oid <commit><eol> + * # branch.head <head><eol> + * [# branch.upstream <upstream><eol> + * [# branch.ab +<ahead> -<behind><eol>]] + * + * <commit> ::= the current commit hash or the the literal + * "(initial)" to indicate an initialized repo + * with no commits. + * + * <head> ::= <branch_name> the current branch name or + * "(detached)" literal when detached head or + * "(unknown)" when something is wrong. + * + * <upstream> ::= the upstream branch name, when set. + * + * <ahead> ::= integer ahead value, when upstream set + * and the commit is present (not gone). + * + * <behind> ::= integer behind value, when upstream set + * and commit is present. + * + * + * The end-of-line is defined by the -z flag. + * + * <eol> ::= NUL when -z, + * LF when NOT -z. + * + */ +static void wt_porcelain_v2_print_tracking(struct wt_status *s) +{ + struct branch *branch; + const char *base; + const char *branch_name; + struct wt_status_state state; + int ab_info, nr_ahead, nr_behind; + char eol = s->null_termination ? '\0' : '\n'; + + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, s->branch && !strcmp(s->branch, "HEAD")); + + fprintf(s->fp, "# branch.oid %s%c", + (s->is_initial ? "(initial)" : sha1_to_hex(s->sha1_commit)), + eol); + + if (!s->branch) + fprintf(s->fp, "# branch.head %s%c", "(unknown)", eol); + else { + if (!strcmp(s->branch, "HEAD")) { + fprintf(s->fp, "# branch.head %s%c", "(detached)", eol); + + if (state.rebase_in_progress || state.rebase_interactive_in_progress) + branch_name = state.onto; + else if (state.detached_from) + branch_name = state.detached_from; + else + branch_name = ""; + } else { + branch_name = NULL; + skip_prefix(s->branch, "refs/heads/", &branch_name); + + fprintf(s->fp, "# branch.head %s%c", branch_name, eol); + } + + /* Lookup stats on the upstream tracking branch, if set. */ + branch = branch_get(branch_name); + base = NULL; + ab_info = (stat_tracking_info(branch, &nr_ahead, &nr_behind, &base) == 0); + if (base) { + base = shorten_unambiguous_ref(base, 0); + fprintf(s->fp, "# branch.upstream %s%c", base, eol); + free((char *)base); + + if (ab_info) + fprintf(s->fp, "# branch.ab +%d -%d%c", nr_ahead, nr_behind, eol); + } + } + + free(state.branch); + free(state.onto); + free(state.detached_from); +} + +/* + * Convert various submodule status values into a + * fixed-length string of characters in the buffer provided. + */ +static void wt_porcelain_v2_submodule_state( + struct wt_status_change_data *d, + char sub[5]) +{ + if (S_ISGITLINK(d->mode_head) || + S_ISGITLINK(d->mode_index) || + S_ISGITLINK(d->mode_worktree)) { + sub[0] = 'S'; + sub[1] = d->new_submodule_commits ? 'C' : '.'; + sub[2] = (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) ? 'M' : '.'; + sub[3] = (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) ? 'U' : '.'; + } else { + sub[0] = 'N'; + sub[1] = '.'; + sub[2] = '.'; + sub[3] = '.'; + } + sub[4] = 0; +} + +/* + * Fix-up changed entries before we print them. + */ +static void wt_porcelain_v2_fix_up_changed( + struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + + if (!d->index_status) { + /* + * This entry is unchanged in the index (relative to the head). + * Therefore, the collect_updated_cb was never called for this + * entry (during the head-vs-index scan) and so the head column + * fields were never set. + * + * We must have data for the index column (from the + * index-vs-worktree scan (otherwise, this entry should not be + * in the list of changes)). + * + * Copy index column fields to the head column, so that our + * output looks complete. + */ + assert(d->mode_head == 0); + d->mode_head = d->mode_index; + oidcpy(&d->oid_head, &d->oid_index); + } + + if (!d->worktree_status) { + /* + * This entry is unchanged in the worktree (relative to the index). + * Therefore, the collect_changed_cb was never called for this entry + * (during the index-vs-worktree scan) and so the worktree column + * fields were never set. + * + * We must have data for the index column (from the head-vs-index + * scan). + * + * Copy the index column fields to the worktree column so that + * our output looks complete. + * + * Note that we only have a mode field in the worktree column + * because the scan code tries really hard to not have to compute it. + */ + assert(d->mode_worktree == 0); + d->mode_worktree = d->mode_index; + } +} + +/* + * Print porcelain v2 info for tracked entries with changes. + */ +static void wt_porcelain_v2_print_changed_entry( + struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + struct strbuf buf_index = STRBUF_INIT; + struct strbuf buf_head = STRBUF_INIT; + const char *path_index = NULL; + const char *path_head = NULL; + char key[3]; + char submodule_token[5]; + char sep_char, eol_char; + + wt_porcelain_v2_fix_up_changed(it, s); + wt_porcelain_v2_submodule_state(d, submodule_token); + + key[0] = d->index_status ? d->index_status : '.'; + key[1] = d->worktree_status ? d->worktree_status : '.'; + key[2] = 0; + + if (s->null_termination) { + /* + * In -z mode, we DO NOT C-quote pathnames. Current path is ALWAYS first. + * A single NUL character separates them. + */ + sep_char = '\0'; + eol_char = '\0'; + path_index = it->string; + path_head = d->head_path; + } else { + /* + * Path(s) are C-quoted if necessary. Current path is ALWAYS first. + * The source path is only present when necessary. + * A single TAB separates them (because paths can contain spaces + * which are not escaped and C-quoting does escape TAB characters). + */ + sep_char = '\t'; + eol_char = '\n'; + path_index = quote_path(it->string, s->prefix, &buf_index); + if (d->head_path) + path_head = quote_path(d->head_path, s->prefix, &buf_head); + } + + if (path_head) + fprintf(s->fp, "2 %s %s %06o %06o %06o %s %s %c%d %s%c%s%c", + key, submodule_token, + d->mode_head, d->mode_index, d->mode_worktree, + oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index), + key[0], d->score, + path_index, sep_char, path_head, eol_char); + else + fprintf(s->fp, "1 %s %s %06o %06o %06o %s %s %s%c", + key, submodule_token, + d->mode_head, d->mode_index, d->mode_worktree, + oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index), + path_index, eol_char); + + strbuf_release(&buf_index); + strbuf_release(&buf_head); +} + +/* + * Print porcelain v2 status info for unmerged entries. + */ +static void wt_porcelain_v2_print_unmerged_entry( + struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + const struct cache_entry *ce; + struct strbuf buf_index = STRBUF_INIT; + const char *path_index = NULL; + int pos, stage, sum; + struct { + int mode; + struct object_id oid; + } stages[3]; + char *key; + char submodule_token[5]; + char unmerged_prefix = 'u'; + char eol_char = s->null_termination ? '\0' : '\n'; + + wt_porcelain_v2_submodule_state(d, submodule_token); + + switch (d->stagemask) { + case 1: key = "DD"; break; /* both deleted */ + case 2: key = "AU"; break; /* added by us */ + case 3: key = "UD"; break; /* deleted by them */ + case 4: key = "UA"; break; /* added by them */ + case 5: key = "DU"; break; /* deleted by us */ + case 6: key = "AA"; break; /* both added */ + case 7: key = "UU"; break; /* both modified */ + default: + die("BUG: unhandled unmerged status %x", d->stagemask); + } + + /* + * Disregard d.aux.porcelain_v2 data that we accumulated + * for the head and index columns during the scans and + * replace with the actual stage data. + * + * Note that this is a last-one-wins for each the individual + * stage [123] columns in the event of multiple cache entries + * for same stage. + */ + memset(stages, 0, sizeof(stages)); + sum = 0; + pos = cache_name_pos(it->string, strlen(it->string)); + assert(pos < 0); + pos = -pos-1; + while (pos < active_nr) { + ce = active_cache[pos++]; + stage = ce_stage(ce); + if (strcmp(ce->name, it->string) || !stage) + break; + stages[stage - 1].mode = ce->ce_mode; + hashcpy(stages[stage - 1].oid.hash, ce->oid.hash); + sum |= (1 << (stage - 1)); + } + if (sum != d->stagemask) + die("BUG: observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask); + + if (s->null_termination) + path_index = it->string; + else + path_index = quote_path(it->string, s->prefix, &buf_index); + + fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c", + unmerged_prefix, key, submodule_token, + stages[0].mode, /* stage 1 */ + stages[1].mode, /* stage 2 */ + stages[2].mode, /* stage 3 */ + d->mode_worktree, + oid_to_hex(&stages[0].oid), /* stage 1 */ + oid_to_hex(&stages[1].oid), /* stage 2 */ + oid_to_hex(&stages[2].oid), /* stage 3 */ + path_index, + eol_char); + + strbuf_release(&buf_index); +} + +/* + * Print porcelain V2 status info for untracked and ignored entries. + */ +static void wt_porcelain_v2_print_other( + struct string_list_item *it, + struct wt_status *s, + char prefix) +{ + struct strbuf buf = STRBUF_INIT; + const char *path; + char eol_char; + + if (s->null_termination) { + path = it->string; + eol_char = '\0'; + } else { + path = quote_path(it->string, s->prefix, &buf); + eol_char = '\n'; + } + + fprintf(s->fp, "%c %s%c", prefix, path, eol_char); + + strbuf_release(&buf); +} + +/* + * Print porcelain V2 status. + * + * [<v2_branch>] + * [<v2_changed_items>]* + * [<v2_unmerged_items>]* + * [<v2_untracked_items>]* + * [<v2_ignored_items>]* + * + */ +static void wt_porcelain_v2_print(struct wt_status *s) +{ + struct wt_status_change_data *d; + struct string_list_item *it; + int i; + + if (s->show_branch) + wt_porcelain_v2_print_tracking(s); + + for (i = 0; i < s->change.nr; i++) { + it = &(s->change.items[i]); + d = it->util; + if (!d->stagemask) + wt_porcelain_v2_print_changed_entry(it, s); + } + + for (i = 0; i < s->change.nr; i++) { + it = &(s->change.items[i]); + d = it->util; + if (d->stagemask) + wt_porcelain_v2_print_unmerged_entry(it, s); + } + + for (i = 0; i < s->untracked.nr; i++) { + it = &(s->untracked.items[i]); + wt_porcelain_v2_print_other(it, s, '?'); + } + + for (i = 0; i < s->ignored.nr; i++) { + it = &(s->ignored.items[i]); + wt_porcelain_v2_print_other(it, s, '!'); + } +} + +void wt_status_print(struct wt_status *s) +{ + switch (s->status_format) { + case STATUS_FORMAT_SHORT: + wt_shortstatus_print(s); + break; + case STATUS_FORMAT_PORCELAIN: + wt_porcelain_print(s); + break; + case STATUS_FORMAT_PORCELAIN_V2: + wt_porcelain_v2_print(s); + break; + case STATUS_FORMAT_UNSPECIFIED: + die("BUG: finalize_deferred_config() should have been called"); + break; + case STATUS_FORMAT_NONE: + case STATUS_FORMAT_LONG: + wt_longstatus_print(s); + break; + } +} diff --git a/wt-status.h b/wt-status.h index 2ca93f6957..e401837707 100644 --- a/wt-status.h +++ b/wt-status.h @@ -38,11 +38,24 @@ struct wt_status_change_data { int worktree_status; int index_status; int stagemask; + int score; + int mode_head, mode_index, mode_worktree; + struct object_id oid_head, oid_index; char *head_path; unsigned dirty_submodule : 2; unsigned new_submodule_commits : 1; }; +enum wt_status_format { + STATUS_FORMAT_NONE = 0, + STATUS_FORMAT_LONG, + STATUS_FORMAT_SHORT, + STATUS_FORMAT_PORCELAIN, + STATUS_FORMAT_PORCELAIN_V2, + + STATUS_FORMAT_UNSPECIFIED +}; + struct wt_status { int is_initial; char *branch; @@ -66,6 +79,9 @@ struct wt_status { int show_branch; int hints; + enum wt_status_format status_format; + unsigned char sha1_commit[GIT_SHA1_RAWSZ]; /* when not Initial */ + /* These are computed during processing of the individual sections */ int commitable; int workdir_dirty; @@ -107,9 +123,6 @@ int wt_status_check_rebase(const struct worktree *wt, int wt_status_check_bisect(const struct worktree *wt, struct wt_status_state *state); -void wt_shortstatus_print(struct wt_status *s); -void wt_porcelain_print(struct wt_status *s); - __attribute__((format (printf, 3, 4))) void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...); __attribute__((format (printf, 3, 4))) diff --git a/xdiff-interface.c b/xdiff-interface.c index f34ea762e4..3bfc69cade 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -178,20 +178,20 @@ int read_mmfile(mmfile_t *ptr, const char *filename) return 0; } -void read_mmblob(mmfile_t *ptr, const unsigned char *sha1) +void read_mmblob(mmfile_t *ptr, const struct object_id *oid) { unsigned long size; enum object_type type; - if (!hashcmp(sha1, null_sha1)) { + if (!oidcmp(oid, &null_oid)) { ptr->ptr = xstrdup(""); ptr->size = 0; return; } - ptr->ptr = read_sha1_file(sha1, &type, &size); + ptr->ptr = read_sha1_file(oid->hash, &type, &size); if (!ptr->ptr || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); + die("unable to read blob object %s", oid_to_hex(oid)); ptr->size = size; } diff --git a/xdiff-interface.h b/xdiff-interface.h index fbb5a1c394..6f6ba9095d 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -1,6 +1,7 @@ #ifndef XDIFF_INTERFACE_H #define XDIFF_INTERFACE_H +#include "cache.h" #include "xdiff/xdiff.h" /* @@ -20,7 +21,7 @@ int parse_hunk_header(char *line, int len, int *ob, int *on, int *nb, int *nn); int read_mmfile(mmfile_t *ptr, const char *filename); -void read_mmblob(mmfile_t *ptr, const unsigned char *sha1); +void read_mmblob(mmfile_t *ptr, const struct object_id *oid); int buffer_is_binary(const char *ptr, unsigned long size); extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags); diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 7423f77fc8..8db16d4ae6 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -42,6 +42,7 @@ extern "C" { #define XDF_IGNORE_BLANK_LINES (1 << 7) #define XDF_COMPACTION_HEURISTIC (1 << 8) +#define XDF_INDENT_HEURISTIC (1 << 9) #define XDL_EMIT_FUNCNAMES (1 << 0) #define XDL_EMIT_FUNCCONTEXT (1 << 2) diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index b3c6848875..67c1cccf08 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -400,138 +400,577 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -static int is_blank_line(xrecord_t **recs, long ix, long flags) +static int is_blank_line(xrecord_t *rec, long flags) { - return xdl_blankline(recs[ix]->ptr, recs[ix]->size, flags); + return xdl_blankline(rec->ptr, rec->size, flags); } -static int recs_match(xrecord_t **recs, long ixs, long ix, long flags) +static int recs_match(xrecord_t *rec1, xrecord_t *rec2, long flags) { - return (recs[ixs]->ha == recs[ix]->ha && - xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, - recs[ix]->ptr, recs[ix]->size, + return (rec1->ha == rec2->ha && + xdl_recmatch(rec1->ptr, rec1->size, + rec2->ptr, rec2->size, flags)); } -int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { - long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; - char *rchg = xdf->rchg, *rchgo = xdfo->rchg; - unsigned int blank_lines; - xrecord_t **recs = xdf->recs; +/* + * If a line is indented more than this, get_indent() just returns this value. + * This avoids having to do absurd amounts of work for data that are not + * human-readable text, and also ensures that the output of get_indent fits within + * an int. + */ +#define MAX_INDENT 200 +/* + * Return the amount of indentation of the specified line, treating TAB as 8 + * columns. Return -1 if line is empty or contains only whitespace. Clamp the + * output value at MAX_INDENT. + */ +static int get_indent(xrecord_t *rec) +{ + long i; + int ret = 0; + + for (i = 0; i < rec->size; i++) { + char c = rec->ptr[i]; + + if (!XDL_ISSPACE(c)) + return ret; + else if (c == ' ') + ret += 1; + else if (c == '\t') + ret += 8 - ret % 8; + /* ignore other whitespace characters */ + + if (ret >= MAX_INDENT) + return MAX_INDENT; + } + + /* The line contains only whitespace. */ + return -1; +} + +/* + * If more than this number of consecutive blank rows are found, just return this + * value. This avoids requiring O(N^2) work for pathological cases, and also + * ensures that the output of score_split fits in an int. + */ +#define MAX_BLANKS 20 + +/* Characteristics measured about a hypothetical split position. */ +struct split_measurement { /* - * This is the same of what GNU diff does. Move back and forward - * change groups for a consistent and pretty diff output. This also - * helps in finding joinable change groups and reduce the diff size. + * Is the split at the end of the file (aside from any blank lines)? */ - for (ix = ixo = 0;;) { - /* - * Find the first changed line in the to-be-compacted file. - * We need to keep track of both indexes, so if we find a - * changed lines group on the other file, while scanning the - * to-be-compacted file, we need to skip it properly. Note - * that loops that are testing for changed lines on rchg* do - * not need index bounding since the array is prepared with - * a zero at position -1 and N. - */ - for (; ix < nrec && !rchg[ix]; ix++) - while (rchgo[ixo++]); - if (ix == nrec) + int end_of_file; + + /* + * How much is the line immediately following the split indented (or -1 if + * the line is blank): + */ + int indent; + + /* + * How many consecutive lines above the split are blank? + */ + int pre_blank; + + /* + * How much is the nearest non-blank line above the split indented (or -1 + * if there is no such line)? + */ + int pre_indent; + + /* + * How many lines after the line following the split are blank? + */ + int post_blank; + + /* + * How much is the nearest non-blank line after the line following the + * split indented (or -1 if there is no such line)? + */ + int post_indent; +}; + +struct split_score { + /* The effective indent of this split (smaller is preferred). */ + int effective_indent; + + /* Penalty for this split (smaller is preferred). */ + int penalty; +}; + +/* + * Fill m with information about a hypothetical split of xdf above line split. + */ +static void measure_split(const xdfile_t *xdf, long split, + struct split_measurement *m) +{ + long i; + + if (split >= xdf->nrec) { + m->end_of_file = 1; + m->indent = -1; + } else { + m->end_of_file = 0; + m->indent = get_indent(xdf->recs[split]); + } + + m->pre_blank = 0; + m->pre_indent = -1; + for (i = split - 1; i >= 0; i--) { + m->pre_indent = get_indent(xdf->recs[i]); + if (m->pre_indent != -1) + break; + m->pre_blank += 1; + if (m->pre_blank == MAX_BLANKS) { + m->pre_indent = 0; + break; + } + } + + m->post_blank = 0; + m->post_indent = -1; + for (i = split + 1; i < xdf->nrec; i++) { + m->post_indent = get_indent(xdf->recs[i]); + if (m->post_indent != -1) break; + m->post_blank += 1; + if (m->post_blank == MAX_BLANKS) { + m->post_indent = 0; + break; + } + } +} + +/* + * The empirically-determined weight factors used by score_split() below. + * Larger values means that the position is a less favorable place to split. + * + * Note that scores are only ever compared against each other, so multiplying + * all of these weight/penalty values by the same factor wouldn't change the + * heuristic's behavior. Still, we need to set that arbitrary scale *somehow*. + * In practice, these numbers are chosen to be large enough that they can be + * adjusted relative to each other with sufficient precision despite using + * integer math. + */ + +/* Penalty if there are no non-blank lines before the split */ +#define START_OF_FILE_PENALTY 1 + +/* Penalty if there are no non-blank lines after the split */ +#define END_OF_FILE_PENALTY 21 +/* Multiplier for the number of blank lines around the split */ +#define TOTAL_BLANK_WEIGHT (-30) + +/* Multiplier for the number of blank lines after the split */ +#define POST_BLANK_WEIGHT 6 + +/* + * Penalties applied if the line is indented more than its predecessor + */ +#define RELATIVE_INDENT_PENALTY (-4) +#define RELATIVE_INDENT_WITH_BLANK_PENALTY 10 + +/* + * Penalties applied if the line is indented less than both its predecessor and + * its successor + */ +#define RELATIVE_OUTDENT_PENALTY 24 +#define RELATIVE_OUTDENT_WITH_BLANK_PENALTY 17 + +/* + * Penalties applied if the line is indented less than its predecessor but not + * less than its successor + */ +#define RELATIVE_DEDENT_PENALTY 23 +#define RELATIVE_DEDENT_WITH_BLANK_PENALTY 17 + +/* + * We only consider whether the sum of the effective indents for splits are + * less than (-1), equal to (0), or greater than (+1) each other. The resulting + * value is multiplied by the following weight and combined with the penalty to + * determine the better of two scores. + */ +#define INDENT_WEIGHT 60 + +/* + * Compute a badness score for the hypothetical split whose measurements are + * stored in m. The weight factors were determined empirically using the tools and + * corpus described in + * + * https://github.com/mhagger/diff-slider-tools + * + * Also see that project if you want to improve the weights based on, for example, + * a larger or more diverse corpus. + */ +static void score_add_split(const struct split_measurement *m, struct split_score *s) +{ + /* + * A place to accumulate penalty factors (positive makes this index more + * favored): + */ + int post_blank, total_blank, indent, any_blanks; + + if (m->pre_indent == -1 && m->pre_blank == 0) + s->penalty += START_OF_FILE_PENALTY; + + if (m->end_of_file) + s->penalty += END_OF_FILE_PENALTY; + + /* + * Set post_blank to the number of blank lines following the split, + * including the line immediately after the split: + */ + post_blank = (m->indent == -1) ? 1 + m->post_blank : 0; + total_blank = m->pre_blank + post_blank; + + /* Penalties based on nearby blank lines: */ + s->penalty += TOTAL_BLANK_WEIGHT * total_blank; + s->penalty += POST_BLANK_WEIGHT * post_blank; + + if (m->indent != -1) + indent = m->indent; + else + indent = m->post_indent; + + any_blanks = (total_blank != 0); + + /* Note that the effective indent is -1 at the end of the file: */ + s->effective_indent += indent; + + if (indent == -1) { + /* No additional adjustments needed. */ + } else if (m->pre_indent == -1) { + /* No additional adjustments needed. */ + } else if (indent > m->pre_indent) { + /* + * The line is indented more than its predecessor. + */ + s->penalty += any_blanks ? + RELATIVE_INDENT_WITH_BLANK_PENALTY : + RELATIVE_INDENT_PENALTY; + } else if (indent == m->pre_indent) { + /* + * The line has the same indentation level as its predecessor. + * No additional adjustments needed. + */ + } else { /* - * Record the start of a changed-group in the to-be-compacted file - * and find the end of it, on both to-be-compacted and other file - * indexes (ix and ixo). + * The line is indented less than its predecessor. It could be + * the block terminator of the previous block, but it could + * also be the start of a new block (e.g., an "else" block, or + * maybe the previous block didn't have a block terminator). + * Try to distinguish those cases based on what comes next: */ - ixs = ix; - for (ix++; rchg[ix]; ix++); - for (; rchgo[ixo]; ixo++); + if (m->post_indent != -1 && m->post_indent > indent) { + /* + * The following line is indented more. So it is likely + * that this line is the start of a block. + */ + s->penalty += any_blanks ? + RELATIVE_OUTDENT_WITH_BLANK_PENALTY : + RELATIVE_OUTDENT_PENALTY; + } else { + /* + * That was probably the end of a block. + */ + s->penalty += any_blanks ? + RELATIVE_DEDENT_WITH_BLANK_PENALTY : + RELATIVE_DEDENT_PENALTY; + } + } +} + +static int score_cmp(struct split_score *s1, struct split_score *s2) +{ + /* -1 if s1.effective_indent < s2->effective_indent, etc. */ + int cmp_indents = ((s1->effective_indent > s2->effective_indent) - + (s1->effective_indent < s2->effective_indent)); + + return INDENT_WEIGHT * cmp_indents + (s1->penalty - s2->penalty); +} + +/* + * Represent a group of changed lines in an xdfile_t (i.e., a contiguous group + * of lines that was inserted or deleted from the corresponding version of the + * file). We consider there to be such a group at the beginning of the file, at + * the end of the file, and between any two unchanged lines, though most such + * groups will usually be empty. + * + * If the first line in a group is equal to the line following the group, then + * the group can be slid down. Similarly, if the last line in a group is equal + * to the line preceding the group, then the group can be slid up. See + * group_slide_down() and group_slide_up(). + * + * Note that loops that are testing for changed lines in xdf->rchg do not need + * index bounding since the array is prepared with a zero at position -1 and N. + */ +struct group { + /* + * The index of the first changed line in the group, or the index of + * the unchanged line above which the (empty) group is located. + */ + long start; + + /* + * The index of the first unchanged line after the group. For an empty + * group, end is equal to start. + */ + long end; +}; + +/* + * Initialize g to point at the first group in xdf. + */ +static void group_init(xdfile_t *xdf, struct group *g) +{ + g->start = g->end = 0; + while (xdf->rchg[g->end]) + g->end++; +} + +/* + * Move g to describe the next (possibly empty) group in xdf and return 0. If g + * is already at the end of the file, do nothing and return -1. + */ +static inline int group_next(xdfile_t *xdf, struct group *g) +{ + if (g->end == xdf->nrec) + return -1; + g->start = g->end + 1; + for (g->end = g->start; xdf->rchg[g->end]; g->end++) + ; + + return 0; +} + +/* + * Move g to describe the previous (possibly empty) group in xdf and return 0. + * If g is already at the beginning of the file, do nothing and return -1. + */ +static inline int group_previous(xdfile_t *xdf, struct group *g) +{ + if (g->start == 0) + return -1; + + g->end = g->start - 1; + for (g->start = g->end; xdf->rchg[g->start - 1]; g->start--) + ; + + return 0; +} + +/* + * If g can be slid toward the end of the file, do so, and if it bumps into a + * following group, expand this group to include it. Return 0 on success or -1 + * if g cannot be slid down. + */ +static int group_slide_down(xdfile_t *xdf, struct group *g, long flags) +{ + if (g->end < xdf->nrec && + recs_match(xdf->recs[g->start], xdf->recs[g->end], flags)) { + xdf->rchg[g->start++] = 0; + xdf->rchg[g->end++] = 1; + + while (xdf->rchg[g->end]) + g->end++; + + return 0; + } else { + return -1; + } +} + +/* + * If g can be slid toward the beginning of the file, do so, and if it bumps + * into a previous group, expand this group to include it. Return 0 on success + * or -1 if g cannot be slid up. + */ +static int group_slide_up(xdfile_t *xdf, struct group *g, long flags) +{ + if (g->start > 0 && + recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1], flags)) { + xdf->rchg[--g->start] = 1; + xdf->rchg[--g->end] = 0; + + while (xdf->rchg[g->start - 1]) + g->start--; + + return 0; + } else { + return -1; + } +} + +static void xdl_bug(const char *msg) +{ + fprintf(stderr, "BUG: %s\n", msg); + exit(1); +} + +/* + * Move back and forward change groups for a consistent and pretty diff output. + * This also helps in finding joinable change groups and reducing the diff + * size. + */ +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { + struct group g, go; + long earliest_end, end_matching_other; + long groupsize; + unsigned int blank_lines; + + group_init(xdf, &g); + group_init(xdfo, &go); + + while (1) { + /* If the group is empty in the to-be-compacted file, skip it: */ + if (g.end == g.start) + goto next; + + /* + * Now shift the change up and then down as far as possible in + * each direction. If it bumps into any other changes, merge them. + */ do { - grpsiz = ix - ixs; - blank_lines = 0; + groupsize = g.end - g.start; /* - * If the line before the current change group, is equal to - * the last line of the current change group, shift backward - * the group. + * Keep track of the last "end" index that causes this + * group to align with a group of changed lines in the + * other file. -1 indicates that we haven't found such + * a match yet: */ - while (ixs > 0 && recs_match(recs, ixs - 1, ix - 1, flags)) { - rchg[--ixs] = 1; - rchg[--ix] = 0; - - /* - * This change might have joined two change groups, - * so we try to take this scenario in account by moving - * the start index accordingly (and so the other-file - * end-of-group index). - */ - for (; rchg[ixs - 1]; ixs--); - while (rchgo[--ixo]); - } + end_matching_other = -1; /* - * Record the end-of-group position in case we are matched - * with a group of changes in the other file (that is, the - * change record before the end-of-group index in the other - * file is set). + * Boolean value that records whether there are any blank + * lines that could be made to be the last line of this + * group. */ - ixref = rchgo[ixo - 1] ? ix: nrec; + blank_lines = 0; + + /* Shift the group backward as much as possible: */ + while (!group_slide_up(xdf, &g, flags)) + if (group_previous(xdfo, &go)) + xdl_bug("group sync broken sliding up"); /* - * If the first line of the current change group, is equal to - * the line next of the current change group, shift forward - * the group. + * This is this highest that this group can be shifted. + * Record its end index: */ - while (ix < nrec && recs_match(recs, ixs, ix, flags)) { - blank_lines += is_blank_line(recs, ix, flags); - - rchg[ixs++] = 0; - rchg[ix++] = 1; - - /* - * This change might have joined two change groups, - * so we try to take this scenario in account by moving - * the start index accordingly (and so the other-file - * end-of-group index). Keep tracking the reference - * index in case we are shifting together with a - * corresponding group of changes in the other file. - */ - for (; rchg[ix]; ix++); - while (rchgo[++ixo]) - ixref = ix; - } - } while (grpsiz != ix - ixs); + earliest_end = g.end; - /* - * Try to move back the possibly merged group of changes, to match - * the recorded position in the other file. - */ - while (ixref < ix) { - rchg[--ixs] = 1; - rchg[--ix] = 0; - while (rchgo[--ixo]); - } + if (go.end > go.start) + end_matching_other = g.end; + + /* Now shift the group forward as far as possible: */ + while (1) { + if (!blank_lines) + blank_lines = is_blank_line( + xdf->recs[g.end - 1], + flags); + + if (group_slide_down(xdf, &g, flags)) + break; + if (group_next(xdfo, &go)) + xdl_bug("group sync broken sliding down"); + + if (go.end > go.start) + end_matching_other = g.end; + } + } while (groupsize != g.end - g.start); /* - * If a group can be moved back and forth, see if there is a - * blank line in the moving space. If there is a blank line, - * make sure the last blank line is the end of the group. + * If the group can be shifted, then we can possibly use this + * freedom to produce a more intuitive diff. * - * As we already shifted the group forward as far as possible - * in the earlier loop, we need to shift it back only if at all. + * The group is currently shifted as far down as possible, so the + * heuristics below only have to handle upwards shifts. */ - if ((flags & XDF_COMPACTION_HEURISTIC) && blank_lines) { - while (ixs > 0 && - !is_blank_line(recs, ix - 1, flags) && - recs_match(recs, ixs - 1, ix - 1, flags)) { - rchg[--ixs] = 1; - rchg[--ix] = 0; + + if (g.end == earliest_end) { + /* no shifting was possible */ + } else if (end_matching_other != -1) { + /* + * Move the possibly merged group of changes back to line + * up with the last group of changes from the other file + * that it can align with. + */ + while (go.end == go.start) { + if (group_slide_up(xdf, &g, flags)) + xdl_bug("match disappeared"); + if (group_previous(xdfo, &go)) + xdl_bug("group sync broken sliding to match"); + } + } else if ((flags & XDF_COMPACTION_HEURISTIC) && blank_lines) { + /* + * Compaction heuristic: if it is possible to shift the + * group to make its bottom line a blank line, do so. + * + * As we already shifted the group forward as far as + * possible in the earlier loop, we only need to handle + * backward shifts, not forward ones. + */ + while (!is_blank_line(xdf->recs[g.end - 1], flags)) { + if (group_slide_up(xdf, &g, flags)) + xdl_bug("blank line disappeared"); + if (group_previous(xdfo, &go)) + xdl_bug("group sync broken sliding to blank line"); + } + } else if (flags & XDF_INDENT_HEURISTIC) { + /* + * Indent heuristic: a group of pure add/delete lines + * implies two splits, one between the end of the "before" + * context and the start of the group, and another between + * the end of the group and the beginning of the "after" + * context. Some splits are aesthetically better and some + * are worse. We compute a badness "score" for each split, + * and add the scores for the two splits to define a + * "score" for each position that the group can be shifted + * to. Then we pick the shift with the lowest score. + */ + long shift, best_shift = -1; + struct split_score best_score; + + for (shift = earliest_end; shift <= g.end; shift++) { + struct split_measurement m; + struct split_score score = {0, 0}; + + measure_split(xdf, shift, &m); + score_add_split(&m, &score); + measure_split(xdf, shift - groupsize, &m); + score_add_split(&m, &score); + if (best_shift == -1 || + score_cmp(&score, &best_score) <= 0) { + best_score.effective_indent = score.effective_indent; + best_score.penalty = score.penalty; + best_shift = shift; + } + } + + while (g.end > best_shift) { + if (group_slide_up(xdf, &g, flags)) + xdl_bug("best shift unreached"); + if (group_previous(xdfo, &go)) + xdl_bug("group sync broken sliding to blank line"); } } + + next: + /* Move past the just-processed group: */ + if (group_next(xdf, &g)) + break; + if (group_next(xdfo, &go)) + xdl_bug("group sync broken moving to next group"); } + if (!group_next(xdfo, &go)) + xdl_bug("group sync broken at end of file"); + return 0; } diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 49aa16ff78..7389ce4102 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -22,15 +22,6 @@ #include "xinclude.h" - - - -static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec); -static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb); - - - - static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { *rec = xdf->recs[ri]->ptr; @@ -248,7 +239,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, if (xche->next) { long l = XDL_MIN(xche->next->i1, xe->xdf1.nrec - 1); - if (l <= e1 || + if (l - xecfg->ctxlen <= e1 || get_func_line(xe, xecfg, NULL, l, e1) < 0) { xche = xche->next; goto post_context_calculation; |