Merge branch 'aw/numbered-stash'
authorJunio C Hamano <gitster@pobox.com>
Mon, 31 Oct 2016 20:15:22 +0000 (13:15 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 31 Oct 2016 20:15:22 +0000 (13:15 -0700)
The user always has to say "stash@{$N}" when naming a single
element in the default location of the stash, i.e. reflogs in
refs/stash. The "git stash" command learned to accept "git stash
apply 4" as a short-hand for "git stash apply stash@{4}".

* aw/numbered-stash:
stash: allow stashes to be referenced by index only

138 files changed:
.travis.yml
Documentation/RelNotes/2.10.2.txt
Documentation/RelNotes/2.11.0.txt
Documentation/diff-config.txt
Documentation/diff-options.txt
Documentation/git-interpret-trailers.txt
Documentation/git-ls-files.txt
Documentation/git-merge-base.txt
Documentation/git-submodule.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitmodules.txt
Documentation/howto/revert-a-faulty-merge.txt
Documentation/pretty-formats.txt
apply.c
attr.c
builtin/archive.c
builtin/commit.c
builtin/fetch.c
builtin/ls-files.c
builtin/merge-base.c
builtin/merge.c
builtin/pack-objects.c
builtin/pull.c
builtin/receive-pack.c
builtin/remote-ext.c
builtin/rev-list.c
builtin/rev-parse.c
builtin/revert.c
builtin/submodule--helper.c
builtin/upload-archive.c
cache.h
combine-diff.c
connect.c
contrib/coccinelle/xstrdup_or_null.cocci [new file with mode: 0644]
contrib/credential/libsecret/Makefile [new file with mode: 0644]
contrib/credential/libsecret/git-credential-libsecret.c [new file with mode: 0644]
contrib/long-running-filter/example.pl [new file with mode: 0755]
convert.c
credential-cache--daemon.c
daemon.c
diff-lib.c
diff.c
diff.h
dir.c
dir.h
environment.c
git-compat-util.h
git-gui/GIT-VERSION-GEN
git-gui/Makefile
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/branch_checkout.tcl
git-gui/lib/branch_create.tcl
git-gui/lib/branch_delete.tcl
git-gui/lib/branch_rename.tcl
git-gui/lib/browser.tcl
git-gui/lib/commit.tcl
git-gui/lib/database.tcl
git-gui/lib/diff.tcl
git-gui/lib/error.tcl
git-gui/lib/index.tcl
git-gui/lib/merge.tcl
git-gui/lib/option.tcl
git-gui/lib/remote.tcl
git-gui/lib/remote_add.tcl
git-gui/lib/remote_branch_delete.tcl
git-gui/lib/shortcut.tcl
git-gui/lib/themed.tcl
git-gui/lib/tools.tcl
git-gui/lib/tools_dlg.tcl
git-gui/lib/transport.tcl
git-gui/po/bg.po
git-gui/po/ru.po
git-svn.perl
git.c
gitweb/gitweb.perl
gpg-interface.c
hex.c
http-backend.c
imap-send.c
mailmap.c
pack-bitmap.c
path.c
pathspec.c
perl/Git.pm
perl/Git/SVN.pm
perl/Git/SVN/Editor.pm
perl/Git/SVN/Fetcher.pm
perl/Git/SVN/Migration.pm
pkt-line.c
pkt-line.h
pretty.c
read-cache.c
refs.c
run-command.c
run-command.h
send-pack.c
sequencer.c
sequencer.h
sha1_file.c
sha1_name.c
shallow.c
t/README
t/helper/test-dump-cache-tree.c
t/helper/test-scrap-cache-tree.c
t/perf/p5550-fetch-tags.sh [new file with mode: 0755]
t/t0020-crlf.sh
t/t0021-conversion.sh
t/t0021/rot13-filter.pl [new file with mode: 0755]
t/t0040-parse-options.sh
t/t0060-path-utils.sh
t/t1512-rev-parse-disambiguation.sh
t/t2025-worktree-add.sh
t/t2203-add-intent.sh
t/t3007-ls-files-recurse-submodules.sh [new file with mode: 0755]
t/t3501-revert-cherry-pick.sh
t/t3600-rm.sh
t/t3700-add.sh
t/t4015-diff-whitespace.sh
t/t4254-am-corrupt.sh
t/t6000-rev-list-misc.sh
t/t6010-merge-base.sh
t/t7064-wtstatus-pv2.sh
t/t7510-signed-commit.sh
t/t7513-interpret-trailers.sh
t/t9000/test.pl
t/t9001-send-email.sh
t/test-lib.sh
trailer.c
transport.c
transport.h
upload-pack.c
worktree.c
write_or_die.c
wt-status.c
wt-status.h
index 37a1e1fb6d48935aa22a384797ce82a39340b2ed..9a65514d829987eb8a0274c12f094381d90e87be 100644 (file)
@@ -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
index ed2de0dc20098441e67d069aeac81bb7e5f33860..c4d4397023de1360b77e7c91dc9316eb16973465 100644 (file)
@@ -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.
index 3bc074fc031e6e790e07fe7d5b0dde6d355d108b..3590620000cc0c7bc9c05d2846f21fe354eb06b9 100644 (file)
@@ -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).
index b27a38f8965179fc312531c0500418742cb6641b..58f4bd6afa12240f9e416860ac12d82776815ee9 100644 (file)
@@ -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>`
index 2d77a196269b2d50de9c661fa8869badf155730a..e6215c372c3c3c876125c8ac363a446fd840e112 100644 (file)
@@ -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].
index 93d1db65284bca04c4dd75dd060acf5930ceec44..e99bda6addbb84ddc13bc3d8f9662cf5139aa3a4 100644 (file)
@@ -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
 -------
index 0d933ac355e8a1bef0758b40d293f2f5a7e7c962..446209e2062df8d0b1c99a25eaaf4dc6f15bce3c 100644 (file)
@@ -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.
index 808426faac5b9b607810777a718113e77b836715..b968b64c380b4fbe2663da42355fc9e8fdd24679 100644 (file)
@@ -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
index bf3bb372ee51397b362266d275cd557781585861..d8415734753e0d03f3f6d37bd4b76369d37f5fb0 100644 (file)
@@ -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::
index 7ecca8e247c57befb08050b66dc07bb9fba9a890..80019c584b11b35b2b14da83a31f326634bc6708 100644 (file)
@@ -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
index b8bec711f47918ad3dc7e05d318525819f3a51ab..ab7215eee2b3e098ab505170f7850548fb8f3f27 100644 (file)
@@ -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
index 7aff94020243a02a7785e4890cf99b71d039884d..976243a63e6ee4c4627a8a0db77bab2846d17f40 100644 (file)
@@ -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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
index 10dcc08ff9977ecd0803915aade4b58a27495bbf..8f7c50f330f36f625951db7f4cc4657a31fa7113 100644 (file)
@@ -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
index 462255ed5d8053cbb4dadeec224cb81d14c4ada6..19f59cc88808a1751296af80a0b9700ccec54396 100644 (file)
@@ -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
index 69c289dd0cdb2b344b7051d25c4889b824e21f9c..3bcee2ddb1244c7ef52888d1de2654a688f6702f 100644 (file)
@@ -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
diff --git a/apply.c b/apply.c
index b03d274b522b87e8666bc3703bf0c5f1c9fc9e5b..705cf562f07098aafcd9f6e27105d2105714751f 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -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.",
diff --git a/attr.c b/attr.c
index eec5d7d15a48fb9ca2fdd43d94ae515191d573f1..1fcf042b87c8a39fb967e07c5acb81c0c364d136 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -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;
index a1e3b940c25ac20927d4daefa417d5b626e38a24..49f491413a172b3fa21e1f21f7cbdfedba785084 100644 (file)
@@ -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);
index 1cba3b75c8dc121cf86558aa96726ea99f28eba3..a2baa6ebd5b2dc1090b1bcc87c1e9cf95abd6398 100644 (file)
@@ -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);
index d5329f915e57225e5046f33fae89f6348263f191..b6a5597cbf332fc56b5feabfd29d03c98d4d6686 100644 (file)
@@ -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(&note);
                        if (ref) {
-                               rc |= update_local_ref(ref, what, rm, &note);
+                               rc |= update_local_ref(ref, what, rm, &note,
+                                                      summary_width);
                                free(ref);
                        } else
                                format_display(&note, '*',
                                               *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);
index 197f153f501e4498122896d4e1242d4c5e1651cc..1592290815c8b93701fa9d175d76f7a7ed85f7d2 100644 (file)
@@ -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;
@@ -67,12 +71,25 @@ 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 */
index c0d1822eb3ad371b9ab0b830aed2cc2741a978bd..b572a37c2611b2a8c20b1db7edac68623d371a38 100644 (file)
@@ -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;
 
index a8b57c7d9856518fdcd4c3819f0656544eadf022..b65eeaa87d303b027230bf2479f78c15a02ca08a 100644 (file)
@@ -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)
index 1e7c2a98a5617b8b42422c22051c5f61b2510751..0fd52bd6b4b985b24d93ab7fb9887f57305b49fd 100644 (file)
@@ -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);
index 398aae16c006f5386ba6d65e3ef266b4ddfe1e74..d6e46ee6d0054c152d721e92e354795c6f6002f8 100644 (file)
@@ -17,6 +17,7 @@
 #include "revision.h"
 #include "tempfile.h"
 #include "lockfile.h"
+#include "wt-status.h"
 
 enum rebase_type {
        REBASE_INVALID = -1,
@@ -325,73 +326,6 @@ static int git_pull_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, 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);
index 04ed38e17db67845ec5da34031f10a894d933bdc..e6b3879a5b90034937dfb8eee32f8d2981f6bb53 100644 (file)
@@ -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";
index 88eb8f901367aceffa008cf33b35ca8bee848626..11b48bfb41193387d53588c879ee50431f77b9cd 100644 (file)
@@ -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);
 }
 
index 8479f6ed28aa75d01350fc17eafbded0b52dca84..c43decda70112a9cdb7789dc81394286459aa3af 100644 (file)
@@ -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
index 4da1f1da25b48c1027a3d255f1f86bb260c1fd21..cfb0f1510c59674abe68fb67ff45142b5c92ac89 100644 (file)
@@ -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)
index 4e693808b197780c1029aaf886478795b3a6d0a1..4ca5b515449a355ae04f0ff496a8ee0b7143e29a 100644 (file)
@@ -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;
index 6182eb3197848dc613da1d02280fce0de003addc..4beeda5f9f49d6c2e2f552308b50bacf70ebd421 100644 (file)
@@ -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))
index 2caedf184959e2120d8e30a02f885925ceb412b4..dc872f6a94c09f4390b5898aa499885d7700c737 100644 (file)
@@ -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) {
diff --git a/cache.h b/cache.h
index 05ecb889ebd7c33386a9c3b4c32b32a4ce1f32da..a50a61a19787de94daa9d66caa9fcccca58081fe 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -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);
 
index 8e2a577bdb43297c619a1f4322dab9e4a5572d6a..59501db99a74ab8f237766730045dda4ecd112b2 100644 (file)
@@ -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)) {
index d99d6435fd01754e3b0f23537d7e1b385a26f262..8cb93b0720d9a33d8fc0cddb21c3d1c1a789da96 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -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 (file)
index 0000000..3fceef1
--- /dev/null
@@ -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 (file)
index 0000000..3e67552
--- /dev/null
@@ -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 (file)
index 0000000..4c56979
--- /dev/null
@@ -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 (executable)
index 0000000..3945705
--- /dev/null
@@ -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!
+
+}
index 077f5e601e3283b7b2566bff791290209be4694e..be91358462a72c9d5a6c02f2b0e44585a76d4783 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -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 = &params;
@@ -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)
index 1e5f16a3a1272d4a87478525585951d76307750a..46c5937526a53c3563ea47bfba8195bcf9352c35 100644 (file)
@@ -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,
index 425aad0507f48ca07b11faa05828ea841bdd302f..473e6b6b63c42e59d5a89985b2573ab5c2815157 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -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;
 }
 
index 3007c8524c9b8017cb3dc3f7460e020b0d1aafd7..52447466b5d16c4e8978795b0122e2182e83c5cb 100644 (file)
@@ -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);
diff --git a/diff.c b/diff.c
index 1d304e0550766edf4679f089aafac4de14258482..8981477c436dd11d97714f0ad7e1b9363e328798 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -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)
diff --git a/diff.h b/diff.h
index 25ae60d5ff6c1267dbb6277b55c1029c4792d3f3..e9ccb38c26c7f1b5b0d5932bb98c2875276cf828 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -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
diff --git a/dir.c b/dir.c
index 3bad1ade8d59f2ae5a02d50ae7db85642f9dcb0e..bfa8c8a9a51297c3a969c8706467504cb11ce51b 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -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)
diff --git a/dir.h b/dir.h
index da1a858b3a12daba5bd348fcaa342534edaacdb6..97c83bb383a6b1fb5ce58d5e81dead570b73ac43 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -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)
index cd5aa57179240d615cb0628c9e24f44ac4b8c2d3..0935ec696e530e1b610c71eda0a0a7072d1d656f 100644 (file)
@@ -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;
 
 /*
index 43718dabae88062b39d65f2d6c1f5e35a6eb4800..49ca28cb6b5aa9e06e16c16febde03a7a5e66959 100644 (file)
@@ -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;
index a88b6824b908d89ee185b84ed92b9c122b0118dd..92373d251a84f448bd673bbb8dd888a64fd2f777 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=0.20.GITGUI
+DEF_VER=0.21.GITGUI
 
 LF='
 '
index 4f00bdd3d69babe8a58c4989406eaa6fb5f36a50..fe30be38dc8c534cabf8a0f7145c41aecd1e6989 100644 (file)
@@ -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
 
index 11048c7a0e94f598b168de98d18fda9aea420c7d..5bc21b878d413e27de8e3d54e66a138ff41c7daa 100755 (executable)
@@ -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
 
index b1d15f46218e793866bee9300684663e2c04fd29..a1aeb8b96e2df47c776bd1bb373fac6c5189a3ae 100644 (file)
@@ -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"]
 
index 2e459a82976c6bf77cf1e9f2f1edeae8872a3364..d06037decc1a44ad0f0c153cd461a22a138f35ea 100644 (file)
@@ -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 .]"
        }
index 4bb907705c5f1ea5a33596a9bbbb2ef5ccf66b9a..ba367d551d217f12cfae3c2f99cd19da58f1226f 100644 (file)
@@ -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 .]"
        }
index 867938ec6a63ba77cc6efd700080e194d23d620f..a5051637bbc2388c4aab14479ded14d2c41df314 100644 (file)
@@ -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"
                }
        }
 
index 6e510ec2e39305e6d89de61ab2c925bcbc821a65..3a2d79a9cc3a1ade90db21721f75266d797c26f0 100644 (file)
@@ -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 .]"
        }
index 0328338fda22c90c674630e0351982ff1bb2fbc4..a98298366763d841d3f88e4478f0afdc6e9a5653 100644 (file)
@@ -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 .
index 01d2cc280ba7832840cb8ef64844daa88ebd9670..83620b7cbc3a194fb17d395c17c10c9dd7f1683f 100644 (file)
@@ -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
+       }
+}
index 1f187ed286f877eab84ea2bc13ae210de9660e21..85783081e0d887a7c7857f07670847ef1bc0629d 100644 (file)
@@ -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
 }
index 0d569862156d9e984f3c04f5662880148f92bc21..4cae10a4c7f197aab9ae0bc145254a8d4a1bae50 100644 (file)
@@ -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
index c0fa69af561b7789cdcfea46b14bf6d2646f5962..8968a57f33e37584e3589f03918db2cb89db24e9 100644 (file)
@@ -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
 }
index 3a3e534aeff723b080a7c9a758f51e81e88fae23..b588db11d9fc46b6c4c33274c3c5f969dfe43e5e 100644 (file)
@@ -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 ?_
                }
                }
index 5ab6f8f10210a0e394315e247a2895d4577569f4..9f253db5b37e5633c19914248a58a3f2feb3c512 100644 (file)
@@ -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 .]"
        }
index b5b6b2fea6c29ce7f1deb9e2c23060b92fbd83e4..e43971bfa3e0084e1a306fc82111895a301f905e 100644 (file)
@@ -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 \
index 4e5c7844188ee2eab0c9f6e00835b99c69bbdfc2..ef77ed7399c5b0cc1bdd06f1471d275ffd0ab3ad 100644 (file)
@@ -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
index 50029d0ceeee0fdab884754022e3015b3925f6f7..480a6b30d0a9c9aa4f667a618b0e852d637432dd 100644 (file)
@@ -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 .]"
        }
index fcc06d03a1dfc4919f762d70e2809f29dab77390..5ba9fcadd17f315a4a47220f91f81ba964d64b08 100644 (file)
@@ -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 .]"
        }
index 78878ef89d11210d614fc8b3d2957705611bdaa3..97d1d7aa02686606912dd5ff7b512343083a17c0 100644 (file)
@@ -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 != {}} {
index 8b88d3678b7ddd802260f47bcfeff8a895d93f3f..351a712c8c503eb5a04466afb8aa5acd7f0259f5 100644 (file)
@@ -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} {
index 6ec94113db7d8c75d78e5300469bb8154e37c940..413f1a170079e0cec78ecdbd1adb7baeb83406f2 100644 (file)
@@ -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) }
index 7eeda9daf23161a0abbe4abcc857ebfb0e18f9de..c05413ce432d2d37cc2461f1f1a739001c2220f4 100644 (file)
@@ -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 .
index e5d211edea05cbfb856e81a9d683a5fb0f4f4b20..a1a424aab540663957c96a37cd277ae666287051 100644 (file)
@@ -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
 }
index 4d9b039dc2813d6771915a61bfc3c353f16bb5a9..5af78f15a86db955903979900190a6f7bbbf7882 100644 (file)
@@ -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 "Ð\92идÑ\8aÑ\82 Ð½Ð° Ñ\84айла Ðµ Ñ\81менен, Ð½Ð¾ Ð² Ð¸Ð½Ð´ÐµÐºÑ\81а Ðµ Ð²Ñ\81е Ð¾Ñ\89е Ñ\81Ñ\82аÑ\80иÑ\8fÑ\82"
+msgstr "Ð\92идÑ\8aÑ\82 Ð½Ð° Ñ\84айла Ðµ Ñ\81менен, Ð½Ð¾ Ð½Ð¾Ð²Ð¸Ñ\8fÑ\82 Ð²Ð¸Ð´ Ð½Ðµ Ðµ Ð² Ð¸Ð½Ð´ÐµÐºÑ\81а"
 
-#: 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 "Данни за отдалеченото хранилище"
-
-#: lib/remote_add.tcl:41 lib/tools_dlg.tcl:51 lib/branch_create.tcl:44
-msgid "Name:"
-msgstr "Име:"
+msgid "%s (%s): File Viewer"
+msgstr "%s (%s): Преглед на файлове"
 
-#: lib/remote_add.tcl:50
-msgid "Location:"
-msgstr "Ð\9cеÑ\81Ñ\82оположение:"
+#: lib/blame.tcl:79
+msgid "Commit:"
+msgstr "Ð\9fодаване:"
 
-#: lib/remote_add.tcl:60
-msgid "Further Action"
-msgstr "СледваÑ\89о Ð´ÐµÐ¹Ñ\81Ñ\82вие"
+#: lib/blame.tcl:280
+msgid "Copy Commit"
+msgstr "Ð\9aопиÑ\80ане Ð½Ð° Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½е"
 
-#: lib/remote_add.tcl:63
-msgid "Fetch Immediately"
-msgstr "Ð\9dезабавно Ð´Ð¾Ñ\81Ñ\82авÑ\8fне"
+#: lib/blame.tcl:284
+msgid "Find Text..."
+msgstr "ТÑ\8aÑ\80Ñ\81ене Ð½Ð° Ñ\82екÑ\81Ñ\82â\80¦"
 
-#: lib/remote_add.tcl:69
-msgid "Initialize Remote Repository and Push"
-msgstr "Ð\98ниÑ\86иализиÑ\80ане Ð½Ð° Ð¾Ñ\82далеÑ\87еноÑ\82о Ñ\85Ñ\80анилиÑ\89е Ð¸ Ð¸Ð·Ñ\82лаÑ\81кване Ð½Ð° Ð¿Ñ\80омениÑ\82е"
+#: lib/blame.tcl:288
+msgid "Goto Line..."
+msgstr "Ð\9aÑ\8aм Ñ\80едâ\80¦"
 
-#: lib/remote_add.tcl:75
-msgid "Do Nothing Else Now"
-msgstr "Ð\94а Ð½Ðµ Ñ\81е Ð¿Ñ\80ави Ð½Ð¸Ñ\89о"
+#: lib/blame.tcl:297
+msgid "Do Full Copy Detection"
+msgstr "Ð\9fÑ\8aлно Ñ\82Ñ\8aÑ\80Ñ\81ене Ð½Ð° ÐºÐ¾Ð¿Ð¸Ñ\80ане"
 
-#: lib/remote_add.tcl:100
-msgid "Please supply a remote name."
-msgstr "Ð\97адайÑ\82е Ð¸Ð¼Ðµ Ð·Ð° Ð¾Ñ\82далеÑ\87еноÑ\82о Ñ\85Ñ\80анилиÑ\89е."
+#: lib/blame.tcl:301
+msgid "Show History Context"
+msgstr "Ð\9fоказване Ð½Ð° ÐºÐ¾Ð½Ñ\82екÑ\81Ñ\82а Ð¾Ñ\82 Ð¸Ñ\81Ñ\82оÑ\80иÑ\8fÑ\82а"
 
-#: 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 "Ð\9dеÑ\83Ñ\81пеÑ\88но Ð´Ð¾Ð±Ð°Ð²Ñ\8fне Ð½Ð° Ð¾Ñ\82далеÑ\87еноÑ\82о Ñ\85Ñ\80анилиÑ\89е â\80\9e%sâ\80\9c Ð¾Ñ\82 Ð°Ð´Ñ\80еÑ\81 â\80\9e%sâ\80\9c."
+msgid "Reading %s..."
+msgstr "ЧеÑ\82е Ñ\81е â\80\9e%sâ\80\9câ\80¦"
 
-#: 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 "СÑ\82аÑ\80Ñ\82иÑ\80анеâ\80¦"
+#: lib/blame.tcl:840
+msgid "Annotation process is already running."
+msgstr "Ð\92 Ð¼Ð¾Ð¼ÐµÐ½Ñ\82а Ñ\82еÑ\87е Ð¿Ñ\80оÑ\86еÑ\81 Ð½Ð° Ð°Ð½Ð¾Ñ\82иÑ\80ане."
 
-#: lib/browser.tcl:27
-msgid "File Browser"
-msgstr "Файлов Ð±Ñ\80аÑ\83зÑ\8aÑ\80"
+#: lib/blame.tcl:879
+msgid "Running thorough copy detection..."
+msgstr "Ð\98зпÑ\8aлнÑ\8fва Ñ\81е Ñ\86Ñ\8fлоÑ\81Ñ\82ен Ð¿Ñ\80оÑ\86еÑ\81 Ð½Ð° Ð¾Ñ\82кÑ\80иване Ð½Ð° ÐºÐ¾Ð¿Ð¸Ñ\80анеâ\80¦"
 
-#: 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 "Разглеждане Ð½Ð° Ñ\84айловеÑ\82е Ð² ÐºÐ»Ð¾Ð½Ð°"
+#: lib/blame.tcl:1004
+msgid "Committer:"
+msgstr "Ð\9fодал:"
 
-#: 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 "Ð\92еÑ\80Ñ\81иÑ\8f"
+#: lib/blame.tcl:1057
+msgid "Cannot find HEAD commit:"
+msgstr "Ð\9fодаванеÑ\82о Ð·Ð° Ð²Ñ\80Ñ\8aÑ\85 â\80\9eHEADâ\80\9c Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ñ\81е Ð¾Ñ\82кÑ\80ие:"
 
-#: 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
+#: lib/branch_create.tcl:23
 #, tcl-format
-msgid "Fetching new changes from %s"
-msgstr "Доставяне на промените от „%s“"
+msgid "%s (%s): Create Branch"
+msgstr "%s (%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/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 "Ð\9aлони Ð·Ð° Ð¸Ð·Ñ\82лаÑ\81кване"
+#: lib/branch_create.tcl:66
+msgid "Starting Revision"
+msgstr "Ð\9dаÑ\87ална Ð²ÐµÑ\80Ñ\81иÑ\8f"
 
-#: lib/transport.tcl:147
-msgid "Source Branches"
-msgstr "Ð\9aлони-изÑ\82оÑ\87ниÑ\86и"
+#: lib/branch_create.tcl:72
+msgid "Update Existing Branch:"
+msgstr "Ð\9eбновÑ\8fване Ð½Ð° Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ваÑ\89 ÐºÐ»Ð¾Ð½:"
 
-#: lib/transport.tcl:162
-msgid "Destination Repository"
-msgstr "Целево Ñ\85Ñ\80анилиÑ\89е"
+#: lib/branch_create.tcl:75
+msgid "No"
+msgstr "Ð\9dе"
 
-#: lib/transport.tcl:165 lib/remote_branch_delete.tcl:51
-msgid "Remote:"
-msgstr "Ð\9eÑ\82далеÑ\87ено Ñ\85Ñ\80анилиÑ\89е:"
+#: lib/branch_create.tcl:80
+msgid "Fast Forward Only"
+msgstr "Само Ñ\82Ñ\80ивиално Ð¿Ñ\80евÑ\8aÑ\80Ñ\82аÑ\89о Ñ\81ливане"
 
-#: lib/transport.tcl:187 lib/remote_branch_delete.tcl:72
-msgid "Arbitrary Location:"
-msgstr "Ð\9fÑ\80оизволно Ð¼ÐµÑ\81Ñ\82оположение:"
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:571
+msgid "Reset"
+msgstr "Ð\9eÑ\82наÑ\87ало"
 
-#: lib/transport.tcl:205
-msgid "Transfer Options"
-msgstr "Ð\9dаÑ\81Ñ\82Ñ\80ойки Ð¿Ñ\80и Ð¿Ñ\80енаÑ\81Ñ\8fнеÑ\82о"
+#: lib/branch_create.tcl:97
+msgid "Checkout After Creation"
+msgstr "Ð\9fÑ\80еминаване ÐºÑ\8aм ÐºÐ»Ð¾Ð½Ð° Ñ\81лед Ñ\81Ñ\8aздаванеÑ\82о Ð¼Ñ\83"
 
-#: 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 "Ð\92клÑ\8eÑ\87ване Ð½Ð° ÐµÑ\82икеÑ\82иÑ\82е"
+#: lib/branch_create.tcl:154 lib/branch_rename.tcl:92
+msgid "Please supply a branch name."
+msgstr "Ð\94айÑ\82е Ð¸Ð¼Ðµ Ð½Ð° ÐºÐ»Ð¾Ð½Ð°."
 
-#: 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 "Ð\9fÑ\80емаÑ\85ване Ð½Ð° Ð¾Ñ\82далеÑ\87ено Ñ\85Ñ\80анилиÑ\89е"
+#: lib/branch_delete.tcl:21
+msgid "Delete Local Branch"
+msgstr "Ð\98зÑ\82Ñ\80иване Ð½Ð° Ð»Ð¾ÐºÐ°Ð»ÐµÐ½ ÐºÐ»Ð¾Ð½"
 
-#: lib/remote.tcl:223
-msgid "Prune from"
-msgstr "Ð\9eкаÑ\81Ñ\82Ñ\80Ñ\8fне Ð¾Ñ\82"
+#: lib/branch_delete.tcl:39
+msgid "Local Branches"
+msgstr "Ð\9bокални ÐºÐ»Ð¾Ð½Ð¸"
 
-#: lib/remote.tcl:228
-msgid "Fetch from"
-msgstr "Ð\94оÑ\81Ñ\82авÑ\8fне Ð¾Ñ\82"
+#: lib/branch_delete.tcl:51
+msgid "Delete Only If Merged Into"
+msgstr "Ð\98зÑ\82Ñ\80иване, Ñ\81амо Ð°ÐºÐ¾ Ð¿Ñ\80омениÑ\82е Ñ\81а Ñ\81леÑ\82и Ð¸ Ð´Ñ\80Ñ\83гаде"
 
-#: lib/sshkey.tcl:31
-msgid "No keys found."
-msgstr "Ð\9dе Ñ\81а Ð¾Ñ\82кÑ\80иÑ\82и ÐºÐ»Ñ\8eÑ\87ове."
+#: lib/branch_delete.tcl:53 lib/remote_branch_delete.tcl:120
+msgid "Always (Do not perform merge checks)"
+msgstr "Ð\92инаги (без Ð¿Ñ\80овеÑ\80ка Ð·Ð° Ñ\81ливане)"
 
-#: 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,426 +898,777 @@ 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/browser.tcl:17
+msgid "Starting..."
+msgstr "Стартиране…"
 
-#: lib/choose_font.tcl:76
-msgid "Font Size"
-msgstr "Размер"
+#: lib/browser.tcl:27
+#, tcl-format
+msgid "%s (%s): File Browser"
+msgstr "%s (%s): Файлов браузър"
 
-#: lib/choose_font.tcl:93
-msgid "Font Example"
-msgstr "Мостра"
+#: lib/browser.tcl:132 lib/browser.tcl:149
+#, tcl-format
+msgid "Loading %s..."
+msgstr "Зареждане на „%s“…"
 
-#: 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:193
+msgid "[Up To Parent]"
+msgstr "[Към родителя]"
 
-#: lib/option.tcl:11
+#: lib/browser.tcl:275
 #, tcl-format
-msgid "Invalid global encoding '%s'"
-msgstr "Неправилно глобално кодиране „%s“"
+msgid "%s (%s): Browse Branch Files"
+msgstr "%s (%s): Разглеждане на файловете в клона"
 
-#: lib/option.tcl:19
-#, tcl-format
-msgid "Invalid repo encoding '%s'"
-msgstr "Неправилно кодиране „%s“ на хранилището"
+#: lib/browser.tcl:282
+msgid "Browse Branch Files"
+msgstr "Разглеждане на файловете в клона"
 
-#: lib/option.tcl:119
-msgid "Restore Defaults"
-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:123
-msgid "Save"
-msgstr "Запазване"
+#: lib/checkout_op.tcl:85
+#, tcl-format
+msgid "Fetching %s from %s"
+msgstr "Доставяне на „%s“ от „%s“"
 
-#: lib/option.tcl:133
+#: lib/checkout_op.tcl:133
 #, tcl-format
-msgid "%s Repository"
-msgstr "Хранилище „%s“"
+msgid "fatal: Cannot resolve %s"
+msgstr "фатална грешка: „%s“ не може да се открие"
 
-#: lib/option.tcl:134
-msgid "Global (All Repositories)"
-msgstr "Глобално (за всички хранилища)"
+#: lib/checkout_op.tcl:146 lib/console.tcl:81 lib/database.tcl:30
+#: lib/sshkey.tcl:55
+msgid "Close"
+msgstr "Затваряне"
 
-#: lib/option.tcl:140
-msgid "User Name"
-msgstr "Потребителско име"
+#: lib/checkout_op.tcl:175
+#, tcl-format
+msgid "Branch '%s' does not exist."
+msgstr "Клонът „%s“ не съществува."
 
-#: lib/option.tcl:141
-msgid "Email Address"
-msgstr "Адрес на е-поща"
+#: lib/checkout_op.tcl:194
+#, tcl-format
+msgid "Failed to configure simplified git-pull for '%s'."
+msgstr "Неуспешно настройване на опростен git-pull за „%s“."
 
-#: lib/option.tcl:143
-msgid "Summarize Merge Commits"
-msgstr "Обобщаване на подаванията при сливане"
+#: 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/option.tcl:144
-msgid "Merge Verbosity"
-msgstr "Подробности при сливанията"
+#: lib/checkout_op.tcl:243
+#, tcl-format
+msgid "Merge strategy '%s' not supported."
+msgstr "Стратегия за сливане  „%s“ не се поддържа."
 
-#: lib/option.tcl:145
-msgid "Show Diffstat After Merge"
-msgstr "Извеждане на статистика след сливанията"
+#: lib/checkout_op.tcl:262
+#, tcl-format
+msgid "Failed to update '%s'."
+msgstr "Неуспешно обновяване на „%s“."
 
-#: lib/option.tcl:146
-msgid "Use Merge Tool"
-msgstr "Ð\98зползване Ð½Ð° Ð¿Ñ\80огÑ\80ама Ð·Ð° Ñ\81ливане"
+#: lib/checkout_op.tcl:274
+msgid "Staging area (index) is already locked."
+msgstr "Ð\98ндекÑ\81Ñ\8aÑ\82 Ð²ÐµÑ\87е Ðµ Ð·Ð°ÐºÐ»Ñ\8eÑ\87ен."
 
-#: lib/option.tcl:148
-msgid "Trust File Modification Timestamps"
-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/option.tcl:149
-msgid "Prune Tracking Branches During Fetch"
-msgstr "Окастряне на следящите клонове при доставяне"
+#: lib/checkout_op.tcl:345
+#, tcl-format
+msgid "Updating working directory to '%s'..."
+msgstr "Работната директория се привежда към „%s“…"
 
-#: lib/option.tcl:150
-msgid "Match Tracking Branches"
-msgstr "Напасване на следящите клонове"
+#: lib/checkout_op.tcl:346
+msgid "files checked out"
+msgstr "файла са изтеглени"
 
-#: lib/option.tcl:151
-msgid "Use Textconv For Diffs and Blames"
+#: lib/checkout_op.tcl:376
+#, tcl-format
+msgid "Aborted checkout of '%s' (file level merging is required)."
 msgstr ""
-"Преобразуване на текста с „textconv“ при анотиране и извеждане на разлики"
+"Преустановяване на изтеглянето на „%s“ (необходимо е пофайлово сливане)."
 
-#: lib/option.tcl:152
-msgid "Blame Copy Only On Changed Files"
-msgstr "Ð\90ноÑ\82иÑ\80ане Ð½Ð° ÐºÐ¾Ð¿Ð¸ÐµÑ\82о Ñ\81амо Ð¿Ð¾ Ð¿Ñ\80оменениÑ\82е Ñ\84айлове"
+#: lib/checkout_op.tcl:377
+msgid "File level merge required."
+msgstr "Ð\9dеобÑ\85одимо Ðµ Ð¿Ð¾Ñ\84айлово Ñ\81ливане."
 
-#: lib/option.tcl:153
-msgid "Maximum Length of Recent Repositories List"
-msgstr "Максимална дължина на списъка със скоро ползвани хранилища"
+#: lib/checkout_op.tcl:381
+#, tcl-format
+msgid "Staying on branch '%s'."
+msgstr "Оставане върху клона „%s“."
 
-#: lib/option.tcl:154
-msgid "Minimum Letters To Blame Copy On"
-msgstr "Минимален брой знаци за анотиране на копието"
+#: 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/option.tcl:155
-msgid "Blame History Context Radius (days)"
-msgstr "Исторически обхват за анотиране в дни"
+#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
+#, tcl-format
+msgid "Checked out '%s'."
+msgstr "„%s“ е изтеглен."
 
-#: lib/option.tcl:156
-msgid "Number of Diff Context Lines"
-msgstr "Брой редове за контекста при извеждане на разликите"
+#: lib/checkout_op.tcl:535
+#, tcl-format
+msgid "Resetting '%s' to '%s' will lose the following commits:"
+msgstr ""
+"Зануляването на „%s“ към „%s“ ще доведе до загубването на следните подавания:"
 
-#: lib/option.tcl:157
-msgid "Additional Diff Parameters"
-msgstr "Ð\94опÑ\8aлниÑ\82елни Ð°Ñ\80гÑ\83менÑ\82и ÐºÑ\8aм â\80\9egit diffâ\80\9c"
+#: lib/checkout_op.tcl:557
+msgid "Recovering lost commits may not be easy."
+msgstr "Ð\92Ñ\8aзÑ\81Ñ\82ановÑ\8fванеÑ\82о Ð½Ð° Ð·Ð°Ð³Ñ\83бениÑ\82е Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ\8f Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ðµ Ñ\82Ñ\80Ñ\83дно."
 
-#: lib/option.tcl:158
-msgid "Commit Message Text Width"
-msgstr "Широчина на текста на съобщението при подаване"
+#: lib/checkout_op.tcl:562
+#, tcl-format
+msgid "Reset '%s'?"
+msgstr "Зануляване на „%s“?"
 
-#: lib/option.tcl:159
-msgid "New Branch Name Template"
-msgstr "Шаблон Ð·Ð° Ð¸Ð¼ÐµÑ\82о Ð½Ð° Ð½Ð¾Ð²Ð¸Ñ\82е ÐºÐ»Ð¾Ð½Ð¸"
+#: lib/checkout_op.tcl:567 lib/merge.tcl:170 lib/tools_dlg.tcl:336
+msgid "Visualize"
+msgstr "Ð\92изÑ\83ализаÑ\86иÑ\8f"
 
-#: lib/option.tcl:160
-msgid "Default File Contents Encoding"
-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/option.tcl:161
-msgid "Warn before committing to a detached head"
-msgstr "Ð\9fÑ\80едÑ\83пÑ\80еждаване Ð¿Ñ\80и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½ÐµÑ\82о Ð¿Ñ\80и Ð½ÐµÑ\81вÑ\8aÑ\80зан Ð²Ñ\80Ñ\8aÑ\85"
+#: lib/choose_font.tcl:41
+msgid "Select"
+msgstr "Ð\98збоÑ\80"
 
-#: lib/option.tcl:162
-msgid "Staging of untracked files"
-msgstr "Ð\92каÑ\80ване Ð½Ð° Ð½ÐµÑ\81ледени Ñ\84айлове Ð² Ð¸Ð½Ð´ÐµÐºÑ\81а"
+#: lib/choose_font.tcl:55
+msgid "Font Family"
+msgstr "ШÑ\80иÑ\84Ñ\82"
 
-#: lib/option.tcl:163
-msgid "Show untracked files"
-msgstr "Ð\9fоказване Ð½Ð° Ð½ÐµÑ\81ледениÑ\82е Ñ\84айлове"
+#: lib/choose_font.tcl:76
+msgid "Font Size"
+msgstr "РазмеÑ\80"
 
-#: lib/option.tcl:164
-msgid "Tab spacing"
-msgstr "РазмеÑ\80 Ð½Ð° Ñ\82абÑ\83лаÑ\86иÑ\8fÑ\82а Ð² Ð¸Ð½Ñ\82еÑ\80вали"
+#: lib/choose_font.tcl:93
+msgid "Font Example"
+msgstr "Ð\9cоÑ\81Ñ\82Ñ\80а"
 
-#: lib/option.tcl:210
-msgid "Change"
-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/choose_repository.tcl:33
+msgid "Git Gui"
+msgstr "ГПИ на Git"
+
+#: lib/choose_repository.tcl:92 lib/choose_repository.tcl:412
+msgid "Create New Repository"
+msgstr "Създаване на ново хранилище"
+
+#: lib/choose_repository.tcl:98
+msgid "New..."
+msgstr "Ново…"
+
+#: lib/choose_repository.tcl:105 lib/choose_repository.tcl:496
+msgid "Clone Existing Repository"
+msgstr "Клониране на съществуващо хранилище"
+
+#: lib/choose_repository.tcl:116
+msgid "Clone..."
+msgstr "Клониране…"
+
+#: lib/choose_repository.tcl:123 lib/choose_repository.tcl:1064
+msgid "Open Existing Repository"
+msgstr "Отваряне на съществуващо хранилище"
+
+#: lib/choose_repository.tcl:129
+msgid "Open..."
+msgstr "Отваряне…"
+
+#: lib/choose_repository.tcl:142
+msgid "Recent Repositories"
+msgstr "Скоро ползвани"
+
+#: lib/choose_repository.tcl:148
+msgid "Open Recent Repository:"
+msgstr "Отваряне на хранилище ползвано наскоро:"
+
+#: lib/choose_repository.tcl:316 lib/choose_repository.tcl:323
+#: lib/choose_repository.tcl:330
+#, tcl-format
+msgid "Failed to create repository %s:"
+msgstr "Неуспешно създаване на хранилището „%s“:"
+
+#: lib/choose_repository.tcl:417
+msgid "Directory:"
+msgstr "Директория:"
+
+#: lib/choose_repository.tcl:447 lib/choose_repository.tcl:573
+#: lib/choose_repository.tcl:1098
+msgid "Git Repository"
+msgstr "Хранилище на Git"
+
+#: lib/choose_repository.tcl:472
+#, tcl-format
+msgid "Directory %s already exists."
+msgstr "Вече съществува директория „%s“."
+
+#: lib/choose_repository.tcl:476
+#, tcl-format
+msgid "File %s already exists."
+msgstr "Вече съществува файл „%s“."
+
+#: lib/choose_repository.tcl:491
+msgid "Clone"
+msgstr "Клониране"
+
+#: lib/choose_repository.tcl:504
+msgid "Source Location:"
+msgstr "Адрес на източника:"
+
+#: lib/choose_repository.tcl:513
+msgid "Target Directory:"
+msgstr "Целева директория:"
+
+#: lib/choose_repository.tcl:523
+msgid "Clone Type:"
+msgstr "Вид клониране:"
+
+#: lib/choose_repository.tcl:528
+msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
+msgstr "Стандартно (бързо, частично споделяне на файлове, твърди връзки)"
+
+#: lib/choose_repository.tcl:533
+msgid "Full Copy (Slower, Redundant Backup)"
+msgstr "Пълно (бавно, пълноценно резервно копие)"
+
+#: lib/choose_repository.tcl:538
+msgid "Shared (Fastest, Not Recommended, No Backup)"
+msgstr "Споделено (най-бързо, не се препоръчва, не прави резервно копие)"
+
+#: lib/choose_repository.tcl:545
+msgid "Recursively clone submodules too"
+msgstr "Рекурсивно клониране и на подмодулите"
+
+#: lib/choose_repository.tcl:579 lib/choose_repository.tcl:626
+#: lib/choose_repository.tcl:772 lib/choose_repository.tcl:842
+#: lib/choose_repository.tcl:1104 lib/choose_repository.tcl:1112
+#, tcl-format
+msgid "Not a Git repository: %s"
+msgstr "Това не е хранилище на Git: %s"
+
+#: lib/choose_repository.tcl:615
+msgid "Standard only available for local repository."
+msgstr "Само локални хранилища могат да се клонират стандартно"
+
+#: lib/choose_repository.tcl:619
+msgid "Shared only available for local repository."
+msgstr "Само локални хранилища могат да се клонират споделено"
+
+#: lib/choose_repository.tcl:640
+#, tcl-format
+msgid "Location %s already exists."
+msgstr "Местоположението „%s“ вече съществува."
+
+#: lib/choose_repository.tcl:651
+msgid "Failed to configure origin"
+msgstr "Неуспешно настройване на хранилището-източник"
+
+#: lib/choose_repository.tcl:663
+msgid "Counting objects"
+msgstr "Преброяване на обекти"
+
+#: lib/choose_repository.tcl:664
+msgid "buckets"
+msgstr "клетки"
+
+#: lib/choose_repository.tcl:688
+#, tcl-format
+msgid "Unable to copy objects/info/alternates: %s"
+msgstr "Обектите/информацията/синонимите не могат да бъдат копирани: %s"
+
+#: lib/choose_repository.tcl:724
+#, tcl-format
+msgid "Nothing to clone from %s."
+msgstr "Няма какво да се клонира от „%s“."
+
+#: lib/choose_repository.tcl:726 lib/choose_repository.tcl:940
+#: lib/choose_repository.tcl:952
+msgid "The 'master' branch has not been initialized."
+msgstr "Основният клон — „master“ не е инициализиран."
+
+#: lib/choose_repository.tcl:739
+msgid "Hardlinks are unavailable.  Falling back to copying."
+msgstr "Не се поддържат твърди връзки. Преминава се към копиране."
+
+#: lib/choose_repository.tcl:751
+#, tcl-format
+msgid "Cloning from %s"
+msgstr "Клониране на „%s“"
+
+#: lib/choose_repository.tcl:782
+msgid "Copying objects"
+msgstr "Копиране на обекти"
+
+#: lib/choose_repository.tcl:783
+msgid "KiB"
+msgstr "KiB"
+
+#: lib/choose_repository.tcl:807
+#, tcl-format
+msgid "Unable to copy object: %s"
+msgstr "Неуспешно копиране на обект: %s"
+
+#: lib/choose_repository.tcl:817
+msgid "Linking objects"
+msgstr "Създаване на връзки към обектите"
+
+#: lib/choose_repository.tcl:818
+msgid "objects"
+msgstr "обекти"
+
+#: lib/choose_repository.tcl:826
+#, tcl-format
+msgid "Unable to hardlink object: %s"
+msgstr "Неуспешно създаване на твърда връзка към обект: %s"
+
+#: lib/choose_repository.tcl:881
+msgid "Cannot fetch branches and objects.  See console output for details."
+msgstr ""
+"Клоните и обектите не могат да бъдат изтеглени. За повече информация "
+"погледнете изхода на конзолата."
+
+#: lib/choose_repository.tcl:892
+msgid "Cannot fetch tags.  See console output for details."
+msgstr ""
+"Етикетите не могат да бъдат изтеглени. За повече информация погледнете "
+"изхода на конзолата."
+
+#: lib/choose_repository.tcl:916
+msgid "Cannot determine HEAD.  See console output for details."
+msgstr ""
+"Върхът „HEAD“ не може да бъде определен. За повече информация погледнете "
+"изхода на конзолата."
+
+#: lib/choose_repository.tcl:925
+#, tcl-format
+msgid "Unable to cleanup %s"
+msgstr "„%s“ не може да се зачисти"
+
+#: lib/choose_repository.tcl:931
+msgid "Clone failed."
+msgstr "Неуспешно клониране."
+
+#: lib/choose_repository.tcl:938
+msgid "No default branch obtained."
+msgstr "Не е получен клон по подразбиране."
+
+#: lib/choose_repository.tcl:949
+#, tcl-format
+msgid "Cannot resolve %s as a commit."
+msgstr "Няма подаване отговарящо на „%s“."
+
+#: lib/choose_repository.tcl:961
+msgid "Creating working directory"
+msgstr "Създаване на работната директория"
+
+#: lib/choose_repository.tcl:962 lib/index.tcl:70 lib/index.tcl:136
+#: lib/index.tcl:207
+msgid "files"
+msgstr "файлове"
+
+#: lib/choose_repository.tcl:981
+msgid "Cannot clone submodules."
+msgstr "Подмодулите не могат да се клонират."
+
+#: lib/choose_repository.tcl:990
+msgid "Cloning submodules"
+msgstr "Клониране на подмодули"
+
+#: lib/choose_repository.tcl:1015
+msgid "Initial file checkout failed."
+msgstr "Неуспешно първоначално изтегляне."
+
+#: lib/choose_repository.tcl:1059
+msgid "Open"
+msgstr "Отваряне"
+
+#: lib/choose_repository.tcl:1069
+msgid "Repository:"
+msgstr "Хранилище:"
+
+#: lib/choose_repository.tcl:1118
+#, tcl-format
+msgid "Failed to open repository %s:"
+msgstr "Неуспешно отваряне на хранилището „%s“:"
+
+#: lib/choose_rev.tcl:52
+msgid "This Detached Checkout"
+msgstr "Това несвързано изтегляне"
+
+#: lib/choose_rev.tcl:60
+msgid "Revision Expression:"
+msgstr "Израз за версия:"
+
+#: lib/choose_rev.tcl:72
+msgid "Local Branch"
+msgstr "Локален клон"
 
-#: lib/option.tcl:254
-msgid "Spelling Dictionary:"
-msgstr "Ð\9fÑ\80авопиÑ\81ен Ñ\80еÑ\87ник:"
+#: lib/choose_rev.tcl:77
+msgid "Tracking Branch"
+msgstr "СледÑ\8fÑ\89 ÐºÐ»Ð¾Ð½"
 
-#: lib/option.tcl:284
-msgid "Change Font"
-msgstr "СмÑ\8fна Ð½Ð° Ñ\88Ñ\80иÑ\84Ñ\82а"
+#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544
+msgid "Tag"
+msgstr "Ð\95Ñ\82икеÑ\82"
 
-#: lib/option.tcl:288
+#: lib/choose_rev.tcl:321
 #, tcl-format
-msgid "Choose %s"
-msgstr "Ð\98збоÑ\80 Ð½Ð° â\80\9e%sâ\80\9c"
+msgid "Invalid revision: %s"
+msgstr "Ð\9dепÑ\80авилна Ð²ÐµÑ\80Ñ\81иÑ\8f: %s"
 
-#: lib/option.tcl:294
-msgid "pt."
-msgstr "тчк."
+#: lib/choose_rev.tcl:342
+msgid "No revision selected."
+msgstr "Не е избрана версия."
 
-#: lib/option.tcl:308
-msgid "Preferences"
-msgstr "Ð\9dаÑ\81Ñ\82Ñ\80ойки"
+#: lib/choose_rev.tcl:350
+msgid "Revision expression is empty."
+msgstr "Ð\98зÑ\80азÑ\8aÑ\82 Ð·Ð° Ð²ÐµÑ\80Ñ\81иÑ\8f Ðµ Ð¿Ñ\80азен."
 
-#: lib/option.tcl:345
-msgid "Failed to completely save options:"
-msgstr "Ð\9dеÑ\83Ñ\81пеÑ\88но Ð·Ð°Ð¿Ð°Ð·Ð²Ð°Ð½Ðµ Ð½Ð° Ð½Ð°Ñ\81Ñ\82Ñ\80ойкиÑ\82е:"
+#: lib/choose_rev.tcl:537
+msgid "Updated"
+msgstr "Ð\9eбновен"
 
-#: lib/encoding.tcl:443
-msgid "Default"
-msgstr "СÑ\82андаÑ\80Ñ\82ноÑ\82о"
+#: lib/choose_rev.tcl:565
+msgid "URL"
+msgstr "Ð\90дÑ\80еÑ\81"
 
-#: lib/encoding.tcl:448
-#, tcl-format
-msgid "System (%s)"
-msgstr "Системното (%s)"
+#: lib/commit.tcl:9
+msgid ""
+"There is nothing to amend.\n"
+"\n"
+"You are about to create the initial commit.  There is no commit before this "
+"to amend.\n"
+msgstr ""
+"Няма какво да се поправи.\n"
+"\n"
+"Ще създадете първоначалното подаване. Преди него няма други подавания, които "
+"да поправите.\n"
 
-#: lib/encoding.tcl:459 lib/encoding.tcl:465
-msgid "Other"
-msgstr "Друго"
+#: 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"
 
-#: lib/mergetool.tcl:8
-msgid "Force resolution to the base version?"
-msgstr "Ð\94а Ñ\81е Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð° Ð±Ð°Ð·Ð¾Ð²Ð°Ñ\82а Ð²ÐµÑ\80Ñ\81иÑ\8f"
+#: lib/commit.tcl:48
+msgid "Error loading commit data for amend:"
+msgstr "Ð\93Ñ\80еÑ\88ка Ð¿Ñ\80и Ð·Ð°Ñ\80еждане Ð½Ð° Ð´Ð°Ð½Ð½Ð¸Ñ\82е Ð¾Ñ\82 Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ, ÐºÐ¾Ð¸Ñ\82о Ð´Ð° Ñ\81е Ð¿Ð¾Ð¿Ñ\80авÑ\8fÑ\82:"
 
-#: lib/mergetool.tcl:9
-msgid "Force resolution to this branch?"
-msgstr "Ð\94а Ñ\81е Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð° Ð²ÐµÑ\80Ñ\81иÑ\8fÑ\82а Ð¾Ñ\82 Ñ\82ози ÐºÐ»Ð¾Ð½"
+#: lib/commit.tcl:75
+msgid "Unable to obtain your identity:"
+msgstr "Ð\98денÑ\82иÑ\84икаÑ\86иÑ\8fÑ\82а Ð²Ð¸ Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¾Ð¿Ñ\80еделена:"
 
-#: lib/mergetool.tcl:10
-msgid "Force resolution to the other branch?"
-msgstr "Ð\94а Ñ\81е Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð° Ð²ÐµÑ\80Ñ\81иÑ\8fÑ\82а Ð¾Ñ\82 Ð´Ñ\80Ñ\83гиÑ\8f ÐºÐ»Ð¾Ð½"
+#: lib/commit.tcl:80
+msgid "Invalid GIT_COMMITTER_IDENT:"
+msgstr "Ð\9dепÑ\80авилно Ð¿Ð¾Ð»Ðµ â\80\9eGIT_COMMITTER_IDENTâ\80\9c:"
 
-#: lib/mergetool.tcl:14
+#: lib/commit.tcl:129
 #, tcl-format
+msgid "warning: Tcl does not support encoding '%s'."
+msgstr "предупреждение: Tcl не поддържа кодирането „%s“."
+
+#: lib/commit.tcl:149
 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 another commit can be created.\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"
-"Тази Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\8f Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¾Ñ\82менена Ñ\81амо Ñ\87Ñ\80ез Ð·Ð°Ð¿Ð¾Ñ\87ване Ð½Ð° Ñ\81ливанеÑ\82о Ð½Ð°Ð½Ð¾Ð²Ð¾."
+"Ð\90вÑ\82омаÑ\82иÑ\87но Ñ\89е Ð·Ð°Ð¿Ð¾Ñ\87не Ð½Ð¾Ð²Ð° Ð¿Ñ\80овеÑ\80ка.\n"
 
-#: lib/mergetool.tcl:45
+#: lib/commit.tcl:173
 #, tcl-format
-msgid "File %s seems to have unresolved conflicts, still stage?"
+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 ""
-"Изглежда, че все още има некоригирани конфликти във файла „%s“. Да се добави "
-"ли файлът към индекса?"
+"Неслетите файлове не могат да бъдат подавани.\n"
+"\n"
+"Във файла „%s“ има конфликти при сливане. За да го подадете, трябва първо да "
+"коригирате конфликтите и да добавите файла към индекса за подаване.\n"
 
-#: lib/mergetool.tcl:60
+#: lib/commit.tcl:181
 #, tcl-format
-msgid "Adding resolution for %s"
-msgstr "Добавяне на корекция на конфликтите в „%s“"
-
-#: lib/mergetool.tcl:141
-msgid "Cannot resolve deletion or link conflicts using a tool"
+msgid ""
+"Unknown file state %s detected.\n"
+"\n"
+"File %s cannot be committed by this program.\n"
 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 "Програмата за сливане вече е стартирана. Да бъде ли изключена?"
+"Непознато състояние на файл „%s“.\n"
+"\n"
+"Файлът „%s“ не може да бъде подаден чрез текущата програма.\n"
 
-#: lib/mergetool.tcl:330
-#, tcl-format
+#: lib/commit.tcl:189
 msgid ""
-"Error retrieving versions:\n"
-"%s"
+"No changes to commit.\n"
+"\n"
+"You must stage at least 1 file before you can commit.\n"
 msgstr ""
-"Грешка при изтеглянето на версии:\n"
-"%s"
+"Няма промени за подаване.\n"
+"\n"
+"Трябва да добавите поне един файл към индекса, за да подадете.\n"
 
-#: lib/mergetool.tcl:350
-#, tcl-format
+#: lib/commit.tcl:204
 msgid ""
-"Could not start the merge tool:\n"
+"Please supply a commit message.\n"
 "\n"
-"%s"
+"A good commit message has the following format:\n"
+"\n"
+"- First line: Describe in one sentence what you did.\n"
+"- Second line: Blank\n"
+"- Remaining lines: Describe why this change is good.\n"
 msgstr ""
-"Ð\9fÑ\80огÑ\80амаÑ\82а Ð·Ð° Ñ\81ливане Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ñ\81Ñ\82аÑ\80Ñ\82иÑ\80ана:\n"
+"Ð\97адайÑ\82е Ð´Ð¾Ð±Ñ\80о Ñ\81Ñ\8aобÑ\89ение Ð¿Ñ\80и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ.\n"
 "\n"
-"%s"
-
-#: lib/mergetool.tcl:354
-msgid "Running merge tool..."
-msgstr "Стартиране на програмата за сливане…"
+"Използвайте следния формат:\n"
+"\n"
+"● Първи ред: описание в едно изречение на промяната.\n"
+"● Втори ред: празен.\n"
+"● Останалите редове: опишете защо се налага тази промяна.\n"
 
-#: lib/mergetool.tcl:382 lib/mergetool.tcl:390
-msgid "Merge tool failed."
-msgstr "Ð\93Ñ\80еÑ\88ка Ð² Ð¿Ñ\80огÑ\80амаÑ\82а Ð·Ð° Ñ\81ливане."
+#: lib/commit.tcl:235
+msgid "Calling pre-commit hook..."
+msgstr "Ð\98зпÑ\8aлнÑ\8fване Ð½Ð° ÐºÑ\83каÑ\82а Ð¿Ñ\80еди Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµâ\80¦"
 
-#: lib/tools_dlg.tcl:22
-msgid "Add Tool"
-msgstr "Ð\94обавÑ\8fне Ð½Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°"
+#: lib/commit.tcl:250
+msgid "Commit declined by pre-commit hook."
+msgstr "Ð\9fодаванеÑ\82о Ðµ Ð¾Ñ\82Ñ\85вÑ\8aÑ\80лено Ð¾Ñ\82 ÐºÑ\83каÑ\82а Ð¿Ñ\80еди Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ."
 
-#: lib/tools_dlg.tcl:28
-msgid "Add New Tool Command"
-msgstr "Добавяне на команда"
+#: lib/commit.tcl:269
+msgid ""
+"You are about to commit on a detached head. This is a potentially dangerous "
+"thing to do because if you switch to another branch you will lose your "
+"changes and it can be difficult to retrieve them later from the reflog. You "
+"should probably cancel this commit and create a new branch to continue.\n"
+" \n"
+" Do you really want to proceed with your Commit?"
+msgstr ""
+"Ще подадете към несвързан, отделѐн указател „HEAD“. Това е опасно, защото "
+"при преминаването към клон ще загубите промените си, като единственият начин "
+"да ги върнете ще е чрез журнала на указателите (reflog). Най-вероятно трябва "
+"да не правите това подаване, а да създадете нов клон, преди да продължите.\n"
+" \n"
+"Сигурни ли сте, че искате да извършите текущото подаване?"
 
-#: lib/tools_dlg.tcl:34
-msgid "Add globally"
-msgstr "Ð\93лобално Ð´Ð¾Ð±Ð°Ð²Ñ\8fне"
+#: lib/commit.tcl:290
+msgid "Calling commit-msg hook..."
+msgstr "Ð\98зпÑ\8aлнÑ\8fване Ð½Ð° ÐºÑ\83каÑ\82а Ð·Ð° Ñ\81Ñ\8aобÑ\89ениеÑ\82о Ð¿Ñ\80и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµâ\80¦"
 
-#: lib/tools_dlg.tcl:46
-msgid "Tool Details"
-msgstr "Подробности за командата"
+#: lib/commit.tcl:305
+msgid "Commit declined by commit-msg hook."
+msgstr "Подаването е отхвърлено от куката за съобщението при подаване."
 
-#: lib/tools_dlg.tcl:49
-msgid "Use '/' separators to create a submenu tree:"
-msgstr "Ð\97а Ñ\81Ñ\8aздаване Ð½Ð° Ð¿Ð¾Ð´Ð¼ÐµÐ½Ñ\8eÑ\82а Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð°Ð¹Ñ\82е Ð·Ð½Ð°ÐºÐ° â\80\9e\80\9c Ð·Ð° Ñ\80азделиÑ\82ел:"
+#: lib/commit.tcl:318
+msgid "Committing changes..."
+msgstr "Ð\9fодаване Ð½Ð° Ð¿Ñ\80омениÑ\82еâ\80¦"
 
-#: lib/tools_dlg.tcl:60
-msgid "Command:"
-msgstr "Ð\9aоманда:"
+#: lib/commit.tcl:334
+msgid "write-tree failed:"
+msgstr "неÑ\83Ñ\81пеÑ\88но Ð·Ð°Ð¿Ð°Ð·Ð²Ð°Ð½Ðµ Ð½Ð° Ð´Ñ\8aÑ\80воÑ\82о (write-tree):"
 
-#: lib/tools_dlg.tcl:71
-msgid "Show a dialog before running"
-msgstr "Ð\9fÑ\80еди Ð¸Ð·Ð¿Ñ\8aлнение Ð´Ð° Ñ\81е Ð¸Ð·Ð²ÐµÐ¶Ð´Ð° Ð´Ð¸Ð°Ð»Ð¾Ð³Ð¾Ð² Ð¿Ñ\80озоÑ\80еÑ\86"
+#: lib/commit.tcl:335 lib/commit.tcl:382 lib/commit.tcl:403
+msgid "Commit failed."
+msgstr "Ð\9dеÑ\83Ñ\81пеÑ\88но Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ."
 
-#: lib/tools_dlg.tcl:77
-msgid "Ask the user to select a revision (sets $REVISION)"
-msgstr "Потребителят да укаже версия (задаване на променливата $REVISION)"
+#: lib/commit.tcl:352
+#, tcl-format
+msgid "Commit %s appears to be corrupt"
+msgstr "Подаването „%s“ изглежда повредено"
 
-#: lib/tools_dlg.tcl:82
-msgid "Ask the user for additional arguments (sets $ARGS)"
+#: lib/commit.tcl:357
+msgid ""
+"No changes to commit.\n"
+"\n"
+"No files were modified by this commit and it was not a merge commit.\n"
+"\n"
+"A rescan will be automatically started now.\n"
 msgstr ""
-"Потребителят да укаже допълнителни аргументи (задаване на променливата $ARGS)"
+"Няма промени за подаване.\n"
+"\n"
+"В това подаване не са променяни никакви файлове, а и не е подаване със "
+"сливане.\n"
+"\n"
+"Автоматично ще започне нова проверка.\n"
 
-#: lib/tools_dlg.tcl:89
-msgid "Don't show the command output window"
-msgstr "Ð\91ез Ð¿Ð¾ÐºÐ°Ð·Ð²Ð°Ð½Ðµ Ð½Ð° Ð¿Ñ\80озоÑ\80еÑ\86 Ñ\81 Ð¸Ð·Ñ\85ода Ð¾Ñ\82 ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°Ñ\82а"
+#: lib/commit.tcl:364
+msgid "No changes to commit."
+msgstr "Ð\9dÑ\8fма Ð¿Ñ\80омени Ð·Ð° Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ."
 
-#: lib/tools_dlg.tcl:94
-msgid "Run only if a diff is selected ($FILENAME not empty)"
-msgstr ""
-"Стартиране само след избор на разлика (променливата $FILENAME не е празна)"
+#: lib/commit.tcl:381
+msgid "commit-tree failed:"
+msgstr "неуспешно подаване на дървото (commit-tree):"
 
-#: lib/tools_dlg.tcl:118
-msgid "Please supply a name for the tool."
-msgstr "Ð\97адайÑ\82е Ð¸Ð¼Ðµ Ð·Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°Ñ\82а."
+#: lib/commit.tcl:402
+msgid "update-ref failed:"
+msgstr "неÑ\83Ñ\81пеÑ\88но Ð¾Ð±Ð½Ð¾Ð²Ñ\8fване Ð½Ð° Ñ\83казаÑ\82елиÑ\82е (update-ref):"
 
-#: lib/tools_dlg.tcl:126
+#: lib/commit.tcl:495
 #, tcl-format
-msgid "Tool '%s' already exists."
-msgstr "Ð\9aомандаÑ\82а â\80\9e%sâ\80\9c Ð²ÐµÑ\87е Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ва."
+msgid "Created commit %s: %s"
+msgstr "УÑ\81пеÑ\88но Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ %s: %s"
 
-#: lib/tools_dlg.tcl:148
-#, tcl-format
-msgid ""
-"Could not add tool:\n"
-"%s"
-msgstr ""
-"Командата не може да бъде добавена:\n"
-"%s"
+#: lib/console.tcl:59
+msgid "Working... please wait..."
+msgstr "В момента се извършва действие, изчакайте…"
 
-#: lib/tools_dlg.tcl:187
-msgid "Remove Tool"
-msgstr "Ð\9fÑ\80емаÑ\85ване Ð½Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°"
+#: lib/console.tcl:186
+msgid "Success"
+msgstr "УÑ\81пеÑ\85"
 
-#: lib/tools_dlg.tcl:193
-msgid "Remove Tool Commands"
-msgstr "Ð\9fÑ\80емаÑ\85ване Ð½Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸"
+#: lib/console.tcl:200
+msgid "Error: Command Failed"
+msgstr "Ð\93Ñ\80еÑ\88ка: Ð½ÐµÑ\83Ñ\81пеÑ\88но Ð¸Ð·Ð¿Ñ\8aлнение Ð½Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°"
 
-#: lib/tools_dlg.tcl:198
-msgid "Remove"
-msgstr "Ð\9fÑ\80емаÑ\85ване"
+#: lib/database.tcl:42
+msgid "Number of loose objects"
+msgstr "Ð\91Ñ\80ой Ð½ÐµÐ¿Ð°ÐºÐµÑ\82иÑ\80ани Ð¾Ð±ÐµÐºÑ\82и"
 
-#: lib/tools_dlg.tcl:231
-msgid "(Blue denotes repository-local tools)"
-msgstr "(командите към локалното хранилище са обозначени в синьо)"
+#: lib/database.tcl:43
+msgid "Disk space used by loose objects"
+msgstr "Дисково пространство заето от непакетирани обекти"
 
-#: lib/tools_dlg.tcl:292
-#, tcl-format
-msgid "Run Command: %s"
-msgstr "Изпълнение на командата „%s“"
+#: lib/database.tcl:44
+msgid "Number of packed objects"
+msgstr "Брой пакетирани обекти"
 
-#: lib/tools_dlg.tcl:306
-msgid "Arguments"
-msgstr "Ð\90Ñ\80гÑ\83менти"
+#: lib/database.tcl:45
+msgid "Number of packs"
+msgstr "Ð\91Ñ\80ой Ð¿Ð°ÐºÐµти"
 
-#: lib/tools_dlg.tcl:336 lib/checkout_op.tcl:567 lib/merge.tcl:166
-msgid "Visualize"
-msgstr "Ð\92изÑ\83ализаÑ\86иÑ\8f"
+#: lib/database.tcl:46
+msgid "Disk space used by packed objects"
+msgstr "Ð\94иÑ\81ково Ð¿Ñ\80оÑ\81Ñ\82Ñ\80анÑ\81Ñ\82во Ð·Ð°ÐµÑ\82о Ð¾Ñ\82 Ð¿Ð°ÐºÐµÑ\82иÑ\80ани Ð¾Ð±ÐµÐºÑ\82и"
 
-#: lib/tools_dlg.tcl:341
-msgid "OK"
-msgstr "Ð\94обÑ\80е"
+#: lib/database.tcl:47
+msgid "Packed objects waiting for pruning"
+msgstr "Ð\9fакеÑ\82иÑ\80ани Ð¾Ð±ÐµÐºÑ\82и Ð·Ð° Ð¾ÐºÐ°Ñ\81Ñ\82Ñ\80Ñ\8fне"
 
-#: lib/search.tcl:48
-msgid "Find:"
-msgstr "ТÑ\8aÑ\80Ñ\81ене:"
+#: lib/database.tcl:48
+msgid "Garbage files"
+msgstr "Файлове Ð·Ð° Ð±Ð¾ÐºÐ»Ñ\83ка"
 
-#: lib/search.tcl:50
-msgid "Next"
-msgstr "Следваща поява"
+#: lib/database.tcl:57 lib/option.tcl:182 lib/option.tcl:197 lib/option.tcl:220
+#: lib/option.tcl:282
+#, tcl-format
+msgid "%s:"
+msgstr "%s:"
 
-#: lib/search.tcl:51
-msgid "Prev"
-msgstr "Предишна поява"
+#: lib/database.tcl:66
+#, tcl-format
+msgid "%s (%s): Database Statistics"
+msgstr "%s (%s): Статистика на базата от данни"
 
-#: lib/search.tcl:52
-msgid "RegExp"
-msgstr "Рег. Ð¸Ð·Ñ\80аз"
+#: lib/database.tcl:72
+msgid "Compressing the object database"
+msgstr "Ð\9aомпÑ\80еÑ\81иÑ\80ане Ð½Ð° Ð±Ð°Ð·Ð°Ñ\82а Ñ\81 Ð´Ð°Ð½Ð½Ð¸ Ð·Ð° Ð¾Ð±ÐµÐºÑ\82иÑ\82е"
 
-#: lib/search.tcl:54
-msgid "Case"
-msgstr "РегиÑ\81Ñ\82Ñ\8aÑ\80"
+#: lib/database.tcl:83
+msgid "Verifying the object database with fsck-objects"
+msgstr "Ð\9fÑ\80овеÑ\80ка Ð½Ð° Ð±Ð°Ð·Ð°Ñ\82а Ñ\81 Ð´Ð°Ð½Ð½Ð¸ Ð·Ð° Ð¾Ð±ÐµÐºÑ\82иÑ\82е Ñ\81 Ð¿Ñ\80огÑ\80амаÑ\82а â\80\9efsck-objectsâ\80\9c"
 
-#: lib/shortcut.tcl:21 lib/shortcut.tcl:62
-msgid "Cannot write shortcut:"
-msgstr "Клавишната комбинация не може да бъде запазена:"
+#: 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/shortcut.tcl:137
-msgid "Cannot write icon:"
-msgstr "Иконата не може да бъде запазена:"
+#: lib/date.tcl:25
+#, tcl-format
+msgid "Invalid date from Git: %s"
+msgstr "Неправилни данни от Git: %s"
 
 #: lib/diff.tcl:77
 #, tcl-format
@@ -1443,7 +1698,7 @@ msgstr ""
 msgid "Loading diff of %s..."
 msgstr "Зареждане на разликите в „%s“…"
 
-#: lib/diff.tcl:140
+#: lib/diff.tcl:143
 msgid ""
 "LOCAL: deleted\n"
 "REMOTE:\n"
@@ -1451,7 +1706,7 @@ msgstr ""
 "ЛОКАЛНО: изтрит\n"
 "ОТДАЛЕЧЕНО:\n"
 
-#: lib/diff.tcl:145
+#: lib/diff.tcl:148
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
@@ -1459,32 +1714,32 @@ msgstr ""
 "ОТДАЛЕЧЕНО: изтрит\n"
 "ЛОКАЛНО:\n"
 
-#: lib/diff.tcl:152
+#: lib/diff.tcl:155
 msgid "LOCAL:\n"
 msgstr "ЛОКАЛНО:\n"
 
-#: lib/diff.tcl:155
+#: lib/diff.tcl:158
 msgid "REMOTE:\n"
 msgstr "ОТДАЛЕЧЕНО:\n"
 
-#: lib/diff.tcl:217 lib/diff.tcl:355
+#: lib/diff.tcl:220 lib/diff.tcl:357
 #, tcl-format
 msgid "Unable to display %s"
 msgstr "Файлът „%s“ не може да бъде показан"
 
-#: lib/diff.tcl:218
+#: lib/diff.tcl:221
 msgid "Error loading file:"
 msgstr "Грешка при зареждане на файл:"
 
-#: lib/diff.tcl:225
+#: lib/diff.tcl:227
 msgid "Git Repository (subproject)"
 msgstr "Хранилище на Git (подмодул)"
 
-#: lib/diff.tcl:237
+#: lib/diff.tcl:239
 msgid "* Binary file (not showing content)."
 msgstr "● Двоичен файл (съдържанието не се показва)."
 
-#: lib/diff.tcl:242
+#: lib/diff.tcl:244
 #, tcl-format
 msgid ""
 "* Untracked file is %d bytes.\n"
@@ -1493,1222 +1748,1060 @@ msgstr ""
 "● Неследеният файл е %d байта.\n"
 "● Показват се само първите %d байта.\n"
 
-#: lib/diff.tcl:248
-#, 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/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 "Винаги (без проверка за сливане)"
-
-#: 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
+#: lib/diff.tcl:250
 #, 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:218 lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult.\n"
-"\n"
-"Delete the selected branches?"
-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“…"
-
-#: lib/choose_repository.tcl:33
-msgid "Git Gui"
-msgstr "ГПИ на Git"
-
-#: lib/choose_repository.tcl:92 lib/choose_repository.tcl:412
-msgid "Create New Repository"
-msgstr "Създаване на ново хранилище"
-
-#: lib/choose_repository.tcl:98
-msgid "New..."
-msgstr "Ново…"
-
-#: lib/choose_repository.tcl:105 lib/choose_repository.tcl:496
-msgid "Clone Existing Repository"
-msgstr "Клониране на съществуващо хранилище"
+"* Untracked file clipped here by %s.\n"
+"* To see the entire file, use an external editor.\n"
+msgstr ""
+"\n"
+"● Неследеният файл е отрязан дотук от програмата „%s“.\n"
+"● Използвайте външен редактор, за да видите целия файл.\n"
 
-#: lib/choose_repository.tcl:116
-msgid "Clone..."
-msgstr "Ð\9aлониÑ\80анеâ\80¦"
+#: lib/diff.tcl:580
+msgid "Failed to unstage selected hunk."
+msgstr "Ð\98збÑ\80аноÑ\82о Ð¿Ð°Ñ\80Ñ\87е Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¸Ð·Ð²Ð°Ð´ÐµÐ½Ð¾ Ð¾Ñ\82 Ð¸Ð½Ð´ÐµÐºÑ\81а."
 
-#: lib/choose_repository.tcl:123 lib/choose_repository.tcl:1064
-msgid "Open Existing Repository"
-msgstr "Ð\9eÑ\82ваÑ\80Ñ\8fне Ð½Ð° Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ваÑ\89о Ñ\85Ñ\80анилиÑ\89е"
+#: lib/diff.tcl:587
+msgid "Failed to stage selected hunk."
+msgstr "Ð\98збÑ\80аноÑ\82о Ð¿Ð°Ñ\80Ñ\87е Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð´Ð¾Ð±Ð°Ð²ÐµÐ½Ð¾ ÐºÑ\8aм Ð¸Ð½Ð´ÐµÐºÑ\81а."
 
-#: lib/choose_repository.tcl:129
-msgid "Open..."
-msgstr "Ð\9eÑ\82ваÑ\80Ñ\8fнеâ\80¦"
+#: lib/diff.tcl:666
+msgid "Failed to unstage selected line."
+msgstr "Ð\98збÑ\80аниÑ\8fÑ\82 Ñ\80ед Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¸Ð·Ð²Ð°Ð´ÐµÐ½ Ð¾Ñ\82 Ð¸Ð½Ð´ÐµÐºÑ\81а."
 
-#: lib/choose_repository.tcl:142
-msgid "Recent Repositories"
-msgstr "СкоÑ\80о Ð¿Ð¾Ð»Ð·Ð²Ð°Ð½Ð¸"
+#: lib/diff.tcl:674
+msgid "Failed to stage selected line."
+msgstr "Ð\98збÑ\80аниÑ\8fÑ\82 Ñ\80ед Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð´Ð¾Ð±Ð°Ð²ÐµÐ½ ÐºÑ\8aм Ð¸Ð½Ð´ÐµÐºÑ\81а."
 
-#: lib/choose_repository.tcl:148
-msgid "Open Recent Repository:"
-msgstr "Ð\9eÑ\82ваÑ\80Ñ\8fне Ð½Ð° Ñ\85Ñ\80анилиÑ\89е Ð¿Ð¾Ð»Ð·Ð²Ð°Ð½Ð¾ Ð½Ð°Ñ\81коÑ\80о:"
+#: lib/encoding.tcl:443
+msgid "Default"
+msgstr "СÑ\82андаÑ\80Ñ\82ноÑ\82о"
 
-#: lib/choose_repository.tcl:316 lib/choose_repository.tcl:323
-#: lib/choose_repository.tcl:330
+#: lib/encoding.tcl:448
 #, tcl-format
-msgid "Failed to create repository %s:"
-msgstr "Неуспешно създаване на хранилището „%s“:"
-
-#: lib/choose_repository.tcl:407 lib/branch_create.tcl:33
-msgid "Create"
-msgstr "Създаване"
+msgid "System (%s)"
+msgstr "Системното (%s)"
 
-#: lib/choose_repository.tcl:417
-msgid "Directory:"
-msgstr "Директория:"
+#: lib/encoding.tcl:459 lib/encoding.tcl:465
+msgid "Other"
+msgstr "Друго"
 
-#: lib/choose_repository.tcl:447 lib/choose_repository.tcl:573
-#: lib/choose_repository.tcl:1098
-msgid "Git Repository"
-msgstr "Хранилище на Git"
+#: lib/error.tcl:20
+#, tcl-format
+msgid "%s: error"
+msgstr "%s: грешка"
 
-#: lib/choose_repository.tcl:472
+#: lib/error.tcl:36
 #, tcl-format
-msgid "Directory %s already exists."
-msgstr "Вече съществува директория „%s“."
+msgid "%s: warning"
+msgstr "%s: предупреждение"
 
-#: lib/choose_repository.tcl:476
+#: lib/error.tcl:80
 #, tcl-format
-msgid "File %s already exists."
-msgstr "Вече съществува файл „%s“."
+msgid "%s hook failed:"
+msgstr "%s: грешка от куката"
 
-#: lib/choose_repository.tcl:491
-msgid "Clone"
-msgstr "Ð\9aлониÑ\80ане"
+#: lib/error.tcl:96
+msgid "You must correct the above errors before committing."
+msgstr "Ð\9fÑ\80еди Ð´Ð° Ð¼Ð¾Ð¶ÐµÑ\82е Ð´Ð° Ð¿Ð¾Ð´Ð°Ð´ÐµÑ\82е, ÐºÐ¾Ñ\80игиÑ\80айÑ\82е Ð³Ð¾Ñ\80ниÑ\82е Ð³Ñ\80еÑ\88ки."
 
-#: lib/choose_repository.tcl:504
-msgid "Source Location:"
-msgstr "Адрес на източника:"
+#: lib/error.tcl:116
+#, tcl-format
+msgid "%s (%s): error"
+msgstr "%s (%s): грешка"
 
-#: lib/choose_repository.tcl:513
-msgid "Target Directory:"
-msgstr "Целева Ð´Ð¸Ñ\80екÑ\82оÑ\80иÑ\8f:"
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "Ð\98ндекÑ\81Ñ\8aÑ\82 Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¾Ñ\82клÑ\8eÑ\87ен."
 
-#: lib/choose_repository.tcl:523
-msgid "Clone Type:"
-msgstr "Ð\92ид ÐºÐ»Ð¾Ð½Ð¸Ñ\80ане:"
+#: lib/index.tcl:17
+msgid "Index Error"
+msgstr "Ð\93Ñ\80еÑ\88ка Ð² Ð¸Ð½Ð´ÐµÐºÑ\81а"
 
-#: lib/choose_repository.tcl:528
-msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
-msgstr "Стандартно (бързо, частично споделяне на файлове, твърди връзки)"
+#: lib/index.tcl:19
+msgid ""
+"Updating the Git index failed.  A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr ""
+"Неуспешно обновяване на индекса на Git. Автоматично ще започне нова проверка "
+"за синхронизирането на git-gui."
 
-#: lib/choose_repository.tcl:533
-msgid "Full Copy (Slower, Redundant Backup)"
-msgstr "Ð\9fÑ\8aлно (бавно, Ð¿Ñ\8aлноÑ\86енно Ñ\80езеÑ\80вно ÐºÐ¾Ð¿Ð¸Ðµ)"
+#: lib/index.tcl:30
+msgid "Continue"
+msgstr "Ð\9fÑ\80одÑ\8aлжаване"
 
-#: lib/choose_repository.tcl:538
-msgid "Shared (Fastest, Not Recommended, No Backup)"
-msgstr "Споделено (най-бÑ\8aÑ\80зо, Ð½Ðµ Ñ\81е Ð¿Ñ\80епоÑ\80Ñ\8aÑ\87ва, Ð½Ðµ Ð¿Ñ\80ави Ñ\80езеÑ\80вно ÐºÐ¾Ð¿Ð¸Ðµ)"
+#: lib/index.tcl:33
+msgid "Unlock Index"
+msgstr "Ð\9eÑ\82клÑ\8eÑ\87ване Ð½Ð° Ð¸Ð½Ð´ÐµÐºÑ\81а"
 
-#: lib/choose_repository.tcl:545
-msgid "Recursively clone submodules too"
-msgstr "РекÑ\83Ñ\80Ñ\81ивно ÐºÐ»Ð¾Ð½Ð¸Ñ\80ане Ð¸ Ð½Ð° Ð¿Ð¾Ð´Ð¼Ð¾Ð´Ñ\83лиÑ\82е"
+#: lib/index.tcl:294
+msgid "Unstaging selected files from commit"
+msgstr "Ð\98зваждане Ð½Ð° Ð¸Ð·Ð±Ñ\80аниÑ\82е Ñ\84айлове Ð¾Ñ\82 Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½ÐµÑ\82о"
 
-#: lib/choose_repository.tcl:579 lib/choose_repository.tcl:626
-#: lib/choose_repository.tcl:772 lib/choose_repository.tcl:842
-#: lib/choose_repository.tcl:1104 lib/choose_repository.tcl:1112
+#: lib/index.tcl:298
 #, tcl-format
-msgid "Not a Git repository: %s"
-msgstr "Това Ð½Ðµ Ðµ Ñ\85Ñ\80анилиÑ\89е Ð½Ð° Git: %s"
+msgid "Unstaging %s from commit"
+msgstr "Ð\98зваждане Ð½Ð° â\80\9e%sâ\80\9c Ð¾Ñ\82 Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½ÐµÑ\82о"
 
-#: lib/choose_repository.tcl:615
-msgid "Standard only available for local repository."
-msgstr "Само Ð»Ð¾ÐºÐ°Ð»Ð½Ð¸ Ñ\85Ñ\80анилиÑ\89а Ð¼Ð¾Ð³Ð°Ñ\82 Ð´Ð° Ñ\81е ÐºÐ»Ð¾Ð½Ð¸Ñ\80аÑ\82 Ñ\81Ñ\82андаÑ\80Ñ\82но"
+#: lib/index.tcl:337
+msgid "Ready to commit."
+msgstr "Ð\93оÑ\82овноÑ\81Ñ\82 Ð·Ð° Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ."
 
-#: lib/choose_repository.tcl:619
-msgid "Shared only available for local repository."
-msgstr "Само Ð»Ð¾ÐºÐ°Ð»Ð½Ð¸ Ñ\85Ñ\80анилиÑ\89а Ð¼Ð¾Ð³Ð°Ñ\82 Ð´Ð° Ñ\81е ÐºÐ»Ð¾Ð½Ð¸Ñ\80аÑ\82 Ñ\81поделено"
+#: lib/index.tcl:346
+msgid "Adding selected files"
+msgstr "Ð\94обавÑ\8fне Ð½Ð° Ð¸Ð·Ð±Ñ\80аниÑ\82е Ñ\84айлове"
 
-#: lib/choose_repository.tcl:640
+#: lib/index.tcl:350
 #, tcl-format
-msgid "Location %s already exists."
-msgstr "Местоположението „%s“ вече съществува."
-
-#: lib/choose_repository.tcl:651
-msgid "Failed to configure origin"
-msgstr "Неуспешно настройване на хранилището-източник"
+msgid "Adding %s"
+msgstr "Добавяне на „%s“"
 
-#: lib/choose_repository.tcl:663
-msgid "Counting objects"
-msgstr "Преброяване на обекти"
+#: lib/index.tcl:380
+#, tcl-format
+msgid "Stage %d untracked files?"
+msgstr "Да се добавят ли %d неследени файла към индекса?"
 
-#: lib/choose_repository.tcl:664
-msgid "buckets"
-msgstr "клеÑ\82ки"
+#: lib/index.tcl:388
+msgid "Adding all changed files"
+msgstr "Ð\94обавÑ\8fне Ð½Ð° Ð²Ñ\81иÑ\87ки Ð¿Ñ\80оменени Ñ\84айлове"
 
-#: lib/choose_repository.tcl:688
+#: lib/index.tcl:428
 #, tcl-format
-msgid "Unable to copy objects/info/alternates: %s"
-msgstr "Ð\9eбекÑ\82иÑ\82е/инÑ\84оÑ\80маÑ\86иÑ\8fÑ\82а/Ñ\81инонимиÑ\82е Ð½Ðµ Ð¼Ð¾Ð³Ð°Ñ\82 Ð´Ð° Ð±Ñ\8aдаÑ\82 ÐºÐ¾Ð¿Ð¸Ñ\80ани: %s"
+msgid "Revert changes in file %s?"
+msgstr "Ð\94а Ñ\81е Ð¼Ð°Ñ\85наÑ\82 Ð»Ð¸ Ð¿Ñ\80омениÑ\82е Ð²Ñ\8aв Ñ\84айла â\80\9e%sâ\80\9c?"
 
-#: lib/choose_repository.tcl:724
+#: lib/index.tcl:430
 #, tcl-format
-msgid "Nothing to clone from %s."
-msgstr "Няма какво да се клонира от „%s“."
-
-#: lib/choose_repository.tcl:726 lib/choose_repository.tcl:940
-#: lib/choose_repository.tcl:952
-msgid "The 'master' branch has not been initialized."
-msgstr "Основният клон — „master“ не е инициализиран."
-
-#: lib/choose_repository.tcl:739
-msgid "Hardlinks are unavailable.  Falling back to copying."
-msgstr "Не се поддържат твърди връзки. Преминава се към копиране."
+msgid "Revert changes in these %i files?"
+msgstr "Да се махнат ли промените в тези %i файла?"
 
-#: lib/choose_repository.tcl:751
-#, tcl-format
-msgid "Cloning from %s"
-msgstr "Клониране на „%s“"
+#: lib/index.tcl:438
+msgid "Any unstaged changes will be permanently lost by the revert."
+msgstr ""
+"Всички промени, които не са били вкарани в индекса, ще бъдат безвъзвратно "
+"загубени."
 
-#: lib/choose_repository.tcl:782
-msgid "Copying objects"
-msgstr "Ð\9aопиÑ\80ане Ð½Ð° Ð¾Ð±ÐµÐºÑ\82и"
+#: lib/index.tcl:441
+msgid "Do Nothing"
+msgstr "Ð\9dиÑ\89о Ð´Ð° Ð½Ðµ Ñ\81е Ð¿Ñ\80ави"
 
-#: lib/choose_repository.tcl:783
-msgid "KiB"
-msgstr "KiB"
+#: lib/index.tcl:459
+msgid "Reverting selected files"
+msgstr "Махане на промените в избраните файлове"
 
-#: lib/choose_repository.tcl:807
+#: lib/index.tcl:463
 #, tcl-format
-msgid "Unable to copy object: %s"
-msgstr "Ð\9dеÑ\83Ñ\81пеÑ\88но ÐºÐ¾Ð¿Ð¸Ñ\80ане Ð½Ð° Ð¾Ð±ÐµÐºÑ\82: %s"
+msgid "Reverting %s"
+msgstr "Ð\9cаÑ\85ане Ð½Ð° Ð¿Ñ\80омениÑ\82е Ð² â\80\9e%sâ\80\9c"
 
-#: lib/choose_repository.tcl:817
-msgid "Linking objects"
-msgstr "СÑ\8aздаване Ð½Ð° Ð²Ñ\80Ñ\8aзки ÐºÑ\8aм Ð¾Ð±ÐµÐºÑ\82иÑ\82е"
+#: lib/line.tcl:17
+msgid "Goto Line:"
+msgstr "Ð\9aÑ\8aм Ñ\80ед:"
 
-#: lib/choose_repository.tcl:818
-msgid "objects"
-msgstr "обекÑ\82и"
+#: lib/line.tcl:23
+msgid "Go"
+msgstr "Ð\9fÑ\80идвижване"
 
-#: lib/choose_repository.tcl:826
-#, tcl-format
-msgid "Unable to hardlink object: %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"
 
-#: lib/choose_repository.tcl:881
-msgid "Cannot fetch branches and objects.  See console output for details."
+#: lib/merge.tcl:27
+msgid ""
+"Last scanned state does not match repository state.\n"
+"\n"
+"Another Git program has modified this repository since the last scan.  A "
+"rescan must be performed before a merge can be performed.\n"
+"\n"
+"The rescan will be automatically started now.\n"
 msgstr ""
-"Клоните и обектите не могат да бъдат изтеглени. За повече информация "
-"погледнете изхода на конзолата."
+"Последно установеното състояние не отговаря на това в хранилището.\n"
+"\n"
+"Някой друг процес за Git е променил хранилището междувременно. Състоянието "
+"трябва да бъде проверено, преди да се извърши сливане.\n"
+"\n"
+"Автоматично ще започне нова проверка.\n"
+"\n"
 
-#: lib/choose_repository.tcl:892
-msgid "Cannot fetch tags.  See console output for details."
+#: lib/merge.tcl:45
+#, tcl-format
+msgid ""
+"You are in the middle of a conflicted merge.\n"
+"\n"
+"File %s has merge conflicts.\n"
+"\n"
+"You must resolve them, stage the file, and commit to complete the current "
+"merge.  Only then can you begin another merge.\n"
 msgstr ""
-"Етикетите не могат да бъдат изтеглени. За повече информация погледнете "
-"изхода на конзолата."
+"В момента тече сливане, но има конфликти.\n"
+"\n"
+"Погледнете файла „%s“.\n"
+"\n"
+"Трябва да коригирате конфликтите в него, да го добавите към индекса и да "
+"завършите текущото сливане чрез подаване. Чак тогава може да започнете ново "
+"сливане.\n"
 
-#: lib/choose_repository.tcl:916
-msgid "Cannot determine HEAD.  See console output for details."
+#: lib/merge.tcl:55
+#, tcl-format
+msgid ""
+"You are in the middle of a change.\n"
+"\n"
+"File %s is modified.\n"
+"\n"
+"You should complete the current commit before starting a merge.  Doing so "
+"will help you abort a failed merge, should the need arise.\n"
 msgstr ""
-"Върхът „HEAD“ не може да бъде определен. За повече информация погледнете "
-"изхода на конзолата."
+"В момента тече подаване.\n"
+"\n"
+"Файлът „%s“ е променен.\n"
+"\n"
+"Трябва да завършите текущото подаване, преди да започнете сливане. Така ще "
+"можете лесно да преустановите сливането, ако възникне нужда.\n"
 
-#: lib/choose_repository.tcl:925
+#: lib/merge.tcl:108
 #, tcl-format
-msgid "Unable to cleanup %s"
-msgstr "„%s“ не може да се зачисти"
+msgid "%s of %s"
+msgstr "%s от общо %s"
 
-#: lib/choose_repository.tcl:931
-msgid "Clone failed."
-msgstr "Неуспешно клониране."
+#: lib/merge.tcl:126
+#, tcl-format
+msgid "Merging %s and %s..."
+msgstr "Сливане на „%s“ и „%s“…"
 
-#: lib/choose_repository.tcl:938
-msgid "No default branch obtained."
-msgstr "Ð\9dе Ðµ Ð¿Ð¾Ð»Ñ\83Ñ\87ен ÐºÐ»Ð¾Ð½ Ð¿Ð¾ Ð¿Ð¾Ð´Ñ\80азбиÑ\80ане."
+#: lib/merge.tcl:137
+msgid "Merge completed successfully."
+msgstr "СливанеÑ\82о Ð·Ð°Ð²Ñ\8aÑ\80Ñ\88и Ñ\83Ñ\81пеÑ\88но."
 
-#: lib/choose_repository.tcl:949
-#, tcl-format
-msgid "Cannot resolve %s as a commit."
-msgstr "Няма подаване отговарящо на „%s“."
+#: lib/merge.tcl:139
+msgid "Merge failed.  Conflict resolution is required."
+msgstr "Неуспешно сливане — има конфликти за коригиране."
 
-#: lib/choose_repository.tcl:961
-msgid "Creating working directory"
-msgstr "Създаване на работната директория"
+#: lib/merge.tcl:156
+#, tcl-format
+msgid "%s (%s): Merge"
+msgstr "%s (%s): Сливане"
 
-#: lib/choose_repository.tcl:962 lib/index.tcl:70 lib/index.tcl:136
-#: lib/index.tcl:207
-msgid "files"
-msgstr "файлове"
+#: lib/merge.tcl:164
+#, tcl-format
+msgid "Merge Into %s"
+msgstr "Сливане в „%s“"
 
-#: lib/choose_repository.tcl:981
-msgid "Cannot clone submodules."
-msgstr "Ð\9fодмодÑ\83лиÑ\82е Ð½Ðµ Ð¼Ð¾Ð³Ð°Ñ\82 Ð´Ð° Ñ\81е ÐºÐ»Ð¾Ð½Ð¸Ñ\80аÑ\82."
+#: lib/merge.tcl:183
+msgid "Revision To Merge"
+msgstr "Ð\92еÑ\80Ñ\81иÑ\8f Ð·Ð° Ñ\81ливане"
 
-#: lib/choose_repository.tcl:990
-msgid "Cloning submodules"
-msgstr "Клониране на подмодулите"
+#: lib/merge.tcl:218
+msgid ""
+"Cannot abort while amending.\n"
+"\n"
+"You must finish amending this commit.\n"
+msgstr ""
+"Поправянето не може да бъде преустановено.\n"
+"\n"
+"Трябва да завършите поправката на това подаване.\n"
 
-#: lib/choose_repository.tcl:1015
-msgid "Initial file checkout failed."
-msgstr "Неуспешно първоначално изтегляне."
+#: lib/merge.tcl:228
+msgid ""
+"Abort merge?\n"
+"\n"
+"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with aborting the current merge?"
+msgstr ""
+"Да се преустанови ли сливането?\n"
+"\n"
+"В такъв случай ●ВСИЧКИ● неподадени промени ще бъдат безвъзвратно загубени.\n"
+"\n"
+"Наистина ли да се преустанови сливането?"
 
-#: lib/choose_repository.tcl:1059
-msgid "Open"
-msgstr "Отваряне"
+#: lib/merge.tcl:234
+msgid ""
+"Reset changes?\n"
+"\n"
+"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
+"\n"
+"Continue with resetting the current changes?"
+msgstr ""
+"Да се занулят ли промените?\n"
+"\n"
+"В такъв случай ●ВСИЧКИ● неподадени промени ще бъдат безвъзвратно загубени.\n"
+"\n"
+"Наистина ли да се занулят промените?"
 
-#: lib/choose_repository.tcl:1069
-msgid "Repository:"
-msgstr "Ð¥Ñ\80анилиÑ\89е:"
+#: lib/merge.tcl:245
+msgid "Aborting"
+msgstr "Ð\9fÑ\80еÑ\83Ñ\81Ñ\82ановÑ\8fване"
 
-#: lib/choose_repository.tcl:1118
-#, tcl-format
-msgid "Failed to open repository %s:"
-msgstr "Неуспешно отваряне на хранилището „%s“:"
+#: lib/merge.tcl:245
+msgid "files reset"
+msgstr "файла със занулени промени"
 
-#: lib/about.tcl:26
-msgid "git-gui - a graphical user interface for Git."
-msgstr "git-gui — графичен интерфейс за Git."
+#: lib/merge.tcl:273
+msgid "Abort failed."
+msgstr "Неуспешно преустановяване."
 
-#: lib/checkout_op.tcl:85
-#, tcl-format
-msgid "Fetching %s from %s"
-msgstr "Доставяне на „%s“ от „%s“"
+#: lib/merge.tcl:275
+msgid "Abort completed.  Ready."
+msgstr "Успешно преустановяване. Готовност за следващо действие."
 
-#: lib/checkout_op.tcl:133
-#, tcl-format
-msgid "fatal: Cannot resolve %s"
-msgstr "фатална грешка: „%s“ не може да се открие"
+#: lib/mergetool.tcl:8
+msgid "Force resolution to the base version?"
+msgstr "Да се използва базовата версия"
 
-#: lib/checkout_op.tcl:175
-#, tcl-format
-msgid "Branch '%s' does not exist."
-msgstr "Клонът „%s“ не съществува."
+#: lib/mergetool.tcl:9
+msgid "Force resolution to this branch?"
+msgstr "Да се използва версията от този клон"
 
-#: lib/checkout_op.tcl:194
-#, tcl-format
-msgid "Failed to configure simplified git-pull for '%s'."
-msgstr "Неуспешно настройване на опростен git-pull за „%s“."
+#: lib/mergetool.tcl:10
+msgid "Force resolution to the other branch?"
+msgstr "Да се използва версията от другия клон"
 
-#: lib/checkout_op.tcl:229
+#: lib/mergetool.tcl:14
 #, tcl-format
 msgid ""
-"Branch '%s' already exists.\n"
+"Note that the diff shows only conflicting changes.\n"
 "\n"
-"It cannot fast-forward to %s.\n"
-"A merge is required."
+"%s will be overwritten.\n"
+"\n"
+"This operation can be undone only by restarting the merge."
 msgstr ""
-"Ð\9aлонÑ\8aÑ\82 â\80\9e%sâ\80\9c Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ва.\n"
+"РазликаÑ\82а Ð¿Ð¾ÐºÐ°Ð·Ð²Ð° Ñ\81амо Ñ\80азликиÑ\82е Ñ\81 ÐºÐ¾Ð½Ñ\84ликÑ\82.\n"
 "\n"
-"Той не може да бъде тривиално слят до „%s“.\n"
-"Необходимо е сливане."
+"Файлът „%s“ ще бъде презаписан.\n"
+"\n"
+"Тази операция може да бъде отменена само чрез започване на сливането наново."
 
-#: lib/checkout_op.tcl:243
+#: lib/mergetool.tcl:45
 #, tcl-format
-msgid "Merge strategy '%s' not supported."
-msgstr "Стратегия за сливане  „%s“ не се поддържа."
+msgid "File %s seems to have unresolved conflicts, still stage?"
+msgstr ""
+"Изглежда, че все още има некоригирани конфликти във файла „%s“. Да се добави "
+"ли файлът към индекса?"
 
-#: lib/checkout_op.tcl:262
+#: lib/mergetool.tcl:60
 #, tcl-format
-msgid "Failed to update '%s'."
-msgstr "Неуспешно обновяване на „%s“."
-
-#: lib/checkout_op.tcl:274
-msgid "Staging area (index) is already locked."
-msgstr "Индексът вече е заключен."
+msgid "Adding resolution for %s"
+msgstr "Добавяне на корекция на конфликтите в „%s“"
 
-#: 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"
+#: lib/mergetool.tcl:141
+msgid "Cannot resolve deletion or link conflicts using a tool"
 msgstr ""
-"Състоянието при последната проверка не отговаря на състоянието на "
-"хранилището.\n"
-"\n"
-"Някой друг процес за Git е променил хранилището междувременно. Състоянието "
-"трябва да бъде проверено, преди да се премине към нов клон.\n"
-"\n"
-"Автоматично ще започне нова проверка.\n"
+"Конфликтите при символни връзки или изтриване не могат да бъдат коригирани с "
+"външна програма."
 
-#: lib/checkout_op.tcl:345
-#, tcl-format
-msgid "Updating working directory to '%s'..."
-msgstr "Работната директория се привежда към „%s“…"
+#: lib/mergetool.tcl:146
+msgid "Conflict file does not exist"
+msgstr "Файлът, в който е конфликтът, не съществува"
 
-#: lib/checkout_op.tcl:346
-msgid "files checked out"
-msgstr "файла са изтеглени"
+#: lib/mergetool.tcl:246
+#, tcl-format
+msgid "Not a GUI merge tool: '%s'"
+msgstr "Това не е графична програма за сливане: „%s“"
 
-#: lib/checkout_op.tcl:376
+#: lib/mergetool.tcl:275
 #, tcl-format
-msgid "Aborted checkout of '%s' (file level merging is required)."
-msgstr ""
-"Преустановяване на изтеглянето на „%s“ (необходимо е пофайлово сливане)."
+msgid "Unsupported merge tool '%s'"
+msgstr "Неподдържана програма за сливане: „%s“"
 
-#: lib/checkout_op.tcl:377
-msgid "File level merge required."
-msgstr "Ð\9dеобÑ\85одимо Ðµ Ð¿Ð¾Ñ\84айлово Ñ\81ливане."
+#: lib/mergetool.tcl:310
+msgid "Merge tool is already running, terminate it?"
+msgstr "Ð\9fÑ\80огÑ\80амаÑ\82а Ð·Ð° Ñ\81ливане Ð²ÐµÑ\87е Ðµ Ñ\81Ñ\82аÑ\80Ñ\82иÑ\80ана. Ð\94а Ð±Ñ\8aде Ð»Ð¸ Ð¸Ð·ÐºÐ»Ñ\8eÑ\87ена?"
 
-#: lib/checkout_op.tcl:381
+#: lib/mergetool.tcl:330
 #, tcl-format
-msgid "Staying on branch '%s'."
-msgstr "Оставане върху клона „%s“."
+msgid ""
+"Error retrieving versions:\n"
+"%s"
+msgstr ""
+"Грешка при изтеглянето на версии:\n"
+"%s"
 
-#: lib/checkout_op.tcl:452
+#: lib/mergetool.tcl:350
+#, tcl-format
 msgid ""
-"You are no longer on a local branch.\n"
+"Could not start the merge tool:\n"
 "\n"
-"If you wanted to be on a branch, create one now starting from 'This Detached "
-"Checkout'."
+"%s"
 msgstr ""
-"Ð\92еÑ\87е Ð½Ðµ Ñ\81Ñ\82е Ð½Ð° Ð»Ð¾ÐºÐ°Ð»ÐµÐ½ ÐºÐ»Ð¾Ð½.\n"
+"Ð\9fÑ\80огÑ\80амаÑ\82а Ð·Ð° Ñ\81ливане Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ñ\81Ñ\82аÑ\80Ñ\82иÑ\80ана:\n"
 "\n"
-"Ако искате да сте на клон, създайте базиран на „Това несвързано изтегляне“."
+"%s"
 
-#: lib/checkout_op.tcl:503 lib/checkout_op.tcl:507
+#: 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 "Checked out '%s'."
-msgstr "„%s“ е изтеглен."
+msgid "Invalid global encoding '%s'"
+msgstr "Неправилно глобално кодиране „%s“"
 
-#: lib/checkout_op.tcl:535
+#: lib/option.tcl:19
 #, tcl-format
-msgid "Resetting '%s' to '%s' will lose the following commits:"
-msgstr ""
-"Зануляването на „%s“ към „%s“ ще доведе до загубването на следните подавания:"
+msgid "Invalid repo encoding '%s'"
+msgstr "Неправилно кодиране „%s“ на хранилището"
 
-#: lib/checkout_op.tcl:557
-msgid "Recovering lost commits may not be easy."
-msgstr "Ð\92Ñ\8aзÑ\81Ñ\82ановÑ\8fванеÑ\82о Ð½Ð° Ð·Ð°Ð³Ñ\83бениÑ\82е Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ\8f Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ðµ Ñ\82Ñ\80Ñ\83дно."
+#: lib/option.tcl:119
+msgid "Restore Defaults"
+msgstr "СÑ\82андаÑ\80Ñ\82ни Ð½Ð°Ñ\81Ñ\82Ñ\80ойки"
 
-#: lib/checkout_op.tcl:562
+#: lib/option.tcl:123
+msgid "Save"
+msgstr "Запазване"
+
+#: lib/option.tcl:133
 #, tcl-format
-msgid "Reset '%s'?"
-msgstr "Ð\97анÑ\83лÑ\8fване Ð½Ð° â\80\9e%sâ\80\9c?"
+msgid "%s Repository"
+msgstr "Ð¥Ñ\80анилиÑ\89е â\80\9e%sâ\80\9c"
 
-#: lib/checkout_op.tcl:571 lib/branch_create.tcl:85
-msgid "Reset"
-msgstr "Ð\9eÑ\82наÑ\87ало"
+#: lib/option.tcl:134
+msgid "Global (All Repositories)"
+msgstr "Ð\93лобално (за Ð²Ñ\81иÑ\87ки Ñ\85Ñ\80анилиÑ\89а)"
 
-#: 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/option.tcl:140
+msgid "User Name"
+msgstr "Потребителско име"
 
-#: lib/branch_create.tcl:23
-msgid "Create Branch"
-msgstr "СÑ\8aздаване Ð½Ð° ÐºÐ»Ð¾Ð½"
+#: lib/option.tcl:141
+msgid "Email Address"
+msgstr "Ð\90дÑ\80еÑ\81 Ð½Ð° Ðµ-поÑ\89а"
 
-#: lib/branch_create.tcl:28
-msgid "Create New Branch"
-msgstr "СÑ\8aздаване Ð½Ð° Ð½Ð¾Ð² ÐºÐ»Ð¾Ð½"
+#: lib/option.tcl:143
+msgid "Summarize Merge Commits"
+msgstr "Ð\9eбобÑ\89аване Ð½Ð° Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ\8fÑ\82а Ð¿Ñ\80и Ñ\81ливане"
 
-#: lib/branch_create.tcl:42
-msgid "Branch Name"
-msgstr "Ð\98ме Ð½Ð° ÐºÐ»Ð¾Ð½а"
+#: lib/option.tcl:144
+msgid "Merge Verbosity"
+msgstr "Ð\9fодÑ\80обноÑ\81Ñ\82и Ð¿Ñ\80и Ñ\81ливаниÑ\8fÑ\82а"
 
-#: lib/branch_create.tcl:57
-msgid "Match Tracking Branch Name"
-msgstr "СÑ\8aвпадане Ð¿Ð¾ Ð¸Ð¼ÐµÑ\82о Ð½Ð° Ñ\81ледениÑ\8f ÐºÐ»Ð¾Ð½"
+#: lib/option.tcl:145
+msgid "Show Diffstat After Merge"
+msgstr "Ð\98звеждане Ð½Ð° Ñ\81Ñ\82аÑ\82иÑ\81Ñ\82ика Ñ\81лед Ñ\81ливаниÑ\8fÑ\82а"
 
-#: lib/branch_create.tcl:66
-msgid "Starting Revision"
-msgstr "Ð\9dаÑ\87ална Ð²ÐµÑ\80Ñ\81иÑ\8f"
+#: lib/option.tcl:146
+msgid "Use Merge Tool"
+msgstr "Ð\98зползване Ð½Ð° Ð¿Ñ\80огÑ\80ама Ð·Ð° Ñ\81ливане"
 
-#: lib/branch_create.tcl:72
-msgid "Update Existing Branch:"
-msgstr "Ð\9eбновÑ\8fване Ð½Ð° Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ваÑ\89 ÐºÐ»Ð¾Ð½:"
+#: lib/option.tcl:148
+msgid "Trust File Modification Timestamps"
+msgstr "Ð\94овеÑ\80ие Ð²Ñ\8aв Ð²Ñ\80емеÑ\82о Ð½Ð° Ð¿Ñ\80омÑ\8fна Ð½Ð° Ñ\84айловеÑ\82е"
 
-#: lib/branch_create.tcl:75
-msgid "No"
-msgstr "Ð\9dе"
+#: lib/option.tcl:149
+msgid "Prune Tracking Branches During Fetch"
+msgstr "Ð\9eкаÑ\81Ñ\82Ñ\80Ñ\8fне Ð½Ð° Ñ\81ледÑ\8fÑ\89иÑ\82е ÐºÐ»Ð¾Ð½Ð¾Ð²Ðµ Ð¿Ñ\80и Ð´Ð¾Ñ\81Ñ\82авÑ\8fне"
 
-#: lib/branch_create.tcl:80
-msgid "Fast Forward Only"
-msgstr "Само Ñ\82Ñ\80ивиално Ð¿Ñ\80евÑ\8aÑ\80Ñ\82аÑ\89о Ñ\81ливане"
+#: lib/option.tcl:150
+msgid "Match Tracking Branches"
+msgstr "Ð\9dапаÑ\81ване Ð½Ð° Ñ\81ледÑ\8fÑ\89иÑ\82е ÐºÐ»Ð¾Ð½Ð¾Ð²е"
 
-#: lib/branch_create.tcl:97
-msgid "Checkout After Creation"
-msgstr "Ð\9fÑ\80еминаване ÐºÑ\8aм ÐºÐ»Ð¾Ð½Ð° Ñ\81лед Ñ\81Ñ\8aздаванеÑ\82о Ð¼Ñ\83"
+#: lib/option.tcl:151
+msgid "Use Textconv For Diffs and Blames"
+msgstr "Ð\98зползване Ð½Ð° â\80\9etextconvâ\80\9c Ð·Ð° Ñ\80азликиÑ\82е Ð¸ Ð°Ð½Ð¾Ñ\82иÑ\80анеÑ\82о"
 
-#: lib/branch_create.tcl:132
-msgid "Please select a tracking branch."
-msgstr "Ð\98збеÑ\80еÑ\82е ÐºÐ»Ð¾Ð½ Ð·Ð° Ñ\81ледени."
+#: lib/option.tcl:152
+msgid "Blame Copy Only On Changed Files"
+msgstr "Ð\90ноÑ\82иÑ\80ане Ð½Ð° ÐºÐ¾Ð¿Ð¸ÐµÑ\82о Ñ\81амо Ð¿Ð¾ Ð¿Ñ\80оменениÑ\82е Ñ\84айлове"
 
-#: lib/branch_create.tcl:141
-#, tcl-format
-msgid "Tracking branch %s is not a branch in the remote repository."
-msgstr "Следящият клон — „%s“, не съществува в отдалеченото хранилище."
+#: lib/option.tcl:153
+msgid "Maximum Length of Recent Repositories List"
+msgstr "Максимален брой на списъка „Скоро ползвани“ хранилища"
 
-#: lib/console.tcl:59
-msgid "Working... please wait..."
-msgstr "Ð\92 Ð¼Ð¾Ð¼ÐµÐ½Ñ\82а Ñ\81е Ð¸Ð·Ð²Ñ\8aÑ\80Ñ\88ва Ð´ÐµÐ¹Ñ\81Ñ\82вие, Ð¸Ð·Ñ\87акайÑ\82еâ\80¦"
+#: lib/option.tcl:154
+msgid "Minimum Letters To Blame Copy On"
+msgstr "Ð\9cинимален Ð±Ñ\80ой Ð·Ð½Ð°Ñ\86и Ð·Ð° Ð°Ð½Ð¾Ñ\82иÑ\80ане Ð½Ð° ÐºÐ¾Ð¿Ð¸ÐµÑ\82о"
 
-#: lib/console.tcl:186
-msgid "Success"
-msgstr "УÑ\81пеÑ\85"
+#: lib/option.tcl:155
+msgid "Blame History Context Radius (days)"
+msgstr "Ð\98Ñ\81Ñ\82оÑ\80иÑ\87еÑ\81ки Ð¾Ð±Ñ\85ваÑ\82 Ð·Ð° Ð°Ð½Ð¾Ñ\82иÑ\80ане Ð² Ð´Ð½Ð¸"
 
-#: lib/console.tcl:200
-msgid "Error: Command Failed"
-msgstr "Ð\93Ñ\80еÑ\88ка: Ð½ÐµÑ\83Ñ\81пеÑ\88но Ð¸Ð·Ð¿Ñ\8aлнение Ð½Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°"
+#: lib/option.tcl:156
+msgid "Number of Diff Context Lines"
+msgstr "Ð\91Ñ\80ой Ñ\80едове Ð·Ð° ÐºÐ¾Ð½Ñ\82екÑ\81Ñ\82а Ð½Ð° Ñ\80азликиÑ\82е"
 
-#: lib/choose_rev.tcl:52
-msgid "This Detached Checkout"
-msgstr "Това Ð½ÐµÑ\81вÑ\8aÑ\80зано Ð¸Ð·Ñ\82еглÑ\8fне"
+#: lib/option.tcl:157
+msgid "Additional Diff Parameters"
+msgstr "Ð\90Ñ\80гÑ\83менÑ\82и ÐºÑ\8aм ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°Ñ\82а Ð·Ð° Ñ\80азликиÑ\82е"
 
-#: lib/choose_rev.tcl:60
-msgid "Revision Expression:"
-msgstr "Ð\98зÑ\80аз Ð·Ð° Ð²ÐµÑ\80Ñ\81иÑ\8f:"
+#: lib/option.tcl:158
+msgid "Commit Message Text Width"
+msgstr "ШиÑ\80оÑ\87ина Ð½Ð° Ñ\82екÑ\81Ñ\82а Ð½Ð° Ñ\81Ñ\8aобÑ\89ениеÑ\82о Ð¿Ñ\80и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ"
 
-#: lib/choose_rev.tcl:72
-msgid "Local Branch"
-msgstr "Ð\9bокален ÐºÐ»Ð¾Ð½"
+#: lib/option.tcl:159
+msgid "New Branch Name Template"
+msgstr "Шаблон Ð·Ð° Ð¸Ð¼ÐµÑ\82о Ð½Ð° Ð½Ð¾Ð²Ð¸Ñ\82е ÐºÐ»Ð¾Ð½Ð¸"
 
-#: lib/choose_rev.tcl:77
-msgid "Tracking Branch"
-msgstr "СледÑ\8fÑ\89 ÐºÐ»Ð¾Ð½"
+#: lib/option.tcl:160
+msgid "Default File Contents Encoding"
+msgstr "Ð\9aодиÑ\80ане Ð½Ð° Ñ\84айловеÑ\82е"
 
-#: lib/choose_rev.tcl:82 lib/choose_rev.tcl:544
-msgid "Tag"
-msgstr "Ð\95Ñ\82икеÑ\82"
+#: lib/option.tcl:161
+msgid "Warn before committing to a detached head"
+msgstr "Ð\9fÑ\80едÑ\83пÑ\80еждаване Ð¿Ñ\80и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ ÐºÑ\8aм Ð½ÐµÑ\81вÑ\8aÑ\80зан Ñ\83казаÑ\82ел"
 
-#: lib/choose_rev.tcl:321
-#, tcl-format
-msgid "Invalid revision: %s"
-msgstr "Неправилна версия: %s"
+#: lib/option.tcl:162
+msgid "Staging of untracked files"
+msgstr "Добавяне на неследените файлове към индекса"
 
-#: lib/choose_rev.tcl:342
-msgid "No revision selected."
-msgstr "Ð\9dе Ðµ Ð¸Ð·Ð±Ñ\80ана Ð²ÐµÑ\80Ñ\81иÑ\8f."
+#: lib/option.tcl:163
+msgid "Show untracked files"
+msgstr "Ð\9fоказване Ð½Ð° Ð½ÐµÑ\81ледениÑ\82е Ñ\84айлове"
 
-#: lib/choose_rev.tcl:350
-msgid "Revision expression is empty."
-msgstr "Ð\98зÑ\80азÑ\8aÑ\82 Ð·Ð° Ð²ÐµÑ\80Ñ\81иÑ\8f Ðµ Ð¿Ñ\80азен."
+#: lib/option.tcl:164
+msgid "Tab spacing"
+msgstr "ШиÑ\80ина Ð½Ð° Ñ\82абÑ\83лаÑ\86иÑ\8fÑ\82а"
 
-#: lib/choose_rev.tcl:537
-msgid "Updated"
-msgstr "Ð\9eбновен"
+#: lib/option.tcl:210
+msgid "Change"
+msgstr "СмÑ\8fна"
 
-#: lib/choose_rev.tcl:565
-msgid "URL"
-msgstr "Ð\90дÑ\80еÑ\81"
+#: lib/option.tcl:254
+msgid "Spelling Dictionary:"
+msgstr "Ð\9fÑ\80авопиÑ\81ен Ñ\80еÑ\87ник:"
 
-#: lib/line.tcl:17
-msgid "Goto Line:"
-msgstr "Ð\9aÑ\8aм Ñ\80ед:"
+#: lib/option.tcl:284
+msgid "Change Font"
+msgstr "СмÑ\8fна Ð½Ð° Ñ\88Ñ\80иÑ\84Ñ\82а"
 
-#: lib/line.tcl:23
-msgid "Go"
-msgstr "Придвижване"
+#: lib/option.tcl:288
+#, tcl-format
+msgid "Choose %s"
+msgstr "Избор на „%s“"
 
-#: lib/commit.tcl:9
-msgid ""
-"There is nothing to amend.\n"
-"\n"
-"You are about to create the initial commit.  There is no commit before this "
-"to amend.\n"
-msgstr ""
-"Няма какво да се поправи.\n"
-"\n"
-"Ще създадете първоначалното подаване. Преди него няма други подавания, които "
-"да поправите.\n"
+#: lib/option.tcl:294
+msgid "pt."
+msgstr "тчк."
 
-#: 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"
+#: lib/option.tcl:308
+msgid "Preferences"
+msgstr "Настройки"
 
-#: lib/commit.tcl:48
-msgid "Error loading commit data for amend:"
-msgstr "Ð\93Ñ\80еÑ\88ка Ð¿Ñ\80и Ð·Ð°Ñ\80еждане Ð½Ð° Ð´Ð°Ð½Ð½Ð¸Ñ\82е Ð¾Ñ\82 Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ, ÐºÐ¾Ð¸Ñ\82о Ð´Ð° Ñ\81е Ð¿Ð¾Ð¿Ñ\80авÑ\8fÑ\82:"
+#: lib/option.tcl:345
+msgid "Failed to completely save options:"
+msgstr "Ð\9dеÑ\83Ñ\81пеÑ\88но Ð·Ð°Ð¿Ð°Ð·Ð²Ð°Ð½Ðµ Ð½Ð° Ð½Ð°Ñ\81Ñ\82Ñ\80ойкиÑ\82е:"
 
-#: lib/commit.tcl:75
-msgid "Unable to obtain your identity:"
-msgstr "Ð\98денÑ\82иÑ\84икаÑ\86иÑ\8fÑ\82а Ð²Ð¸ Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¾Ð¿Ñ\80еделена:"
+#: lib/remote.tcl:200
+msgid "Push to"
+msgstr "Ð\98зÑ\82лаÑ\81кване ÐºÑ\8aм"
 
-#: lib/commit.tcl:80
-msgid "Invalid GIT_COMMITTER_IDENT:"
-msgstr "Ð\9dепÑ\80авилно Ð¿Ð¾Ð»Ðµ â\80\9eGIT_COMMITTER_IDENTâ\80\9c:"
+#: lib/remote.tcl:218
+msgid "Remove Remote"
+msgstr "Ð\9fÑ\80емаÑ\85ване Ð½Ð° Ð¾Ñ\82далеÑ\87ено Ñ\85Ñ\80анилиÑ\89е"
 
-#: lib/commit.tcl:129
-#, tcl-format
-msgid "warning: Tcl does not support encoding '%s'."
-msgstr "предупреждение: Tcl не поддържа кодирането „%s“."
+#: lib/remote.tcl:223
+msgid "Prune from"
+msgstr "Окастряне от"
+
+#: lib/remote.tcl:228
+msgid "Fetch from"
+msgstr "Доставяне от"
 
-#: 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"
-"\n"
-"The rescan will be automatically started now.\n"
-msgstr ""
-"Състоянието при последната проверка не отговаря на състоянието на "
-"хранилището.\n"
-"\n"
-"Някой друг процес за Git е променил хранилището междувременно. Състоянието "
-"трябва да бъде проверено преди ново подаване.\n"
-"\n"
-"Автоматично ще започне нова проверка.\n"
+#: lib/remote.tcl:253 lib/remote.tcl:258
+msgid "All"
+msgstr "Всички"
 
-#: lib/commit.tcl:173
+#: lib/remote_add.tcl:20
 #, 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"
+msgid "%s (%s): Add Remote"
+msgstr "%s (%s): Добавяне на отдалечено хранилище"
 
-#: lib/commit.tcl:181
-#, tcl-format
-msgid ""
-"Unknown file state %s detected.\n"
-"\n"
-"File %s cannot be committed by this program.\n"
-msgstr ""
-"Непознато състояние на файл „%s“.\n"
-"\n"
-"Файлът „%s“ не може да бъде подаден чрез текущата програма.\n"
+#: lib/remote_add.tcl:25
+msgid "Add New Remote"
+msgstr "Добавяне на отдалечено хранилище"
 
-#: lib/commit.tcl:189
-msgid ""
-"No changes to commit.\n"
-"\n"
-"You must stage at least 1 file before you can commit.\n"
-msgstr ""
-"Няма промени за подаване.\n"
-"\n"
-"Трябва да добавите поне един файл към индекса, за да подадете.\n"
+#: lib/remote_add.tcl:30 lib/tools_dlg.tcl:37
+msgid "Add"
+msgstr "Добавяне"
 
-#: lib/commit.tcl:204
-msgid ""
-"Please supply a commit message.\n"
-"\n"
-"A good commit message has the following format:\n"
-"\n"
-"- First line: Describe in one sentence what you did.\n"
-"- Second line: Blank\n"
-"- Remaining lines: Describe why this change is good.\n"
-msgstr ""
-"Задайте добро съобщение при подаване.\n"
-"\n"
-"Използвайте следния формат:\n"
-"\n"
-"● Първи ред: описание в едно изречение на промяната.\n"
-"● Втори ред: празен.\n"
-"● Останалите редове: опишете защо се налага тази промяна.\n"
+#: lib/remote_add.tcl:39
+msgid "Remote Details"
+msgstr "Данни за отдалеченото хранилище"
 
-#: lib/commit.tcl:235
-msgid "Calling pre-commit hook..."
-msgstr "Ð\98зпÑ\8aлнÑ\8fване Ð½Ð° ÐºÑ\83каÑ\82а Ð¿Ñ\80еди Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµâ\80¦"
+#: lib/remote_add.tcl:50
+msgid "Location:"
+msgstr "Ð\9cеÑ\81Ñ\82оположение:"
 
-#: lib/commit.tcl:250
-msgid "Commit declined by pre-commit hook."
-msgstr "Ð\9fодаванеÑ\82о Ðµ Ð¾Ñ\82Ñ\85вÑ\8aÑ\80лено Ð¾Ñ\82 ÐºÑ\83каÑ\82а Ð¿Ñ\80еди Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ."
+#: lib/remote_add.tcl:60
+msgid "Further Action"
+msgstr "СледваÑ\89о Ð´ÐµÐ¹Ñ\81Ñ\82вие"
 
-#: lib/commit.tcl:269
-msgid ""
-"You are about to commit on a detached head. This is a potentially dangerous "
-"thing to do because if you switch to another branch you will lose your "
-"changes and it can be difficult to retrieve them later from the reflog. You "
-"should probably cancel this commit and create a new branch to continue.\n"
-" \n"
-" Do you really want to proceed with your Commit?"
-msgstr ""
-"Ще подавате към несвързан връх. Това е опасно — при изтеглянето на друг клон "
-"ще изгубите промените си. След това може да е невъзможно да ги възстановите "
-"от журнала на указателите „reflog“. Най-вероятно трябва да отмените това "
-"подаване и да създадете клон, в който да подадете.\n"
-" \n"
-"Сигурни ли сте, че искате да подадете към несвързан връх?"
+#: lib/remote_add.tcl:63
+msgid "Fetch Immediately"
+msgstr "Незабавно доставяне"
 
-#: lib/commit.tcl:290
-msgid "Calling commit-msg hook..."
-msgstr "Ð\98зпÑ\8aлнÑ\8fване Ð½Ð° ÐºÑ\83каÑ\82а Ð·Ð° Ñ\81Ñ\8aобÑ\89ениеÑ\82о Ð¿Ñ\80и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµâ\80¦"
+#: lib/remote_add.tcl:69
+msgid "Initialize Remote Repository and Push"
+msgstr "Ð\98ниÑ\86иализиÑ\80ане Ð½Ð° Ð¾Ñ\82далеÑ\87еноÑ\82о Ñ\85Ñ\80анилиÑ\89е Ð¸ Ð¸Ð·Ñ\82лаÑ\81кване Ð½Ð° Ð¿Ñ\80омениÑ\82е"
 
-#: lib/commit.tcl:305
-msgid "Commit declined by commit-msg hook."
-msgstr "Ð\9fодаванеÑ\82о Ðµ Ð¾Ñ\82Ñ\85вÑ\8aÑ\80лено Ð¾Ñ\82 ÐºÑ\83каÑ\82а Ð·Ð° Ñ\81Ñ\8aобÑ\89ениеÑ\82о Ð¿Ñ\80и Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ."
+#: lib/remote_add.tcl:75
+msgid "Do Nothing Else Now"
+msgstr "Ð\94а Ð½Ðµ Ñ\81е Ð¿Ñ\80ави Ð½Ð¸Ñ\89о"
 
-#: lib/commit.tcl:318
-msgid "Committing changes..."
-msgstr "Ð\9fодаване Ð½Ð° Ð¿Ñ\80омениÑ\82еâ\80¦"
+#: lib/remote_add.tcl:100
+msgid "Please supply a remote name."
+msgstr "Ð\97адайÑ\82е Ð¸Ð¼Ðµ Ð·Ð° Ð¾Ñ\82далеÑ\87еноÑ\82о Ñ\85Ñ\80анилиÑ\89е."
 
-#: lib/commit.tcl:334
-msgid "write-tree failed:"
-msgstr "неуспешно запазване на дървото (write-tree):"
+#: lib/remote_add.tcl:113
+#, tcl-format
+msgid "'%s' is not an acceptable remote name."
+msgstr "Отдалечено хранилище не може да се казва „%s“."
 
-#: lib/commit.tcl:335 lib/commit.tcl:379 lib/commit.tcl:400
-msgid "Commit failed."
-msgstr "Неуспешно подаване."
+#: lib/remote_add.tcl:124
+#, tcl-format
+msgid "Failed to add remote '%s' of location '%s'."
+msgstr "Неуспешно добавяне на отдалеченото хранилище „%s“ от адрес „%s“."
 
-#: lib/commit.tcl:352
+#: lib/remote_add.tcl:132 lib/transport.tcl:6
 #, tcl-format
-msgid "Commit %s appears to be corrupt"
-msgstr "Ð\9fодаванеÑ\82о â\80\9e%sâ\80\9c Ð¸Ð·Ð³Ð»ÐµÐ¶Ð´Ð° Ð¿Ð¾Ð²Ñ\80едено"
+msgid "fetch %s"
+msgstr "доÑ\81Ñ\82авÑ\8fне Ð½Ð° â\80\9e%sâ\80\9c"
 
-#: lib/commit.tcl:357
-msgid ""
-"No changes to commit.\n"
-"\n"
-"No files were modified by this commit and it was not a merge commit.\n"
-"\n"
-"A rescan will be automatically started now.\n"
-msgstr ""
-"Няма промени за подаване.\n"
-"\n"
-"В това подаване не са променяни никакви файлове, а и не е подаване със "
-"сливане.\n"
-"\n"
-"Автоматично ще започне нова проверка.\n"
+#: lib/remote_add.tcl:133
+#, tcl-format
+msgid "Fetching the %s"
+msgstr "Доставяне на „%s“"
 
-#: lib/commit.tcl:364
-msgid "No changes to commit."
-msgstr "Няма промени за подаване."
+#: lib/remote_add.tcl:156
+#, tcl-format
+msgid "Do not know how to initialize repository at location '%s'."
+msgstr "Хранилището с местоположение „%s“ не може да бъде инициализирано."
 
-#: lib/commit.tcl:378
-msgid "commit-tree failed:"
-msgstr "неуспешно подаване на дървото (commit-tree):"
+#: 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/commit.tcl:399
-msgid "update-ref failed:"
-msgstr "неуспешно обновяване на указателите (update-ref):"
+#: lib/remote_add.tcl:163
+#, tcl-format
+msgid "Setting up the %s (at %s)"
+msgstr "Добавяне на хранилище „%s“ (с адрес „%s“)"
 
-#: lib/commit.tcl:492
+#: lib/remote_branch_delete.tcl:29
 #, tcl-format
-msgid "Created commit %s: %s"
-msgstr "Успешно подаване %s: %s"
+msgid "%s (%s): Delete Branch Remotely"
+msgstr "%s (%s): Изтриване на отдалечения клон"
 
-#: lib/branch_delete.tcl:16
-msgid "Delete Branch"
-msgstr "Изтриване на клон"
+#: lib/remote_branch_delete.tcl:34
+msgid "Delete Branch Remotely"
+msgstr "Ð\98зÑ\82Ñ\80иване Ð½Ð° Ð¾Ñ\82далеÑ\87ениÑ\8f ÐºÐ»Ð¾Ð½"
 
-#: lib/branch_delete.tcl:21
-msgid "Delete Local Branch"
-msgstr "Ð\98зÑ\82Ñ\80иване Ð½Ð° Ð»Ð¾ÐºÐ°Ð»ÐµÐ½ ÐºÐ»Ð¾Ð½"
+#: lib/remote_branch_delete.tcl:48
+msgid "From Repository"
+msgstr "Ð\9eÑ\82 Ñ\85Ñ\80анилиÑ\89е"
 
-#: lib/branch_delete.tcl:39
-msgid "Local Branches"
-msgstr "Ð\9bокални ÐºÐ»Ð¾Ð½Ð¸"
+#: lib/remote_branch_delete.tcl:51 lib/transport.tcl:165
+msgid "Remote:"
+msgstr "Ð\9eÑ\82далеÑ\87ено Ñ\85Ñ\80анилиÑ\89е:"
 
-#: lib/branch_delete.tcl:51
-msgid "Delete Only If Merged Into"
-msgstr "Ð\98зÑ\82Ñ\80иване, Ñ\81амо Ð°ÐºÐ¾ Ð¿Ñ\80омениÑ\82е Ñ\81а Ñ\81леÑ\82и Ð¸ Ð´Ñ\80Ñ\83гаде"
+#: lib/remote_branch_delete.tcl:72 lib/transport.tcl:187
+msgid "Arbitrary Location:"
+msgstr "Ð\9fÑ\80оизволно Ð¼ÐµÑ\81Ñ\82оположение:"
 
-#: lib/branch_delete.tcl:103
-#, tcl-format
-msgid "The following branches are not completely merged into %s:"
-msgstr "Не всички промени в клоните са слети в „%s“:"
+#: lib/remote_branch_delete.tcl:88
+msgid "Branches"
+msgstr "Клони"
 
-#: lib/branch_delete.tcl:141
+#: 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 ""
-"Failed to delete branches:\n"
-"%s"
+"The following branches are not completely merged into %s:\n"
+"\n"
+" - %s"
 msgstr ""
-"Неуспешно триене на клони:\n"
-"%s"
+"Следните клони не са слети напълно в „%s“:\n"
+"\n"
+" ● %s"
 
-#: lib/blame.tcl:73
-msgid "File Viewer"
-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/blame.tcl:79
-msgid "Commit:"
-msgstr "Ð\9fодаване:"
+#: lib/remote_branch_delete.tcl:208
+msgid "Please select one or more branches to delete."
+msgstr "Ð\98збеÑ\80еÑ\82е Ð¿Ð¾Ð½Ðµ ÐµÐ´Ð¸Ð½ ÐºÐ»Ð¾Ð½ Ð·Ð° Ð¸Ð·Ñ\82Ñ\80иване."
 
-#: lib/blame.tcl:280
-msgid "Copy Commit"
-msgstr "Копиране на подаване"
+#: lib/remote_branch_delete.tcl:227
+#, tcl-format
+msgid "Deleting branches from %s"
+msgstr "Изтриване на клони от „%s“"
 
-#: lib/blame.tcl:284
-msgid "Find Text..."
-msgstr "ТÑ\8aÑ\80Ñ\81ене Ð½Ð° Ñ\82екÑ\81Ñ\82â\80¦"
+#: lib/remote_branch_delete.tcl:300
+msgid "No repository selected."
+msgstr "Ð\9dе Ðµ Ð¸Ð·Ð±Ñ\80ано Ñ\85Ñ\80анилиÑ\89е."
 
-#: lib/blame.tcl:288
-msgid "Goto Line..."
-msgstr "Към ред…"
+#: lib/remote_branch_delete.tcl:305
+#, tcl-format
+msgid "Scanning %s..."
+msgstr "Претърсване на „%s“…"
 
-#: lib/blame.tcl:297
-msgid "Do Full Copy Detection"
-msgstr "Ð\9fÑ\8aлно Ñ\82Ñ\8aÑ\80Ñ\81ене Ð½Ð° ÐºÐ¾Ð¿Ð¸Ñ\80ане"
+#: lib/search.tcl:48
+msgid "Find:"
+msgstr "ТÑ\8aÑ\80Ñ\81ене:"
 
-#: lib/blame.tcl:301
-msgid "Show History Context"
-msgstr "Ð\9fоказване Ð½Ð° ÐºÐ¾Ð½Ñ\82екÑ\81Ñ\82а Ð¾Ñ\82 Ð¸Ñ\81Ñ\82оÑ\80иÑ\8fÑ\82а"
+#: lib/search.tcl:50
+msgid "Next"
+msgstr "СледваÑ\89а Ð¿Ð¾Ñ\8fва"
 
-#: lib/blame.tcl:304
-msgid "Blame Parent Commit"
-msgstr "Ð\90ноÑ\82иÑ\80ане Ð½Ð° Ñ\80одиÑ\82елÑ\81коÑ\82о Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ"
+#: lib/search.tcl:51
+msgid "Prev"
+msgstr "Ð\9fÑ\80едиÑ\88на Ð¿Ð¾Ñ\8fва"
 
-#: lib/blame.tcl:466
-#, tcl-format
-msgid "Reading %s..."
-msgstr "Чете се „%s“…"
+#: lib/search.tcl:52
+msgid "RegExp"
+msgstr "РегИзр"
 
-#: lib/blame.tcl:594
-msgid "Loading copy/move tracking annotations..."
-msgstr "Ð\97аÑ\80еждане Ð½Ð° Ð°Ð½Ð¾Ñ\82аÑ\86ииÑ\82е Ð·Ð° Ð¿Ñ\80оÑ\81ледÑ\8fване Ð½Ð° ÐºÐ¾Ð¿Ð¸Ñ\80анеÑ\82о/пÑ\80емеÑ\81Ñ\82ванеÑ\82оâ\80¦"
+#: lib/search.tcl:54
+msgid "Case"
+msgstr "Ð\93лавни/малки"
 
-#: lib/blame.tcl:614
-msgid "lines annotated"
-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/blame.tcl:806
-msgid "Loading original location annotations..."
-msgstr "Ð\97аÑ\80еждане Ð½Ð° Ð°Ð½Ð¾Ñ\82аÑ\86ииÑ\82е Ð·Ð° Ð¿Ñ\8aÑ\80вонаÑ\87алноÑ\82о Ð¼ÐµÑ\81Ñ\82оположениеâ\80¦"
+#: lib/shortcut.tcl:24 lib/shortcut.tcl:65
+msgid "Cannot write shortcut:"
+msgstr "Ð\9aлавиÑ\88наÑ\82а ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ\86иÑ\8f Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð·Ð°Ð¿Ð°Ð·ÐµÐ½Ð°:"
 
-#: lib/blame.tcl:809
-msgid "Annotation complete."
-msgstr "Ð\90ноÑ\82иÑ\80анеÑ\82о Ð·Ð°Ð²Ñ\8aÑ\80Ñ\88и."
+#: lib/shortcut.tcl:140
+msgid "Cannot write icon:"
+msgstr "Ð\98конаÑ\82а Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð·Ð°Ð¿Ð°Ð·ÐµÐ½Ð°:"
 
-#: lib/blame.tcl:839
-msgid "Busy"
-msgstr "Ð\9eпеÑ\80аÑ\86иÑ\8fÑ\82а Ð½Ðµ Ðµ Ð·Ð°Ð²Ñ\8aÑ\80Ñ\88ила"
+#: lib/spellcheck.tcl:57
+msgid "Unsupported spell checker"
+msgstr "Тази Ð¿Ñ\80огÑ\80ама Ð·Ð° Ð¿Ñ\80овеÑ\80ка Ð½Ð° Ð¿Ñ\80авопиÑ\81а Ð½Ðµ Ñ\81е Ð¿Ð¾Ð´Ð´Ñ\8aÑ\80жа"
 
-#: lib/blame.tcl:840
-msgid "Annotation process is already running."
-msgstr "Ð\92 Ð¼Ð¾Ð¼ÐµÐ½Ñ\82а Ñ\82еÑ\87е Ð¿Ñ\80оÑ\86еÑ\81 Ð½Ð° Ð°Ð½Ð¾Ñ\82иÑ\80ане."
+#: lib/spellcheck.tcl:65
+msgid "Spell checking is unavailable"
+msgstr "Ð\9bипÑ\81ва Ð¿Ñ\80огÑ\80ама Ð·Ð° Ð¿Ñ\80овеÑ\80ка Ð½Ð° Ð¿Ñ\80авопиÑ\81а"
 
-#: lib/blame.tcl:879
-msgid "Running thorough copy detection..."
-msgstr "Ð\98зпÑ\8aлнÑ\8fва Ñ\81е Ñ\86Ñ\8fлоÑ\81Ñ\82ен Ð¿Ñ\80оÑ\86еÑ\81 Ð½Ð° Ð¾Ñ\82кÑ\80иване Ð½Ð° ÐºÐ¾Ð¿Ð¸Ñ\80анеâ\80¦"
+#: lib/spellcheck.tcl:68
+msgid "Invalid spell checking configuration"
+msgstr "Ð\9dепÑ\80авилни Ð½Ð°Ñ\81Ñ\82Ñ\80ойки Ð½Ð° Ð¿Ñ\80овеÑ\80каÑ\82а Ð½Ð° Ð¿Ñ\80авопиÑ\81а"
 
-#: lib/blame.tcl:947
-msgid "Loading annotation..."
-msgstr "Зареждане на анотации…"
+#: lib/spellcheck.tcl:70
+#, tcl-format
+msgid "Reverting dictionary to %s."
+msgstr "Ползване на речник за език „%s“."
 
-#: lib/blame.tcl:1000
-msgid "Author:"
-msgstr "Ð\90вÑ\82оÑ\80:"
+#: lib/spellcheck.tcl:73
+msgid "Spell checker silently failed on startup"
+msgstr "Ð\9fÑ\80огÑ\80амаÑ\82а Ð·Ð° Ð¿Ñ\80авопиÑ\81 Ð´Ð°Ð¶Ðµ Ð½Ðµ Ñ\81Ñ\82аÑ\80Ñ\82иÑ\80а Ñ\83Ñ\81пеÑ\88но."
 
-#: lib/blame.tcl:1004
-msgid "Committer:"
-msgstr "Ð\9fодал:"
+#: lib/spellcheck.tcl:80
+msgid "Unrecognized spell checker"
+msgstr "Ð\9dепознаÑ\82а Ð¿Ñ\80огÑ\80ама Ð·Ð° Ð¿Ñ\80овеÑ\80ка Ð½Ð° Ð¿Ñ\80авопиÑ\81а"
 
-#: lib/blame.tcl:1009
-msgid "Original File:"
-msgstr "Ð\9fÑ\8aÑ\80вонаÑ\87ален Ñ\84айл:"
+#: lib/spellcheck.tcl:186
+msgid "No Suggestions"
+msgstr "Ð\9dÑ\8fма Ð¿Ñ\80едложениÑ\8f"
 
-#: lib/blame.tcl:1057
-msgid "Cannot find HEAD commit:"
-msgstr "Ð\9fодаванеÑ\82о Ð·Ð° Ð²Ñ\80Ñ\8aÑ\85 â\80\9eHEADâ\80\9c Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ñ\81е Ð¾Ñ\82кÑ\80ие:"
+#: lib/spellcheck.tcl:388
+msgid "Unexpected EOF from spell checker"
+msgstr "Ð\9dеоÑ\87акван ÐºÑ\80ай Ð½Ð° Ñ\84айл Ð¾Ñ\82 Ð¿Ñ\80огÑ\80амаÑ\82а Ð·Ð° Ð¿Ñ\80овеÑ\80ка Ð½Ð° Ð¿Ñ\80авопиÑ\81а"
 
-#: lib/blame.tcl:1112
-msgid "Cannot find parent commit:"
-msgstr "РодиÑ\82елÑ\81коÑ\82о Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ðµ Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¾Ñ\82кÑ\80иÑ\82о"
+#: lib/spellcheck.tcl:392
+msgid "Spell Checker Failed"
+msgstr "Ð\93Ñ\80еÑ\88ка Ð² Ð¿Ñ\80огÑ\80амаÑ\82а Ð·Ð° Ð¿Ñ\80овеÑ\80ка Ð½Ð° Ð¿Ñ\80авопиÑ\81а"
 
-#: lib/blame.tcl:1127
-msgid "Unable to display parent"
-msgstr "РодиÑ\82елÑ\8fÑ\82 Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¿Ð¾ÐºÐ°Ð·Ð°Ð½"
+#: lib/sshkey.tcl:31
+msgid "No keys found."
+msgstr "Ð\9dе Ñ\81а Ð¾Ñ\82кÑ\80иÑ\82и ÐºÐ»Ñ\8eÑ\87ове."
 
-#: lib/blame.tcl:1269
-msgid "Originally By:"
-msgstr "Първоначално от:"
+#: lib/sshkey.tcl:34
+#, tcl-format
+msgid "Found a public key in: %s"
+msgstr "Открит е публичен ключ в „%s“"
 
-#: lib/blame.tcl:1275
-msgid "In File:"
-msgstr "Ð\92Ñ\8aв Ñ\84айл:"
+#: lib/sshkey.tcl:40
+msgid "Generate Key"
+msgstr "Ð\93енеÑ\80иÑ\80ане Ð½Ð° ÐºÐ»Ñ\8eÑ\87"
 
-#: lib/blame.tcl:1280
-msgid "Copied Or Moved Here By:"
-msgstr "Ð\9aопиÑ\80ано Ð¸Ð»Ð¸ Ð¿Ñ\80емеÑ\81Ñ\82ено Ñ\82Ñ\83к Ð¾Ñ\82:"
+#: lib/sshkey.tcl:58
+msgid "Copy To Clipboard"
+msgstr "Ð\9aопиÑ\80ане ÐºÑ\8aм Ñ\81иÑ\81Ñ\82емниÑ\8f Ð±Ñ\83Ñ\84еÑ\80"
 
-#: lib/index.tcl:6
-msgid "Unable to unlock the index."
-msgstr "Ð\98ндекÑ\81Ñ\8aÑ\82 Ð½Ðµ Ð¼Ð¾Ð¶Ðµ Ð´Ð° Ð±Ñ\8aде Ð¾Ñ\82клÑ\8eÑ\87ен."
+#: lib/sshkey.tcl:72
+msgid "Your OpenSSH Public Key"
+msgstr "Ð\9fÑ\83блиÑ\87ниÑ\8fÑ\82 Ð²Ð¸ ÐºÐ»Ñ\8eÑ\87 Ð·Ð° OpenSSH"
 
-#: lib/index.tcl:17
-msgid "Index Error"
-msgstr "Грешка в индекса"
+#: lib/sshkey.tcl:80
+msgid "Generating..."
+msgstr "Генериране…"
 
-#: lib/index.tcl:19
+#: lib/sshkey.tcl:86
+#, tcl-format
 msgid ""
-"Updating the Git index failed.  A rescan will be automatically started to "
-"resynchronize git-gui."
+"Could not start ssh-keygen:\n"
+"\n"
+"%s"
 msgstr ""
-"Неуспешно обновяване на индекса на Git. Автоматично ще започне нова проверка "
-"за синхронизирането на git-gui."
+"Програмата „ssh-keygen“ не може да бъде стартирана:\n"
+"\n"
+"%s"
 
-#: lib/index.tcl:30
-msgid "Continue"
-msgstr "Ð\9fÑ\80одÑ\8aлжаване"
+#: lib/sshkey.tcl:113
+msgid "Generation failed."
+msgstr "Ð\9dеÑ\83Ñ\81пеÑ\88но Ð³ÐµÐ½ÐµÑ\80иÑ\80ане."
 
-#: lib/index.tcl:33
-msgid "Unlock Index"
-msgstr "Ð\9eÑ\82клÑ\8eÑ\87ване Ð½Ð° Ð¸Ð½Ð´ÐµÐºÑ\81а"
+#: lib/sshkey.tcl:120
+msgid "Generation succeeded, but no keys found."
+msgstr "Ð\93енеÑ\80иÑ\80анеÑ\82о Ð·Ð°Ð²Ñ\8aÑ\80Ñ\88и Ñ\83Ñ\81пеÑ\88но, Ð° Ð½Ðµ Ñ\81а Ð½Ð°Ð¼ÐµÑ\80ени ÐºÐ»Ñ\8eÑ\87ове."
 
-#: lib/index.tcl:298
+#: lib/sshkey.tcl:123
 #, tcl-format
-msgid "Unstaging %s from commit"
-msgstr "Ð\98зваждане Ð½Ð° â\80\9e%sâ\80\9c Ð¾Ñ\82 Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½ÐµÑ\82о"
+msgid "Your key is in: %s"
+msgstr "Ð\9aлÑ\8eÑ\87Ñ\8aÑ\82 Ð²Ð¸ Ðµ Ð² â\80\9e%sâ\80\9c"
 
-#: lib/index.tcl:337
-msgid "Ready to commit."
-msgstr "Готовност за подаване."
+#: lib/status_bar.tcl:87
+#, tcl-format
+msgid "%s ... %*i of %*i %s (%3i%%)"
+msgstr "%s… %*i от общо %*i %s (%3i%%)"
 
-#: lib/index.tcl:350
+#: lib/tools.tcl:76
 #, tcl-format
-msgid "Adding %s"
-msgstr "Ð\94обавÑ\8fне Ð½Ð° â\80\9e%sâ\80\9c"
+msgid "Running %s requires a selected file."
+msgstr "Ð\97а Ð¸Ð·Ð¿Ñ\8aлнениеÑ\82о Ð½Ð° â\80\9e%sâ\80\9c Ñ\82Ñ\80Ñ\8fбва Ð´Ð° Ð¸Ð·Ð±ÐµÑ\80еÑ\82е Ñ\84айл."
 
-#: lib/index.tcl:380
+#: lib/tools.tcl:92
 #, tcl-format
-msgid "Stage %d untracked files?"
-msgstr "Ð\94а Ñ\81е Ð²ÐºÐ°Ñ\80аÑ\82 Ð»Ð¸ %d Ð½ÐµÑ\81ледени Ñ\84айла Ð² Ð¸Ð½Ð´ÐµÐºÑ\81а?"
+msgid "Are you sure you want to run %1$s on file \"%2$s\"?"
+msgstr "СигÑ\83Ñ\80ни Ð»Ð¸ Ñ\81Ñ\82е, Ñ\87е Ð¸Ñ\81каÑ\82е Ð´Ð° Ð¸Ð·Ð¿Ñ\8aлниÑ\82е â\80\9e%1$sâ\80\9c Ð²Ñ\8aÑ\80Ñ\85Ñ\83 Ñ\84айла â\80\9e%2$sâ\80\9c?"
 
-#: lib/index.tcl:428
+#: lib/tools.tcl:96
 #, tcl-format
-msgid "Revert changes in file %s?"
-msgstr "Ð\94а Ñ\81е Ð¼Ð°Ñ\85наÑ\82 Ð»Ð¸ Ð¿Ñ\80омениÑ\82е Ð²Ñ\8aв Ñ\84айла „%s“?"
+msgid "Are you sure you want to run %s?"
+msgstr "СигÑ\83Ñ\80ни Ð»Ð¸ Ñ\81Ñ\82е, Ñ\87е Ð¸Ñ\81каÑ\82е Ð´Ð° Ð¸Ð·Ð¿Ñ\8aлниÑ\82е „%s“?"
 
-#: lib/index.tcl:430
+#: lib/tools.tcl:118
 #, tcl-format
-msgid "Revert changes in these %i files?"
-msgstr "Ð\94а Ñ\81е Ð¼Ð°Ñ\85наÑ\82 Ð»Ð¸ Ð¿Ñ\80омениÑ\82е Ð² Ñ\82ези %i Ñ\84айла?"
+msgid "Tool: %s"
+msgstr "Ð\9aоманда: %s"
 
-#: lib/index.tcl:438
-msgid "Any unstaged changes will be permanently lost by the revert."
+#: 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/index.tcl:441
-msgid "Do Nothing"
-msgstr "Нищо да не се прави"
+#: 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/index.tcl:459
-msgid "Reverting selected files"
-msgstr "Ð\9cаÑ\85ане Ð½Ð° Ð¿Ñ\80омениÑ\82е Ð² Ð¸Ð·Ð±Ñ\80аниÑ\82е Ñ\84айлове"
+#: lib/tools_dlg.tcl:118
+msgid "Please supply a name for the tool."
+msgstr "Ð\97адайÑ\82е Ð¸Ð¼Ðµ Ð·Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´Ð°Ñ\82а."
 
-#: lib/index.tcl:463
+#: lib/tools_dlg.tcl:126
 #, tcl-format
-msgid "Reverting %s"
-msgstr "Ð\9cаÑ\85ане Ð½Ð° Ð¿Ñ\80омениÑ\82е Ð² â\80\9e%sâ\80\9c"
+msgid "Tool '%s' already exists."
+msgstr "Ð\9aомандаÑ\82а â\80\9e%sâ\80\9c Ð²ÐµÑ\87е Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ва."
 
-#: lib/date.tcl:25
+#: lib/tools_dlg.tcl:148
 #, tcl-format
-msgid "Invalid date from Git: %s"
-msgstr "Неправилни данни от Git: %s"
-
-#: lib/database.tcl:42
-msgid "Number of loose objects"
-msgstr "Брой непакетирани обекти"
+msgid ""
+"Could not add tool:\n"
+"%s"
+msgstr ""
+"Командата не може да бъде добавена:\n"
+"%s"
 
-#: lib/database.tcl:43
-msgid "Disk space used by loose objects"
-msgstr "Дисково пространство заето от непакетирани обекти"
+#: lib/tools_dlg.tcl:187
+#, tcl-format
+msgid "%s (%s): Remove Tool"
+msgstr "%s (%s): Премахване на команда"
 
-#: lib/database.tcl:44
-msgid "Number of packed objects"
-msgstr "Ð\91Ñ\80ой Ð¿Ð°ÐºÐµÑ\82иÑ\80ани Ð¾Ð±ÐµÐºÑ\82и"
+#: lib/tools_dlg.tcl:193
+msgid "Remove Tool Commands"
+msgstr "Ð\9fÑ\80емаÑ\85ване Ð½Ð° ÐºÐ¾Ð¼Ð°Ð½Ð´и"
 
-#: lib/database.tcl:45
-msgid "Number of packs"
-msgstr "Ð\91Ñ\80ой Ð¿Ð°ÐºÐµÑ\82и"
+#: lib/tools_dlg.tcl:198
+msgid "Remove"
+msgstr "Ð\9fÑ\80емаÑ\85ване"
 
-#: lib/database.tcl:46
-msgid "Disk space used by packed objects"
-msgstr "Дисково пространство заето от пакетирани обекти"
+#: lib/tools_dlg.tcl:231
+msgid "(Blue denotes repository-local tools)"
+msgstr "(командите към локалното хранилище са обозначени в синьо)"
 
-#: lib/database.tcl:47
-msgid "Packed objects waiting for pruning"
-msgstr "Пакетирани обекти за окастряне"
+#: lib/tools_dlg.tcl:283
+#, tcl-format
+msgid "%s (%s):"
+msgstr "%s (%s):"
 
-#: lib/database.tcl:48
-msgid "Garbage files"
-msgstr "Файлове за боклука"
+#: lib/tools_dlg.tcl:292
+#, tcl-format
+msgid "Run Command: %s"
+msgstr "Изпълнение на командата „%s“"
 
-#: lib/database.tcl:72
-msgid "Compressing the object database"
-msgstr "Ð\9aомпÑ\80еÑ\81иÑ\80ане Ð½Ð° Ð±Ð°Ð·Ð°Ñ\82а Ñ\81 Ð´Ð°Ð½Ð½Ð¸ Ð·Ð° Ð¾Ð±ÐµÐºÑ\82иÑ\82е"
+#: lib/tools_dlg.tcl:306
+msgid "Arguments"
+msgstr "Ð\90Ñ\80гÑ\83менÑ\82и"
 
-#: lib/database.tcl:83
-msgid "Verifying the object database with fsck-objects"
-msgstr "Ð\9fÑ\80овеÑ\80ка Ð½Ð° Ð±Ð°Ð·Ð°Ñ\82а Ñ\81 Ð´Ð°Ð½Ð½Ð¸ Ð·Ð° Ð¾Ð±ÐµÐºÑ\82иÑ\82е Ñ\81 Ð¿Ñ\80огÑ\80амаÑ\82а â\80\9efsck-objectsâ\80\9c"
+#: lib/tools_dlg.tcl:341
+msgid "OK"
+msgstr "Ð\94обÑ\80е"
 
-#: lib/database.tcl:107
+#: lib/transport.tcl:7
 #, 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"
-"Да се започне ли компресирането?"
+msgid "Fetching new changes from %s"
+msgstr "Доставяне на промените от „%s“"
 
-#: lib/error.tcl:20 lib/error.tcl:116
-msgid "error"
-msgstr "грешка"
+#: lib/transport.tcl:18
+#, tcl-format
+msgid "remote prune %s"
+msgstr "окастряне на следящите клони към „%s“"
 
-#: lib/error.tcl:36
-msgid "warning"
-msgstr "предупреждение"
+#: lib/transport.tcl:19
+#, tcl-format
+msgid "Pruning tracking branches deleted from %s"
+msgstr "Окастряне на следящите клони на изтритите клони от „%s“"
 
-#: lib/error.tcl:96
-msgid "You must correct the above errors before committing."
-msgstr "Ð\9fÑ\80еди Ð´Ð° Ð¼Ð¾Ð¶ÐµÑ\82е Ð´Ð° Ð¿Ð¾Ð´Ð°Ð´ÐµÑ\82е, ÐºÐ¾Ñ\80игиÑ\80айÑ\82е Ð³Ð¾Ñ\80ниÑ\82е Ð³Ñ\80еÑ\88ки."
+#: lib/transport.tcl:25
+msgid "fetch all remotes"
+msgstr "доÑ\81Ñ\82авÑ\8fне Ð¾Ñ\82 Ð²Ñ\81иÑ\87ки Ð¾Ñ\82далеÑ\87ени"
 
-#: lib/merge.tcl:13
-msgid ""
-"Cannot merge while amending.\n"
-"\n"
-"You must finish amending this commit before starting any type of merge.\n"
-msgstr ""
-"По време на поправяне не може да сливане.\n"
-"\n"
-"Трябва да завършите поправянето на текущото подаване, преди да започнете "
-"сливане.\n"
+#: lib/transport.tcl:26
+msgid "Fetching new changes from all remotes"
+msgstr "Доставяне на промените от всички отдалечени хранилища"
 
-#: lib/merge.tcl:27
-msgid ""
-"Last scanned state does not match repository state.\n"
-"\n"
-"Another Git program has modified this repository since the last scan.  A "
-"rescan must be performed before a merge can be performed.\n"
-"\n"
-"The rescan will be automatically started now.\n"
-msgstr ""
-"Последно установеното състояние не отговаря на това в хранилището.\n"
-"\n"
-"Някой друг процес за Git е променил хранилището междувременно. Състоянието "
-"трябва да бъде проверено, преди да се извърши сливане.\n"
-"\n"
-"Автоматично ще започне нова проверка.\n"
-"\n"
+#: lib/transport.tcl:40
+msgid "remote prune all remotes"
+msgstr "окастряне на следящите изтрити"
 
-#: lib/merge.tcl:45
-#, tcl-format
-msgid ""
-"You are in the middle of a conflicted merge.\n"
-"\n"
-"File %s has merge conflicts.\n"
-"\n"
-"You must resolve them, stage the file, and commit to complete the current "
-"merge.  Only then can you begin another merge.\n"
+#: lib/transport.tcl:41
+msgid "Pruning tracking branches deleted from all remotes"
 msgstr ""
-"В момента тече сливане, но има конфликти.\n"
-"\n"
-"Погледнете файла „%s“.\n"
-"\n"
-"Трябва да коригирате конфликтите в него, да го добавите към индекса и да "
-"завършите текущото сливане чрез подаване. Чак тогава може да започнете ново "
-"сливане.\n"
+"Окастряне на следящите клони на изтритите клони от всички отдалечени "
+"хранилища"
 
-#: lib/merge.tcl:55
+#: lib/transport.tcl:55
 #, tcl-format
-msgid ""
-"You are in the middle of a change.\n"
-"\n"
-"File %s is modified.\n"
-"\n"
-"You should complete the current commit before starting a merge.  Doing so "
-"will help you abort a failed merge, should the need arise.\n"
-msgstr ""
-"В момента тече подаване.\n"
-"\n"
-"Файлът „%s“ е променен.\n"
-"\n"
-"Трябва да завършите текущото подаване, преди да започнете сливане. Така ще "
-"можете лесно да преустановите сливането, ако възникне нужда.\n"
+msgid "Pushing changes to %s"
+msgstr "Изтласкване на промените към „%s“"
 
-#: lib/merge.tcl:108
+#: lib/transport.tcl:93
 #, tcl-format
-msgid "%s of %s"
-msgstr "%s от общо %s"
+msgid "Mirroring to %s"
+msgstr "Изтласкване на всичко към „%s“"
 
-#: lib/merge.tcl:122
+#: lib/transport.tcl:111
 #, tcl-format
-msgid "Merging %s and %s..."
-msgstr "Сливане на „%s“ и „%s“…"
-
-#: lib/merge.tcl:133
-msgid "Merge completed successfully."
-msgstr "Сливането завърши успешно."
-
-#: lib/merge.tcl:135
-msgid "Merge failed.  Conflict resolution is required."
-msgstr "Неуспешно сливане — има конфликти за коригиране."
+msgid "Pushing %s %s to %s"
+msgstr "Изтласкване на %s „%s“ към „%s“"
 
-#: lib/merge.tcl:160
-#, tcl-format
-msgid "Merge Into %s"
-msgstr "Сливане в „%s“"
+#: lib/transport.tcl:132
+msgid "Push Branches"
+msgstr "Клони за изтласкване"
 
-#: lib/merge.tcl:179
-msgid "Revision To Merge"
-msgstr "Ð\92еÑ\80Ñ\81иÑ\8f Ð·Ð° Ñ\81ливане"
+#: lib/transport.tcl:147
+msgid "Source Branches"
+msgstr "Ð\9aлони-изÑ\82оÑ\87ниÑ\86и"
 
-#: lib/merge.tcl:214
-msgid ""
-"Cannot abort while amending.\n"
-"\n"
-"You must finish amending this commit.\n"
-msgstr ""
-"Поправянето не може да бъде преустановено.\n"
-"\n"
-"Трябва да завършите поправката на това подаване.\n"
+#: lib/transport.tcl:162
+msgid "Destination Repository"
+msgstr "Целево хранилище"
 
-#: lib/merge.tcl:224
-msgid ""
-"Abort merge?\n"
-"\n"
-"Aborting the current merge will cause *ALL* uncommitted changes to be lost.\n"
-"\n"
-"Continue with aborting the current merge?"
-msgstr ""
-"Да се преустанови ли сливането?\n"
-"\n"
-"В такъв случай ●ВСИЧКИ● неподадени промени ще бъдат безвъзвратно загубени.\n"
-"\n"
-"Наистина ли да се преустанови сливането?"
+#: lib/transport.tcl:205
+msgid "Transfer Options"
+msgstr "Настройки при пренасянето"
 
-#: lib/merge.tcl:230
-msgid ""
-"Reset changes?\n"
-"\n"
-"Resetting the changes will cause *ALL* uncommitted changes to be lost.\n"
-"\n"
-"Continue with resetting the current changes?"
+#: lib/transport.tcl:207
+msgid "Force overwrite existing branch (may discard changes)"
 msgstr ""
-"Да се занулят ли промените?\n"
-"\n"
-"В такъв случай ●ВСИЧКИ● неподадени промени ще бъдат безвъзвратно загубени.\n"
-"\n"
-"Наистина ли да се занулят промените?"
-
-#: lib/merge.tcl:241
-msgid "Aborting"
-msgstr "Преустановяване"
+"Изрично презаписване на съществуващ клон (някои промени може да бъдат "
+"загубени)"
 
-#: lib/merge.tcl:241
-msgid "files reset"
-msgstr "файла със занулени промени"
+#: lib/transport.tcl:211
+msgid "Use thin pack (for slow network connections)"
+msgstr "Максимална компресия (за бавни мрежови връзки)"
 
-#: lib/merge.tcl:269
-msgid "Abort failed."
-msgstr "Ð\9dеÑ\83Ñ\81пеÑ\88но Ð¿Ñ\80еÑ\83Ñ\81Ñ\82ановÑ\8fване."
+#: lib/transport.tcl:215
+msgid "Include tags"
+msgstr "Ð\92клÑ\8eÑ\87ване Ð½Ð° ÐµÑ\82икеÑ\82иÑ\82е"
 
-#: lib/merge.tcl:271
-msgid "Abort completed.  Ready."
-msgstr "Успешно преустановяване. Готовност за следващо действие."
+#: lib/transport.tcl:229
+#, tcl-format
+msgid "%s (%s): Push"
+msgstr "%s (%s): Изтласкване"
index ca4343b40cd9a166fc9c3526d68b6182057e71a3..9f5305c43eeb75c0e1b672efebe6a5aedca1a6ca 100644 (file)
@@ -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 "Ð\98зменено, Ð½Ðµ Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овлено"
+msgstr "Ð\98зменено, Ð½Ðµ Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е"
 
 #: git-gui.sh:1916 git-gui.sh:1924
 msgid "Staged for commit"
-msgstr "Ð\9fодгоÑ\82овлено Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f"
+msgstr "Ð\92 Ð¸Ð½Ð´ÐµÐºÑ\81е Ð´Ð»Ñ\8f ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а"
 
 #: git-gui.sh:1917 git-gui.sh:1925
 msgid "Portions staged for commit"
-msgstr "ЧаÑ\81Ñ\82и, Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овленнÑ\8bе Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f"
+msgstr "ЧаÑ\81Ñ\82и, Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е Ð´Ð»Ñ\8f ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а"
 
 #: git-gui.sh:1918 git-gui.sh:1926
 msgid "Staged for commit, missing"
-msgstr "Ð\9fодгоÑ\82овлено Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f, отсутствует"
+msgstr "Ð\92 Ð¸Ð½Ð´ÐµÐºÑ\81е Ð´Ð»Ñ\8f ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а, отсутствует"
 
 #: git-gui.sh:1920
 msgid "File type changed, not staged"
-msgstr "Тип Ñ\84айла Ð¸Ð·Ð¼ÐµÐ½Ñ\91н, Ð½Ðµ Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овлено"
+msgstr "Тип Ñ\84айла Ð¸Ð·Ð¼ÐµÐ½Ñ\91н, Ð½Ðµ Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е"
 
 #: git-gui.sh:1921
 msgid "File type changed, staged"
-msgstr "Тип Ñ\84айла Ð¸Ð·Ð¼ÐµÐ½Ñ\91н, Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овлено"
+msgstr "Тип Ñ\84айла Ð¸Ð·Ð¼ÐµÐ½Ñ\91н, Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е"
 
 #: git-gui.sh:1923
 msgid "Untracked, not staged"
-msgstr "Ð\9dе Ð¾Ñ\82Ñ\81леживаеÑ\82Ñ\81Ñ\8f, Ð½Ðµ Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овлено"
+msgstr "Ð\9dе Ð¾Ñ\82Ñ\81леживаеÑ\82Ñ\81Ñ\8f, Ð½Ðµ Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е"
 
 #: git-gui.sh:1928
 msgid "Missing"
@@ -140,11 +135,11 @@ msgstr "Отсутствует"
 
 #: git-gui.sh:1929
 msgid "Staged for removal"
-msgstr "Ð\9fодгоÑ\82овлено для удаления"
+msgstr "Ð\92 Ð¸Ð½Ð´ÐµÐºÑ\81е для удаления"
 
 #: git-gui.sh:1930
 msgid "Staged for removal, still present"
-msgstr "Ð\9fодгоÑ\82овлено для удаления, еще не удалено"
+msgstr "Ð\92 Ð¸Ð½Ð´ÐµÐºÑ\81е для удаления, еще не удалено"
 
 #: 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 "Ð\92еÑ\82вÑ\8c"
+msgstr "Ð\92еÑ\82ка"
 
 #: git-gui.sh:2461 lib/choose_rev.tcl:548
 msgid "Commit@@noun"
-msgstr "СоÑ\81Ñ\82оÑ\8fние"
+msgstr "Ð\9aоммиÑ\82"
 
 #: 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 "Ð\9fÑ\80оÑ\81моÑ\82Ñ\80еÑ\82Ñ\8c Ñ\84айлÑ\8b Ñ\82екÑ\83Ñ\89ей Ð²ÐµÑ\82ви"
+msgstr "Ð\9fÑ\80оÑ\81моÑ\82Ñ\80еÑ\82Ñ\8c Ñ\84айлÑ\8b Ñ\82екÑ\83Ñ\89ей Ð²ÐµÑ\82ки"
 
 #: git-gui.sh:2487
 msgid "Browse Branch Files..."
-msgstr "Ð\9fоказаÑ\82Ñ\8c Ñ\84айлÑ\8b Ð²ÐµÑ\82ви..."
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ñ\84айлÑ\8b Ð²ÐµÑ\82киâ\80¦"
 
 #: git-gui.sh:2492
 msgid "Visualize Current Branch's History"
-msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ñ\82екÑ\83Ñ\89ей Ð²ÐµÑ\82ви"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ñ\82екÑ\83Ñ\89ей Ð²ÐµÑ\82ки"
 
 #: git-gui.sh:2496
 msgid "Visualize All Branch History"
-msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ð²Ñ\81еÑ\85 Ð²ÐµÑ\82вей"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ð²Ñ\81еÑ\85 Ð²ÐµÑ\82ок"
 
 #: git-gui.sh:2503
 #, tcl-format
 msgid "Browse %s's Files"
-msgstr "Ð\9fоказаÑ\82Ñ\8c Ñ\84айлÑ\8b Ð²ÐµÑ\82ви %s"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ñ\84айлÑ\8b Ð²ÐµÑ\82ки %s"
 
 #: git-gui.sh:2505
 #, tcl-format
 msgid "Visualize %s's History"
-msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ð²ÐµÑ\82ви %s"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ð²ÐµÑ\82ки %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 "СоÑ\85Ñ\80анить"
+msgstr "Ð\97акоммиÑ\82ить"
 
 #: 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 "Ð\98Ñ\81пÑ\80авиÑ\82Ñ\8c Ð¿Ð¾Ñ\81леднее Ñ\81оÑ\81Ñ\82оÑ\8fние"
+msgstr "Ð\98Ñ\81пÑ\80авиÑ\82Ñ\8c Ð¿Ð¾Ñ\81ледний ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82"
 
 #: 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 "Ð\9fодгоÑ\82овиÑ\82Ñ\8c Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f"
+msgstr "Ð\94обавиÑ\82Ñ\8c Ð² Ð¸Ð½Ð´ÐµÐºÑ\81"
 
 #: git-gui.sh:2649
 msgid "Stage Changed Files To Commit"
-msgstr "Ð\9fодгоÑ\82овиÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð½Ñ\8bе Ñ\84айлÑ\8b Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f"
+msgstr "Ð\94обавиÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½Ñ\91ннÑ\8bе Ñ\84айлÑ\8b Ð² Ð¸Ð½Ð´ÐµÐºÑ\81"
 
 #: git-gui.sh:2655
 msgid "Unstage From Commit"
-msgstr "УбÑ\80аÑ\82Ñ\8c Ð¸Ð· Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овленного"
+msgstr "УбÑ\80аÑ\82Ñ\8c Ð¸Ð· Ð¸Ð·Ð´ÐµÐºÑ\81а"
 
 #: 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 "УдалиÑ\82Ñ\8c Ð²ÐµÑ\82вÑ\8c..."
+msgstr "УдалиÑ\82Ñ\8c Ð²ÐµÑ\82кÑ\83â\80¦"
 
 #: 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 "ТекÑ\83Ñ\89аÑ\8f Ð²ÐµÑ\82вÑ\8c:"
+msgstr "ТекÑ\83Ñ\89аÑ\8f Ð²ÐµÑ\82ка:"
 
 #: git-gui.sh:2947
 msgid "Staged Changes (Will Commit)"
-msgstr "Ð\9fодгоÑ\82овлено (бÑ\83деÑ\82 Ñ\81оÑ\85Ñ\80анено)"
+msgstr "Ð\98зменениÑ\8f Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е (бÑ\83дÑ\83Ñ\82 Ð·Ð°ÐºÐ¾Ð¼Ð¼Ð¸Ñ\87енÑ\8b)"
 
 #: git-gui.sh:2967
 msgid "Unstaged Changes"
@@ -405,7 +400,7 @@ msgstr "Изменено (не будет сохранено)"
 
 #: git-gui.sh:3017
 msgid "Stage Changed"
-msgstr "Ð\9fодгоÑ\82овиÑ\82Ñ\8c Ð²Ñ\81е"
+msgstr "Ð\98ндекÑ\81иÑ\80оваÑ\82Ñ\8c Ð²Ñ\81Ñ\91"
 
 #: 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 "Ð\9aомменÑ\82аÑ\80ий Ðº Ð¿ÐµÑ\80вомÑ\83 Ñ\81оÑ\81Ñ\82оÑ\8fниÑ\8e:"
+msgstr "СообÑ\89ение Ð¿ÐµÑ\80вого ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а:"
 
 #: git-gui.sh:3072
 msgid "Amended Commit Message:"
-msgstr "Ð\9aомменÑ\82аÑ\80ий Ðº Ð¸Ñ\81пÑ\80авленномÑ\83 Ñ\81оÑ\81Ñ\82оÑ\8fниÑ\8e:"
+msgstr "СообÑ\89ение Ð¸Ñ\81пÑ\80авленного ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а:"
 
 #: git-gui.sh:3073
 msgid "Amended Initial Commit Message:"
-msgstr "Ð\9aомменÑ\82аÑ\80ий Ðº Ð¸Ñ\81пÑ\80авленномÑ\83 Ð¿ÐµÑ\80вонаÑ\87алÑ\8cномÑ\83 Ñ\81оÑ\81Ñ\82оÑ\8fниÑ\8e:"
+msgstr "СообÑ\89ение Ð¸Ñ\81пÑ\80авленного Ð¿ÐµÑ\80вого ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а:"
 
 #: git-gui.sh:3074
 msgid "Amended Merge Commit Message:"
-msgstr "Ð\9aомменÑ\82аÑ\80ий Ðº Ð¸Ñ\81пÑ\80авленномÑ\83 Ñ\81лиÑ\8fниÑ\8e:"
+msgstr "СообÑ\89ение Ð¸Ñ\81пÑ\80авленного Ñ\81лиÑ\8fниÑ\8f:"
 
 #: git-gui.sh:3075
 msgid "Merge Commit Message:"
-msgstr "Ð\9aомменÑ\82аÑ\80ий Ðº Ñ\81лиÑ\8fниÑ\8e:"
+msgstr "СообÑ\89ение Ñ\81лиÑ\8fниÑ\8f:"
 
 #: git-gui.sh:3076
 msgid "Commit Message:"
-msgstr "Ð\9aомменÑ\82аÑ\80ий Ðº Ñ\81оÑ\81Ñ\82оÑ\8fниÑ\8e:"
+msgstr "СообÑ\89ение ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а:"
 
 #: 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 "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ñ\82екÑ\83Ñ\89ей Ð²ÐµÑ\82ви подмодуля"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ñ\82екÑ\83Ñ\89ей Ð²ÐµÑ\82ки подмодуля"
 
 #: git-gui.sh:3362
 msgid "Visualize All Branch History In The Submodule"
-msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ð²Ñ\81еÑ\85 Ð²ÐµÑ\82вей подмодуля"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ð²Ñ\81еÑ\85 Ð²ÐµÑ\82ок подмодуля"
 
 #: 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 "Ð\9dе Ñ\81оÑ\85Ñ\80анÑ\8fÑ\82Ñ\8c Ñ\87аÑ\81Ñ\82Ñ\8c"
+msgstr "УбÑ\80аÑ\82Ñ\8c Ð±Ð»Ð¾Ðº Ð¸Ð· Ð¸Ð½Ð´ÐµÐºÑ\81а"
 
 #: git-gui.sh:3391
 msgid "Unstage Lines From Commit"
-msgstr "УбÑ\80аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80оки Ð¸Ð· Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овленного"
+msgstr "УбÑ\80аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80оки Ð¸Ð· Ð¸Ð½Ð´ÐµÐºÑ\81а"
 
 #: git-gui.sh:3393
 msgid "Unstage Line From Commit"
-msgstr "УбÑ\80аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80окÑ\83 Ð¸Ð· Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овленного"
+msgstr "УбÑ\80аÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80окÑ\83 Ð¸Ð· Ð¸Ð½Ð´ÐµÐºÑ\81а"
 
 #: git-gui.sh:3396
 msgid "Stage Hunk For Commit"
-msgstr "Ð\9fодгоÑ\82овиÑ\82Ñ\8c Ñ\87аÑ\81Ñ\82Ñ\8c Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f"
+msgstr "Ð\94обавиÑ\82Ñ\8c Ð±Ð»Ð¾Ðº Ð² Ð¸Ð½Ð´ÐµÐºÑ\81"
 
 #: git-gui.sh:3398
 msgid "Stage Lines For Commit"
-msgstr "Ð\9fодгоÑ\82овиÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80оки Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f"
+msgstr "Ð\94обавиÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80оки Ð² Ð¸Ð½Ð´ÐµÐºÑ\81"
 
 #: git-gui.sh:3400
 msgid "Stage Line For Commit"
-msgstr "Ð\9fодгоÑ\82овиÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80окÑ\83 Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f"
+msgstr "Ð\94обавиÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80окÑ\83 Ð² Ð¸Ð½Ð´ÐµÐºÑ\81"
 
 #: 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 "СоÑ\85Ñ\80аненное Ñ\81оÑ\81Ñ\82оÑ\8fние:"
+msgstr "Ð\9aоммиÑ\82:"
 
 #: lib/blame.tcl:271
 msgid "Copy Commit"
-msgstr "Скопировать SHA-1"
+msgstr "Ð\9aопировать 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 "РаÑ\81Ñ\81моÑ\82Ñ\80еÑ\82Ñ\8c Ñ\81оÑ\81Ñ\82оÑ\8fние Ð¿Ñ\80едка"
+msgstr "Ð\90вÑ\82оÑ\80Ñ\8b Ñ\80одиÑ\82елÑ\8cÑ\81кого ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а"
 
 #: 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 "СоÑ\85Ñ\80анил:"
+msgstr "Ð\9aоммиÑ\82еÑ\80:"
 
 #: 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 "Ð\9fеÑ\80ейÑ\82и Ð½Ð° Ð²ÐµÑ\82вÑ\8c"
+msgstr "Ð\9fеÑ\80ейÑ\82и Ð½Ð° Ð²ÐµÑ\82кÑ\83"
 
 #: 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 "Ð\9fолÑ\83Ñ\87иÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f Ð¸Ð· Ð²Ð½ÐµÑ\88ней Ð²ÐµÑ\82ви"
+msgstr "Ð\98звлеÑ\87Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f Ð¸Ð· Ð²Ð½ÐµÑ\88ней Ð²ÐµÑ\82ки"
 
 #: lib/branch_checkout.tcl:44
 msgid "Detach From Local Branch"
-msgstr "Ð\9eÑ\82Ñ\81оединиÑ\82Ñ\8c Ð¾Ñ\82 Ð»Ð¾ÐºÐ°Ð»Ñ\8cной Ð²ÐµÑ\82ви"
+msgstr "Ð\9eÑ\82Ñ\81оединиÑ\82Ñ\8c Ð¾Ñ\82 Ð»Ð¾ÐºÐ°Ð»Ñ\8cной Ð²ÐµÑ\82ки"
 
 #: lib/branch_create.tcl:22
 msgid "Create Branch"
-msgstr "Создание ветви"
+msgstr "Создать ветку"
 
 #: lib/branch_create.tcl:27
 msgid "Create New Branch"
-msgstr "СоздаÑ\82Ñ\8c Ð½Ð¾Ð²Ñ\83Ñ\8e Ð²ÐµÑ\82вÑ\8c"
+msgstr "СоздаÑ\82Ñ\8c Ð½Ð¾Ð²Ñ\83Ñ\8e Ð²ÐµÑ\82кÑ\83"
 
 #: 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 "Ð\9dазвание Ð²ÐµÑ\82ви"
+msgstr "Ð\98мÑ\8f Ð²ÐµÑ\82ки"
 
 #: 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 "Ð\92зÑ\8fÑ\82Ñ\8c Ð¸Ð· Ð¸Ð¼ÐµÐ½ Ð²ÐµÑ\82вей Ñ\81лежениÑ\8f"
+msgstr "СооÑ\82веÑ\82Ñ\81Ñ\82воваÑ\82Ñ\8c Ð¸Ð¼ÐµÐ½Ð¸ Ð¾Ñ\82Ñ\81леживаемой Ð²ÐµÑ\82ки"
 
 #: lib/branch_create.tcl:66
 msgid "Starting Revision"
@@ -743,7 +723,7 @@ msgstr "Начальная версия"
 
 #: lib/branch_create.tcl:72
 msgid "Update Existing Branch:"
-msgstr "Ð\9eбновиÑ\82Ñ\8c Ð¸Ð¼ÐµÑ\8eÑ\89Ñ\83Ñ\8eÑ\81Ñ\8f Ð²ÐµÑ\82вÑ\8c:"
+msgstr "Ð\9eбновиÑ\82Ñ\8c Ð¸Ð¼ÐµÑ\8eÑ\89Ñ\83Ñ\8eÑ\81Ñ\8f Ð²ÐµÑ\82кÑ\83:"
 
 #: lib/branch_create.tcl:75
 msgid "No"
@@ -763,33 +743,33 @@ msgstr "После создания сделать текущей"
 
 #: lib/branch_create.tcl:131
 msgid "Please select a tracking branch."
-msgstr "УкажиÑ\82е Ð²ÐµÑ\82вÑ\8c Ñ\81лежениÑ\8f."
+msgstr "УкажиÑ\82е Ð¾Ñ\82леживаемÑ\83Ñ\8e Ð²ÐµÑ\82кÑ\83."
 
 #: lib/branch_create.tcl:140
 #, tcl-format
 msgid "Tracking branch %s is not a branch in the remote repository."
-msgstr "Ð\92еÑ\82вÑ\8c Ñ\81лежениÑ\8f %s Ð½Ðµ Ñ\8fвлÑ\8fеÑ\82Ñ\81Ñ\8f Ð²ÐµÑ\82вÑ\8cÑ\8e Ð²Ð¾ внешнем репозитории."
+msgstr "Ð\9eÑ\82Ñ\81леживаемаÑ\8f Ð²ÐµÑ\82ка %s Ð½Ðµ Ñ\8fвлÑ\8fеÑ\82Ñ\81Ñ\8f Ð²ÐµÑ\82кой Ð½Ð° внешнем репозитории."
 
 #: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
 msgid "Please supply a branch name."
-msgstr "УкажиÑ\82е Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ðµ Ð²ÐµÑ\82ви."
+msgstr "УкажиÑ\82е Ð¸Ð¼Ñ\8f Ð²ÐµÑ\82ки."
 
 #: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
 #, tcl-format
 msgid "'%s' is not an acceptable branch name."
-msgstr "Ð\9dедопÑ\83Ñ\81Ñ\82имое Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ðµ Ð²ÐµÑ\82ви '%s'."
+msgstr "Ð\9dедопÑ\83Ñ\81Ñ\82имое Ð¸Ð¼Ñ\8f Ð²ÐµÑ\82ки Â«%s»."
 
 #: lib/branch_delete.tcl:15
 msgid "Delete Branch"
-msgstr "Удаление Ð²ÐµÑ\82ви"
+msgstr "Удаление Ð²ÐµÑ\82ки"
 
 #: lib/branch_delete.tcl:20
 msgid "Delete Local Branch"
-msgstr "УдалиÑ\82Ñ\8c Ð»Ð¾ÐºÐ°Ð»Ñ\8cнÑ\83Ñ\8e Ð²ÐµÑ\82вÑ\8c"
+msgstr "УдалиÑ\82Ñ\8c Ð»Ð¾ÐºÐ°Ð»Ñ\8cнÑ\83Ñ\8e Ð²ÐµÑ\82кÑ\83"
 
 #: lib/branch_delete.tcl:37
 msgid "Local Branches"
-msgstr "Ð\9bокалÑ\8cнÑ\8bе Ð²ÐµÑ\82ви"
+msgstr "Ð\9bокалÑ\8cнÑ\8bе Ð²ÐµÑ\82ки"
 
 #: 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 "Ð\92еÑ\82ви, которые не полностью сливаются с %s:"
+msgstr "Ð\92еÑ\82ки, которые не полностью сливаются с %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 "Ð\9fеÑ\80еименование Ð²ÐµÑ\82ви"
+msgstr "Ð\9fеÑ\80еименование Ð²ÐµÑ\82ки"
 
 #: lib/branch_rename.tcl:26
 msgid "Rename"
@@ -833,7 +808,7 @@ msgstr "Переименовать"
 
 #: lib/branch_rename.tcl:36
 msgid "Branch:"
-msgstr "Ð\92еÑ\82вÑ\8c:"
+msgstr "Ð\92еÑ\82ка:"
 
 #: 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 "УкажиÑ\82е Ð²ÐµÑ\82вÑ\8c для переименования."
+msgstr "УкажиÑ\82е Ð²ÐµÑ\82кÑ\83 для переименования."
 
 #: lib/branch_rename.tcl:96 lib/checkout_op.tcl:202
 #, tcl-format
 msgid "Branch '%s' already exists."
-msgstr "Ð\92еÑ\82вÑ\8c '%s' уже существует."
+msgstr "Ð\92еÑ\82ка Â«%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 "Ð\9fоказаÑ\82Ñ\8c Ñ\84айлÑ\8b Ð²ÐµÑ\82ви"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ñ\84айлÑ\8b Ð²ÐµÑ\82ки"
 
 #: 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 "Ð\9fолÑ\83чение %s из %s "
+msgstr "Ð\98звлечение %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 "Ð\92еÑ\82вÑ\8c '%s' Ð½Ðµ Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 "
+msgstr "Ð\92еÑ\82ка Â«%s» Ð½Ðµ Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82."
 
 #: 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 "Ð\92еÑ\82вÑ\8c '%s' Ð¾Ñ\81Ñ\82ается текущей."
+msgstr "Ð\92еÑ\82ка Â«%s» Ð¾Ñ\81Ñ\82аÑ\91тся текущей."
 
 #: 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 "СклониÑ\80оваÑ\82Ñ\8c..."
+msgstr "Ð\9aлониÑ\80оваÑ\82Ñ\8câ\80¦"
 
 #: 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 "Ð\9dе Ð±Ñ\8bло Ð¿Ð¾Ð»Ñ\83Ñ\87ено Ð²ÐµÑ\82ви Ð¿Ð¾ Ñ\83молÑ\87аниÑ\8e."
+msgstr "Ð\92еÑ\82ка Ð¿Ð¾ Ñ\83молÑ\87аниÑ\8e Ð½Ðµ Ð±Ñ\8bла Ð¿Ð¾Ð»Ñ\83Ñ\87ена."
 
 #: 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 "Ð\9bокалÑ\8cнаÑ\8f Ð²ÐµÑ\82вÑ\8c:"
+msgstr "Ð\9bокалÑ\8cнаÑ\8f Ð²ÐµÑ\82ка:"
 
 #: lib/choose_rev.tcl:79
 msgid "Tracking Branch"
-msgstr "Ð\92еÑ\82вÑ\8c Ñ\81лежениÑ\8f"
+msgstr "Ð\9eÑ\82Ñ\81леживаемаÑ\8f Ð²ÐµÑ\82ка"
 
 #: 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 "Ð\9dевеÑ\80ный GIT_COMMITTER_IDENT:"
+msgstr "Ð\9dедопÑ\83Ñ\81Ñ\82имый 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 "СоÑ\85Ñ\80анение Ð¿Ñ\80еÑ\80вано Ð¿Ñ\80огÑ\80аммой Ð¿Ð¾Ð´Ð´ÐµÑ\80жки Ñ\80епозиÑ\82оÑ\80иÑ\8f pre-commit"
+msgstr "Ð\9aоммиÑ\82 Ð¿Ñ\80еÑ\80ван Ð¿ÐµÑ\80еваÑ\82Ñ\87иком 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 "СоÑ\85Ñ\80анение Ð¿Ñ\80еÑ\80вано Ð¿Ñ\80огÑ\80аммой Ð¿Ð¾Ð´Ð´ÐµÑ\80жки Ñ\80епозиÑ\82оÑ\80иÑ\8f commit-msg"
+msgstr "Ð\9aоммиÑ\82 Ð¿Ñ\80еÑ\80ван Ð¿ÐµÑ\80еваÑ\82Ñ\87иком commit-msg"
 
 #: lib/commit.tcl:300
 msgid "Committing changes..."
-msgstr "СоÑ\85Ñ\80анение Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹..."
+msgstr "Ð\9aоммиÑ\82 Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹â\80¦"
 
 #: 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 "СоÑ\85Ñ\80аниÑ\82Ñ\8c Ñ\81оÑ\81Ñ\82оÑ\8fние Ð½Ðµ Ñ\83далоÑ\81Ñ\8c."
+msgstr "Ð\9dе Ñ\83далоÑ\81Ñ\8c Ð·Ð°ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82иÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f."
 
 #: lib/commit.tcl:334
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
-msgstr "СоÑ\81Ñ\82оÑ\8fние %s Ð²Ñ\8bглÑ\8fдиÑ\82 Ð¿Ð¾Ð²Ñ\80ежденнÑ\8bм"
+msgstr "Ð\9aоммиÑ\82 %s Ð¿Ð¾Ñ\85оже Ð¿Ð¾Ð²Ñ\80ежден"
 
 #: 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 "Ð\9eÑ\82Ñ\81Ñ\83Ñ\82Ñ\81Ñ\82вÑ\83Ñ\8eÑ\82 Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f."
+msgstr "Ð\9dеÑ\82 Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f Ð´Ð»Ñ\8f ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а."
 
 #: 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 Ð¸Ð· Ð¿Ð¾Ð´Ð³Ð¾Ñ\82овленного"
+msgstr "Удаление %s Ð¸Ð· Ð¸Ð½Ð´ÐµÐºÑ\81а"
 
 #: lib/index.tcl:328
 msgid "Ready to commit."
-msgstr "Ð\9fодгоÑ\82овлено Ð´Ð»Ñ\8f Ñ\81оÑ\85Ñ\80анениÑ\8f"
+msgstr "Ð\93оÑ\82ов Ð´Ð»Ñ\8f ÐºÐ¾Ð¼Ð¼Ð¸Ñ\82а."
 
 #: 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 "Ð\9eбÑ\80аÑ\89ение изменений в выбранных файлах"
 
 #: 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 "Ð\98Ñ\81полÑ\8cзоваÑ\82Ñ\8c Ð²ÐµÑ\80Ñ\81иÑ\8e Ð´Ñ\80Ñ\83гой Ð²ÐµÑ\82ви для разрешения конфликта?"
+msgstr "Ð\98Ñ\81полÑ\8cзоваÑ\82Ñ\8c Ð²ÐµÑ\80Ñ\81иÑ\8e Ð¸Ð· Ð´Ñ\80Ñ\83гой Ð²ÐµÑ\82ки для разрешения конфликта?"
 
 #: 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 "Ð\9dеизвеÑ\81Ñ\82наÑ\8f Ð¿Ñ\80огÑ\80амма Ñ\81лиÑ\8fниÑ\8f '%s'"
+msgstr "Ð\9dеподдеÑ\80живаемаÑ\8f Ð¿Ñ\80огÑ\80амма Ñ\81лиÑ\8fниÑ\8f Â«%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 "Ð\9eÑ\88ибка Ð² Ð³Ð»Ð¾Ð±Ð°Ð»Ñ\8cной Ñ\83Ñ\81Ñ\82ановке ÐºÐ¾Ð´Ð¸Ñ\80овки '%s'"
+msgstr "Ð\9dевеÑ\80наÑ\8f Ð³Ð»Ð¾Ð±Ð°Ð»Ñ\8cнаÑ\8f ÐºÐ¾Ð´Ð¸Ñ\80овка Â«%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 "ЧиÑ\81Ñ\82ка Ð²ÐµÑ\82вей Ñ\81лежениÑ\8f Ð¿Ñ\80и Ð¿Ð¾Ð»Ñ\83чении изменений"
+msgstr "ЧиÑ\81Ñ\82ка Ð¾Ñ\82Ñ\81леживаемÑ\8bÑ\85 Ð²ÐµÑ\82ок Ð¿Ñ\80и Ð¸Ð·Ð²Ð»Ðµчении изменений"
 
 #: lib/option.tcl:148
 msgid "Match Tracking Branches"
-msgstr "Ð\98мÑ\8f Ð½Ð¾Ð²Ð¾Ð¹ Ð²ÐµÑ\82ви Ð²Ð·Ñ\8fÑ\82Ñ\8c Ð¸Ð· Ð¸Ð¼ÐµÐ½ Ð²ÐµÑ\82вей Ñ\81лежениÑ\8f"
+msgstr "Такое Ð¶Ðµ Ð¸Ð¼Ñ\8f, ÐºÐ°Ðº Ð¸ Ñ\83 Ð¾Ñ\82Ñ\81леживаемой Ð²ÐµÑ\82ки"
 
 #: 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 "Шаблон Ð´Ð»Ñ\8f Ð¸Ð¼ÐµÐ½Ð¸ Ð½Ð¾Ð²Ð¾Ð¹ Ð²ÐµÑ\82ви"
+msgstr "Шаблон Ð´Ð»Ñ\8f Ð¸Ð¼ÐµÐ½Ð¸ Ð½Ð¾Ð²Ð¾Ð¹ Ð²ÐµÑ\82ки"
 
 #: 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 "Ð\9fолÑ\83чение из"
+msgstr "Ð\98звлечение из"
 
 #: 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 "полÑ\83чение %s"
+msgstr "извлечение %s"
 
 #: lib/remote_add.tcl:134
 #, tcl-format
 msgid "Fetching the %s"
-msgstr "Ð\9fолÑ\83чение %s"
+msgstr "Ð\98звлечение %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 "Удаление Ð²ÐµÑ\82ви во внешнем репозитории"
+msgstr "Удаление Ð²ÐµÑ\82ки во внешнем репозитории"
 
 #: lib/remote_branch_delete.tcl:47
 msgid "From Repository"
@@ -2216,7 +2045,7 @@ msgstr "Указанное положение:"
 
 #: lib/remote_branch_delete.tcl:84
 msgid "Branches"
-msgstr "Ð\92еÑ\82ви"
+msgstr "Ð\92еÑ\82ки"
 
 #: 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 "УкажиÑ\82е Ð¾Ð´Ð½Ñ\83 Ð¸Ð»Ð¸ Ð½ÐµÑ\81колÑ\8cко Ð²ÐµÑ\82вей для удаления."
+msgstr "УкажиÑ\82е Ð¾Ð´Ð½Ñ\83 Ð¸Ð»Ð¸ Ð½ÐµÑ\81колÑ\8cко Ð²ÐµÑ\82ок для удаления."
 
 #: lib/remote_branch_delete.tcl:226
 #, tcl-format
 msgid "Deleting branches from %s"
-msgstr "Удаление Ð²ÐµÑ\82вей из %s"
+msgstr "Удаление Ð²ÐµÑ\82ок из %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 "Ð\9fолÑ\83чение изменений из %s "
+msgstr "Ð\98звлечение изменений из %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 "ЧиÑ\81Ñ\82ка Ð²ÐµÑ\82вей Ñ\81лежениÑ\8f, Ñ\83даленных из %s"
+msgstr "ЧиÑ\81Ñ\82ка Ð¾Ñ\82Ñ\81леживаемÑ\8bÑ\85 Ð²ÐµÑ\82ок, Ñ\83далÑ\91нных из %s"
 
 #: lib/transport.tcl:26
 #, tcl-format
@@ -2537,11 +2355,11 @@ msgstr "Отправка %s %s в %s"
 
 #: lib/transport.tcl:100
 msgid "Push Branches"
-msgstr "Ð\9eÑ\82пÑ\80авиÑ\82Ñ\8c Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ\8f Ð² Ð²ÐµÑ\82вÑ\8fÑ\85"
+msgstr "Ð\9eÑ\82пÑ\80авиÑ\82Ñ\8c Ð²ÐµÑ\82ки"
 
 #: lib/transport.tcl:114
 msgid "Source Branches"
-msgstr "Ð\98Ñ\81Ñ\85однÑ\8bе Ð²ÐµÑ\82ви"
+msgstr "Ð\98Ñ\81Ñ\85однÑ\8bе Ð²ÐµÑ\82ки"
 
 #: 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 "Ð\9dамеÑ\80енно Ð¿ÐµÑ\80епиÑ\81аÑ\82Ñ\8c Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83Ñ\8eÑ\89Ñ\83Ñ\8e Ð²ÐµÑ\82вÑ\8c (возможна потеря изменений)"
+msgstr "Ð\9fÑ\80инÑ\83диÑ\82елÑ\8cно Ð¿ÐµÑ\80езапиÑ\81аÑ\82Ñ\8c Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83Ñ\8eÑ\89Ñ\83Ñ\8e Ð²ÐµÑ\82кÑ\83 (возможна потеря изменений)"
 
 #: lib/transport.tcl:175
 msgid "Use thin pack (for slow network connections)"
index 4d41d220a0da3a2357d63dbb6ba59514bb3f0462..fa423647858fede0cf63a27b2c11eb392b639a09 100755 (executable)
@@ -44,6 +44,7 @@
        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};
diff --git a/git.c b/git.c
index ab5c99cf70c3b46fb70b0e6a6bf5520e392e099c..efa1059fe060e413336ed4a02ac01d9381b34323 100644 (file)
--- a/git.c
+++ b/git.c
@@ -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();
index 44094f41d580bd4259bdb2bdb680b6dff4fd5340..7cf68f07b773f7e520c1e6ba680bff65e5bc7f3a 100755 (executable)
@@ -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;
 }
index 8672edaf4823daafbfa1c51b37faca11250374d4..e44cc27da15f03629989ee0083664d0af09c55b3 100644 (file)
@@ -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);
+                       }
                }
        }
 }
diff --git a/hex.c b/hex.c
index ab2610e498615b197787a8c22cd00f979e488fc8..845b01a874b45314a22e6000bb4e7762c3dd846a 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -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)
index adc8c8c3da6aaabe9f279b2a9841fc924ae39c1d..eef0a361f4f9bcc7f8fb278268334cb40a32fcb3 100644 (file)
@@ -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;
index adb9738c308f70190f6c3996d5781e6fbc4dea3f..5c7e27a89459a9a63018cc469262c82891c6f7b7 100644 (file)
@@ -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);
 
index b5c521fdea8b0bfc577795b172010dfc2b8bf8ff..c1a79c100c9a71083a58401081fb2929420ce361 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -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;
        }
 
index b949e517167dbac6c4c347e0a2cbdf8ca2dabbcf..39bcc168463fc7358ab7cb4e963e6ed8bde94804 100644 (file)
@@ -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)
diff --git a/path.c b/path.c
index a8e72955f6f28f6e27e7faeee144f4f3ed492c83..52d889c88ea8483b7a78937804bb1b789d2daa1e 100644 (file)
--- a/path.c
+++ b/path.c
@@ -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;
 }
index 86f2b449b1b43d7abb0917c07676304a666056a9..22ca74a126e79995607e68a557ae8bd4c2ab02f7 100644 (file)
@@ -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);
index ce7e4e8da3947bb2c527c49d6d09e1e49b0392c3..b2732822af0f34c6f7731df8a1a017d19f3e0508 100644 (file)
@@ -59,7 +59,7 @@ =head1 SYNOPSIS
                 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 @@ sub ident_person {
 
 =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;
                }
index 018beb85a09d6ef524c326c3ec947982c05bf556..711d2687a3005091d80fc964e5fd92aba6167b38 100644 (file)
@@ -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
index 4c4199afecac2c5fc6d918cf0d3b75449504b808..0df16ed72688f77058f471fddfdd25809d8926ae 100644 (file)
@@ -7,7 +7,9 @@ package Git::SVN::Editor;
 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};
index d8c21ad91549b4e52ce95c5e694db2f53f9931c9..64e900a0e910eea63943dfd129e595697b81c542 100644 (file)
@@ -9,7 +9,8 @@ package Git::SVN::Fetcher;
 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;
index cf6ffa7581310986b7618da36346dc499a9fb098..dc90f6a6214281386c401235827a8fd268994eeb 100644 (file)
@@ -44,7 +44,9 @@ package Git::SVN::Migration;
        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:
index 30489c60b1d3f8acc002fad31aa68b528757c8da..d4b6bfe076341d64bb948e4932655b927a369bc0 100644 (file)
@@ -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;
+}
index 3cb9d91baaa5bbd09e1ca55e8754e1c4edb6bd97..18eac6483095319806f4c9b301b0f84e0c68d38c 100644 (file)
  * 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
index 0c3149524059bd46c3c6d70bd774df0aaa65f976..37b2c3b1f9954d79bf799a3af979e8c337698b79 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -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;
index 38d67faf708d7b88f208f10dad23f893dd16f587..db5d910642663e73e4e4fc8d91e0caba4445bdb1 100644 (file)
@@ -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];
diff --git a/refs.c b/refs.c
index abc721b607cd8ff4916cce7f8381521fbb58fb5d..9bd0bc177bb8298148577ce0c3e52134452d6e51 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -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;
 }
 
index 5a4dbb66d7e4d4ed4bd6cf319f132c1b4d870572..ca905a9e8038dda26f8c4e146049ad00ed1cd8df 100644 (file)
@@ -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 @@ int start_command(struct child_process *cmd)
        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 @@ int start_command(struct child_process *cmd)
        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]);
index 50666497aeca8f4d3d403cfaeb0afeb350216a42..dd1c78c28db90b5261454dd8fae925faf4548e21 100644 (file)
@@ -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
index 90f2ac51a741f14cecab51177120cc7c3f063096..6195b43e9abacf346f52b6bbb758c9ca7b617d4e 100644 (file)
@@ -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;
        }
index eec8a60d6bdf22d429c14ad07288f823d9bac7b5..5fd75f30dd4455bf71a12fd628b488fc2b17e192 100644 (file)
 #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)
index 5ed5cb1d97ced70a9ebffd1dad524a12e7f7810a..7a513c576bdccf8730828fdaa586ec9d0af4af6b 100644 (file)
@@ -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[];
 
index 266152de36a099bfa75bfaa8429f0234c52a80b1..5457314e6aaaf1a30d33f68c8e1005f8db584564 100644 (file)
@@ -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;
index 409283614679b625f1cb6d191f1a2a32423bbcda..06409a38451971121e6cf6abd4a8af5bffd231c1 100644 (file)
@@ -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;
 }
index 2531e3af3b0d87e1f78fe09208cb85917ceff207..4d0b005d39c1c1ab2379911666d4df75d66b824c 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -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;
 }
 
index 0f764c0aef7aaa842299abde05effb73a0def9f4..4982d1c5216c10b7d44e66eace34e40d49cb5b2f 100644 (file)
--- a/t/README
+++ b/t/README
@@ -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
index 44f3290258a003317e4aad4f90d17ce5529e3b07..7af116d49e04777b9321044e199254a2211aa4d4 100644 (file)
@@ -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;
index 5b2fd0990894dd59a074ded5822bbcf215a989aa..27fe0405b887671ff0ca4cea1b258aa850e66c8c 100644 (file)
@@ -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 (executable)
index 0000000..a5dc39f
--- /dev/null
@@ -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
index f94120a8940a37c48fbb97abd85002da95e3abe9..71350e0657986d7b3e38801d82058c53f9fd23d0 100755 (executable)
@@ -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
 '
 
 
index e799e5954437e08df4a013f9796c5ec5a9bb077d..a20b9f58e3fe1de2932046d5d7f692b45e3ac854 100755 (executable)
@@ -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 (executable)
index 0000000..ae4c50f
--- /dev/null
@@ -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();
+       }
+}
index db5f60d0c5882fc491ca571eb6385554148bbf3a..74d2cd76fe56bf6d6fab5d44834999b55d3220de 100755 (executable)
@@ -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
index bf2deee10926f0d8ab7ba7d2645eb17a35b9a218..444b5a4df801975cf4f96f8193cb04ac91af3e4d 100755 (executable)
@@ -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"
index 7c659eb5852e986dc5605e082f773e68e746c789..711704ba5a052b15d94ff1ce958ada3fb86530a9 100755 (executable)
@@ -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' '
index 4bcc335a19f9d7864560945ed0f430728d0f1986..b618d6be21ff8926cb0d9dff1881c6e5e9eeb35a 100755 (executable)
@@ -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 &&
index 8f22c43e245cb6fb68449a64ba45222e187f56a9..84a9028c433c02c72adcdf06222bcb409e2eb683 100755 (executable)
@@ -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 (executable)
index 0000000..a542617
--- /dev/null
@@ -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
index 51f3bbb8af446dd24a835a8593475b59a73f6d1f..394f0005a195c1fbc713226c97ecee5cf4600007 100755 (executable)
@@ -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
 
 '
 
index d046d98aec61748ad936515807f28f77600f630e..14f0edca2b6f67e6ae98379702b5566e4083d8e0 100755 (executable)
@@ -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
index 924a26612681817156e54f4c1bc4a89e7cb5e8de..f3a4b4a913f344ce140344ec7b70482a6d36bcbe 100755 (executable)
@@ -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 &&
index 2434157aa7632551e8f7b9db59b34405f8fbefa3..289806d0c7eb02e0acc5244df5e3999d45b4e085 100755 (executable)
@@ -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
index 9bd7dd2ba15cfd1096d3e89b012f5979b1a01eee..168739c7214c764ef7f35a83063d369c1db22ec4 100755 (executable)
@@ -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
index 3e752ce03280ff694fedb6657fe2ed334127f253..969e4e9e5261640dc690806ed06f8e58e760633f 100755 (executable)
@@ -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
index e0c5f44cac0290c6e15c5360b0cc7c1f4d486614..31db7b5f9125ed346450d7bfe29d8bb5fac0460a 100755 (executable)
@@ -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
index 3012a4d7c0aa5015add8ae3de2951981e28ee12a..e319fa2e8470791340a479989a81d934b79405da 100755 (executable)
@@ -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 &&
index 6e839f5489c5a8e3667d182f6de1873c2d29bd0a..762135adea6d4e8594a077f1b52dcb50d49adc4e 100755 (executable)
@@ -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
index aee785cffa8d656fa52e6b893921153faf748fe9..4dd1d7c52085d25546f64692acd0b351a7dddd72 100755 (executable)
@@ -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 &&
index 2d05d3eeab3f801fcf74a93c9305d067ac628937..dfeaa9c6550eac1f9beeca64682cecfebfe2c7d3 100755 (executable)
        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>"],
index b3355d2c7016e55e99a465414f230569c3807230..3dc4a3454d223d37e85de6fa4ae8656046370e99 100755 (executable)
@@ -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
index 11562bde10be50d47f3a82b3885b5a0aab50c14a..b859db61ac0d034b8f2c14cea4a960a1a3d9f576 100644 (file)
@@ -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
index c6ea9ac64d75292021919ad54462a3e4b782b080..f0ecde2d2f8b35c565ab176c76882a5fee8d8fb2 100644 (file)
--- a/trailer.c
+++ b/trailer.c
@@ -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);
 }
@@ -91,12 +118,19 @@ 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);
index 079499dbafd749aaaebcac96d717893c8919ef23..d57e8dec28d6890ce981a8422e9b8b60b697ecd6 100644 (file)
@@ -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;
index 68669f14d08f52d15f476fe97a813044a0d07dda..b8e4ee8099260a74e5207048cf2837c9fd686568 100644 (file)
@@ -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 *);
index 5ec21e61d916999f4639162b8086115ad382f877..e0db8b42be2d6ba400da987f8cb2ba573c5eda01 100644 (file)
@@ -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;
 }
 
index 5acfe4cd64967d86462f3958433f3d2f844ead51..f7869f8d6072c98c3e9be3387a9a3c19508e13ea 100644 (file)
@@ -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)) {
index 073443247a6d56ddd8448aafce854a4b5513e605..eab8c8d0b9aab55c8435b9f451efd81e715131f8 100644 (file)
@@ -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
index ca5c45f94522f5487e3a93438cbccf8aecb65d5d..a2e9d332d8332bb1dbeea26b683c5e3f032a822e 100644 (file)
@@ -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;
+}
index e40183770700cb593d4e4f2bf914a5b6eff0bb09..54fec77032dc9c5d0f7af4226afc886797a209c4 100644 (file)
@@ -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 */