diff options
138 files changed, 6804 insertions, 3264 deletions
diff --git a/.travis.yml b/.travis.yml index 37a1e1fb6d..9a65514d82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ env: - LINUX_GIT_LFS_VERSION="1.2.0" - DEFAULT_TEST_TARGET=prove - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" - - GIT_TEST_OPTS="--verbose --tee" + - GIT_TEST_OPTS="--verbose-log" - GIT_TEST_HTTPD=true - GIT_TEST_CLONE_2GB=YesPlease # t9810 occasionally fails on Travis CI OS X diff --git a/Documentation/RelNotes/2.10.2.txt b/Documentation/RelNotes/2.10.2.txt index ed2de0dc20..c4d4397023 100644 --- a/Documentation/RelNotes/2.10.2.txt +++ b/Documentation/RelNotes/2.10.2.txt @@ -41,5 +41,71 @@ Fixes since v2.10.1 that were detected without dying itself, but under some conditions it didn't and died instead, which has been fixed. + * When "git fetch" tries to find where the history of the repository + it runs in has diverged from what the other side has, it has a + mechanism to avoid digging too deep into irrelevant side branches. + This however did not work well over the "smart-http" transport due + to a design bug, which has been fixed. + + * When we started cURL to talk to imap server when a new enough + version of cURL library is available, we forgot to explicitly add + imap(s):// before the destination. To some folks, that didn't work + and the library tried to make HTTP(s) requests instead. + + * The ./configure script generated from configure.ac was taught how + to detect support of SSL by libcurl better. + + * http.emptyauth configuration is a way to allow an empty username to + pass when attempting to authenticate using mechanisms like + Kerberos. We took an unspecified (NULL) username and sent ":" + (i.e. no username, no password) to CURLOPT_USERPWD, but did not do + the same when the username is explicitly set to an empty string. + + * "git clone" of a local repository can be done at the filesystem + level, but the codepath did not check errors while copying and + adjusting the file that lists alternate object stores. + + * Documentation for "git commit" was updated to clarify that "commit + -p <paths>" adds to the current contents of the index to come up + with what to commit. + + * A stray symbolic link in $GIT_DIR/refs/ directory could make name + resolution loop forever, which has been corrected. + + * The "submodule.<name>.path" stored in .gitmodules is never copied + to .git/config and such a key in .git/config has no meaning, but + the documentation described it and submodule.<name>.url next to + each other as if both belong to .git/config. This has been fixed. + + * Recent git allows submodule.<name>.branch to use a special token + "." instead of the branch name; the documentation has been updated + to describe it. + + * In a worktree connected to a repository elsewhere, created via "git + worktree", "git checkout" attempts to protect users from confusion + by refusing to check out a branch that is already checked out in + another worktree. However, this also prevented checking out a + branch, which is designated as the primary branch of a bare + reopsitory, in a worktree that is connected to the bare + repository. The check has been corrected to allow it. + + * "git rebase" immediately after "git clone" failed to find the fork + point from the upstream. + + * When fetching from a remote that has many tags that are irrelevant + to branches we are following, we used to waste way too many cycles + when checking if the object pointed at by a tag (that we are not + going to fetch!) exists in our repository too carefully. + + * The Travis CI configuration we ship ran the tests with --verbose + option but this risks non-TAP output that happens to be "ok" to be + misinterpreted as TAP signalling a test that passed. This resulted + in unnecessary failure. This has been corrected by introducing a + new mode to run our tests in the test harness to send the verbose + output separately to the log file. + + * Some AsciiDoc formatter mishandles a displayed illustration with + tabs in it. Adjust a few of them in merge-base documentation to + work around them. 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 index 3bc074fc03..3590620000 100644 --- a/Documentation/RelNotes/2.11.0.txt +++ b/Documentation/RelNotes/2.11.0.txt @@ -1,11 +1,37 @@ Git 2.11 Release Notes ====================== +Backward compatibility notes. + + * An empty string used as a pathspec element has always meant + 'everything matches', but it is too easy to write a script that + finds a path to remove in $path and run 'git rm "$paht"', which + ends up removing everything. This release starts warning about the + use of an empty string that is used for 'everything matches' and + asks users to use a more explicit '.' for that instead. + + The hope is that existing users will not mind this change, and + eventually the warning can be turned into a hard error, upgrading + the deprecation into removal of this (mis)feature. + + * The historical argument order "git merge <msg> HEAD <commit>..." + has been deprecated for quite some time, and will be removed in the + next release (not this one). + + * The default abbreviation length, which has historically been 7, now + scales as the repository grows, using the approximate number of + objects in the reopsitory and a bit of math around the birthday + paradox. The logic suggests to use 12 hexdigits for the Linux + kernel, and 9 to 10 for Git itself. + + Updates since v2.10 ------------------- UI, Workflows & Features + * Comes with new version of git-gui, now at its 0.21.0 tag. + * "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. @@ -89,6 +115,35 @@ UI, Workflows & Features * "git mergetool" learned to honor "-O<orderfile>" to control the order of paths to present to the end user. + * "git diff/log --ws-error-highlight=<kind>" lacked the corresponding + configuration variable to set it by default. + + * "git ls-files" learned "--recurse-submodules" option that can be + used to get a listing of tracked files across submodules (i.e. this + only works with "--cached" option, not for listing untracked or + ignored files). This would be a useful tool to sit on the upstream + side of a pipe that is read with xargs to work on all working tree + files from the top-level superproject. + + * A new credential helper that talks via "libsecret" with + implementations of XDG Secret Service API has been added to + contrib/credential/. + + * The GPG verification status shown in "%G?" pretty format specifier + was not rich enough to differentiate a signature made by an expired + key, a signature made by a revoked key, etc. New output letters + have been assigned to express them. + + * In addition to purely abbreviated commit object names, "gitweb" + learned to turn "git describe" output (e.g. v2.9.3-599-g2376d31787) + into clickable links in its output. + + * When new paths were added by "git add -N" to the index, it was + enough to circumvent the check by "git commit" to refrain from + making an empty commit without "--allow-empty". The same logic + prevented "git status" to show such a path as "new file" in the + "Changes not staged for commit" section. + Performance, Internal Implementation, Development Support etc. @@ -160,6 +215,21 @@ Performance, Internal Implementation, Development Support etc. decide if we accept the check, and once we decide, either migrate them to the repository or purge them immediately. + * The require_clean_work_tree() helper was recreated in C when "git + pull" was rewritten from shell; the helper is now made available to + other callers in preparation for upcoming "rebase -i" work. + + * "git upload-pack" had its code cleaned-up and performance improved + by reducing use of timestamp-ordered commit-list, which was + replaced with a priority queue. + + * "git diff --no-index" codepath has been updated not to try to peek + into .git/ directory that happens to be under the current + directory, when we know we are operating outside any repository. + + * Update of the sequencer codebase to make it reusable to reimplement + "rebase -i" continues. + Also contains various documentation updates and code clean-ups. @@ -416,7 +486,78 @@ notes for details). each other as if both belong to .git/config. This has been fixed. (merge 72710165c9 sb/submodule-config-doc-drop-path later to maint). + * In a worktree connected to a repository elsewhere, created via "git + worktree", "git checkout" attempts to protect users from confusion + by refusing to check out a branch that is already checked out in + another worktree. However, this also prevented checking out a + branch, which is designated as the primary branch of a bare + reopsitory, in a worktree that is connected to the bare + repository. The check has been corrected to allow it. + (merge 171c646f8c dk/worktree-dup-checkout-with-bare-is-ok later to maint). + + * "git rebase" immediately after "git clone" failed to find the fork + point from the upstream. + (merge 4f21454b55 jk/merge-base-fork-point-without-reflog later to maint). + + * When fetching from a remote that has many tags that are irrelevant + to branches we are following, we used to waste way too many cycles + when checking if the object pointed at by a tag (that we are not + going to fetch!) exists in our repository too carefully. + (merge 5827a03545 jk/fetch-quick-tag-following later to maint). + + * Protect our code from over-eager compilers. + (merge 0ac52a38e8 jk/tighten-alloc later to maint). + + * Recent git allows submodule.<name>.branch to use a special token + "." instead of the branch name; the documentation has been updated + to describe it. + (merge 15ef78008a bw/submodule-branch-dot-doc later to maint). + + * A hot-fix for a test added by a recent topic that went to both + 'master' and 'maint' already. + (merge 76e368c378 tg/add-chmod+x-fix later to maint). + + * "git send-email" attempts to pick up valid e-mails from the + trailers, but people in real world write non-addresses there, like + "Cc: Stable <add@re.ss> # 4.8+", which broke the output depending + on the availability and vintage of Mail::Address perl module. + (merge dcfafc5214 mm/send-email-cc-cruft-after-address later to maint). + + * The Travis CI configuration we ship ran the tests with --verbose + option but this risks non-TAP output that happens to be "ok" to be + misinterpreted as TAP signalling a test that passed. This resulted + in unnecessary failure. This has been corrected by introducing a + new mode to run our tests in the test harness to send the verbose + output separately to the log file. + (merge 614fe01521 jk/tap-verbose-fix later to maint). + + * Some AsciiDoc formatter mishandles a displayed illustration with + tabs in it. Adjust a few of them in merge-base documentation to + work around them. + (merge 6750f62699 po/fix-doc-merge-base-illustration later to maint). + + * A minor regression fix for "git submodule" that was introduced + when more helper functions were reimplemented in C. + (merge 77b63ac31e sb/submodule-ignore-trailing-slash later to maint). + + * The code that we have used for the past 10+ years to cycle + 4-element ring buffers turns out to be not quite portable in + theoretical world. + (merge bb84735c80 rs/ring-buffer-wraparound later to maint). + + * "git daemon" used fixed-length buffers to turn URL to the + repository the client asked for into the server side directory + path, using snprintf() to avoid overflowing these buffers, but + allowed possibly truncated paths to the directory. This has been + tightened to reject such a request that causes overlong path to be + required to serve. + (merge 6bdb0083be jk/daemon-path-ok-check-truncation later to maint). + * Other minor doc, test and build updates and code cleanups. (merge a94bb68397 rs/cocci later to maint). (merge 641c900b2c js/reset-usage later to maint). (merge 30cfe72d37 rs/pretty-format-color-doc-fix later to maint). + (merge d709f1fb9d jc/diff-unique-abbrev-comments later to maint). + (merge 13092a916d jc/cocci-xstrdup-or-null later to maint). + (merge 86009f32bb pb/test-parse-options-expect later to maint). + (merge 749a2279a4 yk/git-tag-remove-mention-of-old-layout-in-doc later to maint). diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index b27a38f896..58f4bd6afa 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -193,3 +193,9 @@ diff.algorithm:: low-occurrence common elements". -- + + +diff.wsErrorHighlight:: + A comma separated list of `old`, `new`, `context`, that + specifies how whitespace errors on lines are highlighted + with `color.diff.whitespace`. Can be overridden by the + command line option `--ws-error-highlight=<kind>` diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 2d77a19626..e6215c372c 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -308,6 +308,8 @@ ifndef::git-format-patch[] lines are highlighted. E.g. `--ws-error-highlight=new,old` highlights whitespace errors on both deleted and added lines. `all` can be used as a short-hand for `old,new,context`. + The `diff.wsErrorHighlight` configuration variable can be + used to specify the default behaviour. endif::git-format-patch[] @@ -570,5 +572,13 @@ endif::git-format-patch[] --line-prefix=<prefix>:: Prepend an additional prefix to every line of output. +--ita-invisible-in-index:: + By default entries added by "git add -N" appear as an existing + empty file in "git diff" and a new file in "git diff --cached". + This option makes the entry appear as a new file in "git diff" + and non-existent in "git diff --cached". This option could be + reverted with `--ita-visible-in-index`. Both options are + experimental and could be removed in future. + For more detailed explanation on these common options, see also linkgit:gitdiffcore[7]. diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 93d1db6528..e99bda6add 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -48,19 +48,21 @@ with only spaces at the end of the commit message part, one blank line will be added before the new trailer. Existing trailers are extracted from the input message by looking for -a group of one or more lines that contain a colon (by default), where -the group is preceded by one or more empty (or whitespace-only) lines. +a group of one or more lines that (i) are all trailers, or (ii) contains at +least one Git-generated trailer and consists of at least 25% trailers. +The group must be preceded by one or more empty (or whitespace-only) lines. The group must either be at the end of the message or be the last non-whitespace lines before a line that starts with '---'. Such three minus signs start the patch part of the message. -When reading trailers, there can be whitespaces before and after the +When reading trailers, there can be whitespaces after the token, the separator and the value. There can also be whitespaces -inside the token and the value. +inside the token and the value. The value may be split over multiple lines with +each subsequent line starting with whitespace, like the "folding" in RFC 822. Note that 'trailers' do not follow and are not intended to follow many -rules for RFC 822 headers. For example they do not follow the line -folding rules, the encoding rules and probably many other rules. +rules for RFC 822 headers. For example they do not follow +the encoding rules and probably many other rules. OPTIONS ------- diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 0d933ac355..446209e206 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -18,7 +18,8 @@ SYNOPSIS [--exclude-per-directory=<file>] [--exclude-standard] [--error-unmatch] [--with-tree=<tree-ish>] - [--full-name] [--abbrev] [--] [<file>...] + [--full-name] [--recurse-submodules] + [--abbrev] [--] [<file>...] DESCRIPTION ----------- @@ -137,6 +138,10 @@ a space) at the start of each line: option forces paths to be output relative to the project top directory. +--recurse-submodules:: + Recursively calls ls-files on each submodule in the repository. + Currently there is only support for the --cached mode. + --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object lines, show only a partial prefix. diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt index 808426faac..b968b64c38 100644 --- a/Documentation/git-merge-base.txt +++ b/Documentation/git-merge-base.txt @@ -80,8 +80,8 @@ which is reachable from both 'A' and 'B' through the parent relationship. For example, with this topology: - o---o---o---B - / + o---o---o---B + / ---o---1---o---o---o---A the merge base between 'A' and 'B' is '1'. @@ -116,11 +116,11 @@ the best common ancestor of all commits. When the history involves criss-cross merges, there can be more than one 'best' common ancestor for two commits. For example, with this topology: - ---1---o---A - \ / - X - / \ - ---2---o---o---B + ---1---o---A + \ / + X + / \ + ---2---o---o---B both '1' and '2' are merge-bases of A and B. Neither one is better than the other (both are 'best' merge bases). When the `--all` option is not given, @@ -154,13 +154,13 @@ topic origin/master`, the history of remote-tracking branch `origin/master` may have been rewound and rebuilt, leading to a history of this shape: - o---B1 - / + o---B1 + / ---o---o---B2--o---o---o---B (origin/master) - \ - B3 - \ - Derived (topic) + \ + B3 + \ + Derived (topic) where `origin/master` used to point at commits B3, B2, B1 and now it points at B, and your `topic` branch was started on top of it back diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index bf3bb372ee..d841573475 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -259,7 +259,9 @@ OPTIONS --branch:: Branch of repository to add as submodule. The name of the branch is recorded as `submodule.<name>.branch` in - `.gitmodules` for `update --remote`. + `.gitmodules` for `update --remote`. A special value of `.` is used to + indicate that the name of the branch in the submodule should be the + same name as the current branch in the current repository. -f:: --force:: diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 7ecca8e247..80019c584b 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -253,9 +253,8 @@ On Automatic following ~~~~~~~~~~~~~~~~~~~~~~ If you are following somebody else's tree, you are most likely -using remote-tracking branches (`refs/heads/origin` in traditional -layout, or `refs/remotes/origin/master` in the separate-remote -layout). You usually want the tags from the other end. +using remote-tracking branches (eg. `refs/remotes/origin/master`). +You usually want the tags from the other end. On the other hand, if you are fetching because you would want a one-shot merge from somebody else, you typically do not want to diff --git a/Documentation/git.txt b/Documentation/git.txt index b8bec711f4..ab7215eee2 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -13,6 +13,7 @@ SYNOPSIS [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>] + [--super-prefix=<path>] <command> [<args>] DESCRIPTION @@ -43,9 +44,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.10.1/git.html[documentation for release 2.10.1] +* link:v2.10.2/git.html[documentation for release 2.10.2] * release notes for + link:RelNotes/2.10.2.txt[2.10.2], link:RelNotes/2.10.1.txt[2.10.1], link:RelNotes/2.10.0.txt[2.10]. @@ -602,6 +604,11 @@ foo.bar= ...`) sets `foo.bar` to the empty string. details. Equivalent to setting the `GIT_NAMESPACE` environment variable. +--super-prefix=<path>:: + Currently for internal use only. Set a prefix which gives a path from + above a repository down to its root. One use is to give submodules + context about the superproject that invoked it. + --bare:: Treat the repository as a bare repository. If GIT_DIR environment is not set, it is set to the current working diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 7aff940202..976243a63e 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -293,7 +293,15 @@ checkout, when the `smudge` command is specified, the command is fed the blob object from its standard input, and its standard output is used to update the worktree file. Similarly, the `clean` command is used to convert the contents of worktree file -upon checkin. +upon checkin. By default these commands process only a single +blob and terminate. If a long running `process` filter is used +in place of `clean` and/or `smudge` filters, then Git can process +all blobs with a single filter command invocation for the entire +life of a single Git command, for example `git add --all`. If a +long running `process` filter is configured then it always takes +precedence over a configured single blob filter. See section +below for the description of the protocol used to communicate with +a `process` filter. One use of the content filtering is to massage the content into a shape that is more convenient for the platform, filesystem, and the user to use. @@ -373,6 +381,153 @@ not exist, or may have different contents. So, smudge and clean commands should not try to access the file on disk, but only act as filters on the content provided to them on standard input. +Long Running Filter Process +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the filter command (a string value) is defined via +`filter.<driver>.process` then Git can process all blobs with a +single filter invocation for the entire life of a single Git +command. This is achieved by using a packet format (pkt-line, +see technical/protocol-common.txt) based protocol over standard +input and standard output as follows. All packets, except for the +"*CONTENT" packets and the "0000" flush packet, are considered +text and therefore are terminated by a LF. + +Git starts the filter when it encounters the first file +that needs to be cleaned or smudged. After the filter started +Git sends a welcome message ("git-filter-client"), a list of supported +protocol version numbers, and a flush packet. Git expects to read a welcome +response message ("git-filter-server"), exactly one protocol version number +from the previously sent list, and a flush packet. All further +communication will be based on the selected version. The remaining +protocol description below documents "version=2". Please note that +"version=42" in the example below does not exist and is only there +to illustrate how the protocol would look like with more than one +version. + +After the version negotiation Git sends a list of all capabilities that +it supports and a flush packet. Git expects to read a list of desired +capabilities, which must be a subset of the supported capabilities list, +and a flush packet as response: +------------------------ +packet: git> git-filter-client +packet: git> version=2 +packet: git> version=42 +packet: git> 0000 +packet: git< git-filter-server +packet: git< version=2 +packet: git< 0000 +packet: git> capability=clean +packet: git> capability=smudge +packet: git> capability=not-yet-invented +packet: git> 0000 +packet: git< capability=clean +packet: git< capability=smudge +packet: git< 0000 +------------------------ +Supported filter capabilities in version 2 are "clean" and +"smudge". + +Afterwards Git sends a list of "key=value" pairs terminated with +a flush packet. The list will contain at least the filter command +(based on the supported capabilities) and the pathname of the file +to filter relative to the repository root. Right after the flush packet +Git sends the content split in zero or more pkt-line packets and a +flush packet to terminate content. Please note, that the filter +must not send any response before it received the content and the +final flush packet. +------------------------ +packet: git> command=smudge +packet: git> pathname=path/testfile.dat +packet: git> 0000 +packet: git> CONTENT +packet: git> 0000 +------------------------ + +The filter is expected to respond with a list of "key=value" pairs +terminated with a flush packet. If the filter does not experience +problems then the list must contain a "success" status. Right after +these packets the filter is expected to send the content in zero +or more pkt-line packets and a flush packet at the end. Finally, a +second list of "key=value" pairs terminated with a flush packet +is expected. The filter can change the status in the second list +or keep the status as is with an empty list. Please note that the +empty list must be terminated with a flush packet regardless. + +------------------------ +packet: git< status=success +packet: git< 0000 +packet: git< SMUDGED_CONTENT +packet: git< 0000 +packet: git< 0000 # empty list, keep "status=success" unchanged! +------------------------ + +If the result content is empty then the filter is expected to respond +with a "success" status and a flush packet to signal the empty content. +------------------------ +packet: git< status=success +packet: git< 0000 +packet: git< 0000 # empty content! +packet: git< 0000 # empty list, keep "status=success" unchanged! +------------------------ + +In case the filter cannot or does not want to process the content, +it is expected to respond with an "error" status. +------------------------ +packet: git< status=error +packet: git< 0000 +------------------------ + +If the filter experiences an error during processing, then it can +send the status "error" after the content was (partially or +completely) sent. +------------------------ +packet: git< status=success +packet: git< 0000 +packet: git< HALF_WRITTEN_ERRONEOUS_CONTENT +packet: git< 0000 +packet: git< status=error +packet: git< 0000 +------------------------ + +In case the filter cannot or does not want to process the content +as well as any future content for the lifetime of the Git process, +then it is expected to respond with an "abort" status at any point +in the protocol. +------------------------ +packet: git< status=abort +packet: git< 0000 +------------------------ + +Git neither stops nor restarts the filter process in case the +"error"/"abort" status is set. However, Git sets its exit code +according to the `filter.<driver>.required` flag, mimicking the +behavior of the `filter.<driver>.clean` / `filter.<driver>.smudge` +mechanism. + +If the filter dies during the communication or does not adhere to +the protocol then Git will stop the filter process and restart it +with the next file that needs to be processed. Depending on the +`filter.<driver>.required` flag Git will interpret that as error. + +After the filter has processed a blob it is expected to wait for +the next "key=value" list containing a command. Git will close +the command pipe on exit. The filter is expected to detect EOF +and exit gracefully on its own. Git will wait until the filter +process has stopped. + +A long running filter demo implementation can be found in +`contrib/long-running-filter/example.pl` located in the Git +core repository. If you develop your own long running filter +process then the `GIT_TRACE_PACKET` environment variables can be +very helpful for debugging (see linkgit:git[1]). + +Please note that you cannot use an existing `filter.<driver>.clean` +or `filter.<driver>.smudge` command with `filter.<driver>.process` +because the former two use a different inter process communication +protocol than the latter one. + + Interaction between checkin/checkout attributes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 10dcc08ff9..8f7c50f330 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -50,8 +50,11 @@ submodule.<name>.update:: submodule.<name>.branch:: A remote branch name for tracking updates in the upstream submodule. - If the option is not specified, it defaults to 'master'. See the - `--remote` documentation in linkgit:git-submodule[1] for details. + If the option is not specified, it defaults to 'master'. A special + value of `.` is used to indicate that the name of the branch in the + submodule should be the same name as the current branch in the + current repository. See the `--remote` documentation in + linkgit:git-submodule[1] for details. submodule.<name>.fetchRecurseSubmodules:: This option can be used to control recursive fetching of this diff --git a/Documentation/howto/revert-a-faulty-merge.txt b/Documentation/howto/revert-a-faulty-merge.txt index 462255ed5d..19f59cc888 100644 --- a/Documentation/howto/revert-a-faulty-merge.txt +++ b/Documentation/howto/revert-a-faulty-merge.txt @@ -30,7 +30,7 @@ The history immediately after the "revert of the merge" would look like this: ---o---o---o---M---x---x---W - / + / ---A---B where A and B are on the side development that was not so good, M is the @@ -47,7 +47,7 @@ After the developers of the side branch fix their mistakes, the history may look like this: ---o---o---o---M---x---x---W---x - / + / ---A---B-------------------C---D where C and D are to fix what was broken in A and B, and you may already @@ -81,7 +81,7 @@ In such a situation, you would want to first revert the previous revert, which would make the history look like this: ---o---o---o---M---x---x---W---x---Y - / + / ---A---B-------------------C---D where Y is the revert of W. Such a "revert of the revert" can be done @@ -93,14 +93,14 @@ This history would (ignoring possible conflicts between what W and W..Y changed) be equivalent to not having W or Y at all in the history: ---o---o---o---M---x---x-------x---- - / + / ---A---B-------------------C---D and merging the side branch again will not have conflict arising from an earlier revert and revert of the revert. ---o---o---o---M---x---x-------x-------* - / / + / / ---A---B-------------------C---D Of course the changes made in C and D still can conflict with what was @@ -111,13 +111,13 @@ faulty A and B, and redone the changes on top of the updated mainline after the revert, the history would have looked like this: ---o---o---o---M---x---x---W---x---x - / \ + / \ ---A---B A'--B'--C' If you reverted the revert in such a case as in the previous example: ---o---o---o---M---x---x---W---x---x---Y---* - / \ / + / \ / ---A---B A'--B'--C' where Y is the revert of W, A' and B' are rerolled A and B, and there may @@ -129,7 +129,7 @@ lot of overlapping changes that result in conflicts. So do not do "revert of revert" blindly without thinking.. ---o---o---o---M---x---x---W---x---x - / \ + / \ ---A---B A'--B'--C' In the history with rebased side branch, W (and M) are behind the merge diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 69c289dd0c..3bcee2ddb1 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -143,8 +143,14 @@ ifndef::git-rev-list[] - '%N': commit notes endif::git-rev-list[] - '%GG': raw verification message from GPG for a signed commit -- '%G?': show "G" for a good (valid) signature, "B" for a bad signature, - "U" for a good signature with unknown validity and "N" for no signature +- '%G?': show "G" for a good (valid) signature, + "B" for a bad signature, + "U" for a good signature with unknown validity, + "X" for a good signature that has expired, + "Y" for a good signature made by an expired key, + "R" for a good signature made by a revoked key, + "E" if the signature cannot be checked (e.g. missing key) + and "N" for no signature - '%GS': show the name of the signer for a signed commit - '%GK': show the key used to sign a signed commit - '%gD': reflog selector, e.g., `refs/stash@{1}` or @@ -122,9 +122,9 @@ 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."); + return error(_("--reject and --3way cannot be used together.")); if (state->cached && state->threeway) - return error("--cached and --3way cannot be used together."); + return error(_("--cached and --3way cannot be used together.")); if (state->threeway) { if (is_not_gitdir) return error(_("--3way outside a repository")); @@ -1586,8 +1586,8 @@ static int find_header(struct apply_state *state, 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); + error(_("git diff header lacks filename information " + "(line %d)"), state->linenr); return -128; } patch->is_toplevel_relative = 1; @@ -3095,8 +3095,8 @@ static int apply_binary_fragment(struct apply_state *state, /* 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'", + 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; @@ -3141,8 +3141,8 @@ static int apply_binary(struct apply_state *state, 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); + return error(_("cannot apply binary patch to '%s' " + "without full index line"), name); if (patch->old_name) { /* @@ -3151,16 +3151,16 @@ static int apply_binary(struct apply_state *state, */ 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.", + 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); + return error(_("the patch applies to an empty " + "'%s' but it is not empty"), name); } get_oid_hex(patch->new_sha1_prefix, &oid); @@ -3177,8 +3177,8 @@ static int apply_binary(struct apply_state *state, result = read_sha1_file(oid.hash, &type, &size); if (!result) - return error("the necessary postimage %s for " - "'%s' cannot be read", + return error(_("the necessary postimage %s for " + "'%s' cannot be read"), patch->new_sha1_prefix, name); clear_image(img); img->buf = result; @@ -3551,10 +3551,10 @@ static int try_threeway(struct apply_state *state, 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."); + 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"); + fprintf(stderr, _("Falling back to three-way merge...\n")); img = strbuf_detach(&buf, &len); prepare_image(&tmp_image, img, len, 1); @@ -3570,11 +3570,11 @@ static int try_threeway(struct apply_state *state, /* our_oid is ours */ if (patch->is_new) { if (load_current(state, &tmp_image, patch)) - return error("cannot read the current contents of '%s'", + 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'", + 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); @@ -3586,7 +3586,7 @@ static int try_threeway(struct apply_state *state, if (status < 0) { if (state->apply_verbosity > verbosity_silent) fprintf(stderr, - "Failed to fall back on three-way merge...\n"); + _("Failed to fall back on three-way merge...\n")); return status; } @@ -3600,12 +3600,12 @@ static int try_threeway(struct apply_state *state, oidcpy(&patch->threeway_stage[2], &post_oid); if (state->apply_verbosity > verbosity_silent) fprintf(stderr, - "Applied patch to '%s' with conflicts.\n", + _("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", + _("Applied patch to '%s' cleanly.\n"), patch->new_name); } return 0; @@ -4072,18 +4072,18 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list) 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); + 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); + return error(_("mode change for %s, which is not " + "in current HEAD"), name); } else - return error("sha1 information is lacking or useless " - "(%s).", name); + return error(_("sha1 information is lacking or useless " + "(%s)."), name); ce = make_cache_entry(patch->old_mode, oid.hash, name, 0, 0); if (!ce) @@ -4091,7 +4091,7 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list) name); if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD)) { free(ce); - return error("Could not add %s to temporary index", + return error(_("could not add %s to temporary index"), name); } } @@ -4101,7 +4101,7 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list) discard_index(&result); if (res) - return error("Could not write temporary index to %s", + return error(_("could not write temporary index to %s"), state->fake_ancestor); return 0; @@ -4869,10 +4869,12 @@ int apply_all_patches(struct apply_state *state, 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"); + warning(Q_("%d line applied after" + " fixing whitespace errors.", + "%d lines applied after" + " fixing whitespace errors.", + state->applied_after_fixing_ws), + state->applied_after_fixing_ws); else if (state->whitespace_error) warning(Q_("%d line adds whitespace errors.", "%d lines add whitespace errors.", @@ -531,7 +531,11 @@ static void bootstrap_attr_stack(void) debug_push(elem); } - elem = read_attr_from_file(git_path_info_attributes(), 1); + if (startup_info->have_repository) + elem = read_attr_from_file(git_path_info_attributes(), 1); + else + elem = NULL; + if (!elem) elem = xcalloc(1, sizeof(*elem)); elem->origin = NULL; diff --git a/builtin/archive.c b/builtin/archive.c index a1e3b940c2..49f491413a 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -47,10 +47,10 @@ static int run_remote_archiver(int argc, const char **argv, if (name_hint) { const char *format = archive_format_from_filename(name_hint); if (format) - packet_write(fd[1], "argument --format=%s\n", format); + packet_write_fmt(fd[1], "argument --format=%s\n", format); } for (i = 1; i < argc; i++) - packet_write(fd[1], "argument %s\n", argv[i]); + packet_write_fmt(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); buf = packet_read_line(fd[0], NULL); diff --git a/builtin/commit.c b/builtin/commit.c index 1cba3b75c8..a2baa6ebd5 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -183,7 +183,7 @@ static void determine_whence(struct wt_status *s) whence = FROM_MERGE; else if (file_exists(git_path_cherry_pick_head())) { whence = FROM_CHERRY_PICK; - if (file_exists(git_path(SEQ_DIR))) + if (file_exists(git_path_seq_dir())) sequencer_in_use = 1; } else @@ -894,9 +894,14 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (amend) parent = "HEAD^1"; - if (get_sha1(parent, sha1)) - commitable = !!active_nr; - else { + if (get_sha1(parent, sha1)) { + int i, ita_nr = 0; + + for (i = 0; i < active_nr; i++) + if (ce_intent_to_add(active_cache[i])) + ita_nr++; + commitable = active_nr - ita_nr > 0; + } else { /* * Unless the user did explicitly request a submodule * ignore mode by passing a command line option we do @@ -910,7 +915,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (ignore_submodule_arg && !strcmp(ignore_submodule_arg, "all")) diff_flags |= DIFF_OPT_IGNORE_SUBMODULES; - commitable = index_differs_from(parent, diff_flags); + commitable = index_differs_from(parent, diff_flags, 1); } } strbuf_release(&committer_ident); diff --git a/builtin/fetch.c b/builtin/fetch.c index d5329f915e..b6a5597cbf 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -241,9 +241,10 @@ static void find_non_local_tags(struct transport *transport, * as one to ignore by setting util to NULL. */ if (ends_with(ref->name, "^{}")) { - if (item && !has_object_file(&ref->old_oid) && + if (item && + !has_object_file_with_flags(&ref->old_oid, HAS_SHA1_QUICK) && !will_fetch(head, ref->old_oid.hash) && - !has_sha1_file(item->util) && + !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) && !will_fetch(head, item->util)) item->util = NULL; item = NULL; @@ -256,7 +257,8 @@ static void find_non_local_tags(struct transport *transport, * to check if it is a lightweight tag that we want to * fetch. */ - if (item && !has_sha1_file(item->util) && + if (item && + !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) && !will_fetch(head, item->util)) item->util = NULL; @@ -276,7 +278,8 @@ static void find_non_local_tags(struct transport *transport, * We may have a final lightweight tag that needs to be * checked to see if it needs fetching. */ - if (item && !has_sha1_file(item->util) && + if (item && + !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) && !will_fetch(head, item->util)) item->util = NULL; @@ -574,9 +577,12 @@ static void print_compact(struct strbuf *display, static void format_display(struct strbuf *display, char code, const char *summary, const char *error, - const char *remote, const char *local) + const char *remote, const char *local, + int summary_width) { - strbuf_addf(display, "%c %-*s ", code, TRANSPORT_SUMMARY(summary)); + int width = (summary_width + strlen(summary) - gettext_width(summary)); + + strbuf_addf(display, "%c %-*s ", code, width, summary); if (!compact_format) print_remote_to_local(display, remote, local); else @@ -588,7 +594,8 @@ static void format_display(struct strbuf *display, char code, static int update_local_ref(struct ref *ref, const char *remote, const struct ref *remote_ref, - struct strbuf *display) + struct strbuf *display, + int summary_width) { struct commit *current = NULL, *updated; enum object_type type; @@ -602,7 +609,7 @@ static int update_local_ref(struct ref *ref, if (!oidcmp(&ref->old_oid, &ref->new_oid)) { if (verbosity > 0) format_display(display, '=', _("[up to date]"), NULL, - remote, pretty_ref); + remote, pretty_ref, summary_width); return 0; } @@ -616,7 +623,7 @@ static int update_local_ref(struct ref *ref, */ format_display(display, '!', _("[rejected]"), _("can't fetch in current branch"), - remote, pretty_ref); + remote, pretty_ref, summary_width); return 1; } @@ -626,7 +633,7 @@ static int update_local_ref(struct ref *ref, r = s_update_ref("updating tag", ref, 0); format_display(display, r ? '!' : 't', _("[tag update]"), r ? _("unable to update local ref") : NULL, - remote, pretty_ref); + remote, pretty_ref, summary_width); return r; } @@ -659,7 +666,7 @@ static int update_local_ref(struct ref *ref, r = s_update_ref(msg, ref, 0); format_display(display, r ? '!' : '*', what, r ? _("unable to update local ref") : NULL, - remote, pretty_ref); + remote, pretty_ref, summary_width); return r; } @@ -675,7 +682,7 @@ static int update_local_ref(struct ref *ref, r = s_update_ref("fast-forward", ref, 1); format_display(display, r ? '!' : ' ', quickref.buf, r ? _("unable to update local ref") : NULL, - remote, pretty_ref); + remote, pretty_ref, summary_width); strbuf_release(&quickref); return r; } else if (force || ref->force) { @@ -690,12 +697,12 @@ static int update_local_ref(struct ref *ref, r = s_update_ref("forced-update", ref, 1); format_display(display, r ? '!' : '+', quickref.buf, r ? _("unable to update local ref") : _("forced update"), - remote, pretty_ref); + remote, pretty_ref, summary_width); strbuf_release(&quickref); return r; } else { format_display(display, '!', _("[rejected]"), _("non-fast-forward"), - remote, pretty_ref); + remote, pretty_ref, summary_width); return 1; } } @@ -726,6 +733,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, char *url; const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(); int want_status; + int summary_width = transport_summary_width(ref_map); fp = fopen(filename, "a"); if (!fp) @@ -835,13 +843,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_reset(¬e); if (ref) { - rc |= update_local_ref(ref, what, rm, ¬e); + rc |= update_local_ref(ref, what, rm, ¬e, + summary_width); free(ref); } else format_display(¬e, '*', *kind ? kind : "branch", NULL, *what ? what : "HEAD", - "FETCH_HEAD"); + "FETCH_HEAD", summary_width); if (note.len) { if (verbosity >= 0 && !shown_url) { fprintf(stderr, _("From %.*s\n"), @@ -908,6 +917,7 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map, int url_len, i, result = 0; struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map); char *url; + int summary_width = transport_summary_width(stale_refs); const char *dangling_msg = dry_run ? _(" (%s will become dangling)") : _(" (%s has become dangling)"); @@ -943,7 +953,8 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map, shown_url = 1; } format_display(&sb, '-', _("[deleted]"), NULL, - _("(none)"), prettify_refname(ref->name)); + _("(none)"), prettify_refname(ref->name), + summary_width); fprintf(stderr, " %s\n",sb.buf); strbuf_release(&sb); warn_dangling_symref(stderr, dangling_msg, ref->name); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 197f153f50..1592290815 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -14,6 +14,7 @@ #include "resolve-undo.h" #include "string-list.h" #include "pathspec.h" +#include "run-command.h" static int abbrev; static int show_deleted; @@ -28,8 +29,11 @@ static int show_valid_bit; static int line_terminator = '\n'; static int debug_mode; static int show_eol; +static int recurse_submodules; +static struct argv_array submodules_options = ARGV_ARRAY_INIT; static const char *prefix; +static const char *super_prefix; static int max_prefix_len; static int prefix_len; static struct pathspec pathspec; @@ -68,11 +72,24 @@ static void write_eolinfo(const struct cache_entry *ce, const char *path) static void write_name(const char *name) { /* + * Prepend the super_prefix to name to construct the full_name to be + * written. + */ + struct strbuf full_name = STRBUF_INIT; + if (super_prefix) { + strbuf_addstr(&full_name, super_prefix); + strbuf_addstr(&full_name, name); + name = full_name.buf; + } + + /* * With "--full-name", prefix_len=0; this caller needs to pass * an empty string in that case (a NULL is good for ""). */ write_name_quoted_relative(name, prefix_len ? prefix : NULL, stdout, line_terminator); + + strbuf_release(&full_name); } static void show_dir_entry(const char *tag, struct dir_entry *ent) @@ -152,55 +169,117 @@ static void show_killed_files(struct dir_struct *dir) } } +/* + * Compile an argv_array with all of the options supported by --recurse_submodules + */ +static void compile_submodule_options(const struct dir_struct *dir, int show_tag) +{ + if (line_terminator == '\0') + argv_array_push(&submodules_options, "-z"); + if (show_tag) + argv_array_push(&submodules_options, "-t"); + if (show_valid_bit) + argv_array_push(&submodules_options, "-v"); + if (show_cached) + argv_array_push(&submodules_options, "--cached"); + if (show_eol) + argv_array_push(&submodules_options, "--eol"); + if (debug_mode) + argv_array_push(&submodules_options, "--debug"); +} + +/** + * Recursively call ls-files on a submodule + */ +static void show_gitlink(const struct cache_entry *ce) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int status; + int i; + + argv_array_pushf(&cp.args, "--super-prefix=%s%s/", + super_prefix ? super_prefix : "", + ce->name); + argv_array_push(&cp.args, "ls-files"); + argv_array_push(&cp.args, "--recurse-submodules"); + + /* add supported options */ + argv_array_pushv(&cp.args, submodules_options.argv); + + /* + * Pass in the original pathspec args. The submodule will be + * responsible for prepending the 'submodule_prefix' prior to comparing + * against the pathspec for matches. + */ + argv_array_push(&cp.args, "--"); + for (i = 0; i < pathspec.nr; i++) + argv_array_push(&cp.args, pathspec.items[i].original); + + cp.git_cmd = 1; + cp.dir = ce->name; + status = run_command(&cp); + if (status) + exit(status); +} + static void show_ce_entry(const char *tag, const struct cache_entry *ce) { + struct strbuf name = STRBUF_INIT; int len = max_prefix_len; + if (super_prefix) + strbuf_addstr(&name, super_prefix); + strbuf_addstr(&name, ce->name); if (len >= ce_namelen(ce)) die("git ls-files: internal error - cache entry not superset of prefix"); - if (!match_pathspec(&pathspec, ce->name, ce_namelen(ce), - len, ps_matched, - S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) - return; + if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && + submodule_path_match(&pathspec, name.buf, ps_matched)) { + show_gitlink(ce); + } else if (match_pathspec(&pathspec, name.buf, name.len, + len, ps_matched, + S_ISDIR(ce->ce_mode) || + S_ISGITLINK(ce->ce_mode))) { + if (tag && *tag && show_valid_bit && + (ce->ce_flags & CE_VALID)) { + static char alttag[4]; + memcpy(alttag, tag, 3); + if (isalpha(tag[0])) + alttag[0] = tolower(tag[0]); + else if (tag[0] == '?') + alttag[0] = '!'; + else { + alttag[0] = 'v'; + alttag[1] = tag[0]; + alttag[2] = ' '; + alttag[3] = 0; + } + tag = alttag; + } - if (tag && *tag && show_valid_bit && - (ce->ce_flags & CE_VALID)) { - static char alttag[4]; - memcpy(alttag, tag, 3); - if (isalpha(tag[0])) - alttag[0] = tolower(tag[0]); - else if (tag[0] == '?') - alttag[0] = '!'; - else { - alttag[0] = 'v'; - alttag[1] = tag[0]; - alttag[2] = ' '; - alttag[3] = 0; + if (!show_stage) { + fputs(tag, stdout); + } else { + printf("%s%06o %s %d\t", + tag, + ce->ce_mode, + find_unique_abbrev(ce->oid.hash, abbrev), + ce_stage(ce)); + } + write_eolinfo(ce, ce->name); + write_name(ce->name); + if (debug_mode) { + const struct stat_data *sd = &ce->ce_stat_data; + + printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); + printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); + printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); + printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); + printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); } - tag = alttag; } - if (!show_stage) { - fputs(tag, stdout); - } else { - printf("%s%06o %s %d\t", - tag, - ce->ce_mode, - find_unique_abbrev(ce->oid.hash,abbrev), - ce_stage(ce)); - } - write_eolinfo(ce, ce->name); - write_name(ce->name); - if (debug_mode) { - const struct stat_data *sd = &ce->ce_stat_data; - - printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); - printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); - printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); - printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); - printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); - } + strbuf_release(&name); } static void show_ru_info(void) @@ -468,6 +547,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL, N_("make the output relative to the project top directory"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, + OPT_BOOL(0, "recurse-submodules", &recurse_submodules, + N_("recurse through submodules")), OPT_BOOL(0, "error-unmatch", &error_unmatch, N_("if any <file> is not in the index, treat this as an error")), OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"), @@ -484,6 +565,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) prefix = cmd_prefix; if (prefix) prefix_len = strlen(prefix); + super_prefix = get_super_prefix(); git_config(git_default_config, NULL); if (read_cache() < 0) @@ -519,13 +601,32 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (require_work_tree && !is_inside_work_tree()) setup_work_tree(); + if (recurse_submodules) + compile_submodule_options(&dir, show_tag); + + if (recurse_submodules && + (show_stage || show_deleted || show_others || show_unmerged || + show_killed || show_modified || show_resolve_undo || with_tree)) + die("ls-files --recurse-submodules unsupported mode"); + + if (recurse_submodules && error_unmatch) + die("ls-files --recurse-submodules does not support " + "--error-unmatch"); + parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD | PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, prefix, argv); - /* Find common prefix for all pathspec's */ - max_prefix = common_prefix(&pathspec); + /* + * Find common prefix for all pathspec's + * This is used as a performance optimization which unfortunately cannot + * be done when recursing into submodules + */ + if (recurse_submodules) + max_prefix = NULL; + else + max_prefix = common_prefix(&pathspec); max_prefix_len = max_prefix ? strlen(max_prefix) : 0; /* Treat unmatching pathspec elements as errors */ diff --git a/builtin/merge-base.c b/builtin/merge-base.c index c0d1822eb3..b572a37c26 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -173,6 +173,9 @@ static int handle_fork_point(int argc, const char **argv) revs.initial = 1; for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); + if (!revs.nr && !get_sha1(refname, sha1)) + add_one_commit(sha1, &revs); + for (i = 0; i < revs.nr; i++) revs.commit[i]->object.flags &= ~TMP_MARK; diff --git a/builtin/merge.c b/builtin/merge.c index a8b57c7d98..b65eeaa87d 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1374,12 +1374,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) struct commit *commit; if (verbosity >= 0) { - char from[GIT_SHA1_HEXSZ + 1], to[GIT_SHA1_HEXSZ + 1]; - find_unique_abbrev_r(from, head_commit->object.oid.hash, - DEFAULT_ABBREV); - find_unique_abbrev_r(to, remoteheads->item->object.oid.hash, - DEFAULT_ABBREV); - printf(_("Updating %s..%s\n"), from, to); + printf(_("Updating %s..%s\n"), + find_unique_abbrev(head_commit->object.oid.hash, + DEFAULT_ABBREV), + find_unique_abbrev(remoteheads->item->object.oid.hash, + DEFAULT_ABBREV)); } strbuf_addstr(&msg, "Fast-forward"); if (have_message) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 1e7c2a98a5..0fd52bd6b4 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -720,7 +720,7 @@ static off_t write_reused_pack(struct sha1file *f) if (!is_pack_valid(reuse_packfile)) die("packfile is invalid: %s", reuse_packfile->pack_name); - fd = git_open_noatime(reuse_packfile->pack_name); + fd = git_open(reuse_packfile->pack_name); if (fd < 0) die_errno("unable to open packfile for reuse: %s", reuse_packfile->pack_name); diff --git a/builtin/pull.c b/builtin/pull.c index 398aae16c0..d6e46ee6d0 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -17,6 +17,7 @@ #include "revision.h" #include "tempfile.h" #include "lockfile.h" +#include "wt-status.h" enum rebase_type { REBASE_INVALID = -1, @@ -326,73 +327,6 @@ static int git_pull_config(const char *var, const char *value, void *cb) } /** - * Returns 1 if there are unstaged changes, 0 otherwise. - */ -static int has_unstaged_changes(const char *prefix) -{ - struct rev_info rev_info; - int result; - - init_revisions(&rev_info, prefix); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); - DIFF_OPT_SET(&rev_info.diffopt, QUICK); - diff_setup_done(&rev_info.diffopt); - result = run_diff_files(&rev_info, 0); - return diff_result_code(&rev_info.diffopt, result); -} - -/** - * Returns 1 if there are uncommitted changes, 0 otherwise. - */ -static int has_uncommitted_changes(const char *prefix) -{ - struct rev_info rev_info; - int result; - - if (is_cache_unborn()) - return 0; - - init_revisions(&rev_info, prefix); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); - DIFF_OPT_SET(&rev_info.diffopt, QUICK); - add_head_to_pending(&rev_info); - diff_setup_done(&rev_info.diffopt); - result = run_diff_index(&rev_info, 1); - return diff_result_code(&rev_info.diffopt, result); -} - -/** - * If the work tree has unstaged or uncommitted changes, dies with the - * appropriate message. - */ -static void die_on_unclean_work_tree(const char *prefix) -{ - struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); - int do_die = 0; - - hold_locked_index(lock_file, 0); - refresh_cache(REFRESH_QUIET); - update_index_if_able(&the_index, lock_file); - rollback_lock_file(lock_file); - - if (has_unstaged_changes(prefix)) { - error(_("Cannot pull with rebase: You have unstaged changes.")); - do_die = 1; - } - - if (has_uncommitted_changes(prefix)) { - if (do_die) - error(_("Additionally, your index contains uncommitted changes.")); - else - error(_("Cannot pull with rebase: Your index contains uncommitted changes.")); - do_die = 1; - } - - if (do_die) - exit(1); -} - -/** * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge * into merge_heads. */ @@ -875,7 +809,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Updating an unborn branch with changes added to the index.")); if (!autostash) - die_on_unclean_work_tree(prefix); + require_clean_work_tree(N_("pull with rebase"), + _("please commit or stash them."), 1, 0); if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) hashclr(rebase_fork_point); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 04ed38e17d..e6b3879a5b 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -227,7 +227,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb) static void show_ref(const char *path, const unsigned char *sha1) { if (sent_capabilities) { - packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); + packet_write_fmt(1, "%s %s\n", sha1_to_hex(sha1), path); } else { struct strbuf cap = STRBUF_INIT; @@ -242,7 +242,7 @@ static void show_ref(const char *path, const unsigned char *sha1) if (advertise_push_options) strbuf_addstr(&cap, " push-options"); strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); - packet_write(1, "%s %s%c%s\n", + packet_write_fmt(1, "%s %s%c%s\n", sha1_to_hex(sha1), path, 0, cap.buf); strbuf_release(&cap); sent_capabilities = 1; @@ -1163,10 +1163,6 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) struct string_list_item *item; struct command *dst_cmd; unsigned char sha1[GIT_SHA1_RAWSZ]; - char cmd_oldh[GIT_SHA1_HEXSZ + 1], - cmd_newh[GIT_SHA1_HEXSZ + 1], - dst_oldh[GIT_SHA1_HEXSZ + 1], - dst_newh[GIT_SHA1_HEXSZ + 1]; int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); @@ -1197,14 +1193,14 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) dst_cmd->skip_update = 1; - find_unique_abbrev_r(cmd_oldh, cmd->old_sha1, DEFAULT_ABBREV); - find_unique_abbrev_r(cmd_newh, cmd->new_sha1, DEFAULT_ABBREV); - find_unique_abbrev_r(dst_oldh, dst_cmd->old_sha1, DEFAULT_ABBREV); - find_unique_abbrev_r(dst_newh, dst_cmd->new_sha1, DEFAULT_ABBREV); rp_error("refusing inconsistent update between symref '%s' (%s..%s) and" " its target '%s' (%s..%s)", - cmd->ref_name, cmd_oldh, cmd_newh, - dst_cmd->ref_name, dst_oldh, dst_newh); + cmd->ref_name, + find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV), + find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV), + dst_cmd->ref_name, + find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV), + find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV)); cmd->error_string = dst_cmd->error_string = "inconsistent aliased update"; diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index 88eb8f9013..11b48bfb41 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -128,9 +128,9 @@ static void send_git_request(int stdin_fd, const char *serv, const char *repo, const char *vhost) { if (!vhost) - packet_write(stdin_fd, "%s %s%c", serv, repo, 0); + packet_write_fmt(stdin_fd, "%s %s%c", serv, repo, 0); else - packet_write(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0, + packet_write_fmt(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0, vhost, 0); } diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 8479f6ed28..c43decda70 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -145,7 +145,7 @@ static void show_commit(struct commit *commit, void *data) */ if (buf.len && buf.buf[buf.len - 1] == '\n') graph_show_padding(revs->graph); - putchar('\n'); + putchar(info->hdr_termination); } else { /* * If the message buffer is empty, just show diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 4da1f1da25..cfb0f1510c 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -671,8 +671,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) filter &= ~(DO_FLAGS|DO_NOREV); verify = 1; abbrev = DEFAULT_ABBREV; - if (arg[7] == '=') - abbrev = strtoul(arg + 8, NULL, 10); + if (!arg[7]) + continue; + abbrev = strtoul(arg + 8, NULL, 10); if (abbrev < MINIMUM_ABBREV) abbrev = MINIMUM_ABBREV; else if (40 <= abbrev) diff --git a/builtin/revert.c b/builtin/revert.c index 4e693808b1..4ca5b51544 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -71,7 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...) die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } -static void parse_args(int argc, const char **argv, struct replay_opts *opts) +static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) { const char * const * usage_str = revert_or_cherry_pick_usage(opts); const char *me = action_name(opts); @@ -115,25 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (opts->keep_redundant_commits) opts->allow_empty = 1; - /* Set the subcommand */ - if (cmd == 'q') - opts->subcommand = REPLAY_REMOVE_STATE; - else if (cmd == 'c') - opts->subcommand = REPLAY_CONTINUE; - else if (cmd == 'a') - opts->subcommand = REPLAY_ROLLBACK; - else - opts->subcommand = REPLAY_NONE; - /* Check for incompatible command line arguments */ - if (opts->subcommand != REPLAY_NONE) { + if (cmd) { char *this_operation; - if (opts->subcommand == REPLAY_REMOVE_STATE) + if (cmd == 'q') this_operation = "--quit"; - else if (opts->subcommand == REPLAY_CONTINUE) + else if (cmd == 'c') this_operation = "--continue"; else { - assert(opts->subcommand == REPLAY_ROLLBACK); + assert(cmd == 'a'); this_operation = "--abort"; } @@ -156,7 +146,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) "--edit", opts->edit, NULL); - if (opts->subcommand != REPLAY_NONE) { + if (cmd) { opts->revs = NULL; } else { struct setup_revision_opt s_r_opt; @@ -174,20 +164,30 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (argc > 1) usage_with_options(usage_str, options); + + /* These option values will be free()d */ + opts->gpg_sign = xstrdup_or_null(opts->gpg_sign); + opts->strategy = xstrdup_or_null(opts->strategy); + + if (cmd == 'q') + return sequencer_remove_state(opts); + if (cmd == 'c') + return sequencer_continue(opts); + if (cmd == 'a') + return sequencer_rollback(opts); + return sequencer_pick_revisions(opts); } int cmd_revert(int argc, const char **argv, const char *prefix) { - struct replay_opts opts; + struct replay_opts opts = REPLAY_OPTS_INIT; int res; - memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; opts.action = REPLAY_REVERT; git_config(git_default_config, NULL); - parse_args(argc, argv, &opts); - res = sequencer_pick_revisions(&opts); + res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); return res; @@ -195,14 +195,12 @@ int cmd_revert(int argc, const char **argv, const char *prefix) int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - struct replay_opts opts; + struct replay_opts opts = REPLAY_OPTS_INIT; int res; - memset(&opts, 0, sizeof(opts)); opts.action = REPLAY_PICK; git_config(git_default_config, NULL); - parse_args(argc, argv, &opts); - res = sequencer_pick_revisions(&opts); + res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("cherry-pick failed")); return res; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 6182eb3197..4beeda5f9f 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -95,6 +95,8 @@ static int chop_last_dir(char **remoteurl, int is_relative) * NEEDSWORK: This works incorrectly on the domain and protocol part. * remote_url url outcome expectation * http://a.com/b ../c http://a.com/c as is + * http://a.com/b/ ../c http://a.com/c same as previous line, but + * ignore trailing slash in url * http://a.com/b ../../c http://c error out * http://a.com/b ../../../c http:/c error out * http://a.com/b ../../../../c http:c error out @@ -113,8 +115,8 @@ static char *relative_url(const char *remote_url, struct strbuf sb = STRBUF_INIT; size_t len = strlen(remoteurl); - if (is_dir_sep(remoteurl[len])) - remoteurl[len] = '\0'; + if (is_dir_sep(remoteurl[len-1])) + remoteurl[len-1] = '\0'; if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl)) is_relative = 0; @@ -147,6 +149,8 @@ static char *relative_url(const char *remote_url, } strbuf_reset(&sb); strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url); + if (ends_with(url, "/")) + strbuf_setlen(&sb, sb.len - 1); free(remoteurl); if (starts_with_dot_slash(sb.buf)) diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 2caedf1849..dc872f6a94 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -88,11 +88,11 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) writer.git_cmd = 1; if (start_command(&writer)) { int err = errno; - packet_write(1, "NACK unable to spawn subprocess\n"); + packet_write_fmt(1, "NACK unable to spawn subprocess\n"); die("upload-archive: %s", strerror(err)); } - packet_write(1, "ACK\n"); + packet_write_fmt(1, "ACK\n"); packet_flush(1); while (1) { @@ -409,6 +409,7 @@ static inline enum object_type object_type(unsigned int mode) #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE" #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE" #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX" +#define GIT_SUPER_PREFIX_ENVIRONMENT "GIT_INTERNAL_SUPER_PREFIX" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" #define INDEX_ENVIRONMENT "GIT_INDEX_FILE" @@ -476,6 +477,7 @@ extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir); extern int get_common_dir(struct strbuf *sb, const char *gitdir); extern const char *get_git_namespace(void); extern const char *strip_namespace(const char *namespaced_ref); +extern const char *get_super_prefix(void); extern const char *get_git_work_tree(void); /* @@ -901,8 +903,8 @@ extern char *sha1_pack_index_name(const unsigned char *sha1); * The result will be at least `len` characters long, and will be NUL * terminated. * - * The non-`_r` version returns a static buffer which will be overwritten by - * subsequent calls. + * The non-`_r` version returns a static buffer which remains valid until 4 + * more calls to find_unique_abbrev are made. * * The `_r` variant writes to a buffer supplied by the caller, which must be at * least `GIT_SHA1_HEXSZ + 1` bytes. The return value is the number of bytes @@ -1123,7 +1125,7 @@ extern int write_sha1_file(const void *buf, unsigned long len, const char *type, extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags); extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); -extern int git_open_noatime(const char *name); +extern int git_open(const char *name); extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size); extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz); extern int parse_sha1_header(const char *hdr, unsigned long *sizep); @@ -1155,6 +1157,7 @@ static inline int has_sha1_file(const unsigned char *sha1) /* Same as the above, except for struct object_id. */ extern int has_object_file(const struct object_id *oid); +extern int has_object_file_with_flags(const struct object_id *oid, int flags); /* * Return true iff an alternate object database has a loose object @@ -1187,6 +1190,9 @@ static inline int hex2chr(const char *s) #define MINIMUM_ABBREV minimum_abbrev #define DEFAULT_ABBREV default_abbrev +/* used when the code does not know or care what the default abbrev is */ +#define FALLBACK_DEFAULT_ABBREV 7 + struct object_context { unsigned char tree[20]; char path[PATH_MAX]; @@ -1487,6 +1493,12 @@ extern void prepare_packed_git(void); extern void reprepare_packed_git(void); extern void install_packed_git(struct packed_git *pack); +/* + * Give a rough count of objects in the repository. This sacrifices accuracy + * for speed. + */ +unsigned long approximate_object_count(void); + extern struct packed_git *find_sha1_pack(const unsigned char *sha1, struct packed_git *packs); diff --git a/combine-diff.c b/combine-diff.c index 8e2a577bdb..59501db99a 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -1203,9 +1203,9 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re /* Show sha1's */ for (i = 0; i < num_parent; i++) - printf(" %s", diff_unique_abbrev(p->parent[i].oid.hash, - opt->abbrev)); - printf(" %s ", diff_unique_abbrev(p->oid.hash, opt->abbrev)); + printf(" %s", diff_aligned_abbrev(&p->parent[i].oid, + opt->abbrev)); + printf(" %s ", diff_aligned_abbrev(&p->oid, opt->abbrev)); } if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) { @@ -750,7 +750,7 @@ struct child_process *git_connect(int fd[2], const char *url, * Note: Do not add any other headers here! Doing so * will cause older git-daemon servers to crash. */ - packet_write(fd[1], + packet_write_fmt(fd[1], "%s %s%chost=%s%c", prog, path, 0, target_host, 0); diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/contrib/coccinelle/xstrdup_or_null.cocci new file mode 100644 index 0000000000..3fceef132b --- /dev/null +++ b/contrib/coccinelle/xstrdup_or_null.cocci @@ -0,0 +1,7 @@ +@@ +expression E; +expression V; +@@ +- if (E) +- V = xstrdup(E); ++ V = xstrdup_or_null(E); diff --git a/contrib/credential/libsecret/Makefile b/contrib/credential/libsecret/Makefile new file mode 100644 index 0000000000..3e67552cc5 --- /dev/null +++ b/contrib/credential/libsecret/Makefile @@ -0,0 +1,25 @@ +MAIN:=git-credential-libsecret +all:: $(MAIN) + +CC = gcc +RM = rm -f +CFLAGS = -g -O2 -Wall +PKG_CONFIG = pkg-config + +-include ../../../config.mak.autogen +-include ../../../config.mak + +INCS:=$(shell $(PKG_CONFIG) --cflags libsecret-1 glib-2.0) +LIBS:=$(shell $(PKG_CONFIG) --libs libsecret-1 glib-2.0) + +SRCS:=$(MAIN).c +OBJS:=$(SRCS:.c=.o) + +%.o: %.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $< + +$(MAIN): $(OBJS) + $(CC) -o $@ $(LDFLAGS) $^ $(LIBS) + +clean: + @$(RM) $(MAIN) $(OBJS) diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c new file mode 100644 index 0000000000..4c56979d8a --- /dev/null +++ b/contrib/credential/libsecret/git-credential-libsecret.c @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2011 John Szakmeister <john@szakmeister.net> + * 2012 Philipp A. Hartmann <pah@qo.cx> + * 2016 Mantas MikulÄ—nas <grawity@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * Credits: + * - GNOME Keyring API handling originally written by John Szakmeister + * - ported to credential helper API by Philipp A. Hartmann + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> +#include <libsecret/secret.h> + +/* + * This credential struct and API is simplified from git's credential.{h,c} + */ +struct credential { + char *protocol; + char *host; + unsigned short port; + char *path; + char *username; + char *password; +}; + +#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL } + +typedef int (*credential_op_cb)(struct credential *); + +struct credential_operation { + char *name; + credential_op_cb op; +}; + +#define CREDENTIAL_OP_END { NULL, NULL } + +/* ----------------- Secret Service functions ----------------- */ + +static char *make_label(struct credential *c) +{ + if (c->port) + return g_strdup_printf("Git: %s://%s:%hu/%s", + c->protocol, c->host, c->port, c->path ? c->path : ""); + else + return g_strdup_printf("Git: %s://%s/%s", + c->protocol, c->host, c->path ? c->path : ""); +} + +static GHashTable *make_attr_list(struct credential *c) +{ + GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + + if (c->username) + g_hash_table_insert(al, "user", g_strdup(c->username)); + if (c->protocol) + g_hash_table_insert(al, "protocol", g_strdup(c->protocol)); + if (c->host) + g_hash_table_insert(al, "server", g_strdup(c->host)); + if (c->port) + g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port)); + if (c->path) + g_hash_table_insert(al, "object", g_strdup(c->path)); + + return al; +} + +static int keyring_get(struct credential *c) +{ + SecretService *service = NULL; + GHashTable *attributes = NULL; + GError *error = NULL; + GList *items = NULL; + + if (!c->protocol || !(c->host || c->path)) + return EXIT_FAILURE; + + service = secret_service_get_sync(0, NULL, &error); + if (error != NULL) { + g_critical("could not connect to Secret Service: %s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + attributes = make_attr_list(c); + items = secret_service_search_sync(service, + SECRET_SCHEMA_COMPAT_NETWORK, + attributes, + SECRET_SEARCH_LOAD_SECRETS, + NULL, + &error); + g_hash_table_unref(attributes); + if (error != NULL) { + g_critical("lookup failed: %s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + if (items != NULL) { + SecretItem *item; + SecretValue *secret; + const char *s; + + item = items->data; + secret = secret_item_get_secret(item); + attributes = secret_item_get_attributes(item); + + s = g_hash_table_lookup(attributes, "user"); + if (s) { + g_free(c->username); + c->username = g_strdup(s); + } + + s = secret_value_get_text(secret); + if (s) { + g_free(c->password); + c->password = g_strdup(s); + } + + g_hash_table_unref(attributes); + secret_value_unref(secret); + g_list_free_full(items, g_object_unref); + } + + return EXIT_SUCCESS; +} + + +static int keyring_store(struct credential *c) +{ + char *label = NULL; + GHashTable *attributes = NULL; + GError *error = NULL; + + /* + * Sanity check that what we are storing is actually sensible. + * In particular, we can't make a URL without a protocol field. + * Without either a host or pathname (depending on the scheme), + * we have no primary key. And without a username and password, + * we are not actually storing a credential. + */ + if (!c->protocol || !(c->host || c->path) || + !c->username || !c->password) + return EXIT_FAILURE; + + label = make_label(c); + attributes = make_attr_list(c); + secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK, + attributes, + NULL, + label, + c->password, + NULL, + &error); + g_free(label); + g_hash_table_unref(attributes); + + if (error != NULL) { + g_critical("store failed: %s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +static int keyring_erase(struct credential *c) +{ + GHashTable *attributes = NULL; + GError *error = NULL; + + /* + * Sanity check that we actually have something to match + * against. The input we get is a restrictive pattern, + * so technically a blank credential means "erase everything". + * But it is too easy to accidentally send this, since it is equivalent + * to empty input. So explicitly disallow it, and require that the + * pattern have some actual content to match. + */ + if (!c->protocol && !c->host && !c->path && !c->username) + return EXIT_FAILURE; + + attributes = make_attr_list(c); + secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK, + attributes, + NULL, + &error); + g_hash_table_unref(attributes); + + if (error != NULL) { + g_critical("erase failed: %s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/* + * Table with helper operation callbacks, used by generic + * credential helper main function. + */ +static struct credential_operation const credential_helper_ops[] = { + { "get", keyring_get }, + { "store", keyring_store }, + { "erase", keyring_erase }, + CREDENTIAL_OP_END +}; + +/* ------------------ credential functions ------------------ */ + +static void credential_init(struct credential *c) +{ + memset(c, 0, sizeof(*c)); +} + +static void credential_clear(struct credential *c) +{ + g_free(c->protocol); + g_free(c->host); + g_free(c->path); + g_free(c->username); + g_free(c->password); + + credential_init(c); +} + +static int credential_read(struct credential *c) +{ + char *buf; + size_t line_len; + char *key; + char *value; + + key = buf = g_malloc(1024); + + while (fgets(buf, 1024, stdin)) { + line_len = strlen(buf); + + if (line_len && buf[line_len-1] == '\n') + buf[--line_len] = '\0'; + + if (!line_len) + break; + + value = strchr(buf, '='); + if (!value) { + g_warning("invalid credential line: %s", key); + g_free(buf); + return -1; + } + *value++ = '\0'; + + if (!strcmp(key, "protocol")) { + g_free(c->protocol); + c->protocol = g_strdup(value); + } else if (!strcmp(key, "host")) { + g_free(c->host); + c->host = g_strdup(value); + value = strrchr(c->host, ':'); + if (value) { + *value++ = '\0'; + c->port = atoi(value); + } + } else if (!strcmp(key, "path")) { + g_free(c->path); + c->path = g_strdup(value); + } else if (!strcmp(key, "username")) { + g_free(c->username); + c->username = g_strdup(value); + } else if (!strcmp(key, "password")) { + g_free(c->password); + c->password = g_strdup(value); + while (*value) + *value++ = '\0'; + } + /* + * Ignore other lines; we don't know what they mean, but + * this future-proofs us when later versions of git do + * learn new lines, and the helpers are updated to match. + */ + } + + g_free(buf); + + return 0; +} + +static void credential_write_item(FILE *fp, const char *key, const char *value) +{ + if (!value) + return; + fprintf(fp, "%s=%s\n", key, value); +} + +static void credential_write(const struct credential *c) +{ + /* only write username/password, if set */ + credential_write_item(stdout, "username", c->username); + credential_write_item(stdout, "password", c->password); +} + +static void usage(const char *name) +{ + struct credential_operation const *try_op = credential_helper_ops; + const char *basename = strrchr(name, '/'); + + basename = (basename) ? basename + 1 : name; + fprintf(stderr, "usage: %s <", basename); + while (try_op->name) { + fprintf(stderr, "%s", (try_op++)->name); + if (try_op->name) + fprintf(stderr, "%s", "|"); + } + fprintf(stderr, "%s", ">\n"); +} + +int main(int argc, char *argv[]) +{ + int ret = EXIT_SUCCESS; + + struct credential_operation const *try_op = credential_helper_ops; + struct credential cred = CREDENTIAL_INIT; + + if (!argv[1]) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + g_set_application_name("Git Credential Helper"); + + /* lookup operation callback */ + while (try_op->name && strcmp(argv[1], try_op->name)) + try_op++; + + /* unsupported operation given -- ignore silently */ + if (!try_op->name || !try_op->op) + goto out; + + ret = credential_read(&cred); + if (ret) + goto out; + + /* perform credential operation */ + ret = (*try_op->op)(&cred); + + credential_write(&cred); + +out: + credential_clear(&cred); + return ret; +} diff --git a/contrib/long-running-filter/example.pl b/contrib/long-running-filter/example.pl new file mode 100755 index 0000000000..39457055a5 --- /dev/null +++ b/contrib/long-running-filter/example.pl @@ -0,0 +1,128 @@ +#!/usr/bin/perl +# +# Example implementation for the Git filter protocol version 2 +# See Documentation/gitattributes.txt, section "Filter Protocol" +# +# Please note, this pass-thru filter is a minimal skeleton. No proper +# error handling was implemented. +# + +use strict; +use warnings; + +my $MAX_PACKET_CONTENT_SIZE = 65516; + +sub packet_bin_read { + my $buffer; + my $bytes_read = read STDIN, $buffer, 4; + if ( $bytes_read == 0 ) { + + # EOF - Git stopped talking to us! + exit(); + } + elsif ( $bytes_read != 4 ) { + die "invalid packet: '$buffer'"; + } + my $pkt_size = hex($buffer); + if ( $pkt_size == 0 ) { + return ( 1, "" ); + } + elsif ( $pkt_size > 4 ) { + my $content_size = $pkt_size - 4; + $bytes_read = read STDIN, $buffer, $content_size; + if ( $bytes_read != $content_size ) { + die "invalid packet ($content_size bytes expected; $bytes_read bytes read)"; + } + return ( 0, $buffer ); + } + else { + die "invalid packet size: $pkt_size"; + } +} + +sub packet_txt_read { + my ( $res, $buf ) = packet_bin_read(); + unless ( $buf =~ s/\n$// ) { + die "A non-binary line MUST be terminated by an LF."; + } + return ( $res, $buf ); +} + +sub packet_bin_write { + my $buf = shift; + print STDOUT sprintf( "%04x", length($buf) + 4 ); + print STDOUT $buf; + STDOUT->flush(); +} + +sub packet_txt_write { + packet_bin_write( $_[0] . "\n" ); +} + +sub packet_flush { + print STDOUT sprintf( "%04x", 0 ); + STDOUT->flush(); +} + +( packet_txt_read() eq ( 0, "git-filter-client" ) ) || die "bad initialize"; +( packet_txt_read() eq ( 0, "version=2" ) ) || die "bad version"; +( packet_bin_read() eq ( 1, "" ) ) || die "bad version end"; + +packet_txt_write("git-filter-server"); +packet_txt_write("version=2"); +packet_flush(); + +( packet_txt_read() eq ( 0, "capability=clean" ) ) || die "bad capability"; +( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability"; +( packet_bin_read() eq ( 1, "" ) ) || die "bad capability end"; + +packet_txt_write("capability=clean"); +packet_txt_write("capability=smudge"); +packet_flush(); + +while (1) { + my ($command) = packet_txt_read() =~ /^command=([^=]+)$/; + my ($pathname) = packet_txt_read() =~ /^pathname=([^=]+)$/; + + packet_bin_read(); + + my $input = ""; + { + binmode(STDIN); + my $buffer; + my $done = 0; + while ( !$done ) { + ( $done, $buffer ) = packet_bin_read(); + $input .= $buffer; + } + } + + my $output; + if ( $command eq "clean" ) { + ### Perform clean here ### + $output = $input; + } + elsif ( $command eq "smudge" ) { + ### Perform smudge here ### + $output = $input; + } + else { + die "bad command '$command'"; + } + + packet_txt_write("status=success"); + packet_flush(); + while ( length($output) > 0 ) { + my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE ); + packet_bin_write($packet); + if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) { + $output = substr( $output, $MAX_PACKET_CONTENT_SIZE ); + } + else { + $output = ""; + } + } + packet_flush(); # flush content! + packet_flush(); # empty list, keep "status=success" unchanged! + +} @@ -3,6 +3,7 @@ #include "run-command.h" #include "quote.h" #include "sigchain.h" +#include "pkt-line.h" /* * convert.c - convert a file when checking it out and checking it in. @@ -197,17 +198,21 @@ static void check_safe_crlf(const char *path, enum crlf_action crlf_action, * CRLFs would not be restored by checkout */ if (checksafe == SAFE_CRLF_WARN) - warning("CRLF will be replaced by LF in %s.\nThe file will have its original line endings in your working directory.", path); + warning(_("CRLF will be replaced by LF in %s.\n" + "The file will have its original line" + " endings in your working directory."), path); else /* i.e. SAFE_CRLF_FAIL */ - die("CRLF would be replaced by LF in %s.", path); + die(_("CRLF would be replaced by LF in %s."), path); } else if (old_stats->lonelf && !new_stats->lonelf ) { /* * CRLFs would be added by checkout */ if (checksafe == SAFE_CRLF_WARN) - warning("LF will be replaced by CRLF in %s.\nThe file will have its original line endings in your working directory.", path); + warning(_("LF will be replaced by CRLF in %s.\n" + "The file will have its original line" + " endings in your working directory."), path); else /* i.e. SAFE_CRLF_FAIL */ - die("LF would be replaced by CRLF in %s", path); + die(_("LF would be replaced by CRLF in %s"), path); } } @@ -412,7 +417,7 @@ static int filter_buffer_or_fd(int in, int out, void *data) child_process.out = out; if (start_command(&child_process)) - return error("cannot fork to run external filter %s", params->cmd); + return error("cannot fork to run external filter '%s'", params->cmd); sigchain_push(SIGPIPE, SIG_IGN); @@ -430,19 +435,19 @@ static int filter_buffer_or_fd(int in, int out, void *data) if (close(child_process.in)) write_err = 1; if (write_err) - error("cannot feed the input to external filter %s", params->cmd); + error("cannot feed the input to external filter '%s'", params->cmd); sigchain_pop(SIGPIPE); status = finish_command(&child_process); if (status) - error("external filter %s failed %d", params->cmd, status); + error("external filter '%s' failed %d", params->cmd, status); strbuf_release(&cmd); return (write_err || status); } -static int apply_filter(const char *path, const char *src, size_t len, int fd, +static int apply_single_file_filter(const char *path, const char *src, size_t len, int fd, struct strbuf *dst, const char *cmd) { /* @@ -451,17 +456,11 @@ static int apply_filter(const char *path, const char *src, size_t len, int fd, * * (child --> cmd) --> us */ - int ret = 1; + int err = 0; struct strbuf nbuf = STRBUF_INIT; struct async async; struct filter_params params; - if (!cmd || !*cmd) - return 0; - - if (!dst) - return 1; - memset(&async, 0, sizeof(async)); async.proc = filter_buffer_or_fd; async.data = ¶ms; @@ -477,23 +476,304 @@ static int apply_filter(const char *path, const char *src, size_t len, int fd, return 0; /* error was already reported */ if (strbuf_read(&nbuf, async.out, len) < 0) { - error("read from external filter %s failed", cmd); - ret = 0; + err = error("read from external filter '%s' failed", cmd); } if (close(async.out)) { - error("read from external filter %s failed", cmd); - ret = 0; + err = error("read from external filter '%s' failed", cmd); } if (finish_async(&async)) { - error("external filter %s failed", cmd); - ret = 0; + err = error("external filter '%s' failed", cmd); } - if (ret) { + if (!err) { strbuf_swap(dst, &nbuf); } strbuf_release(&nbuf); - return ret; + return !err; +} + +#define CAP_CLEAN (1u<<0) +#define CAP_SMUDGE (1u<<1) + +struct cmd2process { + struct hashmap_entry ent; /* must be the first member! */ + unsigned int supported_capabilities; + const char *cmd; + struct child_process process; +}; + +static int cmd_process_map_initialized; +static struct hashmap cmd_process_map; + +static int cmd2process_cmp(const struct cmd2process *e1, + const struct cmd2process *e2, + const void *unused) +{ + return strcmp(e1->cmd, e2->cmd); +} + +static struct cmd2process *find_multi_file_filter_entry(struct hashmap *hashmap, const char *cmd) +{ + struct cmd2process key; + hashmap_entry_init(&key, strhash(cmd)); + key.cmd = cmd; + return hashmap_get(hashmap, &key, NULL); +} + +static int packet_write_list(int fd, const char *line, ...) +{ + va_list args; + int err; + va_start(args, line); + for (;;) { + if (!line) + break; + if (strlen(line) > LARGE_PACKET_DATA_MAX) + return -1; + err = packet_write_fmt_gently(fd, "%s\n", line); + if (err) + return err; + line = va_arg(args, const char*); + } + va_end(args); + return packet_flush_gently(fd); +} + +static void read_multi_file_filter_status(int fd, struct strbuf *status) +{ + struct strbuf **pair; + char *line; + for (;;) { + line = packet_read_line(fd, NULL); + if (!line) + break; + pair = strbuf_split_str(line, '=', 2); + if (pair[0] && pair[0]->len && pair[1]) { + /* the last "status=<foo>" line wins */ + if (!strcmp(pair[0]->buf, "status=")) { + strbuf_reset(status); + strbuf_addbuf(status, pair[1]); + } + } + strbuf_list_free(pair); + } +} + +static void kill_multi_file_filter(struct hashmap *hashmap, struct cmd2process *entry) +{ + if (!entry) + return; + + entry->process.clean_on_exit = 0; + kill(entry->process.pid, SIGTERM); + finish_command(&entry->process); + + hashmap_remove(hashmap, entry, NULL); + free(entry); +} + +static void stop_multi_file_filter(struct child_process *process) +{ + sigchain_push(SIGPIPE, SIG_IGN); + /* Closing the pipe signals the filter to initiate a shutdown. */ + close(process->in); + close(process->out); + sigchain_pop(SIGPIPE); + /* Finish command will wait until the shutdown is complete. */ + finish_command(process); +} + +static struct cmd2process *start_multi_file_filter(struct hashmap *hashmap, const char *cmd) +{ + int err; + struct cmd2process *entry; + struct child_process *process; + const char *argv[] = { cmd, NULL }; + struct string_list cap_list = STRING_LIST_INIT_NODUP; + char *cap_buf; + const char *cap_name; + + entry = xmalloc(sizeof(*entry)); + entry->cmd = cmd; + entry->supported_capabilities = 0; + process = &entry->process; + + child_process_init(process); + process->argv = argv; + process->use_shell = 1; + process->in = -1; + process->out = -1; + process->clean_on_exit = 1; + process->clean_on_exit_handler = stop_multi_file_filter; + + if (start_command(process)) { + error("cannot fork to run external filter '%s'", cmd); + return NULL; + } + + hashmap_entry_init(entry, strhash(cmd)); + + sigchain_push(SIGPIPE, SIG_IGN); + + err = packet_write_list(process->in, "git-filter-client", "version=2", NULL); + if (err) + goto done; + + err = strcmp(packet_read_line(process->out, NULL), "git-filter-server"); + if (err) { + error("external filter '%s' does not support filter protocol version 2", cmd); + goto done; + } + err = strcmp(packet_read_line(process->out, NULL), "version=2"); + if (err) + goto done; + err = packet_read_line(process->out, NULL) != NULL; + if (err) + goto done; + + err = packet_write_list(process->in, "capability=clean", "capability=smudge", NULL); + + for (;;) { + cap_buf = packet_read_line(process->out, NULL); + if (!cap_buf) + break; + string_list_split_in_place(&cap_list, cap_buf, '=', 1); + + if (cap_list.nr != 2 || strcmp(cap_list.items[0].string, "capability")) + continue; + + cap_name = cap_list.items[1].string; + if (!strcmp(cap_name, "clean")) { + entry->supported_capabilities |= CAP_CLEAN; + } else if (!strcmp(cap_name, "smudge")) { + entry->supported_capabilities |= CAP_SMUDGE; + } else { + warning( + "external filter '%s' requested unsupported filter capability '%s'", + cmd, cap_name + ); + } + + string_list_clear(&cap_list, 0); + } + +done: + sigchain_pop(SIGPIPE); + + if (err || errno == EPIPE) { + error("initialization for external filter '%s' failed", cmd); + kill_multi_file_filter(hashmap, entry); + return NULL; + } + + hashmap_add(hashmap, entry); + return entry; +} + +static int apply_multi_file_filter(const char *path, const char *src, size_t len, + int fd, struct strbuf *dst, const char *cmd, + const unsigned int wanted_capability) +{ + int err; + struct cmd2process *entry; + struct child_process *process; + struct strbuf nbuf = STRBUF_INIT; + struct strbuf filter_status = STRBUF_INIT; + const char *filter_type; + + if (!cmd_process_map_initialized) { + cmd_process_map_initialized = 1; + hashmap_init(&cmd_process_map, (hashmap_cmp_fn) cmd2process_cmp, 0); + entry = NULL; + } else { + entry = find_multi_file_filter_entry(&cmd_process_map, cmd); + } + + fflush(NULL); + + if (!entry) { + entry = start_multi_file_filter(&cmd_process_map, cmd); + if (!entry) + return 0; + } + process = &entry->process; + + if (!(wanted_capability & entry->supported_capabilities)) + return 0; + + if (CAP_CLEAN & wanted_capability) + filter_type = "clean"; + else if (CAP_SMUDGE & wanted_capability) + filter_type = "smudge"; + else + die("unexpected filter type"); + + sigchain_push(SIGPIPE, SIG_IGN); + + assert(strlen(filter_type) < LARGE_PACKET_DATA_MAX - strlen("command=\n")); + err = packet_write_fmt_gently(process->in, "command=%s\n", filter_type); + if (err) + goto done; + + err = strlen(path) > LARGE_PACKET_DATA_MAX - strlen("pathname=\n"); + if (err) { + error("path name too long for external filter"); + goto done; + } + + err = packet_write_fmt_gently(process->in, "pathname=%s\n", path); + if (err) + goto done; + + err = packet_flush_gently(process->in); + if (err) + goto done; + + if (fd >= 0) + err = write_packetized_from_fd(fd, process->in); + else + err = write_packetized_from_buf(src, len, process->in); + if (err) + goto done; + + read_multi_file_filter_status(process->out, &filter_status); + err = strcmp(filter_status.buf, "success"); + if (err) + goto done; + + err = read_packetized_to_strbuf(process->out, &nbuf) < 0; + if (err) + goto done; + + read_multi_file_filter_status(process->out, &filter_status); + err = strcmp(filter_status.buf, "success"); + +done: + sigchain_pop(SIGPIPE); + + if (err || errno == EPIPE) { + if (!strcmp(filter_status.buf, "error")) { + /* The filter signaled a problem with the file. */ + } else if (!strcmp(filter_status.buf, "abort")) { + /* + * The filter signaled a permanent problem. Don't try to filter + * files with the same command for the lifetime of the current + * Git process. + */ + entry->supported_capabilities &= ~wanted_capability; + } else { + /* + * Something went wrong with the protocol filter. + * Force shutdown and restart if another blob requires filtering. + */ + error("external filter '%s' failed", cmd); + kill_multi_file_filter(&cmd_process_map, entry); + } + } else { + strbuf_swap(dst, &nbuf); + } + strbuf_release(&nbuf); + return !err; } static struct convert_driver { @@ -501,9 +781,35 @@ static struct convert_driver { struct convert_driver *next; const char *smudge; const char *clean; + const char *process; int required; } *user_convert, **user_convert_tail; +static int apply_filter(const char *path, const char *src, size_t len, + int fd, struct strbuf *dst, struct convert_driver *drv, + const unsigned int wanted_capability) +{ + const char *cmd = NULL; + + if (!drv) + return 0; + + if (!dst) + return 1; + + if ((CAP_CLEAN & wanted_capability) && !drv->process && drv->clean) + cmd = drv->clean; + else if ((CAP_SMUDGE & wanted_capability) && !drv->process && drv->smudge) + cmd = drv->smudge; + + if (cmd && *cmd) + return apply_single_file_filter(path, src, len, fd, dst, cmd); + else if (drv->process && *drv->process) + return apply_multi_file_filter(path, src, len, fd, dst, drv->process, wanted_capability); + + return 0; +} + static int read_convert_config(const char *var, const char *value, void *cb) { const char *key, *name; @@ -541,6 +847,9 @@ static int read_convert_config(const char *var, const char *value, void *cb) if (!strcmp("clean", key)) return git_config_string(&drv->clean, var, value); + if (!strcmp("process", key)) + return git_config_string(&drv->process, var, value); + if (!strcmp("required", key)) { drv->required = git_config_bool(var, value); return 0; @@ -842,7 +1151,7 @@ int would_convert_to_git_filter_fd(const char *path) if (!ca.drv->required) return 0; - return apply_filter(path, NULL, 0, -1, NULL, ca.drv->clean); + return apply_filter(path, NULL, 0, -1, NULL, ca.drv, CAP_CLEAN); } const char *get_convert_attr_ascii(const char *path) @@ -875,18 +1184,12 @@ int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst, enum safe_crlf checksafe) { int ret = 0; - const char *filter = NULL; - int required = 0; struct conv_attrs ca; convert_attrs(&ca, path); - if (ca.drv) { - filter = ca.drv->clean; - required = ca.drv->required; - } - ret |= apply_filter(path, src, len, -1, dst, filter); - if (!ret && required) + ret |= apply_filter(path, src, len, -1, dst, ca.drv, CAP_CLEAN); + if (!ret && ca.drv && ca.drv->required) die("%s: clean filter '%s' failed", path, ca.drv->name); if (ret && dst) { @@ -908,9 +1211,9 @@ void convert_to_git_filter_fd(const char *path, int fd, struct strbuf *dst, convert_attrs(&ca, path); assert(ca.drv); - assert(ca.drv->clean); + assert(ca.drv->clean || ca.drv->process); - if (!apply_filter(path, NULL, 0, fd, dst, ca.drv->clean)) + if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN)) die("%s: clean filter '%s' failed", path, ca.drv->name); crlf_to_git(path, dst->buf, dst->len, dst, ca.crlf_action, checksafe); @@ -922,15 +1225,9 @@ static int convert_to_working_tree_internal(const char *path, const char *src, int normalizing) { int ret = 0, ret_filter = 0; - const char *filter = NULL; - int required = 0; struct conv_attrs ca; convert_attrs(&ca, path); - if (ca.drv) { - filter = ca.drv->smudge; - required = ca.drv->required; - } ret |= ident_to_worktree(path, src, len, dst, ca.ident); if (ret) { @@ -939,9 +1236,10 @@ static int convert_to_working_tree_internal(const char *path, const char *src, } /* * CRLF conversion can be skipped if normalizing, unless there - * is a smudge filter. The filter might expect CRLFs. + * is a smudge or process filter (even if the process filter doesn't + * support smudge). The filters might expect CRLFs. */ - if (filter || !normalizing) { + if ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) { ret |= crlf_to_worktree(path, src, len, dst, ca.crlf_action); if (ret) { src = dst->buf; @@ -949,8 +1247,8 @@ static int convert_to_working_tree_internal(const char *path, const char *src, } } - ret_filter = apply_filter(path, src, len, -1, dst, filter); - if (!ret_filter && required) + ret_filter = apply_filter(path, src, len, -1, dst, ca.drv, CAP_SMUDGE); + if (!ret_filter && ca.drv && ca.drv->required) die("%s: smudge filter %s failed", path, ca.drv->name); return ret | ret_filter; @@ -1402,7 +1700,7 @@ struct stream_filter *get_stream_filter(const char *path, const unsigned char *s struct stream_filter *filter = NULL; convert_attrs(&ca, path); - if (ca.drv && (ca.drv->smudge || ca.drv->clean)) + if (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean)) return NULL; if (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF) diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index 1e5f16a3a1..46c5937526 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -219,11 +219,11 @@ static void serve_cache(const char *socket_path, int debug) close(fd); } -static const char permissions_advice[] = +static const char permissions_advice[] = N_( "The permissions on your socket directory are too loose; other\n" "users may be able to read your cached credentials. Consider running:\n" "\n" -" chmod 0700 %s"; +" chmod 0700 %s"); static void init_socket_directory(const char *path) { struct stat st; @@ -232,7 +232,7 @@ static void init_socket_directory(const char *path) if (!stat(dir, &st)) { if (st.st_mode & 077) - die(permissions_advice, dir); + die(_(permissions_advice), dir); } else { /* * We must be sure to create the directory with the correct mode, @@ -160,6 +160,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) { static char rpath[PATH_MAX]; static char interp_path[PATH_MAX]; + size_t rlen; const char *path; const char *dir; @@ -187,8 +188,12 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) namlen = slash - dir; restlen -= namlen; loginfo("userpath <%s>, request <%s>, namlen %d, restlen %d, slash <%s>", user_path, dir, namlen, restlen, slash); - snprintf(rpath, PATH_MAX, "%.*s/%s%.*s", - namlen, dir, user_path, restlen, slash); + rlen = snprintf(rpath, sizeof(rpath), "%.*s/%s%.*s", + namlen, dir, user_path, restlen, slash); + if (rlen >= sizeof(rpath)) { + logerror("user-path too large: %s", rpath); + return NULL; + } dir = rpath; } } @@ -207,7 +212,15 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) strbuf_expand(&expanded_path, interpolated_path, expand_path, &context); - strlcpy(interp_path, expanded_path.buf, PATH_MAX); + + rlen = strlcpy(interp_path, expanded_path.buf, + sizeof(interp_path)); + if (rlen >= sizeof(interp_path)) { + logerror("interpolated path too large: %s", + interp_path); + return NULL; + } + strbuf_release(&expanded_path); loginfo("Interpolated dir '%s'", interp_path); @@ -219,7 +232,11 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) logerror("'%s': Non-absolute path denied (base-path active)", dir); return NULL; } - snprintf(rpath, PATH_MAX, "%s%s", base_path, dir); + rlen = snprintf(rpath, sizeof(rpath), "%s%s", base_path, dir); + if (rlen >= sizeof(rpath)) { + logerror("base-path too large: %s", rpath); + return NULL; + } dir = rpath; } @@ -281,7 +298,7 @@ static int daemon_error(const char *dir, const char *msg) { if (!informative_errors) msg = "access denied or repository not exported"; - packet_write(1, "ERR %s: %s", msg, dir); + packet_write_fmt(1, "ERR %s: %s", msg, dir); return -1; } diff --git a/diff-lib.c b/diff-lib.c index 3007c8524c..52447466b5 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -214,6 +214,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option) !is_null_oid(&ce->oid), ce->name, 0); continue; + } else if (revs->diffopt.ita_invisible_in_index && + ce_intent_to_add(ce)) { + diff_addremove(&revs->diffopt, '+', ce->ce_mode, + EMPTY_BLOB_SHA1_BIN, 0, + ce->name, 0); + continue; } changed = match_stat_with_submodule(&revs->diffopt, ce, &st, @@ -379,6 +385,14 @@ static void do_oneway_diff(struct unpack_trees_options *o, struct rev_info *revs = o->unpack_data; int match_missing, cached; + /* i-t-a entries do not actually exist in the index */ + if (revs->diffopt.ita_invisible_in_index && + idx && ce_intent_to_add(idx)) { + idx = NULL; + if (!tree) + return; /* nothing to diff.. */ + } + /* if the entry is not checked out, don't examine work tree */ cached = o->index_only || (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx))); @@ -521,7 +535,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) return 0; } -int index_differs_from(const char *def, int diff_flags) +int index_differs_from(const char *def, int diff_flags, + int ita_invisible_in_index) { struct rev_info rev; struct setup_revision_opt opt; @@ -533,6 +548,7 @@ int index_differs_from(const char *def, int diff_flags) DIFF_OPT_SET(&rev.diffopt, QUICK); DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); rev.diffopt.flags |= diff_flags; + rev.diffopt.ita_invisible_in_index = ita_invisible_in_index; run_diff_index(&rev, 1); if (rev.pending.alloc) free(rev.pending.objects); @@ -43,6 +43,7 @@ static int diff_stat_graph_width; static int diff_dirstat_permille_default = 30; static struct diff_options default_diff_options; static long diff_algorithm; +static unsigned ws_error_highlight_default = WSEH_NEW; static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_RESET, @@ -172,6 +173,43 @@ long parse_algorithm_value(const char *value) return -1; } +static int parse_one_token(const char **arg, const char *token) +{ + const char *rest; + if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) { + *arg = rest; + return 1; + } + return 0; +} + +static int parse_ws_error_highlight(const char *arg) +{ + const char *orig_arg = arg; + unsigned val = 0; + + while (*arg) { + if (parse_one_token(&arg, "none")) + val = 0; + else if (parse_one_token(&arg, "default")) + val = WSEH_NEW; + else if (parse_one_token(&arg, "all")) + val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT; + else if (parse_one_token(&arg, "new")) + val |= WSEH_NEW; + else if (parse_one_token(&arg, "old")) + val |= WSEH_OLD; + else if (parse_one_token(&arg, "context")) + val |= WSEH_CONTEXT; + else { + return -1 - (int)(arg - orig_arg); + } + if (*arg) + arg++; + } + return val; +} + /* * These are to give UI layer defaults. * The core-level commands such as git-diff-files should @@ -256,6 +294,15 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) if (git_diff_heuristic_config(var, value, cb) < 0) return -1; + + if (!strcmp(var, "diff.wserrorhighlight")) { + int val = parse_ws_error_highlight(value); + if (val < 0) + return -1; + ws_error_highlight_default = val; + return 0; + } + if (git_color_config(var, value, cb) < 0) return -1; @@ -3049,6 +3096,21 @@ static int similarity_index(struct diff_filepair *p) return p->score * 100 / MAX_SCORE; } +static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev) +{ + if (startup_info->have_repository) + return find_unique_abbrev(oid->hash, abbrev); + else { + char *hex = oid_to_hex(oid); + if (abbrev < 0) + abbrev = FALLBACK_DEFAULT_ABBREV; + if (abbrev > GIT_SHA1_HEXSZ) + die("BUG: oid abbreviation out of range: %d", abbrev); + hex[abbrev] = '\0'; + return hex; + } +} + static void fill_metainfo(struct strbuf *msg, const char *name, const char *other, @@ -3107,9 +3169,9 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "%s%sindex %s..", line_prefix, set, - find_unique_abbrev(one->oid.hash, abbrev)); - strbuf_add_unique_abbrev(msg, two->oid.hash, abbrev); + strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set, + diff_abbrev_oid(&one->oid, abbrev), + diff_abbrev_oid(&two->oid, abbrev)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); strbuf_addf(msg, "%s\n", reset); @@ -3307,7 +3369,7 @@ void diff_setup(struct diff_options *options) options->rename_limit = -1; options->dirstat_permille = diff_dirstat_permille_default; options->context = diff_context_default; - options->ws_error_highlight = WSEH_NEW; + options->ws_error_highlight = ws_error_highlight_default; DIFF_OPT_SET(options, RENAME_EMPTY); /* pathchange left =NULL by default */ @@ -3421,7 +3483,7 @@ void diff_setup_done(struct diff_options *options) */ read_cache(); } - if (options->abbrev <= 0 || 40 < options->abbrev) + if (40 < options->abbrev) options->abbrev = 40; /* full */ /* @@ -3698,40 +3760,14 @@ static void enable_patch_output(int *fmt) { *fmt |= DIFF_FORMAT_PATCH; } -static int parse_one_token(const char **arg, const char *token) +static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *arg) { - const char *rest; - if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) { - *arg = rest; - return 1; - } - return 0; -} + int val = parse_ws_error_highlight(arg); -static int parse_ws_error_highlight(struct diff_options *opt, const char *arg) -{ - const char *orig_arg = arg; - unsigned val = 0; - while (*arg) { - if (parse_one_token(&arg, "none")) - val = 0; - else if (parse_one_token(&arg, "default")) - val = WSEH_NEW; - else if (parse_one_token(&arg, "all")) - val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT; - else if (parse_one_token(&arg, "new")) - val |= WSEH_NEW; - else if (parse_one_token(&arg, "old")) - val |= WSEH_OLD; - else if (parse_one_token(&arg, "context")) - val |= WSEH_CONTEXT; - else { - error("unknown value after ws-error-highlight=%.*s", - (int)(arg - orig_arg), orig_arg); - return 0; - } - if (*arg) - arg++; + if (val < 0) { + error("unknown value after ws-error-highlight=%.*s", + -1 - val, arg); + return 0; } opt->ws_error_highlight = val; return 1; @@ -3950,7 +3986,11 @@ int diff_opt_parse(struct diff_options *options, else if (skip_prefix(arg, "--submodule=", &arg)) return parse_submodule_opt(options, arg); else if (skip_prefix(arg, "--ws-error-highlight=", &arg)) - return parse_ws_error_highlight(options, arg); + return parse_ws_error_highlight_opt(options, arg); + else if (!strcmp(arg, "--ita-invisible-in-index")) + options->ita_invisible_in_index = 1; + else if (!strcmp(arg, "--ita-visible-in-index")) + options->ita_invisible_in_index = 0; /* misc options */ else if (!strcmp(arg, "-z")) @@ -4136,27 +4176,46 @@ void diff_free_filepair(struct diff_filepair *p) free(p); } -/* This is different from find_unique_abbrev() in that - * it stuffs the result with dots for alignment. - */ -const char *diff_unique_abbrev(const unsigned char *sha1, int len) +const char *diff_aligned_abbrev(const struct object_id *oid, int len) { int abblen; const char *abbrev; - if (len == 40) - return sha1_to_hex(sha1); - abbrev = find_unique_abbrev(sha1, len); + if (len == GIT_SHA1_HEXSZ) + return oid_to_hex(oid); + + abbrev = diff_abbrev_oid(oid, len); abblen = strlen(abbrev); - if (abblen < 37) { - static char hex[41]; + + /* + * In well-behaved cases, where the abbbreviated result is the + * same as the requested length, append three dots after the + * abbreviation (hence the whole logic is limited to the case + * where abblen < 37); when the actual abbreviated result is a + * bit longer than the requested length, we reduce the number + * of dots so that they match the well-behaved ones. However, + * if the actual abbreviation is longer than the requested + * length by more than three, we give up on aligning, and add + * three dots anyway, to indicate that the output is not the + * full object name. Yes, this may be suboptimal, but this + * appears only in "diff --raw --abbrev" output and it is not + * worth the effort to change it now. Note that this would + * likely to work fine when the automatic sizing of default + * abbreviation length is used--we would be fed -1 in "len" in + * that case, and will end up always appending three-dots, but + * the automatic sizing is supposed to give abblen that ensures + * uniqueness across all objects (statistically speaking). + */ + if (abblen < GIT_SHA1_HEXSZ - 3) { + static char hex[GIT_SHA1_HEXSZ + 1]; if (len < abblen && abblen <= len + 2) xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, ".."); else xsnprintf(hex, sizeof(hex), "%s...", abbrev); return hex; } - return sha1_to_hex(sha1); + + return oid_to_hex(oid); } static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) @@ -4167,9 +4226,9 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) fprintf(opt->file, "%s", diff_line_prefix(opt)); if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) { fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode, - diff_unique_abbrev(p->one->oid.hash, opt->abbrev)); + diff_aligned_abbrev(&p->one->oid, opt->abbrev)); fprintf(opt->file, "%s ", - diff_unique_abbrev(p->two->oid.hash, opt->abbrev)); + diff_aligned_abbrev(&p->two->oid, opt->abbrev)); } if (p->score) { fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p), @@ -4638,25 +4697,25 @@ static int is_summary_empty(const struct diff_queue_struct *q) } static const char rename_limit_warning[] = -"inexact rename detection was skipped due to too many files."; +N_("inexact rename detection was skipped due to too many files."); static const char degrade_cc_to_c_warning[] = -"only found copies from modified paths due to too many files."; +N_("only found copies from modified paths due to too many files."); static const char rename_limit_advice[] = -"you may want to set your %s variable to at least " -"%d and retry the command."; +N_("you may want to set your %s variable to at least " + "%d and retry the command."); void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) { if (degraded_cc) - warning(degrade_cc_to_c_warning); + warning(_(degrade_cc_to_c_warning)); else if (needed) - warning(rename_limit_warning); + warning(_(rename_limit_warning)); else return; if (0 < needed && needed < 32767) - warning(rename_limit_advice, varname, needed); + warning(_(rename_limit_advice), varname, needed); } void diff_flush(struct diff_options *options) @@ -146,6 +146,7 @@ struct diff_options { int dirstat_permille; int setup; int abbrev; + int ita_invisible_in_index; /* white-space error highlighting */ #define WSEH_NEW 1 #define WSEH_CONTEXT 2 @@ -340,7 +341,11 @@ extern void diff_warn_rename_limit(const char *varname, int needed, int degraded #define DIFF_STATUS_FILTER_AON '*' #define DIFF_STATUS_FILTER_BROKEN 'B' -extern const char *diff_unique_abbrev(const unsigned char *, int); +/* + * This is different from find_unique_abbrev() in that + * it stuffs the result with dots for alignment. + */ +extern const char *diff_aligned_abbrev(const struct object_id *sha1, int); /* do not report anything on removed paths */ #define DIFF_SILENT_ON_REMOVED 01 @@ -356,7 +361,7 @@ extern int diff_result_code(struct diff_options *, int); extern void diff_no_index(struct rev_info *, int, const char **); -extern int index_differs_from(const char *def, int diff_flags); +extern int index_differs_from(const char *def, int diff_flags, int ita_invisible_in_index); /* * Fill the contents of the filespec "df", respecting any textconv defined by @@ -207,8 +207,9 @@ int within_depth(const char *name, int namelen, return 1; } -#define DO_MATCH_EXCLUDE 1 -#define DO_MATCH_DIRECTORY 2 +#define DO_MATCH_EXCLUDE (1<<0) +#define DO_MATCH_DIRECTORY (1<<1) +#define DO_MATCH_SUBMODULE (1<<2) /* * Does 'match' match the given name? @@ -283,6 +284,32 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix, item->nowildcard_len - prefix)) return MATCHED_FNMATCH; + /* Perform checks to see if "name" is a super set of the pathspec */ + if (flags & DO_MATCH_SUBMODULE) { + /* name is a literal prefix of the pathspec */ + if ((namelen < matchlen) && + (match[namelen] == '/') && + !ps_strncmp(item, match, name, namelen)) + return MATCHED_RECURSIVELY; + + /* name" doesn't match up to the first wild character */ + if (item->nowildcard_len < item->len && + ps_strncmp(item, match, name, + item->nowildcard_len - prefix)) + return 0; + + /* + * Here is where we would perform a wildmatch to check if + * "name" can be matched as a directory (or a prefix) against + * the pathspec. Since wildmatch doesn't have this capability + * at the present we have to punt and say that it is a match, + * potentially returning a false positive + * The submodules themselves will be able to perform more + * accurate matching to determine if the pathspec matches. + */ + return MATCHED_RECURSIVELY; + } + return 0; } @@ -386,6 +413,21 @@ int match_pathspec(const struct pathspec *ps, return negative ? 0 : positive; } +/** + * Check if a submodule is a superset of the pathspec + */ +int submodule_path_match(const struct pathspec *ps, + const char *submodule_name, + char *seen) +{ + int matched = do_match_pathspec(ps, submodule_name, + strlen(submodule_name), + 0, seen, + DO_MATCH_DIRECTORY | + DO_MATCH_SUBMODULE); + return matched; +} + int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix) @@ -2195,8 +2237,6 @@ static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude") void setup_standard_excludes(struct dir_struct *dir) { - const char *path; - dir->exclude_per_dir = ".gitignore"; /* core.excludefile defaulting to $XDG_HOME/git/ignore */ @@ -2207,10 +2247,12 @@ void setup_standard_excludes(struct dir_struct *dir) dir->untracked ? &dir->ss_excludes_file : NULL); /* per repository user preference */ - path = git_path_info_exclude(); - if (!access_or_warn(path, R_OK, 0)) - add_excludes_from_file_1(dir, path, - dir->untracked ? &dir->ss_info_exclude : NULL); + if (startup_info->have_repository) { + const char *path = git_path_info_exclude(); + if (!access_or_warn(path, R_OK, 0)) + add_excludes_from_file_1(dir, path, + dir->untracked ? &dir->ss_info_exclude : NULL); + } } int remove_path(const char *name) @@ -304,6 +304,10 @@ extern int git_fnmatch(const struct pathspec_item *item, const char *pattern, const char *string, int prefix); +extern int submodule_path_match(const struct pathspec *ps, + const char *submodule_name, + char *seen); + static inline int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec, char *seen) diff --git a/environment.c b/environment.c index cd5aa57179..0935ec696e 100644 --- a/environment.c +++ b/environment.c @@ -16,7 +16,7 @@ int trust_executable_bit = 1; int trust_ctime = 1; int check_stat = 1; int has_symlinks = 1; -int minimum_abbrev = 4, default_abbrev = 7; +int minimum_abbrev = 4, default_abbrev = -1; int ignore_case; int assume_unchanged; int prefer_symlink_refs; @@ -99,6 +99,8 @@ static char *work_tree; static const char *namespace; static size_t namespace_len; +static const char *super_prefix; + static const char *git_dir, *git_common_dir; static char *git_object_dir, *git_index_file, *git_graft_file; int git_db_env, git_index_env, git_graft_env, git_common_dir_env; @@ -119,6 +121,7 @@ const char * const local_repo_env[] = { NO_REPLACE_OBJECTS_ENVIRONMENT, GIT_REPLACE_REF_BASE_ENVIRONMENT, GIT_PREFIX_ENVIRONMENT, + GIT_SUPER_PREFIX_ENVIRONMENT, GIT_SHALLOW_FILE_ENVIRONMENT, GIT_COMMON_DIR_ENVIRONMENT, NULL @@ -228,6 +231,16 @@ const char *strip_namespace(const char *namespaced_ref) return namespaced_ref + namespace_len; } +const char *get_super_prefix(void) +{ + static int initialized; + if (!initialized) { + super_prefix = getenv(GIT_SUPER_PREFIX_ENVIRONMENT); + initialized = 1; + } + return super_prefix; +} + static int git_work_tree_initialized; /* diff --git a/git-compat-util.h b/git-compat-util.h index 43718dabae..49ca28cb6b 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -851,11 +851,14 @@ static inline void copy_array(void *dst, const void *src, size_t n, size_t size) * times, and it must be assignable as an lvalue. */ #define FLEX_ALLOC_MEM(x, flexname, buf, len) do { \ - (x) = NULL; /* silence -Wuninitialized for offset calculation */ \ - (x) = xalloc_flex(sizeof(*(x)), (char *)(&((x)->flexname)) - (char *)(x), (buf), (len)); \ + size_t flex_array_len_ = (len); \ + (x) = xcalloc(1, st_add3(sizeof(*(x)), flex_array_len_, 1)); \ + memcpy((void *)(x)->flexname, (buf), flex_array_len_); \ } while (0) #define FLEXPTR_ALLOC_MEM(x, ptrname, buf, len) do { \ - (x) = xalloc_flex(sizeof(*(x)), sizeof(*(x)), (buf), (len)); \ + size_t flex_array_len_ = (len); \ + (x) = xcalloc(1, st_add3(sizeof(*(x)), flex_array_len_, 1)); \ + memcpy((x) + 1, (buf), flex_array_len_); \ (x)->ptrname = (void *)((x)+1); \ } while(0) #define FLEX_ALLOC_STR(x, flexname, str) \ @@ -863,14 +866,6 @@ static inline void copy_array(void *dst, const void *src, size_t n, size_t size) #define FLEXPTR_ALLOC_STR(x, ptrname, str) \ FLEXPTR_ALLOC_MEM((x), ptrname, (str), strlen(str)) -static inline void *xalloc_flex(size_t base_len, size_t offset, - const void *src, size_t src_len) -{ - unsigned char *ret = xcalloc(1, st_add3(base_len, src_len, 1)); - memcpy(ret + offset, src, src_len); - return ret; -} - static inline char *xstrdup_or_null(const char *str) { return str ? xstrdup(str) : NULL; diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN index a88b6824b9..92373d251a 100755 --- a/git-gui/GIT-VERSION-GEN +++ b/git-gui/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=0.20.GITGUI +DEF_VER=0.21.GITGUI LF=' ' diff --git a/git-gui/Makefile b/git-gui/Makefile index 4f00bdd3d6..fe30be38dc 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -259,7 +259,7 @@ lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS rm -f $@ ; \ echo '# Autogenerated by git-gui Makefile' >$@ && \ echo >>$@ && \ - $(foreach p,$(PRELOAD_FILES) $(ALL_LIBFILES),echo '$(subst lib/,,$p)' >>$@ &&) \ + $(foreach p,$(PRELOAD_FILES) $(sort $(ALL_LIBFILES)),echo '$(subst lib/,,$p)' >>$@ &&) \ echo >>$@ ; \ fi diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 11048c7a0e..5bc21b878d 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -275,6 +275,10 @@ proc is_Cygwin {} { set _iscygwin 0 } else { set _iscygwin 1 + # Handle MSys2 which is only cygwin when MSYSTEM is MSYS. + if {[info exists ::env(MSYSTEM)] && $::env(MSYSTEM) ne "MSYS"} { + set _iscygwin 0 + } } } else { set _iscygwin 0 @@ -530,28 +534,10 @@ proc _lappend_nice {cmd_var} { } proc git {args} { - set opt [list] - - while {1} { - switch -- [lindex $args 0] { - --nice { - _lappend_nice opt - } - - default { - break - } - - } - - set args [lrange $args 1 end] - } - - set cmdp [_git_cmd [lindex $args 0]] - set args [lrange $args 1 end] - - _trace_exec [concat $opt $cmdp $args] - set result [eval exec $opt $cmdp $args] + set fd [eval [list git_read] $args] + fconfigure $fd -translation binary -encoding utf-8 + set result [string trimright [read $fd] "\n"] + close $fd if {$::_trace} { puts stderr "< $result" } @@ -1107,7 +1093,7 @@ git-version proc _parse_config {arr_name args} { [list git_read config] \ $args \ [list --null --list]] - fconfigure $fd_rc -translation binary + fconfigure $fd_rc -translation binary -encoding utf-8 set buf [read $fd_rc] close $fd_rc } @@ -1616,11 +1602,13 @@ proc run_prepare_commit_msg_hook {} { if {[file isfile [gitdir MERGE_MSG]]} { set pcm_source "merge" set fd_mm [open [gitdir MERGE_MSG] r] + fconfigure $fd_mm -encoding utf-8 puts -nonewline $fd_pcm [read $fd_mm] close $fd_mm } elseif {[file isfile [gitdir SQUASH_MSG]]} { set pcm_source "squash" set fd_sm [open [gitdir SQUASH_MSG] r] + fconfigure $fd_sm -encoding utf-8 puts -nonewline $fd_pcm [read $fd_sm] close $fd_sm } else { @@ -1685,7 +1673,7 @@ proc read_diff_index {fd after} { set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] set p [string range $buf_rdi $z1 [expr {$z2 - 1}]] merge_state \ - [encoding convertfrom $p] \ + [encoding convertfrom utf-8 $p] \ [lindex $i 4]? \ [list [lindex $i 0] [lindex $i 2]] \ [list] @@ -1718,7 +1706,7 @@ proc read_diff_files {fd after} { set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] set p [string range $buf_rdf $z1 [expr {$z2 - 1}]] merge_state \ - [encoding convertfrom $p] \ + [encoding convertfrom utf-8 $p] \ ?[lindex $i 4] \ [list] \ [list [lindex $i 0] [lindex $i 2]] @@ -1741,7 +1729,7 @@ proc read_ls_others {fd after} { set pck [split $buf_rlo "\0"] set buf_rlo [lindex $pck end] foreach p [lrange $pck 0 end-1] { - set p [encoding convertfrom $p] + set p [encoding convertfrom utf-8 $p] if {[string index $p end] eq {/}} { set p [string range $p 0 end-1] } @@ -2505,13 +2493,28 @@ proc force_first_diff {after} { } } -proc toggle_or_diff {w x y} { +proc toggle_or_diff {mode w args} { global file_states file_lists current_diff_path ui_index ui_workdir global last_clicked selected_paths - set pos [split [$w index @$x,$y] .] - set lno [lindex $pos 0] - set col [lindex $pos 1] + if {$mode eq "click"} { + foreach {x y} $args break + set pos [split [$w index @$x,$y] .] + foreach {lno col} $pos break + } else { + if {$last_clicked ne {}} { + set lno [lindex $last_clicked 1] + } else { + set lno [expr {int([lindex [$w tag ranges in_diff] 0])}] + } + if {$mode eq "toggle"} { + set col 0; set y 2 + } else { + incr lno [expr {$mode eq "up" ? -1 : 1}] + set col 1 + } + } + set path [lindex $file_lists($w) [expr {$lno - 1}]] if {$path eq {}} { set last_clicked {} @@ -2519,6 +2522,7 @@ proc toggle_or_diff {w x y} { } set last_clicked [list $w $lno] + focus $w array unset selected_paths $ui_index tag remove in_sel 0.0 end $ui_workdir tag remove in_sel 0.0 end @@ -2598,7 +2602,7 @@ proc add_range_to_selection {w x y} { global file_lists last_clicked selected_paths if {[lindex $last_clicked 0] ne $w} { - toggle_or_diff $w $x $y + toggle_or_diff click $w $x $y return } @@ -3007,7 +3011,7 @@ bind all <$M1B-Key-W> {destroy [winfo toplevel %W]} set subcommand_args {} proc usage {} { - set s "usage: $::argv0 $::subcommand $::subcommand_args" + set s "[mc usage:] $::argv0 $::subcommand $::subcommand_args" if {[tk windowingsystem] eq "win32"} { wm withdraw . tk_messageBox -icon info -message $s \ @@ -3139,7 +3143,7 @@ gui { # fall through to setup UI for commits } default { - set err "usage: $argv0 \[{blame|browser|citool}\]" + set err "[mc usage:] $argv0 \[{blame|browser|citool}\]" if {[tk windowingsystem] eq "win32"} { wm withdraw . tk_messageBox -icon error -message $err \ @@ -3178,16 +3182,38 @@ if {$use_ttk} { } pack .vpane -anchor n -side top -fill both -expand 1 +# -- Working Directory File List + +textframe .vpane.files.workdir -height 100 -width 200 +tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ + -background lightsalmon -foreground black +ttext $ui_workdir -background white -foreground black \ + -borderwidth 0 \ + -width 20 -height 10 \ + -wrap none \ + -takefocus 1 -highlightthickness 1\ + -cursor $cursor_ptr \ + -xscrollcommand {.vpane.files.workdir.sx set} \ + -yscrollcommand {.vpane.files.workdir.sy set} \ + -state disabled +${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] +${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] +pack .vpane.files.workdir.title -side top -fill x +pack .vpane.files.workdir.sx -side bottom -fill x +pack .vpane.files.workdir.sy -side right -fill y +pack $ui_workdir -side left -fill both -expand 1 + # -- Index File List # -${NS}::frame .vpane.files.index -height 100 -width 200 +textframe .vpane.files.index -height 100 -width 200 tlabel .vpane.files.index.title \ -text [mc "Staged Changes (Will Commit)"] \ -background lightgreen -foreground black -text $ui_index -background white -foreground black \ +ttext $ui_index -background white -foreground black \ -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ + -takefocus 1 -highlightthickness 1\ -cursor $cursor_ptr \ -xscrollcommand {.vpane.files.index.sx set} \ -yscrollcommand {.vpane.files.index.sy set} \ @@ -3199,26 +3225,8 @@ pack .vpane.files.index.sx -side bottom -fill x pack .vpane.files.index.sy -side right -fill y pack $ui_index -side left -fill both -expand 1 -# -- Working Directory File List +# -- Insert the workdir and index into the panes # -${NS}::frame .vpane.files.workdir -height 100 -width 200 -tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ - -background lightsalmon -foreground black -text $ui_workdir -background white -foreground black \ - -borderwidth 0 \ - -width 20 -height 10 \ - -wrap none \ - -cursor $cursor_ptr \ - -xscrollcommand {.vpane.files.workdir.sx set} \ - -yscrollcommand {.vpane.files.workdir.sy set} \ - -state disabled -${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] -${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] -pack .vpane.files.workdir.title -side top -fill x -pack .vpane.files.workdir.sx -side bottom -fill x -pack .vpane.files.workdir.sy -side right -fill y -pack $ui_workdir -side left -fill both -expand 1 - .vpane.files add .vpane.files.workdir .vpane.files add .vpane.files.index if {!$use_ttk} { @@ -3301,7 +3309,7 @@ if {![is_enabled nocommit]} { # ${NS}::frame .vpane.lower.commarea.buffer ${NS}::frame .vpane.lower.commarea.buffer.header -set ui_comm .vpane.lower.commarea.buffer.t +set ui_comm .vpane.lower.commarea.buffer.frame.t set ui_coml .vpane.lower.commarea.buffer.header.l if {![is_enabled nocommit]} { @@ -3344,20 +3352,25 @@ if {![is_enabled nocommit]} { pack .vpane.lower.commarea.buffer.header.new -side right } -text $ui_comm -background white -foreground black \ +textframe .vpane.lower.commarea.buffer.frame +ttext $ui_comm -background white -foreground black \ -borderwidth 1 \ -undo true \ -maxundo 20 \ -autoseparators true \ + -takefocus 1 \ + -highlightthickness 1 \ -relief sunken \ -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \ -font font_diff \ - -yscrollcommand {.vpane.lower.commarea.buffer.sby set} -${NS}::scrollbar .vpane.lower.commarea.buffer.sby \ + -yscrollcommand {.vpane.lower.commarea.buffer.frame.sby set} +${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sby \ -command [list $ui_comm yview] -pack .vpane.lower.commarea.buffer.header -side top -fill x -pack .vpane.lower.commarea.buffer.sby -side right -fill y + +pack .vpane.lower.commarea.buffer.frame.sby -side right -fill y pack $ui_comm -side left -fill y +pack .vpane.lower.commarea.buffer.header -side top -fill x +pack .vpane.lower.commarea.buffer.frame -side left -fill y pack .vpane.lower.commarea.buffer -side left -fill y # -- Commit Message Buffer Context Menu @@ -3455,12 +3468,13 @@ bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" # -- Diff Body # -${NS}::frame .vpane.lower.diff.body +textframe .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t -text $ui_diff -background white -foreground black \ +ttext $ui_diff -background white -foreground black \ -borderwidth 0 \ -width 80 -height 5 -wrap none \ -font font_diff \ + -takefocus 1 -highlightthickness 1 \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ -yscrollcommand {.vpane.lower.diff.body.sby set} \ -state disabled @@ -3815,10 +3829,10 @@ bind . <$M1B-Key-r> ui_do_rescan bind . <$M1B-Key-R> ui_do_rescan bind . <$M1B-Key-s> do_signoff bind . <$M1B-Key-S> do_signoff -bind . <$M1B-Key-t> do_add_selection -bind . <$M1B-Key-T> do_add_selection -bind . <$M1B-Key-u> do_unstage_selection -bind . <$M1B-Key-U> do_unstage_selection +bind . <$M1B-Key-t> { toggle_or_diff toggle %W } +bind . <$M1B-Key-T> { toggle_or_diff toggle %W } +bind . <$M1B-Key-u> { toggle_or_diff toggle %W } +bind . <$M1B-Key-U> { toggle_or_diff toggle %W } bind . <$M1B-Key-j> do_revert_selection bind . <$M1B-Key-J> do_revert_selection bind . <$M1B-Key-i> do_add_all @@ -3830,9 +3844,11 @@ bind . <$M1B-Key-plus> {show_more_context;break} bind . <$M1B-Key-KP_Add> {show_more_context;break} bind . <$M1B-Key-Return> do_commit foreach i [list $ui_index $ui_workdir] { - bind $i <Button-1> "toggle_or_diff $i %x %y; break" - bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break" - bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break" + bind $i <Button-1> { toggle_or_diff click %W %x %y; break } + bind $i <$M1B-Button-1> { add_one_to_selection %W %x %y; break } + bind $i <Shift-Button-1> { add_range_to_selection %W %x %y; break } + bind $i <Key-Up> { toggle_or_diff up %W; break } + bind $i <Key-Down> { toggle_or_diff down %W; break } } unset i diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index b1d15f4621..a1aeb8b96e 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -70,7 +70,7 @@ constructor new {i_commit i_path i_jump} { set path $i_path make_toplevel top w - wm title $top [append "[appname] ([reponame]): " [mc "File Viewer"]] + wm title $top [mc "%s (%s): File Viewer" [appname] [reponame]] set font_w [font measure font_diff "0"] diff --git a/git-gui/lib/branch_checkout.tcl b/git-gui/lib/branch_checkout.tcl index 2e459a8297..d06037decc 100644 --- a/git-gui/lib/branch_checkout.tcl +++ b/git-gui/lib/branch_checkout.tcl @@ -13,7 +13,7 @@ constructor dialog {} { global use_ttk NS make_dialog top w wm withdraw $w - wm title $top [append "[appname] ([reponame]): " [mc "Checkout Branch"]] + wm title $top [mc "%s (%s): Checkout Branch" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } diff --git a/git-gui/lib/branch_create.tcl b/git-gui/lib/branch_create.tcl index 4bb907705c..ba367d551d 100644 --- a/git-gui/lib/branch_create.tcl +++ b/git-gui/lib/branch_create.tcl @@ -20,7 +20,7 @@ constructor dialog {} { make_dialog top w wm withdraw $w - wm title $top [append "[appname] ([reponame]): " [mc "Create Branch"]] + wm title $top [mc "%s (%s): Create Branch" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } diff --git a/git-gui/lib/branch_delete.tcl b/git-gui/lib/branch_delete.tcl index 867938ec6a..a5051637bb 100644 --- a/git-gui/lib/branch_delete.tcl +++ b/git-gui/lib/branch_delete.tcl @@ -13,7 +13,7 @@ constructor dialog {} { make_dialog top w wm withdraw $w - wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch"]] + wm title $top [mc "%s (%s): Delete Branch" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } @@ -128,7 +128,7 @@ method _delete {} { set b [lindex $i 0] set o [lindex $i 1] if {[catch {git branch -D $b} err]} { - append failed " - $b: $err\n" + append failed [mc " - %s:" $b] " $err\n" } } diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl index 6e510ec2e3..3a2d79a9cc 100644 --- a/git-gui/lib/branch_rename.tcl +++ b/git-gui/lib/branch_rename.tcl @@ -12,7 +12,7 @@ constructor dialog {} { make_dialog top w wm withdraw $w - wm title $top [append "[appname] ([reponame]): " [mc "Rename Branch"]] + wm title $top [mc "%s (%s): Rename Branch" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index 0328338fda..a982983667 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@ -24,7 +24,7 @@ constructor new {commit {path {}}} { global cursor_ptr M1B use_ttk NS make_dialog top w wm withdraw $top - wm title $top [append "[appname] ([reponame]): " [mc "File Browser"]] + wm title $top [mc "%s (%s): File Browser" [appname] [reponame]] if {$path ne {}} { if {[string index $path end] ne {/}} { @@ -197,7 +197,7 @@ method _ls {tree_id {name {}}} { $w conf -state disabled set fd [git_read ls-tree -z $tree_id] - fconfigure $fd -blocking 0 -translation binary -encoding binary + fconfigure $fd -blocking 0 -translation binary -encoding utf-8 fileevent $fd readable [cb _read $fd] } @@ -272,7 +272,7 @@ constructor dialog {} { global use_ttk NS make_dialog top w wm withdraw $top - wm title $top [append "[appname] ([reponame]): " [mc "Browse Branch Files"]] + wm title $top [mc "%s (%s): Browse Branch Files" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" wm transient $top . diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 01d2cc280b..83620b7cbc 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -2,7 +2,7 @@ # Copyright (C) 2006, 2007 Shawn Pearce proc load_last_commit {} { - global HEAD PARENT MERGE_HEAD commit_type ui_comm + global HEAD PARENT MERGE_HEAD commit_type ui_comm commit_author global repo_config if {[llength $PARENT] == 0} { @@ -34,6 +34,8 @@ You are currently in the middle of a merge that has not been fully completed. Y lappend parents [string range $line 7 end] } elseif {[string match {encoding *} $line]} { set enc [string tolower [string range $line 9 end]] + } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} { + set commit_author [list name $name email $email date $time] } } set msg [read $fd] @@ -106,9 +108,10 @@ proc do_signoff {} { } proc create_new_commit {} { - global commit_type ui_comm + global commit_type ui_comm commit_author set commit_type normal + unset -nocomplain commit_author $ui_comm delete 0.0 end $ui_comm edit reset $ui_comm edit modified false @@ -322,11 +325,12 @@ proc commit_writetree {curHEAD msg_p} { } proc commit_committree {fd_wt curHEAD msg_p} { - global HEAD PARENT MERGE_HEAD commit_type + global HEAD PARENT MERGE_HEAD commit_type commit_author global current_branch global ui_comm selected_commit_type global file_states selected_paths rescan_active global repo_config + global env gets $fd_wt tree_id if {[catch {close $fd_wt} err]} { @@ -366,6 +370,9 @@ A rescan will be automatically started now. } } + if {[info exists commit_author]} { + set old_author [commit_author_ident $commit_author] + } # -- Create the commit. # set cmd [list commit-tree $tree_id] @@ -381,8 +388,14 @@ A rescan will be automatically started now. error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"] ui_status [mc "Commit failed."] unlock_index + unset -nocomplain commit_author + commit_author_reset $old_author return } + if {[info exists commit_author]} { + unset -nocomplain commit_author + commit_author_reset $old_author + } # -- Update the HEAD ref. # @@ -509,3 +522,20 @@ proc commit_postcommit_wait {fd_ph cmt_id} { } fconfigure $fd_ph -blocking 0 } + +proc commit_author_ident {details} { + global env + array set author $details + set old [array get env GIT_AUTHOR_*] + set env(GIT_AUTHOR_NAME) $author(name) + set env(GIT_AUTHOR_EMAIL) $author(email) + set env(GIT_AUTHOR_DATE) $author(date) + return $old +} +proc commit_author_reset {details} { + global env + unset env(GIT_AUTHOR_NAME) env(GIT_AUTHOR_EMAIL) env(GIT_AUTHOR_DATE) + if {$details ne {}} { + array set env $details + } +} diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl index 1f187ed286..85783081e0 100644 --- a/git-gui/lib/database.tcl +++ b/git-gui/lib/database.tcl @@ -54,7 +54,7 @@ proc do_stats {} { set value "$value[lindex $s 2]" } - ${NS}::label $w.stat.l_$name -text "$label:" -anchor w + ${NS}::label $w.stat.l_$name -text [mc "%s:" $label] -anchor w ${NS}::label $w.stat.v_$name -text $value -anchor w grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} } @@ -63,7 +63,7 @@ proc do_stats {} { bind $w <Visibility> "grab $w; focus $w.buttons.close" bind $w <Key-Escape> [list destroy $w] bind $w <Key-Return> [list destroy $w] - wm title $w [append "[appname] ([reponame]): " [mc "Database Statistics"]] + wm title $w [mc "%s (%s): Database Statistics" [appname] [reponame]] wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index 0d56986215..4cae10a4c7 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -127,6 +127,9 @@ proc show_diff {path w {lno {}} {scroll_pos {}} {callback {}}} { } else { start_show_diff $cont_info } + + global current_diff_path selected_paths + set selected_paths($current_diff_path) 1 } proc show_unmerged_diff {cont_info} { @@ -220,10 +223,9 @@ proc show_other_diff {path w m cont_info} { } $ui_diff conf -state normal if {$type eq {submodule}} { - $ui_diff insert end [append \ - "* " \ - [mc "Git Repository (subproject)"] \ - "\n"] d_info + $ui_diff insert end \ + "* [mc "Git Repository (subproject)"]\n" \ + d_info } elseif {![catch {set type [exec file $path]}]} { set n [string length $path] if {[string equal -length $n $path $type]} { @@ -608,7 +610,7 @@ proc apply_hunk {x y} { puts -nonewline $p $current_diff_header puts -nonewline $p [$ui_diff get $s_lno $e_lno] close $p} err]} { - error_popup [append $failed_msg "\n\n$err"] + error_popup "$failed_msg\n\n$err" unlock_index return } @@ -826,7 +828,7 @@ proc apply_range_or_line {x y} { puts -nonewline $p $current_diff_header puts -nonewline $p $wholepatch close $p} err]} { - error_popup [append $failed_msg "\n\n$err"] + error_popup "$failed_msg\n\n$err" } unlock_index diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl index c0fa69af56..8968a57f33 100644 --- a/git-gui/lib/error.tcl +++ b/git-gui/lib/error.tcl @@ -17,7 +17,7 @@ proc error_popup {msg} { set cmd [list tk_messageBox \ -icon error \ -type ok \ - -title [append "$title: " [mc "error"]] \ + -title [mc "%s: error" $title] \ -message $msg] if {[winfo ismapped [_error_parent]]} { lappend cmd -parent [_error_parent] @@ -33,7 +33,7 @@ proc warn_popup {msg} { set cmd [list tk_messageBox \ -icon warning \ -type ok \ - -title [append "$title: " [mc "warning"]] \ + -title [mc "%s: warning" $title] \ -message $msg] if {[winfo ismapped [_error_parent]]} { lappend cmd -parent [_error_parent] @@ -77,7 +77,7 @@ proc hook_failed_popup {hook msg {is_fatal 1}} { wm withdraw $w ${NS}::frame $w.m - ${NS}::label $w.m.l1 -text "$hook hook failed:" \ + ${NS}::label $w.m.l1 -text [mc "%s hook failed:" $hook] \ -anchor w \ -justify left \ -font font_uibold @@ -113,7 +113,7 @@ proc hook_failed_popup {hook msg {is_fatal 1}} { bind $w <Visibility> "grab $w; focus $w" bind $w <Key-Return> "destroy $w" - wm title $w [strcat "[appname] ([reponame]): " [mc "error"]] + wm title $w [mc "%s (%s): error" [appname] [reponame]] wm deiconify $w tkwait window $w } diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index 3a3e534aef..b588db11d9 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -115,7 +115,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch after} { set info [lindex $s 2] if {$info eq {}} continue - puts -nonewline $fd "$info\t[encoding convertto $path]\0" + puts -nonewline $fd "$info\t[encoding convertto utf-8 $path]\0" display_file $path $new } @@ -186,7 +186,7 @@ proc write_update_index {fd pathList totalCnt batch after} { ?M {set new M_} ?? {continue} } - puts -nonewline $fd "[encoding convertto $path]\0" + puts -nonewline $fd "[encoding convertto utf-8 $path]\0" display_file $path $new } @@ -247,7 +247,7 @@ proc write_checkout_index {fd pathList totalCnt batch after} { ?M - ?T - ?D { - puts -nonewline $fd "[encoding convertto $path]\0" + puts -nonewline $fd "[encoding convertto utf-8 $path]\0" display_file $path ?_ } } diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl index 5ab6f8f102..9f253db5b3 100644 --- a/git-gui/lib/merge.tcl +++ b/git-gui/lib/merge.tcl @@ -112,7 +112,16 @@ method _start {} { close $fh set _last_merged_branch $branch - set cmd [list git merge --strategy=recursive FETCH_HEAD] + if {[git-version >= "2.5.0"]} { + set cmd [list git merge --strategy=recursive FETCH_HEAD] + } else { + set cmd [list git] + lappend cmd merge + lappend cmd --strategy=recursive + lappend cmd [git fmt-merge-msg <[gitdir FETCH_HEAD]] + lappend cmd HEAD + lappend cmd $name + } ui_status [mc "Merging %s and %s..." $current_branch $stitle] set cons [console::new [mc "Merge"] "merge $stitle"] @@ -144,7 +153,7 @@ constructor dialog {} { } make_dialog top w - wm title $top [append "[appname] ([reponame]): " [mc "Merge"]] + wm title $top [mc "%s (%s): Merge" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index b5b6b2fea6..e43971bfa3 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -179,7 +179,7 @@ proc do_options {} { i-* { regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max ${NS}::frame $w.$f.$optid - ${NS}::label $w.$f.$optid.l -text "$text:" + ${NS}::label $w.$f.$optid.l -text [mc "%s:" $text] pack $w.$f.$optid.l -side left -anchor w -fill x tspinbox $w.$f.$optid.v \ -textvariable ${f}_config_new($name) \ @@ -194,7 +194,7 @@ proc do_options {} { c - t { ${NS}::frame $w.$f.$optid - ${NS}::label $w.$f.$optid.l -text "$text:" + ${NS}::label $w.$f.$optid.l -text [mc "%s:" $text] ${NS}::entry $w.$f.$optid.v \ -width 20 \ -textvariable ${f}_config_new($name) @@ -217,7 +217,7 @@ proc do_options {} { s { set opts [eval [lindex $option 3]] ${NS}::frame $w.$f.$optid - ${NS}::label $w.$f.$optid.l -text "$text:" + ${NS}::label $w.$f.$optid.l -text [mc "%s:" $text] if {$use_ttk} { ttk::combobox $w.$f.$optid.v \ -textvariable ${f}_config_new($name) \ @@ -279,7 +279,7 @@ proc do_options {} { [font configure $font -size] ${NS}::frame $w.global.$name - ${NS}::label $w.global.$name.l -text "$text:" + ${NS}::label $w.global.$name.l -text [mc "%s:" $text] ${NS}::button $w.global.$name.b \ -text [mc "Change Font"] \ -command [list \ diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl index 4e5c784418..ef77ed7399 100644 --- a/git-gui/lib/remote.tcl +++ b/git-gui/lib/remote.tcl @@ -246,22 +246,22 @@ proc update_all_remotes_menu_entry {} { if {$have_remote > 1} { make_sure_remote_submenues_exist $remote_m if {[$fetch_m type end] eq "command" \ - && [$fetch_m entrycget end -label] ne "All"} { + && [$fetch_m entrycget end -label] ne [mc "All"]} { $fetch_m insert end separator $fetch_m insert end command \ - -label "All" \ + -label [mc "All"] \ -command fetch_from_all $prune_m insert end separator $prune_m insert end command \ - -label "All" \ + -label [mc "All"] \ -command prune_from_all } } else { if {[winfo exists $fetch_m]} { if {[$fetch_m type end] eq "command" \ - && [$fetch_m entrycget end -label] eq "All"} { + && [$fetch_m entrycget end -label] eq [mc "All"]} { delete_from_menu $fetch_m end delete_from_menu $fetch_m end diff --git a/git-gui/lib/remote_add.tcl b/git-gui/lib/remote_add.tcl index 50029d0cee..480a6b30d0 100644 --- a/git-gui/lib/remote_add.tcl +++ b/git-gui/lib/remote_add.tcl @@ -17,7 +17,7 @@ constructor dialog {} { make_dialog top w wm withdraw $top - wm title $top [append "[appname] ([reponame]): " [mc "Add Remote"]] + wm title $top [mc "%s (%s): Add Remote" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl index fcc06d03a1..5ba9fcadd1 100644 --- a/git-gui/lib/remote_branch_delete.tcl +++ b/git-gui/lib/remote_branch_delete.tcl @@ -26,7 +26,7 @@ constructor dialog {} { global all_remotes M1B use_ttk NS make_dialog top w - wm title $top [append "[appname] ([reponame]): " [mc "Delete Branch Remotely"]] + wm title $top [mc "%s (%s): Delete Branch Remotely" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" } diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl index 78878ef89d..97d1d7aa02 100644 --- a/git-gui/lib/shortcut.tcl +++ b/git-gui/lib/shortcut.tcl @@ -5,17 +5,20 @@ proc do_windows_shortcut {} { global _gitworktree set fn [tk_getSaveFile \ -parent . \ - -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \ + -title [mc "%s (%s): Create Desktop Icon" [appname] [reponame]] \ -initialfile "Git [reponame].lnk"] if {$fn != {}} { if {[file extension $fn] ne {.lnk}} { set fn ${fn}.lnk } + # Use git-gui.exe if available (ie: git-for-windows) + set cmdLine [auto_execok git-gui.exe] + if {$cmdLine eq {}} { + set cmdLine [list [info nameofexecutable] \ + [file normalize $::argv0]] + } if {[catch { - win32_create_lnk $fn [list \ - [info nameofexecutable] \ - [file normalize $::argv0] \ - ] \ + win32_create_lnk $fn $cmdLine \ [file normalize $_gitworktree] } err]} { error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] @@ -37,7 +40,7 @@ proc do_cygwin_shortcut {} { } set fn [tk_getSaveFile \ -parent . \ - -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \ + -title [mc "%s (%s): Create Desktop Icon" [appname] [reponame]] \ -initialdir $desktop \ -initialfile "Git [reponame].lnk"] if {$fn != {}} { @@ -69,7 +72,7 @@ proc do_macosx_app {} { set fn [tk_getSaveFile \ -parent . \ - -title [append "[appname] ([reponame]): " [mc "Create Desktop Icon"]] \ + -title [mc "%s (%s): Create Desktop Icon" [appname] [reponame]] \ -initialdir [file join $env(HOME) Desktop] \ -initialfile "Git [reponame].app"] if {$fn != {}} { diff --git a/git-gui/lib/themed.tcl b/git-gui/lib/themed.tcl index 8b88d3678b..351a712c8c 100644 --- a/git-gui/lib/themed.tcl +++ b/git-gui/lib/themed.tcl @@ -78,6 +78,57 @@ proc InitTheme {} { } } +# Define a style used for the surround of text widgets. +proc InitEntryFrame {} { + ttk::style theme settings default { + ttk::style layout EntryFrame { + EntryFrame.field -sticky nswe -border 0 -children { + EntryFrame.fill -sticky nswe -children { + EntryFrame.padding -sticky nswe + } + } + } + ttk::style configure EntryFrame -padding 1 -relief sunken + ttk::style map EntryFrame -background {} + } + ttk::style theme settings classic { + ttk::style configure EntryFrame -padding 2 -relief sunken + ttk::style map EntryFrame -background {} + } + ttk::style theme settings alt { + ttk::style configure EntryFrame -padding 2 + ttk::style map EntryFrame -background {} + } + ttk::style theme settings clam { + ttk::style configure EntryFrame -padding 2 + ttk::style map EntryFrame -background {} + } + + # Ignore errors for missing native themes + catch { + ttk::style theme settings winnative { + ttk::style configure EntryFrame -padding 2 + } + ttk::style theme settings xpnative { + ttk::style configure EntryFrame -padding 1 + ttk::style element create EntryFrame.field vsapi \ + EDIT 1 {disabled 4 focus 3 active 2 {} 1} -padding 1 + } + ttk::style theme settings vista { + ttk::style configure EntryFrame -padding 2 + ttk::style element create EntryFrame.field vsapi \ + EDIT 6 {disabled 4 focus 3 active 2 {} 1} -padding 2 + } + } + + bind EntryFrame <Enter> {%W instate !disabled {%W state active}} + bind EntryFrame <Leave> {%W state !active} + bind EntryFrame <<ThemeChanged>> { + set pad [ttk::style lookup EntryFrame -padding] + %W configure -padding [expr {$pad eq {} ? 1 : $pad}] + } +} + proc gold_frame {w args} { global use_ttk if {$use_ttk} { @@ -123,7 +174,7 @@ proc paddedlabel {w args} { # place a themed frame over the surface. proc Dialog {w args} { eval [linsert $args 0 toplevel $w -class Dialog] - catch {wm attributes $w -type dialog} + catch {wm attributes $w -type dialog} pave_toplevel $w return $w } @@ -193,6 +244,40 @@ proc tspinbox {w args} { } } +# Create a text widget with any theme specific properties. +proc ttext {w args} { + global use_ttk + if {$use_ttk} { + switch -- [ttk::style theme use] { + "vista" - "xpnative" { + lappend args -highlightthickness 0 -borderwidth 0 + } + } + } + set w [eval [linsert $args 0 text $w]] + if {$use_ttk} { + if {[winfo class [winfo parent $w]] eq "EntryFrame"} { + bind $w <FocusIn> {[winfo parent %W] state focus} + bind $w <FocusOut> {[winfo parent %W] state !focus} + } + } + return $w +} + +# themed frame suitable for surrounding a text field. +proc textframe {w args} { + global use_ttk + if {$use_ttk} { + if {[catch {ttk::style layout EntryFrame}]} { + InitEntryFrame + } + eval [linsert $args 0 ttk::frame $w -class EntryFrame -style EntryFrame] + } else { + eval [linsert $args 0 frame $w] + } + return $w +} + proc tentry {w args} { global use_ttk if {$use_ttk} { diff --git a/git-gui/lib/tools.tcl b/git-gui/lib/tools.tcl index 6ec94113db..413f1a1700 100644 --- a/git-gui/lib/tools.tcl +++ b/git-gui/lib/tools.tcl @@ -69,6 +69,7 @@ proc tools_populate_one {fullname} { proc tools_exec {fullname} { global repo_config env current_diff_path global current_branch is_detached + global selected_paths if {[is_config_true "guitool.$fullname.needsfile"]} { if {$current_diff_path eq {}} { @@ -100,6 +101,7 @@ proc tools_exec {fullname} { set env(GIT_GUITOOL) $fullname set env(FILENAME) $current_diff_path + set env(FILENAMES) [join [array names selected_paths] \n] if {$is_detached} { set env(CUR_BRANCH) "" } else { @@ -121,6 +123,7 @@ proc tools_exec {fullname} { unset env(GIT_GUITOOL) unset env(FILENAME) + unset env(FILENAMES) unset env(CUR_BRANCH) catch { unset env(ARGS) } catch { unset env(REVISION) } diff --git a/git-gui/lib/tools_dlg.tcl b/git-gui/lib/tools_dlg.tcl index 7eeda9daf2..c05413ce43 100644 --- a/git-gui/lib/tools_dlg.tcl +++ b/git-gui/lib/tools_dlg.tcl @@ -19,7 +19,7 @@ constructor dialog {} { global repo_config use_ttk NS make_dialog top w - wm title $top [append "[appname] ([reponame]): " [mc "Add Tool"]] + wm title $top [mc "%s (%s): Add Tool" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" wm transient $top . @@ -184,7 +184,7 @@ constructor dialog {} { load_config 1 make_dialog top w - wm title $top [append "[appname] ([reponame]): " [mc "Remove Tool"]] + wm title $top [mc "%s (%s): Remove Tool" [appname] [reponame]] if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" wm transient $top . @@ -280,7 +280,7 @@ constructor dialog {fullname} { } make_dialog top w -autodelete 0 - wm title $top [append "[appname] ([reponame]): " $title] + wm title $top "[mc "%s (%s):" [appname] [reponame]] $title" if {$top ne {.}} { wm geometry $top "+[winfo rootx .]+[winfo rooty .]" wm transient $top . diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl index e5d211edea..a1a424aab5 100644 --- a/git-gui/lib/transport.tcl +++ b/git-gui/lib/transport.tcl @@ -226,7 +226,7 @@ proc do_push_anywhere {} { bind $w <Visibility> "grab $w; focus $w.buttons.create" bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> [list start_push_anywhere_action $w] - wm title $w [append "[appname] ([reponame]): " [mc "Push"]] + wm title $w [mc "%s (%s): Push" [appname] [reponame]] wm deiconify $w tkwait window $w } diff --git a/git-gui/po/bg.po b/git-gui/po/bg.po index 4d9b039dc2..5af78f15a8 100644 --- a/git-gui/po/bg.po +++ b/git-gui/po/bg.po @@ -1,15 +1,15 @@ # Bulgarian translation of git-gui po-file. -# Copyright (C) 2012, 2013, 2014, 2015 Alexander Shopov <ash@kambanaria.org>. +# Copyright (C) 2012, 2013, 2014, 2015, 2016 Alexander Shopov <ash@kambanaria.org>. # This file is distributed under the same license as the git package. -# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014, 2015. +# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014, 2015, 2016. # # msgid "" msgstr "" "Project-Id-Version: git-gui master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-04-07 07:37+0300\n" -"PO-Revision-Date: 2015-04-07 07:46+0300\n" +"POT-Creation-Date: 2016-10-13 15:16+0300\n" +"PO-Revision-Date: 2016-10-13 15:16+0300\n" "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n" "Language-Team: Bulgarian <dict@fsa-bg.org>\n" "Language: bg\n" @@ -18,33 +18,33 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: git-gui.sh:861 +#: git-gui.sh:865 #, tcl-format msgid "Invalid font specified in %s:" msgstr "Указан е неправилен шрифт в „%s“:" -#: git-gui.sh:915 +#: git-gui.sh:919 msgid "Main Font" msgstr "ОÑновен шрифт" -#: git-gui.sh:916 +#: git-gui.sh:920 msgid "Diff/Console Font" msgstr "Шрифт за разликите/конзолата" -#: git-gui.sh:931 git-gui.sh:945 git-gui.sh:958 git-gui.sh:1048 -#: git-gui.sh:1067 git-gui.sh:3125 +#: git-gui.sh:935 git-gui.sh:949 git-gui.sh:962 git-gui.sh:1052 git-gui.sh:1071 +#: git-gui.sh:3147 msgid "git-gui: fatal error" msgstr "git-gui: фатална грешка" -#: git-gui.sh:932 +#: git-gui.sh:936 msgid "Cannot find git in PATH." msgstr "Командата git липÑва в Ð¿ÑŠÑ‚Ñ (PATH)." -#: git-gui.sh:959 +#: git-gui.sh:963 msgid "Cannot parse Git version string:" msgstr "Ðизът Ñ Ð²ÐµÑ€ÑиÑта на Git не може да бъде интерпретиран:" -#: git-gui.sh:984 +#: git-gui.sh:988 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -63,503 +63,506 @@ msgstr "" "\n" "Да Ñе приеме ли, че „%s“ е верÑÐ¸Ñ â€ž1.5.0“?\n" -#: git-gui.sh:1281 +#: git-gui.sh:1285 msgid "Git directory not found:" msgstr "ДиректориÑта на Git не е открита:" -#: git-gui.sh:1315 +#: git-gui.sh:1319 msgid "Cannot move to top of working directory:" msgstr "Ðе може да Ñе премине към родителÑката директориÑ." -#: git-gui.sh:1323 +#: git-gui.sh:1327 msgid "Cannot use bare repository:" msgstr "Голо хранилище не може да Ñе използва:" -#: git-gui.sh:1331 +#: git-gui.sh:1335 msgid "No working directory" msgstr "Работната Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð»Ð¸Ð¿Ñва" -#: git-gui.sh:1503 lib/checkout_op.tcl:306 +#: git-gui.sh:1507 lib/checkout_op.tcl:306 msgid "Refreshing file status..." msgstr "ОбновÑване на ÑÑŠÑтоÑнието на файла…" -#: git-gui.sh:1563 +#: git-gui.sh:1567 msgid "Scanning for modified files ..." msgstr "Проверка за променени файлове…" -#: git-gui.sh:1639 +#: git-gui.sh:1645 msgid "Calling prepare-commit-msg hook..." msgstr "Куката „prepare-commit-msg“ Ñе изпълнÑва в момента…" -#: git-gui.sh:1656 +#: git-gui.sh:1662 msgid "Commit declined by prepare-commit-msg hook." msgstr "Подаването е отхвърлено от куката „prepare-commit-msg“." -#: git-gui.sh:1814 lib/browser.tcl:252 +#: git-gui.sh:1820 lib/browser.tcl:252 msgid "Ready." msgstr "Готово." -#: git-gui.sh:1978 +#: git-gui.sh:1984 #, tcl-format msgid "" "Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files." msgstr "" -"ДоÑтигнат е макÑималниÑÑ‚ брой файлове за показване (gui.maxfilesdisplayed = " -"%s). Файловете Ñа общо %s." +"ДоÑтигнат е макÑималниÑÑ‚ размер на ÑпиÑъка за извеждане(gui." +"maxfilesdisplayed = %s), Ñъответно не Ñа показани вÑички %s файла." -#: git-gui.sh:2101 +#: git-gui.sh:2107 msgid "Unmodified" msgstr "Ðепроменен" -#: git-gui.sh:2103 +#: git-gui.sh:2109 msgid "Modified, not staged" msgstr "Променен, но не е в индекÑа" -#: git-gui.sh:2104 git-gui.sh:2116 +#: git-gui.sh:2110 git-gui.sh:2122 msgid "Staged for commit" msgstr "Ð’ индекÑа за подаване" -#: git-gui.sh:2105 git-gui.sh:2117 +#: git-gui.sh:2111 git-gui.sh:2123 msgid "Portions staged for commit" msgstr "ЧаÑти Ñа в индекÑа за подаване" -#: git-gui.sh:2106 git-gui.sh:2118 +#: git-gui.sh:2112 git-gui.sh:2124 msgid "Staged for commit, missing" msgstr "Ð’ индекÑа за подаване, но липÑва" -#: git-gui.sh:2108 +#: git-gui.sh:2114 msgid "File type changed, not staged" msgstr "Видът на файла е Ñменен, но не е в индекÑа" -#: git-gui.sh:2109 git-gui.sh:2110 +#: git-gui.sh:2115 git-gui.sh:2116 msgid "File type changed, old type staged for commit" -msgstr "Видът на файла е Ñменен, но в индекÑа е вÑе още ÑтариÑÑ‚" +msgstr "Видът на файла е Ñменен, но новиÑÑ‚ вид не е в индекÑа" -#: git-gui.sh:2111 +#: git-gui.sh:2117 msgid "File type changed, staged" msgstr "Видът на файла е Ñменен и е в индекÑа" -#: git-gui.sh:2112 +#: git-gui.sh:2118 msgid "File type change staged, modification not staged" -msgstr "Видът на файла е Ñменен, но промÑната не е в индекÑа" +msgstr "Видът на файла е Ñменен в индекÑа, но не и Ñъдържанието" -#: git-gui.sh:2113 +#: git-gui.sh:2119 msgid "File type change staged, file missing" -msgstr "Видът на файла е Ñменен, файлът липÑва" +msgstr "Видът на файла е Ñменен в индекÑа, но файлът липÑва" -#: git-gui.sh:2115 +#: git-gui.sh:2121 msgid "Untracked, not staged" msgstr "ÐеÑледен" -#: git-gui.sh:2120 +#: git-gui.sh:2126 msgid "Missing" msgstr "ЛипÑващ" -#: git-gui.sh:2121 +#: git-gui.sh:2127 msgid "Staged for removal" msgstr "Ð’ индекÑа за изтриване" -#: git-gui.sh:2122 +#: git-gui.sh:2128 msgid "Staged for removal, still present" msgstr "Ð’ индекÑа за изтриване, но още го има" -#: git-gui.sh:2124 git-gui.sh:2125 git-gui.sh:2126 git-gui.sh:2127 -#: git-gui.sh:2128 git-gui.sh:2129 +#: git-gui.sh:2130 git-gui.sh:2131 git-gui.sh:2132 git-gui.sh:2133 +#: git-gui.sh:2134 git-gui.sh:2135 msgid "Requires merge resolution" msgstr "ИзиÑква коригиране при Ñливане" -#: git-gui.sh:2164 +#: git-gui.sh:2170 msgid "Starting gitk... please wait..." msgstr "Стартиране на „gitk“…, изчакайте…" -#: git-gui.sh:2176 +#: git-gui.sh:2182 msgid "Couldn't find gitk in PATH" msgstr "Командата „gitk“ липÑва в пътищата, определени от променливата PATH." -#: git-gui.sh:2235 +#: git-gui.sh:2241 msgid "Couldn't find git gui in PATH" msgstr "" "Командата „git gui“ липÑва в пътищата, определени от променливата PATH." -#: git-gui.sh:2654 lib/choose_repository.tcl:41 +#: git-gui.sh:2676 lib/choose_repository.tcl:41 msgid "Repository" msgstr "Хранилище" -#: git-gui.sh:2655 +#: git-gui.sh:2677 msgid "Edit" msgstr "Редактиране" -#: git-gui.sh:2657 lib/choose_rev.tcl:567 +#: git-gui.sh:2679 lib/choose_rev.tcl:567 msgid "Branch" msgstr "Клон" -#: git-gui.sh:2660 lib/choose_rev.tcl:554 +#: git-gui.sh:2682 lib/choose_rev.tcl:554 msgid "Commit@@noun" msgstr "Подаване" -#: git-gui.sh:2663 lib/merge.tcl:123 lib/merge.tcl:152 lib/merge.tcl:170 +#: git-gui.sh:2685 lib/merge.tcl:127 lib/merge.tcl:174 msgid "Merge" msgstr "Сливане" -#: git-gui.sh:2664 lib/choose_rev.tcl:563 +#: git-gui.sh:2686 lib/choose_rev.tcl:563 msgid "Remote" msgstr "Отдалечено хранилище" -#: git-gui.sh:2667 +#: git-gui.sh:2689 msgid "Tools" msgstr "Команди" -#: git-gui.sh:2676 +#: git-gui.sh:2698 msgid "Explore Working Copy" msgstr "Разглеждане на работното копие" -#: git-gui.sh:2682 +#: git-gui.sh:2704 msgid "Git Bash" msgstr "Bash за Git" -#: git-gui.sh:2692 +#: git-gui.sh:2714 msgid "Browse Current Branch's Files" msgstr "Разглеждане на файловете в Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ ÐºÐ»Ð¾Ð½" -#: git-gui.sh:2696 +#: git-gui.sh:2718 msgid "Browse Branch Files..." msgstr "Разглеждане на Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ ÐºÐ»Ð¾Ð½â€¦" -#: git-gui.sh:2701 +#: git-gui.sh:2723 msgid "Visualize Current Branch's History" msgstr "Ð’Ð¸Ð·ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° иÑториÑта на Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ ÐºÐ»Ð¾Ð½" -#: git-gui.sh:2705 +#: git-gui.sh:2727 msgid "Visualize All Branch History" msgstr "Ð’Ð¸Ð·ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° иÑториÑта на вÑички клонове" -#: git-gui.sh:2712 +#: git-gui.sh:2734 #, tcl-format msgid "Browse %s's Files" -msgstr "Разглеждане на файловете в %s" +msgstr "Разглеждане на файловете в „%s“" -#: git-gui.sh:2714 +#: git-gui.sh:2736 #, tcl-format msgid "Visualize %s's History" -msgstr "Ð’Ð¸Ð·ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° иÑториÑта на %s" +msgstr "Ð’Ð¸Ð·ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° иÑториÑта на „%s“" -#: git-gui.sh:2719 lib/database.tcl:40 lib/database.tcl:66 +#: git-gui.sh:2741 lib/database.tcl:40 msgid "Database Statistics" msgstr "СтатиÑтика на базата от данни" -#: git-gui.sh:2722 lib/database.tcl:33 +#: git-gui.sh:2744 lib/database.tcl:33 msgid "Compress Database" msgstr "КомпреÑиране на базата от данни" -#: git-gui.sh:2725 +#: git-gui.sh:2747 msgid "Verify Database" msgstr "Проверка на базата от данни" -#: git-gui.sh:2732 git-gui.sh:2736 git-gui.sh:2740 lib/shortcut.tcl:8 -#: lib/shortcut.tcl:40 lib/shortcut.tcl:72 +#: git-gui.sh:2754 git-gui.sh:2758 git-gui.sh:2762 msgid "Create Desktop Icon" msgstr "ДобавÑне на икона на Ñ€Ð°Ð±Ð¾Ñ‚Ð½Ð¸Ñ Ð¿Ð»Ð¾Ñ‚" -#: git-gui.sh:2748 lib/choose_repository.tcl:193 lib/choose_repository.tcl:201 +#: git-gui.sh:2770 lib/choose_repository.tcl:193 lib/choose_repository.tcl:201 msgid "Quit" msgstr "Спиране на програмата" -#: git-gui.sh:2756 +#: git-gui.sh:2778 msgid "Undo" msgstr "ОтмÑна" -#: git-gui.sh:2759 +#: git-gui.sh:2781 msgid "Redo" msgstr "Повторение" -#: git-gui.sh:2763 git-gui.sh:3368 +#: git-gui.sh:2785 git-gui.sh:3399 msgid "Cut" msgstr "ОтрÑзване" -#: git-gui.sh:2766 git-gui.sh:3371 git-gui.sh:3445 git-gui.sh:3530 +#: git-gui.sh:2788 git-gui.sh:3402 git-gui.sh:3476 git-gui.sh:3562 #: lib/console.tcl:69 msgid "Copy" msgstr "Копиране" -#: git-gui.sh:2769 git-gui.sh:3374 +#: git-gui.sh:2791 git-gui.sh:3405 msgid "Paste" msgstr "ПоÑтавÑне" -#: git-gui.sh:2772 git-gui.sh:3377 lib/remote_branch_delete.tcl:39 -#: lib/branch_delete.tcl:28 +#: git-gui.sh:2794 git-gui.sh:3408 lib/branch_delete.tcl:28 +#: lib/remote_branch_delete.tcl:39 msgid "Delete" msgstr "Изтриване" -#: git-gui.sh:2776 git-gui.sh:3381 git-gui.sh:3534 lib/console.tcl:71 +#: git-gui.sh:2798 git-gui.sh:3412 git-gui.sh:3566 lib/console.tcl:71 msgid "Select All" msgstr "Избиране на вÑичко" -#: git-gui.sh:2785 +#: git-gui.sh:2807 msgid "Create..." msgstr "Създаване…" -#: git-gui.sh:2791 +#: git-gui.sh:2813 msgid "Checkout..." msgstr "ИзтеглÑне…" -#: git-gui.sh:2797 +#: git-gui.sh:2819 msgid "Rename..." msgstr "Преименуване…" -#: git-gui.sh:2802 +#: git-gui.sh:2824 msgid "Delete..." msgstr "Изтриване…" -#: git-gui.sh:2807 +#: git-gui.sh:2829 msgid "Reset..." msgstr "ОтмÑна на промените…" -#: git-gui.sh:2817 +#: git-gui.sh:2839 msgid "Done" msgstr "Готово" -#: git-gui.sh:2819 +#: git-gui.sh:2841 msgid "Commit@@verb" msgstr "Подаване" -#: git-gui.sh:2828 git-gui.sh:3309 +#: git-gui.sh:2850 git-gui.sh:3335 msgid "New Commit" msgstr "Ðово подаване" -#: git-gui.sh:2836 git-gui.sh:3316 +#: git-gui.sh:2858 git-gui.sh:3342 msgid "Amend Last Commit" msgstr "ПоправÑне на поÑледното подаване" -#: git-gui.sh:2846 git-gui.sh:3270 lib/remote_branch_delete.tcl:101 +#: git-gui.sh:2868 git-gui.sh:3296 lib/remote_branch_delete.tcl:101 msgid "Rescan" msgstr "ОбновÑване" -#: git-gui.sh:2852 +#: git-gui.sh:2874 msgid "Stage To Commit" msgstr "Към индекÑа за подаване" -#: git-gui.sh:2858 +#: git-gui.sh:2880 msgid "Stage Changed Files To Commit" msgstr "Ð’Ñички променени файлове към индекÑа за подаване" -#: git-gui.sh:2864 +#: git-gui.sh:2886 msgid "Unstage From Commit" msgstr "Изваждане от индекÑа за подаване" -#: git-gui.sh:2870 lib/index.tcl:442 +#: git-gui.sh:2892 lib/index.tcl:442 msgid "Revert Changes" msgstr "Връщане на оригинала" -#: git-gui.sh:2878 git-gui.sh:3581 git-gui.sh:3612 +#: git-gui.sh:2900 git-gui.sh:3613 git-gui.sh:3644 msgid "Show Less Context" msgstr "По-малко контекÑÑ‚" -#: git-gui.sh:2882 git-gui.sh:3585 git-gui.sh:3616 +#: git-gui.sh:2904 git-gui.sh:3617 git-gui.sh:3648 msgid "Show More Context" msgstr "Повече контекÑÑ‚" -#: git-gui.sh:2889 git-gui.sh:3283 git-gui.sh:3392 +#: git-gui.sh:2911 git-gui.sh:3309 git-gui.sh:3423 msgid "Sign Off" msgstr "ПодпиÑване" -#: git-gui.sh:2905 +#: git-gui.sh:2927 msgid "Local Merge..." msgstr "Локално Ñливане…" -#: git-gui.sh:2910 +#: git-gui.sh:2932 msgid "Abort Merge..." msgstr "ПреуÑтановÑване на Ñливане…" -#: git-gui.sh:2922 git-gui.sh:2950 +#: git-gui.sh:2944 git-gui.sh:2972 msgid "Add..." msgstr "ДобавÑне…" -#: git-gui.sh:2926 +#: git-gui.sh:2948 msgid "Push..." -msgstr "Избутване…" +msgstr "ИзтлаÑкване…" -#: git-gui.sh:2930 +#: git-gui.sh:2952 msgid "Delete Branch..." msgstr "Изтриване на клон…" -#: git-gui.sh:2940 git-gui.sh:3563 +#: git-gui.sh:2962 git-gui.sh:3595 msgid "Options..." msgstr "Опции…" -#: git-gui.sh:2951 +#: git-gui.sh:2973 msgid "Remove..." msgstr "Премахване…" -#: git-gui.sh:2960 lib/choose_repository.tcl:55 +#: git-gui.sh:2982 lib/choose_repository.tcl:55 msgid "Help" msgstr "Помощ" -#: git-gui.sh:2964 git-gui.sh:2968 lib/choose_repository.tcl:49 -#: lib/choose_repository.tcl:58 lib/about.tcl:14 +#: git-gui.sh:2986 git-gui.sh:2990 lib/about.tcl:14 +#: lib/choose_repository.tcl:49 lib/choose_repository.tcl:58 #, tcl-format msgid "About %s" msgstr "ОтноÑно %s" -#: git-gui.sh:2992 +#: git-gui.sh:3014 msgid "Online Documentation" msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð² Интернет" -#: git-gui.sh:2995 lib/choose_repository.tcl:52 lib/choose_repository.tcl:61 +#: git-gui.sh:3017 lib/choose_repository.tcl:52 lib/choose_repository.tcl:61 msgid "Show SSH Key" msgstr "Показване на ключа за SSH" -#: git-gui.sh:3014 git-gui.sh:3146 +#: git-gui.sh:3032 git-gui.sh:3164 +msgid "usage:" +msgstr "употреба:" + +#: git-gui.sh:3036 git-gui.sh:3168 msgid "Usage" msgstr "Употреба" -#: git-gui.sh:3095 lib/blame.tcl:573 +#: git-gui.sh:3117 lib/blame.tcl:573 msgid "Error" msgstr "Грешка" -#: git-gui.sh:3126 +#: git-gui.sh:3148 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "" "ФÐТÐЛÐРГРЕШКÐ: пътÑÑ‚ %s не може да бъде открит: такъв файл или Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ " "нÑма" -#: git-gui.sh:3159 +#: git-gui.sh:3181 msgid "Current Branch:" msgstr "Текущ клон:" -#: git-gui.sh:3185 -msgid "Staged Changes (Will Commit)" -msgstr "Промени в индекÑа (за подаване)" - -#: git-gui.sh:3205 +#: git-gui.sh:3206 msgid "Unstaged Changes" msgstr "Промени извън индекÑа" -#: git-gui.sh:3276 +#: git-gui.sh:3228 +msgid "Staged Changes (Will Commit)" +msgstr "Промени в индекÑа (за подаване)" + +#: git-gui.sh:3302 msgid "Stage Changed" msgstr "ИндекÑÑŠÑ‚ е променен" -#: git-gui.sh:3295 lib/transport.tcl:137 lib/transport.tcl:229 +#: git-gui.sh:3321 lib/transport.tcl:137 msgid "Push" msgstr "ИзтлаÑкване" -#: git-gui.sh:3330 +#: git-gui.sh:3356 msgid "Initial Commit Message:" msgstr "Първоначално Ñъобщение при подаване:" -#: git-gui.sh:3331 +#: git-gui.sh:3357 msgid "Amended Commit Message:" msgstr "Поправено Ñъобщение при подаване:" -#: git-gui.sh:3332 +#: git-gui.sh:3358 msgid "Amended Initial Commit Message:" msgstr "Поправено първоначално Ñъобщение при подаване:" -#: git-gui.sh:3333 +#: git-gui.sh:3359 msgid "Amended Merge Commit Message:" msgstr "Поправено Ñъобщение при подаване ÑÑŠÑ Ñливане:" -#: git-gui.sh:3334 +#: git-gui.sh:3360 msgid "Merge Commit Message:" msgstr "Съобщение при подаване ÑÑŠÑ Ñливане:" -#: git-gui.sh:3335 +#: git-gui.sh:3361 msgid "Commit Message:" msgstr "Съобщение при подаване:" -#: git-gui.sh:3384 git-gui.sh:3538 lib/console.tcl:73 +#: git-gui.sh:3415 git-gui.sh:3570 lib/console.tcl:73 msgid "Copy All" msgstr "Копиране на вÑичко" -#: git-gui.sh:3408 lib/blame.tcl:105 +#: git-gui.sh:3439 lib/blame.tcl:105 msgid "File:" msgstr "Файл:" -#: git-gui.sh:3526 +#: git-gui.sh:3558 msgid "Refresh" msgstr "ОбновÑване" -#: git-gui.sh:3547 +#: git-gui.sh:3579 msgid "Decrease Font Size" msgstr "По-едър шрифт" -#: git-gui.sh:3551 +#: git-gui.sh:3583 msgid "Increase Font Size" msgstr "По-дребен шрифт" -#: git-gui.sh:3559 lib/blame.tcl:294 +#: git-gui.sh:3591 lib/blame.tcl:294 msgid "Encoding" msgstr "Кодиране" -#: git-gui.sh:3570 +#: git-gui.sh:3602 msgid "Apply/Reverse Hunk" msgstr "Прилагане/връщане на парче" -#: git-gui.sh:3575 +#: git-gui.sh:3607 msgid "Apply/Reverse Line" msgstr "Прилагане/връщане на ред" -#: git-gui.sh:3594 +#: git-gui.sh:3626 msgid "Run Merge Tool" msgstr "Изпълнение на програмата за Ñливане" -#: git-gui.sh:3599 +#: git-gui.sh:3631 msgid "Use Remote Version" msgstr "ВерÑÐ¸Ñ Ð¾Ñ‚ отдалеченото хранилище" -#: git-gui.sh:3603 +#: git-gui.sh:3635 msgid "Use Local Version" msgstr "Локална верÑиÑ" -#: git-gui.sh:3607 +#: git-gui.sh:3639 msgid "Revert To Base" msgstr "Връщане към родителÑката верÑиÑ" -#: git-gui.sh:3625 +#: git-gui.sh:3657 msgid "Visualize These Changes In The Submodule" msgstr "Визуализиране на промените в подмодула" -#: git-gui.sh:3629 +#: git-gui.sh:3661 msgid "Visualize Current Branch History In The Submodule" msgstr "Ð’Ð¸Ð·ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° иÑториÑта на Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ ÐºÐ»Ð¾Ð½ в иÑториÑта за подмодула" -#: git-gui.sh:3633 +#: git-gui.sh:3665 msgid "Visualize All Branch History In The Submodule" msgstr "Ð’Ð¸Ð·ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° иÑториÑта на вÑички клони в иÑториÑта за подмодула" -#: git-gui.sh:3638 +#: git-gui.sh:3670 msgid "Start git gui In The Submodule" msgstr "Стартиране на „git gui“ за подмодула" -#: git-gui.sh:3673 +#: git-gui.sh:3705 msgid "Unstage Hunk From Commit" msgstr "Изваждане на парчето от подаването" -#: git-gui.sh:3675 +#: git-gui.sh:3707 msgid "Unstage Lines From Commit" msgstr "Изваждане на редовете от подаването" -#: git-gui.sh:3677 +#: git-gui.sh:3709 msgid "Unstage Line From Commit" msgstr "Изваждане на реда от подаването" -#: git-gui.sh:3680 +#: git-gui.sh:3712 msgid "Stage Hunk For Commit" msgstr "ДобавÑне на парчето за подаване" -#: git-gui.sh:3682 +#: git-gui.sh:3714 msgid "Stage Lines For Commit" msgstr "ДобавÑне на редовете за подаване" -#: git-gui.sh:3684 +#: git-gui.sh:3716 msgid "Stage Line For Commit" msgstr "ДобавÑне на реда за подаване" -#: git-gui.sh:3709 +#: git-gui.sh:3741 msgid "Initializing..." msgstr "Инициализиране…" -#: git-gui.sh:3852 +#: git-gui.sh:3886 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -576,7 +579,7 @@ msgstr "" "от %s:\n" "\n" -#: git-gui.sh:3881 +#: git-gui.sh:3915 msgid "" "\n" "This is due to a known issue with the\n" @@ -586,7 +589,7 @@ msgstr "" "Това е познат проблем и Ñе дължи на\n" "верÑиÑта на Tcl включена в Cygwin." -#: git-gui.sh:3886 +#: git-gui.sh:3920 #, tcl-format msgid "" "\n" @@ -602,199 +605,126 @@ msgstr "" "е да поÑтавите наÑтройките „user.name“ и\n" "„user.email“ в Ð»Ð¸Ñ‡Ð½Ð¸Ñ Ñи файл „~/.gitconfig“.\n" -#: lib/spellcheck.tcl:57 -msgid "Unsupported spell checker" -msgstr "Тази програма за проверка на правопиÑа не Ñе поддържа" - -#: lib/spellcheck.tcl:65 -msgid "Spell checking is unavailable" -msgstr "ЛипÑва програма за проверка на правопиÑа" - -#: lib/spellcheck.tcl:68 -msgid "Invalid spell checking configuration" -msgstr "Ðеправилни наÑтройки на проверката на правопиÑа" +#: lib/about.tcl:26 +msgid "git-gui - a graphical user interface for Git." +msgstr "git-gui — графичен Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð·Ð° Git." -#: lib/spellcheck.tcl:70 +#: lib/blame.tcl:73 #, tcl-format -msgid "Reverting dictionary to %s." -msgstr "Ползване на речник за език „%s“." - -#: lib/spellcheck.tcl:73 -msgid "Spell checker silently failed on startup" -msgstr "Програмата за Ð¿Ñ€Ð°Ð²Ð¾Ð¿Ð¸Ñ Ð´Ð°Ð¶Ðµ не Ñтартира уÑпешно." - -#: lib/spellcheck.tcl:80 -msgid "Unrecognized spell checker" -msgstr "Ðепозната програма за проверка на правопиÑа" - -#: lib/spellcheck.tcl:186 -msgid "No Suggestions" -msgstr "ÐÑма предложениÑ" - -#: lib/spellcheck.tcl:388 -msgid "Unexpected EOF from spell checker" -msgstr "Ðеочакван край на файл от програмата за проверка на правопиÑа" - -#: lib/spellcheck.tcl:392 -msgid "Spell Checker Failed" -msgstr "Грешка в програмата за проверка на правопиÑа" - -#: lib/remote_add.tcl:20 -msgid "Add Remote" -msgstr "ДобавÑне на отдалечено хранилище" - -#: lib/remote_add.tcl:25 -msgid "Add New Remote" -msgstr "ДобавÑне на отдалечено хранилище" - -#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37 -msgid "Add" -msgstr "ДобавÑне" - -#: lib/remote_add.tcl:34 lib/browser.tcl:292 lib/branch_checkout.tcl:30 -#: lib/transport.tcl:141 lib/branch_rename.tcl:32 lib/choose_font.tcl:45 -#: lib/option.tcl:127 lib/tools_dlg.tcl:41 lib/tools_dlg.tcl:202 -#: lib/tools_dlg.tcl:345 lib/remote_branch_delete.tcl:43 -#: lib/checkout_op.tcl:579 lib/branch_create.tcl:37 lib/branch_delete.tcl:34 -#: lib/merge.tcl:174 -msgid "Cancel" -msgstr "Отказване" - -#: lib/remote_add.tcl:39 -msgid "Remote Details" -msgstr "Данни за отдалеченото хранилище" +msgid "%s (%s): File Viewer" +msgstr "%s (%s): Преглед на файлове" -#: lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 lib/branch_create.tcl:44 -msgid "Name:" -msgstr "Име:" - -#: lib/remote_add.tcl:50 -msgid "Location:" -msgstr "МеÑтоположение:" +#: lib/blame.tcl:79 +msgid "Commit:" +msgstr "Подаване:" -#: lib/remote_add.tcl:60 -msgid "Further Action" -msgstr "Следващо дейÑтвие" +#: lib/blame.tcl:280 +msgid "Copy Commit" +msgstr "Копиране на подаване" -#: lib/remote_add.tcl:63 -msgid "Fetch Immediately" -msgstr "Ðезабавно доÑтавÑне" +#: lib/blame.tcl:284 +msgid "Find Text..." +msgstr "ТърÑене на текÑт…" -#: lib/remote_add.tcl:69 -msgid "Initialize Remote Repository and Push" -msgstr "Инициализиране на отдалеченото хранилище и изтлаÑкване на промените" +#: lib/blame.tcl:288 +msgid "Goto Line..." +msgstr "Към ред…" -#: lib/remote_add.tcl:75 -msgid "Do Nothing Else Now" -msgstr "Да не Ñе прави нищо" +#: lib/blame.tcl:297 +msgid "Do Full Copy Detection" +msgstr "Пълно търÑене на копиране" -#: lib/remote_add.tcl:100 -msgid "Please supply a remote name." -msgstr "Задайте име за отдалеченото хранилище." +#: lib/blame.tcl:301 +msgid "Show History Context" +msgstr "Показване на контекÑта от иÑториÑта" -#: lib/remote_add.tcl:113 -#, tcl-format -msgid "'%s' is not an acceptable remote name." -msgstr "Отдалечено хранилище не може да Ñе казва „%s“." +#: lib/blame.tcl:304 +msgid "Blame Parent Commit" +msgstr "Ðнотиране на родителÑкото подаване" -#: lib/remote_add.tcl:124 +#: lib/blame.tcl:466 #, tcl-format -msgid "Failed to add remote '%s' of location '%s'." -msgstr "ÐеуÑпешно добавÑне на отдалеченото хранилище „%s“ от Ð°Ð´Ñ€ÐµÑ â€ž%s“." +msgid "Reading %s..." +msgstr "Чете Ñе „%s“…" -#: lib/remote_add.tcl:132 lib/transport.tcl:6 -#, tcl-format -msgid "fetch %s" -msgstr "доÑтавÑне на „%s“" +#: lib/blame.tcl:594 +msgid "Loading copy/move tracking annotations..." +msgstr "Зареждане на анотациите за проÑледÑване на копирането/премеÑтването…" -#: lib/remote_add.tcl:133 -#, tcl-format -msgid "Fetching the %s" -msgstr "ДоÑтавÑне на „%s“" +#: lib/blame.tcl:614 +msgid "lines annotated" +msgstr "реда анотирани" -#: lib/remote_add.tcl:156 -#, tcl-format -msgid "Do not know how to initialize repository at location '%s'." -msgstr "Хранилището Ñ Ð¼ÐµÑтоположение „%s“ не може да бъде инициализирано." +#: lib/blame.tcl:806 +msgid "Loading original location annotations..." +msgstr "Зареждане на анотациите за първоначалното меÑтоположение…" -#: lib/remote_add.tcl:162 lib/transport.tcl:54 lib/transport.tcl:92 -#: lib/transport.tcl:110 -#, tcl-format -msgid "push %s" -msgstr "изтлаÑкване на „%s“" +#: lib/blame.tcl:809 +msgid "Annotation complete." +msgstr "Ðнотирането завърши." -#: lib/remote_add.tcl:163 -#, tcl-format -msgid "Setting up the %s (at %s)" -msgstr "ДобавÑне на хранилище „%s“ (Ñ Ð°Ð´Ñ€ÐµÑ â€ž%s“)" +#: lib/blame.tcl:839 +msgid "Busy" +msgstr "ОперациÑта не е завършила" -#: lib/browser.tcl:17 -msgid "Starting..." -msgstr "Стартиране…" +#: lib/blame.tcl:840 +msgid "Annotation process is already running." +msgstr "Ð’ момента тече Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð½Ð° анотиране." -#: lib/browser.tcl:27 -msgid "File Browser" -msgstr "Файлов браузър" +#: lib/blame.tcl:879 +msgid "Running thorough copy detection..." +msgstr "ИзпълнÑва Ñе цÑлоÑтен Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð½Ð° откриване на копиране…" -#: lib/browser.tcl:132 lib/browser.tcl:149 -#, tcl-format -msgid "Loading %s..." -msgstr "Зареждане на „%s“…" +#: lib/blame.tcl:947 +msgid "Loading annotation..." +msgstr "Зареждане на анотации…" -#: lib/browser.tcl:193 -msgid "[Up To Parent]" -msgstr "[Към родителÑ]" +#: lib/blame.tcl:1000 +msgid "Author:" +msgstr "Ðвтор:" -#: lib/browser.tcl:275 lib/browser.tcl:282 -msgid "Browse Branch Files" -msgstr "Разглеждане на файловете в клона" +#: lib/blame.tcl:1004 +msgid "Committer:" +msgstr "Подал:" -#: lib/browser.tcl:288 lib/choose_repository.tcl:422 -#: lib/choose_repository.tcl:509 lib/choose_repository.tcl:518 -#: lib/choose_repository.tcl:1074 -msgid "Browse" -msgstr "Разглеждане" +#: lib/blame.tcl:1009 +msgid "Original File:" +msgstr "Първоначален файл:" -#: lib/browser.tcl:297 lib/branch_checkout.tcl:35 lib/tools_dlg.tcl:321 -msgid "Revision" -msgstr "ВерÑиÑ" +#: lib/blame.tcl:1057 +msgid "Cannot find HEAD commit:" +msgstr "Подаването за връх „HEAD“ не може да Ñе открие:" -#: lib/tools.tcl:75 -#, tcl-format -msgid "Running %s requires a selected file." -msgstr "За изпълнението на „%s“ трÑбва да изберете файл." +#: lib/blame.tcl:1112 +msgid "Cannot find parent commit:" +msgstr "РодителÑкото подаване не може да бъде открито" -#: lib/tools.tcl:91 -#, tcl-format -msgid "Are you sure you want to run %1$s on file \"%2$s\"?" -msgstr "Сигурни ли Ñте, че иÑкате да изпълните „%1$s“ върху файла „%2$s“?" +#: lib/blame.tcl:1127 +msgid "Unable to display parent" +msgstr "РодителÑÑ‚ не може да бъде показан" -#: lib/tools.tcl:95 -#, tcl-format -msgid "Are you sure you want to run %s?" -msgstr "Сигурни ли Ñте, че иÑкате да изпълните „%s“?" +#: lib/blame.tcl:1128 lib/diff.tcl:358 +msgid "Error loading diff:" +msgstr "Грешка при зареждане на разлика:" -#: lib/tools.tcl:116 -#, tcl-format -msgid "Tool: %s" -msgstr "Команда: %s" +#: lib/blame.tcl:1269 +msgid "Originally By:" +msgstr "Първоначално от:" -#: lib/tools.tcl:117 -#, tcl-format -msgid "Running: %s" -msgstr "Изпълнение: %s" +#: lib/blame.tcl:1275 +msgid "In File:" +msgstr "Във файл:" -#: lib/tools.tcl:155 -#, tcl-format -msgid "Tool completed successfully: %s" -msgstr "Командата завърши уÑпешно: %s" +#: lib/blame.tcl:1280 +msgid "Copied Or Moved Here By:" +msgstr "Копирано или премеÑтено тук от:" -#: lib/tools.tcl:157 +#: lib/branch_checkout.tcl:16 #, tcl-format -msgid "Tool failed: %s" -msgstr "Командата върна грешка: %s" +msgid "%s (%s): Checkout Branch" +msgstr "%s (%s): Клон за изтеглÑне" -#: lib/branch_checkout.tcl:16 lib/branch_checkout.tcl:21 +#: lib/branch_checkout.tcl:21 msgid "Checkout Branch" msgstr "Клон за изтеглÑне" @@ -802,7 +732,19 @@ msgstr "Клон за изтеглÑне" msgid "Checkout" msgstr "ИзтеглÑне" -#: lib/branch_checkout.tcl:39 lib/option.tcl:310 lib/branch_create.tcl:69 +#: lib/branch_checkout.tcl:30 lib/branch_create.tcl:37 lib/branch_delete.tcl:34 +#: lib/branch_rename.tcl:32 lib/browser.tcl:292 lib/checkout_op.tcl:579 +#: lib/choose_font.tcl:45 lib/merge.tcl:178 lib/option.tcl:127 +#: lib/remote_add.tcl:34 lib/remote_branch_delete.tcl:43 lib/tools_dlg.tcl:41 +#: lib/tools_dlg.tcl:202 lib/tools_dlg.tcl:345 lib/transport.tcl:141 +msgid "Cancel" +msgstr "Отказване" + +#: lib/branch_checkout.tcl:35 lib/browser.tcl:297 lib/tools_dlg.tcl:321 +msgid "Revision" +msgstr "ВерÑиÑ" + +#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:69 lib/option.tcl:310 msgid "Options" msgstr "Опции" @@ -814,167 +756,129 @@ msgstr "ИзтеглÑне на промените от ÑÐ»ÐµÐ´ÐµÐ½Ð¸Ñ ÐºÐ»Ð¾Ð msgid "Detach From Local Branch" msgstr "Изтриване от Ð»Ð¾ÐºÐ°Ð»Ð½Ð¸Ñ ÐºÐ»Ð¾Ð½" -#: 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 +#: lib/branch_create.tcl:23 #, tcl-format -msgid "Pruning tracking branches deleted from %s" -msgstr "ОкаÑтрÑне на ÑледÑщите клони на изтритите клони от „%s“" - -#: lib/transport.tcl:25 -msgid "fetch all remotes" -msgstr "доÑтавÑне на вÑички отдалечени хранилища" +msgid "%s (%s): Create Branch" +msgstr "%s (%s): Създаване на клон" -#: lib/transport.tcl:26 -msgid "Fetching new changes from all remotes" -msgstr "ДоÑтавÑне на новите промени от вÑички отдалечени хранилища" - -#: lib/transport.tcl:40 -msgid "remote prune all remotes" -msgstr "окаÑтрÑне на вÑички ÑледÑщи клони" +#: lib/branch_create.tcl:28 +msgid "Create New Branch" +msgstr "Създаване на нов клон" -#: lib/transport.tcl:41 -msgid "Pruning tracking branches deleted from all remotes" -msgstr "" -"ОкаÑтрÑне на вÑички клони, които ÑледÑÑ‚ изтрити клони от отдалечени хранилища" +#: lib/branch_create.tcl:33 lib/choose_repository.tcl:407 +msgid "Create" +msgstr "Създаване" -#: lib/transport.tcl:55 -#, tcl-format -msgid "Pushing changes to %s" -msgstr "ИзтлаÑкване на промените към „%s“" +#: lib/branch_create.tcl:42 +msgid "Branch Name" +msgstr "Име на клона" -#: lib/transport.tcl:93 -#, tcl-format -msgid "Mirroring to %s" -msgstr "ИзтлаÑкване на вÑичко към „%s“" +#: lib/branch_create.tcl:44 lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 +msgid "Name:" +msgstr "Име:" -#: lib/transport.tcl:111 -#, tcl-format -msgid "Pushing %s %s to %s" -msgstr "ИзтлаÑкване на %s „%s“ към „%s“" +#: lib/branch_create.tcl:57 +msgid "Match Tracking Branch Name" +msgstr "Съвпадане по името на ÑÐ»ÐµÐ´ÐµÐ½Ð¸Ñ ÐºÐ»Ð¾Ð½" -#: lib/transport.tcl:132 -msgid "Push Branches" -msgstr "Клони за изтлаÑкване" +#: lib/branch_create.tcl:66 +msgid "Starting Revision" +msgstr "Ðачална верÑиÑ" -#: lib/transport.tcl:147 -msgid "Source Branches" -msgstr "Клони-източници" +#: lib/branch_create.tcl:72 +msgid "Update Existing Branch:" +msgstr "ОбновÑване на ÑъщеÑтвуващ клон:" -#: lib/transport.tcl:162 -msgid "Destination Repository" -msgstr "Целево хранилище" +#: lib/branch_create.tcl:75 +msgid "No" +msgstr "Ðе" -#: lib/transport.tcl:165 lib/remote_branch_delete.tcl:51 -msgid "Remote:" -msgstr "Отдалечено хранилище:" +#: lib/branch_create.tcl:80 +msgid "Fast Forward Only" +msgstr "Само тривиално превъртащо Ñливане" -#: lib/transport.tcl:187 lib/remote_branch_delete.tcl:72 -msgid "Arbitrary Location:" -msgstr "Произволно меÑтоположение:" +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571 +msgid "Reset" +msgstr "Отначало" -#: lib/transport.tcl:205 -msgid "Transfer Options" -msgstr "ÐаÑтройки при пренаÑÑнето" +#: lib/branch_create.tcl:97 +msgid "Checkout After Creation" +msgstr "Преминаване към клона Ñлед Ñъздаването му" -#: lib/transport.tcl:207 -msgid "Force overwrite existing branch (may discard changes)" -msgstr "" -"Изрично презапиÑване на ÑъщеÑтвуващ клон (нÑкои промени може да бъдат " -"загубени)" +#: lib/branch_create.tcl:132 +msgid "Please select a tracking branch." +msgstr "Изберете клон за Ñледени." -#: lib/transport.tcl:211 -msgid "Use thin pack (for slow network connections)" -msgstr "МакÑимална компреÑÐ¸Ñ (за бавни мрежови връзки)" +#: lib/branch_create.tcl:141 +#, tcl-format +msgid "Tracking branch %s is not a branch in the remote repository." +msgstr "СледÑщиÑÑ‚ клон — „%s“, не ÑъщеÑтвува в отдалеченото хранилище." -#: lib/transport.tcl:215 -msgid "Include tags" -msgstr "Включване на етикетите" +#: lib/branch_create.tcl:154 lib/branch_rename.tcl:92 +msgid "Please supply a branch name." +msgstr "Дайте име на клона." -#: lib/status_bar.tcl:87 +#: lib/branch_create.tcl:165 lib/branch_rename.tcl:112 #, tcl-format -msgid "%s ... %*i of %*i %s (%3i%%)" -msgstr "%s… %*i от общо %*i %s (%3i%%)" +msgid "'%s' is not an acceptable branch name." +msgstr "„%s“ не може да Ñе използва за име на клон." -#: lib/remote.tcl:200 -msgid "Push to" -msgstr "ИзтлаÑкване към" +#: lib/branch_delete.tcl:16 +#, tcl-format +msgid "%s (%s): Delete Branch" +msgstr "%s (%s): Изтриване на клон" -#: lib/remote.tcl:218 -msgid "Remove Remote" -msgstr "Премахване на отдалечено хранилище" +#: lib/branch_delete.tcl:21 +msgid "Delete Local Branch" +msgstr "Изтриване на локален клон" -#: lib/remote.tcl:223 -msgid "Prune from" -msgstr "ОкаÑтрÑне от" +#: lib/branch_delete.tcl:39 +msgid "Local Branches" +msgstr "Локални клони" -#: lib/remote.tcl:228 -msgid "Fetch from" -msgstr "ДоÑтавÑне от" +#: lib/branch_delete.tcl:51 +msgid "Delete Only If Merged Into" +msgstr "Изтриване, Ñамо ако промените Ñа Ñлети и другаде" -#: lib/sshkey.tcl:31 -msgid "No keys found." -msgstr "Ðе Ñа открити ключове." +#: lib/branch_delete.tcl:53 lib/remote_branch_delete.tcl:120 +msgid "Always (Do not perform merge checks)" +msgstr "Винаги (без проверка за Ñливане)" -#: lib/sshkey.tcl:34 +#: lib/branch_delete.tcl:103 #, tcl-format -msgid "Found a public key in: %s" -msgstr "Открит е публичен ключ в „%s“" - -#: lib/sshkey.tcl:40 -msgid "Generate Key" -msgstr "Генериране на ключ" - -#: lib/sshkey.tcl:55 lib/checkout_op.tcl:146 lib/console.tcl:81 -#: lib/database.tcl:30 -msgid "Close" -msgstr "ЗатварÑне" - -#: lib/sshkey.tcl:58 -msgid "Copy To Clipboard" -msgstr "Копиране към ÑиÑÑ‚ÐµÐ¼Ð½Ð¸Ñ Ð±ÑƒÑ„ÐµÑ€" +msgid "The following branches are not completely merged into %s:" +msgstr "Ðе вÑички промени в клоните Ñа Ñлети в „%s“:" -#: lib/sshkey.tcl:72 -msgid "Your OpenSSH Public Key" -msgstr "ПубличниÑÑ‚ ви ключ за OpenSSH" +#: lib/branch_delete.tcl:115 lib/remote_branch_delete.tcl:218 +msgid "" +"Recovering deleted branches is difficult.\n" +"\n" +"Delete the selected branches?" +msgstr "" +"ВъзÑтановÑването на изтрити клони може да е трудно.\n" +"\n" +"Сигурни ли Ñте, че иÑкате да триете?" -#: lib/sshkey.tcl:80 -msgid "Generating..." -msgstr "Генериране…" +#: lib/branch_delete.tcl:131 +#, tcl-format +msgid " - %s:" +msgstr " — „%s:“" -#: lib/sshkey.tcl:86 +#: lib/branch_delete.tcl:141 #, tcl-format msgid "" -"Could not start ssh-keygen:\n" -"\n" +"Failed to delete branches:\n" "%s" msgstr "" -"Програмата „ssh-keygen“ не може да бъде Ñтартирана:\n" -"\n" +"ÐеуÑпешно триене на клони:\n" "%s" -#: lib/sshkey.tcl:113 -msgid "Generation failed." -msgstr "ÐеуÑпешно генериране." - -#: lib/sshkey.tcl:120 -msgid "Generation succeeded, but no keys found." -msgstr "Генерирането завърши уÑпешно, а не Ñа намерени ключове." - -#: lib/sshkey.tcl:123 +#: lib/branch_rename.tcl:15 #, tcl-format -msgid "Your key is in: %s" -msgstr "Ключът ви е в „%s“" +msgid "%s (%s): Rename Branch" +msgstr "%s (%s): Преименуване на клон" -#: lib/branch_rename.tcl:15 lib/branch_rename.tcl:23 +#: lib/branch_rename.tcl:23 msgid "Rename Branch" msgstr "Преименуване на клон" @@ -994,611 +898,218 @@ msgstr "Ðово име:" 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/branch_rename.tcl:102 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." msgstr "Клонът „%s“ вече ÑъщеÑтвува." -#: lib/branch_rename.tcl:112 lib/branch_create.tcl:165 -#, tcl-format -msgid "'%s' is not an acceptable branch name." -msgstr "„%s“ не може да Ñе използва за име на клон." - #: lib/branch_rename.tcl:123 #, tcl-format msgid "Failed to rename '%s'." msgstr "ÐеуÑпешно преименуване на „%s“." -#: 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/browser.tcl:17 +msgid "Starting..." +msgstr "Стартиране…" -#: lib/option.tcl:11 +#: lib/browser.tcl:27 #, tcl-format -msgid "Invalid global encoding '%s'" -msgstr "Ðеправилно глобално кодиране „%s“" +msgid "%s (%s): File Browser" +msgstr "%s (%s): Файлов браузър" -#: lib/option.tcl:19 +#: lib/browser.tcl:132 lib/browser.tcl:149 #, tcl-format -msgid "Invalid repo encoding '%s'" -msgstr "Ðеправилно кодиране „%s“ на хранилището" - -#: lib/option.tcl:119 -msgid "Restore Defaults" -msgstr "Стандартни наÑтройки" +msgid "Loading %s..." +msgstr "Зареждане на „%s“…" -#: lib/option.tcl:123 -msgid "Save" -msgstr "Запазване" +#: lib/browser.tcl:193 +msgid "[Up To Parent]" +msgstr "[Към родителÑ]" -#: lib/option.tcl:133 +#: lib/browser.tcl:275 #, tcl-format -msgid "%s Repository" -msgstr "Хранилище „%s“" - -#: lib/option.tcl:134 -msgid "Global (All Repositories)" -msgstr "Глобално (за вÑички хранилища)" +msgid "%s (%s): Browse Branch Files" +msgstr "%s (%s): Разглеждане на файловете в клона" -#: lib/option.tcl:140 -msgid "User Name" -msgstr "ПотребителÑко име" - -#: lib/option.tcl:141 -msgid "Email Address" -msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ð° е-поща" - -#: lib/option.tcl:143 -msgid "Summarize Merge Commits" -msgstr "Обобщаване на подаваниÑта при Ñливане" +#: lib/browser.tcl:282 +msgid "Browse Branch Files" +msgstr "Разглеждане на файловете в клона" -#: lib/option.tcl:144 -msgid "Merge Verbosity" -msgstr "ПодробноÑти при ÑливаниÑта" +#: lib/browser.tcl:288 lib/choose_repository.tcl:422 +#: lib/choose_repository.tcl:509 lib/choose_repository.tcl:518 +#: lib/choose_repository.tcl:1074 +msgid "Browse" +msgstr "Разглеждане" -#: lib/option.tcl:145 -msgid "Show Diffstat After Merge" -msgstr "Извеждане на ÑтатиÑтика Ñлед ÑливаниÑта" +#: lib/checkout_op.tcl:85 +#, tcl-format +msgid "Fetching %s from %s" +msgstr "ДоÑтавÑне на „%s“ от „%s“" -#: lib/option.tcl:146 -msgid "Use Merge Tool" -msgstr "Използване на програма за Ñливане" +#: lib/checkout_op.tcl:133 +#, tcl-format +msgid "fatal: Cannot resolve %s" +msgstr "фатална грешка: „%s“ не може да Ñе открие" -#: lib/option.tcl:148 -msgid "Trust File Modification Timestamps" -msgstr "Доверие във времето на промÑна на файловете" +#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:30 +#: lib/sshkey.tcl:55 +msgid "Close" +msgstr "ЗатварÑне" -#: lib/option.tcl:149 -msgid "Prune Tracking Branches During Fetch" -msgstr "ОкаÑтрÑне на ÑледÑщите клонове при доÑтавÑне" +#: lib/checkout_op.tcl:175 +#, tcl-format +msgid "Branch '%s' does not exist." +msgstr "Клонът „%s“ не ÑъщеÑтвува." -#: lib/option.tcl:150 -msgid "Match Tracking Branches" -msgstr "ÐапаÑване на ÑледÑщите клонове" +#: lib/checkout_op.tcl:194 +#, tcl-format +msgid "Failed to configure simplified git-pull for '%s'." +msgstr "ÐеуÑпешно наÑтройване на опроÑтен git-pull за „%s“." -#: lib/option.tcl:151 -msgid "Use Textconv For Diffs and Blames" +#: lib/checkout_op.tcl:229 +#, tcl-format +msgid "" +"Branch '%s' already exists.\n" +"\n" +"It cannot fast-forward to %s.\n" +"A merge is required." msgstr "" -"Преобразуване на текÑта Ñ â€ž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 "Брой редове за контекÑта при извеждане на разликите" - -#: lib/option.tcl:157 -msgid "Additional Diff Parameters" -msgstr "Допълнителни аргументи към „git 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 "Предупреждаване при подаването при неÑвързан връх" - -#: 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 "СмÑна на шрифта" +"Клонът „%s“ ÑъщеÑтвува.\n" +"\n" +"Той не може да бъде тривиално ÑлÑÑ‚ до „%s“.\n" +"Ðеобходимо е Ñливане." -#: lib/option.tcl:288 +#: lib/checkout_op.tcl:243 #, tcl-format -msgid "Choose %s" -msgstr "Избор на „%s“" - -#: lib/option.tcl:294 -msgid "pt." -msgstr "тчк." - -#: lib/option.tcl:308 -msgid "Preferences" -msgstr "ÐаÑтройки" - -#: lib/option.tcl:345 -msgid "Failed to completely save options:" -msgstr "ÐеуÑпешно запазване на наÑтройките:" - -#: lib/encoding.tcl:443 -msgid "Default" -msgstr "Стандартното" +msgid "Merge strategy '%s' not supported." +msgstr "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ð·Ð° Ñливане „%s“ не Ñе поддържа." -#: lib/encoding.tcl:448 +#: lib/checkout_op.tcl:262 #, tcl-format -msgid "System (%s)" -msgstr "СиÑтемното (%s)" - -#: lib/encoding.tcl:459 lib/encoding.tcl:465 -msgid "Other" -msgstr "Друго" - -#: lib/mergetool.tcl:8 -msgid "Force resolution to the base version?" -msgstr "Да Ñе използва базовата верÑиÑ" - -#: lib/mergetool.tcl:9 -msgid "Force resolution to this branch?" -msgstr "Да Ñе използва верÑиÑта от този клон" +msgid "Failed to update '%s'." +msgstr "ÐеуÑпешно обновÑване на „%s“." -#: lib/mergetool.tcl:10 -msgid "Force resolution to the other branch?" -msgstr "Да Ñе използва верÑиÑта от Ð´Ñ€ÑƒÐ³Ð¸Ñ ÐºÐ»Ð¾Ð½" +#: lib/checkout_op.tcl:274 +msgid "Staging area (index) is already locked." +msgstr "ИндекÑÑŠÑ‚ вече е заключен." -#: lib/mergetool.tcl:14 -#, tcl-format +#: lib/checkout_op.tcl:289 msgid "" -"Note that the diff shows only conflicting changes.\n" +"Last scanned state does not match repository state.\n" "\n" -"%s will be overwritten.\n" +"Another Git program has modified this repository since the last scan. A " +"rescan must be performed before the current branch can be changed.\n" "\n" -"This operation can be undone only by restarting the merge." +"The rescan will be automatically started now.\n" msgstr "" -"Разликата показва Ñамо разликите Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚.\n" +"СъÑтоÑнието при поÑледната проверка не Ð¾Ñ‚Ð³Ð¾Ð²Ð°Ñ€Ñ Ð½Ð° ÑÑŠÑтоÑнието на " +"хранилището.\n" "\n" -"Файлът „%s“ ще бъде презапиÑан.\n" +"ÐÑкой друг Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð·Ð° Git е променил хранилището междувременно. СъÑтоÑнието " +"трÑбва да бъде проверено, преди да Ñе премине към нов клон.\n" "\n" -"Тази Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да бъде отменена Ñамо чрез започване на Ñливането наново." - -#: lib/mergetool.tcl:45 -#, tcl-format -msgid "File %s seems to have unresolved conflicts, still stage?" -msgstr "" -"Изглежда, че вÑе още има некоригирани конфликти във файла „%s“. Да Ñе добави " -"ли файлът към индекÑа?" +"Ðвтоматично ще започне нова проверка.\n" -#: lib/mergetool.tcl:60 +#: lib/checkout_op.tcl:345 #, 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/mergetool.tcl:146 -msgid "Conflict file does not exist" -msgstr "Файлът, в който е конфликтът, не ÑъщеÑтвува" +msgid "Updating working directory to '%s'..." +msgstr "Работната Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ñе привежда към „%s“…" -#: lib/mergetool.tcl:246 -#, tcl-format -msgid "Not a GUI merge tool: '%s'" -msgstr "Това не е графична програма за Ñливане: „%s“" +#: lib/checkout_op.tcl:346 +msgid "files checked out" +msgstr "файла Ñа изтеглени" -#: lib/mergetool.tcl:275 +#: lib/checkout_op.tcl:376 #, tcl-format -msgid "Unsupported merge tool '%s'" -msgstr "Ðеподдържана програма за Ñливане: „%s“" +msgid "Aborted checkout of '%s' (file level merging is required)." +msgstr "" +"ПреуÑтановÑване на изтеглÑнето на „%s“ (необходимо е пофайлово Ñливане)." -#: lib/mergetool.tcl:310 -msgid "Merge tool is already running, terminate it?" -msgstr "Програмата за Ñливане вече е Ñтартирана. Да бъде ли изключена?" +#: lib/checkout_op.tcl:377 +msgid "File level merge required." +msgstr "Ðеобходимо е пофайлово Ñливане." -#: lib/mergetool.tcl:330 +#: lib/checkout_op.tcl:381 #, tcl-format -msgid "" -"Error retrieving versions:\n" -"%s" -msgstr "" -"Грешка при изтеглÑнето на верÑии:\n" -"%s" +msgid "Staying on branch '%s'." +msgstr "ОÑтаване върху клона „%s“." -#: lib/mergetool.tcl:350 -#, tcl-format +#: lib/checkout_op.tcl:452 msgid "" -"Could not start the merge tool:\n" +"You are no longer on a local branch.\n" "\n" -"%s" +"If you wanted to be on a branch, create one now starting from 'This Detached " +"Checkout'." msgstr "" -"Програмата за Ñливане не може да бъде Ñтартирана:\n" +"Вече не Ñте на локален клон.\n" "\n" -"%s" - -#: lib/mergetool.tcl:354 -msgid "Running merge tool..." -msgstr "Стартиране на програмата за Ñливане…" - -#: lib/mergetool.tcl:382 lib/mergetool.tcl:390 -msgid "Merge tool failed." -msgstr "Грешка в програмата за Ñливане." - -#: 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:46 -msgid "Tool Details" -msgstr "ПодробноÑти за командата" - -#: lib/tools_dlg.tcl:49 -msgid "Use '/' separators to create a submenu tree:" -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 +#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format -msgid "Tool '%s' already exists." -msgstr "Командата „%s“ вече ÑъщеÑтвува." +msgid "Checked out '%s'." +msgstr "„%s“ е изтеглен." -#: lib/tools_dlg.tcl:148 +#: lib/checkout_op.tcl:535 #, tcl-format -msgid "" -"Could not add tool:\n" -"%s" +msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "" -"Командата не може да бъде добавена:\n" -"%s" - -#: lib/tools_dlg.tcl:187 -msgid "Remove Tool" -msgstr "Премахване на команда" - -#: lib/tools_dlg.tcl:193 -msgid "Remove Tool Commands" -msgstr "Премахване на команди" - -#: lib/tools_dlg.tcl:198 -msgid "Remove" -msgstr "Премахване" +"ЗанулÑването на „%s“ към „%s“ ще доведе до загубването на Ñледните подаваниÑ:" -#: lib/tools_dlg.tcl:231 -msgid "(Blue denotes repository-local tools)" -msgstr "(командите към локалното хранилище Ñа обозначени в Ñиньо)" +#: lib/checkout_op.tcl:557 +msgid "Recovering lost commits may not be easy." +msgstr "ВъзÑтановÑването на загубените Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да е трудно." -#: lib/tools_dlg.tcl:292 +#: lib/checkout_op.tcl:562 #, tcl-format -msgid "Run Command: %s" -msgstr "Изпълнение на командата „%s“" - -#: lib/tools_dlg.tcl:306 -msgid "Arguments" -msgstr "Ðргументи" +msgid "Reset '%s'?" +msgstr "ЗанулÑване на „%s“?" -#: lib/tools_dlg.tcl:336 lib/checkout_op.tcl:567 lib/merge.tcl:166 +#: lib/checkout_op.tcl:567 lib/merge.tcl:170 lib/tools_dlg.tcl:336 msgid "Visualize" msgstr "ВизуализациÑ" -#: lib/tools_dlg.tcl:341 -msgid "OK" -msgstr "Добре" - -#: lib/search.tcl:48 -msgid "Find:" -msgstr "ТърÑене:" - -#: lib/search.tcl:50 -msgid "Next" -msgstr "Следваща поÑва" - -#: lib/search.tcl:51 -msgid "Prev" -msgstr "Предишна поÑва" - -#: lib/search.tcl:52 -msgid "RegExp" -msgstr "Рег. израз" - -#: lib/search.tcl:54 -msgid "Case" -msgstr "РегиÑтър" - -#: lib/shortcut.tcl:21 lib/shortcut.tcl:62 -msgid "Cannot write shortcut:" -msgstr "Клавишната ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ†Ð¸Ñ Ð½Ðµ може да бъде запазена:" - -#: lib/shortcut.tcl:137 -msgid "Cannot write icon:" -msgstr "Иконата не може да бъде запазена:" - -#: lib/diff.tcl:77 +#: lib/checkout_op.tcl:635 #, tcl-format msgid "" -"No differences detected.\n" -"\n" -"%s has no changes.\n" -"\n" -"The modification date of this file was updated by another application, but " -"the content within the file was not changed.\n" -"\n" -"A rescan will be automatically started to find other files which may have " -"the same state." -msgstr "" -"Ðе Ñа открити разлики.\n" -"\n" -"ÐÑма промени в „%s“.\n" +"Failed to set current branch.\n" "\n" -"Времето на промÑна на файла е бил зададен от друга програма, но Ñъдържанието " -"му не е променено.\n" +"This working directory is only partially switched. We successfully updated " +"your files, but failed to update an internal Git file.\n" "\n" -"Ðвтоматично ще започне нова проверка дали нÑма други файлове в това " -"ÑÑŠÑтоÑние." - -#: lib/diff.tcl:117 -#, tcl-format -msgid "Loading diff of %s..." -msgstr "Зареждане на разликите в „%s“…" - -#: lib/diff.tcl:140 -msgid "" -"LOCAL: deleted\n" -"REMOTE:\n" -msgstr "" -"ЛОКÐЛÐО: изтрит\n" -"ОТДÐЛЕЧЕÐО:\n" - -#: lib/diff.tcl:145 -msgid "" -"REMOTE: deleted\n" -"LOCAL:\n" -msgstr "" -"ОТДÐЛЕЧЕÐО: изтрит\n" -"ЛОКÐЛÐО:\n" - -#: lib/diff.tcl:152 -msgid "LOCAL:\n" -msgstr "ЛОКÐЛÐО:\n" - -#: lib/diff.tcl:155 -msgid "REMOTE:\n" -msgstr "ОТДÐЛЕЧЕÐО:\n" - -#: lib/diff.tcl:217 lib/diff.tcl:355 -#, tcl-format -msgid "Unable to display %s" -msgstr "Файлът „%s“ не може да бъде показан" - -#: lib/diff.tcl:218 -msgid "Error loading file:" -msgstr "Грешка при зареждане на файл:" - -#: lib/diff.tcl:225 -msgid "Git Repository (subproject)" -msgstr "Хранилище на Git (подмодул)" - -#: lib/diff.tcl:237 -msgid "* Binary file (not showing content)." -msgstr "◠Двоичен файл (Ñъдържанието не Ñе показва)." - -#: lib/diff.tcl:242 -#, tcl-format -msgid "" -"* Untracked file is %d bytes.\n" -"* Showing only first %d bytes.\n" +"This should not have occurred. %s will now close and give up." msgstr "" -"â— ÐеÑледениÑÑ‚ файл е %d байта.\n" -"◠Показват Ñе Ñамо първите %d байта.\n" - -#: lib/diff.tcl:248 -#, tcl-format -msgid "" +"ÐеуÑпешно задаване на Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ ÐºÐ»Ð¾Ð½.\n" "\n" -"* Untracked file clipped here by %s.\n" -"* To see the entire file, use an external editor.\n" -msgstr "" +"Работната Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ðµ Ñамо чаÑтично обновена: файловете Ñа обновени " +"уÑпешно, но нÑкой от вътрешните, Ñлужебни файлове на Git не е бил.\n" "\n" -"â— ÐеÑледениÑÑ‚ файл е отрÑзан дотук от програмата „%s“.\n" -"◠Използвайте външен редактор, за да видите Ñ†ÐµÐ»Ð¸Ñ Ñ„Ð°Ð¹Ð».\n" - -#: lib/diff.tcl:356 lib/blame.tcl:1128 -msgid "Error loading diff:" -msgstr "Грешка при зареждане на разлика:" - -#: lib/diff.tcl:578 -msgid "Failed to unstage selected hunk." -msgstr "Избраното парче не може да бъде извадено от индекÑа." - -#: lib/diff.tcl:585 -msgid "Failed to stage selected hunk." -msgstr "Избраното парче не може да бъде добавено към индекÑа." - -#: lib/diff.tcl:664 -msgid "Failed to unstage selected line." -msgstr "ИзбраниÑÑ‚ ред не може да бъде изваден от индекÑа." - -#: lib/diff.tcl:672 -msgid "Failed to stage selected line." -msgstr "ИзбраниÑÑ‚ ред не може да бъде добавен към индекÑа." - -#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 -msgid "Delete Branch Remotely" -msgstr "Изтриване на Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ ÐºÐ»Ð¾Ð½" - -#: lib/remote_branch_delete.tcl:48 -msgid "From Repository" -msgstr "От хранилище" - -#: lib/remote_branch_delete.tcl:88 -msgid "Branches" -msgstr "Клони" - -#: lib/remote_branch_delete.tcl:110 -msgid "Delete Only If" -msgstr "Изтриване, Ñамо ако" - -#: lib/remote_branch_delete.tcl:112 -msgid "Merged Into:" -msgstr "СлÑÑ‚ в:" - -#: lib/remote_branch_delete.tcl:120 lib/branch_delete.tcl:53 -msgid "Always (Do not perform merge checks)" -msgstr "Винаги (без проверка за Ñливане)" +"Това ÑÑŠÑтоÑние е аварийно и не трÑбва да Ñе Ñлучва. Програмата „%s“ ще " +"преуÑтанови работа." -#: lib/remote_branch_delete.tcl:153 -msgid "A branch is required for 'Merged Into'." -msgstr "За данните „СлÑÑ‚ в“ е необходимо да зададете клон." +#: lib/choose_font.tcl:41 +msgid "Select" +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/choose_font.tcl:55 +msgid "Font Family" +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/choose_font.tcl:76 +msgid "Font Size" +msgstr "Размер" -#: lib/remote_branch_delete.tcl:208 -msgid "Please select one or more branches to delete." -msgstr "Изберете поне един клон за изтриване." +#: lib/choose_font.tcl:93 +msgid "Font Example" +msgstr "МоÑтра" -#: lib/remote_branch_delete.tcl:218 lib/branch_delete.tcl:115 +#: lib/choose_font.tcl:105 msgid "" -"Recovering deleted branches is difficult.\n" -"\n" -"Delete the selected branches?" +"This is example text.\n" +"If you like this text, it can be your font." msgstr "" -"ВъзÑтановÑването на изтрити клони може да е трудно.\n" -"\n" -"Сигурни ли Ñте, че иÑкате да триете?" - -#: lib/remote_branch_delete.tcl:227 -#, tcl-format -msgid "Deleting branches from %s" -msgstr "Изтриване на клони от „%s“" - -#: lib/remote_branch_delete.tcl:300 -msgid "No repository selected." -msgstr "Ðе е избрано хранилище." - -#: lib/remote_branch_delete.tcl:305 -#, tcl-format -msgid "Scanning %s..." -msgstr "ПретърÑване на „%s“…" +"Това е примерен текÑÑ‚.\n" +"Ðко ви хареÑва как изглежда, изберете шрифта." #: lib/choose_repository.tcl:33 msgid "Git Gui" @@ -1642,10 +1153,6 @@ msgstr "ОтварÑне на хранилище ползвано наÑкоро msgid "Failed to create repository %s:" msgstr "ÐеуÑпешно Ñъздаване на хранилището „%s“:" -#: lib/choose_repository.tcl:407 lib/branch_create.tcl:33 -msgid "Create" -msgstr "Създаване" - #: lib/choose_repository.tcl:417 msgid "Directory:" msgstr "ДиректориÑ:" @@ -1830,7 +1337,7 @@ msgstr "Подмодулите не могат да Ñе клонират." #: lib/choose_repository.tcl:990 msgid "Cloning submodules" -msgstr "Клониране на подмодулите" +msgstr "Клониране на подмодули" #: lib/choose_repository.tcl:1015 msgid "Initial file checkout failed." @@ -1849,208 +1356,6 @@ msgstr "Хранилище:" msgid "Failed to open repository %s:" msgstr "ÐеуÑпешно отварÑне на хранилището „%s“:" -#: lib/about.tcl:26 -msgid "git-gui - a graphical user interface for Git." -msgstr "git-gui — графичен Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð·Ð° Git." - -#: lib/checkout_op.tcl:85 -#, tcl-format -msgid "Fetching %s from %s" -msgstr "ДоÑтавÑне на „%s“ от „%s“" - -#: lib/checkout_op.tcl:133 -#, tcl-format -msgid "fatal: Cannot resolve %s" -msgstr "фатална грешка: „%s“ не може да Ñе открие" - -#: lib/checkout_op.tcl:175 -#, tcl-format -msgid "Branch '%s' does not exist." -msgstr "Клонът „%s“ не ÑъщеÑтвува." - -#: lib/checkout_op.tcl:194 -#, tcl-format -msgid "Failed to configure simplified git-pull for '%s'." -msgstr "ÐеуÑпешно наÑтройване на опроÑтен git-pull за „%s“." - -#: lib/checkout_op.tcl:229 -#, tcl-format -msgid "" -"Branch '%s' already exists.\n" -"\n" -"It cannot fast-forward to %s.\n" -"A merge is required." -msgstr "" -"Клонът „%s“ ÑъщеÑтвува.\n" -"\n" -"Той не може да бъде тривиално ÑлÑÑ‚ до „%s“.\n" -"Ðеобходимо е Ñливане." - -#: lib/checkout_op.tcl:243 -#, tcl-format -msgid "Merge strategy '%s' not supported." -msgstr "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ð·Ð° Ñливане „%s“ не Ñе поддържа." - -#: lib/checkout_op.tcl:262 -#, tcl-format -msgid "Failed to update '%s'." -msgstr "ÐеуÑпешно обновÑване на „%s“." - -#: lib/checkout_op.tcl:274 -msgid "Staging area (index) is already locked." -msgstr "ИндекÑÑŠÑ‚ вече е заключен." - -#: lib/checkout_op.tcl:289 -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 the current branch can be changed.\n" -"\n" -"The rescan will be automatically started now.\n" -msgstr "" -"СъÑтоÑнието при поÑледната проверка не Ð¾Ñ‚Ð³Ð¾Ð²Ð°Ñ€Ñ Ð½Ð° ÑÑŠÑтоÑнието на " -"хранилището.\n" -"\n" -"ÐÑкой друг Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð·Ð° Git е променил хранилището междувременно. СъÑтоÑнието " -"трÑбва да бъде проверено, преди да Ñе премине към нов клон.\n" -"\n" -"Ðвтоматично ще започне нова проверка.\n" - -#: lib/checkout_op.tcl:345 -#, tcl-format -msgid "Updating working directory to '%s'..." -msgstr "Работната Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ñе привежда към „%s“…" - -#: lib/checkout_op.tcl:346 -msgid "files checked out" -msgstr "файла Ñа изтеглени" - -#: lib/checkout_op.tcl:376 -#, tcl-format -msgid "Aborted checkout of '%s' (file level merging is required)." -msgstr "" -"ПреуÑтановÑване на изтеглÑнето на „%s“ (необходимо е пофайлово Ñливане)." - -#: lib/checkout_op.tcl:377 -msgid "File level merge required." -msgstr "Ðеобходимо е пофайлово Ñливане." - -#: lib/checkout_op.tcl:381 -#, tcl-format -msgid "Staying on branch '%s'." -msgstr "ОÑтаване върху клона „%s“." - -#: lib/checkout_op.tcl:452 -msgid "" -"You are no longer on a local branch.\n" -"\n" -"If you wanted to be on a branch, create one now starting from 'This Detached " -"Checkout'." -msgstr "" -"Вече не Ñте на локален клон.\n" -"\n" -"Ðко иÑкате да Ñте на клон, Ñъздайте базиран на „Това неÑвързано изтеглÑне“." - -#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 -#, tcl-format -msgid "Checked out '%s'." -msgstr "„%s“ е изтеглен." - -#: lib/checkout_op.tcl:535 -#, tcl-format -msgid "Resetting '%s' to '%s' will lose the following commits:" -msgstr "" -"ЗанулÑването на „%s“ към „%s“ ще доведе до загубването на Ñледните подаваниÑ:" - -#: lib/checkout_op.tcl:557 -msgid "Recovering lost commits may not be easy." -msgstr "ВъзÑтановÑването на загубените Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да е трудно." - -#: lib/checkout_op.tcl:562 -#, tcl-format -msgid "Reset '%s'?" -msgstr "ЗанулÑване на „%s“?" - -#: lib/checkout_op.tcl:571 lib/branch_create.tcl:85 -msgid "Reset" -msgstr "Отначало" - -#: lib/checkout_op.tcl:635 -#, tcl-format -msgid "" -"Failed to set current branch.\n" -"\n" -"This working directory is only partially switched. We successfully updated " -"your files, but failed to update an internal Git file.\n" -"\n" -"This should not have occurred. %s will now close and give up." -msgstr "" -"ÐеуÑпешно задаване на Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ ÐºÐ»Ð¾Ð½.\n" -"\n" -"Работната Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ðµ Ñамо чаÑтично обновена: файловете Ñа обновени " -"уÑпешно, но нÑкой от вътрешните, Ñлужебни файлове на Git не е бил.\n" -"\n" -"Това ÑÑŠÑтоÑние е аварийно и не трÑбва да Ñе Ñлучва. Програмата „%s“ ще " -"преуÑтанови работа." - -#: lib/branch_create.tcl:23 -msgid "Create Branch" -msgstr "Създаване на клон" - -#: lib/branch_create.tcl:28 -msgid "Create New Branch" -msgstr "Създаване на нов клон" - -#: lib/branch_create.tcl:42 -msgid "Branch Name" -msgstr "Име на клона" - -#: lib/branch_create.tcl:57 -msgid "Match Tracking Branch Name" -msgstr "Съвпадане по името на ÑÐ»ÐµÐ´ÐµÐ½Ð¸Ñ ÐºÐ»Ð¾Ð½" - -#: lib/branch_create.tcl:66 -msgid "Starting Revision" -msgstr "Ðачална верÑиÑ" - -#: lib/branch_create.tcl:72 -msgid "Update Existing Branch:" -msgstr "ОбновÑване на ÑъщеÑтвуващ клон:" - -#: lib/branch_create.tcl:75 -msgid "No" -msgstr "Ðе" - -#: lib/branch_create.tcl:80 -msgid "Fast Forward Only" -msgstr "Само тривиално превъртащо Ñливане" - -#: lib/branch_create.tcl:97 -msgid "Checkout After Creation" -msgstr "Преминаване към клона Ñлед Ñъздаването му" - -#: lib/branch_create.tcl:132 -msgid "Please select a tracking branch." -msgstr "Изберете клон за Ñледени." - -#: lib/branch_create.tcl:141 -#, tcl-format -msgid "Tracking branch %s is not a branch in the remote repository." -msgstr "СледÑщиÑÑ‚ клон — „%s“, не ÑъщеÑтвува в отдалеченото хранилище." - -#: lib/console.tcl:59 -msgid "Working... please wait..." -msgstr "Ð’ момента Ñе извършва дейÑтвие, изчакайте…" - -#: lib/console.tcl:186 -msgid "Success" -msgstr "УÑпех" - -#: lib/console.tcl:200 -msgid "Error: Command Failed" -msgstr "Грешка: неуÑпешно изпълнение на команда" - #: lib/choose_rev.tcl:52 msgid "This Detached Checkout" msgstr "Това неÑвързано изтеглÑне" @@ -2092,14 +1397,6 @@ msgstr "Обновен" msgid "URL" msgstr "ÐдреÑ" -#: lib/line.tcl:17 -msgid "Goto Line:" -msgstr "Към ред:" - -#: lib/line.tcl:23 -msgid "Go" -msgstr "Придвижване" - #: lib/commit.tcl:9 msgid "" "There is nothing to amend.\n" @@ -2228,12 +1525,12 @@ msgid "" " \n" " Do you really want to proceed with your Commit?" msgstr "" -"Ще подавате към неÑвързан връх. Това е опаÑно — при изтеглÑнето на друг клон " -"ще изгубите промените Ñи. След това може да е невъзможно да ги възÑтановите " -"от журнала на указателите „reflog“. Ðай-вероÑтно трÑбва да отмените това " -"подаване и да Ñъздадете клон, в който да подадете.\n" +"Ще подадете към неÑвързан, отделÑн указател „HEAD“. Това е опаÑно, защото " +"при преминаването към клон ще загубите промените Ñи, като единÑтвениÑÑ‚ начин " +"да ги върнете ще е чрез журнала на указателите (reflog). Ðай-вероÑтно трÑбва " +"да не правите това подаване, а да Ñъздадете нов клон, преди да продължите.\n" " \n" -"Сигурни ли Ñте, че иÑкате да подадете към неÑвързан връх?" +"Сигурни ли Ñте, че иÑкате да извършите текущото подаване?" #: lib/commit.tcl:290 msgid "Calling commit-msg hook..." @@ -2251,7 +1548,7 @@ msgstr "Подаване на промените…" msgid "write-tree failed:" msgstr "неуÑпешно запазване на дървото (write-tree):" -#: lib/commit.tcl:335 lib/commit.tcl:379 lib/commit.tcl:400 +#: lib/commit.tcl:335 lib/commit.tcl:382 lib/commit.tcl:403 msgid "Commit failed." msgstr "ÐеуÑпешно подаване." @@ -2279,153 +1576,241 @@ msgstr "" msgid "No changes to commit." msgstr "ÐÑма промени за подаване." -#: lib/commit.tcl:378 +#: lib/commit.tcl:381 msgid "commit-tree failed:" msgstr "неуÑпешно подаване на дървото (commit-tree):" -#: lib/commit.tcl:399 +#: lib/commit.tcl:402 msgid "update-ref failed:" msgstr "неуÑпешно обновÑване на указателите (update-ref):" -#: lib/commit.tcl:492 +#: lib/commit.tcl:495 #, tcl-format msgid "Created commit %s: %s" msgstr "УÑпешно подаване %s: %s" -#: lib/branch_delete.tcl:16 -msgid "Delete Branch" -msgstr "Изтриване на клон" +#: lib/console.tcl:59 +msgid "Working... please wait..." +msgstr "Ð’ момента Ñе извършва дейÑтвие, изчакайте…" -#: lib/branch_delete.tcl:21 -msgid "Delete Local Branch" -msgstr "Изтриване на локален клон" +#: lib/console.tcl:186 +msgid "Success" +msgstr "УÑпех" -#: lib/branch_delete.tcl:39 -msgid "Local Branches" -msgstr "Локални клони" +#: lib/console.tcl:200 +msgid "Error: Command Failed" +msgstr "Грешка: неуÑпешно изпълнение на команда" -#: lib/branch_delete.tcl:51 -msgid "Delete Only If Merged Into" -msgstr "Изтриване, Ñамо ако промените Ñа Ñлети и другаде" +#: lib/database.tcl:42 +msgid "Number of loose objects" +msgstr "Брой непакетирани обекти" -#: lib/branch_delete.tcl:103 +#: lib/database.tcl:43 +msgid "Disk space used by loose objects" +msgstr "ДиÑково проÑтранÑтво заето от непакетирани обекти" + +#: lib/database.tcl:44 +msgid "Number of packed objects" +msgstr "Брой пакетирани обекти" + +#: lib/database.tcl:45 +msgid "Number of packs" +msgstr "Брой пакети" + +#: lib/database.tcl:46 +msgid "Disk space used by packed objects" +msgstr "ДиÑково проÑтранÑтво заето от пакетирани обекти" + +#: lib/database.tcl:47 +msgid "Packed objects waiting for pruning" +msgstr "Пакетирани обекти за окаÑтрÑне" + +#: lib/database.tcl:48 +msgid "Garbage files" +msgstr "Файлове за боклука" + +#: lib/database.tcl:57 lib/option.tcl:182 lib/option.tcl:197 lib/option.tcl:220 +#: lib/option.tcl:282 #, tcl-format -msgid "The following branches are not completely merged into %s:" -msgstr "Ðе вÑички промени в клоните Ñа Ñлети в „%s“:" +msgid "%s:" +msgstr "%s:" -#: lib/branch_delete.tcl:141 +#: lib/database.tcl:66 +#, tcl-format +msgid "%s (%s): Database Statistics" +msgstr "%s (%s): СтатиÑтика на базата от данни" + +#: lib/database.tcl:72 +msgid "Compressing the object database" +msgstr "КомпреÑиране на базата Ñ Ð´Ð°Ð½Ð½Ð¸ за обектите" + +#: lib/database.tcl:83 +msgid "Verifying the object database with fsck-objects" +msgstr "Проверка на базата Ñ Ð´Ð°Ð½Ð½Ð¸ за обектите Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð°Ñ‚Ð° „fsck-objects“" + +#: lib/database.tcl:107 #, tcl-format msgid "" -"Failed to delete branches:\n" -"%s" +"This repository currently has approximately %i loose objects.\n" +"\n" +"To maintain optimal performance it is strongly recommended that you compress " +"the database.\n" +"\n" +"Compress the database now?" msgstr "" -"ÐеуÑпешно триене на клони:\n" -"%s" - -#: lib/blame.tcl:73 -msgid "File Viewer" -msgstr "Преглед на файлове" +"Ð’ това хранилище в момента има към %i непакетирани обекти.\n" +"\n" +"За добра производителноÑÑ‚ Ñе препоръчва да компреÑирате базата Ñ Ð´Ð°Ð½Ð½Ð¸ за " +"обектите.\n" +"\n" +"Да Ñе започне ли компреÑирането?" -#: lib/blame.tcl:79 -msgid "Commit:" -msgstr "Подаване:" +#: lib/date.tcl:25 +#, tcl-format +msgid "Invalid date from Git: %s" +msgstr "Ðеправилни данни от Git: %s" -#: lib/blame.tcl:280 -msgid "Copy Commit" -msgstr "Копиране на подаване" +#: lib/diff.tcl:77 +#, tcl-format +msgid "" +"No differences detected.\n" +"\n" +"%s has no changes.\n" +"\n" +"The modification date of this file was updated by another application, but " +"the content within the file was not changed.\n" +"\n" +"A rescan will be automatically started to find other files which may have " +"the same state." +msgstr "" +"Ðе Ñа открити разлики.\n" +"\n" +"ÐÑма промени в „%s“.\n" +"\n" +"Времето на промÑна на файла е бил зададен от друга програма, но Ñъдържанието " +"му не е променено.\n" +"\n" +"Ðвтоматично ще започне нова проверка дали нÑма други файлове в това " +"ÑÑŠÑтоÑние." -#: lib/blame.tcl:284 -msgid "Find Text..." -msgstr "ТърÑене на текÑт…" +#: lib/diff.tcl:117 +#, tcl-format +msgid "Loading diff of %s..." +msgstr "Зареждане на разликите в „%s“…" -#: lib/blame.tcl:288 -msgid "Goto Line..." -msgstr "Към ред…" +#: lib/diff.tcl:143 +msgid "" +"LOCAL: deleted\n" +"REMOTE:\n" +msgstr "" +"ЛОКÐЛÐО: изтрит\n" +"ОТДÐЛЕЧЕÐО:\n" -#: lib/blame.tcl:297 -msgid "Do Full Copy Detection" -msgstr "Пълно търÑене на копиране" +#: lib/diff.tcl:148 +msgid "" +"REMOTE: deleted\n" +"LOCAL:\n" +msgstr "" +"ОТДÐЛЕЧЕÐО: изтрит\n" +"ЛОКÐЛÐО:\n" -#: lib/blame.tcl:301 -msgid "Show History Context" -msgstr "Показване на контекÑта от иÑториÑта" +#: lib/diff.tcl:155 +msgid "LOCAL:\n" +msgstr "ЛОКÐЛÐО:\n" -#: lib/blame.tcl:304 -msgid "Blame Parent Commit" -msgstr "Ðнотиране на родителÑкото подаване" +#: lib/diff.tcl:158 +msgid "REMOTE:\n" +msgstr "ОТДÐЛЕЧЕÐО:\n" -#: lib/blame.tcl:466 +#: lib/diff.tcl:220 lib/diff.tcl:357 #, tcl-format -msgid "Reading %s..." -msgstr "Чете Ñе „%s“…" +msgid "Unable to display %s" +msgstr "Файлът „%s“ не може да бъде показан" -#: lib/blame.tcl:594 -msgid "Loading copy/move tracking annotations..." -msgstr "Зареждане на анотациите за проÑледÑване на копирането/премеÑтването…" +#: lib/diff.tcl:221 +msgid "Error loading file:" +msgstr "Грешка при зареждане на файл:" -#: lib/blame.tcl:614 -msgid "lines annotated" -msgstr "реда анотирани" +#: lib/diff.tcl:227 +msgid "Git Repository (subproject)" +msgstr "Хранилище на Git (подмодул)" -#: lib/blame.tcl:806 -msgid "Loading original location annotations..." -msgstr "Зареждане на анотациите за първоначалното меÑтоположение…" +#: lib/diff.tcl:239 +msgid "* Binary file (not showing content)." +msgstr "◠Двоичен файл (Ñъдържанието не Ñе показва)." -#: lib/blame.tcl:809 -msgid "Annotation complete." -msgstr "Ðнотирането завърши." +#: lib/diff.tcl:244 +#, tcl-format +msgid "" +"* Untracked file is %d bytes.\n" +"* Showing only first %d bytes.\n" +msgstr "" +"â— ÐеÑледениÑÑ‚ файл е %d байта.\n" +"◠Показват Ñе Ñамо първите %d байта.\n" -#: lib/blame.tcl:839 -msgid "Busy" -msgstr "ОперациÑта не е завършила" +#: lib/diff.tcl:250 +#, tcl-format +msgid "" +"\n" +"* Untracked file clipped here by %s.\n" +"* To see the entire file, use an external editor.\n" +msgstr "" +"\n" +"â— ÐеÑледениÑÑ‚ файл е отрÑзан дотук от програмата „%s“.\n" +"◠Използвайте външен редактор, за да видите Ñ†ÐµÐ»Ð¸Ñ Ñ„Ð°Ð¹Ð».\n" -#: lib/blame.tcl:840 -msgid "Annotation process is already running." -msgstr "Ð’ момента тече Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð½Ð° анотиране." +#: lib/diff.tcl:580 +msgid "Failed to unstage selected hunk." +msgstr "Избраното парче не може да бъде извадено от индекÑа." -#: lib/blame.tcl:879 -msgid "Running thorough copy detection..." -msgstr "ИзпълнÑва Ñе цÑлоÑтен Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð½Ð° откриване на копиране…" +#: lib/diff.tcl:587 +msgid "Failed to stage selected hunk." +msgstr "Избраното парче не може да бъде добавено към индекÑа." -#: lib/blame.tcl:947 -msgid "Loading annotation..." -msgstr "Зареждане на анотации…" +#: lib/diff.tcl:666 +msgid "Failed to unstage selected line." +msgstr "ИзбраниÑÑ‚ ред не може да бъде изваден от индекÑа." -#: lib/blame.tcl:1000 -msgid "Author:" -msgstr "Ðвтор:" +#: lib/diff.tcl:674 +msgid "Failed to stage selected line." +msgstr "ИзбраниÑÑ‚ ред не може да бъде добавен към индекÑа." -#: lib/blame.tcl:1004 -msgid "Committer:" -msgstr "Подал:" +#: lib/encoding.tcl:443 +msgid "Default" +msgstr "Стандартното" -#: lib/blame.tcl:1009 -msgid "Original File:" -msgstr "Първоначален файл:" +#: lib/encoding.tcl:448 +#, tcl-format +msgid "System (%s)" +msgstr "СиÑтемното (%s)" -#: lib/blame.tcl:1057 -msgid "Cannot find HEAD commit:" -msgstr "Подаването за връх „HEAD“ не може да Ñе открие:" +#: lib/encoding.tcl:459 lib/encoding.tcl:465 +msgid "Other" +msgstr "Друго" -#: lib/blame.tcl:1112 -msgid "Cannot find parent commit:" -msgstr "РодителÑкото подаване не може да бъде открито" +#: lib/error.tcl:20 +#, tcl-format +msgid "%s: error" +msgstr "%s: грешка" -#: lib/blame.tcl:1127 -msgid "Unable to display parent" -msgstr "РодителÑÑ‚ не може да бъде показан" +#: lib/error.tcl:36 +#, tcl-format +msgid "%s: warning" +msgstr "%s: предупреждение" -#: lib/blame.tcl:1269 -msgid "Originally By:" -msgstr "Първоначално от:" +#: lib/error.tcl:80 +#, tcl-format +msgid "%s hook failed:" +msgstr "%s: грешка от куката" -#: lib/blame.tcl:1275 -msgid "In File:" -msgstr "Във файл:" +#: lib/error.tcl:96 +msgid "You must correct the above errors before committing." +msgstr "Преди да можете да подадете, коригирайте горните грешки." -#: lib/blame.tcl:1280 -msgid "Copied Or Moved Here By:" -msgstr "Копирано или премеÑтено тук от:" +#: lib/error.tcl:116 +#, tcl-format +msgid "%s (%s): error" +msgstr "%s (%s): грешка" #: lib/index.tcl:6 msgid "Unable to unlock the index." @@ -2451,6 +1836,10 @@ msgstr "Продължаване" msgid "Unlock Index" msgstr "Отключване на индекÑа" +#: lib/index.tcl:294 +msgid "Unstaging selected files from commit" +msgstr "Изваждане на избраните файлове от подаването" + #: lib/index.tcl:298 #, tcl-format msgid "Unstaging %s from commit" @@ -2460,6 +1849,10 @@ msgstr "Изваждане на „%s“ от подаването" msgid "Ready to commit." msgstr "ГотовноÑÑ‚ за подаване." +#: lib/index.tcl:346 +msgid "Adding selected files" +msgstr "ДобавÑне на избраните файлове" + #: lib/index.tcl:350 #, tcl-format msgid "Adding %s" @@ -2468,7 +1861,11 @@ msgstr "ДобавÑне на „%s“" #: lib/index.tcl:380 #, tcl-format msgid "Stage %d untracked files?" -msgstr "Да Ñе вкарат ли %d неÑледени файла в индекÑа?" +msgstr "Да Ñе добавÑÑ‚ ли %d неÑледени файла към индекÑа?" + +#: lib/index.tcl:388 +msgid "Adding all changed files" +msgstr "ДобавÑне на вÑички променени файлове" #: lib/index.tcl:428 #, tcl-format @@ -2499,75 +1896,13 @@ msgstr "Махане на промените в избраните файловРmsgid "Reverting %s" msgstr "Махане на промените в „%s“" -#: lib/date.tcl:25 -#, tcl-format -msgid "Invalid date from Git: %s" -msgstr "Ðеправилни данни от Git: %s" - -#: lib/database.tcl:42 -msgid "Number of loose objects" -msgstr "Брой непакетирани обекти" - -#: lib/database.tcl:43 -msgid "Disk space used by loose objects" -msgstr "ДиÑково проÑтранÑтво заето от непакетирани обекти" - -#: lib/database.tcl:44 -msgid "Number of packed objects" -msgstr "Брой пакетирани обекти" - -#: lib/database.tcl:45 -msgid "Number of packs" -msgstr "Брой пакети" - -#: lib/database.tcl:46 -msgid "Disk space used by packed objects" -msgstr "ДиÑково проÑтранÑтво заето от пакетирани обекти" - -#: lib/database.tcl:47 -msgid "Packed objects waiting for pruning" -msgstr "Пакетирани обекти за окаÑтрÑне" - -#: lib/database.tcl:48 -msgid "Garbage files" -msgstr "Файлове за боклука" - -#: lib/database.tcl:72 -msgid "Compressing the object database" -msgstr "КомпреÑиране на базата Ñ Ð´Ð°Ð½Ð½Ð¸ за обектите" - -#: lib/database.tcl:83 -msgid "Verifying the object database with fsck-objects" -msgstr "Проверка на базата Ñ Ð´Ð°Ð½Ð½Ð¸ за обектите Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð°Ñ‚Ð° „fsck-objects“" - -#: lib/database.tcl:107 -#, tcl-format -msgid "" -"This repository currently has approximately %i loose objects.\n" -"\n" -"To maintain optimal performance it is strongly recommended that you compress " -"the database.\n" -"\n" -"Compress the database now?" -msgstr "" -"Ð’ това хранилище в момента има към %i непакетирани обекти.\n" -"\n" -"За добра производителноÑÑ‚ Ñе препоръчва да компреÑирате базата Ñ Ð´Ð°Ð½Ð½Ð¸ за " -"обектите.\n" -"\n" -"Да Ñе започне ли компреÑирането?" - -#: lib/error.tcl:20 lib/error.tcl:116 -msgid "error" -msgstr "грешка" - -#: lib/error.tcl:36 -msgid "warning" -msgstr "предупреждение" +#: lib/line.tcl:17 +msgid "Goto Line:" +msgstr "Към ред:" -#: lib/error.tcl:96 -msgid "You must correct the above errors before committing." -msgstr "Преди да можете да подадете, коригирайте горните грешки." +#: lib/line.tcl:23 +msgid "Go" +msgstr "Придвижване" #: lib/merge.tcl:13 msgid "" @@ -2637,29 +1972,34 @@ msgstr "" msgid "%s of %s" msgstr "%s от общо %s" -#: lib/merge.tcl:122 +#: lib/merge.tcl:126 #, tcl-format msgid "Merging %s and %s..." msgstr "Сливане на „%s“ и „%s“…" -#: lib/merge.tcl:133 +#: lib/merge.tcl:137 msgid "Merge completed successfully." msgstr "Сливането завърши уÑпешно." -#: lib/merge.tcl:135 +#: lib/merge.tcl:139 msgid "Merge failed. Conflict resolution is required." msgstr "ÐеуÑпешно Ñливане — има конфликти за коригиране." -#: lib/merge.tcl:160 +#: lib/merge.tcl:156 +#, tcl-format +msgid "%s (%s): Merge" +msgstr "%s (%s): Сливане" + +#: lib/merge.tcl:164 #, tcl-format msgid "Merge Into %s" msgstr "Сливане в „%s“" -#: lib/merge.tcl:179 +#: lib/merge.tcl:183 msgid "Revision To Merge" msgstr "ВерÑÐ¸Ñ Ð·Ð° Ñливане" -#: lib/merge.tcl:214 +#: lib/merge.tcl:218 msgid "" "Cannot abort while amending.\n" "\n" @@ -2669,7 +2009,7 @@ msgstr "" "\n" "ТрÑбва да завършите поправката на това подаване.\n" -#: lib/merge.tcl:224 +#: lib/merge.tcl:228 msgid "" "Abort merge?\n" "\n" @@ -2683,7 +2023,7 @@ msgstr "" "\n" "ÐаиÑтина ли да Ñе преуÑтанови Ñливането?" -#: lib/merge.tcl:230 +#: lib/merge.tcl:234 msgid "" "Reset changes?\n" "\n" @@ -2697,18 +2037,771 @@ msgstr "" "\n" "ÐаиÑтина ли да Ñе занулÑÑ‚ промените?" -#: lib/merge.tcl:241 +#: lib/merge.tcl:245 msgid "Aborting" msgstr "ПреуÑтановÑване" -#: lib/merge.tcl:241 +#: lib/merge.tcl:245 msgid "files reset" msgstr "файла ÑÑŠÑ Ð·Ð°Ð½ÑƒÐ»ÐµÐ½Ð¸ промени" -#: lib/merge.tcl:269 +#: lib/merge.tcl:273 msgid "Abort failed." msgstr "ÐеуÑпешно преуÑтановÑване." -#: lib/merge.tcl:271 +#: lib/merge.tcl:275 msgid "Abort completed. Ready." msgstr "УÑпешно преуÑтановÑване. ГотовноÑÑ‚ за Ñледващо дейÑтвие." + +#: 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 "" +"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." +msgstr "" +"Разликата показва Ñамо разликите Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚.\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/mergetool.tcl:146 +msgid "Conflict file does not exist" +msgstr "Файлът, в който е конфликтът, не ÑъщеÑтвува" + +#: lib/mergetool.tcl:246 +#, tcl-format +msgid "Not a GUI merge tool: '%s'" +msgstr "Това не е графична програма за Ñливане: „%s“" + +#: 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 "" +"Error retrieving versions:\n" +"%s" +msgstr "" +"Грешка при изтеглÑнето на верÑии:\n" +"%s" + +#: lib/mergetool.tcl:350 +#, tcl-format +msgid "" +"Could not start the merge tool:\n" +"\n" +"%s" +msgstr "" +"Програмата за Ñливане не може да бъде Ñтартирана:\n" +"\n" +"%s" + +#: lib/mergetool.tcl:354 +msgid "Running merge tool..." +msgstr "Стартиране на програмата за Ñливане…" + +#: lib/mergetool.tcl:382 lib/mergetool.tcl:390 +msgid "Merge tool failed." +msgstr "Грешка в програмата за Ñливане." + +#: lib/option.tcl:11 +#, tcl-format +msgid "Invalid global encoding '%s'" +msgstr "Ðеправилно глобално кодиране „%s“" + +#: lib/option.tcl:19 +#, tcl-format +msgid "Invalid repo encoding '%s'" +msgstr "Ðеправилно кодиране „%s“ на хранилището" + +#: lib/option.tcl:119 +msgid "Restore Defaults" +msgstr "Стандартни наÑтройки" + +#: lib/option.tcl:123 +msgid "Save" +msgstr "Запазване" + +#: lib/option.tcl:133 +#, tcl-format +msgid "%s Repository" +msgstr "Хранилище „%s“" + +#: lib/option.tcl:134 +msgid "Global (All Repositories)" +msgstr "Глобално (за вÑички хранилища)" + +#: lib/option.tcl:140 +msgid "User Name" +msgstr "ПотребителÑко име" + +#: lib/option.tcl:141 +msgid "Email Address" +msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ð° е-поща" + +#: 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 "Извеждане на ÑтатиÑтика Ñлед ÑливаниÑта" + +#: 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/option.tcl:150 +msgid "Match Tracking Branches" +msgstr "ÐапаÑване на ÑледÑщите клонове" + +#: lib/option.tcl:151 +msgid "Use Textconv For Diffs and Blames" +msgstr "Използване на „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 "Брой редове за контекÑта на разликите" + +#: lib/option.tcl:157 +msgid "Additional Diff Parameters" +msgstr "Ðргументи към командата за разликите" + +#: 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 "Предупреждаване при подаване към неÑвързан указател" + +#: 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 "Choose %s" +msgstr "Избор на „%s“" + +#: lib/option.tcl:294 +msgid "pt." +msgstr "тчк." + +#: lib/option.tcl:308 +msgid "Preferences" +msgstr "ÐаÑтройки" + +#: lib/option.tcl:345 +msgid "Failed to completely save options:" +msgstr "ÐеуÑпешно запазване на наÑтройките:" + +#: 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/remote.tcl:253 lib/remote.tcl:258 +msgid "All" +msgstr "Ð’Ñички" + +#: lib/remote_add.tcl:20 +#, tcl-format +msgid "%s (%s): Add Remote" +msgstr "%s (%s): ДобавÑне на отдалечено хранилище" + +#: lib/remote_add.tcl:25 +msgid "Add New Remote" +msgstr "ДобавÑне на отдалечено хранилище" + +#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37 +msgid "Add" +msgstr "ДобавÑне" + +#: lib/remote_add.tcl:39 +msgid "Remote Details" +msgstr "Данни за отдалеченото хранилище" + +#: lib/remote_add.tcl:50 +msgid "Location:" +msgstr "МеÑтоположение:" + +#: lib/remote_add.tcl:60 +msgid "Further Action" +msgstr "Следващо дейÑтвие" + +#: lib/remote_add.tcl:63 +msgid "Fetch Immediately" +msgstr "Ðезабавно доÑтавÑне" + +#: lib/remote_add.tcl:69 +msgid "Initialize Remote Repository and Push" +msgstr "Инициализиране на отдалеченото хранилище и изтлаÑкване на промените" + +#: lib/remote_add.tcl:75 +msgid "Do Nothing Else Now" +msgstr "Да не Ñе прави нищо" + +#: lib/remote_add.tcl:100 +msgid "Please supply a remote name." +msgstr "Задайте име за отдалеченото хранилище." + +#: lib/remote_add.tcl:113 +#, tcl-format +msgid "'%s' is not an acceptable remote name." +msgstr "Отдалечено хранилище не може да Ñе казва „%s“." + +#: lib/remote_add.tcl:124 +#, tcl-format +msgid "Failed to add remote '%s' of location '%s'." +msgstr "ÐеуÑпешно добавÑне на отдалеченото хранилище „%s“ от Ð°Ð´Ñ€ÐµÑ â€ž%s“." + +#: lib/remote_add.tcl:132 lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "доÑтавÑне на „%s“" + +#: lib/remote_add.tcl:133 +#, tcl-format +msgid "Fetching the %s" +msgstr "ДоÑтавÑне на „%s“" + +#: lib/remote_add.tcl:156 +#, tcl-format +msgid "Do not know how to initialize repository at location '%s'." +msgstr "Хранилището Ñ Ð¼ÐµÑтоположение „%s“ не може да бъде инициализирано." + +#: lib/remote_add.tcl:162 lib/transport.tcl:54 lib/transport.tcl:92 +#: lib/transport.tcl:110 +#, tcl-format +msgid "push %s" +msgstr "изтлаÑкване на „%s“" + +#: lib/remote_add.tcl:163 +#, tcl-format +msgid "Setting up the %s (at %s)" +msgstr "ДобавÑне на хранилище „%s“ (Ñ Ð°Ð´Ñ€ÐµÑ â€ž%s“)" + +#: lib/remote_branch_delete.tcl:29 +#, tcl-format +msgid "%s (%s): Delete Branch Remotely" +msgstr "%s (%s): Изтриване на Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ ÐºÐ»Ð¾Ð½" + +#: lib/remote_branch_delete.tcl:34 +msgid "Delete Branch Remotely" +msgstr "Изтриване на Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ ÐºÐ»Ð¾Ð½" + +#: lib/remote_branch_delete.tcl:48 +msgid "From Repository" +msgstr "От хранилище" + +#: lib/remote_branch_delete.tcl:51 lib/transport.tcl:165 +msgid "Remote:" +msgstr "Отдалечено хранилище:" + +#: lib/remote_branch_delete.tcl:72 lib/transport.tcl:187 +msgid "Arbitrary Location:" +msgstr "Произволно меÑтоположение:" + +#: lib/remote_branch_delete.tcl:88 +msgid "Branches" +msgstr "Клони" + +#: lib/remote_branch_delete.tcl:110 +msgid "Delete Only If" +msgstr "Изтриване, Ñамо ако" + +#: lib/remote_branch_delete.tcl:112 +msgid "Merged Into:" +msgstr "СлÑÑ‚ в:" + +#: lib/remote_branch_delete.tcl:153 +msgid "A branch is required for 'Merged Into'." +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/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/remote_branch_delete.tcl:208 +msgid "Please select one or more branches to delete." +msgstr "Изберете поне един клон за изтриване." + +#: lib/remote_branch_delete.tcl:227 +#, tcl-format +msgid "Deleting branches from %s" +msgstr "Изтриване на клони от „%s“" + +#: lib/remote_branch_delete.tcl:300 +msgid "No repository selected." +msgstr "Ðе е избрано хранилище." + +#: lib/remote_branch_delete.tcl:305 +#, tcl-format +msgid "Scanning %s..." +msgstr "ПретърÑване на „%s“…" + +#: lib/search.tcl:48 +msgid "Find:" +msgstr "ТърÑене:" + +#: lib/search.tcl:50 +msgid "Next" +msgstr "Следваща поÑва" + +#: lib/search.tcl:51 +msgid "Prev" +msgstr "Предишна поÑва" + +#: lib/search.tcl:52 +msgid "RegExp" +msgstr "РегИзр" + +#: lib/search.tcl:54 +msgid "Case" +msgstr "Главни/малки" + +#: lib/shortcut.tcl:8 lib/shortcut.tcl:43 lib/shortcut.tcl:75 +#, tcl-format +msgid "%s (%s): Create Desktop Icon" +msgstr "%s (%s): ДобавÑне на икона на Ñ€Ð°Ð±Ð¾Ñ‚Ð½Ð¸Ñ Ð¿Ð»Ð¾Ñ‚" + +#: lib/shortcut.tcl:24 lib/shortcut.tcl:65 +msgid "Cannot write shortcut:" +msgstr "Клавишната ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ†Ð¸Ñ Ð½Ðµ може да бъде запазена:" + +#: lib/shortcut.tcl:140 +msgid "Cannot write icon:" +msgstr "Иконата не може да бъде запазена:" + +#: lib/spellcheck.tcl:57 +msgid "Unsupported spell checker" +msgstr "Тази програма за проверка на правопиÑа не Ñе поддържа" + +#: lib/spellcheck.tcl:65 +msgid "Spell checking is unavailable" +msgstr "ЛипÑва програма за проверка на правопиÑа" + +#: lib/spellcheck.tcl:68 +msgid "Invalid spell checking configuration" +msgstr "Ðеправилни наÑтройки на проверката на правопиÑа" + +#: lib/spellcheck.tcl:70 +#, tcl-format +msgid "Reverting dictionary to %s." +msgstr "Ползване на речник за език „%s“." + +#: lib/spellcheck.tcl:73 +msgid "Spell checker silently failed on startup" +msgstr "Програмата за Ð¿Ñ€Ð°Ð²Ð¾Ð¿Ð¸Ñ Ð´Ð°Ð¶Ðµ не Ñтартира уÑпешно." + +#: lib/spellcheck.tcl:80 +msgid "Unrecognized spell checker" +msgstr "Ðепозната програма за проверка на правопиÑа" + +#: lib/spellcheck.tcl:186 +msgid "No Suggestions" +msgstr "ÐÑма предложениÑ" + +#: lib/spellcheck.tcl:388 +msgid "Unexpected EOF from spell checker" +msgstr "Ðеочакван край на файл от програмата за проверка на правопиÑа" + +#: 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: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 "" +"Could not start ssh-keygen:\n" +"\n" +"%s" +msgstr "" +"Програмата „ssh-keygen“ не може да бъде Ñтартирана:\n" +"\n" +"%s" + +#: 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/status_bar.tcl:87 +#, tcl-format +msgid "%s ... %*i of %*i %s (%3i%%)" +msgstr "%s… %*i от общо %*i %s (%3i%%)" + +#: lib/tools.tcl:76 +#, tcl-format +msgid "Running %s requires a selected file." +msgstr "За изпълнението на „%s“ трÑбва да изберете файл." + +#: lib/tools.tcl:92 +#, tcl-format +msgid "Are you sure you want to run %1$s on file \"%2$s\"?" +msgstr "Сигурни ли Ñте, че иÑкате да изпълните „%1$s“ върху файла „%2$s“?" + +#: lib/tools.tcl:96 +#, tcl-format +msgid "Are you sure you want to run %s?" +msgstr "Сигурни ли Ñте, че иÑкате да изпълните „%s“?" + +#: lib/tools.tcl:118 +#, tcl-format +msgid "Tool: %s" +msgstr "Команда: %s" + +#: lib/tools.tcl:119 +#, tcl-format +msgid "Running: %s" +msgstr "Изпълнение: %s" + +#: lib/tools.tcl:158 +#, tcl-format +msgid "Tool completed successfully: %s" +msgstr "Командата завърши уÑпешно: %s" + +#: lib/tools.tcl:160 +#, tcl-format +msgid "Tool failed: %s" +msgstr "Командата върна грешка: %s" + +#: lib/tools_dlg.tcl:22 +#, tcl-format +msgid "%s (%s): Add Tool" +msgstr "%s (%s): ДобавÑне на команда" + +#: lib/tools_dlg.tcl:28 +msgid "Add New Tool Command" +msgstr "ДобавÑне на команда" + +#: lib/tools_dlg.tcl:34 +msgid "Add globally" +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: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 "" +"Could not add tool:\n" +"%s" +msgstr "" +"Командата не може да бъде добавена:\n" +"%s" + +#: lib/tools_dlg.tcl:187 +#, tcl-format +msgid "%s (%s): Remove Tool" +msgstr "%s (%s): Премахване на команда" + +#: lib/tools_dlg.tcl:193 +msgid "Remove Tool Commands" +msgstr "Премахване на команди" + +#: lib/tools_dlg.tcl:198 +msgid "Remove" +msgstr "Премахване" + +#: lib/tools_dlg.tcl:231 +msgid "(Blue denotes repository-local tools)" +msgstr "(командите към локалното хранилище Ñа обозначени в Ñиньо)" + +#: lib/tools_dlg.tcl:283 +#, tcl-format +msgid "%s (%s):" +msgstr "%s (%s):" + +#: lib/tools_dlg.tcl:292 +#, tcl-format +msgid "Run Command: %s" +msgstr "Изпълнение на командата „%s“" + +#: lib/tools_dlg.tcl:306 +msgid "Arguments" +msgstr "Ðргументи" + +#: lib/tools_dlg.tcl:341 +msgid "OK" +msgstr "Добре" + +#: 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: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 "ИзтлаÑкване на %s „%s“ към „%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 "МакÑимална компреÑÐ¸Ñ (за бавни мрежови връзки)" + +#: lib/transport.tcl:215 +msgid "Include tags" +msgstr "Включване на етикетите" + +#: lib/transport.tcl:229 +#, tcl-format +msgid "%s (%s): Push" +msgstr "%s (%s): ИзтлаÑкване" diff --git a/git-gui/po/ru.po b/git-gui/po/ru.po index ca4343b40c..9f5305c43e 100644 --- a/git-gui/po/ru.po +++ b/git-gui/po/ru.po @@ -1,19 +1,22 @@ # Translation of git-gui to russian # Copyright (C) 2007 Shawn Pearce # This file is distributed under the same license as the git-gui package. -# Irina Riesen <irina.riesen@gmail.com>, 2007. -# +# Translators: +# Dimitriy Ryazantcev <DJm00n@mail.ru>, 2015-2016 +# Irina Riesen <irina.riesen@gmail.com>, 2007 msgid "" msgstr "" -"Project-Id-Version: git-gui\n" +"Project-Id-Version: Git Russian Localization Project\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-01-26 15:47-0800\n" -"PO-Revision-Date: 2007-10-22 22:30-0200\n" -"Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n" -"Language-Team: Russian Translation <git@vger.kernel.org>\n" +"PO-Revision-Date: 2016-06-30 12:39+0000\n" +"Last-Translator: Dimitriy Ryazantcev <DJm00n@mail.ru>\n" +"Language-Team: Russian (http://www.transifex.com/djm00n/git-po-ru/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\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 @@ -51,14 +54,7 @@ msgid "" "%s requires at least Git 1.5.0 or later.\n" "\n" "Assume '%s' is version 1.5.0?\n" -msgstr "" -"Ðевозможно определить верÑию Git\n" -"\n" -"%s указывает на верÑию '%s'.\n" -"\n" -"Ð´Ð»Ñ %s требуетÑÑ Ð²ÐµÑ€ÑÐ¸Ñ Git, Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1.5.0\n" -"\n" -"ПринÑть '%s' как верÑию 1.5.0?\n" +msgstr "Ðевозможно определить верÑию Git\n\n%s указывает на верÑию «%s».\n\nÐ´Ð»Ñ %s требуетÑÑ Ð²ÐµÑ€ÑÐ¸Ñ Git, Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1.5.0\n\nПредположить, что «%s» и еÑть верÑÐ¸Ñ 1.5.0?\n" #: git-gui.sh:1128 msgid "Git directory not found:" @@ -78,20 +74,19 @@ msgstr "ОтÑутÑтвует рабочий каталог" #: git-gui.sh:1334 lib/checkout_op.tcl:306 msgid "Refreshing file status..." -msgstr "Обновление информации о ÑоÑтоÑнии файлов..." +msgstr "Обновление информации о ÑоÑтоÑнии файлов…" #: git-gui.sh:1390 msgid "Scanning for modified files ..." -msgstr "ПоиÑк измененных файлов..." +msgstr "ПоиÑк измененных файлов…" #: git-gui.sh:1454 msgid "Calling prepare-commit-msg hook..." -msgstr "Вызов программы поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ prepare-commit-msg..." +msgstr "Вызов перехватчика prepare-commit-msg…" #: git-gui.sh:1471 msgid "Commit declined by prepare-commit-msg hook." -msgstr "" -"Сохранение прервано программой поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ prepare-commit-msg" +msgstr "Коммит прерван перехватчиком prepare-commit-msg." #: git-gui.sh:1629 lib/browser.tcl:246 msgid "Ready." @@ -108,31 +103,31 @@ msgstr "Ðе изменено" #: git-gui.sh:1915 msgid "Modified, not staged" -msgstr "Изменено, не подготовлено" +msgstr "Изменено, не в индекÑе" #: git-gui.sh:1916 git-gui.sh:1924 msgid "Staged for commit" -msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ" +msgstr "Ð’ индекÑе Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°" #: git-gui.sh:1917 git-gui.sh:1925 msgid "Portions staged for commit" -msgstr "ЧаÑти, подготовленные Ð´Ð»Ñ ÑохранениÑ" +msgstr "ЧаÑти, в индекÑе Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°" #: git-gui.sh:1918 git-gui.sh:1926 msgid "Staged for commit, missing" -msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ, отÑутÑтвует" +msgstr "Ð’ индекÑе Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°, отÑутÑтвует" #: git-gui.sh:1920 msgid "File type changed, not staged" -msgstr "Тип файла изменён, не подготовлено" +msgstr "Тип файла изменён, не в индекÑе" #: git-gui.sh:1921 msgid "File type changed, staged" -msgstr "Тип файла изменён, подготовлено" +msgstr "Тип файла изменён, в индекÑе" #: git-gui.sh:1923 msgid "Untracked, not staged" -msgstr "Ðе отÑлеживаетÑÑ, не подготовлено" +msgstr "Ðе отÑлеживаетÑÑ, не в индекÑе" #: git-gui.sh:1928 msgid "Missing" @@ -140,11 +135,11 @@ msgstr "ОтÑутÑтвует" #: git-gui.sh:1929 msgid "Staged for removal" -msgstr "Подготовлено Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ" +msgstr "Ð’ индекÑе Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ" #: git-gui.sh:1930 msgid "Staged for removal, still present" -msgstr "Подготовлено Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ, еще не удалено" +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 @@ -153,7 +148,7 @@ msgstr "ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта при Ñли #: git-gui.sh:1972 msgid "Starting gitk... please wait..." -msgstr "ЗапуÑкаетÑÑ gitk... Подождите, пожалуйÑта..." +msgstr "ЗапуÑкаетÑÑ gitk… Подождите, пожалуйÑта…" #: git-gui.sh:1984 msgid "Couldn't find gitk in PATH" @@ -173,11 +168,11 @@ msgstr "Редактировать" #: git-gui.sh:2458 lib/choose_rev.tcl:561 msgid "Branch" -msgstr "Ветвь" +msgstr "Ветка" #: git-gui.sh:2461 lib/choose_rev.tcl:548 msgid "Commit@@noun" -msgstr "СоÑтоÑние" +msgstr "Коммит" #: git-gui.sh:2464 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" @@ -197,29 +192,29 @@ msgstr "ПроÑмотр рабочего каталога" #: git-gui.sh:2483 msgid "Browse Current Branch's Files" -msgstr "ПроÑмотреть файлы текущей ветви" +msgstr "ПроÑмотреть файлы текущей ветки" #: git-gui.sh:2487 msgid "Browse Branch Files..." -msgstr "Показать файлы ветви..." +msgstr "Показать файлы ветки…" #: git-gui.sh:2492 msgid "Visualize Current Branch's History" -msgstr "Показать иÑторию текущей ветви" +msgstr "Показать иÑторию текущей ветки" #: git-gui.sh:2496 msgid "Visualize All Branch History" -msgstr "Показать иÑторию вÑех ветвей" +msgstr "Показать иÑторию вÑех веток" #: git-gui.sh:2503 #, tcl-format msgid "Browse %s's Files" -msgstr "Показать файлы ветви %s" +msgstr "Показать файлы ветки %s" #: git-gui.sh:2505 #, tcl-format msgid "Visualize %s's History" -msgstr "Показать иÑторию ветви %s" +msgstr "Показать иÑторию ветки %s" #: git-gui.sh:2510 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" @@ -274,23 +269,23 @@ msgstr "Выделить вÑе" #: git-gui.sh:2576 msgid "Create..." -msgstr "Создать..." +msgstr "Создать…" #: git-gui.sh:2582 msgid "Checkout..." -msgstr "Перейти..." +msgstr "Перейти…" #: git-gui.sh:2588 msgid "Rename..." -msgstr "Переименовать..." +msgstr "Переименовать…" #: git-gui.sh:2593 msgid "Delete..." -msgstr "Удалить..." +msgstr "Удалить…" #: git-gui.sh:2598 msgid "Reset..." -msgstr "СброÑить..." +msgstr "СброÑить…" #: git-gui.sh:2608 msgid "Done" @@ -298,15 +293,15 @@ msgstr "Завершено" #: git-gui.sh:2610 msgid "Commit@@verb" -msgstr "Сохранить" +msgstr "Закоммитить" #: git-gui.sh:2619 git-gui.sh:3050 msgid "New Commit" -msgstr "Ðовое ÑоÑтоÑние" +msgstr "Ðовый коммит" #: git-gui.sh:2627 git-gui.sh:3057 msgid "Amend Last Commit" -msgstr "ИÑправить поÑледнее ÑоÑтоÑние" +msgstr "ИÑправить поÑледний коммит" #: git-gui.sh:2637 git-gui.sh:3011 lib/remote_branch_delete.tcl:99 msgid "Rescan" @@ -314,19 +309,19 @@ msgstr "Перечитать" #: git-gui.sh:2643 msgid "Stage To Commit" -msgstr "Подготовить Ð´Ð»Ñ ÑохранениÑ" +msgstr "Добавить в индекÑ" #: git-gui.sh:2649 msgid "Stage Changed Files To Commit" -msgstr "Подготовить измененные файлы Ð´Ð»Ñ ÑохранениÑ" +msgstr "Добавить изменённые файлы в индекÑ" #: git-gui.sh:2655 msgid "Unstage From Commit" -msgstr "Убрать из подготовленного" +msgstr "Убрать из издекÑа" #: git-gui.sh:2661 lib/index.tcl:412 msgid "Revert Changes" -msgstr "Отменить изменениÑ" +msgstr "Обратить изменениÑ" #: git-gui.sh:2669 git-gui.sh:3310 git-gui.sh:3341 msgid "Show Less Context" @@ -342,31 +337,31 @@ msgstr "Ð’Ñтавить Signed-off-by" #: git-gui.sh:2696 msgid "Local Merge..." -msgstr "Локальное ÑлиÑние..." +msgstr "Локальное ÑлиÑние…" #: git-gui.sh:2701 msgid "Abort Merge..." -msgstr "Прервать ÑлиÑние..." +msgstr "Прервать ÑлиÑние…" #: git-gui.sh:2713 git-gui.sh:2741 msgid "Add..." -msgstr "Добавить..." +msgstr "Добавить…" #: git-gui.sh:2717 msgid "Push..." -msgstr "Отправить..." +msgstr "Отправить…" #: git-gui.sh:2721 msgid "Delete Branch..." -msgstr "Удалить ветвь..." +msgstr "Удалить ветку…" #: git-gui.sh:2731 git-gui.sh:3292 msgid "Options..." -msgstr "ÐаÑтройки..." +msgstr "ÐаÑтройки…" #: git-gui.sh:2742 msgid "Remove..." -msgstr "Удалить..." +msgstr "Удалить…" #: git-gui.sh:2751 lib/choose_repository.tcl:50 msgid "Help" @@ -393,11 +388,11 @@ msgstr "критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: %s: нет такого файла #: git-gui.sh:2926 msgid "Current Branch:" -msgstr "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ‚Ð²ÑŒ:" +msgstr "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ‚ÐºÐ°:" #: git-gui.sh:2947 msgid "Staged Changes (Will Commit)" -msgstr "Подготовлено (будет Ñохранено)" +msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² индекÑе (будут закоммичены)" #: git-gui.sh:2967 msgid "Unstaged Changes" @@ -405,7 +400,7 @@ msgstr "Изменено (не будет Ñохранено)" #: git-gui.sh:3017 msgid "Stage Changed" -msgstr "Подготовить вÑе" +msgstr "ИндекÑировать вÑÑ‘" #: git-gui.sh:3036 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" @@ -413,27 +408,27 @@ msgstr "Отправить" #: git-gui.sh:3071 msgid "Initial Commit Message:" -msgstr "Комментарий к первому ÑоÑтоÑнию:" +msgstr "Сообщение первого коммита:" #: git-gui.sh:3072 msgid "Amended Commit Message:" -msgstr "Комментарий к иÑправленному ÑоÑтоÑнию:" +msgstr "Сообщение иÑправленного коммита:" #: git-gui.sh:3073 msgid "Amended Initial Commit Message:" -msgstr "Комментарий к иÑправленному первоначальному ÑоÑтоÑнию:" +msgstr "Сообщение иÑправленного первого коммита:" #: git-gui.sh:3074 msgid "Amended Merge Commit Message:" -msgstr "Комментарий к иÑправленному ÑлиÑнию:" +msgstr "Сообщение иÑправленного ÑлиÑниÑ:" #: git-gui.sh:3075 msgid "Merge Commit Message:" -msgstr "Комментарий к ÑлиÑнию:" +msgstr "Сообщение ÑлиÑниÑ:" #: git-gui.sh:3076 msgid "Commit Message:" -msgstr "Комментарий к ÑоÑтоÑнию:" +msgstr "Сообщение коммита:" #: git-gui.sh:3125 git-gui.sh:3267 lib/console.tcl:73 msgid "Copy All" @@ -481,51 +476,51 @@ msgstr "ВзÑть локальную верÑию" #: git-gui.sh:3336 msgid "Revert To Base" -msgstr "Отменить изменениÑ" +msgstr "Обратить изменениÑ" #: git-gui.sh:3354 msgid "Visualize These Changes In The Submodule" -msgstr "" +msgstr "Показать Ñти Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ð¼Ð¾Ð´ÑƒÐ»Ñ" #: git-gui.sh:3358 msgid "Visualize Current Branch History In The Submodule" -msgstr "Показать иÑторию текущей ветви подмодулÑ" +msgstr "Показать иÑторию текущей ветки подмодулÑ" #: git-gui.sh:3362 msgid "Visualize All Branch History In The Submodule" -msgstr "Показать иÑторию вÑех ветвей подмодулÑ" +msgstr "Показать иÑторию вÑех веток подмодулÑ" #: git-gui.sh:3367 msgid "Start git gui In The Submodule" -msgstr "" +msgstr "ЗапуÑтить git gui в подмодуле" #: git-gui.sh:3389 msgid "Unstage Hunk From Commit" -msgstr "Ðе ÑохранÑть чаÑть" +msgstr "Убрать блок из индекÑа" #: git-gui.sh:3391 msgid "Unstage Lines From Commit" -msgstr "Убрать Ñтроки из подготовленного" +msgstr "Убрать Ñтроки из индекÑа" #: git-gui.sh:3393 msgid "Unstage Line From Commit" -msgstr "Убрать Ñтроку из подготовленного" +msgstr "Убрать Ñтроку из индекÑа" #: git-gui.sh:3396 msgid "Stage Hunk For Commit" -msgstr "Подготовить чаÑть Ð´Ð»Ñ ÑохранениÑ" +msgstr "Добавить блок в индекÑ" #: git-gui.sh:3398 msgid "Stage Lines For Commit" -msgstr "Подготовить Ñтроки Ð´Ð»Ñ ÑохранениÑ" +msgstr "Добавить Ñтроки в индекÑ" #: git-gui.sh:3400 msgid "Stage Line For Commit" -msgstr "Подготовить Ñтроку Ð´Ð»Ñ ÑохранениÑ" +msgstr "Добавить Ñтроку в индекÑ" #: git-gui.sh:3424 msgid "Initializing..." -msgstr "ИнициализациÑ..." +msgstr "ИнициализациÑ…" #: git-gui.sh:3541 #, tcl-format @@ -536,23 +531,14 @@ msgid "" "going to be ignored by any Git subprocess run\n" "by %s:\n" "\n" -msgstr "" -"Возможны ошибки в переменных окружениÑ.\n" -"\n" -"Переменные окружениÑ, которые возможно\n" -"будут проигнорированы командами Git,\n" -"запущенными из %s\n" -"\n" +msgstr "Возможны ошибки в переменных окружениÑ.\n\nПеременные окружениÑ, которые возможно\nбудут проигнорированы командами Git,\nзапущенными из %s\n\n" #: git-gui.sh:3570 msgid "" "\n" "This is due to a known issue with the\n" "Tcl binary distributed by Cygwin." -msgstr "" -"\n" -"Ðто извеÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° Ñ Tcl,\n" -"раÑпроÑтранÑемым Cygwin." +msgstr "\nÐто извеÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° Ñ Tcl,\nраÑпроÑтранÑемым Cygwin." #: git-gui.sh:3575 #, tcl-format @@ -563,13 +549,7 @@ msgid "" "is placing values for the user.name and\n" "user.email settings into your personal\n" "~/.gitconfig file.\n" -msgstr "" -"\n" -"\n" -"ВмеÑто иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ %s можно\n" -"Ñохранить Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ user.name и\n" -"user.email в Вашем перÑональном\n" -"файле ~/.gitconfig.\n" +msgstr "\n\nВмеÑто иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ %s можно\nÑохранить Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ user.name и\nuser.email в Вашем перÑональном\nфайле ~/.gitconfig.\n" #: lib/about.tcl:26 msgid "git-gui - a graphical user interface for Git." @@ -581,15 +561,15 @@ msgstr "ПроÑмотр файла" #: lib/blame.tcl:78 msgid "Commit:" -msgstr "Сохраненное ÑоÑтоÑние:" +msgstr "Коммит:" #: lib/blame.tcl:271 msgid "Copy Commit" -msgstr "Скопировать SHA-1" +msgstr "Копировать SHA-1" #: lib/blame.tcl:275 msgid "Find Text..." -msgstr "Ðайти текÑÑ‚..." +msgstr "Ðайти текÑт…" #: lib/blame.tcl:284 msgid "Do Full Copy Detection" @@ -601,16 +581,16 @@ msgstr "Показать иÑторичеÑкий контекÑÑ‚" #: lib/blame.tcl:291 msgid "Blame Parent Commit" -msgstr "РаÑÑмотреть ÑоÑтоÑние предка" +msgstr "Ðвторы родительÑкого коммита" #: lib/blame.tcl:450 #, tcl-format msgid "Reading %s..." -msgstr "Чтение %s..." +msgstr "Чтение %s…" #: lib/blame.tcl:557 msgid "Loading copy/move tracking annotations..." -msgstr "Загрузка аннотации копирований/переименований..." +msgstr "Загрузка аннотации копирований/переименований…" #: lib/blame.tcl:577 msgid "lines annotated" @@ -618,7 +598,7 @@ msgstr "Ñтрок прокомментировано" #: lib/blame.tcl:769 msgid "Loading original location annotations..." -msgstr "Загрузка аннотаций первоначального Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°..." +msgstr "Загрузка аннотаций первоначального Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°â€¦" #: lib/blame.tcl:772 msgid "Annotation complete." @@ -634,11 +614,11 @@ msgstr "ÐÐ½Ð½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ ÑƒÐ¶Ðµ запущена" #: lib/blame.tcl:842 msgid "Running thorough copy detection..." -msgstr "Выполнение полного поиÑка копий..." +msgstr "Выполнение полного поиÑка копий…" #: lib/blame.tcl:910 msgid "Loading annotation..." -msgstr "Загрузка аннотации..." +msgstr "Загрузка аннотации…" #: lib/blame.tcl:963 msgid "Author:" @@ -646,7 +626,7 @@ msgstr "Ðвтор:" #: lib/blame.tcl:967 msgid "Committer:" -msgstr "Сохранил:" +msgstr "Коммитер:" #: lib/blame.tcl:972 msgid "Original File:" @@ -654,11 +634,11 @@ msgstr "ИÑходный файл:" #: lib/blame.tcl:1020 msgid "Cannot find HEAD commit:" -msgstr "Ðевозможно найти текущее ÑоÑтоÑние:" +msgstr "Ðе удалоÑÑŒ найти текущее ÑоÑтоÑние:" #: lib/blame.tcl:1075 msgid "Cannot find parent commit:" -msgstr "Ðевозможно найти ÑоÑтоÑние предка:" +msgstr "Ðе удалоÑÑŒ найти родительÑкое ÑоÑтоÑние:" #: lib/blame.tcl:1090 msgid "Unable to display parent" @@ -682,7 +662,7 @@ msgstr "Скопировано/перемещено в:" #: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 msgid "Checkout Branch" -msgstr "Перейти на ветвь" +msgstr "Перейти на ветку" #: lib/branch_checkout.tcl:23 msgid "Checkout" @@ -707,19 +687,19 @@ msgstr "ÐаÑтройки" #: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 msgid "Fetch Tracking Branch" -msgstr "Получить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð· внешней ветви" +msgstr "Извлечь Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð· внешней ветки" #: lib/branch_checkout.tcl:44 msgid "Detach From Local Branch" -msgstr "ОтÑоединить от локальной ветви" +msgstr "ОтÑоединить от локальной ветки" #: lib/branch_create.tcl:22 msgid "Create Branch" -msgstr "Создание ветви" +msgstr "Создать ветку" #: lib/branch_create.tcl:27 msgid "Create New Branch" -msgstr "Создать новую ветвь" +msgstr "Создать новую ветку" #: lib/branch_create.tcl:31 lib/choose_repository.tcl:381 msgid "Create" @@ -727,7 +707,7 @@ msgstr "Создать" #: lib/branch_create.tcl:40 msgid "Branch Name" -msgstr "Ðазвание ветви" +msgstr "Ð˜Ð¼Ñ Ð²ÐµÑ‚ÐºÐ¸" #: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50 msgid "Name:" @@ -735,7 +715,7 @@ msgstr "Ðазвание:" #: lib/branch_create.tcl:58 msgid "Match Tracking Branch Name" -msgstr "ВзÑть из имен ветвей ÑлежениÑ" +msgstr "СоответÑтвовать имени отÑлеживаемой ветки" #: lib/branch_create.tcl:66 msgid "Starting Revision" @@ -743,7 +723,7 @@ msgstr "ÐÐ°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑиÑ" #: lib/branch_create.tcl:72 msgid "Update Existing Branch:" -msgstr "Обновить имеющуюÑÑ Ð²ÐµÑ‚Ð²ÑŒ:" +msgstr "Обновить имеющуюÑÑ Ð²ÐµÑ‚ÐºÑƒ:" #: lib/branch_create.tcl:75 msgid "No" @@ -763,33 +743,33 @@ msgstr "ПоÑле ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñделать текущей" #: lib/branch_create.tcl:131 msgid "Please select a tracking branch." -msgstr "Укажите ветвь ÑлежениÑ." +msgstr "Укажите отлеживаемую ветку." #: lib/branch_create.tcl:140 #, tcl-format msgid "Tracking branch %s is not a branch in the remote repository." -msgstr "Ветвь ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ %s не ÑвлÑетÑÑ Ð²ÐµÑ‚Ð²ÑŒÑŽ во внешнем репозитории." +msgstr "ОтÑÐ»ÐµÐ¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð²ÐµÑ‚ÐºÐ° %s не ÑвлÑетÑÑ Ð²ÐµÑ‚ÐºÐ¾Ð¹ на внешнем репозитории." #: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 msgid "Please supply a branch name." -msgstr "Укажите название ветви." +msgstr "Укажите Ð¸Ð¼Ñ Ð²ÐµÑ‚ÐºÐ¸." #: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 #, tcl-format msgid "'%s' is not an acceptable branch name." -msgstr "ÐедопуÑтимое название ветви '%s'." +msgstr "ÐедопуÑтимое Ð¸Ð¼Ñ Ð²ÐµÑ‚ÐºÐ¸ «%s»." #: lib/branch_delete.tcl:15 msgid "Delete Branch" -msgstr "Удаление ветви" +msgstr "Удаление ветки" #: lib/branch_delete.tcl:20 msgid "Delete Local Branch" -msgstr "Удалить локальную ветвь" +msgstr "Удалить локальную ветку" #: lib/branch_delete.tcl:37 msgid "Local Branches" -msgstr "Локальные ветви" +msgstr "Локальные ветки" #: lib/branch_delete.tcl:52 msgid "Delete Only If Merged Into" @@ -802,30 +782,25 @@ msgstr "Ð’Ñегда (не выполнÑть проверку на ÑлиÑни #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" -msgstr "Ветви, которые не полноÑтью ÑливаютÑÑ Ñ %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" -"Продолжить?" +msgstr "ВоÑÑтановить удаленные ветки Ñложно.\n\nПродолжить?" #: lib/branch_delete.tcl:141 #, tcl-format msgid "" "Failed to delete branches:\n" "%s" -msgstr "" -"Ðе удалоÑÑŒ удалить ветви:\n" -"%s" +msgstr "Ðе удалоÑÑŒ удалить ветки:\n%s" #: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 msgid "Rename Branch" -msgstr "Переименование ветви" +msgstr "Переименование ветки" #: lib/branch_rename.tcl:26 msgid "Rename" @@ -833,7 +808,7 @@ msgstr "Переименовать" #: lib/branch_rename.tcl:36 msgid "Branch:" -msgstr "Ветвь:" +msgstr "Ветка:" #: lib/branch_rename.tcl:39 msgid "New Name:" @@ -841,21 +816,21 @@ msgstr "Ðовое название:" #: lib/branch_rename.tcl:75 msgid "Please select a branch to rename." -msgstr "Укажите ветвь Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ." +msgstr "Укажите ветку Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ." #: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202 #, tcl-format msgid "Branch '%s' already exists." -msgstr "Ветвь '%s' уже ÑущеÑтвует." +msgstr "Ветка «%s» уже ÑущеÑтвует." #: lib/branch_rename.tcl:117 #, tcl-format msgid "Failed to rename '%s'." -msgstr "Ðе удалоÑÑŒ переименовать '%s'. " +msgstr "Ðе удалоÑÑŒ переименовать «%s». " #: lib/browser.tcl:17 msgid "Starting..." -msgstr "ЗапуÑк..." +msgstr "ЗапуÑк…" #: lib/browser.tcl:26 msgid "File Browser" @@ -864,7 +839,7 @@ msgstr "ПроÑмотр ÑпиÑка файлов" #: lib/browser.tcl:126 lib/browser.tcl:143 #, tcl-format msgid "Loading %s..." -msgstr "Загрузка %s..." +msgstr "Загрузка %s…" #: lib/browser.tcl:187 msgid "[Up To Parent]" @@ -872,7 +847,7 @@ msgstr "[Ðа уровень выше]" #: lib/browser.tcl:267 lib/browser.tcl:273 msgid "Browse Branch Files" -msgstr "Показать файлы ветви" +msgstr "Показать файлы ветки" #: lib/browser.tcl:278 lib/choose_repository.tcl:398 #: lib/choose_repository.tcl:486 lib/choose_repository.tcl:497 @@ -883,7 +858,7 @@ msgstr "Показать" #: lib/checkout_op.tcl:85 #, tcl-format msgid "Fetching %s from %s" -msgstr "Получение %s из %s " +msgstr "Извлечение %s из %s " #: lib/checkout_op.tcl:133 #, tcl-format @@ -898,12 +873,12 @@ msgstr "Закрыть" #: lib/checkout_op.tcl:175 #, tcl-format msgid "Branch '%s' does not exist." -msgstr "Ветвь '%s' не ÑущеÑтвует " +msgstr "Ветка «%s» не ÑущеÑтвует." #: lib/checkout_op.tcl:194 #, tcl-format msgid "Failed to configure simplified git-pull for '%s'." -msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÑƒÐ¿Ñ€Ð¾Ñ‰Ñ‘Ð½Ð½Ð¾Ð¹ конфигурации git pull Ð´Ð»Ñ '%s'." +msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÑƒÐ¿Ñ€Ð¾Ñ‰Ñ‘Ð½Ð½Ð¾Ð¹ конфигурации git pull Ð´Ð»Ñ Â«%s»." #: lib/checkout_op.tcl:229 #, tcl-format @@ -912,21 +887,17 @@ msgid "" "\n" "It cannot fast-forward to %s.\n" "A merge is required." -msgstr "" -"Ветвь '%s' уже ÑущеÑтвует.\n" -"\n" -"Она не может быть прокручена(fast-forward) к %s.\n" -"ТребуетÑÑ ÑлиÑние." +msgstr "Ветка «%s» уже ÑущеÑтвует.\n\nОна не может быть перемотана вперед к %s.\nТребуетÑÑ ÑлиÑние." #: lib/checkout_op.tcl:243 #, tcl-format msgid "Merge strategy '%s' not supported." -msgstr "ÐеизвеÑÑ‚Ð½Ð°Ñ ÑÑ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ ÑлиÑниÑ: '%s'." +msgstr "ÐеизвеÑÑ‚Ð½Ð°Ñ ÑÑ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ ÑлиÑÐ½Ð¸Ñ Â«%s»." #: lib/checkout_op.tcl:262 #, tcl-format msgid "Failed to update '%s'." -msgstr "Ðе удалоÑÑŒ обновить '%s'." +msgstr "Ðе удалоÑÑŒ обновить «%s»." #: lib/checkout_op.tcl:274 msgid "Staging area (index) is already locked." @@ -936,22 +907,15 @@ msgstr "Ð Ð°Ð±Ð¾Ñ‡Ð°Ñ Ð¾Ð±Ð»Ð°Ñть заблокирована другим п 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 the current branch can be changed.\n" +"Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed.\n" "\n" "The rescan will be automatically started now.\n" -msgstr "" -"ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n" -"\n" -"С момента поÑледней проверки репозиторий был изменен другой программой Git. " -"Ðеобходимо перечитать репозиторий, прежде чем изменÑть текущую ветвь.\n" -"\n" -"Ðто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" +msgstr "ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n\nС момента поÑледней проверки репозиторий был изменен другой программой Git. Ðеобходимо перечитать репозиторий, прежде чем Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ‚ÐºÐ° может быть изменена.\n\nÐто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" #: lib/checkout_op.tcl:345 #, tcl-format msgid "Updating working directory to '%s'..." -msgstr "Обновление рабочего каталога из '%s'..." +msgstr "Обновление рабочего каталога из «%s»…" #: lib/checkout_op.tcl:346 msgid "files checked out" @@ -960,7 +924,7 @@ msgstr "файлы извлечены" #: lib/checkout_op.tcl:376 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." -msgstr "Прерван переход на '%s' (требуетÑÑ ÑлиÑние ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²)" +msgstr "Прерван переход на «%s» (требуетÑÑ ÑлиÑние Ñодержимого файлов)" #: lib/checkout_op.tcl:377 msgid "File level merge required." @@ -969,38 +933,33 @@ msgstr "ТребуетÑÑ ÑлиÑние ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²." #: lib/checkout_op.tcl:381 #, tcl-format msgid "Staying on branch '%s'." -msgstr "Ветвь '%s' оÑтаетÑÑ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹." +msgstr "Ветка «%s» оÑтаётÑÑ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹." #: lib/checkout_op.tcl:452 msgid "" "You are no longer on a local branch.\n" "\n" -"If you wanted to be on a branch, create one now starting from 'This Detached " -"Checkout'." -msgstr "" -"Ð’Ñ‹ находитеÑÑŒ не в локальной ветви.\n" -"\n" -"ЕÑли вы хотите Ñнова вернутьÑÑ Ðº какой-нибудь ветви, Ñоздайте ее ÑейчаÑ, " -"Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 'Текущего отÑоединенного ÑоÑтоÑниÑ'." +"If you wanted to be on a branch, create one now starting from 'This Detached Checkout'." +msgstr "Ð’Ñ‹ более не находитеÑÑŒ на локальной ветке.\n\nЕÑли вы хотите Ñнова вернутьÑÑ Ðº какой-нибудь ветке, Ñоздайте её ÑейчаÑ, Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ Â«Ð¢ÐµÐºÑƒÑ‰ÐµÐ³Ð¾ отÑоединенного ÑоÑтоÑниÑ»." #: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507 #, tcl-format msgid "Checked out '%s'." -msgstr "Ветвь '%s' Ñделана текущей." +msgstr "Выполнен переход на «%s»." #: lib/checkout_op.tcl:535 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" -msgstr "Ð¡Ð±Ñ€Ð¾Ñ '%s' в '%s' приведет к потере Ñледующих Ñохраненных ÑоÑтоÑний: " +msgstr "Ð¡Ð±Ñ€Ð¾Ñ Â«%s» на «%s» приведет к потере Ñледующих коммитов:" #: lib/checkout_op.tcl:557 msgid "Recovering lost commits may not be easy." -msgstr "ВоÑÑтановить потерÑнные Ñохраненные ÑоÑтоÑÐ½Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ Ñложно." +msgstr "ВоÑÑтановить потерÑнные коммиты будет Ñложно." #: lib/checkout_op.tcl:562 #, tcl-format msgid "Reset '%s'?" -msgstr "СброÑить '%s'?" +msgstr "СброÑить «%s»?" #: lib/checkout_op.tcl:567 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" @@ -1011,17 +970,10 @@ msgstr "ÐаглÑдно" msgid "" "Failed to set current branch.\n" "\n" -"This working directory is only partially switched. We successfully updated " -"your files, but failed to update an internal Git file.\n" +"This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file.\n" "\n" "This should not have occurred. %s will now close and give up." -msgstr "" -"Ðе удалоÑÑŒ уÑтановить текущую ветвь.\n" -"\n" -"Ваш рабочий каталог обновлен только чаÑтично. Были обновлены вÑе файлы кроме " -"Ñлужебных файлов Git. \n" -"\n" -"Ðтого не должно было произойти. %s завершаетÑÑ." +msgstr "Ðе удалоÑÑŒ уÑтановить текущую ветку.\n\nВаш рабочий каталог обновлён только чаÑтично. Были обновлены вÑе файлы кроме Ñлужебных файлов Git. \n\nÐтого не должно было произойти. %s завершаетÑÑ." #: lib/choose_font.tcl:39 msgid "Select" @@ -1043,9 +995,7 @@ msgstr "Пример текÑта" msgid "" "This is example text.\n" "If you like this text, it can be your font." -msgstr "" -"Ðто пример текÑта.\n" -"ЕÑли Вам нравитÑÑ Ñтот текÑÑ‚, Ñто может быть Ваш шрифт." +msgstr "Ðто пример текÑта.\nЕÑли Вам нравитÑÑ Ñтот текÑÑ‚, Ñто может быть Ваш шрифт." #: lib/choose_repository.tcl:28 msgid "Git Gui" @@ -1057,7 +1007,7 @@ msgstr "Создать новый репозиторий" #: lib/choose_repository.tcl:93 msgid "New..." -msgstr "Ðовый..." +msgstr "Ðовый…" #: lib/choose_repository.tcl:100 lib/choose_repository.tcl:471 msgid "Clone Existing Repository" @@ -1065,7 +1015,7 @@ msgstr "Склонировать ÑущеÑтвующий репозиторий #: lib/choose_repository.tcl:106 msgid "Clone..." -msgstr "Склонировать..." +msgstr "Клонировать…" #: lib/choose_repository.tcl:113 lib/choose_repository.tcl:1016 msgid "Open Existing Repository" @@ -1073,7 +1023,7 @@ msgstr "Выбрать ÑущеÑтвующий репозиторий" #: lib/choose_repository.tcl:119 msgid "Open..." -msgstr "Открыть..." +msgstr "Открыть…" #: lib/choose_repository.tcl:132 msgid "Recent Repositories" @@ -1126,7 +1076,7 @@ msgstr "Тип клона:" #: lib/choose_repository.tcl:508 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" -msgstr "Стандартный (БыÑтрый, полуизбыточный, \"жеÑткие\" ÑÑылки)" +msgstr "Стандартный (БыÑтрый, полуизбыточный, «жеÑткие» ÑÑылки)" #: lib/choose_repository.tcl:514 msgid "Full Copy (Slower, Redundant Backup)" @@ -1166,7 +1116,7 @@ msgstr "Считаю объекты" #: lib/choose_repository.tcl:641 msgid "buckets" -msgstr "" +msgstr "блоки" #: lib/choose_repository.tcl:665 #, tcl-format @@ -1181,11 +1131,11 @@ msgstr "Ðечего клонировать Ñ %s." #: lib/choose_repository.tcl:703 lib/choose_repository.tcl:917 #: lib/choose_repository.tcl:929 msgid "The 'master' branch has not been initialized." -msgstr "Ðе инициализирована ветвь 'master'." +msgstr "Ðе инициализирована ветвь «master»." #: lib/choose_repository.tcl:716 msgid "Hardlinks are unavailable. Falling back to copying." -msgstr "\"ЖеÑткие ÑÑылки\" недоÑтупны. Будет иÑпользовано копирование." +msgstr "«ЖеÑткие ÑÑылки» недоÑтупны. Будет иÑпользовано копирование." #: lib/choose_repository.tcl:728 #, tcl-format @@ -1216,16 +1166,15 @@ msgstr "объекты" #: lib/choose_repository.tcl:803 #, tcl-format msgid "Unable to hardlink object: %s" -msgstr "Ðе могу \"жеÑтко ÑвÑзать\" объект: %s" +msgstr "Ðе могу Ñоздать «жеÑткую ÑÑылку» на объект: %s" #: lib/choose_repository.tcl:858 msgid "Cannot fetch branches and objects. See console output for details." -msgstr "" -"Ðе могу получить ветви и объекты. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." +msgstr "Ðе удалоÑÑŒ извлечь ветки и объекты. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." #: lib/choose_repository.tcl:869 msgid "Cannot fetch tags. See console output for details." -msgstr "Ðе могу получить метки. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." +msgstr "Ðе удалоÑÑŒ извлечь метки. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." #: lib/choose_repository.tcl:893 msgid "Cannot determine HEAD. See console output for details." @@ -1242,12 +1191,12 @@ msgstr "Клонирование не удалоÑÑŒ." #: lib/choose_repository.tcl:915 msgid "No default branch obtained." -msgstr "Ðе было получено ветви по умолчанию." +msgstr "Ветка по умолчанию не была получена." #: lib/choose_repository.tcl:926 #, tcl-format msgid "Cannot resolve %s as a commit." -msgstr "Ðе могу раÑпознать %s как ÑоÑтоÑние." +msgstr "Ðе могу раÑпознать %s как коммит." #: lib/choose_repository.tcl:938 msgid "Creating working directory" @@ -1285,11 +1234,11 @@ msgstr "Выражение Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð²ÐµÑ€Ñии:" #: lib/choose_rev.tcl:74 msgid "Local Branch" -msgstr "Ð›Ð¾ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ‚Ð²ÑŒ:" +msgstr "Ð›Ð¾ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ‚ÐºÐ°:" #: lib/choose_rev.tcl:79 msgid "Tracking Branch" -msgstr "Ветвь ÑлежениÑ" +msgstr "ОтÑÐ»ÐµÐ¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð²ÐµÑ‚ÐºÐ°" #: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538 msgid "Tag" @@ -1320,29 +1269,19 @@ msgstr "СÑылка" 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" +"You are about to create the initial commit. There is no commit before this to amend.\n" +msgstr "ОтÑутÑтвует коммиты Ð´Ð»Ñ Ð¸ÑправлениÑ.\n\nÐ’Ñ‹ Ñоздаете начальный коммит, здеÑÑŒ еще нечего иÑправлÑть.\n" #: lib/commit.tcl:18 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" -msgstr "" -"Ðевозможно иÑправить ÑоÑтоÑние во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ ÑлиÑниÑ.\n" -"\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" +msgstr "Ðевозможно иÑправить коммит во Ð²Ñ€ÐµÐ¼Ñ ÑлиÑниÑ.\n\nТекущее ÑлиÑние не завершено. Ðевозможно иÑправить предыдуий коммит, не Ð¿Ñ€ÐµÑ€Ñ‹Ð²Ð°Ñ Ñту операцию.\n" #: lib/commit.tcl:48 msgid "Error loading commit data for amend:" -msgstr "Ошибка при загрузке данных Ð´Ð»Ñ Ð¸ÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñохраненного ÑоÑтоÑниÑ:" +msgstr "Ошибка при загрузке данных Ð´Ð»Ñ Ð¸ÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°:" #: lib/commit.tcl:75 msgid "Unable to obtain your identity:" @@ -1350,41 +1289,29 @@ msgstr "Ðевозможно получить информацию об авто #: lib/commit.tcl:80 msgid "Invalid GIT_COMMITTER_IDENT:" -msgstr "Ðеверный GIT_COMMITTER_IDENT:" +msgstr "ÐедопуÑтимый GIT_COMMITTER_IDENT:" #: lib/commit.tcl:129 #, tcl-format msgid "warning: Tcl does not support encoding '%s'." -msgstr "предупреждение: Tcl не поддерживает кодировку '%s'." +msgstr "предупреждение: Tcl не поддерживает кодировку «%s»." #: lib/commit.tcl:149 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 another commit can be created.\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" "The rescan will be automatically started now.\n" -msgstr "" -"ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n" -"\n" -"С момента поÑледней проверки репозиторий был изменен другой программой Git. " -"Ðеобходимо перечитать репозиторий, прежде чем изменÑть текущую ветвь. \n" -"\n" -"Ðто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" +msgstr "ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n\nС момента поÑледней проверки репозиторий был изменен другой программой Git. Ðеобходимо перечитать репозиторий, прежде чем изменÑть текущую ветвь. \n\nÐто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" #: lib/commit.tcl:172 #, 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" -msgstr "" -"ÐÐµÐ»ÑŒÐ·Ñ Ñохранить файлы Ñ Ð½ÐµÐ·Ð°Ð²ÐµÑ€ÑˆÑ‘Ð½Ð½Ð¾Ð¹ операцией ÑлиÑниÑ.\n" -"\n" -"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт ÑлиÑниÑ. Разрешите конфликт и добавьте к " -"подготовленным файлам перед Ñохранением.\n" +"File %s has merge conflicts. You must resolve them and stage the file before committing.\n" +msgstr "ÐÐµÐ»ÑŒÐ·Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÑŒ коммит Ñ Ð½ÐµÐ·Ð°Ð²ÐµÑ€ÑˆÑ‘Ð½Ð½Ð¾Ð¹ операцией ÑлиÑниÑ.\n\nÐ”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт ÑлиÑниÑ. Разрешите конфликт и добавьте их в Ð¸Ð½Ð´ÐµÐºÑ Ð¿ÐµÑ€ÐµÐ´ выполнением коммита.\n" #: lib/commit.tcl:180 #, tcl-format @@ -1392,20 +1319,14 @@ msgid "" "Unknown file state %s detected.\n" "\n" "File %s cannot be committed by this program.\n" -msgstr "" -"Обнаружено неизвеÑтное ÑоÑтоÑние файла %s.\n" -"\n" -"Файл %s не может быть Ñохранен данной программой.\n" +msgstr "Обнаружено неизвеÑтное ÑоÑтоÑние файла %s.\n\nФайл %s не может быть закоммичен Ñтой программой.\n" #: lib/commit.tcl:188 msgid "" "No changes to commit.\n" "\n" "You must stage at least 1 file before you can commit.\n" -msgstr "" -"ОтÑутÑтвуют Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ.\n" -"\n" -"Подготовьте Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один файл до ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñохраненного ÑоÑтоÑниÑ.\n" +msgstr "ОтÑутÑтвуют Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ.\n\nДобавьте в Ð¸Ð½Ð´ÐµÐºÑ Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один файл перед выполнением коммита.\n" #: lib/commit.tcl:203 msgid "" @@ -1416,34 +1337,27 @@ msgid "" "- 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" -"- оÑтавшиеÑÑ Ñтроки: опишите, что дают ваши изменениÑ.\n" +msgstr "Укажите Ñообщение коммита.\n\nРекомендуетÑÑ Ñледующий формат ÑообщениÑ:\n\n- в первой Ñтроке краткое опиÑание Ñделанных изменений\n- Ð²Ñ‚Ð¾Ñ€Ð°Ñ Ñтрока пуÑтаÑ\n- в оÑтавшихÑÑ Ñтроках опишите, что дают ваши изменениÑ\n" #: lib/commit.tcl:234 msgid "Calling pre-commit hook..." -msgstr "Вызов программы поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ pre-commit..." +msgstr "Вызов перехватчика pre-commit…" #: lib/commit.tcl:249 msgid "Commit declined by pre-commit hook." -msgstr "Сохранение прервано программой поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ pre-commit" +msgstr "Коммит прерван переватчиком pre-commit." #: lib/commit.tcl:272 msgid "Calling commit-msg hook..." -msgstr "Вызов программы поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ commit-msg..." +msgstr "Вызов перехватчика commit-msg…" #: lib/commit.tcl:287 msgid "Commit declined by commit-msg hook." -msgstr "Сохранение прервано программой поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ commit-msg" +msgstr "Коммит прерван переватчиком commit-msg" #: lib/commit.tcl:300 msgid "Committing changes..." -msgstr "Сохранение изменений..." +msgstr "Коммит изменений…" #: lib/commit.tcl:316 msgid "write-tree failed:" @@ -1451,12 +1365,12 @@ msgstr "Программа write-tree завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:" #: lib/commit.tcl:317 lib/commit.tcl:361 lib/commit.tcl:382 msgid "Commit failed." -msgstr "Сохранить ÑоÑтоÑние не удалоÑÑŒ." +msgstr "Ðе удалоÑÑŒ закоммитить изменениÑ." #: lib/commit.tcl:334 #, tcl-format msgid "Commit %s appears to be corrupt" -msgstr "СоÑтоÑние %s выглÑдит поврежденным" +msgstr "Коммит %s похоже поврежден" #: lib/commit.tcl:339 msgid "" @@ -1465,16 +1379,11 @@ msgid "" "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" +msgstr "Ðет Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°.\n\nÐи один файл не был изменен и не было ÑлиÑниÑ.\n\nÐ¡ÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки запуÑтитÑÑ Ð¿ÐµÑ€ÐµÑ‡Ð¸Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ðµ репозиториÑ.\n" #: lib/commit.tcl:346 msgid "No changes to commit." -msgstr "ОтÑутÑтвуют Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ." +msgstr "Ðет Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°." #: lib/commit.tcl:360 msgid "commit-tree failed:" @@ -1487,11 +1396,11 @@ msgstr "Программа update-ref завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:" #: lib/commit.tcl:469 #, tcl-format msgid "Created commit %s: %s" -msgstr "Создано ÑоÑтоÑние %s: %s " +msgstr "Создан коммит %s: %s " #: lib/console.tcl:59 msgid "Working... please wait..." -msgstr "Ð’ процеÑÑе... пожалуйÑта, ждите..." +msgstr "Ð’ процеÑÑе… пожалуйÑта, ждите…" #: lib/console.tcl:186 msgid "Success" @@ -1542,16 +1451,10 @@ msgstr "Проверка базы объектов при помощи fsck" msgid "" "This repository currently has approximately %i loose objects.\n" "\n" -"To maintain optimal performance it is strongly recommended that you compress " -"the database.\n" +"To maintain optimal performance it is strongly recommended that you compress the database.\n" "\n" "Compress the database now?" -msgstr "" -"Ðтот репозиторий ÑÐµÐ¹Ñ‡Ð°Ñ Ñодержит примерно %i Ñвободных объектов\n" -"\n" -"Ð”Ð»Ñ Ð»ÑƒÑ‡ÑˆÐµÐ¹ производительноÑти рекомендуетÑÑ Ñжать базу данных.\n" -"\n" -"Сжать базу данных ÑейчаÑ?" +msgstr "Ðтот репозиторий ÑÐµÐ¹Ñ‡Ð°Ñ Ñодержит примерно %i Ñвободных объектов\n\nÐ”Ð»Ñ Ð»ÑƒÑ‡ÑˆÐµÐ¹ производительноÑти рекомендуетÑÑ Ñжать базу данных.\n\nСжать базу данных ÑейчаÑ?" #: lib/date.tcl:25 #, tcl-format @@ -1565,41 +1468,27 @@ msgid "" "\n" "%s has no changes.\n" "\n" -"The modification date of this file was updated by another application, but " -"the content within the file was not changed.\n" -"\n" -"A rescan will be automatically started to find other files which may have " -"the same state." -msgstr "" -"Изменений не обнаружено.\n" +"The modification date of this file was updated by another application, but the content within the file was not changed.\n" "\n" -"в %s отÑутÑтвуют изменениÑ.\n" -"\n" -"Дата Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° была обновлена другой программой, но Ñодержимое файла " -"оÑталоÑÑŒ прежним.\n" -"\n" -"Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð±ÑƒÐ´ÐµÑ‚ запущено перечитывание репозиториÑ, чтобы найти подобные файлы." +"A rescan will be automatically started to find other files which may have the same state." +msgstr "Изменений не обнаружено.\n\nв %s отÑутÑтвуют изменениÑ.\n\nДата Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° была обновлена другой программой, но Ñодержимое файла оÑталоÑÑŒ прежним.\n\nÐ¡ÐµÐ¹Ñ‡Ð°Ñ Ð±ÑƒÐ´ÐµÑ‚ запущено перечитывание репозиториÑ, чтобы найти подобные файлы." #: lib/diff.tcl:104 #, tcl-format msgid "Loading diff of %s..." -msgstr "Загрузка изменений в %s..." +msgstr "Загрузка изменений %s…" #: lib/diff.tcl:125 msgid "" "LOCAL: deleted\n" "REMOTE:\n" -msgstr "" -"ЛОКÐЛЬÐО: удалён\n" -"Ð’ÐЕШÐИЙ:\n" +msgstr "ЛОКÐЛЬÐО: удалён\nÐ’ÐЕШÐИЙ:\n" #: lib/diff.tcl:130 msgid "" "REMOTE: deleted\n" "LOCAL:\n" -msgstr "" -"Ð’ÐЕШÐИЙ: удалён\n" -"ЛОКÐЛЬÐО:\n" +msgstr "Ð’ÐЕШÐИЙ: удалён\nЛОКÐЛЬÐО:\n" #: lib/diff.tcl:137 msgid "LOCAL:\n" @@ -1631,9 +1520,7 @@ msgstr "* Двоичный файл (Ñодержимое не показано) msgid "" "* Untracked file is %d bytes.\n" "* Showing only first %d bytes.\n" -msgstr "" -"* Размер неподготовленного файла %d байт.\n" -"* Показано первых %d байт.\n" +msgstr "* Размер неотÑлеживаемого файла %d байт.\n* Показано первых %d байт.\n" #: lib/diff.tcl:233 #, tcl-format @@ -1641,10 +1528,7 @@ msgid "" "\n" "* Untracked file clipped here by %s.\n" "* To see the entire file, use an external editor.\n" -msgstr "" -"\n" -"* Ðеподготовленный файл обрезан: %s.\n" -"* Чтобы увидеть веÑÑŒ файл, иÑпользуйте программу-редактор.\n" +msgstr "\n* ÐеотÑлеживаемый файл обрезан: %s.\n* Чтобы увидеть веÑÑŒ файл, иÑпользуйте внешний редактор.\n" #: lib/diff.tcl:482 msgid "Failed to unstage selected hunk." @@ -1652,7 +1536,7 @@ msgstr "Ðе удалоÑÑŒ иÑключить выбранную чаÑть." #: lib/diff.tcl:489 msgid "Failed to stage selected hunk." -msgstr "Ðе удалоÑÑŒ подготовить к Ñохранению выбранную чаÑть." +msgstr "Ðе удалоÑÑŒ проиндекÑировать выбранный блок изменений." #: lib/diff.tcl:568 msgid "Failed to unstage selected line." @@ -1660,7 +1544,7 @@ msgstr "Ðе удалоÑÑŒ иÑключить выбранную Ñтроку." #: lib/diff.tcl:576 msgid "Failed to stage selected line." -msgstr "Ðе удалоÑÑŒ подготовить к Ñохранению выбранную Ñтроку." +msgstr "Ðе удалоÑÑŒ проиндекÑировать выбранную Ñтроку." #: lib/encoding.tcl:443 msgid "Default" @@ -1685,7 +1569,7 @@ msgstr "предупреждение" #: lib/error.tcl:94 msgid "You must correct the above errors before committing." -msgstr "Прежде чем Ñохранить, иÑправьте вышеуказанные ошибки." +msgstr "Перед коммитом, иÑправьте вышеуказанные ошибки." #: lib/index.tcl:6 msgid "Unable to unlock the index." @@ -1699,9 +1583,7 @@ msgstr "Ошибка в индекÑе" msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." -msgstr "" -"Ðе удалоÑÑŒ обновить Ð¸Ð½Ð´ÐµÐºÑ Git. СоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ перечитано " -"автоматичеÑки." +msgstr "Ðе удалоÑÑŒ обновить Ð¸Ð½Ð´ÐµÐºÑ Git. СоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ перечитано автоматичеÑки." #: lib/index.tcl:28 msgid "Continue" @@ -1714,32 +1596,30 @@ msgstr "Разблокировать индекÑ" #: lib/index.tcl:289 #, tcl-format msgid "Unstaging %s from commit" -msgstr "Удаление %s из подготовленного" +msgstr "Удаление %s из индекÑа" #: lib/index.tcl:328 msgid "Ready to commit." -msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ" +msgstr "Готов Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°." #: lib/index.tcl:341 #, tcl-format msgid "Adding %s" -msgstr "Добавление %s..." +msgstr "Добавление %s…" #: lib/index.tcl:398 #, tcl-format msgid "Revert changes in file %s?" -msgstr "Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² файле %s?" +msgstr "Обратить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² файле %s?" #: lib/index.tcl:400 #, tcl-format msgid "Revert changes in these %i files?" -msgstr "Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² %i файле(-ах)?" +msgstr "Обратить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² %i файле(-ах)?" #: lib/index.tcl:408 msgid "Any unstaged changes will be permanently lost by the revert." -msgstr "" -"Любые изменениÑ, не подготовленные к Ñохранению, будут потерÑны при данной " -"операции." +msgstr "Любые непроиндекÑированные изменениÑ, будут потерÑны при обращении изменений." #: lib/index.tcl:411 msgid "Do Nothing" @@ -1747,38 +1627,28 @@ msgstr "Ðичего не делать" #: lib/index.tcl:429 msgid "Reverting selected files" -msgstr "Удаление изменений в выбранных файлах" +msgstr "Обращение изменений в выбранных файлах" #: lib/index.tcl:433 #, tcl-format msgid "Reverting %s" -msgstr "Отмена изменений в %s" +msgstr "Обращение изменений в %s" #: 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" +msgstr "Ðевозможно выполнить ÑлиÑние во Ð²Ñ€ÐµÐ¼Ñ Ð¸ÑправлениÑ.\n\nЗавершите иÑправление данного коммита перед выполнением операции ÑлиÑниÑ.\n" #: 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" +"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" +msgstr "ПоÑледнее прочитанное ÑоÑтоÑние Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑоответÑтвует текущему.\n\nС момента поÑледней проверки репозиторий был изменен другой программой Git. Ðеобходимо перечитать репозиторий, прежде чем ÑлиÑние может быть Ñделано.\n\nÐто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" #: lib/merge.tcl:45 #, tcl-format @@ -1787,15 +1657,8 @@ msgid "" "\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" +"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/merge.tcl:55 #, tcl-format @@ -1804,15 +1667,8 @@ msgid "" "\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" +"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/merge.tcl:107 #, tcl-format @@ -1822,7 +1678,7 @@ msgstr "%s из %s" #: lib/merge.tcl:120 #, tcl-format msgid "Merging %s and %s..." -msgstr "СлиÑние %s и %s..." +msgstr "СлиÑние %s и %s…" #: lib/merge.tcl:131 msgid "Merge completed successfully." @@ -1846,10 +1702,7 @@ msgid "" "Cannot abort while amending.\n" "\n" "You must finish amending this commit.\n" -msgstr "" -"Ðевозможно прервать иÑправление.\n" -"\n" -"Завершите текущее иÑправление Ñохраненного ÑоÑтоÑниÑ.\n" +msgstr "Ðевозможно прервать иÑправление.\n\nЗавершите текущее иÑправление коммита.\n" #: lib/merge.tcl:222 msgid "" @@ -1858,12 +1711,7 @@ msgid "" "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" -"Продолжить?" +msgstr "Прервать операцию ÑлиÑниÑ?\n\nПрерывание текущего ÑлиÑÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´ÐµÑ‚ к потере *ВСЕХ* неÑохраненных изменений.\n\nПродолжить?" #: lib/merge.tcl:228 msgid "" @@ -1872,12 +1720,7 @@ msgid "" "Resetting the changes will cause *ALL* uncommitted changes to be lost.\n" "\n" "Continue with resetting the current changes?" -msgstr "" -"Прервать операцию ÑлиÑниÑ?\n" -"\n" -"Прерывание Ñтой операции приведет к потере *ВСЕХ* неÑохраненных изменений.\n" -"\n" -"Продолжить?" +msgstr "СброÑить изменениÑ?\n\nÐ¡Ð±Ñ€Ð¾Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ приведет к потере *ВСЕХ* неÑохраненных изменений.\n\nПродолжить?" #: lib/merge.tcl:239 msgid "Aborting" @@ -1901,11 +1744,11 @@ msgstr "ИÑпользовать базовую верÑию Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆ #: lib/mergetool.tcl:9 msgid "Force resolution to this branch?" -msgstr "ИÑпользовать верÑию Ñтой ветви Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚Ð°?" +msgstr "ИÑпользовать верÑию из Ñтой ветки Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚Ð°?" #: lib/mergetool.tcl:10 msgid "Force resolution to the other branch?" -msgstr "ИÑпользовать верÑию другой ветви Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚Ð°?" +msgstr "ИÑпользовать верÑию из другой ветки Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚Ð°?" #: lib/mergetool.tcl:14 #, tcl-format @@ -1915,19 +1758,12 @@ msgid "" "%s will be overwritten.\n" "\n" "This operation can be undone only by restarting the merge." -msgstr "" -"Внимание! СпиÑок изменений показывает только конфликтующие отличиÑ.\n" -"\n" -"%s будет перепиÑан.\n" -"\n" -"Ðто дейÑтвие можно отменить только перезапуÑком операции ÑлиÑниÑ." +msgstr "Внимание! СпиÑок изменений показывает только конфликтующие отличиÑ.\n\n%s будет перепиÑан.\n\nÐто дейÑтвие можно отменить только перезапуÑком операции ÑлиÑниÑ." #: lib/mergetool.tcl:45 #, tcl-format msgid "File %s seems to have unresolved conflicts, still stage?" -msgstr "" -"Файл %s, похоже, Ñодержит необработанные конфликты. Продолжить подготовку к " -"Ñохранению?" +msgstr "Похоже, что файл %s Ñодержит неразрешенные конфликты. Продолжить индекÑацию?" #: lib/mergetool.tcl:60 #, tcl-format @@ -1936,8 +1772,7 @@ msgstr "ДобавлÑÑŽ результат Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð´Ð»Ñ %s" #: lib/mergetool.tcl:141 msgid "Cannot resolve deletion or link conflicts using a tool" -msgstr "" -"Программа ÑлиÑÐ½Ð¸Ñ Ð½Ðµ обрабатывает конфликты Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸ÐµÐ¼ или учаÑтием ÑÑылок" +msgstr "Программа ÑлиÑÐ½Ð¸Ñ Ð½Ðµ обрабатывает конфликты Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸ÐµÐ¼ или учаÑтием ÑÑылок" #: lib/mergetool.tcl:146 msgid "Conflict file does not exist" @@ -1946,12 +1781,12 @@ msgstr "Конфликтующий файл не ÑущеÑтвует" #: lib/mergetool.tcl:264 #, tcl-format msgid "Not a GUI merge tool: '%s'" -msgstr "'%s' не ÑвлÑетÑÑ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð¾Ð¹ ÑлиÑниÑ" +msgstr "«%s» не ÑвлÑетÑÑ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð¾Ð¹ ÑлиÑниÑ" #: lib/mergetool.tcl:268 #, tcl-format msgid "Unsupported merge tool '%s'" -msgstr "ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° ÑлиÑÐ½Ð¸Ñ '%s'" +msgstr "ÐÐµÐ¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° ÑлиÑÐ½Ð¸Ñ Â«%s»" #: lib/mergetool.tcl:303 msgid "Merge tool is already running, terminate it?" @@ -1962,9 +1797,7 @@ msgstr "Программа ÑлиÑÐ½Ð¸Ñ ÑƒÐ¶Ðµ работает. ÐŸÑ€ÐµÑ€Ð²Ð°Ñ msgid "" "Error retrieving versions:\n" "%s" -msgstr "" -"Ошибка Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð²ÐµÑ€Ñий:\n" -"%s" +msgstr "Ошибка Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð²ÐµÑ€Ñий:\n%s" #: lib/mergetool.tcl:343 #, tcl-format @@ -1972,14 +1805,11 @@ msgid "" "Could not start the merge tool:\n" "\n" "%s" -msgstr "" -"Ошибка запуÑка программы ÑлиÑниÑ:\n" -"\n" -"%s" +msgstr "Ошибка запуÑка программы ÑлиÑниÑ:\n\n%s" #: lib/mergetool.tcl:347 msgid "Running merge tool..." -msgstr "ЗапуÑк программы ÑлиÑниÑ..." +msgstr "ЗапуÑк программы ÑлиÑниÑ…" #: lib/mergetool.tcl:375 lib/mergetool.tcl:383 msgid "Merge tool failed." @@ -1988,12 +1818,12 @@ msgstr "Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñ‹ ÑлиÑниÑ." #: lib/option.tcl:11 #, tcl-format msgid "Invalid global encoding '%s'" -msgstr "Ошибка в глобальной уÑтановке кодировки '%s'" +msgstr "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ° «%s»" #: lib/option.tcl:19 #, tcl-format msgid "Invalid repo encoding '%s'" -msgstr "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ° репозиториÑ: '%s'" +msgstr "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ° Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ Â«%s»" #: lib/option.tcl:117 msgid "Restore Defaults" @@ -2022,7 +1852,7 @@ msgstr "ÐÐ´Ñ€ÐµÑ Ñлектронной почты" #: lib/option.tcl:141 msgid "Summarize Merge Commits" -msgstr "Суммарный комментарий при ÑлиÑнии" +msgstr "Суммарное Ñообщение при ÑлиÑнии" #: lib/option.tcl:142 msgid "Merge Verbosity" @@ -2042,11 +1872,11 @@ msgstr "ДоверÑть времени модификации файла" #: lib/option.tcl:147 msgid "Prune Tracking Branches During Fetch" -msgstr "ЧиÑтка ветвей ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ получении изменений" +msgstr "ЧиÑтка отÑлеживаемых веток при извлечении изменений" #: lib/option.tcl:148 msgid "Match Tracking Branches" -msgstr "Ð˜Ð¼Ñ Ð½Ð¾Ð²Ð¾Ð¹ ветви взÑть из имен ветвей ÑлежениÑ" +msgstr "Такое же имÑ, как и у отÑлеживаемой ветки" #: lib/option.tcl:149 msgid "Blame Copy Only On Changed Files" @@ -2066,11 +1896,11 @@ msgstr "ЧиÑло Ñтрок в контекÑте diff" #: lib/option.tcl:153 msgid "Commit Message Text Width" -msgstr "Ширина текÑта комментариÑ" +msgstr "Ширина текÑта ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¼Ð¸Ñ‚Ð°" #: lib/option.tcl:154 msgid "New Branch Name Template" -msgstr "Шаблон Ð´Ð»Ñ Ð¸Ð¼ÐµÐ½Ð¸ новой ветви" +msgstr "Шаблон Ð´Ð»Ñ Ð¸Ð¼ÐµÐ½Ð¸ новой ветки" #: lib/option.tcl:155 msgid "Default File Contents Encoding" @@ -2093,7 +1923,6 @@ msgstr "Изменить" msgid "Choose %s" msgstr "Выберите %s" -# carbon copy #: lib/option.tcl:264 msgid "pt." msgstr "pt." @@ -2116,7 +1945,7 @@ msgstr "ЧиÑтка" #: lib/remote.tcl:173 msgid "Fetch from" -msgstr "Получение из" +msgstr "Извлечение из" #: lib/remote.tcl:215 msgid "Push to" @@ -2132,7 +1961,7 @@ msgstr "Добавить внешний репозиторий" #: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36 msgid "Add" -msgstr "" +msgstr "Добавить" #: lib/remote_add.tcl:37 msgid "Remote Details" @@ -2148,7 +1977,7 @@ msgstr "Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ" #: lib/remote_add.tcl:65 msgid "Fetch Immediately" -msgstr "Скачать Ñразу" +msgstr "Сразу извлечь изменениÑ" #: lib/remote_add.tcl:71 msgid "Initialize Remote Repository and Push" @@ -2165,27 +1994,27 @@ msgstr "Укажите название внешнего репозиториÑ. #: lib/remote_add.tcl:114 #, tcl-format msgid "'%s' is not an acceptable remote name." -msgstr "ÐедопуÑтимое название внешнего Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ '%s'." +msgstr "«%s» не ÑвлÑетÑÑ Ð´Ð¾Ð¿ÑƒÑтимым именем внешнего репозиториÑ." #: lib/remote_add.tcl:125 #, tcl-format msgid "Failed to add remote '%s' of location '%s'." -msgstr "Ðе удалоÑÑŒ добавить '%s' из '%s'. " +msgstr "Ðе удалоÑÑŒ добавить «%s» из «%s». " #: lib/remote_add.tcl:133 lib/transport.tcl:6 #, tcl-format msgid "fetch %s" -msgstr "получение %s" +msgstr "извлечение %s" #: lib/remote_add.tcl:134 #, tcl-format msgid "Fetching the %s" -msgstr "Получение %s" +msgstr "Извлечение %s" #: lib/remote_add.tcl:157 #, tcl-format msgid "Do not know how to initialize repository at location '%s'." -msgstr "Ðевозможно инициализировать репозиторий в '%s'." +msgstr "Ðевозможно инициализировать репозиторий в «%s»." #: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63 #: lib/transport.tcl:81 @@ -2200,7 +2029,7 @@ msgstr "ÐаÑтройка %s (в %s)" #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 msgid "Delete Branch Remotely" -msgstr "Удаление ветви во внешнем репозитории" +msgstr "Удаление ветки во внешнем репозитории" #: lib/remote_branch_delete.tcl:47 msgid "From Repository" @@ -2216,7 +2045,7 @@ msgstr "Указанное положение:" #: lib/remote_branch_delete.tcl:84 msgid "Branches" -msgstr "Ветви" +msgstr "Ветки" #: lib/remote_branch_delete.tcl:109 msgid "Delete Only If" @@ -2228,7 +2057,7 @@ msgstr "СлиÑние Ñ:" #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." -msgstr "Ð”Ð»Ñ Ð¾Ð¿Ñ†Ð¸Ð¸ 'СлиÑние Ñ' требуетÑÑ ÑƒÐºÐ°Ð·Ð°Ñ‚ÑŒ ветвь." +msgstr "Ð”Ð»Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ «СлиÑние Ñ» требуетÑÑ ÑƒÐºÐ°Ð·Ð°Ñ‚ÑŒ ветку." #: lib/remote_branch_delete.tcl:184 #, tcl-format @@ -2236,28 +2065,23 @@ msgid "" "The following branches are not completely merged into %s:\n" "\n" " - %s" -msgstr "" -"Следующие ветви могут быть объединены Ñ %s при помощи операции ÑлиÑниÑ:\n" -"\n" -" - %s" +msgstr "Следующие ветки могут быть объединены Ñ %s при помощи операции ÑлиÑниÑ:\n\n - %s" #: 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." +msgstr "Ðекоторые теÑты на ÑлиÑние не прошли, потому что вы не извлекли необходимые коммиты. ПопытайтеÑÑŒ извлечь их из %s." #: lib/remote_branch_delete.tcl:207 msgid "Please select one or more branches to delete." -msgstr "Укажите одну или неÑколько ветвей Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ." +msgstr "Укажите одну или неÑколько веток Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ." #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" -msgstr "Удаление ветвей из %s" +msgstr "Удаление веток из %s" #: lib/remote_branch_delete.tcl:292 msgid "No repository selected." @@ -2266,7 +2090,7 @@ msgstr "Ðе указан репозиторий." #: lib/remote_branch_delete.tcl:297 #, tcl-format msgid "Scanning %s..." -msgstr "Перечитывание %s... " +msgstr "Перечитывание %s…" #: lib/search.tcl:21 msgid "Find:" @@ -2352,7 +2176,7 @@ msgstr "Ваш публичный ключ OpenSSH" #: lib/sshkey.tcl:78 msgid "Generating..." -msgstr "Создание..." +msgstr "Создание…" #: lib/sshkey.tcl:84 #, tcl-format @@ -2360,10 +2184,7 @@ msgid "" "Could not start ssh-keygen:\n" "\n" "%s" -msgstr "" -"Ошибка запуÑка ssh-keygen:\n" -"\n" -"%s" +msgstr "Ошибка запуÑка ssh-keygen:\n\n%s" #: lib/sshkey.tcl:111 msgid "Generation failed." @@ -2381,7 +2202,7 @@ msgstr "Ваш ключ находитÑÑ Ð²: %s" #: lib/status_bar.tcl:83 #, tcl-format msgid "%s ... %*i of %*i %s (%3i%%)" -msgstr "%s ... %*i из %*i %s (%3i%%)" +msgstr "%s … %*i из %*i %s (%3i%%)" #: lib/tools.tcl:75 #, tcl-format @@ -2431,7 +2252,7 @@ msgstr "ОпиÑание вÑпомогательной операции" #: lib/tools_dlg.tcl:48 msgid "Use '/' separators to create a submenu tree:" -msgstr "ИÑпользуйте '/' Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð´Ð¼ÐµÐ½ÑŽ" +msgstr "ИÑпользуйте «/» Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð´Ð¼ÐµÐ½ÑŽ" #: lib/tools_dlg.tcl:61 msgid "Command:" @@ -2464,16 +2285,14 @@ msgstr "Укажите название вÑпомогательной оперР#: lib/tools_dlg.tcl:129 #, tcl-format msgid "Tool '%s' already exists." -msgstr "Ð’ÑÐ¿Ð¾Ð¼Ð¾Ð³Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ '%s' уже ÑущеÑтвует." +msgstr "Ð’ÑÐ¿Ð¾Ð¼Ð¾Ð³Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Â«%s» уже ÑущеÑтвует." #: lib/tools_dlg.tcl:151 #, tcl-format msgid "" "Could not add tool:\n" "%s" -msgstr "" -"Ошибка Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñ‹:\n" -"%s" +msgstr "Ошибка Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñ‹:\n%s" #: lib/tools_dlg.tcl:190 msgid "Remove Tool" @@ -2507,9 +2326,8 @@ msgstr "OK" #: lib/transport.tcl:7 #, tcl-format msgid "Fetching new changes from %s" -msgstr "Получение изменений из %s " +msgstr "Извлечение изменений из %s " -# carbon copy #: lib/transport.tcl:18 #, tcl-format msgid "remote prune %s" @@ -2518,7 +2336,7 @@ msgstr "чиÑтка внешнего %s" #: lib/transport.tcl:19 #, tcl-format msgid "Pruning tracking branches deleted from %s" -msgstr "ЧиÑтка ветвей ÑлежениÑ, удаленных из %s" +msgstr "ЧиÑтка отÑлеживаемых веток, удалённых из %s" #: lib/transport.tcl:26 #, tcl-format @@ -2537,11 +2355,11 @@ msgstr "Отправка %s %s в %s" #: lib/transport.tcl:100 msgid "Push Branches" -msgstr "Отправить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² ветвÑÑ…" +msgstr "Отправить ветки" #: lib/transport.tcl:114 msgid "Source Branches" -msgstr "ИÑходные ветви" +msgstr "ИÑходные ветки" #: lib/transport.tcl:131 msgid "Destination Repository" @@ -2553,7 +2371,7 @@ msgstr "ÐаÑтройки отправки" #: lib/transport.tcl:171 msgid "Force overwrite existing branch (may discard changes)" -msgstr "Ðамеренно перепиÑать ÑущеÑтвующую ветвь (возможна Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹)" +msgstr "Принудительно перезапиÑать ÑущеÑтвующую ветку (возможна Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹)" #: lib/transport.tcl:175 msgid "Use thin pack (for slow network connections)" diff --git a/git-svn.perl b/git-svn.perl index 4d41d220a0..fa42364785 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -44,6 +44,7 @@ use Git qw( command_close_pipe command_bidi_pipe command_close_bidi_pipe + get_record ); BEGIN { @@ -1699,7 +1700,7 @@ sub cmd_gc { "files will not be compressed.\n"; } File::Find::find({ wanted => \&gc_directory, no_chdir => 1}, - "$ENV{GIT_DIR}/svn"); + Git::SVN::svn_dir()); } ########################### utility functions ######################### @@ -1733,7 +1734,7 @@ sub post_fetch_checkout { return unless verify_ref('HEAD^0'); return if $ENV{GIT_DIR} !~ m#^(?:.*/)?\.git$#; - my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index"; + my $index = command_oneline(qw(rev-parse --git-path index)); return if -f $index; return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false'; @@ -1835,8 +1836,9 @@ sub get_tree_from_treeish { sub get_commit_entry { my ($treeish) = shift; my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) ); - my $commit_editmsg = "$ENV{GIT_DIR}/COMMIT_EDITMSG"; - my $commit_msg = "$ENV{GIT_DIR}/COMMIT_MSG"; + my @git_path = qw(rev-parse --git-path); + my $commit_editmsg = command_oneline(@git_path, 'COMMIT_EDITMSG'); + my $commit_msg = command_oneline(@git_path, 'COMMIT_MSG'); open my $log_fh, '>', $commit_editmsg or croak $!; my $type = command_oneline(qw/cat-file -t/, $treeish); @@ -1880,10 +1882,9 @@ sub get_commit_entry { { require Encode; # SVN requires messages to be UTF-8 when entering the repo - local $/; open $log_fh, '<', $commit_msg or croak $!; binmode $log_fh; - chomp($log_entry{log} = <$log_fh>); + chomp($log_entry{log} = get_record($log_fh, undef)); my $enc = Git::config('i18n.commitencoding') || 'UTF-8'; my $msg = $log_entry{log}; @@ -35,8 +35,7 @@ static void save_env_before_alias(void) orig_cwd = xgetcwd(); for (i = 0; i < ARRAY_SIZE(env_names); i++) { orig_env[i] = getenv(env_names[i]); - if (orig_env[i]) - orig_env[i] = xstrdup(orig_env[i]); + orig_env[i] = xstrdup_or_null(orig_env[i]); } } @@ -164,6 +163,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) setenv(GIT_WORK_TREE_ENVIRONMENT, cmd, 1); if (envchanged) *envchanged = 1; + } else if (!strcmp(cmd, "--super-prefix")) { + if (*argc < 2) { + fprintf(stderr, "No prefix given for --super-prefix.\n" ); + usage(git_usage_string); + } + setenv(GIT_SUPER_PREFIX_ENVIRONMENT, (*argv)[1], 1); + if (envchanged) + *envchanged = 1; + (*argv)++; + (*argc)--; + } else if (skip_prefix(cmd, "--super-prefix=", &cmd)) { + setenv(GIT_SUPER_PREFIX_ENVIRONMENT, cmd, 1); + if (envchanged) + *envchanged = 1; } else if (!strcmp(cmd, "--bare")) { char *cwd = xgetcwd(); is_bare_repository_cfg = 1; @@ -310,6 +323,7 @@ static int handle_alias(int *argcp, const char ***argv) * RUN_SETUP for reading from the configuration file. */ #define NEED_WORK_TREE (1<<3) +#define SUPPORT_SUPER_PREFIX (1<<4) struct cmd_struct { const char *cmd; @@ -344,6 +358,13 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) } commit_pager_choice(); + if (!help && get_super_prefix()) { + if (!(p->option & SUPPORT_SUPER_PREFIX)) + die("%s doesn't support --super-prefix", p->cmd); + if (prefix) + die("can't use --super-prefix from a subdirectory"); + } + if (!help && p->option & NEED_WORK_TREE) setup_work_tree(); @@ -421,7 +442,7 @@ static struct cmd_struct commands[] = { { "init-db", cmd_init_db }, { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY }, { "log", cmd_log, RUN_SETUP }, - { "ls-files", cmd_ls_files, RUN_SETUP }, + { "ls-files", cmd_ls_files, RUN_SETUP | SUPPORT_SUPER_PREFIX }, { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, @@ -558,6 +579,9 @@ static void execv_dashed_external(const char **argv) const char *tmp; int status; + if (get_super_prefix()) + die("%s doesn't support --super-prefix", argv[0]); + if (use_pager == -1) use_pager = check_pager_config(argv[0]); commit_pager_choice(); diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 44094f41d5..7cf68f07b7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1616,7 +1616,7 @@ sub esc_path { return $str; } -# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0) +# Sanitize for use in XHTML + application/xml+xhtml (valid XML 1.0) sub sanitize { my $str = shift; @@ -2036,10 +2036,24 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - $line =~ s{\b([0-9a-fA-F]{8,40})\b}{ + $line =~ s{ + \b + ( + # The output of "git describe", e.g. v2.10.0-297-gf6727b0 + # or hadoop-20160921-113441-20-g094fb7d + (?<!-) # see strbuf_check_tag_ref(). Tags can't start with - + [A-Za-z0-9.-]+ + (?!\.) # refs can't end with ".", see check_refname_format() + -g[0-9a-fA-F]{7,40} + | + # Just a normal looking Git SHA1 + [0-9a-fA-F]{7,40} + ) + \b + }{ $cgi->a({-href => href(action=>"object", hash=>$1), -class => "text"}, $1); - }eg; + }egx; return $line; } diff --git a/gpg-interface.c b/gpg-interface.c index 8672edaf48..e44cc27da1 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -33,6 +33,10 @@ static struct { { 'B', "\n[GNUPG:] BADSIG " }, { 'U', "\n[GNUPG:] TRUST_NEVER" }, { 'U', "\n[GNUPG:] TRUST_UNDEFINED" }, + { 'E', "\n[GNUPG:] ERRSIG "}, + { 'X', "\n[GNUPG:] EXPSIG "}, + { 'Y', "\n[GNUPG:] EXPKEYSIG "}, + { 'R', "\n[GNUPG:] REVKEYSIG "}, }; void parse_gpg_output(struct signature_check *sigc) @@ -54,9 +58,12 @@ void parse_gpg_output(struct signature_check *sigc) /* The trust messages are not followed by key/signer information */ if (sigc->result != 'U') { sigc->key = xmemdupz(found, 16); - found += 17; - next = strchrnul(found, '\n'); - sigc->signer = xmemdupz(found, next - found); + /* The ERRSIG message is not followed by signer information */ + if (sigc-> result != 'E') { + found += 17; + next = strchrnul(found, '\n'); + sigc->signer = xmemdupz(found, next - found); + } } } } @@ -78,7 +78,8 @@ char *sha1_to_hex(const unsigned char *sha1) { static int bufno; static char hexbuffer[4][GIT_SHA1_HEXSZ + 1]; - return sha1_to_hex_r(hexbuffer[3 & ++bufno], sha1); + bufno = (bufno + 1) % ARRAY_SIZE(hexbuffer); + return sha1_to_hex_r(hexbuffer[bufno], sha1); } char *oid_to_hex(const struct object_id *oid) diff --git a/http-backend.c b/http-backend.c index adc8c8c3da..eef0a361f4 100644 --- a/http-backend.c +++ b/http-backend.c @@ -464,7 +464,7 @@ static void get_info_refs(struct strbuf *hdr, char *arg) hdr_str(hdr, content_type, buf.buf); end_headers(hdr); - packet_write(1, "# service=git-%s\n", svc->name); + packet_write_fmt(1, "# service=git-%s\n", svc->name); packet_flush(1); argv[0] = svc->name; diff --git a/imap-send.c b/imap-send.c index adb9738c30..5c7e27a894 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1082,10 +1082,8 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, char *f cred.protocol = xstrdup(srvc->use_ssl ? "imaps" : "imap"); cred.host = xstrdup(srvc->host); - if (srvc->user) - cred.username = xstrdup(srvc->user); - if (srvc->pass) - cred.password = xstrdup(srvc->pass); + cred.username = xstrdup_or_null(srvc->user); + cred.password = xstrdup_or_null(srvc->pass); credential_fill(&cred); @@ -103,10 +103,8 @@ static void add_mapping(struct string_list *map, } else { struct mailmap_info *mi = xcalloc(1, sizeof(struct mailmap_info)); debug_mm("mailmap: adding (complex) entry for '%s'\n", old_email); - if (new_name) - mi->name = xstrdup(new_name); - if (new_email) - mi->email = xstrdup(new_email); + mi->name = xstrdup_or_null(new_name); + mi->email = xstrdup_or_null(new_email); string_list_insert(&me->namemap, old_name)->util = mi; } diff --git a/pack-bitmap.c b/pack-bitmap.c index b949e51716..39bcc16846 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -266,7 +266,7 @@ static int open_pack_bitmap_1(struct packed_git *packfile) return -1; idx_name = pack_bitmap_filename(packfile); - fd = git_open_noatime(idx_name); + fd = git_open(idx_name); free(idx_name); if (fd < 0) @@ -25,7 +25,8 @@ static struct strbuf *get_pathname(void) STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT }; static int index; - struct strbuf *sb = &pathname_array[3 & ++index]; + struct strbuf *sb = &pathname_array[index]; + index = (index + 1) % ARRAY_SIZE(pathname_array); strbuf_reset(sb); return sb; } diff --git a/pathspec.c b/pathspec.c index 86f2b449b1..22ca74a126 100644 --- a/pathspec.c +++ b/pathspec.c @@ -364,7 +364,7 @@ void parse_pathspec(struct pathspec *pathspec, { struct pathspec_item *item; const char *entry = argv ? *argv : NULL; - int i, n, prefixlen, nr_exclude = 0; + int i, n, prefixlen, warn_empty_string, nr_exclude = 0; memset(pathspec, 0, sizeof(*pathspec)); @@ -402,8 +402,15 @@ void parse_pathspec(struct pathspec *pathspec, } n = 0; - while (argv[n]) + warn_empty_string = 1; + while (argv[n]) { + if (*argv[n] == '\0' && warn_empty_string) { + warning(_("empty strings as pathspecs will be made invalid in upcoming releases. " + "please use . instead if you meant to match all paths")); + warn_empty_string = 0; + } n++; + } pathspec->nr = n; ALLOC_ARRAY(pathspec->items, n); diff --git a/perl/Git.pm b/perl/Git.pm index ce7e4e8da3..b2732822af 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -59,7 +59,7 @@ require Exporter; command_bidi_pipe command_close_bidi_pipe version exec_path html_path hash_object git_cmd_try remote_refs prompt - get_tz_offset + get_tz_offset get_record credential credential_read credential_write temp_acquire temp_is_locked temp_release temp_reset temp_path); @@ -538,6 +538,20 @@ sub get_tz_offset { return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]); } +=item get_record ( FILEHANDLE, INPUT_RECORD_SEPARATOR ) + +Read one record from FILEHANDLE delimited by INPUT_RECORD_SEPARATOR, +removing any trailing INPUT_RECORD_SEPARATOR. + +=cut + +sub get_record { + my ($fh, $rs) = @_; + local $/ = $rs; + my $rec = <$fh>; + chomp $rec if defined $rs; + $rec; +} =item prompt ( PROMPT , ISPASSWORD ) @@ -871,6 +885,8 @@ Return an array of mailboxes extracted from a string. =cut +# Very close to Mail::Address's parser, but we still have minor +# differences in some cases (see t9000 for examples). sub parse_mailboxes { my $re_comment = qr/\((?:[^)]*)\)/; my $re_quote = qr/"(?:[^\"\\]|\\.)*"/; @@ -879,6 +895,7 @@ sub parse_mailboxes { # divide the string in tokens of the above form my $re_token = qr/(?:$re_quote|$re_word|$re_comment|\S)/; my @tokens = map { $_ =~ /\s*($re_token)\s*/g } @_; + my $end_of_addr_seen = 0; # add a delimiter to simplify treatment for the last mailbox push @tokens, ","; @@ -888,10 +905,10 @@ sub parse_mailboxes { if ($token =~ /^[,;]$/) { # if buffer still contains undeterminated strings # append it at the end of @address or @phrase - if (@address) { - push @address, @buffer; - } else { + if ($end_of_addr_seen) { push @phrase, @buffer; + } else { + push @address, @buffer; } my $str_phrase = join ' ', @phrase; @@ -915,16 +932,16 @@ sub parse_mailboxes { push @addr_list, $str_mailbox if ($str_mailbox); @phrase = @address = @comment = @buffer = (); + $end_of_addr_seen = 0; } elsif ($token =~ /^\(/) { push @comment, $token; } elsif ($token eq "<") { push @phrase, (splice @address), (splice @buffer); } elsif ($token eq ">") { + $end_of_addr_seen = 1; push @address, (splice @buffer); - } elsif ($token eq "@") { + } elsif ($token eq "@" && !$end_of_addr_seen) { push @address, (splice @buffer), "@"; - } elsif ($token eq ".") { - push @address, (splice @buffer), "."; } else { push @buffer, $token; } diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm index 018beb85a0..711d2687a3 100644 --- a/perl/Git/SVN.pm +++ b/perl/Git/SVN.pm @@ -807,10 +807,15 @@ sub get_fetch_range { (++$min, $max); } +sub svn_dir { + command_oneline(qw(rev-parse --git-path svn)); +} + sub tmp_config { my (@args) = @_; - my $old_def_config = "$ENV{GIT_DIR}/svn/config"; - my $config = "$ENV{GIT_DIR}/svn/.metadata"; + my $svn_dir = svn_dir(); + my $old_def_config = "$svn_dir/config"; + my $config = "$svn_dir/.metadata"; if (! -f $config && -f $old_def_config) { rename $old_def_config, $config or die "Failed rename $old_def_config => $config: $!\n"; @@ -1658,7 +1663,17 @@ sub tie_for_persistent_memoization { if ($memo_backend > 0) { tie %$hash => 'Git::SVN::Memoize::YAML', "$path.yaml"; } else { - tie %$hash => 'Memoize::Storable', "$path.db", 'nstore'; + # first verify that any existing file can actually be loaded + # (it may have been saved by an incompatible version) + my $db = "$path.db"; + if (-e $db) { + use Storable qw(retrieve); + + if (!eval { retrieve($db); 1 }) { + unlink $db or die "unlink $db failed: $!"; + } + } + tie %$hash => 'Memoize::Storable', $db, 'nstore'; } } @@ -1671,7 +1686,7 @@ sub tie_for_persistent_memoization { return if $memoized; $memoized = 1; - my $cache_path = "$ENV{GIT_DIR}/svn/.caches/"; + my $cache_path = svn_dir() . '/.caches/'; mkpath([$cache_path]) unless -d $cache_path; my %lookup_svn_merge_cache; @@ -1712,7 +1727,7 @@ sub tie_for_persistent_memoization { sub clear_memoized_mergeinfo_caches { die "Only call this method in non-memoized context" if ($memoized); - my $cache_path = "$ENV{GIT_DIR}/svn/.caches/"; + my $cache_path = svn_dir() . '/.caches/'; return unless -d $cache_path; for my $cache_file (("$cache_path/lookup_svn_merge", @@ -2446,12 +2461,13 @@ sub _new { "refs/remotes/$prefix$default_ref_id"; } $_[1] = $repo_id; - my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; + my $svn_dir = svn_dir(); + my $dir = "$svn_dir/$ref_id"; - # Older repos imported by us used $GIT_DIR/svn/foo instead of - # $GIT_DIR/svn/refs/remotes/foo when tracking refs/remotes/foo + # Older repos imported by us used $svn_dir/foo instead of + # $svn_dir/refs/remotes/foo when tracking refs/remotes/foo if ($ref_id =~ m{^refs/remotes/(.+)}) { - my $old_dir = "$ENV{GIT_DIR}/svn/$1"; + my $old_dir = "$svn_dir/$1"; if (-d $old_dir && ! -d $dir) { $dir = $old_dir; } @@ -2461,7 +2477,7 @@ sub _new { mkpath([$dir]); my $obj = bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", - config => "$ENV{GIT_DIR}/svn/config", + config => "$svn_dir/config", map_root => "$dir/.rev_map", repo_id => $repo_id }, $class; # Ensure it gets canonicalized diff --git a/perl/Git/SVN/Editor.pm b/perl/Git/SVN/Editor.pm index 4c4199afec..0df16ed726 100644 --- a/perl/Git/SVN/Editor.pm +++ b/perl/Git/SVN/Editor.pm @@ -7,7 +7,9 @@ use SVN::Delta; use Carp qw/croak/; use Git qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe - command_bidi_pipe command_close_bidi_pipe/; + command_bidi_pipe command_close_bidi_pipe + get_record/; + BEGIN { @ISA = qw(SVN::Delta::Editor); } @@ -57,11 +59,9 @@ sub generate_diff { push @diff_tree, "-l$_rename_limit" if defined $_rename_limit; push @diff_tree, $tree_a, $tree_b; my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); - local $/ = "\0"; my $state = 'meta'; my @mods; - while (<$diff_fh>) { - chomp $_; # this gets rid of the trailing "\0" + while (defined($_ = get_record($diff_fh, "\0"))) { if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s ($::sha1)\s($::sha1)\s ([MTCRAD])\d*$/xo) { @@ -173,9 +173,7 @@ sub rmdirs { my ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/, $self->{tree_b}); - local $/ = "\0"; - while (<$fh>) { - chomp; + while (defined($_ = get_record($fh, "\0"))) { my @dn = split m#/#, $_; while (pop @dn) { delete $rm->{join '/', @dn}; diff --git a/perl/Git/SVN/Fetcher.pm b/perl/Git/SVN/Fetcher.pm index d8c21ad915..64e900a0e9 100644 --- a/perl/Git/SVN/Fetcher.pm +++ b/perl/Git/SVN/Fetcher.pm @@ -9,7 +9,8 @@ use Carp qw/croak/; use File::Basename qw/dirname/; use Git qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe - command_bidi_pipe command_close_bidi_pipe/; + command_bidi_pipe command_close_bidi_pipe + get_record/; BEGIN { @ISA = qw(SVN::Delta::Editor); } @@ -86,11 +87,9 @@ sub _mark_empty_symlinks { my $printed_warning; chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`); my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt); - local $/ = "\0"; my $pfx = defined($switch_path) ? $switch_path : $git_svn->path; $pfx .= '/' if length($pfx); - while (<$ls>) { - chomp; + while (defined($_ = get_record($ls, "\0"))) { s/\A100644 blob $empty_blob\t//o or next; unless ($printed_warning) { print STDERR "Scanning for empty symlinks, ", @@ -179,9 +178,7 @@ sub delete_entry { my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r --name-only -z/, $tree); - local $/ = "\0"; - while (<$ls>) { - chomp; + while (defined($_ = get_record($ls, "\0"))) { my $rmpath = "$gpath/$_"; $self->{gii}->remove($rmpath); print "\tD\t$rmpath\n" unless $::_q; @@ -247,9 +244,7 @@ sub add_directory { my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r --name-only -z/, $self->{c}); - local $/ = "\0"; - while (<$ls>) { - chomp; + while (defined($_ = get_record($ls, "\0"))) { $self->{gii}->remove($_); print "\tD\t$_\n" unless $::_q; push @deleted_gpath, $gpath; diff --git a/perl/Git/SVN/Migration.pm b/perl/Git/SVN/Migration.pm index cf6ffa7581..dc90f6a621 100644 --- a/perl/Git/SVN/Migration.pm +++ b/perl/Git/SVN/Migration.pm @@ -44,7 +44,9 @@ use Git qw( command_noisy command_output_pipe command_close_pipe + command_oneline ); +use Git::SVN; sub migrate_from_v0 { my $git_dir = $ENV{GIT_DIR}; @@ -55,7 +57,9 @@ sub migrate_from_v0 { chomp; my ($id, $orig_ref) = ($_, $_); next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#; - next unless -f "$git_dir/$id/info/url"; + my $info_url = command_oneline(qw(rev-parse --git-path), + "$id/info/url"); + next unless -f $info_url; my $new_ref = "refs/remotes/$id"; if (::verify_ref("$new_ref^0")) { print STDERR "W: $orig_ref is probably an old ", @@ -82,7 +86,7 @@ sub migrate_from_v1 { my $git_dir = $ENV{GIT_DIR}; my $migrated = 0; return $migrated unless -d $git_dir; - my $svn_dir = "$git_dir/svn"; + my $svn_dir = Git::SVN::svn_dir(); # just in case somebody used 'svn' as their $id at some point... return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url"; @@ -97,27 +101,28 @@ sub migrate_from_v1 { my $x = $_; next unless $x =~ s#^refs/remotes/##; chomp $x; - next unless -f "$git_dir/$x/info/url"; - my $u = eval { ::file_to_s("$git_dir/$x/info/url") }; + my $info_url = command_oneline(qw(rev-parse --git-path), + "$x/info/url"); + next unless -f $info_url; + my $u = eval { ::file_to_s($info_url) }; next unless $u; - my $dn = dirname("$git_dir/svn/$x"); + my $dn = dirname("$svn_dir/$x"); mkpath([$dn]) unless -d $dn; if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID: - mkpath(["$git_dir/svn/svn"]); + mkpath(["$svn_dir/svn"]); print STDERR " - $git_dir/$x/info => ", - "$git_dir/svn/$x/info\n"; - rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or + "$svn_dir/$x/info\n"; + rename "$git_dir/$x/info", "$svn_dir/$x/info" or croak "$!: $x"; # don't worry too much about these, they probably # don't exist with repos this old (save for index, # and we can easily regenerate that) foreach my $f (qw/unhandled.log index .rev_db/) { - rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f"; + rename "$git_dir/$x/$f", "$svn_dir/$x/$f"; } } else { - print STDERR " - $git_dir/$x => $git_dir/svn/$x\n"; - rename "$git_dir/$x", "$git_dir/svn/$x" or - croak "$!: $x"; + print STDERR " - $git_dir/$x => $svn_dir/$x\n"; + rename "$git_dir/$x", "$svn_dir/$x" or croak "$!: $x"; } $migrated++; } @@ -139,9 +144,10 @@ sub read_old_urls { push @dir, $_; } } + my $svn_dir = Git::SVN::svn_dir(); foreach (@dir) { my $x = $_; - $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; + $x =~ s!^\Q$svn_dir\E/!!o; read_old_urls($l_map, $x, $_); } } @@ -150,7 +156,7 @@ sub migrate_from_v2 { my @cfg = command(qw/config -l/); return if grep /^svn-remote\..+\.url=/, @cfg; my %l_map; - read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn"); + read_old_urls(\%l_map, '', Git::SVN::svn_dir()); my $migrated = 0; require Git::SVN; @@ -239,7 +245,8 @@ sub minimize_connections { } } if (@emptied) { - my $file = $ENV{GIT_CONFIG} || "$ENV{GIT_DIR}/config"; + my $file = $ENV{GIT_CONFIG} || + command_oneline(qw(rev-parse --git-path config)); print STDERR <<EOF; The following [svn-remote] sections in your config file ($file) are empty and can be safely removed: diff --git a/pkt-line.c b/pkt-line.c index 30489c60b1..d4b6bfe076 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -91,16 +91,34 @@ void packet_flush(int fd) write_or_die(fd, "0000", 4); } +int packet_flush_gently(int fd) +{ + packet_trace("0000", 4, 1); + if (write_in_full(fd, "0000", 4) == 4) + return 0; + return error("flush packet write failed"); +} + void packet_buf_flush(struct strbuf *buf) { packet_trace("0000", 4, 1); strbuf_add(buf, "0000", 4); } -#define hex(a) (hexchar[(a) & 15]) -static void format_packet(struct strbuf *out, const char *fmt, va_list args) +static void set_packet_header(char *buf, const int size) { static char hexchar[] = "0123456789abcdef"; + + #define hex(a) (hexchar[(a) & 15]) + buf[0] = hex(size >> 12); + buf[1] = hex(size >> 8); + buf[2] = hex(size >> 4); + buf[3] = hex(size); + #undef hex +} + +static void format_packet(struct strbuf *out, const char *fmt, va_list args) +{ size_t orig_len, n; orig_len = out->len; @@ -111,23 +129,63 @@ static void format_packet(struct strbuf *out, const char *fmt, va_list args) if (n > LARGE_PACKET_MAX) die("protocol error: impossibly long line"); - out->buf[orig_len + 0] = hex(n >> 12); - out->buf[orig_len + 1] = hex(n >> 8); - out->buf[orig_len + 2] = hex(n >> 4); - out->buf[orig_len + 3] = hex(n); + set_packet_header(&out->buf[orig_len], n); packet_trace(out->buf + orig_len + 4, n - 4, 1); } -void packet_write(int fd, const char *fmt, ...) +static int packet_write_fmt_1(int fd, int gently, + const char *fmt, va_list args) +{ + struct strbuf buf = STRBUF_INIT; + ssize_t count; + + format_packet(&buf, fmt, args); + count = write_in_full(fd, buf.buf, buf.len); + if (count == buf.len) + return 0; + + if (!gently) { + check_pipe(errno); + die_errno("packet write with format failed"); + } + return error("packet write with format failed"); +} + +void packet_write_fmt(int fd, const char *fmt, ...) { - static struct strbuf buf = STRBUF_INIT; va_list args; - strbuf_reset(&buf); va_start(args, fmt); - format_packet(&buf, fmt, args); + packet_write_fmt_1(fd, 0, fmt, args); + va_end(args); +} + +int packet_write_fmt_gently(int fd, const char *fmt, ...) +{ + int status; + va_list args; + + va_start(args, fmt); + status = packet_write_fmt_1(fd, 1, fmt, args); va_end(args); - write_or_die(fd, buf.buf, buf.len); + return status; +} + +static int packet_write_gently(const int fd_out, const char *buf, size_t size) +{ + static char packet_write_buffer[LARGE_PACKET_MAX]; + size_t packet_size; + + if (size > sizeof(packet_write_buffer) - 4) + return error("packet write failed - data exceeds max packet size"); + + packet_trace(buf, size, 1); + packet_size = size + 4; + set_packet_header(packet_write_buffer, packet_size); + memcpy(packet_write_buffer + 4, buf, size); + if (write_in_full(fd_out, packet_write_buffer, packet_size) == packet_size) + return 0; + return error("packet write failed"); } void packet_buf_write(struct strbuf *buf, const char *fmt, ...) @@ -139,6 +197,46 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) va_end(args); } +int write_packetized_from_fd(int fd_in, int fd_out) +{ + static char buf[LARGE_PACKET_DATA_MAX]; + int err = 0; + ssize_t bytes_to_write; + + while (!err) { + bytes_to_write = xread(fd_in, buf, sizeof(buf)); + if (bytes_to_write < 0) + return COPY_READ_ERROR; + if (bytes_to_write == 0) + break; + err = packet_write_gently(fd_out, buf, bytes_to_write); + } + if (!err) + err = packet_flush_gently(fd_out); + return err; +} + +int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) +{ + int err = 0; + size_t bytes_written = 0; + size_t bytes_to_write; + + while (!err) { + if ((len - bytes_written) > LARGE_PACKET_DATA_MAX) + bytes_to_write = LARGE_PACKET_DATA_MAX; + else + bytes_to_write = len - bytes_written; + if (bytes_to_write == 0) + break; + err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write); + bytes_written += bytes_to_write; + } + if (!err) + err = packet_flush_gently(fd_out); + return err; +} + static int get_packet_data(int fd, char **src_buf, size_t *src_size, void *dst, unsigned size, int options) { @@ -229,3 +327,35 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len) { return packet_read_line_generic(-1, src, src_len, dst_len); } + +ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out) +{ + int packet_len; + + size_t orig_len = sb_out->len; + size_t orig_alloc = sb_out->alloc; + + for (;;) { + strbuf_grow(sb_out, LARGE_PACKET_DATA_MAX); + packet_len = packet_read(fd_in, NULL, NULL, + /* strbuf_grow() above always allocates one extra byte to + * store a '\0' at the end of the string. packet_read() + * writes a '\0' extra byte at the end, too. Let it know + * that there is already room for the extra byte. + */ + sb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1, + PACKET_READ_GENTLE_ON_EOF); + if (packet_len <= 0) + break; + sb_out->len += packet_len; + } + + if (packet_len < 0) { + if (orig_alloc == 0) + strbuf_release(sb_out); + else + strbuf_setlen(sb_out, orig_len); + return packet_len; + } + return sb_out->len - orig_len; +} diff --git a/pkt-line.h b/pkt-line.h index 3cb9d91baa..18eac64830 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -20,9 +20,13 @@ * side can't, we stay with pure read/write interfaces. */ void packet_flush(int fd); -void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); void packet_buf_flush(struct strbuf *buf); void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +int packet_flush_gently(int fd); +int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); +int write_packetized_from_fd(int fd_in, int fd_out); +int write_packetized_from_buf(const char *src_in, size_t len, int fd_out); /* * Read a packetized line into the buffer, which must be at least size bytes @@ -75,8 +79,14 @@ char *packet_read_line(int fd, int *size); */ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size); +/* + * Reads a stream of variable sized packets until a flush packet is detected. + */ +ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out); + #define DEFAULT_PACKET_MAX 1000 #define LARGE_PACKET_MAX 65520 +#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4) extern char packet_buffer[LARGE_PACKET_MAX]; #endif @@ -1230,8 +1230,12 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ switch (c->signature_check.result) { case 'G': case 'B': + case 'E': case 'U': case 'N': + case 'X': + case 'Y': + case 'R': strbuf_addch(sb, c->signature_check.result); } break; diff --git a/read-cache.c b/read-cache.c index 38d67faf70..db5d910642 100644 --- a/read-cache.c +++ b/read-cache.c @@ -156,7 +156,14 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) static int ce_compare_data(const struct cache_entry *ce, struct stat *st) { int match = -1; - int fd = open(ce->name, O_RDONLY); + static int cloexec = O_CLOEXEC; + int fd = open(ce->name, O_RDONLY | cloexec); + + if ((cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) { + /* Try again w/o O_CLOEXEC: the kernel might not support it */ + cloexec &= ~O_CLOEXEC; + fd = open(ce->name, O_RDONLY | cloexec); + } if (fd >= 0) { unsigned char sha1[20]; @@ -816,8 +816,7 @@ struct ref_update *ref_transaction_add_update( hashcpy(update->new_sha1, new_sha1); if (flags & REF_HAVE_OLD) hashcpy(update->old_sha1, old_sha1); - if (msg) - update->msg = xstrdup(msg); + update->msg = xstrdup_or_null(msg); return update; } diff --git a/run-command.c b/run-command.c index 5a4dbb66d7..ca905a9e80 100644 --- a/run-command.c +++ b/run-command.c @@ -21,6 +21,7 @@ void child_process_clear(struct child_process *child) struct child_to_clean { pid_t pid; + struct child_process *process; struct child_to_clean *next; }; static struct child_to_clean *children_to_clean; @@ -31,6 +32,18 @@ static void cleanup_children(int sig, int in_signal) while (children_to_clean) { struct child_to_clean *p = children_to_clean; children_to_clean = p->next; + + if (p->process && !in_signal) { + struct child_process *process = p->process; + if (process->clean_on_exit_handler) { + trace_printf( + "trace: run_command: running exit handler for pid %" + PRIuMAX, (uintmax_t)p->pid + ); + process->clean_on_exit_handler(process); + } + } + kill(p->pid, sig); if (!in_signal) free(p); @@ -49,10 +62,11 @@ static void cleanup_children_on_exit(void) cleanup_children(SIGTERM, 0); } -static void mark_child_for_cleanup(pid_t pid) +static void mark_child_for_cleanup(pid_t pid, struct child_process *process) { struct child_to_clean *p = xmalloc(sizeof(*p)); p->pid = pid; + p->process = process; p->next = children_to_clean; children_to_clean = p; @@ -422,7 +436,7 @@ fail_pipe: if (cmd->pid < 0) error_errno("cannot fork() for %s", cmd->argv[0]); else if (cmd->clean_on_exit) - mark_child_for_cleanup(cmd->pid); + mark_child_for_cleanup(cmd->pid, cmd); /* * Wait for child's execvp. If the execvp succeeds (or if fork() @@ -483,7 +497,7 @@ fail_pipe: if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) error_errno("cannot spawn %s", cmd->argv[0]); if (cmd->clean_on_exit && cmd->pid >= 0) - mark_child_for_cleanup(cmd->pid); + mark_child_for_cleanup(cmd->pid, cmd); argv_array_clear(&nargv); cmd->argv = sargv; @@ -634,7 +648,7 @@ int in_async(void) return !pthread_equal(main_thread, pthread_self()); } -void NORETURN async_exit(int code) +static void NORETURN async_exit(int code) { pthread_exit((void *)(intptr_t)code); } @@ -684,13 +698,26 @@ int in_async(void) return process_is_async; } -void NORETURN async_exit(int code) +static void NORETURN async_exit(int code) { exit(code); } #endif +void check_pipe(int err) +{ + if (err == EPIPE) { + if (in_async()) + async_exit(141); + + signal(SIGPIPE, SIG_DFL); + raise(SIGPIPE); + /* Should never happen, but just in case... */ + exit(141); + } +} + int start_async(struct async *async) { int need_in, need_out; @@ -752,7 +779,7 @@ int start_async(struct async *async) exit(!!async->proc(proc_in, proc_out, async->data)); } - mark_child_for_cleanup(async->pid); + mark_child_for_cleanup(async->pid, NULL); if (need_in) close(fdin[0]); diff --git a/run-command.h b/run-command.h index 50666497ae..dd1c78c28d 100644 --- a/run-command.h +++ b/run-command.h @@ -43,6 +43,8 @@ struct child_process { unsigned stdout_to_stderr:1; unsigned use_shell:1; unsigned clean_on_exit:1; + void (*clean_on_exit_handler)(struct child_process *process); + void *clean_on_exit_handler_cbdata; }; #define CHILD_PROCESS_INIT { NULL, ARGV_ARRAY_INIT, ARGV_ARRAY_INIT } @@ -139,7 +141,7 @@ struct async { int start_async(struct async *async); int finish_async(struct async *async); int in_async(void); -void NORETURN async_exit(int code); +void check_pipe(int err); /** * This callback should initialize the child process and preload the diff --git a/send-pack.c b/send-pack.c index 90f2ac51a7..6195b43e9a 100644 --- a/send-pack.c +++ b/send-pack.c @@ -181,8 +181,7 @@ static int receive_status(int in, struct ref *refs) hint->status = REF_STATUS_REMOTE_REJECT; ret = -1; } - if (msg) - hint->remote_status = xstrdup(msg); + hint->remote_status = xstrdup_or_null(msg); /* start our next search from the next ref */ hint = hint->next; } diff --git a/sequencer.c b/sequencer.c index eec8a60d6b..5fd75f30dd 100644 --- a/sequencer.c +++ b/sequencer.c @@ -15,16 +15,46 @@ #include "merge-recursive.h" #include "refs.h" #include "argv-array.h" +#include "quote.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; -static GIT_PATH_FUNC(git_path_todo_file, SEQ_TODO_FILE) -static GIT_PATH_FUNC(git_path_opts_file, SEQ_OPTS_FILE) -static GIT_PATH_FUNC(git_path_seq_dir, SEQ_DIR) -static GIT_PATH_FUNC(git_path_head_file, SEQ_HEAD_FILE) +GIT_PATH_FUNC(git_path_seq_dir, "sequencer") + +static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") +static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") +static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") + +/* + * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and + * GIT_AUTHOR_DATE that will be used for the commit that is currently + * being rebased. + */ +static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") +/* + * The following files are written by git-rebase just after parsing the + * command-line (and are only consumed, not modified, by the sequencer). + */ +static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") + +/* We will introduce the 'interactive rebase' mode later */ +static inline int is_rebase_i(const struct replay_opts *opts) +{ + return 0; +} + +static const char *get_dir(const struct replay_opts *opts) +{ + return git_path_seq_dir(); +} + +static const char *get_todo_path(const struct replay_opts *opts) +{ + return git_path_todo_file(); +} static int is_rfc2822_line(const char *buf, int len) { @@ -108,18 +138,37 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, return 1; } -static void remove_sequencer_state(void) +static const char *gpg_sign_opt_quoted(struct replay_opts *opts) +{ + static struct strbuf buf = STRBUF_INIT; + + strbuf_reset(&buf); + if (opts->gpg_sign) + sq_quotef(&buf, "-S%s", opts->gpg_sign); + return buf.buf; +} + +int sequencer_remove_state(struct replay_opts *opts) { - struct strbuf seq_dir = STRBUF_INIT; + struct strbuf dir = STRBUF_INIT; + int i; + + free(opts->gpg_sign); + free(opts->strategy); + for (i = 0; i < opts->xopts_nr; i++) + free(opts->xopts[i]); + free(opts->xopts); + + strbuf_addf(&dir, "%s", get_dir(opts)); + remove_dir_recursively(&dir, 0); + strbuf_release(&dir); - strbuf_addstr(&seq_dir, git_path(SEQ_DIR)); - remove_dir_recursively(&seq_dir, 0); - strbuf_release(&seq_dir); + return 0; } static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; + return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); } struct commit_message { @@ -129,13 +178,18 @@ struct commit_message { const char *message; }; +static const char *short_commit_name(struct commit *commit) +{ + return find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV); +} + static int get_message(struct commit *commit, struct commit_message *out) { const char *abbrev, *subject; int subject_len; out->message = logmsg_reencode(commit, NULL, get_commit_output_encoding()); - abbrev = find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV); + abbrev = short_commit_name(commit); subject_len = find_commit_subject(out->message, &subject); @@ -180,22 +234,64 @@ static void print_advice(int show_hint, struct replay_opts *opts) } } -static int write_message(struct strbuf *msgbuf, const char *filename) +static int write_message(const void *buf, size_t len, const char *filename, + int append_eol) { static struct lock_file msg_file; 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) - return error_errno(_("Could not write to %s"), filename); - strbuf_release(msgbuf); - if (commit_lock_file(&msg_file) < 0) - return error(_("Error wrapping up %s."), filename); + return error_errno(_("could not lock '%s'"), filename); + if (write_in_full(msg_fd, buf, len) < 0) { + rollback_lock_file(&msg_file); + return error_errno(_("could not write to '%s'"), filename); + } + if (append_eol && write(msg_fd, "\n", 1) < 0) { + rollback_lock_file(&msg_file); + return error_errno(_("could not write eol to '%s"), filename); + } + if (commit_lock_file(&msg_file) < 0) { + rollback_lock_file(&msg_file); + return error(_("failed to finalize '%s'."), filename); + } return 0; } +/* + * Reads a file that was presumably written by a shell script, i.e. with an + * end-of-line marker that needs to be stripped. + * + * Note that only the last end-of-line marker is stripped, consistent with the + * behavior of "$(cat path)" in a shell script. + * + * Returns 1 if the file was read, 0 if it could not be read or does not exist. + */ +static int read_oneliner(struct strbuf *buf, + const char *path, int skip_if_empty) +{ + int orig_len = buf->len; + + if (!file_exists(path)) + return 0; + + if (strbuf_read_file(buf, path, 0) < 0) { + warning_errno(_("could not read '%s'"), path); + return 0; + } + + if (buf->len > orig_len && buf->buf[buf->len - 1] == '\n') { + if (--buf->len > orig_len && buf->buf[buf->len - 1] == '\r') + --buf->len; + buf->buf[buf->len] = '\0'; + } + + if (skip_if_empty && buf->len == orig_len) + return 0; + + return 1; +} + static struct tree *empty_tree(void) { return lookup_tree(EMPTY_TREE_SHA1_BIN); @@ -204,16 +300,13 @@ static struct tree *empty_tree(void) static int error_dirty_index(struct replay_opts *opts) { if (read_cache_unmerged()) - return error_resolve_conflict(action_name(opts)); + return error_resolve_conflict(_(action_name(opts))); - /* Different translation strings for cherry-pick and revert */ - if (opts->action == REPLAY_PICK) - error(_("Your local changes would be overwritten by cherry-pick.")); - else - error(_("Your local changes would be overwritten by revert.")); + error(_("your local changes would be overwritten by %s."), + _(action_name(opts))); if (advice_commit_before_merge) - advise(_("Commit your changes or stash them to proceed.")); + advise(_("commit your changes or stash them to proceed.")); return -1; } @@ -228,7 +321,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, if (checkout_fast_forward(from, to, 1)) return -1; /* the callee should have complained already */ - strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts)); + strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts))); transaction = ref_transaction_begin(&err); if (!transaction || @@ -274,7 +367,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; int clean; - const char **xopt; + char **xopt; static struct lock_file index_lock; hold_locked_index(&index_lock, 1); @@ -304,7 +397,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ return error(_("%s: Unable to write new index file"), - action_name(opts)); + _(action_name(opts))); rollback_lock_file(&index_lock); if (opts->signoff) @@ -322,7 +415,7 @@ static int is_index_unchanged(void) struct commit *head_commit; if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) - return error(_("Could not resolve HEAD commit\n")); + return error(_("could not resolve HEAD commit\n")); head_commit = lookup_commit(head_sha1); @@ -342,41 +435,115 @@ static int is_index_unchanged(void) if (!cache_tree_fully_valid(active_cache_tree)) if (cache_tree_update(&the_index, 0)) - return error(_("Unable to update cache tree\n")); + return error(_("unable to update cache tree\n")); return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } /* + * Read the author-script file into an environment block, ready for use in + * run_command(), that can be free()d afterwards. + */ +static char **read_author_script(void) +{ + struct strbuf script = STRBUF_INIT; + int i, count = 0; + char *p, *p2, **env; + size_t env_size; + + if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) + return NULL; + + for (p = script.buf; *p; p++) + if (skip_prefix(p, "'\\\\''", (const char **)&p2)) + strbuf_splice(&script, p - script.buf, p2 - p, "'", 1); + else if (*p == '\'') + strbuf_splice(&script, p-- - script.buf, 1, "", 0); + else if (*p == '\n') { + *p = '\0'; + count++; + } + + env_size = (count + 1) * sizeof(*env); + strbuf_grow(&script, env_size); + memmove(script.buf + env_size, script.buf, script.len); + p = script.buf + env_size; + env = (char **)strbuf_detach(&script, NULL); + + for (i = 0; i < count; i++) { + env[i] = p; + p += strlen(p) + 1; + } + env[count] = NULL; + + return env; +} + +static const char staged_changes_advice[] = +N_("you have staged changes in your working tree\n" +"If these changes are meant to be squashed into the previous commit, run:\n" +"\n" +" git commit --amend %s\n" +"\n" +"If they are meant to go into a new commit, run:\n" +"\n" +" git commit %s\n" +"\n" +"In both cases, once you're done, continue with:\n" +"\n" +" git rebase --continue\n"); + +/* * If we are cherry-pick, and if the merge did not result in * hand-editing, we will hit this commit and inherit the original * author date and name. + * * If we are revert, or if our cherry-pick results in a hand merge, * we had better say that the current user is responsible for that. + * + * An exception is when run_git_commit() is called during an + * interactive rebase: in that case, we will want to retain the + * author metadata. */ static int run_git_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty) + int allow_empty, int edit, int amend, + int cleanup_commit_message) { + char **env = NULL; struct argv_array array; int rc; const char *value; + if (is_rebase_i(opts)) { + env = read_author_script(); + if (!env) { + const char *gpg_opt = gpg_sign_opt_quoted(opts); + + return error(_(staged_changes_advice), + gpg_opt, gpg_opt); + } + } + argv_array_init(&array); argv_array_push(&array, "commit"); argv_array_push(&array, "-n"); + if (amend) + argv_array_push(&array, "--amend"); if (opts->gpg_sign) argv_array_pushf(&array, "-S%s", opts->gpg_sign); if (opts->signoff) argv_array_push(&array, "-s"); - if (!opts->edit) { - argv_array_push(&array, "-F"); - argv_array_push(&array, defmsg); - if (!opts->signoff && - !opts->record_origin && - git_config_get_value("commit.cleanup", &value)) - argv_array_push(&array, "--cleanup=verbatim"); - } + if (defmsg) + argv_array_pushl(&array, "-F", defmsg, NULL); + if (cleanup_commit_message) + argv_array_push(&array, "--cleanup=strip"); + if (edit) + argv_array_push(&array, "-e"); + else if (!cleanup_commit_message && + !opts->signoff && !opts->record_origin && + git_config_get_value("commit.cleanup", &value)) + argv_array_push(&array, "--cleanup=verbatim"); if (allow_empty) argv_array_push(&array, "--allow-empty"); @@ -384,8 +551,11 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, if (opts->allow_empty_message) argv_array_push(&array, "--allow-empty-message"); - rc = run_command_v_opt(array.argv, RUN_GIT_CMD); + rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, + (const char *const *)env); argv_array_clear(&array); + free(env); + return rc; } @@ -394,12 +564,12 @@ static int is_original_commit_empty(struct commit *commit) const unsigned char *ptree_sha1; if (parse_commit(commit)) - return error(_("Could not parse commit %s\n"), + return error(_("could not parse commit %s\n"), oid_to_hex(&commit->object.oid)); if (commit->parents) { struct commit *parent = commit->parents->item; if (parse_commit(parent)) - return error(_("Could not parse parent commit %s\n"), + return error(_("could not parse parent commit %s\n"), oid_to_hex(&parent->object.oid)); ptree_sha1 = parent->tree->object.oid.hash; } else { @@ -447,7 +617,26 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } -static int do_pick_commit(struct commit *commit, struct replay_opts *opts) +enum todo_command { + TODO_PICK = 0, + TODO_REVERT +}; + +static const char *todo_command_strings[] = { + "pick", + "revert" +}; + +static const char *command_to_string(const enum todo_command command) +{ + if (command < ARRAY_SIZE(todo_command_strings)) + return todo_command_strings[command]; + die("Unknown command: %d", command); +} + + +static int do_pick_commit(enum todo_command command, struct commit *commit, + struct replay_opts *opts) { unsigned char head[20]; struct commit *base, *next, *parent; @@ -464,12 +653,12 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * to work on. */ if (write_cache_as_tree(head, 0, NULL)) - return error(_("Your index file is unmerged.")); + return error(_("your index file is unmerged.")); } else { unborn = get_sha1("HEAD", head); if (unborn) hashcpy(head, EMPTY_TREE_SHA1_BIN); - if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0)) + if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0)) return error_dirty_index(opts); } discard_cache(); @@ -483,7 +672,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) struct commit_list *p; if (!opts->mainline) - return error(_("Commit %s is a merge but no -m option was given."), + return error(_("commit %s is a merge but no -m option was given."), oid_to_hex(&commit->object.oid)); for (cnt = 1, p = commit->parents; @@ -491,11 +680,11 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) cnt++) p = p->next; if (cnt != opts->mainline || !p) - return error(_("Commit %s does not have parent %d"), + return error(_("commit %s does not have parent %d"), oid_to_hex(&commit->object.oid), opts->mainline); parent = p->item; } else if (0 < opts->mainline) - return error(_("Mainline was specified but commit %s is not a merge."), + return error(_("mainline was specified but commit %s is not a merge."), oid_to_hex(&commit->object.oid)); else parent = commit->parents->item; @@ -506,13 +695,14 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) return fast_forward_to(commit->object.oid.hash, head, unborn, opts); if (parent && parse_commit(parent) < 0) - /* TRANSLATORS: The first %s will be "revert" or - "cherry-pick", the second %s a SHA1 */ + /* TRANSLATORS: The first %s will be a "todo" command like + "revert" or "pick", the second %s a SHA1. */ return error(_("%s: cannot parse parent commit %s"), - action_name(opts), oid_to_hex(&parent->object.oid)); + command_to_string(command), + oid_to_hex(&parent->object.oid)); if (get_message(commit, &msg) != 0) - return error(_("Cannot get commit message for %s"), + return error(_("cannot get commit message for %s"), oid_to_hex(&commit->object.oid)); /* @@ -522,7 +712,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * reverse of it if we are revert. */ - if (opts->action == REPLAY_REVERT) { + if (command == TODO_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -563,25 +753,29 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) return res; - res |= write_message(&msgbuf, git_path_merge_msg()); + res |= write_message(msgbuf.buf, msgbuf.len, + git_path_merge_msg(), 0); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; - res = write_message(&msgbuf, git_path_merge_msg()); + res = write_message(msgbuf.buf, msgbuf.len, + git_path_merge_msg(), 0); 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, (const char **)opts->xopts, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); } + strbuf_release(&msgbuf); /* * If the merge was clean or if it failed due to conflict, we write @@ -589,21 +783,20 @@ 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) && + if (command == TODO_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) && + if (command == TODO_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 + error(command == TODO_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), - find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), - msg.subject); + short_commit_name(commit), msg.subject); print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); goto leave; @@ -615,7 +808,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) goto leave; } if (!opts->no_commit) - res = run_git_commit(git_path_merge_msg(), opts, allow); + res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(), + opts, allow, opts->edit, 0, 0); leave: free_message(commit, &msg); @@ -647,133 +841,160 @@ static int read_and_refresh_cache(struct replay_opts *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)); + _(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)) { rollback_lock_file(&index_lock); return error(_("git %s: failed to refresh the index"), - action_name(opts)); + _(action_name(opts))); } } rollback_lock_file(&index_lock); return 0; } -static int format_todo(struct strbuf *buf, struct commit_list *todo_list, - struct replay_opts *opts) +struct todo_item { + enum todo_command command; + struct commit *commit; + const char *arg; + int arg_len; + size_t offset_in_buf; +}; + +struct todo_list { + struct strbuf buf; + struct todo_item *items; + int nr, alloc, current; +}; + +#define TODO_LIST_INIT { STRBUF_INIT } + +static void todo_list_release(struct todo_list *todo_list) { - struct commit_list *cur = NULL; - const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; - const char *subject; - int subject_len; + strbuf_release(&todo_list->buf); + free(todo_list->items); + todo_list->items = NULL; + todo_list->nr = todo_list->alloc = 0; +} - for (cur = todo_list; cur; cur = cur->next) { - const char *commit_buffer = get_commit_buffer(cur->item, NULL); - sha1_abbrev = find_unique_abbrev(cur->item->object.oid.hash, DEFAULT_ABBREV); - subject_len = find_commit_subject(commit_buffer, &subject); - strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, - subject_len, subject); - unuse_commit_buffer(cur->item, commit_buffer); - } - return 0; +static struct todo_item *append_new_todo(struct todo_list *todo_list) +{ + ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc); + return todo_list->items + todo_list->nr++; } -static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts) +static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) { unsigned char commit_sha1[20]; - enum replay_action action; char *end_of_object_name; - int saved, status, padding; - - if (starts_with(bol, "pick")) { - action = REPLAY_PICK; - bol += strlen("pick"); - } else if (starts_with(bol, "revert")) { - action = REPLAY_REVERT; - bol += strlen("revert"); - } else - return NULL; + int i, saved, status, padding; + + /* left-trim */ + bol += strspn(bol, " \t"); + + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) + if (skip_prefix(bol, todo_command_strings[i], &bol)) { + item->command = i; + break; + } + if (i >= ARRAY_SIZE(todo_command_strings)) + return -1; /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) - return NULL; + return -1; bol += padding; - end_of_object_name = bol + strcspn(bol, " \t\n"); + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; status = get_sha1(bol, commit_sha1); *end_of_object_name = saved; - /* - * Verify that the action matches up with the one in - * opts; we don't support arbitrary instructions - */ - if (action != opts->action) { - if (action == REPLAY_REVERT) - error((opts->action == REPLAY_REVERT) - ? _("Cannot revert during another revert.") - : _("Cannot revert during a cherry-pick.")); - else - error((opts->action == REPLAY_REVERT) - ? _("Cannot cherry-pick during a revert.") - : _("Cannot cherry-pick during another cherry-pick.")); - return NULL; - } + item->arg = end_of_object_name + strspn(end_of_object_name, " \t"); + item->arg_len = (int)(eol - item->arg); if (status < 0) - return NULL; + return -1; - return lookup_commit_reference(commit_sha1); + item->commit = lookup_commit_reference(commit_sha1); + return !item->commit; } -static int parse_insn_buffer(char *buf, struct commit_list **todo_list, - struct replay_opts *opts) +static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { - struct commit_list **next = todo_list; - struct commit *commit; - char *p = buf; - int i; + struct todo_item *item; + char *p = buf, *next_p; + int i, res = 0; - for (i = 1; *p; i++) { + for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); - commit = parse_insn_line(p, eol, opts); - if (!commit) - return error(_("Could not parse line %d."), i); - next = commit_list_append(commit, next); - p = *eol ? eol + 1 : eol; + + next_p = *eol ? eol + 1 /* skip LF */ : eol; + + if (p != eol && eol[-1] == '\r') + eol--; /* strip Carriage Return */ + + item = append_new_todo(todo_list); + item->offset_in_buf = p - todo_list->buf.buf; + if (parse_insn_line(item, p, eol)) { + res = error(_("invalid line %d: %.*s"), + i, (int)(eol - p), p); + item->command = -1; + } } - if (!*todo_list) - return error(_("No commits parsed.")); - return 0; + if (!todo_list->nr) + return error(_("no commits parsed.")); + return res; } -static int read_populate_todo(struct commit_list **todo_list, +static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { - struct strbuf buf = STRBUF_INIT; + const char *todo_file = get_todo_path(opts); int fd, res; - fd = open(git_path_todo_file(), O_RDONLY); + strbuf_reset(&todo_list->buf); + fd = open(todo_file, O_RDONLY); if (fd < 0) - return error_errno(_("Could not open %s"), - git_path_todo_file()); - if (strbuf_read(&buf, fd, 0) < 0) { + return error_errno(_("could not open '%s'"), todo_file); + if (strbuf_read(&todo_list->buf, fd, 0) < 0) { close(fd); - strbuf_release(&buf); - return error(_("Could not read %s."), git_path_todo_file()); + return error(_("could not read '%s'."), todo_file); } close(fd); - res = parse_insn_buffer(buf.buf, todo_list, opts); - strbuf_release(&buf); + res = parse_insn_buffer(todo_list->buf.buf, todo_list); if (res) - return error(_("Unusable instruction sheet: %s"), - git_path_todo_file()); + return error(_("unusable instruction sheet: '%s'"), todo_file); + + if (!is_rebase_i(opts)) { + enum todo_command valid = + opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; + int i; + + for (i = 0; i < todo_list->nr; i++) + if (valid == todo_list->items[i].command) + continue; + else if (valid == TODO_PICK) + return error(_("cannot cherry-pick during a revert.")); + else + return error(_("cannot revert during a cherry-pick.")); + } + + return 0; +} + +static int git_config_string_dup(char **dest, + const char *var, const char *value) +{ + if (!value) + return config_error_nonbool(var); + free(*dest); + *dest = xstrdup(value); return 0; } @@ -797,23 +1018,39 @@ static int populate_opts_cb(const char *key, const char *value, void *data) else if (!strcmp(key, "options.mainline")) opts->mainline = git_config_int(key, value); else if (!strcmp(key, "options.strategy")) - git_config_string(&opts->strategy, key, value); + git_config_string_dup(&opts->strategy, key, value); else if (!strcmp(key, "options.gpg-sign")) - git_config_string(&opts->gpg_sign, key, value); + git_config_string_dup(&opts->gpg_sign, key, value); else if (!strcmp(key, "options.strategy-option")) { ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); opts->xopts[opts->xopts_nr++] = xstrdup(value); } else - return error(_("Invalid key: %s"), key); + return error(_("invalid key: %s"), key); if (!error_flag) - return error(_("Invalid value for %s: %s"), key, value); + return error(_("invalid value for %s: %s"), key, value); return 0; } -static int read_populate_opts(struct replay_opts **opts) +static int read_populate_opts(struct replay_opts *opts) { + if (is_rebase_i(opts)) { + struct strbuf buf = STRBUF_INIT; + + if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) { + if (!starts_with(buf.buf, "-S")) + strbuf_reset(&buf); + else { + free(opts->gpg_sign); + opts->gpg_sign = xstrdup(buf.buf + 2); + } + } + strbuf_release(&buf); + + return 0; + } + if (!file_exists(git_path_opts_file())) return 0; /* @@ -822,24 +1059,39 @@ static int read_populate_opts(struct replay_opts **opts) * 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"), + 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 int walk_revs_populate_todo(struct commit_list **todo_list, +static int walk_revs_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { + enum todo_command command = opts->action == REPLAY_PICK ? + TODO_PICK : TODO_REVERT; + const char *command_string = todo_command_strings[command]; struct commit *commit; - struct commit_list **next; if (prepare_revs(opts)) return -1; - next = todo_list; - while ((commit = get_revision(opts->revs))) - next = commit_list_append(commit, next); + while ((commit = get_revision(opts->revs))) { + struct todo_item *item = append_new_todo(todo_list); + const char *commit_buffer = get_commit_buffer(commit, NULL); + const char *subject; + int subject_len; + + item->command = command; + item->commit = commit; + item->arg = NULL; + item->arg_len = 0; + item->offset_in_buf = todo_list->buf.len; + subject_len = find_commit_subject(commit_buffer, &subject); + strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, + short_commit_name(commit), subject_len, subject); + unuse_commit_buffer(commit, commit_buffer); + } return 0; } @@ -851,7 +1103,7 @@ static int create_seq_dir(void) return -1; } else if (mkdir(git_path_seq_dir(), 0777) < 0) - return error_errno(_("Could not create sequencer directory %s"), + return error_errno(_("could not create sequencer directory '%s'"), git_path_seq_dir()); return 0; } @@ -865,17 +1117,17 @@ static int save_head(const char *head) 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")); + return error_errno(_("could not lock HEAD")); } strbuf_addf(&buf, "%s\n", head); if (write_in_full(fd, buf.buf, buf.len) < 0) { rollback_lock_file(&head_lock); - return error_errno(_("Could not write to %s"), + 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 error(_("failed to finalize '%s'."), git_path_head_file()); } return 0; } @@ -904,7 +1156,7 @@ static int rollback_single_pick(void) return reset_for_rollback(head_sha1); } -static int sequencer_rollback(struct replay_opts *opts) +int sequencer_rollback(struct replay_opts *opts) { FILE *f; unsigned char sha1[20]; @@ -920,9 +1172,9 @@ static int sequencer_rollback(struct replay_opts *opts) return rollback_single_pick(); } if (!f) - return error_errno(_("cannot open %s"), git_path_head_file()); + return error_errno(_("cannot open '%s'"), git_path_head_file()); if (strbuf_getline_lf(&buf, f)) { - error(_("cannot read %s: %s"), git_path_head_file(), + error(_("cannot read '%s': %s"), git_path_head_file(), ferror(f) ? strerror(errno) : _("unexpected end of file")); fclose(f); goto fail; @@ -939,38 +1191,29 @@ static int sequencer_rollback(struct replay_opts *opts) } if (reset_for_rollback(sha1)) goto fail; - remove_sequencer_state(); strbuf_release(&buf); - return 0; + return sequencer_remove_state(opts); fail: strbuf_release(&buf); return -1; } -static int save_todo(struct commit_list *todo_list, struct replay_opts *opts) +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) { static struct lock_file todo_lock; - struct strbuf buf = STRBUF_INIT; - int fd; + const char *todo_path = get_todo_path(opts); + int next = todo_list->current, offset, fd; - fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), 0); + fd = hold_lock_file_for_update(&todo_lock, todo_path, 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); - return error_errno(_("Could not write to %s"), - git_path_todo_file()); - } - if (commit_lock_file(&todo_lock) < 0) { - strbuf_release(&buf); - return error(_("Error wrapping up %s."), git_path_todo_file()); - } - strbuf_release(&buf); + return error_errno(_("could not lock '%s'"), todo_path); + offset = next < todo_list->nr ? + todo_list->items[next].offset_in_buf : todo_list->buf.len; + if (write_in_full(fd, todo_list->buf.buf + offset, + todo_list->buf.len - offset) < 0) + return error_errno(_("could not write to '%s'"), todo_path); + if (commit_lock_file(&todo_lock) < 0) + return error(_("failed to finalize '%s'."), todo_path); return 0; } @@ -1009,9 +1252,8 @@ static int save_opts(struct replay_opts *opts) return res; } -static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) +static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - struct commit_list *cur; int res; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); @@ -1021,10 +1263,12 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; - for (cur = todo_list; cur; cur = cur->next) { - if (save_todo(cur, opts)) + while (todo_list->current < todo_list->nr) { + struct todo_item *item = todo_list->items + todo_list->current; + if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(cur->item, opts); + res = do_pick_commit(item->command, item->commit, opts); + todo_list->current++; if (res) return res; } @@ -1033,8 +1277,7 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory */ - remove_sequencer_state(); - return 0; + return sequencer_remove_state(opts); } static int continue_single_pick(void) @@ -1047,61 +1290,56 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } -static int sequencer_continue(struct replay_opts *opts) +int sequencer_continue(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct todo_list todo_list = TODO_LIST_INIT; + int res; - if (!file_exists(git_path_todo_file())) + if (read_and_refresh_cache(opts)) + return -1; + + if (!file_exists(get_todo_path(opts))) return continue_single_pick(); - if (read_populate_opts(&opts) || - read_populate_todo(&todo_list, opts)) + if (read_populate_opts(opts)) return -1; + if ((res = read_populate_todo(&todo_list, opts))) + goto release_todo_list; /* Verify that the conflict has been resolved */ if (file_exists(git_path_cherry_pick_head()) || file_exists(git_path_revert_head())) { - int ret = continue_single_pick(); - if (ret) - return ret; + res = continue_single_pick(); + if (res) + goto release_todo_list; } - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - todo_list = todo_list->next; - return pick_commits(todo_list, opts); + if (index_differs_from("HEAD", 0, 0)) { + res = error_dirty_index(opts); + goto release_todo_list; + } + todo_list.current++; + res = pick_commits(&todo_list, opts); +release_todo_list: + todo_list_release(&todo_list); + return res; } static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); - return do_pick_commit(cmit, opts); + return do_pick_commit(opts->action == REPLAY_PICK ? + TODO_PICK : TODO_REVERT, cmit, opts); } int sequencer_pick_revisions(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct todo_list todo_list = TODO_LIST_INIT; unsigned char sha1[20]; - int i; - - if (opts->subcommand == REPLAY_NONE) - assert(opts->revs); + int i, res; + assert(opts->revs); if (read_and_refresh_cache(opts)) return -1; - /* - * Decide what to do depending on the arguments; a fresh - * cherry-pick should be handled differently from an existing - * one that is being continued - */ - if (opts->subcommand == REPLAY_REMOVE_STATE) { - remove_sequencer_state(); - return 0; - } - if (opts->subcommand == REPLAY_ROLLBACK) - return sequencer_rollback(opts); - if (opts->subcommand == REPLAY_CONTINUE) - return sequencer_continue(opts); - for (i = 0; i < opts->revs->pending.nr; i++) { unsigned char sha1[20]; const char *name = opts->revs->pending.objects[i].name; @@ -1150,12 +1388,14 @@ int sequencer_pick_revisions(struct replay_opts *opts) create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT)) - return error(_("Can't revert as initial commit")); + return error(_("can't revert as initial commit")); if (save_head(sha1_to_hex(sha1))) return -1; if (save_opts(opts)) return -1; - return pick_commits(todo_list, opts); + res = pick_commits(&todo_list, opts); + todo_list_release(&todo_list); + return res; } void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) diff --git a/sequencer.h b/sequencer.h index 5ed5cb1d97..7a513c576b 100644 --- a/sequencer.h +++ b/sequencer.h @@ -1,10 +1,7 @@ #ifndef SEQUENCER_H #define SEQUENCER_H -#define SEQ_DIR "sequencer" -#define SEQ_HEAD_FILE "sequencer/head" -#define SEQ_TODO_FILE "sequencer/todo" -#define SEQ_OPTS_FILE "sequencer/opts" +const char *git_path_seq_dir(void); #define APPEND_SIGNOFF_DEDUP (1u << 0) @@ -13,16 +10,8 @@ enum replay_action { REPLAY_PICK }; -enum replay_subcommand { - REPLAY_NONE, - REPLAY_REMOVE_STATE, - REPLAY_CONTINUE, - REPLAY_ROLLBACK -}; - struct replay_opts { enum replay_action action; - enum replay_subcommand subcommand; /* Boolean options */ int edit; @@ -37,18 +26,22 @@ struct replay_opts { int mainline; - const char *gpg_sign; + char *gpg_sign; /* Merge strategy */ - const char *strategy; - const char **xopts; + char *strategy; + char **xopts; size_t xopts_nr, xopts_alloc; /* Only used by REPLAY_NONE */ struct rev_info *revs; }; +#define REPLAY_OPTS_INIT { -1 } int sequencer_pick_revisions(struct replay_opts *opts); +int sequencer_continue(struct replay_opts *opts); +int sequencer_rollback(struct replay_opts *opts); +int sequencer_remove_state(struct replay_opts *opts); extern const char sign_off_header[]; diff --git a/sha1_file.c b/sha1_file.c index 266152de36..5457314e6a 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -370,7 +370,7 @@ void read_info_alternates(const char * relative_base, int depth) int fd; path = xstrfmt("%s/info/alternates", relative_base); - fd = git_open_noatime(path); + fd = git_open(path); free(path); if (fd < 0) return; @@ -663,7 +663,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) struct pack_idx_header *hdr; size_t idx_size; uint32_t version, nr, i, *index; - int fd = git_open_noatime(path); + int fd = git_open(path); struct stat st; if (fd < 0) @@ -1069,7 +1069,7 @@ static int open_packed_git_1(struct packed_git *p) while (pack_max_fds <= pack_open_fds && close_one_pack()) ; /* nothing */ - p->pack_fd = git_open_noatime(p->pack_name); + p->pack_fd = git_open(p->pack_name); if (p->pack_fd < 0 || fstat(p->pack_fd, &st)) return -1; pack_open_fds++; @@ -1410,6 +1410,32 @@ static void prepare_packed_git_one(char *objdir, int local) strbuf_release(&path); } +static int approximate_object_count_valid; + +/* + * Give a fast, rough count of the number of objects in the repository. This + * ignores loose objects completely. If you have a lot of them, then either + * you should repack because your performance will be awful, or they are + * all unreachable objects about to be pruned, in which case they're not really + * interesting as a measure of repo size in the first place. + */ +unsigned long approximate_object_count(void) +{ + static unsigned long count; + if (!approximate_object_count_valid) { + struct packed_git *p; + + prepare_packed_git(); + count = 0; + for (p = packed_git; p; p = p->next) { + if (open_pack_index(p)) + continue; + count += p->num_objects; + } + } + return count; +} + static void *get_next_packed_git(const void *p) { return ((const struct packed_git *)p)->next; @@ -1481,6 +1507,7 @@ void prepare_packed_git(void) void reprepare_packed_git(void) { + approximate_object_count_valid = 0; prepare_packed_git_run_once = 0; prepare_packed_git(); } @@ -1559,9 +1586,9 @@ int check_sha1_signature(const unsigned char *sha1, void *map, return hashcmp(sha1, real_sha1) ? -1 : 0; } -int git_open_noatime(const char *name) +int git_open(const char *name) { - static int sha1_file_open_flag = O_NOATIME; + static int sha1_file_open_flag = O_NOATIME | O_CLOEXEC; for (;;) { int fd; @@ -1571,12 +1598,17 @@ int git_open_noatime(const char *name) if (fd >= 0) return fd; - /* Might the failure be due to O_NOATIME? */ - if (errno != ENOENT && sha1_file_open_flag) { - sha1_file_open_flag = 0; + /* Try again w/o O_CLOEXEC: the kernel might not support it */ + if ((sha1_file_open_flag & O_CLOEXEC) && errno == EINVAL) { + sha1_file_open_flag &= ~O_CLOEXEC; continue; } + /* Might the failure be due to O_NOATIME? */ + if (errno != ENOENT && (sha1_file_open_flag & O_NOATIME)) { + sha1_file_open_flag &= ~O_NOATIME; + continue; + } return -1; } } @@ -1605,7 +1637,7 @@ static int open_sha1_file(const unsigned char *sha1) struct alternate_object_database *alt; int most_interesting_errno; - fd = git_open_noatime(sha1_file_name(sha1)); + fd = git_open(sha1_file_name(sha1)); if (fd >= 0) return fd; most_interesting_errno = errno; @@ -1613,7 +1645,7 @@ static int open_sha1_file(const unsigned char *sha1) prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { const char *path = alt_sha1_path(alt, sha1); - fd = git_open_noatime(path); + fd = git_open(path); if (fd >= 0) return fd; if (most_interesting_errno == ENOENT) @@ -3335,6 +3367,11 @@ int has_object_file(const struct object_id *oid) return has_sha1_file(oid->hash); } +int has_object_file_with_flags(const struct object_id *oid, int flags) +{ + return has_sha1_file_with_flags(oid->hash, flags); +} + static void check_tree(const void *buf, size_t size) { struct tree_desc desc; diff --git a/sha1_name.c b/sha1_name.c index 4092836146..06409a3845 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -448,10 +448,46 @@ int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data) return ret; } +/* + * Return the slot of the most-significant bit set in "val". There are various + * ways to do this quickly with fls() or __builtin_clzl(), but speed is + * probably not a big deal here. + */ +static unsigned msb(unsigned long val) +{ + unsigned r = 0; + while (val >>= 1) + r++; + return r; +} + int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len) { int status, exists; + if (len < 0) { + unsigned long count = approximate_object_count(); + /* + * Add one because the MSB only tells us the highest bit set, + * not including the value of all the _other_ bits (so "15" + * is only one off of 2^4, but the MSB is the 3rd bit. + */ + len = msb(count) + 1; + /* + * We now know we have on the order of 2^len objects, which + * expects a collision at 2^(len/2). But we also care about hex + * chars, not bits, and there are 4 bits per hex. So all + * together we need to divide by 2; but we also want to round + * odd numbers up, hence adding one before dividing. + */ + len = (len + 1) / 2; + /* + * For very small repos, we stick with our regular fallback. + */ + if (len < FALLBACK_DEFAULT_ABBREV) + len = FALLBACK_DEFAULT_ABBREV; + } + sha1_to_hex_r(hex, sha1); if (len == 40 || !len) return 40; @@ -472,7 +508,9 @@ int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len) const char *find_unique_abbrev(const unsigned char *sha1, int len) { - static char hex[GIT_SHA1_HEXSZ + 1]; + static int bufno; + static char hexbuffer[4][GIT_SHA1_HEXSZ + 1]; + char *hex = hexbuffer[3 & ++bufno]; find_unique_abbrev_r(hex, sha1, len); return hex; } @@ -338,7 +338,7 @@ static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *c { int fd = *(int *)cb; if (graft->nr_parent == -1) - packet_write(fd, "shallow %s\n", oid_to_hex(&graft->oid)); + packet_write_fmt(fd, "shallow %s\n", oid_to_hex(&graft->oid)); return 0; } @@ -153,6 +153,12 @@ appropriately before running "make". As the names depend on the tests' file names, it is safe to run the tests with this option in parallel. +--verbose-log:: + Write verbose output to the same logfile as `--tee`, but do + _not_ write it to stdout. Unlike `--tee --verbose`, this option + is safe to use when stdout is being consumed by a TAP parser + like `prove`. Implies `--tee` and `--verbose`. + --with-dashes:: By default tests are run without dashed forms of commands (like git-commit) in the PATH (it only uses diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index 44f3290258..7af116d49e 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c @@ -58,6 +58,7 @@ int cmd_main(int ac, const char **av) { struct index_state istate; struct cache_tree *another = cache_tree(); + setup_git_directory(); if (read_cache() < 0) die("unable to read index file"); istate = the_index; diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 5b2fd09908..27fe0405b8 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -7,6 +7,7 @@ static struct lock_file index_lock; int cmd_main(int ac, const char **av) { + setup_git_directory(); hold_locked_index(&index_lock, 1); if (read_cache() < 0) die("unable to read index file"); diff --git a/t/perf/p5550-fetch-tags.sh b/t/perf/p5550-fetch-tags.sh new file mode 100755 index 0000000000..a5dc39f86a --- /dev/null +++ b/t/perf/p5550-fetch-tags.sh @@ -0,0 +1,99 @@ +#!/bin/sh + +test_description='performance of tag-following with many tags + +This tests a fairly pathological case, so rather than rely on a real-world +case, we will construct our own repository. The situation is roughly as +follows. + +The parent repository has a large number of tags which are disconnected from +the rest of history. That makes them candidates for tag-following, but we never +actually grab them (and thus they will impact each subsequent fetch). + +The child repository is a clone of parent, without the tags, and is at least +one commit behind the parent (meaning that we will fetch one object and then +examine the tags to see if they need followed). Furthermore, it has a large +number of packs. + +The exact values of "large" here are somewhat arbitrary; I picked values that +start to show a noticeable performance problem on my machine, but without +taking too long to set up and run the tests. +' +. ./perf-lib.sh + +# make a long nonsense history on branch $1, consisting of $2 commits, each +# with a unique file pointing to the blob at $2. +create_history () { + perl -le ' + my ($branch, $n, $blob) = @ARGV; + for (1..$n) { + print "commit refs/heads/$branch"; + print "committer nobody <nobody@example.com> now"; + print "data 4"; + print "foo"; + print "M 100644 $blob $_"; + } + ' "$@" | + git fast-import --date-format=now +} + +# make a series of tags, one per commit in the revision range given by $@ +create_tags () { + git rev-list "$@" | + perl -lne 'print "create refs/tags/$. $_"' | + git update-ref --stdin +} + +# create $1 nonsense packs, each with a single blob +create_packs () { + perl -le ' + my ($n) = @ARGV; + for (1..$n) { + print "blob"; + print "data <<EOF"; + print "$_"; + print "EOF"; + } + ' "$@" | + git fast-import && + + git cat-file --batch-all-objects --batch-check='%(objectname)' | + while read sha1 + do + echo $sha1 | git pack-objects .git/objects/pack/pack + done +} + +test_expect_success 'create parent and child' ' + git init parent && + git -C parent commit --allow-empty -m base && + git clone parent child && + git -C parent commit --allow-empty -m trigger-fetch +' + +test_expect_success 'populate parent tags' ' + ( + cd parent && + blob=$(echo content | git hash-object -w --stdin) && + create_history cruft 3000 $blob && + create_tags cruft && + git branch -D cruft + ) +' + +test_expect_success 'create child packs' ' + ( + cd child && + git config gc.auto 0 && + git config gc.autopacklimit 0 && + create_packs 500 + ) +' + +test_perf 'fetch' ' + # make sure there is something to fetch on each iteration + git -C child update-ref -d refs/remotes/origin/master && + git -C child fetch +' + +test_done diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index f94120a894..71350e0657 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -83,7 +83,11 @@ test_expect_success 'safecrlf: print warning only once' ' git add doublewarn && git commit -m "nowarn" && for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >doublewarn && - test $(git add doublewarn 2>&1 | grep "CRLF will be replaced by LF" | wc -l) = 1 + git add doublewarn 2>err && + if test_have_prereq C_LOCALE_OUTPUT + then + test $(grep "CRLF will be replaced by LF" err | wc -l) = 1 + fi ' diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index e799e59544..a20b9f58e3 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -4,13 +4,72 @@ test_description='blob conversion via gitattributes' . ./test-lib.sh -cat <<EOF >rot13.sh +TEST_ROOT="$(pwd)" + +cat <<EOF >"$TEST_ROOT/rot13.sh" #!$SHELL_PATH tr \ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ 'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM' EOF -chmod +x rot13.sh +chmod +x "$TEST_ROOT/rot13.sh" + +generate_random_characters () { + LEN=$1 + NAME=$2 + test-genrandom some-seed $LEN | + perl -pe "s/./chr((ord($&) % 26) + ord('a'))/sge" >"$TEST_ROOT/$NAME" +} + +file_size () { + cat "$1" | wc -c | sed "s/^[ ]*//" +} + +filter_git () { + rm -f rot13-filter.log && + git "$@" 2>git-stderr.log && + rm -f git-stderr.log +} + +# Compare two files and ensure that `clean` and `smudge` respectively are +# called at least once if specified in the `expect` file. The actual +# invocation count is not relevant because their number can vary. +# c.f. http://public-inbox.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/ +test_cmp_count () { + expect=$1 + actual=$2 + for FILE in "$expect" "$actual" + do + sort "$FILE" | uniq -c | sed "s/^[ ]*//" | + sed "s/^\([0-9]\) IN: clean/x IN: clean/" | + sed "s/^\([0-9]\) IN: smudge/x IN: smudge/" >"$FILE.tmp" && + mv "$FILE.tmp" "$FILE" + done && + test_cmp "$expect" "$actual" +} + +# Compare two files but exclude all `clean` invocations because Git can +# call `clean` zero or more times. +# c.f. http://public-inbox.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/ +test_cmp_exclude_clean () { + expect=$1 + actual=$2 + for FILE in "$expect" "$actual" + do + grep -v "IN: clean" "$FILE" >"$FILE.tmp" && + mv "$FILE.tmp" "$FILE" + done && + test_cmp "$expect" "$actual" +} + +# Check that the contents of two files are equal and that their rot13 version +# is equal to the committed content. +test_cmp_committed_rot13 () { + test_cmp "$1" "$2" && + "$TEST_ROOT/rot13.sh" <"$1" >expected && + git cat-file blob :"$2" >actual && + test_cmp expected actual +} test_expect_success setup ' git config filter.rot13.smudge ./rot13.sh && @@ -31,15 +90,18 @@ test_expect_success setup ' cat test >test.i && git add test test.t test.i && rm -f test test.t test.i && - git checkout -- test test.t test.i + git checkout -- test test.t test.i && + + echo "content-test2" >test2.o && + echo "content-test3 - filename with special characters" >"test3 '\''sq'\'',\$x.o" ' script='s/^\$Id: \([0-9a-f]*\) \$/\1/p' test_expect_success check ' - cmp test.o test && - cmp test.o test.t && + test_cmp test.o test && + test_cmp test.o test.t && # ident should be stripped in the repository git diff --raw --exit-code :test :test.i && @@ -47,10 +109,10 @@ test_expect_success check ' embedded=$(sed -ne "$script" test.i) && test "z$id" = "z$embedded" && - git cat-file blob :test.t > test.r && + git cat-file blob :test.t >test.r && - ./rot13.sh < test.o > test.t && - cmp test.r test.t + ./rot13.sh <test.o >test.t && + test_cmp test.r test.t ' # If an expanded ident ever gets into the repository, we want to make sure that @@ -130,7 +192,7 @@ test_expect_success 'filter shell-escaped filenames' ' # delete the files and check them out again, using a smudge filter # that will count the args and echo the command-line back to us - git config filter.argc.smudge "sh ./argc.sh %f" && + test_config filter.argc.smudge "sh ./argc.sh %f" && rm "$normal" "$special" && git checkout -- "$normal" "$special" && @@ -141,7 +203,7 @@ test_expect_success 'filter shell-escaped filenames' ' test_cmp expect "$special" && # do the same thing, but with more args in the filter expression - git config filter.argc.smudge "sh ./argc.sh %f --my-extra-arg" && + test_config filter.argc.smudge "sh ./argc.sh %f --my-extra-arg" && rm "$normal" "$special" && git checkout -- "$normal" "$special" && @@ -154,9 +216,9 @@ test_expect_success 'filter shell-escaped filenames' ' ' test_expect_success 'required filter should filter data' ' - git config filter.required.smudge ./rot13.sh && - git config filter.required.clean ./rot13.sh && - git config filter.required.required true && + test_config filter.required.smudge ./rot13.sh && + test_config filter.required.clean ./rot13.sh && + test_config filter.required.required true && echo "*.r filter=required" >.gitattributes && @@ -165,17 +227,17 @@ test_expect_success 'required filter should filter data' ' rm -f test.r && git checkout -- test.r && - cmp test.o test.r && + test_cmp test.o test.r && ./rot13.sh <test.o >expected && git cat-file blob :test.r >actual && - cmp expected actual + test_cmp expected actual ' test_expect_success 'required filter smudge failure' ' - git config filter.failsmudge.smudge false && - git config filter.failsmudge.clean cat && - git config filter.failsmudge.required true && + test_config filter.failsmudge.smudge false && + test_config filter.failsmudge.clean cat && + test_config filter.failsmudge.required true && echo "*.fs filter=failsmudge" >.gitattributes && @@ -186,9 +248,9 @@ test_expect_success 'required filter smudge failure' ' ' test_expect_success 'required filter clean failure' ' - git config filter.failclean.smudge cat && - git config filter.failclean.clean false && - git config filter.failclean.required true && + test_config filter.failclean.smudge cat && + test_config filter.failclean.clean false && + test_config filter.failclean.required true && echo "*.fc filter=failclean" >.gitattributes && @@ -197,8 +259,8 @@ test_expect_success 'required filter clean failure' ' ' test_expect_success 'filtering large input to small output should use little memory' ' - git config filter.devnull.clean "cat >/dev/null" && - git config filter.devnull.required true && + test_config filter.devnull.clean "cat >/dev/null" && + test_config filter.devnull.required true && for i in $(test_seq 1 30); do printf "%1048576d" 1; done >30MB && echo "30MB filter=devnull" >.gitattributes && GIT_MMAP_LIMIT=1m GIT_ALLOC_LIMIT=1m git add 30MB @@ -207,7 +269,7 @@ test_expect_success 'filtering large input to small output should use little mem test_expect_success 'filter that does not read is fine' ' test-genrandom foo $((128 * 1024 + 1)) >big && echo "big filter=epipe" >.gitattributes && - git config filter.epipe.clean "echo xyzzy" && + test_config filter.epipe.clean "echo xyzzy" && git add big && git cat-file blob :big >actual && echo xyzzy >expect && @@ -215,20 +277,20 @@ test_expect_success 'filter that does not read is fine' ' ' test_expect_success EXPENSIVE 'filter large file' ' - git config filter.largefile.smudge cat && - git config filter.largefile.clean cat && + test_config filter.largefile.smudge cat && + test_config filter.largefile.clean cat && for i in $(test_seq 1 2048); do printf "%1048576d" 1; done >2GB && echo "2GB filter=largefile" >.gitattributes && git add 2GB 2>err && - ! test -s err && + test_must_be_empty err && rm -f 2GB && git checkout -- 2GB 2>err && - ! test -s err + test_must_be_empty err ' test_expect_success "filter: clean empty file" ' - git config filter.in-repo-header.clean "echo cleaned && cat" && - git config filter.in-repo-header.smudge "sed 1d" && + test_config filter.in-repo-header.clean "echo cleaned && cat" && + test_config filter.in-repo-header.smudge "sed 1d" && echo "empty-in-worktree filter=in-repo-header" >>.gitattributes && >empty-in-worktree && @@ -240,8 +302,8 @@ test_expect_success "filter: clean empty file" ' ' test_expect_success "filter: smudge empty file" ' - git config filter.empty-in-repo.clean "cat >/dev/null" && - git config filter.empty-in-repo.smudge "echo smudged && cat" && + test_config filter.empty-in-repo.clean "cat >/dev/null" && + test_config filter.empty-in-repo.smudge "echo smudged && cat" && echo "empty-in-repo filter=empty-in-repo" >>.gitattributes && echo dead data walking >empty-in-repo && @@ -279,4 +341,380 @@ test_expect_success 'diff does not reuse worktree files that need cleaning' ' test_line_count = 0 count ' +test_expect_success PERL 'required process filter should filter data' ' + test_config_global filter.protocol.process "$TEST_DIRECTORY/t0021/rot13-filter.pl clean smudge" && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "git-stderr.log" >.gitignore && + echo "*.r filter=protocol" >.gitattributes && + git add . && + git commit . -m "test commit 1" && + git branch empty-branch && + + cp "$TEST_ROOT/test.o" test.r && + cp "$TEST_ROOT/test2.o" test2.r && + mkdir testsubdir && + cp "$TEST_ROOT/test3 '\''sq'\'',\$x.o" "testsubdir/test3 '\''sq'\'',\$x.r" && + >test4-empty.r && + + S=$(file_size test.r) && + S2=$(file_size test2.r) && + S3=$(file_size "testsubdir/test3 '\''sq'\'',\$x.r") && + + filter_git add . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + IN: clean test2.r $S2 [OK] -- OUT: $S2 . [OK] + IN: clean test4-empty.r 0 [OK] -- OUT: 0 [OK] + IN: clean testsubdir/test3 '\''sq'\'',\$x.r $S3 [OK] -- OUT: $S3 . [OK] + STOP + EOF + test_cmp_count expected.log rot13-filter.log && + + filter_git commit . -m "test commit 2" && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + IN: clean test2.r $S2 [OK] -- OUT: $S2 . [OK] + IN: clean test4-empty.r 0 [OK] -- OUT: 0 [OK] + IN: clean testsubdir/test3 '\''sq'\'',\$x.r $S3 [OK] -- OUT: $S3 . [OK] + IN: clean test.r $S [OK] -- OUT: $S . [OK] + IN: clean test2.r $S2 [OK] -- OUT: $S2 . [OK] + IN: clean test4-empty.r 0 [OK] -- OUT: 0 [OK] + IN: clean testsubdir/test3 '\''sq'\'',\$x.r $S3 [OK] -- OUT: $S3 . [OK] + STOP + EOF + test_cmp_count expected.log rot13-filter.log && + + rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x.r" && + + filter_git checkout --quiet --no-progress . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] + IN: smudge testsubdir/test3 '\''sq'\'',\$x.r $S3 [OK] -- OUT: $S3 . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log rot13-filter.log && + + filter_git checkout --quiet --no-progress empty-branch && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log rot13-filter.log && + + filter_git checkout --quiet --no-progress master && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge test.r $S [OK] -- OUT: $S . [OK] + IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] + IN: smudge test4-empty.r 0 [OK] -- OUT: 0 [OK] + IN: smudge testsubdir/test3 '\''sq'\'',\$x.r $S3 [OK] -- OUT: $S3 . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log rot13-filter.log && + + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && + test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && + test_cmp_committed_rot13 "$TEST_ROOT/test3 '\''sq'\'',\$x.o" "testsubdir/test3 '\''sq'\'',\$x.r" + ) +' + +test_expect_success PERL 'required process filter takes precedence' ' + test_config_global filter.protocol.clean false && + test_config_global filter.protocol.process "$TEST_DIRECTORY/t0021/rot13-filter.pl clean" && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + cp "$TEST_ROOT/test.o" test.r && + S=$(file_size test.r) && + + # Check that the process filter is invoked here + filter_git add . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + STOP + EOF + test_cmp_count expected.log rot13-filter.log + ) +' + +test_expect_success PERL 'required process filter should be used only for "clean" operation only' ' + test_config_global filter.protocol.process "$TEST_DIRECTORY/t0021/rot13-filter.pl clean" && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + cp "$TEST_ROOT/test.o" test.r && + S=$(file_size test.r) && + + filter_git add . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + STOP + EOF + test_cmp_count expected.log rot13-filter.log && + + rm test.r && + + filter_git checkout --quiet --no-progress . && + # If the filter would be used for "smudge", too, we would see + # "IN: smudge test.r 57 [OK] -- OUT: 57 . [OK]" here + cat >expected.log <<-EOF && + START + init handshake complete + STOP + EOF + test_cmp_exclude_clean expected.log rot13-filter.log + ) +' + +test_expect_success PERL 'required process filter should process multiple packets' ' + test_config_global filter.protocol.process "$TEST_DIRECTORY/t0021/rot13-filter.pl clean smudge" && + test_config_global filter.protocol.required true && + + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + # Generate data requiring 1, 2, 3 packets + S=65516 && # PKTLINE_DATA_MAXLEN -> Maximal size of a packet + generate_random_characters $(($S )) 1pkt_1__.file && + generate_random_characters $(($S +1)) 2pkt_1+1.file && + generate_random_characters $(($S*2-1)) 2pkt_2-1.file && + generate_random_characters $(($S*2 )) 2pkt_2__.file && + generate_random_characters $(($S*2+1)) 3pkt_2+1.file && + + for FILE in "$TEST_ROOT"/*.file + do + cp "$FILE" . && + "$TEST_ROOT/rot13.sh" <"$FILE" >"$FILE.rot13" + done && + + echo "*.file filter=protocol" >.gitattributes && + filter_git add *.file .gitattributes && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean 1pkt_1__.file $(($S )) [OK] -- OUT: $(($S )) . [OK] + IN: clean 2pkt_1+1.file $(($S +1)) [OK] -- OUT: $(($S +1)) .. [OK] + IN: clean 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK] + IN: clean 2pkt_2__.file $(($S*2 )) [OK] -- OUT: $(($S*2 )) .. [OK] + IN: clean 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK] + STOP + EOF + test_cmp_count expected.log rot13-filter.log && + + rm -f *.file && + + filter_git checkout --quiet --no-progress -- *.file && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge 1pkt_1__.file $(($S )) [OK] -- OUT: $(($S )) . [OK] + IN: smudge 2pkt_1+1.file $(($S +1)) [OK] -- OUT: $(($S +1)) .. [OK] + IN: smudge 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK] + IN: smudge 2pkt_2__.file $(($S*2 )) [OK] -- OUT: $(($S*2 )) .. [OK] + IN: smudge 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK] + STOP + EOF + test_cmp_exclude_clean expected.log rot13-filter.log && + + for FILE in *.file + do + test_cmp_committed_rot13 "$TEST_ROOT/$FILE" $FILE + done + ) +' + +test_expect_success PERL 'required process filter with clean error should fail' ' + test_config_global filter.protocol.process "$TEST_DIRECTORY/t0021/rot13-filter.pl clean smudge" && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + echo "this is going to fail" >clean-write-fail.r && + echo "content-test3-subdir" >test3.r && + + test_must_fail git add . + ) +' + +test_expect_success PERL 'process filter should restart after unexpected write failure' ' + test_config_global filter.protocol.process "$TEST_DIRECTORY/t0021/rot13-filter.pl clean smudge" && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + cp "$TEST_ROOT/test2.o" test2.r && + echo "this is going to fail" >smudge-write-fail.o && + cp smudge-write-fail.o smudge-write-fail.r && + + S=$(file_size test.r) && + S2=$(file_size test2.r) && + SF=$(file_size smudge-write-fail.r) && + + git add . && + rm -f *.r && + + rm -f rot13-filter.log && + git checkout --quiet --no-progress . 2>git-stderr.log && + + grep "smudge write error at" git-stderr.log && + grep "error: external filter" git-stderr.log && + + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge smudge-write-fail.r $SF [OK] -- OUT: $SF [WRITE FAIL] + START + init handshake complete + IN: smudge test.r $S [OK] -- OUT: $S . [OK] + IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log rot13-filter.log && + + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && + test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && + + # Smudge failed + ! test_cmp smudge-write-fail.o smudge-write-fail.r && + "$TEST_ROOT/rot13.sh" <smudge-write-fail.o >expected && + git cat-file blob :smudge-write-fail.r >actual && + test_cmp expected actual + ) +' + +test_expect_success PERL 'process filter should not be restarted if it signals an error' ' + test_config_global filter.protocol.process "$TEST_DIRECTORY/t0021/rot13-filter.pl clean smudge" && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + cp "$TEST_ROOT/test2.o" test2.r && + echo "this will cause an error" >error.o && + cp error.o error.r && + + S=$(file_size test.r) && + S2=$(file_size test2.r) && + SE=$(file_size error.r) && + + git add . && + rm -f *.r && + + filter_git checkout --quiet --no-progress . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge error.r $SE [OK] -- OUT: 0 [ERROR] + IN: smudge test.r $S [OK] -- OUT: $S . [OK] + IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log rot13-filter.log && + + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && + test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && + test_cmp error.o error.r + ) +' + +test_expect_success PERL 'process filter abort stops processing of all further files' ' + test_config_global filter.protocol.process "$TEST_DIRECTORY/t0021/rot13-filter.pl clean smudge" && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + cp "$TEST_ROOT/test2.o" test2.r && + echo "error this blob and all future blobs" >abort.o && + cp abort.o abort.r && + + SA=$(file_size abort.r) && + + git add . && + rm -f *.r && + + # Note: This test assumes that Git filters files in alphabetical + # order ("abort.r" before "test.r"). + filter_git checkout --quiet --no-progress . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge abort.r $SA [OK] -- OUT: 0 [ABORT] + STOP + EOF + test_cmp_exclude_clean expected.log rot13-filter.log && + + test_cmp "$TEST_ROOT/test.o" test.r && + test_cmp "$TEST_ROOT/test2.o" test2.r && + test_cmp abort.o abort.r + ) +' + +test_expect_success PERL 'invalid process filter must fail (and not hang!)' ' + test_config_global filter.protocol.process cat && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + test_must_fail git add . 2>git-stderr.log && + grep "does not support filter protocol version" git-stderr.log + ) +' + test_done diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl new file mode 100755 index 0000000000..ae4c50f5c5 --- /dev/null +++ b/t/t0021/rot13-filter.pl @@ -0,0 +1,192 @@ +#!/usr/bin/perl +# +# Example implementation for the Git filter protocol version 2 +# See Documentation/gitattributes.txt, section "Filter Protocol" +# +# The script takes the list of supported protocol capabilities as +# arguments ("clean", "smudge", etc). +# +# This implementation supports special test cases: +# (1) If data with the pathname "clean-write-fail.r" is processed with +# a "clean" operation then the write operation will die. +# (2) If data with the pathname "smudge-write-fail.r" is processed with +# a "smudge" operation then the write operation will die. +# (3) If data with the pathname "error.r" is processed with any +# operation then the filter signals that it cannot or does not want +# to process the file. +# (4) If data with the pathname "abort.r" is processed with any +# operation then the filter signals that it cannot or does not want +# to process the file and any file after that is processed with the +# same command. +# + +use strict; +use warnings; + +my $MAX_PACKET_CONTENT_SIZE = 65516; +my @capabilities = @ARGV; + +open my $debug, ">>", "rot13-filter.log" or die "cannot open log file: $!"; + +sub rot13 { + my $str = shift; + $str =~ y/A-Za-z/N-ZA-Mn-za-m/; + return $str; +} + +sub packet_bin_read { + my $buffer; + my $bytes_read = read STDIN, $buffer, 4; + if ( $bytes_read == 0 ) { + # EOF - Git stopped talking to us! + print $debug "STOP\n"; + exit(); + } + elsif ( $bytes_read != 4 ) { + die "invalid packet: '$buffer'"; + } + my $pkt_size = hex($buffer); + if ( $pkt_size == 0 ) { + return ( 1, "" ); + } + elsif ( $pkt_size > 4 ) { + my $content_size = $pkt_size - 4; + $bytes_read = read STDIN, $buffer, $content_size; + if ( $bytes_read != $content_size ) { + die "invalid packet ($content_size bytes expected; $bytes_read bytes read)"; + } + return ( 0, $buffer ); + } + else { + die "invalid packet size: $pkt_size"; + } +} + +sub packet_txt_read { + my ( $res, $buf ) = packet_bin_read(); + unless ( $buf =~ s/\n$// ) { + die "A non-binary line MUST be terminated by an LF."; + } + return ( $res, $buf ); +} + +sub packet_bin_write { + my $buf = shift; + print STDOUT sprintf( "%04x", length($buf) + 4 ); + print STDOUT $buf; + STDOUT->flush(); +} + +sub packet_txt_write { + packet_bin_write( $_[0] . "\n" ); +} + +sub packet_flush { + print STDOUT sprintf( "%04x", 0 ); + STDOUT->flush(); +} + +print $debug "START\n"; +$debug->flush(); + +( packet_txt_read() eq ( 0, "git-filter-client" ) ) || die "bad initialize"; +( packet_txt_read() eq ( 0, "version=2" ) ) || die "bad version"; +( packet_bin_read() eq ( 1, "" ) ) || die "bad version end"; + +packet_txt_write("git-filter-server"); +packet_txt_write("version=2"); +packet_flush(); + +( packet_txt_read() eq ( 0, "capability=clean" ) ) || die "bad capability"; +( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability"; +( packet_bin_read() eq ( 1, "" ) ) || die "bad capability end"; + +foreach (@capabilities) { + packet_txt_write( "capability=" . $_ ); +} +packet_flush(); +print $debug "init handshake complete\n"; +$debug->flush(); + +while (1) { + my ($command) = packet_txt_read() =~ /^command=([^=]+)$/; + print $debug "IN: $command"; + $debug->flush(); + + my ($pathname) = packet_txt_read() =~ /^pathname=([^=]+)$/; + print $debug " $pathname"; + $debug->flush(); + + # Flush + packet_bin_read(); + + my $input = ""; + { + binmode(STDIN); + my $buffer; + my $done = 0; + while ( !$done ) { + ( $done, $buffer ) = packet_bin_read(); + $input .= $buffer; + } + print $debug " " . length($input) . " [OK] -- "; + $debug->flush(); + } + + my $output; + if ( $pathname eq "error.r" or $pathname eq "abort.r" ) { + $output = ""; + } + elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) { + $output = rot13($input); + } + elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) { + $output = rot13($input); + } + else { + die "bad command '$command'"; + } + + print $debug "OUT: " . length($output) . " "; + $debug->flush(); + + if ( $pathname eq "error.r" ) { + print $debug "[ERROR]\n"; + $debug->flush(); + packet_txt_write("status=error"); + packet_flush(); + } + elsif ( $pathname eq "abort.r" ) { + print $debug "[ABORT]\n"; + $debug->flush(); + packet_txt_write("status=abort"); + packet_flush(); + } + else { + packet_txt_write("status=success"); + packet_flush(); + + if ( $pathname eq "${command}-write-fail.r" ) { + print $debug "[WRITE FAIL]\n"; + $debug->flush(); + die "${command} write error"; + } + + while ( length($output) > 0 ) { + my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE ); + packet_bin_write($packet); + # dots represent the number of packets + print $debug "."; + if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) { + $output = substr( $output, $MAX_PACKET_CONTENT_SIZE ); + } + else { + $output = ""; + } + } + packet_flush(); + print $debug " [OK]\n"; + $debug->flush(); + packet_flush(); + } +} diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index db5f60d0c5..74d2cd76fe 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -208,32 +208,15 @@ test_expect_success 'unambiguously abbreviated option' ' ' test_expect_success 'unambiguously abbreviated option with "="' ' - test-parse-options --int=2 >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="integer: 2" --int=2 ' test_expect_success 'ambiguously abbreviated option' ' test_expect_code 129 test-parse-options --strin 123 ' -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: 123 -abbrev: 7 -verbose: -1 -quiet: 0 -dry run: no -file: (not set) -EOF - test_expect_success 'non ambiguous option (after two options it abbreviates)' ' - test-parse-options --st 123 >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="string: 123" --st 123 ' cat >typo.err <<\EOF @@ -256,24 +239,8 @@ test_expect_success 'detect possible typos' ' test_cmp typo.err output.err ' -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: -1 -quiet: 0 -dry run: no -file: (not set) -arg 00: --quux -EOF - test_expect_success 'keep some options as arguments' ' - test-parse-options --quux >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="arg 00: --quux" --quux ' cat >expect <<\EOF @@ -350,54 +317,20 @@ test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' ' test_cmp expect output ' -cat >expect <<\EOF -boolean: 6 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: -1 -quiet: 0 -dry run: no -file: (not set) -EOF - test_expect_success 'OPT_BIT() works' ' - test-parse-options -bb --or4 >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="boolean: 6" -bb --or4 ' test_expect_success 'OPT_NEGBIT() works' ' - test-parse-options -bb --no-neg-or4 >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="boolean: 6" -bb --no-neg-or4 ' test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' ' - test-parse-options + + + + + + >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="boolean: 6" + + + + + + ' -cat >expect <<\EOF -boolean: 0 -integer: 12345 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: -1 -quiet: 0 -dry run: no -file: (not set) -EOF - test_expect_success 'OPT_NUMBER_CALLBACK() works' ' - test-parse-options -12345 >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="integer: 12345" -12345 ' cat >expect <<\EOF @@ -435,118 +368,28 @@ test_expect_success '--no-list resets list' ' test_cmp expect output ' -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: -1 -quiet: 3 -dry run: no -file: (not set) -EOF - test_expect_success 'multiple quiet levels' ' - test-parse-options -q -q -q >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="quiet: 3" -q -q -q ' -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: 3 -quiet: 0 -dry run: no -file: (not set) -EOF - test_expect_success 'multiple verbose levels' ' - test-parse-options -v -v -v >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="verbose: 3" -v -v -v ' -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: -1 -quiet: 0 -dry run: no -file: (not set) -EOF - test_expect_success '--no-quiet sets --quiet to 0' ' - test-parse-options --no-quiet >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="quiet: 0" --no-quiet ' -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: -1 -quiet: 0 -dry run: no -file: (not set) -EOF - test_expect_success '--no-quiet resets multiple -q to 0' ' - test-parse-options -q -q -q --no-quiet >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="quiet: 0" -q -q -q --no-quiet ' -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: 0 -quiet: 0 -dry run: no -file: (not set) -EOF - test_expect_success '--no-verbose sets verbose to 0' ' - test-parse-options --no-verbose >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="verbose: 0" --no-verbose ' -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: 0 -quiet: 0 -dry run: no -file: (not set) -EOF - test_expect_success '--no-verbose resets multiple verbose to 0' ' - test-parse-options -v -v -v --no-verbose >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="verbose: 0" -v -v -v --no-verbose ' test_done diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index bf2deee109..444b5a4df8 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -305,8 +305,9 @@ test_git_path GIT_COMMON_DIR=bar config bar/config test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs test_git_path GIT_COMMON_DIR=bar shallow bar/shallow -# In the tests below, the distinction between $PWD and $(pwd) is important: -# on Windows, $PWD is POSIX style (/c/foo), $(pwd) has drive letter (c:/foo). +# In the tests below, $(pwd) must be used because it is a native path on +# Windows and avoids MSYS's path mangling (which simplifies "foo/../bar" and +# strips the dot from trailing "/."). test_submodule_relative_url "../" "../foo" "../submodule" "../../submodule" test_submodule_relative_url "../" "../foo/bar" "../submodule" "../../foo/submodule" @@ -314,27 +315,29 @@ test_submodule_relative_url "../" "../foo/submodule" "../submodule" "../../foo/s test_submodule_relative_url "../" "./foo" "../submodule" "../submodule" test_submodule_relative_url "../" "./foo/bar" "../submodule" "../foo/submodule" test_submodule_relative_url "../../../" "../foo/bar" "../sub/a/b/c" "../../../../foo/sub/a/b/c" -test_submodule_relative_url "../" "$PWD/addtest" "../repo" "$(pwd)/repo" +test_submodule_relative_url "../" "$(pwd)/addtest" "../repo" "$(pwd)/repo" test_submodule_relative_url "../" "foo/bar" "../submodule" "../foo/submodule" test_submodule_relative_url "../" "foo" "../submodule" "../submodule" test_submodule_relative_url "(null)" "../foo/bar" "../sub/a/b/c" "../foo/sub/a/b/c" +test_submodule_relative_url "(null)" "../foo/bar" "../sub/a/b/c/" "../foo/sub/a/b/c" +test_submodule_relative_url "(null)" "../foo/bar/" "../sub/a/b/c" "../foo/sub/a/b/c" test_submodule_relative_url "(null)" "../foo/bar" "../submodule" "../foo/submodule" test_submodule_relative_url "(null)" "../foo/submodule" "../submodule" "../foo/submodule" test_submodule_relative_url "(null)" "../foo" "../submodule" "../submodule" test_submodule_relative_url "(null)" "./foo/bar" "../submodule" "foo/submodule" test_submodule_relative_url "(null)" "./foo" "../submodule" "submodule" test_submodule_relative_url "(null)" "//somewhere else/repo" "../subrepo" "//somewhere else/subrepo" -test_submodule_relative_url "(null)" "$PWD/subsuper_update_r" "../subsubsuper_update_r" "$(pwd)/subsubsuper_update_r" -test_submodule_relative_url "(null)" "$PWD/super_update_r2" "../subsuper_update_r" "$(pwd)/subsuper_update_r" -test_submodule_relative_url "(null)" "$PWD/." "../." "$(pwd)/." -test_submodule_relative_url "(null)" "$PWD" "./." "$(pwd)/." -test_submodule_relative_url "(null)" "$PWD/addtest" "../repo" "$(pwd)/repo" -test_submodule_relative_url "(null)" "$PWD" "./Ã¥ äö" "$(pwd)/Ã¥ äö" -test_submodule_relative_url "(null)" "$PWD/." "../submodule" "$(pwd)/submodule" -test_submodule_relative_url "(null)" "$PWD/submodule" "../submodule" "$(pwd)/submodule" -test_submodule_relative_url "(null)" "$PWD/home2/../remote" "../bundle1" "$(pwd)/home2/../bundle1" -test_submodule_relative_url "(null)" "$PWD/submodule_update_repo" "./." "$(pwd)/submodule_update_repo/." +test_submodule_relative_url "(null)" "$(pwd)/subsuper_update_r" "../subsubsuper_update_r" "$(pwd)/subsubsuper_update_r" +test_submodule_relative_url "(null)" "$(pwd)/super_update_r2" "../subsuper_update_r" "$(pwd)/subsuper_update_r" +test_submodule_relative_url "(null)" "$(pwd)/." "../." "$(pwd)/." +test_submodule_relative_url "(null)" "$(pwd)" "./." "$(pwd)/." +test_submodule_relative_url "(null)" "$(pwd)/addtest" "../repo" "$(pwd)/repo" +test_submodule_relative_url "(null)" "$(pwd)" "./Ã¥ äö" "$(pwd)/Ã¥ äö" +test_submodule_relative_url "(null)" "$(pwd)/." "../submodule" "$(pwd)/submodule" +test_submodule_relative_url "(null)" "$(pwd)/submodule" "../submodule" "$(pwd)/submodule" +test_submodule_relative_url "(null)" "$(pwd)/home2/../remote" "../bundle1" "$(pwd)/home2/../bundle1" +test_submodule_relative_url "(null)" "$(pwd)/submodule_update_repo" "./." "$(pwd)/submodule_update_repo/." test_submodule_relative_url "(null)" "file:///tmp/repo" "../subrepo" "file:///tmp/subrepo" test_submodule_relative_url "(null)" "foo/bar" "../submodule" "foo/submodule" test_submodule_relative_url "(null)" "foo" "../submodule" "submodule" diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 7c659eb585..711704ba5a 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -42,7 +42,7 @@ test_expect_success 'blob and tree' ' test_expect_success 'warn ambiguity when no candidate matches type hint' ' test_must_fail git rev-parse --verify 000000000^{commit} 2>actual && - grep "short SHA1 000000000 is ambiguous" actual + test_i18ngrep "short SHA1 000000000 is ambiguous" actual ' test_expect_success 'disambiguate tree-ish' ' diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index 4bcc335a19..b618d6be21 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -138,6 +138,14 @@ test_expect_success 'checkout from a bare repo without "add"' ' ) ' +test_expect_success '"add" default branch of a bare repo' ' + ( + git clone --bare . bare2 && + cd bare2 && + git worktree add ../there3 master + ) +' + test_expect_success 'checkout with grafts' ' test_when_finished rm .git/info/grafts && test_commit abc && diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh index 8f22c43e24..84a9028c43 100755 --- a/t/t2203-add-intent.sh +++ b/t/t2203-add-intent.sh @@ -5,10 +5,24 @@ test_description='Intent to add' . ./test-lib.sh test_expect_success 'intent to add' ' + test_commit 1 && + git rm 1.t && + echo hello >1.t && echo hello >file && echo hello >elif && git add -N file && - git add elif + git add elif && + git add -N 1.t +' + +test_expect_success 'git status' ' + git status --porcelain | grep -v actual >actual && + cat >expect <<-\EOF && + DA 1.t + A elif + A file + EOF + test_cmp expect actual ' test_expect_success 'check result of "add -N"' ' @@ -43,7 +57,9 @@ test_expect_success 'i-t-a entry is simply ignored' ' git add -N nitfol && git commit -m second && test $(git ls-tree HEAD -- nitfol | wc -l) = 0 && - test $(git diff --name-only HEAD -- nitfol | wc -l) = 1 + test $(git diff --name-only HEAD -- nitfol | wc -l) = 1 && + test $(git diff --name-only --ita-invisible-in-index HEAD -- nitfol | wc -l) = 0 && + test $(git diff --name-only --ita-invisible-in-index -- nitfol | wc -l) = 1 ' test_expect_success 'can commit with an unrelated i-t-a entry in index' ' @@ -113,5 +129,26 @@ test_expect_success 'cache-tree does skip dir that becomes empty' ' ) ' +test_expect_success 'commit: ita entries ignored in empty intial commit check' ' + git init empty-intial-commit && + ( + cd empty-intial-commit && + : >one && + git add -N one && + test_must_fail git commit -m nothing-new-here + ) +' + +test_expect_success 'commit: ita entries ignored in empty commit check' ' + git init empty-subsequent-commit && + ( + cd empty-subsequent-commit && + test_commit one && + : >two && + git add -N two && + test_must_fail git commit -m nothing-new-here + ) +' + test_done diff --git a/t/t3007-ls-files-recurse-submodules.sh b/t/t3007-ls-files-recurse-submodules.sh new file mode 100755 index 0000000000..a5426171d3 --- /dev/null +++ b/t/t3007-ls-files-recurse-submodules.sh @@ -0,0 +1,210 @@ +#!/bin/sh + +test_description='Test ls-files recurse-submodules feature + +This test verifies the recurse-submodules feature correctly lists files from +submodules. +' + +. ./test-lib.sh + +test_expect_success 'setup directory structure and submodules' ' + echo a >a && + mkdir b && + echo b >b/b && + git add a b && + git commit -m "add a and b" && + git init submodule && + echo c >submodule/c && + git -C submodule add c && + git -C submodule commit -m "add c" && + git submodule add ./submodule && + git commit -m "added submodule" +' + +test_expect_success 'ls-files correctly outputs files in submodule' ' + cat >expect <<-\EOF && + .gitmodules + a + b/b + submodule/c + EOF + + git ls-files --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'ls-files correctly outputs files in submodule with -z' ' + lf_to_nul >expect <<-\EOF && + .gitmodules + a + b/b + submodule/c + EOF + + git ls-files --recurse-submodules -z >actual && + test_cmp expect actual +' + +test_expect_success 'ls-files does not output files not added to a repo' ' + cat >expect <<-\EOF && + .gitmodules + a + b/b + submodule/c + EOF + + echo a >not_added && + echo b >b/not_added && + echo c >submodule/not_added && + git ls-files --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'ls-files recurses more than 1 level' ' + cat >expect <<-\EOF && + .gitmodules + a + b/b + submodule/.gitmodules + submodule/c + submodule/subsub/d + EOF + + git init submodule/subsub && + echo d >submodule/subsub/d && + git -C submodule/subsub add d && + git -C submodule/subsub commit -m "add d" && + git -C submodule submodule add ./subsub && + git -C submodule commit -m "added subsub" && + git ls-files --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs setup' ' + echo e >submodule/subsub/e.txt && + git -C submodule/subsub add e.txt && + git -C submodule/subsub commit -m "adding e.txt" && + echo f >submodule/f.TXT && + echo g >submodule/g.txt && + git -C submodule add f.TXT g.txt && + git -C submodule commit -m "add f and g" && + echo h >h.txt && + mkdir sib && + echo sib >sib/file && + git add h.txt sib/file && + git commit -m "add h and sib/file" && + git init sub && + echo sub >sub/file && + git -C sub add file && + git -C sub commit -m "add file" && + git submodule add ./sub && + git commit -m "added sub" && + + cat >expect <<-\EOF && + .gitmodules + a + b/b + h.txt + sib/file + sub/file + submodule/.gitmodules + submodule/c + submodule/f.TXT + submodule/g.txt + submodule/subsub/d + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules >actual && + test_cmp expect actual && + cat actual && + git ls-files --recurse-submodules "*" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/g.txt + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules "*.txt" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/f.TXT + submodule/g.txt + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules ":(icase)*.txt" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/f.TXT + submodule/g.txt + EOF + + git ls-files --recurse-submodules ":(icase)*.txt" ":(exclude)submodule/subsub/*" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + sub/file + EOF + + git ls-files --recurse-submodules "sub" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "sub/" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "sub/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "su*/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "su?/file" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + sib/file + sub/file + EOF + + git ls-files --recurse-submodules "s??/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "s???file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "s*file" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules does not support --error-unmatch' ' + test_must_fail git ls-files --recurse-submodules --error-unmatch 2>actual && + test_i18ngrep "does not support --error-unmatch" actual +' + +test_incompatible_with_recurse_submodules () { + test_expect_success "--recurse-submodules and $1 are incompatible" " + test_must_fail git ls-files --recurse-submodules $1 2>actual && + test_i18ngrep 'unsupported mode' actual + " +} + +test_incompatible_with_recurse_submodules --deleted +test_incompatible_with_recurse_submodules --modified +test_incompatible_with_recurse_submodules --others +test_incompatible_with_recurse_submodules --stage +test_incompatible_with_recurse_submodules --killed +test_incompatible_with_recurse_submodules --unmerged + +test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 51f3bbb8af..394f0005a1 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -96,7 +96,7 @@ test_expect_success 'revert forbidden on dirty working tree' ' echo content >extra_file && git add extra_file && test_must_fail git revert HEAD 2>errors && - test_i18ngrep "Your local changes would be overwritten by " errors + test_i18ngrep "your local changes would be overwritten by " errors ' diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index d046d98aec..14f0edca2b 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -881,4 +881,9 @@ test_expect_success 'rm files with two different errors' ' test_i18ncmp expect actual ' +test_expect_success 'rm empty string should invoke warning' ' + git rm -rf "" 2>output && + test_i18ngrep "warning: empty strings" output +' + test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 924a266126..f3a4b4a913 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -331,6 +331,11 @@ test_expect_success 'git add --dry-run --ignore-missing of non-existing file out test_i18ncmp expect.err actual.err ' +test_expect_success 'git add empty string should invoke warning' ' + git add "" 2>output && + test_i18ngrep "warning: empty strings" output +' + test_expect_success 'git add --chmod=[+-]x stages correctly' ' rm -f foo1 && echo foo >foo1 && @@ -350,6 +355,7 @@ test_expect_success POSIXPERM,SYMLINKS 'git add --chmod=+x with symlinks' ' ' test_expect_success 'git add --chmod=[+-]x changes index with already added file' ' + rm -f foo3 xfoo3 && echo foo >foo3 && git add foo3 && git add --chmod=+x foo3 && diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 2434157aa7..289806d0c7 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -869,7 +869,8 @@ test_expect_success 'diff that introduces and removes ws breakages' ' test_cmp expected current ' -test_expect_success 'the same with --ws-error-highlight' ' +test_expect_success 'ws-error-highlight test setup' ' + git reset --hard && { echo "0. blank-at-eol " && @@ -882,10 +883,7 @@ test_expect_success 'the same with --ws-error-highlight' ' echo "2. and a new line " } >x && - git -c color.diff=always diff --ws-error-highlight=default,old | - test_decode_color >current && - - cat >expected <<-\EOF && + cat >expect.default-old <<-\EOF && <BOLD>diff --git a/x b/x<RESET> <BOLD>index d0233a2..700886e 100644<RESET> <BOLD>--- a/x<RESET> @@ -897,12 +895,7 @@ test_expect_success 'the same with --ws-error-highlight' ' <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET> EOF - test_cmp expected current && - - git -c color.diff=always diff --ws-error-highlight=all | - test_decode_color >current && - - cat >expected <<-\EOF && + cat >expect.all <<-\EOF && <BOLD>diff --git a/x b/x<RESET> <BOLD>index d0233a2..700886e 100644<RESET> <BOLD>--- a/x<RESET> @@ -914,12 +907,7 @@ test_expect_success 'the same with --ws-error-highlight' ' <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET> EOF - test_cmp expected current && - - git -c color.diff=always diff --ws-error-highlight=none | - test_decode_color >current && - - cat >expected <<-\EOF && + cat >expect.none <<-\EOF <BOLD>diff --git a/x b/x<RESET> <BOLD>index d0233a2..700886e 100644<RESET> <BOLD>--- a/x<RESET> @@ -931,7 +919,57 @@ test_expect_success 'the same with --ws-error-highlight' ' <GREEN>+2. and a new line <RESET> EOF - test_cmp expected current +' + +test_expect_success 'test --ws-error-highlight option' ' + + git -c color.diff=always diff --ws-error-highlight=default,old | + test_decode_color >current && + test_cmp expect.default-old current && + + git -c color.diff=always diff --ws-error-highlight=all | + test_decode_color >current && + test_cmp expect.all current && + + git -c color.diff=always diff --ws-error-highlight=none | + test_decode_color >current && + test_cmp expect.none current + +' + +test_expect_success 'test diff.wsErrorHighlight config' ' + + git -c color.diff=always -c diff.wsErrorHighlight=default,old diff | + test_decode_color >current && + test_cmp expect.default-old current && + + git -c color.diff=always -c diff.wsErrorHighlight=all diff | + test_decode_color >current && + test_cmp expect.all current && + + git -c color.diff=always -c diff.wsErrorHighlight=none diff | + test_decode_color >current && + test_cmp expect.none current + +' + +test_expect_success 'option overrides diff.wsErrorHighlight' ' + + git -c color.diff=always -c diff.wsErrorHighlight=none \ + diff --ws-error-highlight=default,old | + test_decode_color >current && + test_cmp expect.default-old current && + + git -c color.diff=always -c diff.wsErrorHighlight=default \ + diff --ws-error-highlight=all | + test_decode_color >current && + test_cmp expect.all current && + + git -c color.diff=always -c diff.wsErrorHighlight=all \ + diff --ws-error-highlight=none | + test_decode_color >current && + test_cmp expect.none current + ' test_done diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index 9bd7dd2ba1..168739c721 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -31,7 +31,7 @@ test_expect_success 'try to apply corrupted patch' ' test_expect_success 'compare diagnostic; ensure file is still here' ' echo "error: git diff header lacks filename information (line 4)" >expected && test_path_is_file f && - test_cmp expected actual + test_i18ncmp expected actual ' test_done diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index 3e752ce032..969e4e9e52 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -100,4 +100,18 @@ test_expect_success '--bisect and --first-parent can not be combined' ' test_must_fail git rev-list --bisect --first-parent HEAD ' +test_expect_success '--header shows a NUL after each commit' ' + # We know that there is no Q in the true payload; names and + # addresses of the authors and the committers do not have + # any, and object names or header names do not, either. + git rev-list --header --max-count=2 HEAD | + nul_to_q | + grep "^Q" >actual && + cat >expect <<-EOF && + Q$(git rev-parse HEAD~1) + Q + EOF + test_cmp expect actual +' + test_done diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index e0c5f44cac..31db7b5f91 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -260,6 +260,12 @@ test_expect_success 'using reflog to find the fork point' ' test_cmp expect3 actual ' +test_expect_success '--fork-point works with empty reflog' ' + git -c core.logallrefupdates=false branch no-reflog base && + git merge-base --fork-point no-reflog derived && + test_cmp expect3 actual +' + test_expect_success 'merge-base --octopus --all for complex tree' ' # Best common ancestor for JE, JAA and JDD is JC # JE diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh index 3012a4d7c0..e319fa2e84 100755 --- a/t/t7064-wtstatus-pv2.sh +++ b/t/t7064-wtstatus-pv2.sh @@ -246,8 +246,8 @@ test_expect_success 'verify --intent-to-add output' ' 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 + 1 .A N... 000000 000000 100644 $_z40 $_z40 intent1.add + 1 .A N... 000000 000000 100644 $_z40 $_z40 intent2.add EOF git status --porcelain=v2 >actual && diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 6e839f5489..762135adea 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -2,6 +2,7 @@ test_description='signed commit tests' . ./test-lib.sh +GNUPGHOME_NOT_USED=$GNUPGHOME . "$TEST_DIRECTORY/lib-gpg.sh" test_expect_success GPG 'create signed commits' ' @@ -190,7 +191,7 @@ test_expect_success GPG 'show bad signature with custom format' ' test_cmp expect actual ' -test_expect_success GPG 'show unknown signature with custom format' ' +test_expect_success GPG 'show untrusted signature with custom format' ' cat >expect <<-\EOF && U 61092E85B7227189 @@ -200,6 +201,16 @@ test_expect_success GPG 'show unknown signature with custom format' ' test_cmp expect actual ' +test_expect_success GPG 'show unknown signature with custom format' ' + cat >expect <<-\EOF && + E + 61092E85B7227189 + + EOF + GNUPGHOME="$GNUPGHOME_NOT_USED" git log -1 --format="%G?%n%GK%n%GS" eighth-signed-alt >actual && + test_cmp expect actual +' + test_expect_success GPG 'show lack of signature with custom format' ' cat >expect <<-\EOF && N diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index aee785cffa..4dd1d7c520 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -126,6 +126,305 @@ test_expect_success 'with multiline title in the message' ' test_cmp expected actual ' +test_expect_success 'with non-trailer lines mixed with Signed-off-by' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + Signed-off-by: a <a@example.com> + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + Signed-off-by: a <a@example.com> + this is not a trailer + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with cherry picked from' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + (cherry picked from commit x) + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + (cherry picked from commit x) + this is not a trailer + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with a configured trailer' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + My-trailer: x + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + My-trailer: x + this is not a trailer + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with a non-configured trailer' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + I-am-not-configured: x + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + I-am-not-configured: x + this is not a trailer + + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with all non-configured trailers' ' + cat >patch <<-\EOF && + + I-am-not-configured: x + I-am-also-not-configured: x + EOF + cat >expected <<-\EOF && + + I-am-not-configured: x + I-am-also-not-configured: x + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines only' ' + cat >patch <<-\EOF && + + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'line with leading whitespace is not trailer' ' + q_to_tab >patch <<-\EOF && + + Qtoken: value + EOF + q_to_tab >expected <<-\EOF && + + Qtoken: value + + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as one trailer for 25% check' ' + q_to_tab >patch <<-\EOF && + + Signed-off-by: a <a@example.com> + name: value on + Qmultiple lines + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + EOF + q_to_tab >expected <<-\EOF && + + Signed-off-by: a <a@example.com> + name: value on + Qmultiple lines + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + name: value + EOF + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for placement' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + another: trailer + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + name: value + another: trailer + EOF + test_config trailer.name.where after && + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for replacement' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + another: trailer + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + another: trailer + name: value + EOF + test_config trailer.name.ifexists replace && + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for difference check' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + test_config trailer.name.ifexists addIfDifferent && + + q_to_tab >trailer <<-\EOF && + name: first line + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line + QQQQQsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + name: first line + QQQQQsecond line + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line *DIFFERENT* + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + name: first line *DIFFERENT* + Qsecond line + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for neighbor check' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + test_config trailer.name.where after && + test_config trailer.name.ifexists addIfDifferentNeighbor && + + q_to_tab >trailer <<-\EOF && + name: first line + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line + QQQQQsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + name: first line + QQQQQsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual +' + test_expect_success 'with config setup' ' git config trailer.ack.key "Acked-by: " && cat >expected <<-\EOF && diff --git a/t/t9000/test.pl b/t/t9000/test.pl index 2d05d3eeab..dfeaa9c655 100755 --- a/t/t9000/test.pl +++ b/t/t9000/test.pl @@ -32,15 +32,15 @@ my @success_list = (q[Jane], q["Jane\" Doe" <jdoe@example.com>], q[Doe, jane <jdoe@example.com>], q["Jane Doe <jdoe@example.com>], - q['Jane 'Doe' <jdoe@example.com>]); + q['Jane 'Doe' <jdoe@example.com>], + q[Jane@:;\.,()<>Doe <jdoe@example.com>], + q[Jane <jdoe@example.com> Doe], + q[<jdoe@example.com> Jane Doe]); my @known_failure_list = (q[Jane\ Doe <jdoe@example.com>], q["Doe, Ja"ne <jdoe@example.com>], q["Doe, Katarina" Jane <jdoe@example.com>], - q[Jane@:;\.,()<>Doe <jdoe@example.com>], q[Jane jdoe@example.com], - q[<jdoe@example.com> Jane Doe], - q[Jane <jdoe@example.com> Doe], q["Jane "Kat"a" ri"na" ",Doe" <jdoe@example.com>], q[Jane Doe], q[Jane "Doe <jdoe@example.com>"], diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index b3355d2c70..3dc4a3454d 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -140,6 +140,35 @@ test_expect_success $PREREQ 'Verify commandline' ' test_cmp expected commandline1 ' +test_expect_success $PREREQ 'setup expect for cc trailer' " +cat >expected-cc <<\EOF +!recipient@example.com! +!author@example.com! +!one@example.com! +!two@example.com! +!three@example.com! +!four@example.com! +!five@example.com! +EOF +" + +test_expect_success $PREREQ 'cc trailer with various syntax' ' + test_commit cc-trailer && + test_when_finished "git reset --hard HEAD^" && + git commit --amend -F - <<-EOF && + Test Cc: trailers. + + Cc: one@example.com + Cc: <two@example.com> # this is part of the name + Cc: <three@example.com>, <four@example.com> # not.five@example.com + Cc: "Some # Body" <five@example.com> [part.of.name.too] + EOF + clean_fake_sendmail && + git send-email -1 --to=recipient@example.com \ + --smtp-server="$(pwd)/fake.sendmail" && + test_cmp expected-cc commandline1 +' + test_expect_success $PREREQ 'setup expect' " cat >expected-show-all-headers <<\EOF 0001-Second.patch diff --git a/t/test-lib.sh b/t/test-lib.sh index 11562bde10..b859db61ac 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -54,12 +54,22 @@ case "$GIT_TEST_TEE_STARTED, $* " in done,*) # do not redirect again ;; -*' --tee '*|*' --va'*) +*' --tee '*|*' --va'*|*' --verbose-log '*) mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" + + # Make this filename available to the sub-process in case it is using + # --verbose-log. + GIT_TEST_TEE_OUTPUT_FILE=$BASE.out + export GIT_TEST_TEE_OUTPUT_FILE + + # Truncate before calling "tee -a" to get rid of the results + # from any previous runs. + >"$GIT_TEST_TEE_OUTPUT_FILE" + (GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1; - echo $? > $BASE.exit) | tee $BASE.out - test "$(cat $BASE.exit)" = 0 + echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + test "$(cat "$BASE.exit")" = 0 exit ;; esac @@ -246,6 +256,9 @@ do trace=t verbose=t shift ;; + --verbose-log) + verbose_log=t + shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac @@ -308,6 +321,16 @@ say () { say_color info "$*" } +if test -n "$HARNESS_ACTIVE" +then + if test "$verbose" = t || test -n "$verbose_only" + then + printf 'Bail out! %s\n' \ + 'verbose mode forbidden under TAP harness; try --verbose-log' + exit 1 + fi +fi + test "${test_description}" != "" || error "Test script did not set test_description." @@ -319,7 +342,10 @@ fi exec 5>&1 exec 6<&0 -if test "$verbose" = "t" +if test "$verbose_log" = "t" +then + exec 3>>"$GIT_TEST_TEE_OUTPUT_FILE" 4>&3 +elif test "$verbose" = "t" then exec 4>&2 3>&1 else @@ -4,6 +4,7 @@ #include "commit.h" #include "tempfile.h" #include "trailer.h" +#include "list.h" /* * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> */ @@ -25,19 +26,40 @@ struct conf_info { static struct conf_info default_conf_info; struct trailer_item { - struct trailer_item *previous; - struct trailer_item *next; - const char *token; - const char *value; + struct list_head list; + /* + * If this is not a trailer line, the line is stored in value + * (excluding the terminating newline) and token is NULL. + */ + char *token; + char *value; +}; + +struct arg_item { + struct list_head list; + char *token; + char *value; struct conf_info conf; }; -static struct trailer_item *first_conf_item; +static LIST_HEAD(conf_head); static char *separators = ":"; #define TRAILER_ARG_STRING "$ARG" +static const char *git_generated_prefixes[] = { + "Signed-off-by: ", + "(cherry picked from commit ", + NULL +}; + +/* Iterate over the elements of the list. */ +#define list_for_each_dir(pos, head, is_reverse) \ + for (pos = is_reverse ? (head)->prev : (head)->next; \ + pos != (head); \ + pos = is_reverse ? pos->prev : pos->next) + static int after_or_end(enum action_where where) { return (where == WHERE_AFTER) || (where == WHERE_END); @@ -56,21 +78,26 @@ static size_t token_len_without_separator(const char *token, size_t len) return len; } -static int same_token(struct trailer_item *a, struct trailer_item *b) +static int same_token(struct trailer_item *a, struct arg_item *b) { - size_t a_len = token_len_without_separator(a->token, strlen(a->token)); - size_t b_len = token_len_without_separator(b->token, strlen(b->token)); - size_t min_len = (a_len > b_len) ? b_len : a_len; + size_t a_len, b_len, min_len; + + if (!a->token) + return 0; + + a_len = token_len_without_separator(a->token, strlen(a->token)); + b_len = token_len_without_separator(b->token, strlen(b->token)); + min_len = (a_len > b_len) ? b_len : a_len; return !strncasecmp(a->token, b->token, min_len); } -static int same_value(struct trailer_item *a, struct trailer_item *b) +static int same_value(struct trailer_item *a, struct arg_item *b) { return !strcasecmp(a->value, b->value); } -static int same_trailer(struct trailer_item *a, struct trailer_item *b) +static int same_trailer(struct trailer_item *a, struct arg_item *b) { return same_token(a, b) && same_value(a, b); } @@ -92,11 +119,18 @@ static inline void strbuf_replace(struct strbuf *sb, const char *a, const char * static void free_trailer_item(struct trailer_item *item) { + free(item->token); + free(item->value); + free(item); +} + +static void free_arg_item(struct arg_item *item) +{ free(item->conf.name); free(item->conf.key); free(item->conf.command); - free((char *)item->token); - free((char *)item->value); + free(item->token); + free(item->value); free(item); } @@ -111,7 +145,14 @@ static char last_non_space_char(const char *s) static void print_tok_val(FILE *outfile, const char *tok, const char *val) { - char c = last_non_space_char(tok); + char c; + + if (!tok) { + fprintf(outfile, "%s\n", val); + return; + } + + c = last_non_space_char(tok); if (!c) return; if (strchr(separators, c)) @@ -120,108 +161,68 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val) fprintf(outfile, "%s%c %s\n", tok, separators[0], val); } -static void print_all(FILE *outfile, struct trailer_item *first, int trim_empty) +static void print_all(FILE *outfile, struct list_head *head, int trim_empty) { + struct list_head *pos; struct trailer_item *item; - for (item = first; item; item = item->next) { + list_for_each(pos, head) { + item = list_entry(pos, struct trailer_item, list); if (!trim_empty || strlen(item->value) > 0) print_tok_val(outfile, item->token, item->value); } } -static void update_last(struct trailer_item **last) -{ - if (*last) - while ((*last)->next != NULL) - *last = (*last)->next; -} - -static void update_first(struct trailer_item **first) +static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok) { - if (*first) - while ((*first)->previous != NULL) - *first = (*first)->previous; + struct trailer_item *new = xcalloc(sizeof(*new), 1); + new->token = arg_tok->token; + new->value = arg_tok->value; + arg_tok->token = arg_tok->value = NULL; + free_arg_item(arg_tok); + return new; } static void add_arg_to_input_list(struct trailer_item *on_tok, - struct trailer_item *arg_tok, - struct trailer_item **first, - struct trailer_item **last) -{ - if (after_or_end(arg_tok->conf.where)) { - arg_tok->next = on_tok->next; - on_tok->next = arg_tok; - arg_tok->previous = on_tok; - if (arg_tok->next) - arg_tok->next->previous = arg_tok; - update_last(last); - } else { - arg_tok->previous = on_tok->previous; - on_tok->previous = arg_tok; - arg_tok->next = on_tok; - if (arg_tok->previous) - arg_tok->previous->next = arg_tok; - update_first(first); - } + struct arg_item *arg_tok) +{ + int aoe = after_or_end(arg_tok->conf.where); + struct trailer_item *to_add = trailer_from_arg(arg_tok); + if (aoe) + list_add(&to_add->list, &on_tok->list); + else + list_add_tail(&to_add->list, &on_tok->list); } static int check_if_different(struct trailer_item *in_tok, - struct trailer_item *arg_tok, - int check_all) + struct arg_item *arg_tok, + int check_all, + struct list_head *head) { enum action_where where = arg_tok->conf.where; + struct list_head *next_head; do { - if (!in_tok) - return 1; if (same_trailer(in_tok, arg_tok)) return 0; /* * if we want to add a trailer after another one, * we have to check those before this one */ - in_tok = after_or_end(where) ? in_tok->previous : in_tok->next; + next_head = after_or_end(where) ? in_tok->list.prev + : in_tok->list.next; + if (next_head == head) + break; + in_tok = list_entry(next_head, struct trailer_item, list); } while (check_all); return 1; } -static void remove_from_list(struct trailer_item *item, - struct trailer_item **first, - struct trailer_item **last) -{ - struct trailer_item *next = item->next; - struct trailer_item *previous = item->previous; - - if (next) { - item->next->previous = previous; - item->next = NULL; - } else if (last) - *last = previous; - - if (previous) { - item->previous->next = next; - item->previous = NULL; - } else if (first) - *first = next; -} - -static struct trailer_item *remove_first(struct trailer_item **first) -{ - struct trailer_item *item = *first; - *first = item->next; - if (item->next) { - item->next->previous = NULL; - item->next = NULL; - } - return item; -} - -static const char *apply_command(const char *command, const char *arg) +static char *apply_command(const char *command, const char *arg) { struct strbuf cmd = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; struct child_process cp = CHILD_PROCESS_INIT; const char *argv[] = {NULL, NULL}; - const char *result; + char *result; strbuf_addstr(&cmd, command); if (arg) @@ -246,7 +247,7 @@ static const char *apply_command(const char *command, const char *arg) return result; } -static void apply_item_command(struct trailer_item *in_tok, struct trailer_item *arg_tok) +static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok) { if (arg_tok->conf.command) { const char *arg; @@ -264,121 +265,108 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item } static void apply_arg_if_exists(struct trailer_item *in_tok, - struct trailer_item *arg_tok, + struct arg_item *arg_tok, struct trailer_item *on_tok, - struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last) + struct list_head *head) { switch (arg_tok->conf.if_exists) { case EXISTS_DO_NOTHING: - free_trailer_item(arg_tok); + free_arg_item(arg_tok); break; case EXISTS_REPLACE: apply_item_command(in_tok, arg_tok); - add_arg_to_input_list(on_tok, arg_tok, - in_tok_first, in_tok_last); - remove_from_list(in_tok, in_tok_first, in_tok_last); + add_arg_to_input_list(on_tok, arg_tok); + list_del(&in_tok->list); free_trailer_item(in_tok); break; case EXISTS_ADD: apply_item_command(in_tok, arg_tok); - add_arg_to_input_list(on_tok, arg_tok, - in_tok_first, in_tok_last); + add_arg_to_input_list(on_tok, arg_tok); break; case EXISTS_ADD_IF_DIFFERENT: apply_item_command(in_tok, arg_tok); - if (check_if_different(in_tok, arg_tok, 1)) - add_arg_to_input_list(on_tok, arg_tok, - in_tok_first, in_tok_last); + if (check_if_different(in_tok, arg_tok, 1, head)) + add_arg_to_input_list(on_tok, arg_tok); else - free_trailer_item(arg_tok); + free_arg_item(arg_tok); break; case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR: apply_item_command(in_tok, arg_tok); - if (check_if_different(on_tok, arg_tok, 0)) - add_arg_to_input_list(on_tok, arg_tok, - in_tok_first, in_tok_last); + if (check_if_different(on_tok, arg_tok, 0, head)) + add_arg_to_input_list(on_tok, arg_tok); else - free_trailer_item(arg_tok); + free_arg_item(arg_tok); break; } } -static void apply_arg_if_missing(struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last, - struct trailer_item *arg_tok) +static void apply_arg_if_missing(struct list_head *head, + struct arg_item *arg_tok) { - struct trailer_item **in_tok; enum action_where where; + struct trailer_item *to_add; switch (arg_tok->conf.if_missing) { case MISSING_DO_NOTHING: - free_trailer_item(arg_tok); + free_arg_item(arg_tok); break; case MISSING_ADD: where = arg_tok->conf.where; - in_tok = after_or_end(where) ? in_tok_last : in_tok_first; apply_item_command(NULL, arg_tok); - if (*in_tok) { - add_arg_to_input_list(*in_tok, arg_tok, - in_tok_first, in_tok_last); - } else { - *in_tok_first = arg_tok; - *in_tok_last = arg_tok; - } - break; + to_add = trailer_from_arg(arg_tok); + if (after_or_end(where)) + list_add_tail(&to_add->list, head); + else + list_add(&to_add->list, head); } } -static int find_same_and_apply_arg(struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last, - struct trailer_item *arg_tok) +static int find_same_and_apply_arg(struct list_head *head, + struct arg_item *arg_tok) { + struct list_head *pos; struct trailer_item *in_tok; struct trailer_item *on_tok; - struct trailer_item *following_tok; enum action_where where = arg_tok->conf.where; int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE); int backwards = after_or_end(where); - struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first; + struct trailer_item *start_tok; + + if (list_empty(head)) + return 0; - for (in_tok = start_tok; in_tok; in_tok = following_tok) { - following_tok = backwards ? in_tok->previous : in_tok->next; + start_tok = list_entry(backwards ? head->prev : head->next, + struct trailer_item, + list); + + list_for_each_dir(pos, head, backwards) { + in_tok = list_entry(pos, struct trailer_item, list); if (!same_token(in_tok, arg_tok)) continue; on_tok = middle ? in_tok : start_tok; - apply_arg_if_exists(in_tok, arg_tok, on_tok, - in_tok_first, in_tok_last); + apply_arg_if_exists(in_tok, arg_tok, on_tok, head); return 1; } return 0; } -static void process_trailers_lists(struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last, - struct trailer_item **arg_tok_first) +static void process_trailers_lists(struct list_head *head, + struct list_head *arg_head) { - struct trailer_item *arg_tok; - struct trailer_item *next_arg; - - if (!*arg_tok_first) - return; + struct list_head *pos, *p; + struct arg_item *arg_tok; - for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) { + list_for_each_safe(pos, p, arg_head) { int applied = 0; + arg_tok = list_entry(pos, struct arg_item, list); - next_arg = arg_tok->next; - remove_from_list(arg_tok, arg_tok_first, NULL); + list_del(pos); - applied = find_same_and_apply_arg(in_tok_first, - in_tok_last, - arg_tok); + applied = find_same_and_apply_arg(head, arg_tok); if (!applied) - apply_arg_if_missing(in_tok_first, - in_tok_last, - arg_tok); + apply_arg_if_missing(head, arg_tok); } } @@ -425,41 +413,32 @@ static int set_if_missing(struct conf_info *item, const char *value) return 0; } -static void duplicate_conf(struct conf_info *dst, struct conf_info *src) +static void duplicate_conf(struct conf_info *dst, const struct conf_info *src) { *dst = *src; - if (src->name) - dst->name = xstrdup(src->name); - if (src->key) - dst->key = xstrdup(src->key); - if (src->command) - dst->command = xstrdup(src->command); + dst->name = xstrdup_or_null(src->name); + dst->key = xstrdup_or_null(src->key); + dst->command = xstrdup_or_null(src->command); } -static struct trailer_item *get_conf_item(const char *name) +static struct arg_item *get_conf_item(const char *name) { - struct trailer_item *item; - struct trailer_item *previous; + struct list_head *pos; + struct arg_item *item; /* Look up item with same name */ - for (previous = NULL, item = first_conf_item; - item; - previous = item, item = item->next) { + list_for_each(pos, &conf_head) { + item = list_entry(pos, struct arg_item, list); if (!strcasecmp(item->conf.name, name)) return item; } /* Item does not already exists, create it */ - item = xcalloc(sizeof(struct trailer_item), 1); + item = xcalloc(sizeof(*item), 1); duplicate_conf(&item->conf, &default_conf_info); item->conf.name = xstrdup(name); - if (!previous) - first_conf_item = item; - else { - previous->next = item; - item->previous = previous; - } + list_add_tail(&item->list, &conf_head); return item; } @@ -509,7 +488,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value, v static int git_trailer_config(const char *conf_key, const char *value, void *cb) { const char *trailer_item, *variable_name; - struct trailer_item *item; + struct arg_item *item; struct conf_info *conf; char *name = NULL; enum trailer_info_type type; @@ -567,33 +546,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb) return 0; } -static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer) -{ - size_t len; - struct strbuf seps = STRBUF_INIT; - strbuf_addstr(&seps, separators); - strbuf_addch(&seps, '='); - len = strcspn(trailer, seps.buf); - strbuf_release(&seps); - if (len == 0) { - int l = strlen(trailer); - while (l > 0 && isspace(trailer[l - 1])) - l--; - return error(_("empty trailer token in trailer '%.*s'"), l, trailer); - } - if (len < strlen(trailer)) { - strbuf_add(tok, trailer, len); - strbuf_trim(tok); - strbuf_addstr(val, trailer + len + 1); - strbuf_trim(val); - } else { - strbuf_addstr(tok, trailer); - strbuf_trim(tok); - } - return 0; -} - -static const char *token_from_item(struct trailer_item *item, char *tok) +static const char *token_from_item(struct arg_item *item, char *tok) { if (item->conf.key) return item->conf.key; @@ -602,94 +555,134 @@ static const char *token_from_item(struct trailer_item *item, char *tok) return item->conf.name; } -static struct trailer_item *new_trailer_item(struct trailer_item *conf_item, - char *tok, char *val) -{ - struct trailer_item *new = xcalloc(sizeof(*new), 1); - new->value = val ? val : xstrdup(""); - - if (conf_item) { - duplicate_conf(&new->conf, &conf_item->conf); - new->token = xstrdup(token_from_item(conf_item, tok)); - free(tok); - } else { - duplicate_conf(&new->conf, &default_conf_info); - new->token = tok; - } - - return new; -} - -static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len) +static int token_matches_item(const char *tok, struct arg_item *item, int tok_len) { if (!strncasecmp(tok, item->conf.name, tok_len)) return 1; return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0; } -static struct trailer_item *create_trailer_item(const char *string) +/* + * Return the location of the first separator in line, or -1 if there is no + * separator. + */ +static int find_separator(const char *line, const char *separators) { - struct strbuf tok = STRBUF_INIT; - struct strbuf val = STRBUF_INIT; - struct trailer_item *item; - int tok_len; + int loc = strcspn(line, separators); + if (!line[loc]) + return -1; + return loc; +} - if (parse_trailer(&tok, &val, string)) - return NULL; +/* + * Obtain the token, value, and conf from the given trailer. + * + * separator_pos must not be 0, since the token cannot be an empty string. + * + * If separator_pos is -1, interpret the whole trailer as a token. + */ +static void parse_trailer(struct strbuf *tok, struct strbuf *val, + const struct conf_info **conf, const char *trailer, + int separator_pos) +{ + struct arg_item *item; + int tok_len; + struct list_head *pos; - tok_len = token_len_without_separator(tok.buf, tok.len); + if (separator_pos != -1) { + strbuf_add(tok, trailer, separator_pos); + strbuf_trim(tok); + strbuf_addstr(val, trailer + separator_pos + 1); + strbuf_trim(val); + } else { + strbuf_addstr(tok, trailer); + strbuf_trim(tok); + } /* Lookup if the token matches something in the config */ - for (item = first_conf_item; item; item = item->next) { - if (token_matches_item(tok.buf, item, tok_len)) - return new_trailer_item(item, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL)); + tok_len = token_len_without_separator(tok->buf, tok->len); + if (conf) + *conf = &default_conf_info; + list_for_each(pos, &conf_head) { + item = list_entry(pos, struct arg_item, list); + if (token_matches_item(tok->buf, item, tok_len)) { + char *tok_buf = strbuf_detach(tok, NULL); + if (conf) + *conf = &item->conf; + strbuf_addstr(tok, token_from_item(item, tok_buf)); + free(tok_buf); + break; + } } +} - return new_trailer_item(NULL, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL)); +static struct trailer_item *add_trailer_item(struct list_head *head, char *tok, + char *val) +{ + struct trailer_item *new = xcalloc(sizeof(*new), 1); + new->token = tok; + new->value = val; + list_add_tail(&new->list, head); + return new; } -static void add_trailer_item(struct trailer_item **first, - struct trailer_item **last, - struct trailer_item *new) +static void add_arg_item(struct list_head *arg_head, char *tok, char *val, + const struct conf_info *conf) { - if (!new) - return; - if (!*last) { - *first = new; - *last = new; - } else { - (*last)->next = new; - new->previous = *last; - *last = new; - } + struct arg_item *new = xcalloc(sizeof(*new), 1); + new->token = tok; + new->value = val; + duplicate_conf(&new->conf, conf); + list_add_tail(&new->list, arg_head); } -static struct trailer_item *process_command_line_args(struct string_list *trailers) +static void process_command_line_args(struct list_head *arg_head, + struct string_list *trailers) { - struct trailer_item *arg_tok_first = NULL; - struct trailer_item *arg_tok_last = NULL; struct string_list_item *tr; - struct trailer_item *item; + struct arg_item *item; + struct strbuf tok = STRBUF_INIT; + struct strbuf val = STRBUF_INIT; + const struct conf_info *conf; + struct list_head *pos; - /* Add a trailer item for each configured trailer with a command */ - for (item = first_conf_item; item; item = item->next) { - if (item->conf.command) { - struct trailer_item *new = new_trailer_item(item, NULL, NULL); - add_trailer_item(&arg_tok_first, &arg_tok_last, new); - } + /* + * In command-line arguments, '=' is accepted (in addition to the + * separators that are defined). + */ + char *cl_separators = xstrfmt("=%s", separators); + + /* Add an arg item for each configured trailer with a command */ + list_for_each(pos, &conf_head) { + item = list_entry(pos, struct arg_item, list); + if (item->conf.command) + add_arg_item(arg_head, + xstrdup(token_from_item(item, NULL)), + xstrdup(""), + &item->conf); } - /* Add a trailer item for each trailer on the command line */ + /* Add an arg item for each trailer on the command line */ for_each_string_list_item(tr, trailers) { - struct trailer_item *new = create_trailer_item(tr->string); - add_trailer_item(&arg_tok_first, &arg_tok_last, new); + int separator_pos = find_separator(tr->string, cl_separators); + if (separator_pos == 0) { + struct strbuf sb = STRBUF_INIT; + strbuf_addstr(&sb, tr->string); + strbuf_trim(&sb); + error(_("empty trailer token in trailer '%.*s'"), + (int) sb.len, sb.buf); + strbuf_release(&sb); + } else { + parse_trailer(&tok, &val, &conf, tr->string, + separator_pos); + add_arg_item(arg_head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL), + conf); + } } - return arg_tok_first; + free(cl_separators); } static struct strbuf **read_input_file(const char *file) @@ -737,6 +730,15 @@ static int find_patch_start(struct strbuf **lines, int count) static int find_trailer_start(struct strbuf **lines, int count) { int start, end_of_title, only_spaces = 1; + int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; + /* + * Number of possible continuation lines encountered. This will be + * reset to 0 if we encounter a trailer (since those lines are to be + * considered continuations of that trailer), and added to + * non_trailer_lines if we encounter a non-trailer (since those lines + * are to be considered non-trailers). + */ + int possible_continuation_lines = 0; /* The first paragraph is the title and cannot be trailers */ for (start = 0; start < count; start++) { @@ -748,26 +750,71 @@ static int find_trailer_start(struct strbuf **lines, int count) end_of_title = start; /* - * Get the start of the trailers by looking starting from the end - * for a line with only spaces before lines with one separator. + * Get the start of the trailers by looking starting from the end for a + * blank line before a set of non-blank lines that (i) are all + * trailers, or (ii) contains at least one Git-generated trailer and + * consists of at least 25% trailers. */ for (start = count - 1; start >= end_of_title; start--) { - if (lines[start]->buf[0] == comment_line_char) + const char **p; + int separator_pos; + + if (lines[start]->buf[0] == comment_line_char) { + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; continue; + } if (contains_only_spaces(lines[start]->buf)) { if (only_spaces) continue; - return start + 1; + non_trailer_lines += possible_continuation_lines; + if (recognized_prefix && + trailer_lines * 3 >= non_trailer_lines) + return start + 1; + if (trailer_lines && !non_trailer_lines) + return start + 1; + return count; } - if (strcspn(lines[start]->buf, separators) < lines[start]->len) { - if (only_spaces) - only_spaces = 0; - continue; + only_spaces = 0; + + for (p = git_generated_prefixes; *p; p++) { + if (starts_with(lines[start]->buf, *p)) { + trailer_lines++; + possible_continuation_lines = 0; + recognized_prefix = 1; + goto continue_outer_loop; + } + } + + separator_pos = find_separator(lines[start]->buf, separators); + if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) { + struct list_head *pos; + + trailer_lines++; + possible_continuation_lines = 0; + if (recognized_prefix) + continue; + list_for_each(pos, &conf_head) { + struct arg_item *item; + item = list_entry(pos, struct arg_item, list); + if (token_matches_item(lines[start]->buf, item, + separator_pos)) { + recognized_prefix = 1; + break; + } + } + } else if (isspace(lines[start]->buf[0])) + possible_continuation_lines++; + else { + non_trailer_lines++; + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; } - return count; +continue_outer_loop: + ; } - return only_spaces ? count : 0; + return count; } /* Get the index of the end of the trailers */ @@ -805,11 +852,13 @@ static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end static int process_input_file(FILE *outfile, struct strbuf **lines, - struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last) + struct list_head *head) { int count = 0; int patch_start, trailer_start, trailer_end, i; + struct strbuf tok = STRBUF_INIT; + struct strbuf val = STRBUF_INIT; + struct trailer_item *last = NULL; /* Get the line count */ while (lines[count]) @@ -827,20 +876,43 @@ static int process_input_file(FILE *outfile, /* Parse trailer lines */ for (i = trailer_start; i < trailer_end; i++) { - if (lines[i]->buf[0] != comment_line_char) { - struct trailer_item *new = create_trailer_item(lines[i]->buf); - add_trailer_item(in_tok_first, in_tok_last, new); + int separator_pos; + if (lines[i]->buf[0] == comment_line_char) + continue; + if (last && isspace(lines[i]->buf[0])) { + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%s\n%s", last->value, lines[i]->buf); + strbuf_strip_suffix(&sb, "\n"); + free(last->value); + last->value = strbuf_detach(&sb, NULL); + continue; + } + separator_pos = find_separator(lines[i]->buf, separators); + if (separator_pos >= 1) { + parse_trailer(&tok, &val, NULL, lines[i]->buf, + separator_pos); + last = add_trailer_item(head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL)); + } else { + strbuf_addbuf(&val, lines[i]); + strbuf_strip_suffix(&val, "\n"); + add_trailer_item(head, + NULL, + strbuf_detach(&val, NULL)); + last = NULL; } } return trailer_end; } -static void free_all(struct trailer_item **first) +static void free_all(struct list_head *head) { - while (*first) { - struct trailer_item *item = remove_first(first); - free_trailer_item(item); + struct list_head *pos, *p; + list_for_each_safe(pos, p, head) { + list_del(pos); + free_trailer_item(list_entry(pos, struct trailer_item, list)); } } @@ -877,9 +949,8 @@ static FILE *create_in_place_tempfile(const char *file) void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers) { - struct trailer_item *in_tok_first = NULL; - struct trailer_item *in_tok_last = NULL; - struct trailer_item *arg_tok_first; + LIST_HEAD(head); + LIST_HEAD(arg_head); struct strbuf **lines; int trailer_end; FILE *outfile = stdout; @@ -894,15 +965,15 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str outfile = create_in_place_tempfile(file); /* Print the lines before the trailers */ - trailer_end = process_input_file(outfile, lines, &in_tok_first, &in_tok_last); + trailer_end = process_input_file(outfile, lines, &head); - arg_tok_first = process_command_line_args(trailers); + process_command_line_args(&arg_head, trailers); - process_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first); + process_trailers_lists(&head, &arg_head); - print_all(outfile, in_tok_first, trim_empty); + print_all(outfile, &head, trim_empty); - free_all(&in_tok_first); + free_all(&head); /* Print the lines after the trailers as is */ print_lines(outfile, lines, trailer_end, INT_MAX); diff --git a/transport.c b/transport.c index 079499dbaf..d57e8dec28 100644 --- a/transport.c +++ b/transport.c @@ -307,7 +307,9 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v } } -static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, int porcelain) +static void print_ref_status(char flag, const char *summary, + struct ref *to, struct ref *from, const char *msg, + int porcelain, int summary_width) { if (porcelain) { if (from) @@ -319,7 +321,7 @@ static void print_ref_status(char flag, const char *summary, struct ref *to, str else fprintf(stdout, "%s\n", summary); } else { - fprintf(stderr, " %c %-*s ", flag, TRANSPORT_SUMMARY_WIDTH, summary); + fprintf(stderr, " %c %-*s ", flag, summary_width, summary); if (from) fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); else @@ -333,15 +335,16 @@ static void print_ref_status(char flag, const char *summary, struct ref *to, str } } -static void print_ok_ref_status(struct ref *ref, int porcelain) +static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width) { if (ref->deletion) - print_ref_status('-', "[deleted]", ref, NULL, NULL, porcelain); + print_ref_status('-', "[deleted]", ref, NULL, NULL, + porcelain, summary_width); else if (is_null_oid(&ref->old_oid)) print_ref_status('*', (starts_with(ref->name, "refs/tags/") ? "[new tag]" : "[new branch]"), - ref, ref->peer_ref, NULL, porcelain); + ref, ref->peer_ref, NULL, porcelain, summary_width); else { struct strbuf quickref = STRBUF_INIT; char type; @@ -361,12 +364,14 @@ static void print_ok_ref_status(struct ref *ref, int porcelain) strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash, DEFAULT_ABBREV); - print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg, porcelain); + print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg, + porcelain, summary_width); strbuf_release(&quickref); } } -static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain) +static int print_one_push_status(struct ref *ref, const char *dest, int count, + int porcelain, int summary_width) { if (!count) { char *url = transport_anonymize_url(dest); @@ -376,62 +381,87 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i switch(ref->status) { case REF_STATUS_NONE: - print_ref_status('X', "[no match]", ref, NULL, NULL, porcelain); + print_ref_status('X', "[no match]", ref, NULL, NULL, + porcelain, summary_width); break; case REF_STATUS_REJECT_NODELETE: print_ref_status('!', "[rejected]", ref, NULL, - "remote does not support deleting refs", porcelain); + "remote does not support deleting refs", + porcelain, summary_width); break; case REF_STATUS_UPTODATE: print_ref_status('=', "[up to date]", ref, - ref->peer_ref, NULL, porcelain); + ref->peer_ref, NULL, porcelain, summary_width); break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast-forward", porcelain); + "non-fast-forward", porcelain, summary_width); break; case REF_STATUS_REJECT_ALREADY_EXISTS: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "already exists", porcelain); + "already exists", porcelain, summary_width); break; case REF_STATUS_REJECT_FETCH_FIRST: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "fetch first", porcelain); + "fetch first", porcelain, summary_width); break; case REF_STATUS_REJECT_NEEDS_FORCE: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "needs force", porcelain); + "needs force", porcelain, summary_width); break; case REF_STATUS_REJECT_STALE: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "stale info", porcelain); + "stale info", porcelain, summary_width); break; case REF_STATUS_REJECT_SHALLOW: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "new shallow roots not allowed", porcelain); + "new shallow roots not allowed", + porcelain, summary_width); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, - ref->deletion ? NULL : ref->peer_ref, - ref->remote_status, porcelain); + ref->deletion ? NULL : ref->peer_ref, + ref->remote_status, porcelain, summary_width); break; case REF_STATUS_EXPECTING_REPORT: print_ref_status('!', "[remote failure]", ref, - ref->deletion ? NULL : ref->peer_ref, - "remote failed to report status", porcelain); + ref->deletion ? NULL : ref->peer_ref, + "remote failed to report status", + porcelain, summary_width); break; case REF_STATUS_ATOMIC_PUSH_FAILED: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "atomic push failed", porcelain); + "atomic push failed", porcelain, summary_width); break; case REF_STATUS_OK: - print_ok_ref_status(ref, porcelain); + print_ok_ref_status(ref, porcelain, summary_width); break; } return 1; } +static int measure_abbrev(const struct object_id *oid, int sofar) +{ + char hex[GIT_SHA1_HEXSZ + 1]; + int w = find_unique_abbrev_r(hex, oid->hash, DEFAULT_ABBREV); + + return (w < sofar) ? sofar : w; +} + +int transport_summary_width(const struct ref *refs) +{ + int maxw = -1; + + for (; refs; refs = refs->next) { + maxw = measure_abbrev(&refs->old_oid, maxw); + maxw = measure_abbrev(&refs->new_oid, maxw); + } + if (maxw < 0) + maxw = FALLBACK_DEFAULT_ABBREV; + return (2 * maxw + 3); +} + void transport_print_push_status(const char *dest, struct ref *refs, int verbose, int porcelain, unsigned int *reject_reasons) { @@ -439,25 +469,29 @@ void transport_print_push_status(const char *dest, struct ref *refs, int n = 0; unsigned char head_sha1[20]; char *head; + int summary_width = transport_summary_width(refs); head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); if (verbose) { for (ref = refs; ref; ref = ref->next) if (ref->status == REF_STATUS_UPTODATE) - n += print_one_push_status(ref, dest, n, porcelain); + n += print_one_push_status(ref, dest, n, + porcelain, summary_width); } for (ref = refs; ref; ref = ref->next) if (ref->status == REF_STATUS_OK) - n += print_one_push_status(ref, dest, n, porcelain); + n += print_one_push_status(ref, dest, n, + porcelain, summary_width); *reject_reasons = 0; for (ref = refs; ref; ref = ref->next) { if (ref->status != REF_STATUS_NONE && ref->status != REF_STATUS_UPTODATE && ref->status != REF_STATUS_OK) - n += print_one_push_status(ref, dest, n, porcelain); + n += print_one_push_status(ref, dest, n, + porcelain, summary_width); if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) { if (head != NULL && !strcmp(head, ref->name)) *reject_reasons |= REJECT_NON_FF_HEAD; diff --git a/transport.h b/transport.h index 68669f14d0..b8e4ee8099 100644 --- a/transport.h +++ b/transport.h @@ -147,8 +147,7 @@ struct transport { #define TRANSPORT_PUSH_ATOMIC 8192 #define TRANSPORT_PUSH_OPTIONS 16384 -#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) -#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x) +extern int transport_summary_width(const struct ref *refs); /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); diff --git a/upload-pack.c b/upload-pack.c index 5ec21e61d9..e0db8b42be 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -16,6 +16,7 @@ #include "string-list.h" #include "parse-options.h" #include "argv-array.h" +#include "prio-queue.h" static const char * const upload_pack_usage[] = { N_("git upload-pack [<options>] <dir>"), @@ -319,12 +320,12 @@ static int got_sha1(const char *hex, unsigned char *sha1) static int reachable(struct commit *want) { - struct commit_list *work = NULL; + struct prio_queue work = { compare_commits_by_commit_date }; - commit_list_insert_by_date(want, &work); - while (work) { + prio_queue_put(&work, want); + while (work.nr) { struct commit_list *list; - struct commit *commit = pop_commit(&work); + struct commit *commit = prio_queue_get(&work); if (commit->object.flags & THEY_HAVE) { want->object.flags |= COMMON_KNOWN; @@ -340,12 +341,12 @@ static int reachable(struct commit *want) for (list = commit->parents; list; list = list->next) { struct commit *parent = list->item; if (!(parent->object.flags & REACHABLE)) - commit_list_insert_by_date(parent, &work); + prio_queue_put(&work, parent); } } want->object.flags |= REACHABLE; clear_commit_marks(want, REACHABLE); - free_commit_list(work); + clear_prio_queue(&work); return (want->object.flags & COMMON_KNOWN); } @@ -397,13 +398,13 @@ static int get_common_commits(void) if (multi_ack == 2 && got_common && !got_other && ok_to_give_up()) { sent_ready = 1; - packet_write(1, "ACK %s ready\n", last_hex); + packet_write_fmt(1, "ACK %s ready\n", last_hex); } if (have_obj.nr == 0 || multi_ack) - packet_write(1, "NAK\n"); + packet_write_fmt(1, "NAK\n"); if (no_done && sent_ready) { - packet_write(1, "ACK %s\n", last_hex); + packet_write_fmt(1, "ACK %s\n", last_hex); return 0; } if (stateless_rpc) @@ -420,20 +421,20 @@ static int get_common_commits(void) const char *hex = sha1_to_hex(sha1); if (multi_ack == 2) { sent_ready = 1; - packet_write(1, "ACK %s ready\n", hex); + packet_write_fmt(1, "ACK %s ready\n", hex); } else - packet_write(1, "ACK %s continue\n", hex); + packet_write_fmt(1, "ACK %s continue\n", hex); } break; default: got_common = 1; memcpy(last_hex, sha1_to_hex(sha1), 41); if (multi_ack == 2) - packet_write(1, "ACK %s common\n", last_hex); + packet_write_fmt(1, "ACK %s common\n", last_hex); else if (multi_ack) - packet_write(1, "ACK %s continue\n", last_hex); + packet_write_fmt(1, "ACK %s continue\n", last_hex); else if (have_obj.nr == 1) - packet_write(1, "ACK %s\n", last_hex); + packet_write_fmt(1, "ACK %s\n", last_hex); break; } continue; @@ -441,10 +442,10 @@ static int get_common_commits(void) if (!strcmp(line, "done")) { if (have_obj.nr > 0) { if (multi_ack) - packet_write(1, "ACK %s\n", last_hex); + packet_write_fmt(1, "ACK %s\n", last_hex); return 0; } - packet_write(1, "NAK\n"); + packet_write_fmt(1, "NAK\n"); return -1; } die("git upload-pack: expected SHA1 list, got '%s'", line); @@ -637,8 +638,8 @@ static void send_shallow(struct commit_list *result) while (result) { struct object *object = &result->item->object; if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) { - packet_write(1, "shallow %s", - oid_to_hex(&object->oid)); + packet_write_fmt(1, "shallow %s", + oid_to_hex(&object->oid)); register_shallow(object->oid.hash); shallow_nr++; } @@ -654,8 +655,8 @@ static void send_unshallow(const struct object_array *shallows) struct object *object = shallows->objects[i].item; if (object->flags & NOT_SHALLOW) { struct commit_list *parents; - packet_write(1, "unshallow %s", - oid_to_hex(&object->oid)); + packet_write_fmt(1, "unshallow %s", + oid_to_hex(&object->oid)); object->flags &= ~CLIENT_SHALLOW; /* * We want to _register_ "object" as shallow, but we @@ -931,7 +932,7 @@ static int send_ref(const char *refname, const struct object_id *oid, struct strbuf symref_info = STRBUF_INIT; format_symref_info(&symref_info, cb_data); - packet_write(1, "%s %s%c%s%s%s%s%s agent=%s\n", + packet_write_fmt(1, "%s %s%c%s%s%s%s%s agent=%s\n", oid_to_hex(oid), refname_nons, 0, capabilities, (allow_unadvertised_object_request & ALLOW_TIP_SHA1) ? @@ -943,11 +944,11 @@ static int send_ref(const char *refname, const struct object_id *oid, git_user_agent_sanitized()); strbuf_release(&symref_info); } else { - packet_write(1, "%s %s\n", oid_to_hex(oid), refname_nons); + packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), refname_nons); } capabilities = NULL; if (!peel_ref(refname, peeled.hash)) - packet_write(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons); + packet_write_fmt(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons); return 0; } diff --git a/worktree.c b/worktree.c index 5acfe4cd64..f7869f8d60 100644 --- a/worktree.c +++ b/worktree.c @@ -345,6 +345,8 @@ const struct worktree *find_shared_symref(const char *symref, for (i = 0; worktrees[i]; i++) { struct worktree *wt = worktrees[i]; + if (wt->is_bare) + continue; if (wt->is_detached && !strcmp(symref, "HEAD")) { if (is_worktree_being_rebased(wt, target)) { diff --git a/write_or_die.c b/write_or_die.c index 073443247a..eab8c8d0b9 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -1,19 +1,6 @@ #include "cache.h" #include "run-command.h" -static void check_pipe(int err) -{ - if (err == EPIPE) { - if (in_async()) - async_exit(141); - - signal(SIGPIPE, SIG_DFL); - raise(SIGPIPE); - /* Should never happen, but just in case... */ - exit(141); - } -} - /* * Some cases use stdio, but want to flush after the write * to get error handling (and to get better interactive diff --git a/wt-status.c b/wt-status.c index ca5c45f945..a2e9d332d8 100644 --- a/wt-status.c +++ b/wt-status.c @@ -16,6 +16,7 @@ #include "strbuf.h" #include "utf8.h" #include "worktree.h" +#include "lockfile.h" static const char cut_line[] = "------------------------ >8 ------------------------\n"; @@ -437,7 +438,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, switch (p->status) { case DIFF_STATUS_ADDED: - die("BUG: worktree status add???"); + d->mode_worktree = p->two->mode; break; case DIFF_STATUS_DELETED: @@ -547,6 +548,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) setup_revisions(0, NULL, &rev, NULL); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES); + rev.diffopt.ita_invisible_in_index = 1; if (!s->show_untracked_files) DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); if (s->ignore_submodule_arg) { @@ -570,6 +572,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) setup_revisions(0, NULL, &rev, &opt); DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); + rev.diffopt.ita_invisible_in_index = 1; if (s->ignore_submodule_arg) { handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); } else { @@ -605,6 +608,8 @@ static void wt_status_collect_changes_initial(struct wt_status *s) if (!ce_path_match(ce, &s->pathspec, NULL)) continue; + if (ce_intent_to_add(ce)) + continue; it = string_list_insert(&s->change, ce->name); d = it->util; if (!d) { @@ -911,6 +916,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s) init_revisions(&rev, NULL); DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); + rev.diffopt.ita_invisible_in_index = 1; memset(&opt, 0, sizeof(opt)); opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; @@ -2206,3 +2212,80 @@ void wt_status_print(struct wt_status *s) break; } } + +/** + * Returns 1 if there are unstaged changes, 0 otherwise. + */ +int has_unstaged_changes(int ignore_submodules) +{ + struct rev_info rev_info; + int result; + + init_revisions(&rev_info, NULL); + if (ignore_submodules) + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + diff_setup_done(&rev_info.diffopt); + result = run_diff_files(&rev_info, 0); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * Returns 1 if there are uncommitted changes, 0 otherwise. + */ +int has_uncommitted_changes(int ignore_submodules) +{ + struct rev_info rev_info; + int result; + + if (is_cache_unborn()) + return 0; + + init_revisions(&rev_info, NULL); + if (ignore_submodules) + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + add_head_to_pending(&rev_info); + diff_setup_done(&rev_info.diffopt); + result = run_diff_index(&rev_info, 1); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * If the work tree has unstaged or uncommitted changes, dies with the + * appropriate message. + */ +int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently) +{ + struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); + int err = 0; + + hold_locked_index(lock_file, 0); + refresh_cache(REFRESH_QUIET); + update_index_if_able(&the_index, lock_file); + rollback_lock_file(lock_file); + + if (has_unstaged_changes(ignore_submodules)) { + /* TRANSLATORS: the action is e.g. "pull with rebase" */ + error(_("cannot %s: You have unstaged changes."), _(action)); + err = 1; + } + + if (has_uncommitted_changes(ignore_submodules)) { + if (err) + error(_("additionally, your index contains uncommitted changes.")); + else + error(_("cannot %s: Your index contains uncommitted changes."), + _(action)); + err = 1; + } + + if (err) { + if (hint) + error("%s", hint); + if (!gently) + exit(128); + } + + return err; +} diff --git a/wt-status.h b/wt-status.h index e401837707..54fec77032 100644 --- a/wt-status.h +++ b/wt-status.h @@ -128,4 +128,10 @@ void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, . __attribute__((format (printf, 3, 4))) void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); +/* The following functions expect that the caller took care of reading the index. */ +int has_unstaged_changes(int ignore_submodules); +int has_uncommitted_changes(int ignore_submodules); +int require_clean_work_tree(const char *action, const char *hint, + int ignore_submodules, int gently); + #endif /* STATUS_H */ |