Merge branch 'ls/p4-test-updates'
authorJunio C Hamano <gitster@pobox.com>
Tue, 20 Oct 2015 22:26:09 +0000 (15:26 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 20 Oct 2015 22:26:09 +0000 (15:26 -0700)
A few test scripts around "git p4" have been improved for
portability.

* ls/p4-test-updates:
git-p4: skip t9819 test case on case insensitive file systems
git-p4: avoid "stat" command in t9815 git-p4-submit-fail

217 files changed:
.gitignore
Documentation/RelNotes/2.6.2.txt [new file with mode: 0644]
Documentation/RelNotes/2.7.0.txt [new file with mode: 0644]
Documentation/blame-options.txt
Documentation/config.txt
Documentation/git-am.txt
Documentation/git-bisect-lk2009.txt
Documentation/git-bisect.txt
Documentation/git-branch.txt
Documentation/git-cherry-pick.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-for-each-ref.txt
Documentation/git-gc.txt
Documentation/git-grep.txt
Documentation/git-interpret-trailers.txt
Documentation/git-log.txt
Documentation/git-ls-remote.txt
Documentation/git-merge.txt
Documentation/git-notes.txt
Documentation/git-p4.txt
Documentation/git-quiltimport.txt
Documentation/git-rebase.txt
Documentation/git-remote.txt
Documentation/git-rev-list.txt
Documentation/git-revert.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/gitignore.txt
Documentation/glossary-content.txt
Documentation/pretty-options.txt
Documentation/rev-list-options.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
RelNotes
advice.c
archive-tar.c
archive.c
bisect.c
builtin.h
builtin/am.c
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/clean.c
builtin/clone.c
builtin/config.c
builtin/fetch.c
builtin/for-each-ref.c
builtin/fsck.c
builtin/gc.c
builtin/help.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/ls-remote.c
builtin/ls-tree.c
builtin/mailsplit.c
builtin/merge-index.c
builtin/merge-recursive.c
builtin/merge.c
builtin/name-rev.c
builtin/pull.c
builtin/read-tree.c
builtin/receive-pack.c
builtin/remote-ext.c
builtin/remote.c
builtin/rerere.c
builtin/rev-list.c
builtin/show-branch.c
builtin/submodule--helper.c [new file with mode: 0644]
builtin/tag.c
builtin/unpack-file.c
builtin/upload-archive.c
bulk-checkin.c
cache.h
color.c
color.h
compat/hstrerror.c
compat/inet_ntop.c
compat/mingw.c
compat/nedmalloc/nedmalloc.c
compat/precompose_utf8.c
compat/precompose_utf8.h
compat/winansi.c
config.mak.uname
configure.ac
connect.c
contrib/examples/git-pull.sh
contrib/hooks/multimail/CHANGES
contrib/hooks/multimail/CONTRIBUTING.rst [new file with mode: 0644]
contrib/hooks/multimail/README
contrib/hooks/multimail/README.Git
contrib/hooks/multimail/doc/gerrit.rst [new file with mode: 0644]
contrib/hooks/multimail/doc/gitolite.rst [new file with mode: 0644]
contrib/hooks/multimail/git_multimail.py
contrib/hooks/multimail/migrate-mailhook-config
contrib/hooks/multimail/post-receive.example
contrib/subtree/git-subtree.sh
contrib/subtree/t/t7900-subtree.sh
convert.c
daemon.c
date.c
diff-no-index.c
diff.c
dir.c
entry.c
environment.c
fast-import.c
fetch-pack.c
git-bisect.sh
git-compat-util.h
git-filter-branch.sh
git-p4.py
git-quiltimport.sh
git-rebase--interactive.sh
git-rebase.sh
git-send-email.perl
git-stash.sh
git-submodule.sh
git.c
grep.c
hex.c
http-push.c
http-walker.c
http.c
imap-send.c
ll-merge.c
mailmap.c
merge-recursive.c
notes.c
pack-bitmap.c
pager.c
parse-options-cb.c
parse-options.h
path.c
pkt-line.c
po/ru.po
progress.c
quote.c
reachable.c
ref-filter.c
ref-filter.h
reflog-walk.c
refs.c
refs.h
remote-curl.c
remote.c
rerere.c
rerere.h
revision.c
run-command.c
run-command.h
setup.c
sha1_file.c
sha1_name.c
sideband.c
strbuf.c
strbuf.h
submodule-config.c
submodule.c
t/annotate-tests.sh
t/perf/aggregate.perl
t/t0001-init.sh
t/t0002-gitfile.sh
t/t0060-path-utils.sh
t/t1400-update-ref.sh
t/t1401-symbolic-ref.sh
t/t1430-bad-ref-name.sh
t/t1450-fsck.sh
t/t2025-worktree-add.sh
t/t2026-prune-linked-checkouts.sh [deleted file]
t/t2026-worktree-prune.sh [new file with mode: 0755]
t/t3001-ls-files-others-exclude.sh
t/t3203-branch-output.sh
t/t3210-pack-refs.sh
t/t3301-notes.sh
t/t3404-rebase-interactive.sh
t/t3420-rebase-autostash.sh
t/t5505-remote.sh
t/t5507-remote-environment.sh [new file with mode: 0755]
t/t5560-http-backend-noserver.sh
t/t5561-http-backend.sh
t/t556x_common
t/t5700-clone-reference.sh
t/t5801-remote-helpers.sh
t/t5802-connect-helper.sh
t/t6030-bisect-porcelain.sh
t/t6300-for-each-ref.sh
t/t6302-for-each-ref-filter.sh [new file with mode: 0755]
t/t6500-gc.sh
t/t7003-filter-branch.sh
t/t7004-tag.sh
t/t7410-submodule-checkout-to.sh
t/t7610-mergetool.sh
t/t7800-difftool.sh
t/t9811-git-p4-label-import.sh
t/t9822-git-p4-path-encoding.sh [new file with mode: 0755]
t/t9823-git-p4-mock-lfs.sh [new file with mode: 0755]
t/t9824-git-p4-git-lfs.sh [new file with mode: 0755]
t/t9825-git-p4-handle-utf16-without-bom.sh [new file with mode: 0755]
t/test-lib-functions.sh
tag.c
test-dump-cache-tree.c
test-path-utils.c
trace.c
transport.c
unpack-trees.c
url.c
utf8.c
utf8.h
walker.c
wrapper.c
wt-status.c
index 4fd81baf856669fb984a5a0b82a1115e840fc16d..1c2f8321386f89ef8c03d11159c97a0f194c4423 100644 (file)
 /git-status
 /git-stripspace
 /git-submodule
+/git-submodule--helper
 /git-svn
 /git-symbolic-ref
 /git-tag
diff --git a/Documentation/RelNotes/2.6.2.txt b/Documentation/RelNotes/2.6.2.txt
new file mode 100644 (file)
index 0000000..5b65e35
--- /dev/null
@@ -0,0 +1,65 @@
+Git v2.6.2 Release Notes
+========================
+
+Fixes since v2.6.1
+------------------
+
+ * There were some classes of errors that "git fsck" diagnosed to its
+   standard error that did not cause it to exit with non-zero status.
+
+ * A test script for the HTTP service had a timing dependent bug,
+   which was fixed.
+
+ * Performance-measurement tests did not work without an installed Git.
+
+ * On a case insensitive filesystems, setting GIT_WORK_TREE variable
+   using a random cases that does not agree with what the filesystem
+   thinks confused Git that it wasn't inside the working tree.
+
+ * When "git am" was rewritten as a built-in, it stopped paying
+   attention to user.signingkey, which was fixed.
+
+ * After "git checkout --detach", "git status" reported a fairly
+   useless "HEAD detached at HEAD", instead of saying at which exact
+   commit.
+
+ * "git rebase -i" had a minor regression recently, which stopped
+   considering a line that begins with an indented '#' in its insn
+   sheet not a comment, which is now fixed.
+
+ * Description of the "log.follow" configuration variable in "git log"
+   documentation is now also copied to "git config" documentation.
+
+ * Allocation related functions and stdio are unsafe things to call
+   inside a signal handler, and indeed killing the pager can cause
+   glibc to deadlock waiting on allocation mutex as our signal handler
+   tries to free() some data structures in wait_for_pager().  Reduce
+   these unsafe calls.
+
+ * The way how --ref/--notes to specify the notes tree reference are
+   DWIMmed was not clearly documented.
+
+ * Customization to change the behaviour with "make -w" and "make -s"
+   in our Makefile was broken when they were used together.
+
+ * The Makefile always runs the library archiver with hardcoded "crs"
+   options, which was inconvenient for exotic platforms on which
+   people want to use programs with totally different set of command
+   line options.
+
+ * The ssh transport, just like any other transport over the network,
+   did not clear GIT_* environment variables, but it is possible to
+   use SendEnv and AcceptEnv to leak them to the remote invocation of
+   Git, which is not a good idea at all.  Explicitly clear them just
+   like we do for the local transport.
+
+ * "git blame --first-parent v1.0..v2.0" was not rejected but did not
+   limit the blame to commits on the first parent chain.
+
+ * Very small number of options take a parameter that is optional
+   (which is not a great UI element as they can only appear at the end
+   of the command line).  Add notice to documentation of each and
+   every one of them.
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
diff --git a/Documentation/RelNotes/2.7.0.txt b/Documentation/RelNotes/2.7.0.txt
new file mode 100644 (file)
index 0000000..0512902
--- /dev/null
@@ -0,0 +1,227 @@
+Git 2.7 Release Notes
+=====================
+
+Updates since v2.6
+------------------
+
+UI, Workflows & Features
+
+ * "git remote" learned "get-url" subcommand to show the URL for a
+   given remote name used for fetching and pushing.
+
+ * There was no way to defeat a configured rebase.autostash variable
+   from the command line, as "git rebase --no-autostash" was missing.
+
+ * "git log --date=local" used to only show the normal (default)
+   format in the local timezone.  The command learned to take 'local'
+   as an instruction to use the local timezone with other formats,
+
+ * The refs used during a "git bisect" session is now per-worktree so
+   that independent bisect sessions can be done in different worktrees
+   created with "git worktree add".
+
+ * Users who are too busy to type three extra keystrokes to ask for
+   "git stash show -p" can now set stash.showPatch configuration
+   varible to true to always see the actual patch, not just the list
+   of paths affected with feel for the extent of damage via diffstat.
+
+ * "quiltimport" allows to specify the series file by honoring the
+   $QUILT_SERIES environment and also --series command line option.
+
+ * The use of 'good/bad' in "git bisect" made it confusing to use when
+   hunting for a state change that is not a regression (e.g. bugfix).
+   The command learned 'old/new' and then allows the end user to
+   say e.g. "bisect start --term-old=fast --term-new=slow" to find a
+   performance regression.
+
+ * "git interpret-trailers" can now run outside of a Git repository.
+
+ * "git p4" learned to reencode the pathname it uses to communicate
+   with the p4 depot with a new option.
+
+ * Give progress meter to "git filter-branch".
+
+ * Allow a later "!/abc/def" to override an earlier "/abc" that
+   appears in the same .gitignore file to make it easier to express
+   "everything in /abc directory is ignored, except for ...".
+
+ * Teach "git p4" to send large blobs outside the repository by
+   talking to Git LFS.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * The infrastructure to rewrite "git submodule" in C is being built
+   incrementally.  Let's polish these early parts well enough and make
+   them graduate to 'next' and 'master', so that the more involved
+   follow-up can start cooking on a solid ground.
+
+ * Some features from "git tag -l" and "git branch -l" have been made
+   available to "git for-each-ref" so that eventually the unified
+   implementation can be shared across all three.
+
+ * Because "test_when_finished" in our test framework queues the
+   clean-up tasks to be done in a shell variable, it should not be
+   used inside a subshell.  Add a mechanism to allow 'bash' to catch
+   such uses, and fix the ones that were found.
+   (merge 0968f12 jk/test-lint-forbid-when-finished-in-subshell later to maint).
+
+ * The debugging infrastructure for pkt-line based communication has
+   been improved to mark the side-band communication specifically.
+   (merge fd89433 jk/async-pkt-line later to maint).
+
+ * Update "git branch" that list existing branches, using the
+   ref-filter API that is shared with "git tag" and "git
+   for-each-ref".
+
+
+Also contains various documentation updates and code clean-ups.
+
+
+Fixes since v2.6
+----------------
+
+Unless otherwise noted, all the fixes since v2.6 in the maintenance
+track are contained in this release (see the maintenance releases'
+notes for details).
+
+ * Very small number of options take a parameter that is optional
+   (which is not a great UI element as they can only appear at the end
+   of the command line).  Add notice to documentation of each and
+   every one of them.
+   (merge 2b594bf mm/keyid-docs later to maint).
+
+ * "git blame --first-parent v1.0..v2.0" was not rejected but did not
+   limit the blame to commits on the first parent chain.
+   (merge 95a4fb0 jk/blame-first-parent later to maint).
+
+ * "git subtree" (in contrib/) now can take whitespaces in the
+   pathnames, not only in the in-tree pathname but the name of the
+   directory that the repository is in.  (merge 5b6ab38
+   as/subtree-with-spaces later to maint).
+
+ * The ssh transport, just like any other transport over the network,
+   did not clear GIT_* environment variables, but it is possible to
+   use SendEnv and AcceptEnv to leak them to the remote invocation of
+   Git, which is not a good idea at all.  Explicitly clear them just
+   like we do for the local transport.
+   (merge a48b409 jk/connect-clear-env later to maint).
+
+ * Correct "git p4 --detect-labels" so that it does not fail to create
+   a tag that points at a commit that is also being imported.
+   (merge b43702a ld/p4-import-labels later to maint).
+
+ * The Makefile always runs the library archiver with hardcoded "crs"
+   options, which was inconvenient for exotic platforms on which
+   people want to use programs with totally different set of command
+   line options.
+   (merge ac179b4 jw/make-arflags-customizable later to maint).
+
+ * Customization to change the behaviour with "make -w" and "make -s"
+   in our Makefile was broken when they were used together.
+   (merge ef49e05 jk/make-findstring-makeflags-fix later to maint).
+
+ * Allocation related functions and stdio are unsafe things to call
+   inside a signal handler, and indeed killing the pager can cause
+   glibc to deadlock waiting on allocation mutex as our signal handler
+   tries to free() some data structures in wait_for_pager().  Reduce
+   these unsafe calls.
+   (merge 507d780 ti/glibc-stdio-mutex-from-signal-handler later to maint).
+
+ * The way how --ref/--notes to specify the notes tree reference are
+   DWIMmed was not clearly documented.
+   (merge e14c92e jk/notes-dwim-doc later to maint).
+
+ * "git gc" used to barf when a symbolic ref has gone dangling
+   (e.g. the branch that used to be your upstream's default when you
+   cloned from it is now gone, and you did "fetch --prune").
+   (merge 14886b4 js/gc-with-stale-symref later to maint).
+
+ * "git clone --dissociate" runs a big "git repack" process at the
+   end, and it helps to close file descriptors that are open on the
+   packs and their idx files before doing so on filesystems that
+   cannot remove a file that is still open.
+   (merge 786b150 js/clone-dissociate later to maint).
+
+ * Description of the "log.follow" configuration variable in "git log"
+   documentation is now also copied to "git config" documentation.
+   (merge fd8d07e dt/log-follow-config later to maint).
+
+ * "git rebase -i" had a minor regression recently, which stopped
+   considering a line that begins with an indented '#' in its insn
+   sheet not a comment, which is now fixed.
+   (merge 1db168e gr/rebase-i-drop-warn later to maint).
+
+ * After "git checkout --detach", "git status" reported a fairly
+   useless "HEAD detached at HEAD", instead of saying at which exact
+   commit.
+   (merge 0eb8548 mm/detach-at-HEAD-reflog later to maint).
+
+ * When "git send-email" wanted to talk over Net::SMTP::SSL,
+   Net::Cmd::datasend() did not like to be fed too many bytes at the
+   same time and failed to send messages.  Send the payload one line
+   at a time to work around the problem.
+   (merge f60c483 sa/send-email-smtp-batch-data-limit later to maint).
+
+ * When "git am" was rewritten as a built-in, it stopped paying
+   attention to user.signingkey, which was fixed.
+   (merge 434c64d pt/am-builtin later to maint).
+
+ * It was not possible to use a repository-lookalike created by "git
+   worktree add" as a local source of "git clone".
+   (merge d78db84 nd/clone-linked-checkout later to maint).
+
+ * On a case insensitive filesystems, setting GIT_WORK_TREE variable
+   using a random cases that does not agree with what the filesystem
+   thinks confused Git that it wasn't inside the working tree.
+   (merge 63ec5e1 js/icase-wt-detection later to maint).
+
+ * Performance-measurement tests did not work without an installed Git.
+   (merge 31cd128 sb/perf-without-installed-git later to maint).
+
+ * A test script for the HTTP service had a timing dependent bug,
+   which was fixed.
+   (merge 362d8b6 sb/http-flaky-test-fix later to maint).
+
+ * There were some classes of errors that "git fsck" diagnosed to its
+   standard error that did not cause it to exit with non-zero status.
+   (merge 122f76f jc/fsck-dropped-errors later to maint).
+
+ * Work around "git p4" failing when the P4 depot records the contents
+   in UTF-16 without UTF-16 BOM.
+   (merge 1f5f390 ls/p4-translation-failure later to maint).
+
+ * When "git gc --auto" is backgrounded, its diagnosis message is
+   lost.  Save it to a file in $GIT_DIR and show it next time the "gc
+   --auto" is run.
+   (merge 329e6e8 nd/gc-auto-background-fix later to maint).
+
+ * The submodule code has been taught to work better with separate
+   work trees created via "git worktree add".
+   (merge 11f9dd7 mk/submodule-gitdir-path later to maint).
+
+ * "git gc" is safe to run anytime only because it has the built-in
+   grace period to protect young objects.  In order to run with no
+   grace period, the user must make sure that the repository is
+   quiescent.
+   (merge fae1a90 jc/doc-gc-prune-now later to maint).
+
+ * A recent "filter-branch --msg-filter" broke skipping of the commit
+   object header, which is fixed.
+   (merge a5a4b3f jk/filter-branch-use-of-sed-on-incomplete-line later to maint).
+
+ * The normalize_ceiling_entry() function does not muck with the end
+   of the path it accepts, and the real world callers do rely on that,
+   but a test insisted that the function drops a trailing slash.
+   (merge b2a7123 rd/test-path-utils later to maint).
+
+ * Code clean-up and minor fixes.
+   (merge 15ed07d jc/rerere later to maint).
+   (merge b744767 pt/pull-builtin later to maint).
+   (merge 29bc480 nd/ls-remote-does-not-have-u-option later to maint).
+   (merge be510e0 jk/asciidoctor-section-heading-markup-fix later to maint).
+   (merge 83e6bda tk/typofix-connect-unknown-proto-error later to maint).
+   (merge a43eb67 tk/doc-interpret-trailers-grammo later to maint).
+   (merge ba128e2 es/worktree-add-cleanup later to maint).
+   (merge 44cd91e cc/quote-comments later to maint).
+   (merge 147875f sb/submodule-config-parse later to maint).
index a09969ba086609af5d35bbb3c5f30dfea5db00b6..760eab7428357ad4006437ae3032cee958dce803 100644 (file)
@@ -63,11 +63,10 @@ include::line-range-format.txt[]
        `-` to make the command read from the standard input).
 
 --date <format>::
-       The value is one of the following alternatives:
-       {relative,local,default,iso,rfc,short}. If --date is not
+       Specifies the format used to output dates. If --date is not
        provided, the value of the blame.date config variable is
        used. If the blame.date config variable is also not set, the
-       iso format is used. For more information, See the discussion
+       iso format is used. For supported values, see the discussion
        of the --date option at linkgit:git-log[1].
 
 -M|<num>|::
index 0cc87a6f6565bbe794420bc47be8407e4ac47f22..391a0c3c857081e0509ea5a1b2ac085e79439d8f 100644 (file)
@@ -1829,9 +1829,7 @@ log.abbrevCommit::
 log.date::
        Set the default date-time mode for the 'log' command.
        Setting a value for log.date is similar to using 'git log''s
-       `--date` option.  Possible values are `relative`, `local`,
-       `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1]
-       for details.
+       `--date` option.  See linkgit:git-log[1] for details.
 
 log.decorate::
        Print out the ref names of any commits that are shown by the log
@@ -1840,6 +1838,12 @@ log.decorate::
        specified, the full ref name (including prefix) will be printed.
        This is the same as the log commands '--decorate' option.
 
+log.follow::
+       If `true`, `git log` will act as if the `--follow` option was used when
+       a single <path> is given.  This has the same limitations as `--follow`,
+       i.e. it cannot be used to follow multiple files and does not work well
+       on non-linear history.
+
 log.showRoot::
        If true, the initial commit will be shown as a big creation event.
        This is equivalent to a diff against an empty tree.
@@ -2587,6 +2591,16 @@ status.submoduleSummary::
        submodule summary' command, which shows a similar output but does
        not honor these settings.
 
+stash.showPatch::
+       If this is set to true, the `git stash show` command without an
+       option will show the stash in patch form.  Defaults to false.
+       See description of 'show' command in linkgit:git-stash[1].
+
+stash.showStat::
+       If this is set to true, the `git stash show` command without an
+       option will show diffstat of the stash.  Defaults to true.
+       See description of 'show' command in linkgit:git-stash[1].
+
 submodule.<name>.path::
 submodule.<name>.url::
        The path within this project and URL for a submodule. These
index dbea6e7ae9131a1e5c51dec460d58cc6deda701b..452c1feb2319a3ac47836b5039ebeba519be3c87 100644 (file)
@@ -141,7 +141,9 @@ default.   You can use `--no-utf8` to override this.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign commits.
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 --continue::
 -r::
index 0f0c6ff0829dfab3bd45b34b76a5b489f648eba9..c06efbd42a6c1010f0a5ec17ba460e0ebcd91d9d 100644 (file)
@@ -1321,7 +1321,7 @@ So git bisect is unconditional goodness - and feel free to quote that
 _____________
 
 Acknowledgments
-----------------
+---------------
 
 Many thanks to Junio Hamano for his help in reviewing this paper, for
 reviewing the patches I sent to the Git mailing list, for discussing
index e97f2de21bdc58cc2de35731f7b339353b121bd0..2044fe6820e0507a6dcc1823f8fe3fce407e4d90 100644 (file)
@@ -16,9 +16,11 @@ DESCRIPTION
 The command takes various subcommands, and different options depending
 on the subcommand:
 
- git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
- git bisect bad [<rev>]
- git bisect good [<rev>...]
+ git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
+                 [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]
+ git bisect (bad|new) [<rev>]
+ git bisect (good|old) [<rev>...]
+ git bisect terms [--term-good | --term-bad]
  git bisect skip [(<rev>|<range>)...]
  git bisect reset [<commit>]
  git bisect visualize
@@ -36,6 +38,13 @@ whether the selected commit is "good" or "bad". It continues narrowing
 down the range until it finds the exact commit that introduced the
 change.
 
+In fact, `git bisect` can be used to find the commit that changed
+*any* property of your project; e.g., the commit that fixed a bug, or
+the commit that caused a benchmark's performance to improve. To
+support this more general usage, the terms "old" and "new" can be used
+in place of "good" and "bad", or you can choose your own terms. See
+section "Alternate terms" below for more information.
+
 Basic bisect commands: start, bad, good
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -111,6 +120,79 @@ bad revision, while `git bisect reset HEAD` will leave you on the
 current bisection commit and avoid switching commits at all.
 
 
+Alternate terms
+~~~~~~~~~~~~~~~
+
+Sometimes you are not looking for the commit that introduced a
+breakage, but rather for a commit that caused a change between some
+other "old" state and "new" state. For example, you might be looking
+for the commit that introduced a particular fix. Or you might be
+looking for the first commit in which the source-code filenames were
+finally all converted to your company's naming standard. Or whatever.
+
+In such cases it can be very confusing to use the terms "good" and
+"bad" to refer to "the state before the change" and "the state after
+the change". So instead, you can use the terms "old" and "new",
+respectively, in place of "good" and "bad". (But note that you cannot
+mix "good" and "bad" with "old" and "new" in a single session.)
+
+In this more general usage, you provide `git bisect` with a "new"
+commit has some property and an "old" commit that doesn't have that
+property. Each time `git bisect` checks out a commit, you test if that
+commit has the property. If it does, mark the commit as "new";
+otherwise, mark it as "old". When the bisection is done, `git bisect`
+will report which commit introduced the property.
+
+To use "old" and "new" instead of "good" and bad, you must run `git
+bisect start` without commits as argument and then run the following
+commands to add the commits:
+
+------------------------------------------------
+git bisect old [<rev>]
+------------------------------------------------
+
+to indicate that a commit was before the sought change, or
+
+------------------------------------------------
+git bisect new [<rev>...]
+------------------------------------------------
+
+to indicate that it was after.
+
+To get a reminder of the currently used terms, use
+
+------------------------------------------------
+git bisect terms
+------------------------------------------------
+
+You can get just the old (respectively new) term with `git bisect term
+--term-old` or `git bisect term --term-good`.
+
+If you would like to use your own terms instead of "bad"/"good" or
+"new"/"old", you can choose any names you like (except existing bisect
+subcommands like `reset`, `start`, ...) by starting the
+bisection using
+
+------------------------------------------------
+git bisect start --term-old <term-old> --term-new <term-new>
+------------------------------------------------
+
+For example, if you are looking for a commit that introduced a
+performance regression, you might use
+
+------------------------------------------------
+git bisect start --term-old fast --term-new slow
+------------------------------------------------
+
+Or if you are looking for the commit that fixed a bug, you might use
+
+------------------------------------------------
+git bisect start --term-new fixed --term-old broken
+------------------------------------------------
+
+Then, use `git bisect <term-old>` and `git bisect <term-new>` instead
+of `git bisect good` and `git bisect bad` to mark commits.
+
 Bisect visualize
 ~~~~~~~~~~~~~~~~
 
@@ -387,6 +469,21 @@ In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit
 has at least one parent whose reachable graph is fully traversable in the sense
 required by 'git pack objects'.
 
+* Look for a fix instead of a regression in the code
++
+------------
+$ git bisect start
+$ git bisect new HEAD    # current commit is marked as new
+$ git bisect old HEAD~10 # the tenth commit from now is marked as old
+------------
++
+or:
+------------
+$ git bisect start --term-old broken --term-new fixed
+$ git bisect fixed
+$ git bisect broken HEAD~10
+------------
+
 Getting help
 ~~~~~~~~~~~~
 
index bbbade4f5112645c13bde24b4dcbb929a4f8c4b3..4a7037f1c8eed24db05f00c4012aca420f98b73b 100644 (file)
@@ -11,7 +11,8 @@ SYNOPSIS
 'git branch' [--color[=<when>] | --no-color] [-r | -a]
        [--list] [-v [--abbrev=<length> | --no-abbrev]]
        [--column[=<options>] | --no-column]
-       [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
+       [(--merged | --no-merged | --contains) [<commit>]] [--sort=<key>]
+       [--points-at <object>] [<pattern>...]
 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
@@ -231,6 +232,19 @@ start-point is either a local or remote-tracking branch.
        The new name for an existing branch. The same restrictions as for
        <branchname> apply.
 
+--sort=<key>::
+       Sort based on the key given. Prefix `-` to sort in descending
+       order of the value. You may use the --sort=<key> option
+       multiple times, in which case the last key becomes the primary
+       key. The keys supported are the same as those in `git
+       for-each-ref`. Sort order defaults to sorting based on the
+       full refname (including `refs/...` prefix). This lists
+       detached HEAD (if present) first, then local branches and
+       finally remote-tracking branches.
+
+
+--points-at <object>::
+       Only list branches of the given object.
 
 Examples
 --------
index 1147c71da605c1f5779835137fb6bab5e2c349a6..77da29a474518c56c7dab23f5921aa50566506dd 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff]
-                 [-S[<key-id>]] <commit>...
+                 [-S[<keyid>]] <commit>...
 'git cherry-pick' --continue
 'git cherry-pick' --quit
 'git cherry-pick' --abort
@@ -101,9 +101,11 @@ effect to your index in a row.
 --signoff::
        Add Signed-off-by line at the end of the commit message.
 
--S[<key-id>]::
---gpg-sign[=<key-id>]::
-       GPG-sign commits.
+-S[<keyid>]::
+--gpg-sign[=<keyid>]::
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 --ff::
        If the current HEAD is the same as the parent of the
index f5f2a8d326502714299cf85d9819bb065d5af390..a0b5457304008cec1cf728e5ac168c5f547173eb 100644 (file)
@@ -56,7 +56,9 @@ OPTIONS
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign commit.
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 --no-gpg-sign::
        Countermand `commit.gpgSign` configuration variable that is
index 904dafa0f7070fc438f138393a3b356542ae04d9..7f34a5b33103ed1c870f234687079badc393be77 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
           [--date=<date>] [--cleanup=<mode>] [--[no-]status]
-          [-i | -o] [-S[<key-id>]] [--] [<file>...]
+          [-i | -o] [-S[<keyid>]] [--] [<file>...]
 
 DESCRIPTION
 -----------
@@ -314,7 +314,9 @@ changes to tracked files.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign commit.
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 --no-gpg-sign::
        Countermand `commit.gpgSign` configuration variable that is
index 7f8d9a5b5f358baa50bdfe205f555ea904f9f629..c6f073cea42a2a91fba1aeb17505fe6cd7f46ac8 100644 (file)
@@ -10,6 +10,8 @@ SYNOPSIS
 [verse]
 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
                   [(--sort=<key>)...] [--format=<format>] [<pattern>...]
+                  [--points-at <object>] [(--merged | --no-merged) [<object>]]
+                  [--contains [<object>]]
 
 DESCRIPTION
 -----------
@@ -62,6 +64,20 @@ OPTIONS
        the specified host language.  This is meant to produce
        a scriptlet that can directly be `eval`ed.
 
+--points-at <object>::
+       Only list refs which points at the given object.
+
+--merged [<object>]::
+       Only list refs whose tips are reachable from the
+       specified commit (HEAD if not specified).
+
+--no-merged [<object>]::
+       Only list refs whose tips are not reachable from the
+       specified commit (HEAD if not specified).
+
+--contains [<object>]::
+       Only list tags which contain the specified commit (HEAD if not
+       specified).
 
 FIELD NAMES
 -----------
@@ -111,6 +127,17 @@ color::
        Change output color.  Followed by `:<colorname>`, where names
        are described in `color.branch.*`.
 
+align::
+       Left-, middle-, or right-align the content between
+       %(align:...) and %(end). The "align:" is followed by `<width>`
+       and `<position>` in any order separated by a comma, where the
+       `<position>` is either left, right or middle, default being
+       left and `<width>` is the total length of the content with
+       alignment. If the contents length is more than the width then
+       no alignment is performed. If used with '--quote' everything
+       in between %(align:...) and %(end) is quoted, but if nested
+       then only the topmost level performs quoting.
+
 In addition to the above, for commit and tag objects, the header
 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
 be used to specify the value in the header field.
@@ -123,20 +150,23 @@ The complete message in a commit and tag object is `contents`.
 Its first line is `contents:subject`, where subject is the concatenation
 of all lines of the commit message up to the first blank line.  The next
 line is 'contents:body', where body is all of the lines after the first
-blank line.  Finally, the optional GPG signature is `contents:signature`.
+blank line.  The optional GPG signature is `contents:signature`.  The
+first `N` lines of the message is obtained using `contents:lines=N`.
 
 For sorting purposes, fields with numeric values sort in numeric
 order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
 All other fields are used to sort in their byte-value order.
 
+There is also an option to sort by versions, this can be done by using
+the fieldname `version:refname` or its alias `v:refname`.
+
 In any case, a field name that refers to a field inapplicable to
 the object referred by the ref does not cause an error.  It
 returns an empty string instead.
 
 As a special case for the date-type fields, you may specify a format for
-the date by adding one of `:default`, `:relative`, `:short`, `:local`,
-`:iso8601`, `:rfc2822` or `:raw` to the end of the fieldname; e.g.
-`%(taggerdate:relative)`.
+the date by adding `:` followed by date format name (see the
+values the `--date` option to linkgit::git-rev-list[1] takes).
 
 
 EXAMPLES
index 52234987f9993a0b9f6d3f50ce9b26e074f3b58d..fa1510480a1d55236f2c467a0d4e901bbc60dc84 100644 (file)
@@ -63,8 +63,11 @@ automatic consolidation of packs.
 --prune=<date>::
        Prune loose objects older than date (default is 2 weeks ago,
        overridable by the config variable `gc.pruneExpire`).
-       --prune=all prunes loose objects regardless of their age.
-       --prune is on by default.
+       --prune=all prunes loose objects regardless of their age (do
+       not use --prune=all unless you know exactly what you are doing.
+       Unless the repository is quiescent, you will lose newly created
+       objects that haven't been anchored with the refs and end up
+       corrupting your repository).  --prune is on by default.
 
 --no-prune::
        Do not prune any loose objects.
index 31811f16bdaac49d01d38dcdea82bb191942b1cf..4a44d6da13cb749759f9d6164b88974ba55485ab 100644 (file)
@@ -160,12 +160,15 @@ OPTIONS
        For better compatibility with 'git diff', `--name-only` is a
        synonym for `--files-with-matches`.
 
--O [<pager>]::
---open-files-in-pager [<pager>]::
+-O[<pager>]::
+--open-files-in-pager[=<pager>]::
        Open the matching files in the pager (not the output of 'grep').
        If the pager happens to be "less" or "vi", and the user
        specified only one pattern, the first file is positioned at
-       the first match automatically.
+       the first match automatically. The `pager` argument is
+       optional; if specified, it must be stuck to the option
+       without a space. If `pager` is unspecified, the default pager
+       will be used (see `core.pager` in linkgit:git-config[1]).
 
 -z::
 --null::
index d6d9231b506b6a6918b4b7a342b33351eafd7e97..0ecd497c4de77ea2ed7afc5603b4bcbd8558d174 100644 (file)
@@ -67,7 +67,7 @@ OPTIONS
 --trim-empty::
        If the <value> part of any trailer contains only whitespace,
        the whole trailer will be removed from the resulting message.
-       This apply to existing trailers as well as new trailers.
+       This applies to existing trailers as well as new trailers.
 
 --trailer <token>[(=|:)<value>]::
        Specify a (<token>, <value>) pair that should be applied as a
index 97b9993ee8c2aeef504e96a041424e1d396a75d8..03f958029ad7a35ec29f111217cd76ba21de9b5e 100644 (file)
@@ -185,10 +185,10 @@ log.date::
        dates like `Sat May 8 19:35:34 2010 -0500`.
 
 log.follow::
-       If a single <path> is given to git log, it will act as
-       if the `--follow` option was also used.  This has the same
-       limitations as `--follow`, i.e. it cannot be used to follow
-       multiple files and does not work well on non-linear history.
+       If `true`, `git log` will act as if the `--follow` option was used when
+       a single <path> is given.  This has the same limitations as `--follow`,
+       i.e. it cannot be used to follow multiple files and does not work well
+       on non-linear history.
 
 log.showRoot::
        If `false`, `git log` and related commands will not treat the
index 2e22915eb857bd960f09afc725107198642bfd22..d510c05e11d3cbfb7fd1393331306e70bb7645db 100644 (file)
@@ -9,7 +9,7 @@ git-ls-remote - List references in a remote repository
 SYNOPSIS
 --------
 [verse]
-'git ls-remote' [--heads] [--tags]  [-u <exec> | --upload-pack <exec>]
+'git ls-remote' [--heads] [--tags]  [--upload-pack=<exec>]
              [--exit-code] <repository> [<refs>...]
 
 DESCRIPTION
@@ -29,7 +29,6 @@ OPTIONS
        both, references stored in refs/heads and refs/tags are
        displayed.
 
--u <exec>::
 --upload-pack=<exec>::
        Specify the full path of 'git-upload-pack' on the remote
        host. This allows listing references from repositories accessed via
index a62d6729b94963787ad435c486e1dc419c776012..07f7295ec8b603fcbd907e780d4ed1109ac5b358 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
-       [-s <strategy>] [-X <strategy-option>] [-S[<key-id>]]
+       [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
        [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...]
 'git merge' <msg> HEAD <commit>...
 'git merge' --abort
@@ -67,7 +67,9 @@ include::merge-options.txt[]
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign the resulting merge commit.
+       GPG-sign the resulting merge commit. The `keyid` argument is
+       optional and defaults to the committer identity; if specified,
+       it must be stuck to the option without a space.
 
 -m <msg>::
        Set the commit message to be used for the merge commit (in
index a9a916f360ec9efb8a801acc982ba39a38f41cf5..8de349968a3be4ea7e65a4a254041b0f0fb63347 100644 (file)
@@ -162,7 +162,9 @@ OPTIONS
 --ref <ref>::
        Manipulate the notes tree in <ref>.  This overrides
        'GIT_NOTES_REF' and the "core.notesRef" configuration.  The ref
-       is taken to be in `refs/notes/` if it is not qualified.
+       specifies the full refname when it begins with `refs/notes/`; when it
+       begins with `notes/`, `refs/` and otherwise `refs/notes/` is prefixed
+       to form a full name of the ref.
 
 --ignore-missing::
        Do not consider it an error to request removing notes from an
index 82aa5d60736ccc75510c19ddec2da9eb4c5611a8..c3ff7d0d9b1a62709128c985bcc7e8943756949a 100644 (file)
@@ -510,6 +510,45 @@ git-p4.useClientSpec::
        option '--use-client-spec'.  See the "CLIENT SPEC" section above.
        This variable is a boolean, not the name of a p4 client.
 
+git-p4.pathEncoding::
+       Perforce keeps the encoding of a path as given by the originating OS.
+       Git expects paths encoded as UTF-8. Use this config to tell git-p4
+       what encoding Perforce had used for the paths. This encoding is used
+       to transcode the paths to UTF-8. As an example, Perforce on Windows
+       often uses “cp1252” to encode path names.
+
+git-p4.largeFileSystem::
+       Specify the system that is used for large (binary) files. Please note
+       that large file systems do not support the 'git p4 submit' command.
+       Only Git LFS [1] is implemented right now. Download
+       and install the Git LFS command line extension to use this option
+       and configure it like this:
++
+-------------
+git config       git-p4.largeFileSystem GitLFS
+-------------
++
+       [1] https://git-lfs.github.com/
+
+git-p4.largeFileExtensions::
+       All files matching a file extension in the list will be processed
+       by the large file system. Do not prefix the extensions with '.'.
+
+git-p4.largeFileThreshold::
+       All files with an uncompressed size exceeding the threshold will be
+       processed by the large file system. By default the threshold is
+       defined in bytes. Add the suffix k, m, or g to change the unit.
+
+git-p4.largeFileCompressedThreshold::
+       All files with a compressed size exceeding the threshold will be
+       processed by the large file system. This option might slow down
+       your clone/sync process. By default the threshold is defined in
+       bytes. Add the suffix k, m, or g to change the unit.
+
+git-p4.largeFilePush::
+       Boolean variable which defines if large files are automatically
+       pushed to a server.
+
 Submit variables
 ~~~~~~~~~~~~~~~~
 git-p4.detectRenames::
index d64388cb8e454be17e4c20caf95897a71619c11f..ff633b0db7d54d8db12c4af248fc2bd3939b6f9c 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>]
+               [--series <file>]
 
 
 DESCRIPTION
@@ -42,13 +43,19 @@ OPTIONS
        information can be found in the patch description.
 
 --patches <dir>::
-       The directory to find the quilt patches and the
-       quilt series file.
+       The directory to find the quilt patches.
 +
 The default for the patch directory is patches
 or the value of the $QUILT_PATCHES environment
 variable.
 
+--series <file>::
+       The quilt series file.
++
+The default for the series file is <patches>/series
+or the value of the $QUILT_SERIES environment
+variable.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index ca039546a463bee511ca3a1c7d301689fb56a412..6cca8bb51dcabd47474ad63d908d7ec5d06d8703 100644 (file)
@@ -294,7 +294,9 @@ which makes little sense.
 
 -S[<keyid>]::
 --gpg-sign[=<keyid>]::
-       GPG-sign commits.
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 -q::
 --quiet::
@@ -432,7 +434,8 @@ If the '--autosquash' option is enabled by default using the
 configuration variable `rebase.autoSquash`, this option can be
 used to override and disable this setting.
 
---[no-]autostash::
+--autostash::
+--no-autostash::
        Automatically create a temporary stash before the operation
        begins, and apply it after the operation ends.  This means
        that you can run rebase on a dirty worktree.  However, use
index 4c6d6de7b77c2f1a70e2335fed0e65e956f99c6f..3c9bf45829e084a2309117b44520ad4275e9d4d7 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
 'git remote remove' <name>
 'git remote set-head' <name> (-a | --auto | -d | --delete | <branch>)
 'git remote set-branches' [--add] <name> <branch>...
+'git remote get-url' [--push] [--all] <name>
 'git remote set-url' [--push] <name> <newurl> [<oldurl>]
 'git remote set-url --add' [--push] <name> <newurl>
 'git remote set-url --delete' [--push] <name> <url>
@@ -131,6 +132,15 @@ The named branches will be interpreted as if specified with the
 With `--add`, instead of replacing the list of currently tracked
 branches, adds to that list.
 
+'get-url'::
+
+Retrieves the URLs for a remote. Configurations for `insteadOf` and
+`pushInsteadOf` are expanded here. By default, only the first URL is listed.
++
+With '--push', push URLs are queried rather than fetch URLs.
++
+With '--all', all URLs for the remote will be listed.
+
 'set-url'::
 
 Changes URLs for the remote. Sets first URL for remote <name> that matches
index 7b49c85347ec583c4d9849a3f0bc878d776e9519..ef22f1775b634812a6d0595ca3d88ce0b0c51506 100644 (file)
@@ -45,7 +45,7 @@ SYNOPSIS
             [ --regexp-ignore-case | -i ]
             [ --extended-regexp | -E ]
             [ --fixed-strings | -F ]
-            [ --date=(local|relative|default|iso|iso-strict|rfc|short) ]
+            [ --date=<format>]
             [ [ --objects | --objects-edge | --objects-edge-aggressive ]
               [ --unpacked ] ]
             [ --pretty | --header ]
index cceb5f2f7fa0c41174215901a091158161d89bc3..b15139ffdcda488c9e2314540885965723292a48 100644 (file)
@@ -8,7 +8,7 @@ git-revert - Revert some existing commits
 SYNOPSIS
 --------
 [verse]
-'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<key-id>]] <commit>...
+'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<keyid>]] <commit>...
 'git revert' --continue
 'git revert' --quit
 'git revert' --abort
@@ -80,9 +80,11 @@ more details.
 This is useful when reverting more than one commits'
 effect to your index in a row.
 
--S[<key-id>]::
---gpg-sign[=<key-id>]::
-       GPG-sign commits.
+-S[<keyid>]::
+--gpg-sign[=<keyid>]::
+       GPG-sign commits. The `keyid` argument is optional and
+       defaults to the committer identity; if specified, it must be
+       stuck to the option without a space.
 
 -s::
 --signoff::
index 375213fe463fa3cd9cb7644cff60ee656c13dcd8..92df596e5fe9757fee399078e05e3ba44196fda3 100644 (file)
@@ -95,6 +95,8 @@ show [<stash>]::
        shows the latest one. By default, the command shows the diffstat, but
        it will accept any format known to 'git diff' (e.g., `git stash show
        -p stash@{1}` to view the second most recent stash in patch form).
+       You can use stash.showStat and/or stash.showPatch config variables
+       to change the default behavior.
 
 pop [--index] [-q|--quiet] [<stash>]::
 
index 335f3123353482cfd708420dac3b766f181e89ff..e1e8f57cdd217b43b9b04bc54381e9b155d9cbde 100644 (file)
@@ -53,8 +53,9 @@ OPTIONS
 --untracked-files[=<mode>]::
        Show untracked files.
 +
-The mode parameter is optional (defaults to 'all'), and is used to
-specify the handling of untracked files.
+The mode parameter is used to specify the handling of untracked files.
+It is optional: it defaults to 'all', and if specified, it must be
+stuck to the option (e.g. `-uno`, but not `-u no`).
 +
 The possible options are:
 +
index 84f6496bf228454acaa04e570f7857ba1975cda4..7220e5eca1bddfcaf9828522c7f04ab78418874e 100644 (file)
@@ -9,11 +9,12 @@ git-tag - Create, list, delete or verify a tag object signed with GPG
 SYNOPSIS
 --------
 [verse]
-'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
+'git tag' [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>]
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
-       [--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]
+       [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
+       [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -24,19 +25,19 @@ to delete, list or verify tags.
 
 Unless `-f` is given, the named tag must not yet exist.
 
-If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
+If one of `-a`, `-s`, or `-u <keyid>` is passed, the command
 creates a 'tag' object, and requires a tag message.  Unless
 `-m <msg>` or `-F <file>` is given, an editor is started for the user to type
 in the tag message.
 
-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`
+If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <keyid>`
 are absent, `-a` is implied.
 
 Otherwise just a tag reference for the SHA-1 object name of the commit object is
 created (i.e. a lightweight tag).
 
 A GnuPG signed tag object will be created when `-s` or `-u
-<key-id>` is used.  When `-u <key-id>` is not used, the
+<keyid>` is used.  When `-u <keyid>` is not used, the
 committer identity for the current user is used to find the
 GnuPG key for signing.         The configuration variable `gpg.program`
 is used to specify custom GnuPG binary.
@@ -63,8 +64,8 @@ OPTIONS
 --sign::
        Make a GPG-signed tag, using the default e-mail address's key.
 
--u <key-id>::
---local-user=<key-id>::
+-u <keyid>::
+--local-user=<keyid>::
        Make a GPG-signed tag, using the given key.
 
 -f::
@@ -94,14 +95,16 @@ OPTIONS
        using fnmatch(3)).  Multiple patterns may be given; if any of
        them matches, the tag is shown.
 
---sort=<type>::
-       Sort in a specific order. Supported type is "refname"
-       (lexicographic order), "version:refname" or "v:refname" (tag
+--sort=<key>::
+       Sort based on the key given.  Prefix `-` to sort in
+       descending order of the value. You may use the --sort=<key> option
+       multiple times, in which case the last key becomes the primary
+       key. Also supports "version:refname" or "v:refname" (tag
        names are treated as versions). The "version:refname" sort
        order can also be affected by the
-       "versionsort.prereleaseSuffix" configuration variable. Prepend
-       "-" to reverse sort order. When this option is not given, the
-       sort order defaults to the value configured for the 'tag.sort'
+       "versionsort.prereleaseSuffix" configuration variable.
+       The keys supported are the same as those in `git for-each-ref`.
+       Sort order defaults to the value configured for the 'tag.sort'
        variable if it exists, or lexicographic order otherwise. See
        linkgit:git-config[1].
 
@@ -125,14 +128,14 @@ This option is only applicable when listing tags without annotation lines.
        Use the given tag message (instead of prompting).
        If multiple `-m` options are given, their values are
        concatenated as separate paragraphs.
-       Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
+       Implies `-a` if none of `-a`, `-s`, or `-u <keyid>`
        is given.
 
 -F <file>::
 --file=<file>::
        Take the tag message from the given file.  Use '-' to
        read the message from the standard input.
-       Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
+       Implies `-a` if none of `-a`, `-s`, or `-u <keyid>`
        is given.
 
 --cleanup=<mode>::
@@ -156,6 +159,16 @@ This option is only applicable when listing tags without annotation lines.
        The object that the new tag will refer to, usually a commit.
        Defaults to HEAD.
 
+<format>::
+       A string that interpolates `%(fieldname)` from the object
+       pointed at by a ref being shown.  The format is the same as
+       that of linkgit:git-for-each-ref[1].  When unspecified,
+       defaults to `%(refname:short)`.
+
+--[no-]merged [<commit>]::
+       Only list tags whose tips are reachable, or not reachable
+       if '--no-merged' is used, from the specified commit ('HEAD'
+       if not specified).
 
 CONFIGURATION
 -------------
@@ -166,7 +179,7 @@ it in the repository configuration as follows:
 
 -------------------------------------
 [user]
-    signingKey = <gpg-key-id>
+    signingKey = <gpg-keyid>
 -------------------------------------
 
 
index 1a426311175941f66efa1865f3a03c63956060fe..4585103f996c0e68753520ad5064a1a9879054c8 100644 (file)
@@ -43,9 +43,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.6.1/git.html[documentation for release 2.6.1]
+* link:v2.6.2/git.html[documentation for release 2.6.2]
 
 * release notes for
+  link:RelNotes/2.6.2.txt[2.6.2],
   link:RelNotes/2.6.1.txt[2.6.1],
   link:RelNotes/2.6.0.txt[2.6].
 
index 473623d6318a859c9ed2cf600222ea6cb4a25d4c..79a1948a0bd52c778dcfccb253f9f2508b99f27e 100644 (file)
@@ -82,12 +82,12 @@ PATTERN FORMAT
 
  - An optional prefix "`!`" which negates the pattern; any
    matching file excluded by a previous pattern will become
-   included again. It is not possible to re-include a file if a parent
-   directory of that file is excluded. Git doesn't list excluded
-   directories for performance reasons, so any patterns on contained
-   files have no effect, no matter where they are defined.
+   included again.
    Put a backslash ("`\`") in front of the first "`!`" for patterns
    that begin with a literal "`!`", for example, "`\!important!.txt`".
+   It is possible to re-include a file if a parent directory of that
+   file is excluded if certain conditions are met. See section NOTES
+   for detail.
 
  - If the pattern ends with a slash, it is removed for the
    purpose of the following description, but it would only find
@@ -141,6 +141,21 @@ not tracked by Git remain untracked.
 To stop tracking a file that is currently tracked, use
 'git rm --cached'.
 
+To re-include files or directories when their parent directory is
+excluded, the following conditions must be met:
+
+ - The rules to exclude a directory and re-include a subset back must
+   be in the same .gitignore file.
+
+ - The directory part in the re-include rules must be literal (i.e. no
+   wildcards)
+
+ - The rules to exclude the parent directory must not end with a
+   trailing slash.
+
+ - The rules to exclude the parent directory must have at least one
+   slash.
+
 EXAMPLES
 --------
 
index 8c6478b2f2cab195dcc07f47cfc59cbace286fda..e225974253833c97980e5ff190e291c8323757c9 100644 (file)
@@ -413,8 +413,9 @@ exclude;;
 
 [[def_per_worktree_ref]]per-worktree ref::
        Refs that are per-<<def_working_tree,worktree>>, rather than
-       global.  This is presently only <<def_HEAD,HEAD>>, but might
-       later include other unusual refs.
+       global.  This is presently only <<def_HEAD,HEAD>> and any refs
+       that start with `refs/bisect/`, but might later include other
+       unusual refs.
 
 [[def_pseudoref]]pseudoref::
        Pseudorefs are a class of files under `$GIT_DIR` which behave
index 8d6c5cec4c5edc904a5f2d7595fd8943457f550d..4b659ac1a6a6c1b57d0cadc5cd69ce6b69525cc1 100644 (file)
@@ -55,8 +55,9 @@ By default, the notes shown are from the notes refs listed in the
 environment overrides). See linkgit:git-config[1] for more details.
 +
 With an optional '<ref>' argument, show this notes ref instead of the
-default notes ref(s). The ref is taken to be in `refs/notes/` if it
-is not qualified.
+default notes ref(s). The ref specifies the full refname when it begins
+with `refs/notes/`; when it begins with `notes/`, `refs/` and otherwise
+`refs/notes/` is prefixed to form a full name of the ref.
 +
 Multiple --notes options can be combined to control which notes are
 being displayed. Examples: "--notes=foo" will show only notes from
index f1c52208f08c3dc9f50e8d18f546a96276a47fec..4f009d44240e3725e25bc8a9a3acd999d69cc487 100644 (file)
@@ -701,15 +701,19 @@ include::pretty-options.txt[]
 --relative-date::
        Synonym for `--date=relative`.
 
---date=(relative|local|default|iso|iso-strict|rfc|short|raw)::
+--date=<format>::
        Only takes effect for dates shown in human-readable format, such
        as when using `--pretty`. `log.date` config variable sets a default
-       value for the log command's `--date` option.
+       value for the log command's `--date` option. By default, dates
+       are shown in the original time zone (either committer's or
+       author's). If `-local` is appended to the format (e.g.,
+       `iso-local`), the user's local time zone is used instead.
 +
 `--date=relative` shows dates relative to the current time,
-e.g. ``2 hours ago''.
+e.g. ``2 hours ago''. The `-local` option cannot be used with
+`--raw` or `--relative`.
 +
-`--date=local` shows timestamps in user's local time zone.
+`--date=local` is an alias for `--date=default-local`.
 +
 `--date=iso` (or `--date=iso8601`) shows timestamps in a ISO 8601-like format.
 The differences to the strict ISO 8601 format are:
@@ -732,10 +736,15 @@ format, often found in email messages.
 `--date=format:...` feeds the format `...` to your system `strftime`.
 Use `--date=format:%c` to show the date in your system locale's
 preferred format.  See the `strftime` manual for a complete list of
-format placeholders.
+format placeholders. When using `-local`, the correct syntax is
+`--date=format-local:...`.
 +
-`--date=default` shows timestamps in the original time zone
-(either committer's or author's).
+`--date=default` is the default format, and is similar to
+`--date=rfc2822`, with a few exceptions:
+
+       - there is no comma after the day-of-week
+
+       - the time zone is omitted when the local time zone is used
 
 ifdef::git-rev-list[]
 --header::
index 68978f53381baf1b179b70f22e97084893d5c227..1b7987e73767f1c71d777909f3b275b5976419aa 100644 (file)
@@ -3424,7 +3424,7 @@ just missing one particular blob version.
 
 [[the-index]]
 The index
------------
+---------
 
 The index is a binary file (generally kept in `.git/index`) containing a
 sorted list of path names, each with permissions and the SHA-1 of a blob
index e1aba8533f5b6d63ac01fb06961843a8f32f75e9..94e40c400078794f48d36236bd105a2d3be59d7c 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.6.1
+DEF_VER=v2.6.0.GIT
 
 LF='
 '
index 8d5df7ea1ee672753e2f29302ef9a1a7db77638e..0d9f5dddbc68e2f7fc2c5c5a2f27f2c30466a32e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -74,8 +74,6 @@ all::
 # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
 # it specifies.
 #
-# Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
-#
 # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
 # d_type in struct dirent (Cygwin 1.5, fixed in Cygwin 1.7).
 #
@@ -375,6 +373,9 @@ ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS)
 ALL_LDFLAGS = $(LDFLAGS)
 STRIP ?= strip
 
+# Create as necessary, replace existing, make ranlib unneeded.
+ARFLAGS = rcs
+
 # Among the variables below, these:
 #   gitexecdir
 #   template_dir
@@ -902,6 +903,7 @@ BUILTIN_OBJS += builtin/shortlog.o
 BUILTIN_OBJS += builtin/show-branch.o
 BUILTIN_OBJS += builtin/show-ref.o
 BUILTIN_OBJS += builtin/stripspace.o
+BUILTIN_OBJS += builtin/submodule--helper.o
 BUILTIN_OBJS += builtin/symbolic-ref.o
 BUILTIN_OBJS += builtin/tag.o
 BUILTIN_OBJS += builtin/unpack-file.o
@@ -1160,9 +1162,6 @@ endif
 ifdef NO_D_TYPE_IN_DIRENT
        BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
 endif
-ifdef NO_D_INO_IN_DIRENT
-       BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
-endif
 ifdef NO_GECOS_IN_PWENT
        BASIC_CFLAGS += -DNO_GECOS_IN_PWENT
 endif
@@ -1465,13 +1464,13 @@ endif
 QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
 QUIET_SUBDIR1  =
 
-ifneq ($(findstring $(MAKEFLAGS),w),w)
+ifneq ($(findstring w,$(MAKEFLAGS)),w)
 PRINT_DIR = --no-print-directory
 else # "make -w"
 NO_SUBDIR = :
 endif
 
-ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifneq ($(findstring s,$(MAKEFLAGS)),s)
 ifndef V
        QUIET_CC       = @echo '   ' CC $@;
        QUIET_AR       = @echo '   ' AR $@;
@@ -1995,13 +1994,13 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
 $(LIB_FILE): $(LIB_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
+       $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
+       $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 $(VCSSVN_LIB): $(VCSSVN_OBJS)
-       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $^
+       $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
 
 export DEFAULT_EDITOR DEFAULT_PAGER
 
index def6ebd43038cd537c2e34617d479d48cc40361c..3ba13ce25351c60c93d0c8ed78f207d9a591d517 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.6.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.7.0.txt
\ No newline at end of file
index 4965686e19cbb44b86d48363f8f3e289bebef94d..4dc5cf10a8533b1bfc3bd1483e91df115aa8f868 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -100,7 +100,7 @@ void NORETURN die_conclude_merge(void)
 {
        error(_("You have not concluded your merge (MERGE_HEAD exists)."));
        if (advice_resolve_conflict)
-               advise(_("Please, commit your changes before you can merge."));
+               advise(_("Please, commit your changes before merging."));
        die(_("Exiting because of unfinished merge."));
 }
 
index 0d1e6bd7542dd7c76d2f349de0d0238a8d1b55af..501ca97760b1f62ada876d372bd1abbbc41c6cd0 100644 (file)
@@ -167,21 +167,21 @@ static void prepare_header(struct archiver_args *args,
                           struct ustar_header *header,
                           unsigned int mode, unsigned long size)
 {
-       sprintf(header->mode, "%07o", mode & 07777);
-       sprintf(header->size, "%011lo", S_ISREG(mode) ? size : 0);
-       sprintf(header->mtime, "%011lo", (unsigned long) args->time);
+       xsnprintf(header->mode, sizeof(header->mode), "%07o", mode & 07777);
+       xsnprintf(header->size, sizeof(header->size), "%011lo", S_ISREG(mode) ? size : 0);
+       xsnprintf(header->mtime, sizeof(header->mtime), "%011lo", (unsigned long) args->time);
 
-       sprintf(header->uid, "%07o", 0);
-       sprintf(header->gid, "%07o", 0);
+       xsnprintf(header->uid, sizeof(header->uid), "%07o", 0);
+       xsnprintf(header->gid, sizeof(header->gid), "%07o", 0);
        strlcpy(header->uname, "root", sizeof(header->uname));
        strlcpy(header->gname, "root", sizeof(header->gname));
-       sprintf(header->devmajor, "%07o", 0);
-       sprintf(header->devminor, "%07o", 0);
+       xsnprintf(header->devmajor, sizeof(header->devmajor), "%07o", 0);
+       xsnprintf(header->devminor, sizeof(header->devminor), "%07o", 0);
 
        memcpy(header->magic, "ustar", 6);
        memcpy(header->version, "00", 2);
 
-       sprintf(header->chksum, "%07o", ustar_header_chksum(header));
+       snprintf(header->chksum, sizeof(header->chksum), "%07o", ustar_header_chksum(header));
 }
 
 static int write_extended_header(struct archiver_args *args,
@@ -193,7 +193,7 @@ static int write_extended_header(struct archiver_args *args,
        memset(&header, 0, sizeof(header));
        *header.typeflag = TYPEFLAG_EXT_HEADER;
        mode = 0100666;
-       sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+       xsnprintf(header.name, sizeof(header.name), "%s.paxheader", sha1_to_hex(sha1));
        prepare_header(args, &header, mode, size);
        write_blocked(&header, sizeof(header));
        write_blocked(buffer, size);
@@ -233,10 +233,10 @@ static int write_tar_entry(struct archiver_args *args,
                size_t rest = pathlen - plen - 1;
                if (plen > 0 && rest <= sizeof(header.name)) {
                        memcpy(header.prefix, path, plen);
-                               memcpy(header.name, path + plen + 1, rest);
+                       memcpy(header.name, path + plen + 1, rest);
                } else {
-                       sprintf(header.name, "%s.data",
-                               sha1_to_hex(sha1));
+                       xsnprintf(header.name, sizeof(header.name), "%s.data",
+                                 sha1_to_hex(sha1));
                        strbuf_append_ext_header(&ext_header, "path",
                                                 path, pathlen);
                }
@@ -259,8 +259,8 @@ static int write_tar_entry(struct archiver_args *args,
 
        if (S_ISLNK(mode)) {
                if (size > sizeof(header.linkname)) {
-                       sprintf(header.linkname, "see %s.paxheader",
-                               sha1_to_hex(sha1));
+                       xsnprintf(header.linkname, sizeof(header.linkname),
+                                 "see %s.paxheader", sha1_to_hex(sha1));
                        strbuf_append_ext_header(&ext_header, "linkpath",
                                                 buffer, size);
                } else
@@ -301,7 +301,7 @@ static int write_global_extended_header(struct archiver_args *args)
        memset(&header, 0, sizeof(header));
        *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
        mode = 0100666;
-       strcpy(header.name, "pax_global_header");
+       xsnprintf(header.name, sizeof(header.name), "pax_global_header");
        prepare_header(args, &header, mode, ext_header.len);
        write_blocked(&header, sizeof(header));
        write_blocked(ext_header.buf, ext_header.len);
index 01b0899b3f9027c5a392bd58667ed0e216d082ed..4ac86c837384c45c33dae141c3ff53dd37eb0fd5 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -171,13 +171,14 @@ static void queue_directory(const unsigned char *sha1,
                unsigned mode, int stage, struct archiver_context *c)
 {
        struct directory *d;
-       d = xmallocz(sizeof(*d) + base->len + 1 + strlen(filename));
+       size_t len = base->len + 1 + strlen(filename) + 1;
+       d = xmalloc(sizeof(*d) + len);
        d->up      = c->bottom;
        d->baselen = base->len;
        d->mode    = mode;
        d->stage   = stage;
        c->bottom  = d;
-       d->len = sprintf(d->path, "%.*s%s/", (int)base->len, base->buf, filename);
+       d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename);
        hashcpy(d->oid.hash, sha1);
 }
 
index 041a13d093a21597c60d799c0f6943998f2d6a8a..053d1a2ab91a6c41a0fe957569ff905d12b00bee 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -730,6 +730,11 @@ static void handle_bad_merge_base(void)
                                "This means the bug has been fixed "
                                "between %s and [%s].\n",
                                bad_hex, bad_hex, good_hex);
+               } else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
+                       fprintf(stderr, "The merge base %s is new.\n"
+                               "The property has changed "
+                               "between %s and [%s].\n",
+                               bad_hex, bad_hex, good_hex);
                } else {
                        fprintf(stderr, "The merge base %s is %s.\n"
                                "This means the first '%s' commit is "
@@ -762,11 +767,11 @@ static void handle_skipped_merge_base(const unsigned char *mb)
 }
 
 /*
- * "check_merge_bases" checks that merge bases are not "bad".
+ * "check_merge_bases" checks that merge bases are not "bad" (or "new").
  *
- * - If one is "bad", it means the user assumed something wrong
+ * - If one is "bad" (or "new"), it means the user assumed something wrong
  * and we must exit with a non 0 error code.
- * - If one is "good", that's good, we have nothing to do.
+ * - If one is "good" (or "old"), that's good, we have nothing to do.
  * - If one is "skipped", we can't know but we should warn.
  * - If we don't know, we should check it out and ask the user to test.
  */
index 79aaf0afe8912d154573df3baa6ea5c28f8097d6..6b95006a0a38d8bec522342fb72a5edeb0af7f07 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -120,6 +120,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_status(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
+extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
index 4f77e07b9549f8ff144d770fb0b66e9124ae4dc6..3bd4fd701bdccab91f1343b371cf19e822c14db5 100644 (file)
@@ -2208,6 +2208,17 @@ enum resume_mode {
        RESUME_ABORT
 };
 
+static int git_am_config(const char *k, const char *v, void *cb)
+{
+       int status;
+
+       status = git_gpg_config(k, v, NULL);
+       if (status)
+               return status;
+
+       return git_default_config(k, v, NULL);
+}
+
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
        struct am_state state;
@@ -2308,7 +2319,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       git_config(git_default_config, NULL);
+       git_config(git_am_config, NULL);
 
        am_state_init(&state, git_path("rebase-apply"));
 
index 4aa53f7fd87d5fd23c0929360b6746f48e8e3e73..deb1364fa81452d236a224109a99b28a034f9a9a 100644 (file)
@@ -77,8 +77,7 @@ static enum ws_ignore {
 
 
 static const char *patch_input_file;
-static const char *root;
-static int root_len;
+static struct strbuf root = STRBUF_INIT;
 static int read_stdin = 1;
 static int options;
 
@@ -494,8 +493,8 @@ static char *find_name_gnu(const char *line, const char *def, int p_value)
        }
 
        strbuf_remove(&name, 0, cp - name.buf);
-       if (root)
-               strbuf_insert(&name, 0, root, root_len);
+       if (root.len)
+               strbuf_insert(&name, 0, root.buf, root.len);
        return squash_slash(strbuf_detach(&name, NULL));
 }
 
@@ -697,11 +696,8 @@ static char *find_name_common(const char *line, const char *def,
                        return squash_slash(xstrdup(def));
        }
 
-       if (root) {
-               char *ret = xmalloc(root_len + len + 1);
-               strcpy(ret, root);
-               memcpy(ret + root_len, start, len);
-               ret[root_len + len] = '\0';
+       if (root.len) {
+               char *ret = xstrfmt("%s%.*s", root.buf, len, start);
                return squash_slash(ret);
        }
 
@@ -1277,8 +1273,8 @@ static int parse_git_header(const char *line, int len, unsigned int size, struct
         * the default name from the header.
         */
        patch->def_name = git_header_name(line, len);
-       if (patch->def_name && root) {
-               char *s = xstrfmt("%s%s", root, patch->def_name);
+       if (patch->def_name && root.len) {
+               char *s = xstrfmt("%s%s", root.buf, patch->def_name);
                free(patch->def_name);
                patch->def_name = s;
        }
@@ -4501,14 +4497,9 @@ static int option_parse_whitespace(const struct option *opt,
 static int option_parse_directory(const struct option *opt,
                                  const char *arg, int unset)
 {
-       root_len = strlen(arg);
-       if (root_len && arg[root_len - 1] != '/') {
-               char *new_root;
-               root = new_root = xmalloc(root_len + 2);
-               strcpy(new_root, arg);
-               strcpy(new_root + root_len++, "/");
-       } else
-               root = arg;
+       strbuf_reset(&root);
+       strbuf_addstr(&root, arg);
+       strbuf_complete(&root, '/');
        return 0;
 }
 
index 245d253d041021429265eb81fb008e528613af34..6fc7bff9a37e7b9acf24eb65546dfb8e3c87c8fb 100644 (file)
@@ -459,12 +459,13 @@ static void queue_blames(struct scoreboard *sb, struct origin *porigin,
 static struct origin *make_origin(struct commit *commit, const char *path)
 {
        struct origin *o;
-       o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+       size_t pathlen = strlen(path) + 1;
+       o = xcalloc(1, sizeof(*o) + pathlen);
        o->commit = commit;
        o->refcnt = 1;
        o->next = commit->util;
        commit->util = o;
-       strcpy(o->path, path);
+       memcpy(o->path, path, pathlen); /* includes NUL */
        return o;
 }
 
@@ -1371,8 +1372,15 @@ static void pass_whole_blame(struct scoreboard *sb,
  */
 static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
 {
-       if (!reverse)
+       if (!reverse) {
+               if (revs->first_parent_only &&
+                   commit->parents &&
+                   commit->parents->next) {
+                       free_commit_list(commit->parents->next);
+                       commit->parents->next = NULL;
+               }
                return commit->parents;
+       }
        return lookup_decoration(&revs->children, &commit->object);
 }
 
@@ -1872,9 +1880,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
        int cnt;
        const char *cp;
        struct origin *suspect = ent->suspect;
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
 
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       sha1_to_hex_r(hex, suspect->commit->object.sha1);
        printf("%s %d %d %d\n",
               hex,
               ent->s_lno + 1,
@@ -1910,11 +1918,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
        const char *cp;
        struct origin *suspect = ent->suspect;
        struct commit_info ci;
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
 
        get_commit_info(suspect->commit, &ci, 1);
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       sha1_to_hex_r(hex, suspect->commit->object.sha1);
 
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
@@ -2605,7 +2613,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                   fewer display columns. */
                blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
                break;
-       case DATE_LOCAL:
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
@@ -2685,6 +2692,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        }
        else if (contents_from)
                die("--contents and --children do not blend well.");
+       else if (revs.first_parent_only)
+               die("combining --first-parent and --reverse is not supported");
        else {
                final_commit_name = prepare_initial(&sb);
                sb.commits.compare = compare_commits_by_reverse_commit_date;
index ff05869949b7c5c4cd0034e7a0ba54e2adeae6b4..01f9530822d9f4c060e2bcfa6d80b03618f526d9 100644 (file)
 #include "column.h"
 #include "utf8.h"
 #include "wt-status.h"
+#include "ref-filter.h"
 
 static const char * const builtin_branch_usage[] = {
        N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
        N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
        N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
        N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
+       N_("git branch [<options>] [-r | -a] [--points-at]"),
        NULL
 };
 
-#define REF_LOCAL_BRANCH    0x01
-#define REF_REMOTE_BRANCH   0x02
-
 static const char *head;
 static unsigned char head_sha1[20];
 
@@ -52,13 +51,6 @@ enum color_branch {
        BRANCH_COLOR_UPSTREAM = 5
 };
 
-static enum merge_filter {
-       NO_FILTER = 0,
-       SHOW_NOT_MERGED,
-       SHOW_MERGED
-} merge_filter;
-static unsigned char merge_filter_ref[20];
-
 static struct string_list output = STRING_LIST_INIT_DUP;
 static unsigned int colopts;
 
@@ -121,7 +113,7 @@ static int branch_merged(int kind, const char *name,
        void *reference_name_to_free = NULL;
        int merged;
 
-       if (kind == REF_LOCAL_BRANCH) {
+       if (kind == FILTER_REFS_BRANCHES) {
                struct branch *branch = branch_get(name);
                const char *upstream = branch_get_upstream(branch, NULL);
                unsigned char sha1[20];
@@ -199,14 +191,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        struct strbuf bname = STRBUF_INIT;
 
        switch (kinds) {
-       case REF_REMOTE_BRANCH:
+       case FILTER_REFS_REMOTES:
                fmt = "refs/remotes/%s";
                /* For subsequent UI messages */
                remote_branch = 1;
 
                force = 1;
                break;
-       case REF_LOCAL_BRANCH:
+       case FILTER_REFS_BRANCHES:
                fmt = "refs/heads/%s";
                break;
        default:
@@ -223,7 +215,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                int flags = 0;
 
                strbuf_branchname(&bname, argv[i]);
-               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
+               if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {
                        error(_("Cannot delete the branch '%s' "
                              "which you are currently on."), bname.buf);
                        ret = 1;
@@ -279,147 +271,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        return(ret);
 }
 
-struct ref_item {
-       char *name;
-       char *dest;
-       unsigned int kind, width;
-       struct commit *commit;
-       int ignore;
-};
-
-struct ref_list {
-       struct rev_info revs;
-       int index, alloc, maxwidth, verbose, abbrev;
-       struct ref_item *list;
-       struct commit_list *with_commit;
-       int kinds;
-};
-
-static char *resolve_symref(const char *src, const char *prefix)
-{
-       unsigned char sha1[20];
-       int flag;
-       const char *dst;
-
-       dst = resolve_ref_unsafe(src, 0, sha1, &flag);
-       if (!(dst && (flag & REF_ISSYMREF)))
-               return NULL;
-       if (prefix)
-               skip_prefix(dst, prefix, &dst);
-       return xstrdup(dst);
-}
-
-struct append_ref_cb {
-       struct ref_list *ref_list;
-       const char **pattern;
-       int ret;
-};
-
-static int match_patterns(const char **pattern, const char *refname)
-{
-       if (!*pattern)
-               return 1; /* no pattern always matches */
-       while (*pattern) {
-               if (!wildmatch(*pattern, refname, 0, NULL))
-                       return 1;
-               pattern++;
-       }
-       return 0;
-}
-
-static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data)
-{
-       struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
-       struct ref_list *ref_list = cb->ref_list;
-       struct ref_item *newitem;
-       struct commit *commit;
-       int kind, i;
-       const char *prefix, *orig_refname = refname;
-
-       static struct {
-               int kind;
-               const char *prefix;
-       } ref_kind[] = {
-               { REF_LOCAL_BRANCH, "refs/heads/" },
-               { REF_REMOTE_BRANCH, "refs/remotes/" },
-       };
-
-       /* Detect kind */
-       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
-               prefix = ref_kind[i].prefix;
-               if (skip_prefix(refname, prefix, &refname)) {
-                       kind = ref_kind[i].kind;
-                       break;
-               }
-       }
-       if (ARRAY_SIZE(ref_kind) <= i)
-               return 0;
-
-       /* Don't add types the caller doesn't want */
-       if ((kind & ref_list->kinds) == 0)
-               return 0;
-
-       if (!match_patterns(cb->pattern, refname))
-               return 0;
-
-       commit = NULL;
-       if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
-               commit = lookup_commit_reference_gently(oid->hash, 1);
-               if (!commit) {
-                       cb->ret = error(_("branch '%s' does not point at a commit"), refname);
-                       return 0;
-               }
-
-               /* Filter with with_commit if specified */
-               if (!is_descendant_of(commit, ref_list->with_commit))
-                       return 0;
-
-               if (merge_filter != NO_FILTER)
-                       add_pending_object(&ref_list->revs,
-                                          (struct object *)commit, refname);
-       }
-
-       ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc);
-
-       /* Record the new item */
-       newitem = &(ref_list->list[ref_list->index++]);
-       newitem->name = xstrdup(refname);
-       newitem->kind = kind;
-       newitem->commit = commit;
-       newitem->width = utf8_strwidth(refname);
-       newitem->dest = resolve_symref(orig_refname, prefix);
-       newitem->ignore = 0;
-       /* adjust for "remotes/" */
-       if (newitem->kind == REF_REMOTE_BRANCH &&
-           ref_list->kinds != REF_REMOTE_BRANCH)
-               newitem->width += 8;
-       if (newitem->width > ref_list->maxwidth)
-               ref_list->maxwidth = newitem->width;
-
-       return 0;
-}
-
-static void free_ref_list(struct ref_list *ref_list)
-{
-       int i;
-
-       for (i = 0; i < ref_list->index; i++) {
-               free(ref_list->list[i].name);
-               free(ref_list->list[i].dest);
-       }
-       free(ref_list->list);
-}
-
-static int ref_cmp(const void *r1, const void *r2)
-{
-       struct ref_item *c1 = (struct ref_item *)(r1);
-       struct ref_item *c2 = (struct ref_item *)(r2);
-
-       if (c1->kind != c2->kind)
-               return c1->kind - c2->kind;
-       return strcmp(c1->name, c2->name);
-}
-
 static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
                int show_upstream_ref)
 {
@@ -482,8 +333,8 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
        free(ref);
 }
 
-static void add_verbose_info(struct strbuf *out, struct ref_item *item,
-                            int verbose, int abbrev)
+static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
+                            struct ref_filter *filter, const char *refname)
 {
        struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
        const char *sub = _(" **** invalid ref ****");
@@ -494,32 +345,74 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item,
                sub = subject.buf;
        }
 
-       if (item->kind == REF_LOCAL_BRANCH)
-               fill_tracking_info(&stat, item->name, verbose > 1);
+       if (item->kind == FILTER_REFS_BRANCHES)
+               fill_tracking_info(&stat, refname, filter->verbose > 1);
 
        strbuf_addf(out, " %s %s%s",
-               find_unique_abbrev(item->commit->object.sha1, abbrev),
+               find_unique_abbrev(item->commit->object.sha1, filter->abbrev),
                stat.buf, sub);
        strbuf_release(&stat);
        strbuf_release(&subject);
 }
 
-static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
-                          int abbrev, int current, char *prefix)
+static char *get_head_description(void)
+{
+       struct strbuf desc = STRBUF_INIT;
+       struct wt_status_state state;
+       memset(&state, 0, sizeof(state));
+       wt_status_get_state(&state, 1);
+       if (state.rebase_in_progress ||
+           state.rebase_interactive_in_progress)
+               strbuf_addf(&desc, _("(no branch, rebasing %s)"),
+                           state.branch);
+       else if (state.bisect_in_progress)
+               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
+                           state.branch);
+       else if (state.detached_from) {
+               /* TRANSLATORS: make sure these match _("HEAD detached at ")
+                  and _("HEAD detached from ") in wt-status.c */
+               if (state.detached_at)
+                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
+                               state.detached_from);
+               else
+                       strbuf_addf(&desc, _("(HEAD detached from %s)"),
+                               state.detached_from);
+       }
+       else
+               strbuf_addstr(&desc, _("(no branch)"));
+       free(state.branch);
+       free(state.onto);
+       free(state.detached_from);
+       return strbuf_detach(&desc, NULL);
+}
+
+static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
+                                     struct ref_filter *filter, const char *remote_prefix)
 {
        char c;
+       int current = 0;
        int color;
        struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
-
-       if (item->ignore)
-               return;
+       const char *prefix = "";
+       const char *desc = item->refname;
+       char *to_free = NULL;
 
        switch (item->kind) {
-       case REF_LOCAL_BRANCH:
-               color = BRANCH_COLOR_LOCAL;
+       case FILTER_REFS_BRANCHES:
+               skip_prefix(desc, "refs/heads/", &desc);
+               if (!filter->detached && !strcmp(desc, head))
+                       current = 1;
+               else
+                       color = BRANCH_COLOR_LOCAL;
                break;
-       case REF_REMOTE_BRANCH:
+       case FILTER_REFS_REMOTES:
+               skip_prefix(desc, "refs/remotes/", &desc);
                color = BRANCH_COLOR_REMOTE;
+               prefix = remote_prefix;
+               break;
+       case FILTER_REFS_DETACHED_HEAD:
+               desc = to_free = get_head_description();
+               current = 1;
                break;
        default:
                color = BRANCH_COLOR_PLAIN;
@@ -532,8 +425,8 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
                color = BRANCH_COLOR_CURRENT;
        }
 
-       strbuf_addf(&name, "%s%s", prefix, item->name);
-       if (verbose) {
+       strbuf_addf(&name, "%s%s", prefix, desc);
+       if (filter->verbose) {
                int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
                strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
                            maxwidth + utf8_compensation, name.buf,
@@ -542,155 +435,82 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
                strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
                            name.buf, branch_get_color(BRANCH_COLOR_RESET));
 
-       if (item->dest)
-               strbuf_addf(&out, " -> %s", item->dest);
-       else if (verbose)
+       if (item->symref) {
+               skip_prefix(item->symref, "refs/remotes/", &desc);
+               strbuf_addf(&out, " -> %s", desc);
+       }
+       else if (filter->verbose)
                /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
-               add_verbose_info(&out, item, verbose, abbrev);
+               add_verbose_info(&out, item, filter, desc);
        if (column_active(colopts)) {
-               assert(!verbose && "--column and --verbose are incompatible");
+               assert(!filter->verbose && "--column and --verbose are incompatible");
                string_list_append(&output, out.buf);
        } else {
                printf("%s\n", out.buf);
        }
        strbuf_release(&name);
        strbuf_release(&out);
+       free(to_free);
 }
 
-static int calc_maxwidth(struct ref_list *refs)
-{
-       int i, w = 0;
-       for (i = 0; i < refs->index; i++) {
-               if (refs->list[i].ignore)
-                       continue;
-               if (refs->list[i].width > w)
-                       w = refs->list[i].width;
-       }
-       return w;
-}
-
-static char *get_head_description(void)
-{
-       struct strbuf desc = STRBUF_INIT;
-       struct wt_status_state state;
-       memset(&state, 0, sizeof(state));
-       wt_status_get_state(&state, 1);
-       if (state.rebase_in_progress ||
-           state.rebase_interactive_in_progress)
-               strbuf_addf(&desc, _("(no branch, rebasing %s)"),
-                           state.branch);
-       else if (state.bisect_in_progress)
-               strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
-                           state.branch);
-       else if (state.detached_from) {
-               /* TRANSLATORS: make sure these match _("HEAD detached at ")
-                  and _("HEAD detached from ") in wt-status.c */
-               if (state.detached_at)
-                       strbuf_addf(&desc, _("(HEAD detached at %s)"),
-                               state.detached_from);
-               else
-                       strbuf_addf(&desc, _("(HEAD detached from %s)"),
-                               state.detached_from);
-       }
-       else
-               strbuf_addstr(&desc, _("(no branch)"));
-       free(state.branch);
-       free(state.onto);
-       free(state.detached_from);
-       return strbuf_detach(&desc, NULL);
-}
-
-static void show_detached(struct ref_list *ref_list)
+static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
 {
-       struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
-
-       if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
-               struct ref_item item;
-               item.name = get_head_description();
-               item.width = utf8_strwidth(item.name);
-               item.kind = REF_LOCAL_BRANCH;
-               item.dest = NULL;
-               item.commit = head_commit;
-               item.ignore = 0;
-               if (item.width > ref_list->maxwidth)
-                       ref_list->maxwidth = item.width;
-               print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
-               free(item.name);
+       int i, max = 0;
+       for (i = 0; i < refs->nr; i++) {
+               struct ref_array_item *it = refs->items[i];
+               const char *desc = it->refname;
+               int w;
+
+               skip_prefix(it->refname, "refs/heads/", &desc);
+               skip_prefix(it->refname, "refs/remotes/", &desc);
+               w = utf8_strwidth(desc);
+
+               if (it->kind == FILTER_REFS_REMOTES)
+                       w += remote_bonus;
+               if (w > max)
+                       max = w;
        }
+       return max;
 }
 
-static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
+static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting)
 {
        int i;
-       struct append_ref_cb cb;
-       struct ref_list ref_list;
-
-       memset(&ref_list, 0, sizeof(ref_list));
-       ref_list.kinds = kinds;
-       ref_list.verbose = verbose;
-       ref_list.abbrev = abbrev;
-       ref_list.with_commit = with_commit;
-       if (merge_filter != NO_FILTER)
-               init_revisions(&ref_list.revs, NULL);
-       cb.ref_list = &ref_list;
-       cb.pattern = pattern;
-       cb.ret = 0;
-       for_each_rawref(append_ref, &cb);
-       if (merge_filter != NO_FILTER) {
-               struct commit *filter;
-               filter = lookup_commit_reference_gently(merge_filter_ref, 0);
-               if (!filter)
-                       die(_("object '%s' does not point to a commit"),
-                           sha1_to_hex(merge_filter_ref));
-
-               filter->object.flags |= UNINTERESTING;
-               add_pending_object(&ref_list.revs,
-                                  (struct object *) filter, "");
-               ref_list.revs.limited = 1;
-
-               if (prepare_revision_walk(&ref_list.revs))
-                       die(_("revision walk setup failed"));
-
-               for (i = 0; i < ref_list.index; i++) {
-                       struct ref_item *item = &ref_list.list[i];
-                       struct commit *commit = item->commit;
-                       int is_merged = !!(commit->object.flags & UNINTERESTING);
-                       item->ignore = is_merged != (merge_filter == SHOW_MERGED);
-               }
+       struct ref_array array;
+       int maxwidth = 0;
+       const char *remote_prefix = "";
 
-               for (i = 0; i < ref_list.index; i++) {
-                       struct ref_item *item = &ref_list.list[i];
-                       clear_commit_marks(item->commit, ALL_REV_FLAGS);
-               }
-               clear_commit_marks(filter, ALL_REV_FLAGS);
+       /*
+        * If we are listing more than just remote branches,
+        * then remote branches will have a "remotes/" prefix.
+        * We need to account for this in the width.
+        */
+       if (filter->kind != FILTER_REFS_REMOTES)
+               remote_prefix = "remotes/";
 
-               if (verbose)
-                       ref_list.maxwidth = calc_maxwidth(&ref_list);
-       }
+       memset(&array, 0, sizeof(array));
 
-       qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
-
-       detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached && match_patterns(pattern, "HEAD"))
-               show_detached(&ref_list);
-
-       for (i = 0; i < ref_list.index; i++) {
-               int current = !detached &&
-                       (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
-                       !strcmp(ref_list.list[i].name, head);
-               char *prefix = (kinds != REF_REMOTE_BRANCH &&
-                               ref_list.list[i].kind == REF_REMOTE_BRANCH)
-                               ? "remotes/" : "";
-               print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
-                              abbrev, current, prefix);
-       }
+       verify_ref_format("%(refname)%(symref)");
+       filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
 
-       free_ref_list(&ref_list);
+       if (filter->verbose)
+               maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
 
-       if (cb.ret)
-               error(_("some refs could not be read"));
+       /*
+        * If no sorting parameter is given then we default to sorting
+        * by 'refname'. This would give us an alphabetically sorted
+        * array with the 'HEAD' ref at the beginning followed by
+        * local branches 'refs/heads/...' and finally remote-tacking
+        * branches 'refs/remotes/...'.
+        */
+       if (!sorting)
+               sorting = ref_default_sorting();
+       ref_array_sort(sorting, &array);
+
+       for (i = 0; i < array.nr; i++)
+               format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
 
-       return cb.ret;
+       ref_array_clear(&array);
 }
 
 static void rename_branch(const char *oldname, const char *newname, int force)
@@ -746,20 +566,6 @@ static void rename_branch(const char *oldname, const char *newname, int force)
        strbuf_release(&newsection);
 }
 
-static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
-{
-       merge_filter = ((opt->long_name[0] == 'n')
-                       ? SHOW_NOT_MERGED
-                       : SHOW_MERGED);
-       if (unset)
-               merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
-       if (!arg)
-               arg = "HEAD";
-       if (get_sha1(arg, merge_filter_ref))
-               die(_("malformed object name %s"), arg);
-       return 0;
-}
-
 static const char edit_description[] = "BRANCH_DESCRIPTION";
 
 static int edit_branch_description(const char *branch_name)
@@ -799,17 +605,16 @@ static int edit_branch_description(const char *branch_name)
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, force = 0, list = 0;
-       int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0, edit_description = 0;
        int quiet = 0, unset_upstream = 0;
        const char *new_upstream = NULL;
        enum branch_track track;
-       int kinds = REF_LOCAL_BRANCH;
-       struct commit_list *with_commit = NULL;
+       struct ref_filter filter;
+       static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 
        struct option options[] = {
                OPT_GROUP(N_("Generic options")),
-               OPT__VERBOSE(&verbose,
+               OPT__VERBOSE(&filter.verbose,
                        N_("show hash and subject, give twice for upstream branch")),
                OPT__QUIET(&quiet, N_("suppress informational messages")),
                OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
@@ -819,25 +624,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"),
                OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"),
                OPT__COLOR(&branch_use_color, N_("use colored output")),
-               OPT_SET_INT('r', "remotes",     &kinds, N_("act on remote-tracking branches"),
-                       REF_REMOTE_BRANCH),
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
-                       N_("print only branches that contain the commit"),
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
-                       N_("print only branches that contain the commit"),
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t) "HEAD",
-               },
-               OPT__ABBREV(&abbrev),
+               OPT_SET_INT('r', "remotes",     &filter.kind, N_("act on remote-tracking branches"),
+                       FILTER_REFS_REMOTES),
+               OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
+               OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
+               OPT__ABBREV(&filter.abbrev),
 
                OPT_GROUP(N_("Specific git-branch actions:")),
-               OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"),
-                       REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
+               OPT_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"),
+                       FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES),
                OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1),
                OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
                OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
@@ -847,22 +642,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "edit-description", &edit_description,
                         N_("edit the description for the branch")),
                OPT__FORCE(&force, N_("force creation, move/rename, deletion")),
+               OPT_MERGED(&filter, N_("print only branches that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
+               OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
+               OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+                            N_("field name to sort on"), &parse_opt_ref_sorting),
                {
-                       OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
-                       N_("commit"), N_("print only not merged branches"),
-                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
-                       opt_parse_merge_filter, (intptr_t) "HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
-                       N_("commit"), N_("print only merged branches"),
-                       PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
-                       opt_parse_merge_filter, (intptr_t) "HEAD",
+                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+                       N_("print only branches of the object"), 0, parse_opt_object_name
                },
-               OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
                OPT_END(),
        };
 
+       memset(&filter, 0, sizeof(filter));
+       filter.kind = FILTER_REFS_BRANCHES;
+       filter.abbrev = -1;
+
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_branch_usage, options);
 
@@ -874,11 +669,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
-               detached = 1;
+               filter.detached = 1;
        else if (!skip_prefix(head, "refs/heads/", &head))
                die(_("HEAD not found below refs/heads!"));
-       hashcpy(merge_filter_ref, head_sha1);
-
 
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
@@ -886,17 +679,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
                list = 1;
 
-       if (with_commit || merge_filter != NO_FILTER)
+       if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr)
                list = 1;
 
        if (!!delete + !!rename + !!new_upstream +
            list + unset_upstream > 1)
                usage_with_options(builtin_branch_usage, options);
 
-       if (abbrev == -1)
-               abbrev = DEFAULT_ABBREV;
+       if (filter.abbrev == -1)
+               filter.abbrev = DEFAULT_ABBREV;
        finalize_colopts(&colopts, -1);
-       if (verbose) {
+       if (filter.verbose) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and --verbose are incompatible"));
                colopts = 0;
@@ -910,20 +703,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (delete) {
                if (!argc)
                        die(_("branch name required"));
-               return delete_branches(argc, argv, delete > 1, kinds, quiet);
+               return delete_branches(argc, argv, delete > 1, filter.kind, quiet);
        } else if (list) {
-               int ret = print_ref_list(kinds, detached, verbose, abbrev,
-                                        with_commit, argv);
+               /*  git branch --local also shows HEAD when it is detached */
+               if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
+                       filter.kind |= FILTER_REFS_DETACHED_HEAD;
+               filter.name_patterns = argv;
+               print_ref_list(&filter, sorting);
                print_columns(&output, colopts, NULL);
                string_list_clear(&output, 0);
-               return ret;
+               return 0;
        }
        else if (edit_description) {
                const char *branch_name;
                struct strbuf branch_ref = STRBUF_INIT;
 
                if (!argc) {
-                       if (detached)
+                       if (filter.detached)
                                die(_("Cannot give description to detached HEAD"));
                        branch_name = head;
                } else if (argc == 1)
@@ -1011,7 +807,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                if (!branch)
                        die(_("no such branch '%s'"), argv[0]);
 
-               if (kinds != REF_LOCAL_BRANCH)
+               if (filter.kind != FILTER_REFS_BRANCHES)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
 
                if (track == BRANCH_TRACK_OVERRIDE)
index df53def63f29e295e09261b9bd56960cf27582b1..d7acb94a9594563b1473ecac29bd5de9a5e8a55a 100644 (file)
@@ -159,8 +159,7 @@ static int is_git_repository(struct strbuf *path)
        int gitfile_error;
        size_t orig_path_len = path->len;
        assert(orig_path_len != 0);
-       if (path->buf[orig_path_len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
        strbuf_addstr(path, ".git");
        if (read_gitfile_gently(path->buf, &gitfile_error) || is_git_directory(path->buf))
                ret = 1;
@@ -206,8 +205,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
                return res;
        }
 
-       if (path->buf[original_len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
 
        len = path->len;
        while ((e = readdir(dir)) != NULL) {
index 578da85254418a620d3c7c847af8eed6a3a29d58..9eaecd9a7cdb9ad6726626c96edd05d2d9305453 100644 (file)
@@ -294,9 +294,14 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
                char *ref_git_git = mkpathdup("%s/.git", ref_git);
                free(ref_git);
                ref_git = ref_git_git;
-       } else if (!is_directory(mkpath("%s/objects", ref_git)))
+       } else if (!is_directory(mkpath("%s/objects", ref_git))) {
+               struct strbuf sb = STRBUF_INIT;
+               if (get_common_dir(&sb, ref_git))
+                       die(_("reference repository '%s' as a linked checkout is not supported yet."),
+                           item->string);
                die(_("reference repository '%s' is not a local repository."),
                    item->string);
+       }
 
        if (!access(mkpath("%s/shallow", ref_git), F_OK))
                die(_("reference repository '%s' is shallow"), item->string);
@@ -424,8 +429,10 @@ static void clone_local(const char *src_repo, const char *dest_repo)
        } else {
                struct strbuf src = STRBUF_INIT;
                struct strbuf dest = STRBUF_INIT;
-               strbuf_addf(&src, "%s/objects", src_repo);
-               strbuf_addf(&dest, "%s/objects", dest_repo);
+               get_common_dir(&src, src_repo);
+               get_common_dir(&dest, dest_repo);
+               strbuf_addstr(&src, "/objects");
+               strbuf_addstr(&dest, "/objects");
                copy_or_link_directory(&src, &dest, src_repo, src.len);
                strbuf_release(&src);
                strbuf_release(&dest);
@@ -1064,8 +1071,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        transport_unlock_pack(transport);
        transport_disconnect(transport);
 
-       if (option_dissociate)
+       if (option_dissociate) {
+               close_all_packs();
                dissociate_from_references();
+       }
 
        junk_mode = JUNK_LEAVE_REPO;
        err = checkout();
index 71acc4414352e8d097447993937db96a1cc6efaa..adc772786a7ddf9952aaccb7578b57d10396da93 100644 (file)
@@ -246,8 +246,6 @@ static int get_value(const char *key_, const char *regex_)
 
 static char *normalize_value(const char *key, const char *value)
 {
-       char *normalized;
-
        if (!value)
                return NULL;
 
@@ -258,27 +256,21 @@ static char *normalize_value(const char *key, const char *value)
                 * "~/foobar/" in the config file, and to expand the ~
                 * when retrieving the value.
                 */
-               normalized = xstrdup(value);
-       else {
-               normalized = xmalloc(64);
-               if (types == TYPE_INT) {
-                       int64_t v = git_config_int64(key, value);
-                       sprintf(normalized, "%"PRId64, v);
-               }
-               else if (types == TYPE_BOOL)
-                       sprintf(normalized, "%s",
-                               git_config_bool(key, value) ? "true" : "false");
-               else if (types == TYPE_BOOL_OR_INT) {
-                       int is_bool, v;
-                       v = git_config_bool_or_int(key, value, &is_bool);
-                       if (!is_bool)
-                               sprintf(normalized, "%d", v);
-                       else
-                               sprintf(normalized, "%s", v ? "true" : "false");
-               }
+               return xstrdup(value);
+       if (types == TYPE_INT)
+               return xstrfmt("%"PRId64, git_config_int64(key, value));
+       if (types == TYPE_BOOL)
+               return xstrdup(git_config_bool(key, value) ?  "true" : "false");
+       if (types == TYPE_BOOL_OR_INT) {
+               int is_bool, v;
+               v = git_config_bool_or_int(key, value, &is_bool);
+               if (!is_bool)
+                       return xstrfmt("%d", v);
+               else
+                       return xstrdup(v ? "true" : "false");
        }
 
-       return normalized;
+       die("BUG: cannot normalize type %d", types);
 }
 
 static int get_color_found;
index 9a3869f4ffd81f84f738e54c6405aaedb2514f18..ed84963a57b692ab36aecfed61606afcfaa6d2cd 100644 (file)
@@ -528,36 +528,38 @@ static int update_local_ref(struct ref *ref,
        }
 
        if (in_merge_bases(current, updated)) {
-               char quickref[83];
+               struct strbuf quickref = STRBUF_INIT;
                int r;
-               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
-               strcat(quickref, "..");
-               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+               strbuf_add_unique_abbrev(&quickref, current->object.sha1, DEFAULT_ABBREV);
+               strbuf_addstr(&quickref, "..");
+               strbuf_add_unique_abbrev(&quickref, ref->new_sha1, DEFAULT_ABBREV);
                if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_sha1);
                r = s_update_ref("fast-forward", ref, 1);
                strbuf_addf(display, "%c %-*s %-*s -> %s%s",
                            r ? '!' : ' ',
-                           TRANSPORT_SUMMARY_WIDTH, quickref,
+                           TRANSPORT_SUMMARY_WIDTH, quickref.buf,
                            REFCOL_WIDTH, remote, pretty_ref,
                            r ? _("  (unable to update local ref)") : "");
+               strbuf_release(&quickref);
                return r;
        } else if (force || ref->force) {
-               char quickref[84];
+               struct strbuf quickref = STRBUF_INIT;
                int r;
-               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
-               strcat(quickref, "...");
-               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+               strbuf_add_unique_abbrev(&quickref, current->object.sha1, DEFAULT_ABBREV);
+               strbuf_addstr(&quickref, "...");
+               strbuf_add_unique_abbrev(&quickref, ref->new_sha1, DEFAULT_ABBREV);
                if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_sha1);
                r = s_update_ref("forced-update", ref, 1);
                strbuf_addf(display, "%c %-*s %-*s -> %s  (%s)",
                            r ? '!' : '+',
-                           TRANSPORT_SUMMARY_WIDTH, quickref,
+                           TRANSPORT_SUMMARY_WIDTH, quickref.buf,
                            REFCOL_WIDTH, remote, pretty_ref,
                            r ? _("unable to update local ref") : _("forced update"));
+               strbuf_release(&quickref);
                return r;
        } else {
                strbuf_addf(display, "! %-*s %-*s -> %s  %s",
@@ -637,8 +639,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                continue;
 
                        if (rm->peer_ref) {
-                               ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
-                               strcpy(ref->name, rm->peer_ref->name);
+                               ref = alloc_ref(rm->peer_ref->name);
                                hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
                                hashcpy(ref->new_sha1, rm->old_sha1);
                                ref->force = rm->peer_ref->force;
@@ -1156,11 +1157,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                        die(_("--depth and --unshallow cannot be used together"));
                else if (!is_repository_shallow())
                        die(_("--unshallow on a complete repository does not make sense"));
-               else {
-                       static char inf_depth[12];
-                       sprintf(inf_depth, "%d", INFINITE_DEPTH);
-                       depth = inf_depth;
-               }
+               else
+                       depth = xstrfmt("%d", INFINITE_DEPTH);
        }
 
        /* no need to be strict, transport_set_option() will validate it again */
index 7919206187c9968cc974c10bb9b5f75c7574e630..4e9f6c29bf1e0c1cc7b44548f49077c2e4a81ec8 100644 (file)
@@ -7,6 +7,9 @@
 
 static char const * const for_each_ref_usage[] = {
        N_("git for-each-ref [<options>] [<pattern>]"),
+       N_("git for-each-ref [--points-at <object>]"),
+       N_("git for-each-ref [(--merged | --no-merged) [<object>]]"),
+       N_("git for-each-ref [--contains [<object>]]"),
        NULL
 };
 
@@ -34,9 +37,18 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
                OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
                            N_("field name to sort on"), &parse_opt_ref_sorting),
+               OPT_CALLBACK(0, "points-at", &filter.points_at,
+                            N_("object"), N_("print only refs which points at the given object"),
+                            parse_opt_object_name),
+               OPT_MERGED(&filter, N_("print only refs that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
+               OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
                OPT_END(),
        };
 
+       memset(&array, 0, sizeof(array));
+       memset(&filter, 0, sizeof(filter));
+
        parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
        if (maxcount < 0) {
                error("invalid --count argument: `%d'", maxcount);
@@ -55,9 +67,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
        /* for warn_ambiguous_refs */
        git_config(git_default_config, NULL);
 
-       memset(&array, 0, sizeof(array));
-       memset(&filter, 0, sizeof(filter));
        filter.name_patterns = argv;
+       filter.match_as_path = 1;
        filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
        ref_array_sort(sorting, &array);
 
index 079470342fc926b97ae1c065d37926cf5a449101..8b8bb42c51d13ce9263c0a6fd30fa36810fef78c 100644 (file)
@@ -38,14 +38,7 @@ static int show_dangling = 1;
 #define ERROR_OBJECT 01
 #define ERROR_REACHABLE 02
 #define ERROR_PACK 04
-
-#ifdef NO_D_INO_IN_DIRENT
-#define SORT_DIRENT 0
-#define DIRENT_SORT_HINT(de) 0
-#else
-#define SORT_DIRENT 1
-#define DIRENT_SORT_HINT(de) ((de)->d_ino)
-#endif
+#define ERROR_REFS 010
 
 static int fsck_config(const char *var, const char *value, void *cb)
 {
@@ -373,102 +366,6 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
        return fsck_obj(obj);
 }
 
-/*
- * This is the sorting chunk size: make it reasonably
- * big so that we can sort well..
- */
-#define MAX_SHA1_ENTRIES (1024)
-
-struct sha1_entry {
-       unsigned long ino;
-       unsigned char sha1[20];
-};
-
-static struct {
-       unsigned long nr;
-       struct sha1_entry *entry[MAX_SHA1_ENTRIES];
-} sha1_list;
-
-static int ino_compare(const void *_a, const void *_b)
-{
-       const struct sha1_entry *a = _a, *b = _b;
-       unsigned long ino1 = a->ino, ino2 = b->ino;
-       return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
-}
-
-static void fsck_sha1_list(void)
-{
-       int i, nr = sha1_list.nr;
-
-       if (SORT_DIRENT)
-               qsort(sha1_list.entry, nr,
-                     sizeof(struct sha1_entry *), ino_compare);
-       for (i = 0; i < nr; i++) {
-               struct sha1_entry *entry = sha1_list.entry[i];
-               unsigned char *sha1 = entry->sha1;
-
-               sha1_list.entry[i] = NULL;
-               if (fsck_sha1(sha1))
-                       errors_found |= ERROR_OBJECT;
-               free(entry);
-       }
-       sha1_list.nr = 0;
-}
-
-static void add_sha1_list(unsigned char *sha1, unsigned long ino)
-{
-       struct sha1_entry *entry = xmalloc(sizeof(*entry));
-       int nr;
-
-       entry->ino = ino;
-       hashcpy(entry->sha1, sha1);
-       nr = sha1_list.nr;
-       if (nr == MAX_SHA1_ENTRIES) {
-               fsck_sha1_list();
-               nr = 0;
-       }
-       sha1_list.entry[nr] = entry;
-       sha1_list.nr = ++nr;
-}
-
-static inline int is_loose_object_file(struct dirent *de,
-                                      char *name, unsigned char *sha1)
-{
-       if (strlen(de->d_name) != 38)
-               return 0;
-       memcpy(name + 2, de->d_name, 39);
-       return !get_sha1_hex(name, sha1);
-}
-
-static void fsck_dir(int i, char *path)
-{
-       DIR *dir = opendir(path);
-       struct dirent *de;
-       char name[100];
-
-       if (!dir)
-               return;
-
-       if (verbose)
-               fprintf(stderr, "Checking directory %s\n", path);
-
-       sprintf(name, "%02x", i);
-       while ((de = readdir(dir)) != NULL) {
-               unsigned char sha1[20];
-
-               if (is_dot_or_dotdot(de->d_name))
-                       continue;
-               if (is_loose_object_file(de, name, sha1)) {
-                       add_sha1_list(sha1, DIRENT_SORT_HINT(de));
-                       continue;
-               }
-               if (starts_with(de->d_name, "tmp_obj_"))
-                       continue;
-               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
-       }
-       closedir(dir);
-}
-
 static int default_refs;
 
 static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1)
@@ -521,8 +418,10 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid,
                /* We'll continue with the rest despite the error.. */
                return 0;
        }
-       if (obj->type != OBJ_COMMIT && is_branch(refname))
+       if (obj->type != OBJ_COMMIT && is_branch(refname)) {
                error("%s: not a commit", refname);
+               errors_found |= ERROR_REFS;
+       }
        default_refs++;
        obj->used = 1;
        mark_object_reachable(obj);
@@ -556,9 +455,28 @@ static void get_default_heads(void)
        }
 }
 
+static int fsck_loose(const unsigned char *sha1, const char *path, void *data)
+{
+       if (fsck_sha1(sha1))
+               errors_found |= ERROR_OBJECT;
+       return 0;
+}
+
+static int fsck_cruft(const char *basename, const char *path, void *data)
+{
+       if (!starts_with(basename, "tmp_obj_"))
+               fprintf(stderr, "bad sha1 file: %s\n", path);
+       return 0;
+}
+
+static int fsck_subdir(int nr, const char *path, void *progress)
+{
+       display_progress(progress, nr + 1);
+       return 0;
+}
+
 static void fsck_object_dir(const char *path)
 {
-       int i;
        struct progress *progress = NULL;
 
        if (verbose)
@@ -566,14 +484,11 @@ static void fsck_object_dir(const char *path)
 
        if (show_progress)
                progress = start_progress(_("Checking object directories"), 256);
-       for (i = 0; i < 256; i++) {
-               static char dir[4096];
-               sprintf(dir, "%s/%02x", path, i);
-               fsck_dir(i, dir);
-               display_progress(progress, i+1);
-       }
+
+       for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,
+                                     progress);
+       display_progress(progress, 256);
        stop_progress(&progress);
-       fsck_sha1_list();
 }
 
 static int fsck_head_link(void)
@@ -585,17 +500,23 @@ static int fsck_head_link(void)
                fprintf(stderr, "Checking HEAD link\n");
 
        head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag);
-       if (!head_points_at)
+       if (!head_points_at) {
+               errors_found |= ERROR_REFS;
                return error("Invalid HEAD");
+       }
        if (!strcmp(head_points_at, "HEAD"))
                /* detached HEAD */
                null_is_error = 1;
-       else if (!starts_with(head_points_at, "refs/heads/"))
+       else if (!starts_with(head_points_at, "refs/heads/")) {
+               errors_found |= ERROR_REFS;
                return error("HEAD points to something strange (%s)",
                             head_points_at);
+       }
        if (is_null_oid(&head_oid)) {
-               if (null_is_error)
+               if (null_is_error) {
+                       errors_found |= ERROR_REFS;
                        return error("HEAD: detached HEAD points at nothing");
+               }
                fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
                        head_points_at + 11);
        }
@@ -615,6 +536,7 @@ static int fsck_cache_tree(struct cache_tree *it)
                if (!obj) {
                        error("%s: invalid sha1 pointer in cache-tree",
                              sha1_to_hex(it->sha1));
+                       errors_found |= ERROR_REFS;
                        return 1;
                }
                obj->used = 1;
@@ -678,16 +600,18 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        git_config(fsck_config, NULL);
 
        fsck_head_link();
-       if (!connectivity_only)
+       if (!connectivity_only) {
                fsck_object_dir(get_object_directory());
 
-       prepare_alt_odb();
-       for (alt = alt_odb_list; alt; alt = alt->next) {
-               char namebuf[PATH_MAX];
-               int namelen = alt->name - alt->base;
-               memcpy(namebuf, alt->base, namelen);
-               namebuf[namelen - 1] = 0;
-               fsck_object_dir(namebuf);
+               prepare_alt_odb();
+               for (alt = alt_odb_list; alt; alt = alt->next) {
+                       /* directory name, minus trailing slash */
+                       size_t namelen = alt->name - alt->base - 1;
+                       struct strbuf name = STRBUF_INIT;
+                       strbuf_add(&name, alt->base, namelen);
+                       fsck_object_dir(name.buf);
+                       strbuf_release(&name);
+               }
        }
 
        if (check_full) {
index 0ad8d30b56f89a9ea6ac3a9ee5ae4593ae9754a0..eeeb21b1c46dae897ec9db51e58f8d15fc4f915e 100644 (file)
@@ -44,6 +44,7 @@ static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
 static struct argv_array rerere = ARGV_ARRAY_INIT;
 
 static struct tempfile pidfile;
+static struct lock_file log_lock;
 
 static void git_config_date_string(const char *key, const char **output)
 {
@@ -56,6 +57,28 @@ static void git_config_date_string(const char *key, const char **output)
        }
 }
 
+static void process_log_file(void)
+{
+       struct stat st;
+       if (!fstat(get_lock_file_fd(&log_lock), &st) && st.st_size)
+               commit_lock_file(&log_lock);
+       else
+               rollback_lock_file(&log_lock);
+}
+
+static void process_log_file_at_exit(void)
+{
+       fflush(stderr);
+       process_log_file();
+}
+
+static void process_log_file_on_signal(int signo)
+{
+       process_log_file();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
 static void gc_config(void)
 {
        const char *value;
@@ -194,7 +217,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
                return NULL;
 
        if (gethostname(my_host, sizeof(my_host)))
-               strcpy(my_host, "unknown");
+               xsnprintf(my_host, sizeof(my_host), "unknown");
 
        pidfile_path = git_pathdup("gc.pid");
        fd = hold_lock_file_for_update(&lock, pidfile_path,
@@ -241,6 +264,24 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
        return NULL;
 }
 
+static int report_last_gc_error(void)
+{
+       struct strbuf sb = STRBUF_INIT;
+       int ret;
+
+       ret = strbuf_read_file(&sb, git_path("gc.log"), 0);
+       if (ret > 0)
+               return error(_("The last gc run reported the following. "
+                              "Please correct the root cause\n"
+                              "and remove %s.\n"
+                              "Automatic cleanup will not be performed "
+                              "until the file is removed.\n\n"
+                              "%s"),
+                            git_path("gc.log"), sb.buf);
+       strbuf_release(&sb);
+       return 0;
+}
+
 static int gc_before_repack(void)
 {
        if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
@@ -262,6 +303,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        int force = 0;
        const char *name;
        pid_t pid;
+       int daemonized = 0;
 
        struct option builtin_gc_options[] = {
                OPT__QUIET(&quiet, N_("suppress progress reporting")),
@@ -318,13 +360,16 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
                }
                if (detach_auto) {
+                       if (report_last_gc_error())
+                               return -1;
+
                        if (gc_before_repack())
                                return -1;
                        /*
                         * failure to daemonize is ok, we'll continue
                         * in foreground
                         */
-                       daemonize();
+                       daemonized = !daemonize();
                }
        } else
                add_repack_all_option();
@@ -337,6 +382,15 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                    name, (uintmax_t)pid);
        }
 
+       if (daemonized) {
+               hold_lock_file_for_update(&log_lock,
+                                         git_path("gc.log"),
+                                         LOCK_DIE_ON_ERROR);
+               dup2(get_lock_file_fd(&log_lock), 2);
+               sigchain_push_common(process_log_file_on_signal);
+               atexit(process_log_file_at_exit);
+       }
+
        if (gc_before_repack())
                return -1;
 
index 3422e7307998b9da91f85512084aeae515022a60..1cd0c1ee44daf056befb8a26bba2f4fd343c8658 100644 (file)
@@ -140,17 +140,10 @@ static void exec_man_konqueror(const char *path, const char *page)
 
                /* It's simpler to launch konqueror using kfmclient. */
                if (path) {
-                       const char *file = strrchr(path, '/');
-                       if (file && !strcmp(file + 1, "konqueror")) {
-                               char *new = xstrdup(path);
-                               char *dest = strrchr(new, '/');
-
-                               /* strlen("konqueror") == strlen("kfmclient") */
-                               strcpy(dest + 1, "kfmclient");
-                               path = new;
-                       }
-                       if (file)
-                               filename = file;
+                       size_t len;
+                       if (strip_suffix(path, "/konqueror", &len))
+                               path = xstrfmt("%.*s/kfmclient", (int)len, path);
+                       filename = basename((char *)path);
                } else
                        path = "kfmclient";
                strbuf_addf(&man_page, "man:%s(1)", page);
@@ -183,7 +176,7 @@ static void add_man_viewer(const char *name)
        while (*p)
                p = &((*p)->next);
        *p = xcalloc(1, (sizeof(**p) + len + 1));
-       strncpy((*p)->name, name, len);
+       memcpy((*p)->name, name, len); /* NUL-terminated by xcalloc */
 }
 
 static int supported_man_viewer(const char *name, size_t len)
@@ -199,7 +192,7 @@ static void do_add_man_viewer_info(const char *name,
 {
        struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
 
-       strncpy(new->name, name, len);
+       memcpy(new->name, name, len); /* NUL-terminated by xcalloc */
        new->info = xstrdup(value);
        new->next = man_viewer_info_list;
        man_viewer_info_list = new;
@@ -295,16 +288,6 @@ static int is_git_command(const char *s)
                is_in_cmdlist(&other_cmds, s);
 }
 
-static const char *prepend(const char *prefix, const char *cmd)
-{
-       size_t pre_len = strlen(prefix);
-       size_t cmd_len = strlen(cmd);
-       char *p = xmalloc(pre_len + cmd_len + 1);
-       memcpy(p, prefix, pre_len);
-       strcpy(p + pre_len, cmd);
-       return p;
-}
-
 static const char *cmd_to_page(const char *git_cmd)
 {
        if (!git_cmd)
@@ -312,9 +295,9 @@ static const char *cmd_to_page(const char *git_cmd)
        else if (starts_with(git_cmd, "git"))
                return git_cmd;
        else if (is_git_command(git_cmd))
-               return prepend("git-", git_cmd);
+               return xstrfmt("git-%s", git_cmd);
        else
-               return prepend("git", git_cmd);
+               return xstrfmt("git%s", git_cmd);
 }
 
 static void setup_man_path(void)
index 3431de2362d981b1239d8269d17fb75d8de094fd..1ad1bde69600d3097d71ed67d230d2e2f916544e 100644 (file)
@@ -441,7 +441,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size,
        int hdrlen;
 
        if (!is_delta_type(type)) {
-               hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1;
+               hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), size) + 1;
                git_SHA1_Init(&c);
                git_SHA1_Update(&c, hdr, hdrlen);
        } else
index 69323e186cda73fe02658a535d8eae8387ff5444..f59f40768e90cae98cf6541f4c383d762b68b9d1 100644 (file)
@@ -36,10 +36,11 @@ static void safe_create_dir(const char *dir, int share)
                die(_("Could not make %s writable by group"), dir);
 }
 
-static void copy_templates_1(char *path, int baselen,
-                            char *template, int template_baselen,
+static void copy_templates_1(struct strbuf *path, struct strbuf *template,
                             DIR *dir)
 {
+       size_t path_baselen = path->len;
+       size_t template_baselen = template->len;
        struct dirent *de;
 
        /* Note: if ".git/hooks" file exists in the repository being
@@ -49,77 +50,64 @@ static void copy_templates_1(char *path, int baselen,
         * with the way the namespace under .git/ is organized, should
         * be really carefully chosen.
         */
-       safe_create_dir(path, 1);
+       safe_create_dir(path->buf, 1);
        while ((de = readdir(dir)) != NULL) {
                struct stat st_git, st_template;
-               int namelen;
                int exists = 0;
 
+               strbuf_setlen(path, path_baselen);
+               strbuf_setlen(template, template_baselen);
+
                if (de->d_name[0] == '.')
                        continue;
-               namelen = strlen(de->d_name);
-               if ((PATH_MAX <= baselen + namelen) ||
-                   (PATH_MAX <= template_baselen + namelen))
-                       die(_("insanely long template name %s"), de->d_name);
-               memcpy(path + baselen, de->d_name, namelen+1);
-               memcpy(template + template_baselen, de->d_name, namelen+1);
-               if (lstat(path, &st_git)) {
+               strbuf_addstr(path, de->d_name);
+               strbuf_addstr(template, de->d_name);
+               if (lstat(path->buf, &st_git)) {
                        if (errno != ENOENT)
-                               die_errno(_("cannot stat '%s'"), path);
+                               die_errno(_("cannot stat '%s'"), path->buf);
                }
                else
                        exists = 1;
 
-               if (lstat(template, &st_template))
-                       die_errno(_("cannot stat template '%s'"), template);
+               if (lstat(template->buf, &st_template))
+                       die_errno(_("cannot stat template '%s'"), template->buf);
 
                if (S_ISDIR(st_template.st_mode)) {
-                       DIR *subdir = opendir(template);
-                       int baselen_sub = baselen + namelen;
-                       int template_baselen_sub = template_baselen + namelen;
+                       DIR *subdir = opendir(template->buf);
                        if (!subdir)
-                               die_errno(_("cannot opendir '%s'"), template);
-                       path[baselen_sub++] =
-                               template[template_baselen_sub++] = '/';
-                       path[baselen_sub] =
-                               template[template_baselen_sub] = 0;
-                       copy_templates_1(path, baselen_sub,
-                                        template, template_baselen_sub,
-                                        subdir);
+                               die_errno(_("cannot opendir '%s'"), template->buf);
+                       strbuf_addch(path, '/');
+                       strbuf_addch(template, '/');
+                       copy_templates_1(path, template, subdir);
                        closedir(subdir);
                }
                else if (exists)
                        continue;
                else if (S_ISLNK(st_template.st_mode)) {
-                       char lnk[256];
-                       int len;
-                       len = readlink(template, lnk, sizeof(lnk));
-                       if (len < 0)
-                               die_errno(_("cannot readlink '%s'"), template);
-                       if (sizeof(lnk) <= len)
-                               die(_("insanely long symlink %s"), template);
-                       lnk[len] = 0;
-                       if (symlink(lnk, path))
-                               die_errno(_("cannot symlink '%s' '%s'"), lnk, path);
+                       struct strbuf lnk = STRBUF_INIT;
+                       if (strbuf_readlink(&lnk, template->buf, 0) < 0)
+                               die_errno(_("cannot readlink '%s'"), template->buf);
+                       if (symlink(lnk.buf, path->buf))
+                               die_errno(_("cannot symlink '%s' '%s'"),
+                                         lnk.buf, path->buf);
+                       strbuf_release(&lnk);
                }
                else if (S_ISREG(st_template.st_mode)) {
-                       if (copy_file(path, template, st_template.st_mode))
-                               die_errno(_("cannot copy '%s' to '%s'"), template,
-                                         path);
+                       if (copy_file(path->buf, template->buf, st_template.st_mode))
+                               die_errno(_("cannot copy '%s' to '%s'"),
+                                         template->buf, path->buf);
                }
                else
-                       error(_("ignoring template %s"), template);
+                       error(_("ignoring template %s"), template->buf);
        }
 }
 
 static void copy_templates(const char *template_dir)
 {
-       char path[PATH_MAX];
-       char template_path[PATH_MAX];
-       int template_len;
+       struct strbuf path = STRBUF_INIT;
+       struct strbuf template_path = STRBUF_INIT;
+       size_t template_len;
        DIR *dir;
-       const char *git_dir = get_git_dir();
-       int len = strlen(git_dir);
        char *to_free = NULL;
 
        if (!template_dir)
@@ -132,26 +120,23 @@ static void copy_templates(const char *template_dir)
                free(to_free);
                return;
        }
-       template_len = strlen(template_dir);
-       if (PATH_MAX <= (template_len+strlen("/config")))
-               die(_("insanely long template path %s"), template_dir);
-       strcpy(template_path, template_dir);
-       if (template_path[template_len-1] != '/') {
-               template_path[template_len++] = '/';
-               template_path[template_len] = 0;
-       }
-       dir = opendir(template_path);
+
+       strbuf_addstr(&template_path, template_dir);
+       strbuf_complete(&template_path, '/');
+       template_len = template_path.len;
+
+       dir = opendir(template_path.buf);
        if (!dir) {
                warning(_("templates not found %s"), template_dir);
                goto free_return;
        }
 
        /* Make sure that template is from the correct vintage */
-       strcpy(template_path + template_len, "config");
+       strbuf_addstr(&template_path, "config");
        repository_format_version = 0;
        git_config_from_file(check_repository_format_version,
-                            template_path, NULL);
-       template_path[template_len] = 0;
+                            template_path.buf, NULL);
+       strbuf_setlen(&template_path, template_len);
 
        if (repository_format_version &&
            repository_format_version != GIT_REPO_VERSION) {
@@ -162,17 +147,15 @@ static void copy_templates(const char *template_dir)
                goto close_free_return;
        }
 
-       memcpy(path, git_dir, len);
-       if (len && path[len - 1] != '/')
-               path[len++] = '/';
-       path[len] = 0;
-       copy_templates_1(path, len,
-                        template_path, template_len,
-                        dir);
+       strbuf_addstr(&path, get_git_dir());
+       strbuf_complete(&path, '/');
+       copy_templates_1(&path, &template_path, dir);
 close_free_return:
        closedir(dir);
 free_return:
        free(to_free);
+       strbuf_release(&path);
+       strbuf_release(&template_path);
 }
 
 static int git_init_db_config(const char *k, const char *v, void *cb)
@@ -199,28 +182,20 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
 
 static int create_default_files(const char *template_path)
 {
-       const char *git_dir = get_git_dir();
-       unsigned len = strlen(git_dir);
-       static char path[PATH_MAX];
        struct stat st1;
+       struct strbuf buf = STRBUF_INIT;
+       char *path;
        char repo_version_string[10];
        char junk[2];
        int reinit;
        int filemode;
 
-       if (len > sizeof(path)-50)
-               die(_("insane git directory %s"), git_dir);
-       memcpy(path, git_dir, len);
-
-       if (len && path[len-1] != '/')
-               path[len++] = '/';
-
        /*
         * Create .git/refs/{heads,tags}
         */
-       safe_create_dir(git_path("refs"), 1);
-       safe_create_dir(git_path("refs/heads"), 1);
-       safe_create_dir(git_path("refs/tags"), 1);
+       safe_create_dir(git_path_buf(&buf, "refs"), 1);
+       safe_create_dir(git_path_buf(&buf, "refs/heads"), 1);
+       safe_create_dir(git_path_buf(&buf, "refs/tags"), 1);
 
        /* Just look for `init.templatedir` */
        git_config(git_init_db_config, NULL);
@@ -244,16 +219,16 @@ static int create_default_files(const char *template_path)
         */
        if (shared_repository) {
                adjust_shared_perm(get_git_dir());
-               adjust_shared_perm(git_path("refs"));
-               adjust_shared_perm(git_path("refs/heads"));
-               adjust_shared_perm(git_path("refs/tags"));
+               adjust_shared_perm(git_path_buf(&buf, "refs"));
+               adjust_shared_perm(git_path_buf(&buf, "refs/heads"));
+               adjust_shared_perm(git_path_buf(&buf, "refs/tags"));
        }
 
        /*
         * Create the default symlink from ".git/HEAD" to the "master"
         * branch, if it does not exist yet.
         */
-       strcpy(path + len, "HEAD");
+       path = git_path_buf(&buf, "HEAD");
        reinit = (!access(path, R_OK)
                  || readlink(path, junk, sizeof(junk)-1) != -1);
        if (!reinit) {
@@ -262,13 +237,12 @@ static int create_default_files(const char *template_path)
        }
 
        /* This forces creation of new config file */
-       sprintf(repo_version_string, "%d", GIT_REPO_VERSION);
+       xsnprintf(repo_version_string, sizeof(repo_version_string),
+                 "%d", GIT_REPO_VERSION);
        git_config_set("core.repositoryformatversion", repo_version_string);
 
-       path[len] = 0;
-       strcpy(path + len, "config");
-
        /* Check filemode trustability */
+       path = git_path_buf(&buf, "config");
        filemode = TEST_FILEMODE;
        if (TEST_FILEMODE && !lstat(path, &st1)) {
                struct stat st2;
@@ -289,14 +263,13 @@ static int create_default_files(const char *template_path)
                /* allow template config file to override the default */
                if (log_all_ref_updates == -1)
                    git_config_set("core.logallrefupdates", "true");
-               if (needs_work_tree_config(git_dir, work_tree))
+               if (needs_work_tree_config(get_git_dir(), work_tree))
                        git_config_set("core.worktree", work_tree);
        }
 
        if (!reinit) {
                /* Check if symlink is supported in the work tree */
-               path[len] = 0;
-               strcpy(path + len, "tXXXXXX");
+               path = git_path_buf(&buf, "tXXXXXX");
                if (!close(xmkstemp(path)) &&
                    !unlink(path) &&
                    !symlink("testing", path) &&
@@ -307,31 +280,35 @@ static int create_default_files(const char *template_path)
                        git_config_set("core.symlinks", "false");
 
                /* Check if the filesystem is case-insensitive */
-               path[len] = 0;
-               strcpy(path + len, "CoNfIg");
+               path = git_path_buf(&buf, "CoNfIg");
                if (!access(path, F_OK))
                        git_config_set("core.ignorecase", "true");
-               probe_utf8_pathname_composition(path, len);
+               probe_utf8_pathname_composition();
        }
 
+       strbuf_release(&buf);
        return reinit;
 }
 
 static void create_object_directory(void)
 {
-       const char *object_directory = get_object_directory();
-       int len = strlen(object_directory);
-       char *path = xmalloc(len + 40);
+       struct strbuf path = STRBUF_INIT;
+       size_t baselen;
+
+       strbuf_addstr(&path, get_object_directory());
+       baselen = path.len;
+
+       safe_create_dir(path.buf, 1);
 
-       memcpy(path, object_directory, len);
+       strbuf_setlen(&path, baselen);
+       strbuf_addstr(&path, "/pack");
+       safe_create_dir(path.buf, 1);
 
-       safe_create_dir(object_directory, 1);
-       strcpy(path+len, "/pack");
-       safe_create_dir(path, 1);
-       strcpy(path+len, "/info");
-       safe_create_dir(path, 1);
+       strbuf_setlen(&path, baselen);
+       strbuf_addstr(&path, "/info");
+       safe_create_dir(path.buf, 1);
 
-       free(path);
+       strbuf_release(&path);
 }
 
 int set_git_dir_init(const char *git_dir, const char *real_git_dir,
@@ -414,13 +391,13 @@ int init_db(const char *template_dir, unsigned int flags)
                 */
                if (shared_repository < 0)
                        /* force to the mode value */
-                       sprintf(buf, "0%o", -shared_repository);
+                       xsnprintf(buf, sizeof(buf), "0%o", -shared_repository);
                else if (shared_repository == PERM_GROUP)
-                       sprintf(buf, "%d", OLD_PERM_GROUP);
+                       xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP);
                else if (shared_repository == PERM_EVERYBODY)
-                       sprintf(buf, "%d", OLD_PERM_EVERYBODY);
+                       xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY);
                else
-                       die("oops");
+                       die("BUG: invalid value for shared_repository");
                git_config_set("core.sharedrepository", buf);
                git_config_set("receive.denyNonFastforwards", "true");
        }
index a491d3dea0e412624e7212060658e22a421cdfbf..dda671d975255494a6987e82e2ccf23f7f69f7ee 100644 (file)
@@ -796,8 +796,7 @@ static int reopen_stdout(struct commit *commit, const char *subject,
                if (filename.len >=
                    PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
                        return error(_("name of output directory is too long"));
-               if (filename.buf[filename.len - 1] != '/')
-                       strbuf_addch(&filename, '/');
+               strbuf_complete(&filename, '/');
        }
 
        if (rev->numbered_files)
index 4554dbc8a98c0daaa67c8ea65f5ddf5c48f19383..a31024900b0a812c94dfa86024cdf45df36b96a1 100644 (file)
@@ -4,7 +4,7 @@
 #include "remote.h"
 
 static const char ls_remote_usage[] =
-"git ls-remote [--heads] [--tags]  [-u <exec> | --upload-pack <exec>]\n"
+"git ls-remote [--heads] [--tags]  [--upload-pack=<exec>]\n"
 "                     [-q | --quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]";
 
 /*
@@ -93,12 +93,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        if (argv[i]) {
                int j;
                pattern = xcalloc(argc - i + 1, sizeof(const char *));
-               for (j = i; j < argc; j++) {
-                       int len = strlen(argv[j]);
-                       char *p = xmalloc(len + 3);
-                       sprintf(p, "*/%s", argv[j]);
-                       pattern[j - i] = p;
-               }
+               for (j = i; j < argc; j++)
+                       pattern[j - i] = xstrfmt("*/%s", argv[j]);
        }
        remote = remote_get(dest);
        if (!remote) {
index 3b04a0f082a48c7b9c16f57a5bf4be94c159204c..0e30d862303b67ace2c7accf2e7c9346d8f63264 100644 (file)
@@ -96,12 +96,13 @@ static int show_tree(const unsigned char *sha1, struct strbuf *base,
                        if (!strcmp(type, blob_type)) {
                                unsigned long size;
                                if (sha1_object_info(sha1, &size) == OBJ_BAD)
-                                       strcpy(size_text, "BAD");
+                                       xsnprintf(size_text, sizeof(size_text),
+                                                 "BAD");
                                else
-                                       snprintf(size_text, sizeof(size_text),
-                                                "%lu", size);
+                                       xsnprintf(size_text, sizeof(size_text),
+                                                 "%lu", size);
                        } else
-                               strcpy(size_text, "-");
+                               xsnprintf(size_text, sizeof(size_text), "-");
                        printf("%06o %s %s %7s\t", mode, type,
                               find_unique_abbrev(sha1, abbrev),
                               size_text);
index 8e02ea109ac8f9fd739c4d54b7a709916f88022a..104277acc49a08d9472523edc8337c3eeeb345cd 100644 (file)
@@ -98,30 +98,37 @@ static int populate_maildir_list(struct string_list *list, const char *path)
 {
        DIR *dir;
        struct dirent *dent;
-       char name[PATH_MAX];
+       char *name = NULL;
        char *subs[] = { "cur", "new", NULL };
        char **sub;
+       int ret = -1;
 
        for (sub = subs; *sub; ++sub) {
-               snprintf(name, sizeof(name), "%s/%s", path, *sub);
+               free(name);
+               name = xstrfmt("%s/%s", path, *sub);
                if ((dir = opendir(name)) == NULL) {
                        if (errno == ENOENT)
                                continue;
                        error("cannot opendir %s (%s)", name, strerror(errno));
-                       return -1;
+                       goto out;
                }
 
                while ((dent = readdir(dir)) != NULL) {
                        if (dent->d_name[0] == '.')
                                continue;
-                       snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
+                       free(name);
+                       name = xstrfmt("%s/%s", *sub, dent->d_name);
                        string_list_insert(list, name);
                }
 
                closedir(dir);
        }
 
-       return 0;
+       ret = 0;
+
+out:
+       free(name);
+       return ret;
 }
 
 static int maildir_filename_cmp(const char *a, const char *b)
@@ -148,8 +155,8 @@ static int maildir_filename_cmp(const char *a, const char *b)
 static int split_maildir(const char *maildir, const char *dir,
        int nr_prec, int skip)
 {
-       char file[PATH_MAX];
-       char name[PATH_MAX];
+       char *file = NULL;
+       FILE *f = NULL;
        int ret = -1;
        int i;
        struct string_list list = STRING_LIST_INIT_DUP;
@@ -160,8 +167,11 @@ static int split_maildir(const char *maildir, const char *dir,
                goto out;
 
        for (i = 0; i < list.nr; i++) {
-               FILE *f;
-               snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string);
+               char *name;
+
+               free(file);
+               file = xstrfmt("%s/%s", maildir, list.items[i].string);
+
                f = fopen(file, "r");
                if (!f) {
                        error("cannot open mail %s (%s)", file, strerror(errno));
@@ -173,14 +183,19 @@ static int split_maildir(const char *maildir, const char *dir,
                        goto out;
                }
 
-               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+               name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
                split_one(f, name, 1);
+               free(name);
 
                fclose(f);
+               f = NULL;
        }
 
        ret = skip;
 out:
+       if (f)
+               fclose(f);
+       free(file);
        string_list_clear(&list, 1);
        return ret;
 }
@@ -188,7 +203,6 @@ static int split_maildir(const char *maildir, const char *dir,
 static int split_mbox(const char *file, const char *dir, int allow_bare,
                      int nr_prec, int skip)
 {
-       char name[PATH_MAX];
        int ret = -1;
        int peek;
 
@@ -215,8 +229,9 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
        }
 
        while (!file_done) {
-               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+               char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
                file_done = split_one(f, name, allow_bare);
+               free(name);
        }
 
        if (f != stdin)
index 1a1eafa6fdc2e9dc66c98d3a81b7ee50e44727da..1c3427c36caa40e8740b090c44a048201e81c706 100644 (file)
@@ -9,7 +9,7 @@ static int merge_entry(int pos, const char *path)
 {
        int found;
        const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
-       char hexbuf[4][60];
+       char hexbuf[4][GIT_SHA1_HEXSZ + 1];
        char ownbuf[4][60];
 
        if (pos >= active_nr)
@@ -22,8 +22,8 @@ static int merge_entry(int pos, const char *path)
                if (strcmp(ce->name, path))
                        break;
                found++;
-               strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
-               sprintf(ownbuf[stage], "%o", ce->ce_mode);
+               sha1_to_hex_r(hexbuf[stage], ce->sha1);
+               xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode);
                arguments[stage] = hexbuf[stage];
                arguments[stage + 4] = ownbuf[stage];
        } while (++pos < active_nr);
index a90f28f34d2072b7d40964a7fa3cf46d3bb98cdd..491efd556e87d0b263bb1029e0cabb9b7f6f1472 100644 (file)
@@ -14,7 +14,7 @@ static const char *better_branch_name(const char *branch)
 
        if (strlen(branch) != 40)
                return branch;
-       sprintf(githead_env, "GITHEAD_%s", branch);
+       xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch);
        name = getenv(githead_env);
        return name ? name : branch;
 }
index a0edacab20bbe55458bb114bd00d618b1bc41a7a..a0a93282922ebb8c58b79ff365d859500041351a 100644 (file)
@@ -1319,13 +1319,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        if (verify_signatures) {
                for (p = remoteheads; p; p = p->next) {
                        struct commit *commit = p->item;
-                       char hex[41];
+                       char hex[GIT_SHA1_HEXSZ + 1];
                        struct signature_check signature_check;
                        memset(&signature_check, 0, sizeof(signature_check));
 
                        check_commit_signature(commit, &signature_check);
 
-                       strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+                       find_unique_abbrev_r(hex, commit->object.sha1, DEFAULT_ABBREV);
                        switch (signature_check.result) {
                        case 'G':
                                break;
@@ -1415,15 +1415,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
                struct commit *commit;
-               char hex[41];
 
-               strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
-
-               if (verbosity >= 0)
-                       printf(_("Updating %s..%s\n"),
-                               hex,
-                               find_unique_abbrev(remoteheads->item->object.sha1,
-                               DEFAULT_ABBREV));
+               if (verbosity >= 0) {
+                       char from[GIT_SHA1_HEXSZ + 1], to[GIT_SHA1_HEXSZ + 1];
+                       find_unique_abbrev_r(from, head_commit->object.sha1,
+                                             DEFAULT_ABBREV);
+                       find_unique_abbrev_r(to, remoteheads->item->object.sha1,
+                                             DEFAULT_ABBREV);
+                       printf(_("Updating %s..%s\n"), from, to);
+               }
                strbuf_addstr(&msg, "Fast-forward");
                if (have_message)
                        strbuf_addstr(&msg,
index 248a3eb260cfa22a11d4b1b5420c42bc608f13c5..0377fc11428b8148b508658547987ea8954723e6 100644 (file)
@@ -55,20 +55,16 @@ static void name_rev(struct commit *commit,
                        parents;
                        parents = parents->next, parent_number++) {
                if (parent_number > 1) {
-                       int len = strlen(tip_name);
-                       char *new_name = xmalloc(len +
-                               1 + decimal_length(generation) +  /* ~<n> */
-                               1 + 2 +                           /* ^NN */
-                               1);
-
-                       if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
-                               len -= 2;
+                       size_t len;
+                       char *new_name;
+
+                       strip_suffix(tip_name, "^0", &len);
                        if (generation > 0)
-                               sprintf(new_name, "%.*s~%d^%d", len, tip_name,
-                                               generation, parent_number);
+                               new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name,
+                                                  generation, parent_number);
                        else
-                               sprintf(new_name, "%.*s^%d", len, tip_name,
-                                               parent_number);
+                               new_name = xstrfmt("%.*s^%d", (int)len, tip_name,
+                                                  parent_number);
 
                        name_rev(parents->item, new_name, 0,
                                distance + MERGE_TRAVERSAL_WEIGHT, 0);
index a39bb0a11f72982f51222cdcf19d1010367c908f..bf3fd3f9c8d80ff84ace4150214118355585e917 100644 (file)
@@ -66,7 +66,7 @@ static int parse_opt_rebase(const struct option *opt, const char *arg, int unset
 }
 
 static const char * const pull_usage[] = {
-       N_("git pull [options] [<repository> [<refspec>...]]"),
+       N_("git pull [<options>] [<repository> [<refspec>...]]"),
        NULL
 };
 
index 2379e11069ef223a82580a9451ede8fbdb1eb8d9..8c693e756852ef2ec3aac3bfc77d71c6258a8306 100644 (file)
@@ -90,7 +90,7 @@ static int debug_merge(const struct cache_entry * const *stages,
        debug_stage("index", stages[0], o);
        for (i = 1; i <= o->merge_size; i++) {
                char buf[24];
-               sprintf(buf, "ent#%d", i);
+               xsnprintf(buf, sizeof(buf), "ent#%d", i);
                debug_stage(buf, stages[i], o);
        }
        return 0;
index e6b93d026478dce208ab4c267d9b6e67fa856c8d..bcb624bc054833cd8f4d15e47ee96b31fa79f754 100644 (file)
@@ -280,10 +280,10 @@ static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2
 
 static void report_message(const char *prefix, const char *err, va_list params)
 {
-       int sz = strlen(prefix);
+       int sz;
        char msg[4096];
 
-       strncpy(msg, prefix, sz);
+       sz = xsnprintf(msg, sizeof(msg), "%s", prefix);
        sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
        if (sz > (sizeof(msg) - 1))
                sz = sizeof(msg) - 1;
@@ -1071,8 +1071,11 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
        const char *dst_name;
        struct string_list_item *item;
        struct command *dst_cmd;
-       unsigned char sha1[20];
-       char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
+       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);
@@ -1103,10 +1106,10 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
 
        dst_cmd->skip_update = 1;
 
-       strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV));
-       strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV));
-       strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV));
-       strcpy(dst_newh, find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV));
+       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,
@@ -1521,15 +1524,18 @@ static const char *unpack(int err_fd, struct shallow_info *si)
                if (status)
                        return "unpack-objects abnormal exit";
        } else {
-               int s;
-               char keep_arg[256];
-
-               s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
-               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
-                       strcpy(keep_arg + s, "localhost");
+               char hostname[256];
 
                argv_array_pushl(&child.args, "index-pack",
-                                "--stdin", hdr_arg, keep_arg, NULL);
+                                "--stdin", hdr_arg, NULL);
+
+               if (gethostname(hostname, sizeof(hostname)))
+                       xsnprintf(hostname, sizeof(hostname), "localhost");
+               argv_array_pushf(&child.args,
+                                "--keep=receive-pack %"PRIuMAX" on %s",
+                                (uintmax_t)getpid(),
+                                hostname);
+
                if (fsck_objects)
                        argv_array_pushf(&child.args, "--strict%s",
                                fsck_msg_types.buf);
index 3b8c22cc75ed04eaf450f9d5a1e9e71b914339eb..e3cd25d580b0f1ee14b4a5037ceae8dc20b31f30 100644 (file)
@@ -1,6 +1,7 @@
 #include "builtin.h"
 #include "transport.h"
 #include "run-command.h"
+#include "pkt-line.h"
 
 /*
  * URL syntax:
@@ -142,36 +143,11 @@ static const char **parse_argv(const char *arg, const char *service)
 static void send_git_request(int stdin_fd, const char *serv, const char *repo,
        const char *vhost)
 {
-       size_t bufferspace;
-       size_t wpos = 0;
-       char *buffer;
-
-       /*
-        * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
-        * 6 bytes extra (xxxx \0) if there is no vhost.
-        */
-       if (vhost)
-               bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
+       if (!vhost)
+               packet_write(stdin_fd, "%s %s%c", serv, repo, 0);
        else
-               bufferspace = strlen(serv) + strlen(repo) + 6;
-
-       if (bufferspace > 0xFFFF)
-               die("Request too large to send");
-       buffer = xmalloc(bufferspace);
-
-       /* Make the packet. */
-       wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
-               serv, repo, 0);
-
-       /* Add vhost if any. */
-       if (vhost)
-               sprintf(buffer + wpos, "host=%s%c", vhost, 0);
-
-       /* Send the request */
-       if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
-               die_errno("Failed to send request");
-
-       free(buffer);
+               packet_write(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0,
+                            vhost, 0);
 }
 
 static int run_child(const char *arg, const char *service)
index 181668dedddef9bf79ab91d9740607cf31115a16..e4c3ea130c98b01f87bed617a6386b03158b42d5 100644 (file)
@@ -18,6 +18,7 @@ static const char * const builtin_remote_usage[] = {
        N_("git remote prune [-n | --dry-run] <name>"),
        N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"),
        N_("git remote set-branches [--add] <name> <branch>..."),
+       N_("git remote get-url [--push] [--all] <name>"),
        N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
        N_("git remote set-url --add <name> <newurl>"),
        N_("git remote set-url --delete <name> <url>"),
@@ -65,6 +66,11 @@ static const char * const builtin_remote_update_usage[] = {
        NULL
 };
 
+static const char * const builtin_remote_geturl_usage[] = {
+       N_("git remote get-url [--push] [--all] <name>"),
+       NULL
+};
+
 static const char * const builtin_remote_seturl_usage[] = {
        N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
        N_("git remote set-url --add <name> <newurl>"),
@@ -1467,6 +1473,57 @@ static int set_branches(int argc, const char **argv)
        return set_remote_branches(argv[0], argv + 1, add_mode);
 }
 
+static int get_url(int argc, const char **argv)
+{
+       int i, push_mode = 0, all_mode = 0;
+       const char *remotename = NULL;
+       struct remote *remote;
+       const char **url;
+       int url_nr;
+       struct option options[] = {
+               OPT_BOOL('\0', "push", &push_mode,
+                        N_("query push URLs rather than fetch URLs")),
+               OPT_BOOL('\0', "all", &all_mode,
+                        N_("return all URLs")),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0);
+
+       if (argc != 1)
+               usage_with_options(builtin_remote_geturl_usage, options);
+
+       remotename = argv[0];
+
+       if (!remote_is_configured(remotename))
+               die(_("No such remote '%s'"), remotename);
+       remote = remote_get(remotename);
+
+       url_nr = 0;
+       if (push_mode) {
+               url = remote->pushurl;
+               url_nr = remote->pushurl_nr;
+       }
+       /* else fetch mode */
+
+       /* Use the fetch URL when no push URLs were found or requested. */
+       if (!url_nr) {
+               url = remote->url;
+               url_nr = remote->url_nr;
+       }
+
+       if (!url_nr)
+               die(_("no URLs configured for remote '%s'"), remotename);
+
+       if (all_mode) {
+               for (i = 0; i < url_nr; i++)
+                       printf_ln("%s", url[i]);
+       } else {
+               printf_ln("%s", *url);
+       }
+
+       return 0;
+}
+
 static int set_url(int argc, const char **argv)
 {
        int i, push_mode = 0, add_mode = 0, delete_mode = 0;
@@ -1576,6 +1633,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
                result = set_head(argc, argv);
        else if (!strcmp(argv[0], "set-branches"))
                result = set_branches(argc, argv);
+       else if (!strcmp(argv[0], "get-url"))
+               result = get_url(argc, argv);
        else if (!strcmp(argv[0], "set-url"))
                result = set_url(argc, argv);
        else if (!strcmp(argv[0], "show"))
index 88e1359ebcaad338d96dd942a5f88f54d6cce271..1bf72423bf72d7b7d224c14808b0652d78305d63 100644 (file)
@@ -104,9 +104,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
                        return 0;
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
-                       const char *name = (const char *)merge_rr.items[i].util;
-                       if (diff_two(rerere_path(name, "preimage"), path, path, path))
-                               die("unable to generate diff for %s", name);
+                       const struct rerere_id *id = merge_rr.items[i].util;
+                       if (diff_two(rerere_path(id, "preimage"), path, path, path))
+                               die("unable to generate diff for %s", rerere_path(id, NULL));
                }
        } else
                usage_with_options(rerere_usage, options);
index d80d1ed35944144aa0aeeeb1b0e08c9efdec6523..491d298fa25cebb1afd9b33cbbd099efe72629bc 100644 (file)
@@ -217,7 +217,7 @@ static void print_var_int(const char *var, int val)
 static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
 {
        int cnt, flags = info->flags;
-       char hex[41] = "";
+       char hex[GIT_SHA1_HEXSZ + 1] = "";
        struct commit_list *tried;
        struct rev_info *revs = info->revs;
 
@@ -242,7 +242,7 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
                cnt = reaches;
 
        if (revs->commits)
-               strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
+               sha1_to_hex_r(hex, revs->commits->item->object.sha1);
 
        if (flags & BISECT_SHOW_ALL) {
                traverse_commit_list(revs, show_commit, show_object, info);
index 408ce7030731f765228de93066a815bd4db59e30..092b59b0b3c0782a9802b9c3200d5523ac4e2e3e 100644 (file)
@@ -743,6 +743,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        fake_av[1] = NULL;
                        av = fake_av;
                        ac = 1;
+                       if (!*av)
+                               die("no branches given, and HEAD is not valid");
                }
                if (ac != 1)
                        die("--reflog option needs one branch name");
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
new file mode 100644 (file)
index 0000000..f4c3eff
--- /dev/null
@@ -0,0 +1,282 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "quote.h"
+#include "pathspec.h"
+#include "dir.h"
+#include "utf8.h"
+#include "submodule.h"
+#include "submodule-config.h"
+#include "string-list.h"
+#include "run-command.h"
+
+struct module_list {
+       const struct cache_entry **entries;
+       int alloc, nr;
+};
+#define MODULE_LIST_INIT { NULL, 0, 0 }
+
+static int module_list_compute(int argc, const char **argv,
+                              const char *prefix,
+                              struct pathspec *pathspec,
+                              struct module_list *list)
+{
+       int i, result = 0;
+       char *max_prefix, *ps_matched = NULL;
+       int max_prefix_len;
+       parse_pathspec(pathspec, 0,
+                      PATHSPEC_PREFER_FULL |
+                      PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+                      prefix, argv);
+
+       /* Find common prefix for all pathspec's */
+       max_prefix = common_prefix(pathspec);
+       max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
+
+       if (pathspec->nr)
+               ps_matched = xcalloc(pathspec->nr, 1);
+
+       if (read_cache() < 0)
+               die(_("index file corrupt"));
+
+       for (i = 0; i < active_nr; i++) {
+               const struct cache_entry *ce = active_cache[i];
+
+               if (!S_ISGITLINK(ce->ce_mode) ||
+                   !match_pathspec(pathspec, ce->name, ce_namelen(ce),
+                                   max_prefix_len, ps_matched, 1))
+                       continue;
+
+               ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
+               list->entries[list->nr++] = ce;
+               while (i + 1 < active_nr &&
+                      !strcmp(ce->name, active_cache[i + 1]->name))
+                       /*
+                        * Skip entries with the same name in different stages
+                        * to make sure an entry is returned only once.
+                        */
+                       i++;
+       }
+       free(max_prefix);
+
+       if (ps_matched && report_path_error(ps_matched, pathspec, prefix))
+               result = -1;
+
+       free(ps_matched);
+
+       return result;
+}
+
+static int module_list(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+
+       struct option module_list_options[] = {
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("alternative anchor for relative paths")),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper list [--prefix=<path>] [<path>...]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_list_options,
+                            git_submodule_helper_usage, 0);
+
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) {
+               printf("#unmatched\n");
+               return 1;
+       }
+
+       for (i = 0; i < list.nr; i++) {
+               const struct cache_entry *ce = list.entries[i];
+
+               if (ce_stage(ce))
+                       printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1));
+               else
+                       printf("%06o %s %d\t", ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce));
+
+               utf8_fprintf(stdout, "%s\n", ce->name);
+       }
+       return 0;
+}
+
+static int module_name(int argc, const char **argv, const char *prefix)
+{
+       const struct submodule *sub;
+
+       if (argc != 2)
+               usage(_("git submodule--helper name <path>"));
+
+       gitmodules_config();
+       sub = submodule_from_path(null_sha1, argv[1]);
+
+       if (!sub)
+               die(_("no submodule mapping found in .gitmodules for path '%s'"),
+                   argv[1]);
+
+       printf("%s\n", sub->name);
+
+       return 0;
+}
+static int clone_submodule(const char *path, const char *gitdir, const char *url,
+                          const char *depth, const char *reference, int quiet)
+{
+       struct child_process cp;
+       child_process_init(&cp);
+
+       argv_array_push(&cp.args, "clone");
+       argv_array_push(&cp.args, "--no-checkout");
+       if (quiet)
+               argv_array_push(&cp.args, "--quiet");
+       if (depth && *depth)
+               argv_array_pushl(&cp.args, "--depth", depth, NULL);
+       if (reference && *reference)
+               argv_array_pushl(&cp.args, "--reference", reference, NULL);
+       if (gitdir && *gitdir)
+               argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
+
+       argv_array_push(&cp.args, url);
+       argv_array_push(&cp.args, path);
+
+       cp.git_cmd = 1;
+       cp.env = local_repo_env;
+       cp.no_stdin = 1;
+
+       return run_command(&cp);
+}
+
+static int module_clone(int argc, const char **argv, const char *prefix)
+{
+       const char *path = NULL, *name = NULL, *url = NULL;
+       const char *reference = NULL, *depth = NULL;
+       int quiet = 0;
+       FILE *submodule_dot_git;
+       char *sm_gitdir, *cwd, *p;
+       struct strbuf rel_path = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT;
+
+       struct option module_clone_options[] = {
+               OPT_STRING(0, "prefix", &prefix,
+                          N_("path"),
+                          N_("alternative anchor for relative paths")),
+               OPT_STRING(0, "path", &path,
+                          N_("path"),
+                          N_("where the new submodule will be cloned to")),
+               OPT_STRING(0, "name", &name,
+                          N_("string"),
+                          N_("name of the new submodule")),
+               OPT_STRING(0, "url", &url,
+                          N_("string"),
+                          N_("url where to clone the submodule from")),
+               OPT_STRING(0, "reference", &reference,
+                          N_("string"),
+                          N_("reference repository")),
+               OPT_STRING(0, "depth", &depth,
+                          N_("string"),
+                          N_("depth for shallow clones")),
+               OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
+                  "[--reference <repository>] [--name <name>] [--url <url>]"
+                  "[--depth <depth>] [--] [<path>...]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_clone_options,
+                            git_submodule_helper_usage, 0);
+
+       strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
+       sm_gitdir = strbuf_detach(&sb, NULL);
+
+       if (!file_exists(sm_gitdir)) {
+               if (safe_create_leading_directories_const(sm_gitdir) < 0)
+                       die(_("could not create directory '%s'"), sm_gitdir);
+               if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet))
+                       die(_("clone of '%s' into submodule path '%s' failed"),
+                           url, path);
+       } else {
+               if (safe_create_leading_directories_const(path) < 0)
+                       die(_("could not create directory '%s'"), path);
+               strbuf_addf(&sb, "%s/index", sm_gitdir);
+               unlink_or_warn(sb.buf);
+               strbuf_reset(&sb);
+       }
+
+       /* Write a .git file in the submodule to redirect to the superproject. */
+       if (safe_create_leading_directories_const(path) < 0)
+               die(_("could not create directory '%s'"), path);
+
+       if (path && *path)
+               strbuf_addf(&sb, "%s/.git", path);
+       else
+               strbuf_addstr(&sb, ".git");
+
+       if (safe_create_leading_directories_const(sb.buf) < 0)
+               die(_("could not create leading directories of '%s'"), sb.buf);
+       submodule_dot_git = fopen(sb.buf, "w");
+       if (!submodule_dot_git)
+               die_errno(_("cannot open file '%s'"), sb.buf);
+
+       fprintf(submodule_dot_git, "gitdir: %s\n",
+               relative_path(sm_gitdir, path, &rel_path));
+       if (fclose(submodule_dot_git))
+               die(_("could not close file %s"), sb.buf);
+       strbuf_reset(&sb);
+       strbuf_reset(&rel_path);
+
+       cwd = xgetcwd();
+       /* Redirect the worktree of the submodule in the superproject's config */
+       if (!is_absolute_path(sm_gitdir)) {
+               strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir);
+               free(sm_gitdir);
+               sm_gitdir = strbuf_detach(&sb, NULL);
+       }
+
+       strbuf_addf(&sb, "%s/%s", cwd, path);
+       p = git_pathdup_submodule(path, "config");
+       if (!p)
+               die(_("could not get submodule directory for '%s'"), path);
+       git_config_set_in_file(p, "core.worktree",
+                              relative_path(sb.buf, sm_gitdir, &rel_path));
+       strbuf_release(&sb);
+       strbuf_release(&rel_path);
+       free(sm_gitdir);
+       free(cwd);
+       free(p);
+       return 0;
+}
+
+struct cmd_struct {
+       const char *cmd;
+       int (*fn)(int, const char **, const char *);
+};
+
+static struct cmd_struct commands[] = {
+       {"list", module_list},
+       {"name", module_name},
+       {"clone", module_clone},
+};
+
+int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       if (argc < 2)
+               die(_("fatal: submodule--helper subcommand must be "
+                     "called with a subcommand"));
+
+       for (i = 0; i < ARRAY_SIZE(commands); i++)
+               if (!strcmp(argv[1], commands[i].cmd))
+                       return commands[i].fn(argc - 1, argv + 1, prefix);
+
+       die(_("fatal: '%s' is not a valid submodule--helper "
+             "subcommand"), argv[1]);
+}
index cba0e2266623d57fe96fcec1910c562bf424d143..9e17dca7cac30738c966d0e23258f56c0cb9bc70 100644 (file)
 #include "gpg-interface.h"
 #include "sha1-array.h"
 #include "column.h"
+#include "ref-filter.h"
 
 static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
-               "\n\t\t[<pattern>...]"),
+               "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
        N_("git tag -v <tagname>..."),
        NULL
 };
 
-#define STRCMP_SORT     0      /* must be zero */
-#define VERCMP_SORT     1
-#define SORT_MASK       0x7fff
-#define REVERSE_SORT    0x8000
-
-static int tag_sort;
-
-struct tag_filter {
-       const char **patterns;
-       int lines;
-       int sort;
-       struct string_list tags;
-       struct commit_list *with_commit;
-};
-
-static struct sha1_array points_at;
 static unsigned int colopts;
 
-static int match_pattern(const char **patterns, const char *ref)
-{
-       /* no pattern means match everything */
-       if (!*patterns)
-               return 1;
-       for (; *patterns; patterns++)
-               if (!wildmatch(*patterns, ref, 0, NULL))
-                       return 1;
-       return 0;
-}
-
-static const unsigned char *match_points_at(const char *refname,
-                                           const unsigned char *sha1)
-{
-       const unsigned char *tagged_sha1 = NULL;
-       struct object *obj;
-
-       if (sha1_array_lookup(&points_at, sha1) >= 0)
-               return sha1;
-       obj = parse_object(sha1);
-       if (!obj)
-               die(_("malformed object at '%s'"), refname);
-       if (obj->type == OBJ_TAG)
-               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
-       if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
-               return tagged_sha1;
-       return NULL;
-}
-
-static int in_commit_list(const struct commit_list *want, struct commit *c)
-{
-       for (; want; want = want->next)
-               if (!hashcmp(want->item->object.sha1, c->object.sha1))
-                       return 1;
-       return 0;
-}
-
-enum contains_result {
-       CONTAINS_UNKNOWN = -1,
-       CONTAINS_NO = 0,
-       CONTAINS_YES = 1
-};
-
-/*
- * Test whether the candidate or one of its parents is contained in the list.
- * Do not recurse to find out, though, but return -1 if inconclusive.
- */
-static enum contains_result contains_test(struct commit *candidate,
-                           const struct commit_list *want)
-{
-       /* was it previously marked as containing a want commit? */
-       if (candidate->object.flags & TMP_MARK)
-               return 1;
-       /* or marked as not possibly containing a want commit? */
-       if (candidate->object.flags & UNINTERESTING)
-               return 0;
-       /* or are we it? */
-       if (in_commit_list(want, candidate)) {
-               candidate->object.flags |= TMP_MARK;
-               return 1;
-       }
-
-       if (parse_commit(candidate) < 0)
-               return 0;
-
-       return -1;
-}
-
-/*
- * Mimicking the real stack, this stack lives on the heap, avoiding stack
- * overflows.
- *
- * At each recursion step, the stack items points to the commits whose
- * ancestors are to be inspected.
- */
-struct stack {
-       int nr, alloc;
-       struct stack_entry {
-               struct commit *commit;
-               struct commit_list *parents;
-       } *stack;
-};
-
-static void push_to_stack(struct commit *candidate, struct stack *stack)
-{
-       int index = stack->nr++;
-       ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
-       stack->stack[index].commit = candidate;
-       stack->stack[index].parents = candidate->parents;
-}
-
-static enum contains_result contains(struct commit *candidate,
-               const struct commit_list *want)
-{
-       struct stack stack = { 0, 0, NULL };
-       int result = contains_test(candidate, want);
-
-       if (result != CONTAINS_UNKNOWN)
-               return result;
-
-       push_to_stack(candidate, &stack);
-       while (stack.nr) {
-               struct stack_entry *entry = &stack.stack[stack.nr - 1];
-               struct commit *commit = entry->commit;
-               struct commit_list *parents = entry->parents;
-
-               if (!parents) {
-                       commit->object.flags |= UNINTERESTING;
-                       stack.nr--;
-               }
-               /*
-                * If we just popped the stack, parents->item has been marked,
-                * therefore contains_test will return a meaningful 0 or 1.
-                */
-               else switch (contains_test(parents->item, want)) {
-               case CONTAINS_YES:
-                       commit->object.flags |= TMP_MARK;
-                       stack.nr--;
-                       break;
-               case CONTAINS_NO:
-                       entry->parents = parents->next;
-                       break;
-               case CONTAINS_UNKNOWN:
-                       push_to_stack(parents->item, &stack);
-                       break;
-               }
-       }
-       free(stack.stack);
-       return contains_test(candidate, want);
-}
-
-static void show_tag_lines(const struct object_id *oid, int lines)
+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
 {
+       struct ref_array array;
+       char *to_free = NULL;
        int i;
-       unsigned long size;
-       enum object_type type;
-       char *buf, *sp, *eol;
-       size_t len;
-
-       buf = read_sha1_file(oid->hash, &type, &size);
-       if (!buf)
-               die_errno("unable to read object %s", oid_to_hex(oid));
-       if (type != OBJ_COMMIT && type != OBJ_TAG)
-               goto free_return;
-       if (!size)
-               die("an empty %s object %s?",
-                   typename(type), oid_to_hex(oid));
-
-       /* skip header */
-       sp = strstr(buf, "\n\n");
-       if (!sp)
-               goto free_return;
-
-       /* only take up to "lines" lines, and strip the signature from a tag */
-       if (type == OBJ_TAG)
-               size = parse_signature(buf, size);
-       for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
-               if (i)
-                       printf("\n    ");
-               eol = memchr(sp, '\n', size - (sp - buf));
-               len = eol ? eol - sp : size - (sp - buf);
-               fwrite(sp, len, 1, stdout);
-               if (!eol)
-                       break;
-               sp = eol + 1;
-       }
-free_return:
-       free(buf);
-}
-
-static int show_reference(const char *refname, const struct object_id *oid,
-                         int flag, void *cb_data)
-{
-       struct tag_filter *filter = cb_data;
 
-       if (match_pattern(filter->patterns, refname)) {
-               if (filter->with_commit) {
-                       struct commit *commit;
+       memset(&array, 0, sizeof(array));
 
-                       commit = lookup_commit_reference_gently(oid->hash, 1);
-                       if (!commit)
-                               return 0;
-                       if (!contains(commit, filter->with_commit))
-                               return 0;
-               }
-
-               if (points_at.nr && !match_points_at(refname, oid->hash))
-                       return 0;
+       if (filter->lines == -1)
+               filter->lines = 0;
 
-               if (!filter->lines) {
-                       if (filter->sort)
-                               string_list_append(&filter->tags, refname);
-                       else
-                               printf("%s\n", refname);
-                       return 0;
-               }
-               printf("%-15s ", refname);
-               show_tag_lines(oid, filter->lines);
-               putchar('\n');
+       if (!format) {
+               if (filter->lines) {
+                       to_free = xstrfmt("%s %%(contents:lines=%d)",
+                                         "%(align:15)%(refname:short)%(end)",
+                                         filter->lines);
+                       format = to_free;
+               } else
+                       format = "%(refname:short)";
        }
 
-       return 0;
-}
+       verify_ref_format(format);
+       filter_refs(&array, filter, FILTER_REFS_TAGS);
+       ref_array_sort(sorting, &array);
 
-static int sort_by_version(const void *a_, const void *b_)
-{
-       const struct string_list_item *a = a_;
-       const struct string_list_item *b = b_;
-       return versioncmp(a->string, b->string);
-}
+       for (i = 0; i < array.nr; i++)
+               show_ref_array_item(array.items[i], format, 0);
+       ref_array_clear(&array);
+       free(to_free);
 
-static int list_tags(const char **patterns, int lines,
-                    struct commit_list *with_commit, int sort)
-{
-       struct tag_filter filter;
-
-       filter.patterns = patterns;
-       filter.lines = lines;
-       filter.sort = sort;
-       filter.with_commit = with_commit;
-       memset(&filter.tags, 0, sizeof(filter.tags));
-       filter.tags.strdup_strings = 1;
-
-       for_each_tag_ref(show_reference, (void *)&filter);
-       if (sort) {
-               int i;
-               if ((sort & SORT_MASK) == VERCMP_SORT)
-                       qsort(filter.tags.items, filter.tags.nr,
-                             sizeof(struct string_list_item), sort_by_version);
-               if (sort & REVERSE_SORT)
-                       for (i = filter.tags.nr - 1; i >= 0; i--)
-                               printf("%s\n", filter.tags.items[i].string);
-               else
-                       for (i = 0; i < filter.tags.nr; i++)
-                               printf("%s\n", filter.tags.items[i].string);
-               string_list_clear(&filter.tags, 0);
-       }
        return 0;
 }
 
@@ -348,35 +126,26 @@ static const char tag_template_nocleanup[] =
        "Lines starting with '%c' will be kept; you may remove them"
        " yourself if you want to.\n");
 
-/*
- * Parse a sort string, and return 0 if parsed successfully. Will return
- * non-zero when the sort string does not parse into a known type. If var is
- * given, the error message becomes a warning and includes information about
- * the configuration value.
- */
-static int parse_sort_string(const char *var, const char *arg, int *sort)
+/* Parse arg given and add it the ref_sorting array */
+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
 {
-       int type = 0, flags = 0;
+       struct ref_sorting *s;
+       int len;
 
-       if (skip_prefix(arg, "-", &arg))
-               flags |= REVERSE_SORT;
+       s = xcalloc(1, sizeof(*s));
+       s->next = *sorting_tail;
+       *sorting_tail = s;
 
-       if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
-               type = VERCMP_SORT;
-       else
-               type = STRCMP_SORT;
-
-       if (strcmp(arg, "refname")) {
-               if (!var)
-                       return error(_("unsupported sort specification '%s'"), arg);
-               else {
-                       warning(_("unsupported sort specification '%s' in variable '%s'"),
-                               var, arg);
-                       return -1;
-               }
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
        }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
 
-       *sort = (type | flags);
+       len = strlen(arg);
+       s->atom = parse_ref_filter_atom(arg, arg+len);
 
        return 0;
 }
@@ -384,11 +153,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort)
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
        int status;
+       struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
 
        if (!strcmp(var, "tag.sort")) {
                if (!value)
                        return config_error_nonbool(var);
-               parse_sort_string(var, value, &tag_sort);
+               parse_sorting_string(value, sorting_tail);
                return 0;
        }
 
@@ -546,30 +316,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
        return check_refname_format(sb->buf, 0);
 }
 
-static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
-                       const char *arg, int unset)
-{
-       unsigned char sha1[20];
-
-       if (unset) {
-               sha1_array_clear(&points_at);
-               return 0;
-       }
-       if (!arg)
-               return error(_("switch 'points-at' requires an object"));
-       if (get_sha1(arg, sha1))
-               return error(_("malformed object name '%s'"), arg);
-       sha1_array_append(&points_at, sha1);
-       return 0;
-}
-
-static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
-{
-       int *sort = opt->value;
-
-       return parse_sort_string(NULL, arg, sort);
-}
-
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -578,17 +324,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        const char *object_ref, *tag;
        struct create_tag_options opt;
        char *cleanup_arg = NULL;
-       int annotate = 0, force = 0, lines = -1;
        int create_reflog = 0;
+       int annotate = 0, force = 0;
        int cmdmode = 0;
        const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
-       struct commit_list *with_commit = NULL;
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
+       struct ref_filter filter;
+       static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+       const char *format = NULL;
        struct option options[] = {
                OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
-               { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
+               { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
                                N_("print <n> lines of each tag message"),
                                PARSE_OPT_OPTARG, NULL, 1 },
                OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
@@ -610,32 +358,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
                OPT_GROUP(N_("Tag listing options")),
                OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
+               OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
+               OPT_MERGED(&filter, N_("print only tags that are merged")),
+               OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
+               OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+                            N_("field name to sort on"), &parse_opt_ref_sorting),
                {
-                       OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
-                       PARSE_OPT_NONEG, parse_opt_sort
-               },
-               {
-                       OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
-                       N_("print only tags that contain the commit"),
-                       PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
-                       parse_opt_with_commit, (intptr_t)"HEAD",
-               },
-               {
-                       OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
-                       N_("print only tags of the object"), 0, parse_opt_points_at
+                       OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+                       N_("print only tags of the object"), 0, parse_opt_object_name
                },
+               OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_END()
        };
 
-       git_config(git_tag_config, NULL);
+       git_config(git_tag_config, sorting_tail);
 
        memset(&opt, 0, sizeof(opt));
+       memset(&filter, 0, sizeof(filter));
+       filter.lines = -1;
 
        argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
 
@@ -652,11 +393,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                usage_with_options(git_tag_usage, options);
 
        finalize_colopts(&colopts, -1);
-       if (cmdmode == 'l' && lines != -1) {
+       if (cmdmode == 'l' && filter.lines != -1) {
                if (explicitly_enable_column(colopts))
                        die(_("--column and -n are incompatible"));
                colopts = 0;
        }
+       if (!sorting)
+               sorting = ref_default_sorting();
        if (cmdmode == 'l') {
                int ret;
                if (column_active(colopts)) {
@@ -665,19 +408,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
-               if (lines != -1 && tag_sort)
-                       die(_("--sort and -n are incompatible"));
-               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
+               filter.name_patterns = argv;
+               ret = list_tags(&filter, sorting, format);
                if (column_active(colopts))
                        stop_column_filter();
                return ret;
        }
-       if (lines != -1)
+       if (filter.lines != -1)
                die(_("-n option is only allowed with -l."));
-       if (with_commit)
+       if (filter.with_commit)
                die(_("--contains option is only allowed with -l."));
-       if (points_at.nr)
+       if (filter.points_at.nr)
                die(_("--points-at option is only allowed with -l."));
+       if (filter.merge_commit)
+               die(_("--merged and --no-merged option are only allowed with -l"));
        if (cmdmode == 'd')
                return for_each_tag_name(argv, delete_tag);
        if (cmdmode == 'v')
index 19200291a2632554223b1a675d5324b4682248ab..6fc6bcdf7f014a52703fcd44677a705c824bc0b0 100644 (file)
@@ -12,7 +12,7 @@ static char *create_temp_file(unsigned char *sha1)
        if (!buf || type != OBJ_BLOB)
                die("unable to read blob object %s", sha1_to_hex(sha1));
 
-       strcpy(path, ".merge_file_XXXXXX");
+       xsnprintf(path, sizeof(path), ".merge_file_XXXXXX");
        fd = xmkstemp(path);
        if (write_in_full(fd, buf, size) != size)
                die_errno("unable to write temp-file");
index 32ab94cd06b43cd05316f9a16981adc883c0d066..dbfe14f3fec3f8122e4855727bd7d1c3e4b3073e 100644 (file)
@@ -49,15 +49,14 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
 __attribute__((format (printf, 1, 2)))
 static void error_clnt(const char *fmt, ...)
 {
-       char buf[1024];
+       struct strbuf buf = STRBUF_INIT;
        va_list params;
-       int len;
 
        va_start(params, fmt);
-       len = vsprintf(buf, fmt, params);
+       strbuf_vaddf(&buf, fmt, params);
        va_end(params);
-       send_sideband(1, 3, buflen, LARGE_PACKET_MAX);
-       die("sent error to the client: %s", buf);
+       send_sideband(1, 3, buf.buf, buf.len, LARGE_PACKET_MAX);
+       die("sent error to the client: %s", buf.buf);
 }
 
 static ssize_t process_input(int child_fd, int band)
index 7cffc3a579e4a02c1422284092cef18cd4b7d059..4347f5c76aa72873eb5ccb090584428399f3159c 100644 (file)
@@ -200,8 +200,8 @@ static int deflate_to_pack(struct bulk_checkin_state *state,
        if (seekback == (off_t) -1)
                return error("cannot find the current offset");
 
-       header_len = sprintf((char *)obuf, "%s %" PRIuMAX,
-                            typename(type), (uintmax_t)size) + 1;
+       header_len = xsnprintf((char *)obuf, sizeof(obuf), "%s %" PRIuMAX,
+                              typename(type), (uintmax_t)size) + 1;
        git_SHA1_Init(&ctx);
        git_SHA1_Update(&ctx, obuf, header_len);
 
diff --git a/cache.h b/cache.h
index 79066e57dc806d118366d9807e0af338b72e2fb2..f735d14dc29310f07ee48ad54d1584c881f97110 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -443,6 +443,7 @@ extern char *get_object_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
 extern int set_git_dir(const char *path);
+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);
@@ -723,6 +724,8 @@ extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
+extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
+       __attribute__((format (printf, 2, 3)));
 extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
                                      const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
@@ -783,7 +786,24 @@ extern char *sha1_pack_name(const unsigned char *sha1);
  */
 extern char *sha1_pack_index_name(const unsigned char *sha1);
 
-extern const char *find_unique_abbrev(const unsigned char *sha1, int);
+/*
+ * Return an abbreviated sha1 unique within this repository's object database.
+ * 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 `_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
+ * written (excluding the NUL terminator).
+ *
+ * Note that while this version avoids the static buffer, it is not fully
+ * reentrant, as it calls into other non-reentrant git code.
+ */
+extern const char *find_unique_abbrev(const unsigned char *sha1, int len);
+extern int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len);
+
 extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
 
 static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
@@ -1065,6 +1085,18 @@ extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern int get_oid_hex(const char *hex, struct object_id *sha1);
 
+/*
+ * Convert a binary sha1 to its hex equivalent. The `_r` variant is reentrant,
+ * and writes the NUL-terminated output to the buffer `out`, which must be at
+ * least `GIT_SHA1_HEXSZ + 1` bytes, and returns a pointer to out for
+ * convenience.
+ *
+ * The non-`_r` variant returns a static buffer, but uses a ring of 4
+ * buffers, making it safe to make multiple calls for a single statement, like:
+ *
+ *   printf("%s -> %s", sha1_to_hex(one), sha1_to_hex(two));
+ */
+extern char *sha1_to_hex_r(char *out, const unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern char *oid_to_hex(const struct object_id *oid);  /* same static buffer as sha1_to_hex */
 
@@ -1091,7 +1123,6 @@ struct date_mode {
                DATE_NORMAL = 0,
                DATE_RELATIVE,
                DATE_SHORT,
-               DATE_LOCAL,
                DATE_ISO8601,
                DATE_ISO8601_STRICT,
                DATE_RFC2822,
@@ -1099,6 +1130,7 @@ struct date_mode {
                DATE_RAW
        } type;
        const char *strftime_fmt;
+       int local;
 };
 
 /*
@@ -1275,10 +1307,11 @@ extern void close_pack_index(struct packed_git *);
 
 extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
 extern void close_pack_windows(struct packed_git *);
+extern void close_all_packs(void);
 extern void unuse_pack(struct pack_window **);
 extern void free_pack_by_name(const char *);
 extern void clear_delta_base_cache(void);
-extern struct packed_git *add_packed_git(const char *, int, int);
+extern struct packed_git *add_packed_git(const char *path, size_t path_len, int local);
 
 /*
  * Return the SHA-1 of the nth object within the specified packfile.
diff --git a/color.c b/color.c
index 9027352ad7786746537adea9007bd46f46edbb4e..8f85153d0d2c1f4bf9340ced9371797eb3c92de9 100644 (file)
--- a/color.c
+++ b/color.c
@@ -145,27 +145,34 @@ int color_parse(const char *value, char *dst)
        return color_parse_mem(value, strlen(value), dst);
 }
 
+void color_set(char *dst, const char *color_bytes)
+{
+       xsnprintf(dst, COLOR_MAXLEN, "%s", color_bytes);
+}
+
 /*
  * Write the ANSI color codes for "c" to "out"; the string should
  * already have the ANSI escape code in it. "out" should have enough
  * space in it to fit any color.
  */
-static char *color_output(char *out, const struct color *c, char type)
+static char *color_output(char *out, int len, const struct color *c, char type)
 {
        switch (c->type) {
        case COLOR_UNSPECIFIED:
        case COLOR_NORMAL:
                break;
        case COLOR_ANSI:
+               if (len < 2)
+                       die("BUG: color parsing ran out of space");
                *out++ = type;
                *out++ = '0' + c->value;
                break;
        case COLOR_256:
-               out += sprintf(out, "%c8;5;%d", type, c->value);
+               out += xsnprintf(out, len, "%c8;5;%d", type, c->value);
                break;
        case COLOR_RGB:
-               out += sprintf(out, "%c8;2;%d;%d;%d", type,
-                              c->red, c->green, c->blue);
+               out += xsnprintf(out, len, "%c8;2;%d;%d;%d", type,
+                                c->red, c->green, c->blue);
                break;
        }
        return out;
@@ -180,12 +187,13 @@ int color_parse_mem(const char *value, int value_len, char *dst)
 {
        const char *ptr = value;
        int len = value_len;
+       char *end = dst + COLOR_MAXLEN;
        unsigned int attr = 0;
        struct color fg = { COLOR_UNSPECIFIED };
        struct color bg = { COLOR_UNSPECIFIED };
 
        if (!strncasecmp(value, "reset", len)) {
-               strcpy(dst, GIT_COLOR_RESET);
+               xsnprintf(dst, end - dst, GIT_COLOR_RESET);
                return 0;
        }
 
@@ -224,12 +232,19 @@ int color_parse_mem(const char *value, int value_len, char *dst)
                        goto bad;
        }
 
+#undef OUT
+#define OUT(x) do { \
+       if (dst == end) \
+               die("BUG: color parsing ran out of space"); \
+       *dst++ = (x); \
+} while(0)
+
        if (attr || !color_empty(&fg) || !color_empty(&bg)) {
                int sep = 0;
                int i;
 
-               *dst++ = '\033';
-               *dst++ = '[';
+               OUT('\033');
+               OUT('[');
 
                for (i = 0; attr; i++) {
                        unsigned bit = (1 << i);
@@ -237,27 +252,28 @@ int color_parse_mem(const char *value, int value_len, char *dst)
                                continue;
                        attr &= ~bit;
                        if (sep++)
-                               *dst++ = ';';
-                       dst += sprintf(dst, "%d", i);
+                               OUT(';');
+                       dst += xsnprintf(dst, end - dst, "%d", i);
                }
                if (!color_empty(&fg)) {
                        if (sep++)
-                               *dst++ = ';';
+                               OUT(';');
                        /* foreground colors are all in the 3x range */
-                       dst = color_output(dst, &fg, '3');
+                       dst = color_output(dst, end - dst, &fg, '3');
                }
                if (!color_empty(&bg)) {
                        if (sep++)
-                               *dst++ = ';';
+                               OUT(';');
                        /* background colors are all in the 4x range */
-                       dst = color_output(dst, &bg, '4');
+                       dst = color_output(dst, end - dst, &bg, '4');
                }
-               *dst++ = 'm';
+               OUT('m');
        }
-       *dst = 0;
+       OUT(0);
        return 0;
 bad:
        return error(_("invalid color value: %.*s"), value_len, value);
+#undef OUT
 }
 
 int git_config_colorbool(const char *var, const char *value)
diff --git a/color.h b/color.h
index 7fe77fb55edac6aa8a284f6c6671c54da79e2591..e155d13f784362a5fdd993e1e383e660fa324c30 100644 (file)
--- a/color.h
+++ b/color.h
@@ -75,6 +75,13 @@ extern int color_stdout_is_tty;
 int git_color_config(const char *var, const char *value, void *cb);
 int git_color_default_config(const char *var, const char *value, void *cb);
 
+/*
+ * Set the color buffer (which must be COLOR_MAXLEN bytes)
+ * to the raw color bytes; this is useful for initializing
+ * default color variables.
+ */
+void color_set(char *dst, const char *color_bytes);
+
 int git_config_colorbool(const char *var, const char *value);
 int want_color(int var);
 int color_parse(const char *value, char *dst);
index 069c555da47ea168eea937fcc2d788294bf92ef5..b85a2fa9561f48657bc976a063560ddf7e00d449 100644 (file)
@@ -16,6 +16,6 @@ const char *githstrerror(int err)
        case TRY_AGAIN:
                return "Non-authoritative \"host not found\", or SERVERFAIL";
        }
-       sprintf(buffer, "Name resolution error %d", err);
+       snprintf(buffer, sizeof(buffer), "Name resolution error %d", err);
        return buffer;
 }
index 90b7cc45f33411bbc39cd731ca3e43288cb40b69..68307262be04955993590e32daecc08fcf7eda60 100644 (file)
@@ -53,11 +53,11 @@ inet_ntop4(const u_char *src, char *dst, size_t size)
        nprinted = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]);
        if (nprinted < 0)
                return (NULL);  /* we assume "errno" was set by "snprintf()" */
-       if ((size_t)nprinted > size) {
+       if ((size_t)nprinted >= size) {
                errno = ENOSPC;
                return (NULL);
        }
-       strcpy(dst, tmp);
+       strlcpy(dst, tmp, size);
        return (dst);
 }
 
@@ -154,7 +154,7 @@ inet_ntop6(const u_char *src, char *dst, size_t size)
                errno = ENOSPC;
                return (NULL);
        }
-       strcpy(dst, tmp);
+       strlcpy(dst, tmp, size);
        return (dst);
 }
 #endif
index f74da235f598d8b0346fac8b48c4155f55ae5b81..a168800ae0d1a6ffe741dfda26dbe5133cc4c4fc 100644 (file)
@@ -2133,9 +2133,11 @@ int uname(struct utsname *buf)
 {
        DWORD v = GetVersion();
        memset(buf, 0, sizeof(*buf));
-       strcpy(buf->sysname, "Windows");
-       sprintf(buf->release, "%u.%u", v & 0xff, (v >> 8) & 0xff);
+       xsnprintf(buf->sysname, sizeof(buf->sysname), "Windows");
+       xsnprintf(buf->release, sizeof(buf->release),
+                "%u.%u", v & 0xff, (v >> 8) & 0xff);
        /* assuming NT variants only.. */
-       sprintf(buf->version, "%u", (v >> 16) & 0x7fff);
+       xsnprintf(buf->version, sizeof(buf->version),
+                 "%u", (v >> 16) & 0x7fff);
        return 0;
 }
index 609ebba1258eac47983f2b9da5d582a5d5af5482..a0a16eb1bbfb22c13f29c15d2fd68ccc9c7e01ad 100644 (file)
@@ -957,8 +957,9 @@ char *strdup(const char *s1)
 {
        char *s2 = 0;
        if (s1) {
-               s2 = malloc(strlen(s1) + 1);
-               strcpy(s2, s1);
+               size_t len = strlen(s1) + 1;
+               s2 = malloc(len);
+               memcpy(s2, s1, len);
        }
        return s2;
 }
index 95fe849e42d3b9e1f7bc9590fcfac93016f38146..079070ff1d4bd629712eff5f6f6e239898640963 100644 (file)
@@ -36,24 +36,26 @@ static size_t has_non_ascii(const char *s, size_t maxlen, size_t *strlen_c)
 }
 
 
-void probe_utf8_pathname_composition(char *path, int len)
+void probe_utf8_pathname_composition(void)
 {
+       struct strbuf path = STRBUF_INIT;
        static const char *auml_nfc = "\xc3\xa4";
        static const char *auml_nfd = "\x61\xcc\x88";
        int output_fd;
        if (precomposed_unicode != -1)
                return; /* We found it defined in the global config, respect it */
-       strcpy(path + len, auml_nfc);
-       output_fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0600);
+       git_path_buf(&path, "%s", auml_nfc);
+       output_fd = open(path.buf, O_CREAT|O_EXCL|O_RDWR, 0600);
        if (output_fd >= 0) {
                close(output_fd);
-               strcpy(path + len, auml_nfd);
-               precomposed_unicode = access(path, R_OK) ? 0 : 1;
+               git_path_buf(&path, "%s", auml_nfd);
+               precomposed_unicode = access(path.buf, R_OK) ? 0 : 1;
                git_config_set("core.precomposeunicode", precomposed_unicode ? "true" : "false");
-               strcpy(path + len, auml_nfc);
-               if (unlink(path))
-                       die_errno(_("failed to unlink '%s'"), path);
+               git_path_buf(&path, "%s", auml_nfc);
+               if (unlink(path.buf))
+                       die_errno(_("failed to unlink '%s'"), path.buf);
        }
+       strbuf_release(&path);
 }
 
 
@@ -139,9 +141,8 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir)
                                size_t inleft = namelenz;
                                char *outpos = &prec_dir->dirent_nfc->d_name[0];
                                size_t outsz = prec_dir->dirent_nfc->max_name_len;
-                               size_t cnt;
                                errno = 0;
-                               cnt = iconv(prec_dir->ic_precompose, &cp, &inleft, &outpos, &outsz);
+                               iconv(prec_dir->ic_precompose, &cp, &inleft, &outpos, &outsz);
                                if (errno || inleft) {
                                        /*
                                         * iconv() failed and errno could be E2BIG, EILSEQ, EINVAL, EBADF
index 3b73585fc5d8c1f40f1c467829b67f439bca3483..a94e7c43422d87ae062842ae2c2aad430c4b13e4 100644 (file)
@@ -27,7 +27,7 @@ typedef struct {
 } PREC_DIR;
 
 void precompose_argv(int argc, const char **argv);
-void probe_utf8_pathname_composition(char *, int);
+void probe_utf8_pathname_composition(void);
 
 PREC_DIR *precompose_utf8_opendir(const char *dirname);
 struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *dirp);
index efc5bb3a4b63166eccb33f6ec6a8ad57e4c9ac36..ceff55bd67696403f374f68ae6d3db97f603aebe 100644 (file)
@@ -539,7 +539,7 @@ void winansi_init(void)
                return;
 
        /* create a named pipe to communicate with the console thread */
-       sprintf(name, "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId());
+       xsnprintf(name, sizeof(name), "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId());
        hwrite = CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND,
                PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL);
        if (hwrite == INVALID_HANDLE_VALUE)
index 943c43965e68fe39fa6e0ea266c3ac376f6e0125..f34dcaad20d66954c0c13a50bdcbd0f0312cc982 100644 (file)
@@ -166,7 +166,6 @@ endif
 ifeq ($(uname_O),Cygwin)
        ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4)
                NO_D_TYPE_IN_DIRENT = YesPlease
-               NO_D_INO_IN_DIRENT = YesPlease
                NO_STRCASESTR = YesPlease
                NO_MEMMEM = YesPlease
                NO_MKSTEMPS = YesPlease
@@ -370,7 +369,6 @@ ifeq ($(uname_S),Windows)
        NO_POSIX_GOODIES = UnfortunatelyYes
        NATIVE_CRLF = YesPlease
        DEFAULT_HELP_FORMAT = html
-       NO_D_INO_IN_DIRENT = YesPlease
 
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
@@ -520,7 +518,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_INET_NTOP = YesPlease
        NO_POSIX_GOODIES = UnfortunatelyYes
        DEFAULT_HELP_FORMAT = html
-       NO_D_INO_IN_DIRENT = YesPlease
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -D_USE_32BIT_TIME_T -DNOGDI -Icompat -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
index 14012fad720c1fdade1f305b1a7092abf2146ac1..3fcca618d252d999c69de2bb9372b5d7a4478014 100644 (file)
@@ -767,13 +767,6 @@ elif test x$ac_cv_member_struct_stat_st_mtim_tv_nsec != xyes; then
        GIT_CONF_SUBST([NO_NSEC])
 fi
 #
-# Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
-AC_CHECK_MEMBER(struct dirent.d_ino,
-[NO_D_INO_IN_DIRENT=],
-[NO_D_INO_IN_DIRENT=YesPlease],
-[#include <dirent.h>])
-GIT_CONF_SUBST([NO_D_INO_IN_DIRENT])
-#
 # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
 # d_type in struct dirent (latest Cygwin -- will be fixed soonish).
 AC_CHECK_MEMBER(struct dirent.d_type,
index 27a706f76621621a25b7e58188e5d1da9b9a2ccd..108f5ab60e0ab122f0a86612ba9e4875ded29f26 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -255,7 +255,7 @@ static const char *prot_name(enum protocol protocol)
                case PROTO_GIT:
                        return "git";
                default:
-                       return "unkown protocol";
+                       return "unknown protocol";
        }
 }
 
@@ -333,7 +333,7 @@ static const char *ai_name(const struct addrinfo *ai)
        static char addr[NI_MAXHOST];
        if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0,
                        NI_NUMERICHOST) != 0)
-               strcpy(addr, "(unknown)");
+               xsnprintf(addr, sizeof(addr), "(unknown)");
 
        return addr;
 }
@@ -724,10 +724,13 @@ struct child_process *git_connect(int fd[2], const char *url,
                strbuf_addch(&cmd, ' ');
                sq_quote_buf(&cmd, path);
 
+               /* remove repo-local variables from the environment */
+               conn->env = local_repo_env;
+               conn->use_shell = 1;
                conn->in = conn->out = -1;
                if (protocol == PROTO_SSH) {
                        const char *ssh;
-                       int putty, tortoiseplink = 0;
+                       int putty = 0, tortoiseplink = 0;
                        char *ssh_host = hostandport;
                        const char *port = NULL;
                        transport_check_allowed("ssh");
@@ -750,13 +753,17 @@ struct child_process *git_connect(int fd[2], const char *url,
                        }
 
                        ssh = getenv("GIT_SSH_COMMAND");
-                       if (ssh) {
-                               conn->use_shell = 1;
-                               putty = 0;
-                       } else {
+                       if (!ssh) {
                                const char *base;
                                char *ssh_dup;
 
+                               /*
+                                * GIT_SSH is the no-shell version of
+                                * GIT_SSH_COMMAND (and must remain so for
+                                * historical compatibility).
+                                */
+                               conn->use_shell = 0;
+
                                ssh = getenv("GIT_SSH");
                                if (!ssh)
                                        ssh = "ssh";
@@ -766,8 +773,9 @@ struct child_process *git_connect(int fd[2], const char *url,
 
                                tortoiseplink = !strcasecmp(base, "tortoiseplink") ||
                                        !strcasecmp(base, "tortoiseplink.exe");
-                               putty = !strcasecmp(base, "plink") ||
-                                       !strcasecmp(base, "plink.exe") || tortoiseplink;
+                               putty = tortoiseplink ||
+                                       !strcasecmp(base, "plink") ||
+                                       !strcasecmp(base, "plink.exe");
 
                                free(ssh_dup);
                        }
@@ -782,9 +790,6 @@ struct child_process *git_connect(int fd[2], const char *url,
                        }
                        argv_array_push(&conn->args, ssh_host);
                } else {
-                       /* remove repo-local variables from the environment */
-                       conn->env = local_repo_env;
-                       conn->use_shell = 1;
                        transport_check_allowed("file");
                }
                argv_array_push(&conn->args, cmd.buf);
index e8dc2e0e7d5d8fb37f818a05c3ef4c58f28850da..6b3a03f9b070e6451572db9b160b293044279fa3 100755 (executable)
@@ -69,7 +69,7 @@ as appropriate to mark resolution and make a commit.")"
 die_merge () {
     if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then
        die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).
-Please, commit your changes before you can merge.")"
+Please, commit your changes before merging.")"
     else
        die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
     fi
index 6bb12306b8210ea2d7ae58d6e997801ec44e1604..bc77e66b8555679ed479929f2e756152cdee19fc 100644 (file)
@@ -1,3 +1,44 @@
+Release 1.2.0
+=============
+
+* It is now possible to exclude some refs (e.g. exclude some branches
+  or tags). See refFilterDoSendRegex, refFilterDontSendRegex,
+  refFilterInclusionRegex and refFilterExclusionRegex.
+
+* New commitEmailFormat option which can be set to "html" to generate
+  simple colorized diffs using HTML for the commit emails.
+
+* git-multimail can now be ran as a Gerrit ref-updated hook, or from
+  Atlassian BitBucket Server (formerly known as Atlassian Stash).
+
+* The From: field is now more customizeable. It can be set
+  independently for refchange emails and commit emails (see
+  fromCommit, fromRefChange). The special values pusher and author can
+  be used in these configuration variable.
+
+* A new command-line option, --version, was added. The version is also
+  available in the X-Git-Multimail-Version header of sent emails.
+
+* Set X-Git-NotificationType header to differentiate the various types
+  of notifications. Current values are: diff, ref_changed_plus_diff,
+  ref_changed.
+
+* Preliminary support for Python 3. The testsuite passes with Python 3,
+  but it has not received as much testing as the Python 2 version yet.
+
+* Several encoding-related fixes. UTF-8 characters work in more
+  situations (but non-ascii characters in email address are still not
+  supported).
+
+* The testsuite and its documentation has been greatly improved.
+
+Plus all the bugfixes from version 1.1.1.
+
+This version has been tested with Python 2.4 and 2.6 to 3.5, and Git
+v1.7.10-406-gdc801e7, git-1.8.2.3 and 2.6.0. Git versions prior to
+v1.7.10-406-gdc801e7 probably work, but cannot run the testsuite
+properly.
+
 Release 1.1.1 (bugfix-only release)
 ===================================
 
diff --git a/contrib/hooks/multimail/CONTRIBUTING.rst b/contrib/hooks/multimail/CONTRIBUTING.rst
new file mode 100644 (file)
index 0000000..09efdb0
--- /dev/null
@@ -0,0 +1,30 @@
+git-multimail is an open-source project, built by volunteers. We would
+welcome your help!
+
+The current maintainers are Michael Haggerty <mhagger@alum.mit.edu>
+and Matthieu Moy <matthieu.moy@grenoble-inp.fr>.
+
+Please note that although a copy of git-multimail is distributed in
+the "contrib" section of the main Git project, development takes place
+in a separate git-multimail repository on GitHub:
+
+    https://github.com/git-multimail/git-multimail
+
+Whenever enough changes to git-multimail have accumulated, a new
+code-drop of git-multimail will be submitted for inclusion in the Git
+project.
+
+We use the GitHub issue tracker to keep track of bugs and feature
+requests, and we use GitHub pull requests to exchange patches (though,
+if you prefer, you can send patches via the Git mailing list with CC
+to the maintainers). Please sign off your patches as per the `Git
+project practice
+<https://github.com/git/git/blob/master/Documentation/SubmittingPatches#L234>`__.
+
+General discussion of git-multimail can take place on the main Git
+mailing list,
+
+    git@vger.kernel.org
+
+Please CC emails regarding git-multimail to the maintainers so that we
+don't overlook them.
index e552c90c4511bfb0b60ede3d1dbe8e063c6fecf1..55120685f04674daed7c0c32b8c9f33b0c8b79e3 100644 (file)
@@ -1,5 +1,5 @@
-git-multimail Version 1.1.1
-===========================
+git-multimail (version 1.2.0)
+=============================
 
 .. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
     :target: https://travis-ci.org/git-multimail/git-multimail
@@ -53,11 +53,13 @@ By default, for each push received by the repository, git-multimail:
      + [git] 07/08: Merge branch 'mm/api-credentials-doc'
      + [git] 08/08: Git 1.7.11-rc2
 
-   Each commit appears in exactly one commit email, the first time
-   that it is pushed to the repository.  If a commit is later merged
-   into another branch, then a one-line summary of the commit is
-   included in the reference change email (as usual), but no
-   additional commit email is generated.
+   By default, each commit appears in exactly one commit email, the
+   first time that it is pushed to the repository.  If a commit is later
+   merged into another branch, then a one-line summary of the commit
+   is included in the reference change email (as usual), but no
+   additional commit email is generated. See
+   `multimailhook.refFilter(Inclusion|Exclusion|DoSend|DontSend)Regex`
+   below to configure which branches and tags are watched by the hook.
 
    By default, reference change emails have their "Reply-To" field set
    to the person who pushed the change, and commit emails have their
@@ -73,21 +75,8 @@ Requirements
 ------------
 
 * Python 2.x, version 2.4 or later.  No non-standard Python modules
-  are required.  git-multimail does *not* currently work with Python
-  3.x.
-
-  The example scripts invoke Python using the following shebang line
-  (following PEP 394 [1]_)::
-
-      #! /usr/bin/env python2
-
-  If your system's Python2 interpreter is not in your PATH or is not
-  called ``python2``, you can change the lines accordingly.  Or you can
-  invoke the Python interpreter explicitly, for example via a tiny
-  shell script like::
-
-      #! /bin/sh
-      /usr/local/bin/python /path/to/git_multimail.py "$@"
+  are required.  git-multimail has preliminary support for Python 3
+  (but it has been better tested with Python 2).
 
 * The ``git`` command must be in your PATH.  git-multimail is known to
   work with Git versions back to 1.7.1.  (Earlier versions have not
@@ -146,7 +135,9 @@ following ``git config`` settings:
 
 multimailhook.environment
 
-    This describes the general environment of the repository.
+    This describes the general environment of the repository. In most
+    cases, you do not need to specify a value for this variable:
+    `git-multimail` will autodetect which environment to use.
     Currently supported values:
 
     * generic
@@ -161,18 +152,57 @@ multimailhook.environment
       optionally read from gitolite.conf (see multimailhook.from).
 
       For more information about gitolite and git-multimail, read
-      doc/gitolite.rst
+      `<doc/gitolite.rst>`__
+
+    * stash
+
+      Environment to use when ``git-multimail`` is ran as an Atlassian
+      BitBucket Server (formerly known as Atlassian Stash) hook.
+
+      **Warning:** this mode was provided by a third-party contributor
+      and never tested by the git-multimail maintainers. It is
+      provided as-is and may or may not work for you.
+
+      This value is automatically assumed when the stash-specific
+      flags (``--stash-user`` and ``--stash-repo``) are specified on
+      the command line. When this environment is active, the username
+      and repo come from these two command line flags, which must be
+      specified.
+
+    * gerrit
+
+      Environment to use when ``git-multimail`` is ran as a
+      ``ref-updated`` Gerrit hook.
+
+      This value is used when the gerrit-specific command line flags
+      (``--oldrev``, ``--newrev``, ``--refname``, ``--project``) for
+      gerrit's ref-updated hook are present. When this environment is
+      active, the username of the pusher is taken from the
+      ``--submitter`` argument if that command line option is passed,
+      otherwise 'Gerrit' is used. The repository name is taken from
+      the ``--project`` option on the command line, which must be passed.
+
+      For more information about gerrit and git-multimail, read
+      `<doc/gerrit.rst>`__
 
-    If neither of these environments is suitable for your setup, then
-    you can implement a Python class that inherits from Environment
-    and instantiate it via a script that looks like the example
+    If none of these environments is suitable for your setup, then you
+    can implement a Python class that inherits from Environment and
+    instantiate it via a script that looks like the example
     post-receive script.
 
     The environment value can be specified on the command line using
-    the --environment option.  If it is not specified on the command
-    line or by multimailhook.environment, then it defaults to
-    ``gitolite`` if the environment contains variables $GL_USER and
-    $GL_REPO; otherwise ``generic``.
+    the ``--environment`` option. If it is not specified on the
+    command line or by ``multimailhook.environment``, the value is
+    guessed as follows:
+
+    * If stash-specific (respectively gerrit-specific) command flags
+      are present on the command-line, then ``stash`` (respectively
+      ``gerrit``) is used.
+
+    * If the environment variables $GL_USER and $GL_REPO are set, then
+      ``gitolite`` is used.
+
+    * If none of the above apply, then ``generic`` is used.
 
 multimailhook.repoName
 
@@ -196,8 +226,8 @@ multimailhook.refchangeList
     reference changes should be sent, as RFC 2822 email addresses
     separated by commas.  This configuration option can be
     multivalued.  The default is the value in
-    multimailhook.mailingList.  Set this value to the empty string to
-    prevent reference change emails from being sent even if
+    multimailhook.mailingList.  Set this value to "none" (or the empty
+    string) to prevent reference change emails from being sent even if
     multimailhook.mailingList is set.
 
 multimailhook.announceList
@@ -206,9 +236,9 @@ multimailhook.announceList
     tags should be sent, as RFC 2822 email addresses separated by
     commas.  This configuration option can be multivalued.  The
     default is the value in multimailhook.refchangeList or
-    multimailhook.mailingList.  Set this value to the empty string to
-    prevent annotated tag announcement emails from being sent even if
-    one of the other values is set.
+    multimailhook.mailingList.  Set this value to "none" (or the empty
+    string) to prevent annotated tag announcement emails from being sent
+    even if one of the other values is set.
 
 multimailhook.commitList
 
@@ -216,7 +246,7 @@ multimailhook.commitList
     commits should be sent, as RFC 2822 email addresses separated by
     commas.  This configuration option can be multivalued.  The
     default is the value in multimailhook.mailingList.  Set this value
-    to the empty string to prevent notification emails about
+    to "none" (or the empty string) to prevent notification emails about
     individual commits from being sent even if
     multimailhook.mailingList is set.
 
@@ -230,6 +260,20 @@ multimailhook.announceShortlog
     not so straightforward, then the shortlog might be confusing
     rather than useful.  Default is false.
 
+multimailhook.commitEmailFormat
+
+    The format of email messages for the individual commits, can be "text" or
+    "html". In the latter case, the emails will include diffs using colorized
+    HTML instead of plain text used by default. Note that this  currently the
+    ref change emails are always sent in plain text.
+
+    Note that when using "html", the formatting is done by parsing the
+    output of ``git log`` with ``-p``. When using
+    ``multimailhook.commitLogOpts`` to specify a ``--format`` for
+    ``git log``, one may get false positive (e.g. lines in the body of
+    the message starting with ``+++`` or ``---`` colored in red or
+    green).
+
 multimailhook.refchangeShowGraph
 
     If this option is set to true, then summary emails about reference
@@ -305,7 +349,7 @@ multimailhook.mailer
 
       * multimailhook.smtpEncryption
 
-        Set the security type. Allowed values: none, ssl.
+        Set the security type. Allowed values: none, ssl, tls.
         Default=none.
 
       * multimailhook.smtpServerDebugLevel
@@ -313,9 +357,26 @@ multimailhook.mailer
         Integer number. Set to greater than 0 to activate debugging.
 
 multimailhook.from
+multimailhook.fromCommit
+multimailhook.fromRefchange
+
+    If set, use this value in the From: field of generated emails.
+    ``fromCommit`` is used for commit emails, ``fromRefchange`` is
+    used for refchange emails, and ``from`` is used as fall-back in
+    all cases.
+
+    The value for these variables can be either:
+
+    - An email address, which will be used directly.
+
+    - The value ``pusher``, in which case the pusher's address (if
+      available) will be used.
 
-    If set, use this value in the From: field of generated emails.  If
-    unset, the value of the From: header is determined as follows:
+    - The value ``author`` (meaningful only for replyToCommit), in which
+      case the commit author's address will be used.
+
+    If config values are unset, the value of the From: header is
+    determined as follows:
 
     1. (gitolite environment only) Parse gitolite.conf, looking for a
        block of comments that looks like this::
@@ -425,6 +486,15 @@ multimailhook.commitLogOpts
     --stat -p --cc``.  Shell quoting is allowed; see
     multimailhook.logOpts for details.
 
+multimailhook.dateSubstitute
+
+    String to use as a substitute for ``Date:`` in the output of ``git
+    log`` while formatting commit messages. This is usefull to avoid
+    emitting a line that can be interpreted by mailers as the start of
+    a cited message (Zimbra webmail in particular). Defaults to
+    ``CommitDate: ``. Set to an empty string or ``none`` to deactivate
+    the behavior.
+
 multimailhook.emailDomain
 
     Domain name appended to the username of the person doing the push
@@ -440,21 +510,13 @@ multimailhook.replyToRefchange
     Addresses to use in the Reply-To: field for commit emails
     (replyToCommit) and refchange emails (replyToRefchange).
     multimailhook.replyTo is used as default when replyToCommit or
-    replyToRefchange is not set.  The value for these variables can be
-    either:
-
-    - An email address, which will be used directly.
-
-    - The value `pusher`, in which case the pusher's address (if
-      available) will be used.  This is the default for refchange
-      emails.
+    replyToRefchange is not set. The shortcuts ``pusher`` and
+    ``author`` are allowed with the same semantics as for
+    ``multimailhook.from``. In addition, the value ``none`` can be
+    used to omit the ``Reply-To:`` field.
 
-    - The value `author` (meaningful only for replyToCommit), in which
-      case the commit author's address will be used.  This is the
-      default for commit emails.
-
-    - The value `none`, in which case the Reply-To: field will be
-      omitted.
+    The default is ``pusher`` for refchange emails, and ``author`` for
+    commit emails.
 
 multimailhook.quiet
 
@@ -478,6 +540,63 @@ multimailhook.combineWhenSingleCommit
     single email.
     Default: true
 
+multimailhook.refFilterInclusionRegex
+multimailhook.refFilterExclusionRegex
+multimailhook.refFilterDoSendRegex
+multimailhook.refFilterDontSendRegex
+
+    **Warning:** these options are experimental. They should work, but
+    the user-interface is not stable yet (in particular, the option
+    names may change). If you want to participate in stabilizing the
+    feature, please contact the maintainers and/or send pull-requests.
+
+    Regular expressions that can be used to limit refs for which email
+    updates will be sent.  It is an error to specify both an inclusion
+    and an exclusion regex.  If a ``refFilterInclusionRegex`` is
+    specified, emails will only be sent for refs which match this
+    regex.  If a ``refFilterExclusionRegex`` regex is specified,
+    emails will be sent for all refs except those that match this
+    regex (or that match a predefined regex specific to the
+    environment, such as "^refs/notes" for most environments and
+    "^refs/notes|^refs/changes" for the gerrit environment).
+
+    The expressions are matched against the complete refname, and is
+    considered to match if any substring matches. For example, to
+    filter-out all tags, set ``refFilterExclusionRegex`` to
+    ``^refs/tags/`` (note the leading ``^`` but no trailing ``$``). If
+    you set ``refFilterExclusionRegex`` to ``master``, then any ref
+    containing ``master`` will be excluded (the ``master`` branch, but
+    also ``refs/tags/master`` or ``refs/heads/foo-master-bar``).
+
+    ``refFilterDoSendRegex`` and ``refFilterDontSendRegex`` are
+    analogous to ``refFilterInclusionRegex`` and
+    ``refFilterExclusionRegex`` with one difference: with
+    ``refFilterDoSendRegex`` and ``refFilterDontSendRegex``, commits
+    introduced by one excluded ref will not be considered as new when
+    they reach an included ref. Typically, if you add a branch ``foo``
+    to  ``refFilterDontSendRegex``, push commits to this branch, and
+    later merge branch ``foo`` into ``master``, then the notification
+    email for ``master`` will contain a commit email only for the
+    merge commit. If you include ``foo`` in
+    ``refFilterExclusionRegex``, then at the time of merge, you will
+    receive one commit email per commit in the branch.
+
+    These variables can be multi-valued, like::
+
+      [multimailhook]
+              refFilterExclusionRegex = ^refs/tags/
+              refFilterExclusionRegex = ^refs/heads/master$
+
+    You can also provide a whitespace-separated list like::
+
+      [multimailhook]
+              refFilterExclusionRegex = ^refs/tags/ ^refs/heads/master$
+
+    Both examples exclude tags and the master branch, and are
+    equivalent to::
+
+      [multimailhook]
+              refFilterExclusionRegex = ^refs/tags/|^refs/heads/master$
 
 Email filtering aids
 --------------------
@@ -547,35 +666,8 @@ consider sharing them with the community!
 Getting involved
 ----------------
 
-git-multimail is an open-source project, built by volunteers. We would
-welcome your help!
-
-The current maintainers are Michael Haggerty <mhagger@alum.mit.edu>
-and Matthieu Moy <matthieu.moy@grenoble-inp.fr>.
-
-Please note that although a copy of git-multimail is distributed in
-the "contrib" section of the main Git project, development takes place
-in a separate git-multimail repository on GitHub:
-
-    https://github.com/git-multimail/git-multimail
-
-Whenever enough changes to git-multimail have accumulated, a new
-code-drop of git-multimail will be submitted for inclusion in the Git
-project.
-
-We use the GitHub issue tracker to keep track of bugs and feature
-requests, and we use GitHub pull requests to exchange patches (though,
-if you prefer, you can send patches via the Git mailing list with CC
-to the maintainers). Please sign off your patches as per the Git
-project practice.
-
-General discussion of git-multimail can take place on the main Git
-mailing list,
-
-    git@vger.kernel.org
-
-Please CC emails regarding git-multimail to the maintainers so that we
-don't overlook them.
+Please, read `<CONTRIBUTING.rst>`__ for instructions on how to
+contribute to git-multimail.
 
 
 Footnotes
index f5d59a8d313a3114c1fd0bdfa0faaea64a7b71d9..300a2a4d2d479b3ab002a5e6480c3e9a83e36fb7 100644 (file)
@@ -6,10 +6,10 @@ website:
     https://github.com/git-multimail/git-multimail
 
 The version in this directory was obtained from the upstream project
-on July 03 2015 and consists of the "git-multimail" subdirectory from
+on October 11 2015 and consists of the "git-multimail" subdirectory from
 revision
 
-    6d6c9eb62a054143322cfaecde3949189c065b46 refs/tags/1.1.1
+    c0791b9ef5821a746fc3475c25765e640452eaae refs/tags/1.2.0
 
 Please see the README file in this directory for information about how
 to report bugs or contribute to git-multimail.
diff --git a/contrib/hooks/multimail/doc/gerrit.rst b/contrib/hooks/multimail/doc/gerrit.rst
new file mode 100644 (file)
index 0000000..8011d05
--- /dev/null
@@ -0,0 +1,56 @@
+Setting up git-multimail on Gerrit
+==================================
+
+Gerrit has its own email-sending system, but you may prefer using
+``git-multimail`` instead. It supports Gerrit natively as a Gerrit
+``ref-updated`` hook (Warning: `Gerrit hooks
+<https://gerrit-review.googlesource.com/Documentation/config-hooks.html>`__
+are distinct from Git hooks). Setting up ``git-multimail`` on a Gerrit
+installation can be done following the instructions below.
+
+The explanations show an easy way to set up ``git-multimail``,
+but leave ``git-multimail`` installed and unconfigured for a while. If
+you run Gerrit on a production server, it is advised that you
+execute the step "Set up the hook" last to avoid confusing your users
+in the meantime.
+
+Set up the hook
+---------------
+
+Create a directory ``$site_path/hooks/`` if it does not exist (if you
+don't know what ``$site_path`` is, run ``gerrit.sh status`` and look
+for a ``GERRIT_SITE`` line). Either copy ``git_multimail.py`` to
+``$site_path/hooks/ref-updated`` or create a wrapper script like
+this::
+
+  #! /bin/sh
+  exec /path/to/git_multimail.py "$@"
+
+In both cases, make sure the file is named exactly
+``$site_path/hooks/ref-updated`` and is executable.
+
+(Alternatively, you may configure the ``[hooks]`` section of
+gerrit.config)
+
+Configuration
+-------------
+
+Log on the gerrit server and edit ``$site_path/git/$project/config``
+to configure ``git-multimail``.
+
+Troubleshooting
+---------------
+
+Warning: this will disable ``git-multimail`` during the debug, and
+could confuse your users. Don't run on a production server.
+
+To debug configuration issues with ``git-multimail``, you can add the
+``--stdout`` option when calling ``git_multimail.py`` like this::
+
+  #!/bin/sh
+  exec /path/to/git-multimail/git-multimail/git_multimail.py \
+    --stdout "$@" >> /tmp/log.txt
+
+and try pushing from a test repository. You should see the source of
+the email that would have been sent in the output of ``git push`` in
+the file ``/tmp/log.txt``.
diff --git a/contrib/hooks/multimail/doc/gitolite.rst b/contrib/hooks/multimail/doc/gitolite.rst
new file mode 100644 (file)
index 0000000..00aedd9
--- /dev/null
@@ -0,0 +1,109 @@
+Setting up git-multimail on gitolite
+====================================
+
+``git-multimail`` supports gitolite 3 natively.
+The explanations below show an easy way to set up ``git-multimail``,
+but leave ``git-multimail`` installed and unconfigured for a while. If
+you run gitolite on a production server, it is advised that you
+execute the step "Set up the hook" last to avoid confusing your users
+in the meantime.
+
+Set up the hook
+---------------
+
+Log in as your gitolite user.
+
+Create a file ``.gitolite/hooks/common/post-receive`` on your gitolite
+account containing (adapt the path, obviously)::
+
+  #!/bin/sh
+  exec /path/to/git-multimail/git-multimail/git_multimail.py "$@"
+
+Make sure it's executable (``chmod +x``). Record the hook in
+gitolite::
+
+  gitolite setup
+
+Configuration
+-------------
+
+First, you have to allow the admin to set Git configuration variables.
+
+As gitolite user, edit the line containing ``GIT_CONFIG_KEYS`` in file
+``.gitolite.rc``, to make it look like::
+
+  GIT_CONFIG_KEYS                 =>  'multimailhook\..*',
+
+You can now log out and return to your normal user.
+
+In the ``gitolite-admin`` clone, edit the file ``conf/gitolite.conf``
+and add::
+
+  repo @all
+      # Not strictly needed as git_multimail.py will chose gitolite if
+      # $GL_USER is set.
+      config multimailhook.environment = gitolite
+      config multimailhook.mailingList = # Where emails should be sent
+      config multimailhook.from = # From address to use
+
+Obviously, you can customize all parameters on a per-repository basis by
+adding these ``config multimailhook.*`` lines in the section
+corresponding to a repository or set of repositories.
+
+To activate ``git-multimail`` on a per-repository basis, do not set
+``multimailhook.mailingList`` in the ``@all`` section and set it only
+for repositories for which you want ``git-multimail``.
+
+Alternatively, you can set up the ``From:`` field on a per-user basis
+by adding a ``BEGIN USER EMAILS``/``END USER EMAILS`` section (see
+``../README``).
+
+Specificities of Gitolite for Configuration
+-------------------------------------------
+
+Empty configuration variables
+.............................
+
+With gitolite, the syntax ``config multimailhook.commitList = ""``
+unsets the variable instead of setting it to an empty string (see
+`here
+<http://gitolite.com/gitolite/git-config.html#an-important-warning-about-deleting-a-config-line>`__).
+As a result, there is no way to set a variable to the empty string.
+In all most places where an empty value is required, git-multimail
+now allows to specify special ``"none"`` value (case-sensitive) to
+mean the same.
+
+Alternatively, one can use ``" "`` (a single space) instead of ``""``.
+In most cases (in particular ``multimailhook.*List`` variables), this
+will be equivalent to an empty string.
+
+If you have a use-case where ``"none"`` is not an acceptable value and
+you need ``" "`` or  ``""`` instead, please report it as a bug to
+git-multimail.
+
+Allowing Regular Expressions in Configuration
+.............................................
+
+gitolite has a mechanism to prevent unsafe configuration variable
+values, which prevent characters like ``|`` commonly used in regular
+expressions. If you do not need the safety feature of gitolite and
+need to use regular expressions in your configuration (e.g. for
+``multimailhook.refFilter*`` variables), set
+`UNSAFE_PATT
+<http://gitolite.com/gitolite/git-config.html#unsafe-patt>`__ to a
+less restrictive value.
+
+Troubleshooting
+---------------
+
+Warning: this will disable ``git-multimail`` during the debug, and
+could confuse your users. Don't run on a production server.
+
+To debug configuration issues with ``git-multimail``, you can add the
+``--stdout`` option when calling ``git_multimail.py`` like this::
+
+  #!/bin/sh
+  exec /path/to/git-multimail/git-multimail/git_multimail.py --stdout "$@"
+
+and try pushing from a test repository. You should see the source of
+the email that would have been sent in the output of ``git push``.
index c06ce7a5158175b684a70e6a148031593a12fb47..0180dba43126df209bde82e3e5aea3616f402c96 100755 (executable)
@@ -1,4 +1,6 @@
-#! /usr/bin/env python2
+#! /usr/bin/env python
+
+__version__ = '1.2.0'
 
 # Copyright (c) 2015 Matthieu Moy and others
 # Copyright (c) 2012-2014 Michael Haggerty and others
 import optparse
 import smtplib
 import time
+import cgi
+
+PYTHON3 = sys.version_info >= (3, 0)
+
+if sys.version_info <= (2, 5):
+    def all(iterable):
+        for element in iterable:
+            if not element:
+                return False
+            return True
+
+
+def is_ascii(s):
+    return all(ord(c) < 128 and ord(c) > 0 for c in s)
+
+
+if PYTHON3:
+    def str_to_bytes(s):
+        return s.encode(ENCODING)
+
+    def bytes_to_str(s):
+        return s.decode(ENCODING)
+
+    unicode = str
+
+    def write_str(f, msg):
+        # Try outputing with the default encoding. If it fails,
+        # try UTF-8.
+        try:
+            f.buffer.write(msg.encode(sys.getdefaultencoding()))
+        except UnicodeEncodeError:
+            f.buffer.write(msg.encode(ENCODING))
+else:
+    def str_to_bytes(s):
+        return s
+
+    def bytes_to_str(s):
+        return s
+
+    def write_str(f, msg):
+        f.write(msg)
+
+    def next(it):
+        return it.next()
+
 
 try:
+    from email.charset import Charset
     from email.utils import make_msgid
     from email.utils import getaddresses
     from email.utils import formataddr
     from email.header import Header
 except ImportError:
     # Prior to Python 2.5, the email module used different names:
+    from email.Charset import Charset
     from email.Utils import make_msgid
     from email.Utils import getaddresses
     from email.Utils import formataddr
 To: %(recipients)s
 Subject: %(subject)s
 MIME-Version: 1.0
-Content-Type: text/plain; charset=%(charset)s
+Content-Type: text/%(contenttype)s; charset=%(charset)s
 Content-Transfer-Encoding: 8bit
 Message-ID: %(msgid)s
 From: %(fromaddr)s
 X-Git-Reftype: %(refname_type)s
 X-Git-Oldrev: %(oldrev)s
 X-Git-Newrev: %(newrev)s
+X-Git-NotificationType: ref_changed
+X-Git-Multimail-Version: %(multimail_version)s
 Auto-Submitted: auto-generated
 """
 
 Cc: %(cc_recipients)s
 Subject: %(emailprefix)s%(num)02d/%(tot)02d: %(oneline)s
 MIME-Version: 1.0
-Content-Type: text/plain; charset=%(charset)s
+Content-Type: text/%(contenttype)s; charset=%(charset)s
 Content-Transfer-Encoding: 8bit
 From: %(fromaddr)s
 Reply-To: %(reply_to)s
 X-Git-Refname: %(refname)s
 X-Git-Reftype: %(refname_type)s
 X-Git-Rev: %(rev)s
+X-Git-NotificationType: diff
+X-Git-Multimail-Version: %(multimail_version)s
 Auto-Submitted: auto-generated
 """
 
 To: %(recipients)s
 Subject: %(subject)s
 MIME-Version: 1.0
-Content-Type: text/plain; charset=%(charset)s
+Content-Type: text/%(contenttype)s; charset=%(charset)s
 Content-Transfer-Encoding: 8bit
 Message-ID: %(msgid)s
 From: %(fromaddr)s
 X-Git-Oldrev: %(oldrev)s
 X-Git-Newrev: %(newrev)s
 X-Git-Rev: %(rev)s
+X-Git-NotificationType: ref_changed_plus_diff
+X-Git-Multimail-Version: %(multimail_version)s
 Auto-Submitted: auto-generated
 """
 
@@ -352,12 +407,14 @@ def read_git_output(args, input=None, keepends=False, **kw):
 def read_output(cmd, input=None, keepends=False, **kw):
     if input:
         stdin = subprocess.PIPE
+        input = str_to_bytes(input)
     else:
         stdin = None
     p = subprocess.Popen(
         cmd, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kw
         )
     (out, err) = p.communicate(input)
+    out = bytes_to_str(out)
     retcode = p.wait()
     if retcode:
         raise CommandError(cmd, retcode)
@@ -418,26 +475,37 @@ def git_log(spec, **kw):
 def header_encode(text, header_name=None):
     """Encode and line-wrap the value of an email header field."""
 
-    try:
-        if isinstance(text, str):
-            text = text.decode(ENCODING, 'replace')
-        return Header(text, header_name=header_name).encode()
-    except UnicodeEncodeError:
-        return Header(text, header_name=header_name, charset=CHARSET,
-                      errors='replace').encode()
+    # Convert to unicode, if required.
+    if not isinstance(text, unicode):
+        text = unicode(text, 'utf-8')
+
+    if is_ascii(text):
+        charset = 'ascii'
+    else:
+        charset = 'utf-8'
+
+    return Header(text, header_name=header_name, charset=Charset(charset)).encode()
 
 
 def addr_header_encode(text, header_name=None):
     """Encode and line-wrap the value of an email header field containing
     email addresses."""
 
-    return Header(
-        ', '.join(
-            formataddr((header_encode(name), emailaddr))
-            for name, emailaddr in getaddresses([text])
-            ),
-        header_name=header_name
-        ).encode()
+    # Convert to unicode, if required.
+    if not isinstance(text, unicode):
+        text = unicode(text, 'utf-8')
+
+    text = ', '.join(
+        formataddr((header_encode(name), emailaddr))
+        for name, emailaddr in getaddresses([text])
+        )
+
+    if is_ascii(text):
+        charset = 'ascii'
+    else:
+        charset = 'utf-8'
+
+    return Header(text, header_name=header_name, charset=Charset(charset)).encode()
 
 
 class Config(object):
@@ -496,7 +564,8 @@ def get_all(self, name, default=None):
                 ['config', '--get-all', '--null', '%s.%s' % (self.section, name)],
                 env=self.env, keepends=True,
                 ))
-        except CommandError, e:
+        except CommandError:
+            t, e, traceback = sys.exc_info()
             if e.retcode == 1:
                 # "the section or key is invalid"; i.e., there is no
                 # value for the specified key.
@@ -504,18 +573,6 @@ def get_all(self, name, default=None):
             else:
                 raise
 
-    def get_recipients(self, name, default=None):
-        """Read a recipients list from the configuration.
-
-        Return the result as a comma-separated list of email
-        addresses, or default if the option is unset.  If the setting
-        has multiple values, concatenate them with comma separators."""
-
-        lines = self.get_all(name, default=None)
-        if lines is None:
-            return default
-        return ', '.join(line.strip() for line in lines)
-
     def set(self, name, value):
         read_git_output(
             ['config', '%s.%s' % (self.section, name), value],
@@ -542,7 +599,8 @@ def unset_all(self, name):
                 ['config', '--unset-all', '%s.%s' % (self.section, name)],
                 env=self.env,
                 )
-        except CommandError, e:
+        except CommandError:
+            t, e, traceback = sys.exc_info()
             if e.retcode == 5:
                 # The name doesn't exist, which is what we wanted anyway...
                 pass
@@ -636,7 +694,7 @@ def get_summary(self):
         if not self.sha1:
             raise ValueError('Empty commit has no summary')
 
-        return iter(generate_summaries('--no-walk', self.sha1)).next()
+        return next(iter(generate_summaries('--no-walk', self.sha1)))
 
     def __eq__(self, other):
         return isinstance(other, GitObject) and self.sha1 == other.sha1
@@ -647,6 +705,10 @@ def __hash__(self):
     def __nonzero__(self):
         return bool(self.sha1)
 
+    def __bool__(self):
+        """Python 2 backward compatibility"""
+        return self.__nonzero__()
+
     def __str__(self):
         return self.sha1 or ZEROS
 
@@ -661,6 +723,12 @@ class Change(object):
     def __init__(self, environment):
         self.environment = environment
         self._values = None
+        self._contains_html_diff = False
+
+    def _contains_diff(self):
+        # We do contain a diff, should it be rendered in HTML?
+        if self.environment.commit_email_format == "html":
+            self._contains_html_diff = True
 
     def _compute_values(self):
         """Return a dictionary {keyword: expansion} for this Change.
@@ -670,7 +738,12 @@ def _compute_values(self):
         get_values().  The return value should always be a new
         dictionary."""
 
-        return self.environment.get_values()
+        values = self.environment.get_values()
+        fromaddr = self.environment.get_fromaddr(change=self)
+        if fromaddr is not None:
+            values['fromaddr'] = fromaddr
+        values['multimail_version'] = get_version()
+        return values
 
     def get_values(self, **extra_values):
         """Return a dictionary {keyword: expansion} for this Change.
@@ -713,12 +786,18 @@ def expand_header_lines(self, template, **extra_values):
         skip lines that contain references to unknown variables."""
 
         values = self.get_values(**extra_values)
+        if self._contains_html_diff:
+            values['contenttype'] = 'html'
+        else:
+            values['contenttype'] = 'plain'
+
         for line in template.splitlines():
-            (name, value) = line.split(':', 1)
+            (name, value) = line.split(': ', 1)
 
             try:
                 value = value % values
-            except KeyError, e:
+            except KeyError:
+                t, e, traceback = sys.exc_info()
                 if DEBUG:
                     self.environment.log_warning(
                         'Warning: unknown variable %r in the following line; line skipped:\n'
@@ -764,6 +843,24 @@ def generate_email_footer(self):
 
         raise NotImplementedError()
 
+    def _wrap_for_html(self, lines):
+        """Wrap the lines in HTML <pre> tag when using HTML format.
+
+        Escape special HTML characters and add <pre> and </pre> tags around
+        the given lines if we should be generating HTML as indicated by
+        self._contains_html_diff being set to true.
+        """
+        if self._contains_html_diff:
+            yield "<pre style='margin:0'>\n"
+
+            for line in lines:
+                yield cgi.escape(line)
+
+            yield '</pre>\n'
+        else:
+            for line in lines:
+                yield line
+
     def generate_email(self, push, body_filter=None, extra_header_values={}):
         """Generate an email describing this change.
 
@@ -779,18 +876,76 @@ def generate_email(self, push, body_filter=None, extra_header_values={}):
         for line in self.generate_email_header(**extra_header_values):
             yield line
         yield '\n'
-        for line in self.generate_email_intro():
+        for line in self._wrap_for_html(self.generate_email_intro()):
             yield line
 
         body = self.generate_email_body(push)
         if body_filter is not None:
             body = body_filter(body)
+
+        diff_started = False
+        if self._contains_html_diff:
+            # "white-space: pre" is the default, but we need to
+            # specify it again in case the message is viewed in a
+            # webmail which wraps it in an element setting white-space
+            # to something else (Zimbra does this and sets
+            # white-space: pre-line).
+            yield '<pre style="white-space: pre; background: #F8F8F8">'
         for line in body:
+            if self._contains_html_diff:
+                # This is very, very naive. It would be much better to really
+                # parse the diff, i.e. look at how many lines do we have in
+                # the hunk headers instead of blindly highlighting everything
+                # that looks like it might be part of a diff.
+                bgcolor = ''
+                fgcolor = ''
+                if line.startswith('--- a/'):
+                    diff_started = True
+                    bgcolor = 'e0e0ff'
+                elif line.startswith('diff ') or line.startswith('index '):
+                    diff_started = True
+                    fgcolor = '808080'
+                elif diff_started:
+                    if line.startswith('+++ '):
+                        bgcolor = 'e0e0ff'
+                    elif line.startswith('@@'):
+                        bgcolor = 'e0e0e0'
+                    elif line.startswith('+'):
+                        bgcolor = 'e0ffe0'
+                    elif line.startswith('-'):
+                        bgcolor = 'ffe0e0'
+                elif line.startswith('commit '):
+                    fgcolor = '808000'
+                elif line.startswith('    '):
+                    fgcolor = '404040'
+
+                # Chop the trailing LF, we don't want it inside <pre>.
+                line = cgi.escape(line[:-1])
+
+                if bgcolor or fgcolor:
+                    style = 'display:block; white-space:pre;'
+                    if bgcolor:
+                        style += 'background:#' + bgcolor + ';'
+                    if fgcolor:
+                        style += 'color:#' + fgcolor + ';'
+                    # Use a <span style='display:block> to color the
+                    # whole line. The newline must be inside the span
+                    # to display properly both in Firefox and in
+                    # text-based browser.
+                    line = "<span style='%s'>%s\n</span>" % (style, line)
+                else:
+                    line = line + '\n'
+
             yield line
+        if self._contains_html_diff:
+            yield '</pre>'
 
-        for line in self.generate_email_footer():
+        for line in self._wrap_for_html(self.generate_email_footer()):
             yield line
 
+    def get_alt_fromaddr(self):
+        return None
+
 
 class Revision(Change):
     """A Change consisting of a single git commit."""
@@ -867,14 +1022,25 @@ def generate_email_intro(self):
     def generate_email_body(self, push):
         """Show this revision."""
 
-        return read_git_lines(
-            ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1],
-            keepends=True,
-            )
+        for line in read_git_lines(
+                ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1],
+                keepends=True,
+                ):
+            if line.startswith('Date:   ') and self.environment.date_substitute:
+                yield self.environment.date_substitute + line[len('Date:   '):]
+            else:
+                yield line
 
     def generate_email_footer(self):
         return self.expand_lines(REVISION_FOOTER_TEMPLATE)
 
+    def generate_email(self, push, body_filter=None, extra_header_values={}):
+        self._contains_diff()
+        return Change.generate_email(self, push, body_filter, extra_header_values)
+
+    def get_alt_fromaddr(self):
+        return self.environment.from_commit
+
 
 class ReferenceChange(Change):
     """A Change to a Git reference.
@@ -1096,10 +1262,10 @@ def generate_revision_change_log(self, new_commits_list):
             yield '\n'
             yield 'Detailed log of new commits:\n\n'
             for line in read_git_lines(
-                    ['log', '--no-walk']
-                    + self.logopts
-                    + new_commits_list
-                    ['--'],
+                    ['log', '--no-walk'] +
+                    self.logopts +
+                    new_commits_list +
+                    ['--'],
                     keepends=True,
                     ):
                 yield line
@@ -1253,9 +1419,9 @@ def generate_revision_change_summary(self, push):
             yield '\n'
             yield 'Summary of changes:\n'
             for line in read_git_lines(
-                    ['diff-tree']
-                    + self.diffopts
-                    ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
+                    ['diff-tree'] +
+                    self.diffopts +
+                    ['%s..%s' % (self.old.commit_sha1, self.new.commit_sha1,)],
                     keepends=True,
                     ):
                 yield line
@@ -1316,6 +1482,9 @@ def generate_delete_summary(self, push):
             )
         yield '\n'
 
+    def get_alt_fromaddr(self):
+        return self.environment.from_refchange
+
 
 class BranchChange(ReferenceChange):
     refname_type = 'branch'
@@ -1397,9 +1566,9 @@ def split_line(line):
             # commit is a non-merge commit, though it may make sense to
             # combine if it is a merge as well.
             if not (
-                    len(new_commits) == 1
-                    and len(new_commits[0][1]) == 1
-                    and new_commits[0][0] in known_added_sha1s
+                    len(new_commits) == 1 and
+                    len(new_commits[0][1]) == 1 and
+                    new_commits[0][0] in known_added_sha1s
                     ):
                 return None
 
@@ -1432,6 +1601,7 @@ def generate_combined_email(self, push, revision, body_filter=None, extra_header
             values['subject'] = self.expand(COMBINED_REFCHANGE_REVISION_SUBJECT_TEMPLATE, **values)
 
         self._single_revision = revision
+        self._contains_diff()
         self.header_template = COMBINED_HEADER_TEMPLATE
         self.intro_template = COMBINED_INTRO_TEMPLATE
         self.footer_template = COMBINED_FOOTER_TEMPLATE
@@ -1690,17 +1860,18 @@ def __init__(self, command=None, envelopesender=None):
     def send(self, lines, to_addrs):
         try:
             p = subprocess.Popen(self.command, stdin=subprocess.PIPE)
-        except OSError, e:
+        except OSError:
             sys.stderr.write(
-                '*** Cannot execute command: %s\n' % ' '.join(self.command)
-                + '*** %s\n' % str(e)
-                + '*** Try setting multimailhook.mailer to "smtp"\n'
+                '*** Cannot execute command: %s\n' % ' '.join(self.command) +
+                '*** %s\n' % sys.exc_info()[1] +
+                '*** Try setting multimailhook.mailer to "smtp"\n' +
                 '*** to send emails without using the sendmail command.\n'
                 )
             sys.exit(1)
         try:
+            lines = (str_to_bytes(line) for line in lines)
             p.stdin.writelines(lines)
-        except Exception, e:
+        except Exception:
             sys.stderr.write(
                 '*** Error while generating commit email\n'
                 '***  - mail sending aborted.\n'
@@ -1710,7 +1881,7 @@ def send(self, lines, to_addrs):
                 p.terminate()
             except AttributeError:
                 pass
-            raise e
+            raise
         else:
             p.stdin.close()
             retcode = p.wait()
@@ -1770,11 +1941,11 @@ def call(klass, server, timeout):
                     "*** Setting debug on for SMTP server connection (%s) ***\n"
                     % self.smtpserverdebuglevel)
                 self.smtp.set_debuglevel(self.smtpserverdebuglevel)
-        except Exception, e:
+        except Exception:
             sys.stderr.write(
                 '*** Error establishing SMTP connection to %s ***\n'
                 % self.smtpserver)
-            sys.stderr.write('*** %s\n' % str(e))
+            sys.stderr.write('*** %s\n' % sys.exc_info()[1])
             sys.exit(1)
 
     def __del__(self):
@@ -1784,16 +1955,15 @@ def __del__(self):
     def send(self, lines, to_addrs):
         try:
             if self.username or self.password:
-                sys.stderr.write("*** Authenticating as %s ***\n" % self.username)
                 self.smtp.login(self.username, self.password)
             msg = ''.join(lines)
             # turn comma-separated list into Python list if needed.
             if isinstance(to_addrs, basestring):
                 to_addrs = [email for (name, email) in getaddresses([to_addrs])]
             self.smtp.sendmail(self.envelopesender, to_addrs, msg)
-        except Exception, e:
+        except Exception:
             sys.stderr.write('*** Error sending email ***\n')
-            sys.stderr.write('*** %s\n' % str(e))
+            sys.stderr.write('*** %s\n' % sys.exc_info()[1])
             self.smtp.quit()
             sys.exit(1)
 
@@ -1809,9 +1979,10 @@ def __init__(self, f):
         self.f = f
 
     def send(self, lines, to_addrs):
-        self.f.write(self.SEPARATOR)
-        self.f.writelines(lines)
-        self.f.write(self.SEPARATOR)
+        write_str(self.f, self.SEPARATOR)
+        for line in lines:
+            write_str(self.f, line)
+        write_str(self.f, self.SEPARATOR)
 
 
 def get_git_dir():
@@ -1877,11 +2048,13 @@ class Environment(object):
             Return the address to be used as the 'From' email address
             in the email envelope.
 
-        get_fromaddr()
+        get_fromaddr(change=None)
 
             Return the 'From' email address used in the email 'From:'
-            headers.  (May be a full RFC 2822 email address like 'Joe
-            User <user@example.com>'.)
+            headers.  If the change is known when this function is
+            called, it is passed in as the 'change' parameter.  (May
+            be a full RFC 2822 email address like 'Joe User
+            <user@example.com>'.)
 
         get_administrator()
 
@@ -1901,12 +2074,29 @@ class Environment(object):
             get_reply_to_commit() is used for individual commit
             emails.
 
+        get_ref_filter_regex()
+
+            Return a tuple -- a compiled regex, and a boolean indicating
+            whether the regex picks refs to include (if False, the regex
+            matches on refs to exclude).
+
+        get_default_ref_ignore_regex()
+
+            Return a regex that should be ignored for both what emails
+            to send and when computing what commits are considered new
+            to the repository.  Default is "^refs/notes/".
+
     They should also define the following attributes:
 
         announce_show_shortlog (bool)
 
             True iff announce emails should include a shortlog.
 
+        commit_email_format (string)
+
+            If "html", generate commit emails in HTML instead of plain text
+            used by default.
+
         refchange_showgraph (bool)
 
             True iff refchanges emails should include a detailed graph.
@@ -1939,6 +2129,11 @@ class Environment(object):
             commit mail.  The value should be a list of strings
             representing words to be passed to the command.
 
+        date_substitute (string)
+
+            String to be used in substitution for 'Date:' at start of
+            line in the output of 'git log'.
+
         quiet (bool)
             On success do not write to stderr
 
@@ -1950,6 +2145,13 @@ class Environment(object):
             True if a combined email should be produced when a single
             new commit is pushed to a branch, False otherwise.
 
+        from_refchange, from_commit (strings)
+
+            Addresses to use for the From: field for refchange emails
+            and commit emails respectively.  Set from
+            multimailhook.fromRefchange and multimailhook.fromCommit
+            by ConfigEnvironmentMixin.
+
     """
 
     REPO_NAME_RE = re.compile(r'^(?P<name>.+?)(?:\.git)$')
@@ -1957,6 +2159,7 @@ class Environment(object):
     def __init__(self, osenv=None):
         self.osenv = osenv or os.environ
         self.announce_show_shortlog = False
+        self.commit_email_format = "text"
         self.maxcommitemails = 500
         self.diffopts = ['--stat', '--summary', '--find-copies-harder']
         self.graphopts = ['--oneline', '--decorate']
@@ -1964,6 +2167,7 @@ def __init__(self, osenv=None):
         self.refchange_showgraph = False
         self.refchange_showlog = False
         self.commitlogopts = ['-C', '--stat', '-p', '--cc']
+        self.date_substitute = 'AuthorDate: '
         self.quiet = False
         self.stdout = False
         self.combine_when_single_commit = True
@@ -1972,7 +2176,6 @@ def __init__(self, osenv=None):
             'administrator',
             'charset',
             'emailprefix',
-            'fromaddr',
             'pusher',
             'pusher_email',
             'repo_path',
@@ -1998,7 +2201,7 @@ def get_pusher(self):
     def get_pusher_email(self):
         return None
 
-    def get_fromaddr(self):
+    def get_fromaddr(self, change=None):
         config = Config('user')
         fromname = config.get('name', default='')
         fromemail = config.get('email', default='')
@@ -2080,6 +2283,15 @@ def get_revision_recipients(self, revision):
     def get_reply_to_commit(self, revision):
         return revision.author
 
+    def get_default_ref_ignore_regex(self):
+        # The commit messages of git notes are essentially meaningless
+        # and "filenames" in git notes commits are an implementational
+        # detail that might surprise users at first.  As such, we
+        # would need a completely different method for handling emails
+        # of git notes in order for them to be of benefit for users,
+        # which we simply do not have right now.
+        return "^refs/notes/"
+
     def filter_body(self, lines):
         """Filter the lines intended for an email body.
 
@@ -2095,19 +2307,19 @@ def log_msg(self, msg):
         """Write the string msg on a log file or on stderr.
 
         Sends the text to stderr by default, override to change the behavior."""
-        sys.stderr.write(msg)
+        write_str(sys.stderr, msg)
 
     def log_warning(self, msg):
         """Write the string msg on a log file or on stderr.
 
         Sends the text to stderr by default, override to change the behavior."""
-        sys.stderr.write(msg)
+        write_str(sys.stderr, msg)
 
     def log_error(self, msg):
         """Write the string msg on a log file or on stderr.
 
         Sends the text to stderr by default, override to change the behavior."""
-        sys.stderr.write(msg)
+        write_str(sys.stderr, msg)
 
 
 class ConfigEnvironmentMixin(Environment):
@@ -2128,6 +2340,14 @@ def __init__(self, config, **kw):
 class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
     """An Environment that reads most of its information from "git config"."""
 
+    @staticmethod
+    def forbid_field_values(name, value, forbidden):
+        for forbidden_val in forbidden:
+            if value is not None and value.lower() == forbidden:
+                raise ConfigurationException(
+                    '"%s" is not an allowed setting for %s' % (value, name)
+                    )
+
     def __init__(self, config, **kw):
         super(ConfigOptionsEnvironmentMixin, self).__init__(
             config=config, **kw
@@ -2144,14 +2364,26 @@ def __init__(self, config, **kw):
             if val is not None:
                 setattr(self, var, val)
 
+        commit_email_format = config.get('commitEmailFormat')
+        if commit_email_format is not None:
+            if commit_email_format != "html" and commit_email_format != "text":
+                self.log_warning(
+                    '*** Unknown value for multimailhook.commitEmailFormat: %s\n' %
+                    commit_email_format +
+                    '*** Expected either "text" or "html".  Ignoring.\n'
+                    )
+            else:
+                self.commit_email_format = commit_email_format
+
         maxcommitemails = config.get('maxcommitemails')
         if maxcommitemails is not None:
             try:
                 self.maxcommitemails = int(maxcommitemails)
             except ValueError:
                 self.log_warning(
-                    '*** Malformed value for multimailhook.maxCommitEmails: %s\n' % maxcommitemails
-                    + '*** Expected a number.  Ignoring.\n'
+                    '*** Malformed value for multimailhook.maxCommitEmails: %s\n'
+                    % maxcommitemails +
+                    '*** Expected a number.  Ignoring.\n'
                     )
 
         diffopts = config.get('diffopts')
@@ -2170,32 +2402,44 @@ def __init__(self, config, **kw):
         if commitlogopts is not None:
             self.commitlogopts = shlex.split(commitlogopts)
 
+        date_substitute = config.get('dateSubstitute')
+        if date_substitute == 'none':
+            self.date_substitute = None
+        elif date_substitute is not None:
+            self.date_substitute = date_substitute
+
         reply_to = config.get('replyTo')
         self.__reply_to_refchange = config.get('replyToRefchange', default=reply_to)
-        if (
-                self.__reply_to_refchange is not None
-                and self.__reply_to_refchange.lower() == 'author'
-                ):
-            raise ConfigurationException(
-                '"author" is not an allowed setting for replyToRefchange'
-                )
+        self.forbid_field_values('replyToRefchange',
+                                 self.__reply_to_refchange,
+                                 ['author'])
         self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
 
+        from_addr = self.config.get('from')
+        self.from_refchange = config.get('fromRefchange')
+        self.forbid_field_values('fromRefchange',
+                                 self.from_refchange,
+                                 ['author', 'none'])
+        self.from_commit = config.get('fromCommit')
+        self.forbid_field_values('fromCommit',
+                                 self.from_commit,
+                                 ['none'])
+
         combine = config.get_bool('combineWhenSingleCommit')
         if combine is not None:
             self.combine_when_single_commit = combine
 
     def get_administrator(self):
         return (
-            self.config.get('administrator')
-            or self.get_sender()
-            or super(ConfigOptionsEnvironmentMixin, self).get_administrator()
+            self.config.get('administrator') or
+            self.get_sender() or
+            super(ConfigOptionsEnvironmentMixin, self).get_administrator()
             )
 
     def get_repo_shortname(self):
         return (
-            self.config.get('reponame')
-            or super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname()
+            self.config.get('reponame') or
+            super(ConfigOptionsEnvironmentMixin, self).get_repo_shortname()
             )
 
     def get_emailprefix(self):
@@ -2212,33 +2456,42 @@ def get_emailprefix(self):
     def get_sender(self):
         return self.config.get('envelopesender')
 
-    def get_fromaddr(self):
+    def process_addr(self, addr, change):
+        if addr.lower() == 'author':
+            if hasattr(change, 'author'):
+                return change.author
+            else:
+                return None
+        elif addr.lower() == 'pusher':
+            return self.get_pusher_email()
+        elif addr.lower() == 'none':
+            return None
+        else:
+            return addr
+
+    def get_fromaddr(self, change=None):
         fromaddr = self.config.get('from')
+        if change:
+            alt_fromaddr = change.get_alt_fromaddr()
+            if alt_fromaddr:
+                fromaddr = alt_fromaddr
+        if fromaddr:
+            fromaddr = self.process_addr(fromaddr, change)
         if fromaddr:
             return fromaddr
-        return super(ConfigOptionsEnvironmentMixin, self).get_fromaddr()
+        return super(ConfigOptionsEnvironmentMixin, self).get_fromaddr(change)
 
     def get_reply_to_refchange(self, refchange):
         if self.__reply_to_refchange is None:
             return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_refchange(refchange)
-        elif self.__reply_to_refchange.lower() == 'pusher':
-            return self.get_pusher_email()
-        elif self.__reply_to_refchange.lower() == 'none':
-            return None
         else:
-            return self.__reply_to_refchange
+            return self.process_addr(self.__reply_to_refchange, refchange)
 
     def get_reply_to_commit(self, revision):
         if self.__reply_to_commit is None:
             return super(ConfigOptionsEnvironmentMixin, self).get_reply_to_commit(revision)
-        elif self.__reply_to_commit.lower() == 'author':
-            return revision.author
-        elif self.__reply_to_commit.lower() == 'pusher':
-            return self.get_pusher_email()
-        elif self.__reply_to_commit.lower() == 'none':
-            return None
         else:
-            return self.__reply_to_commit
+            return self.process_addr(self.__reply_to_commit, revision)
 
     def get_scancommitforcc(self):
         return self.config.get('scancommitforcc')
@@ -2270,12 +2523,14 @@ def __init__(self, strict_utf8=True, emailmaxlinelength=500, **kw):
     def filter_body(self, lines):
         lines = super(FilterLinesEnvironmentMixin, self).filter_body(lines)
         if self.__strict_utf8:
-            lines = (line.decode(ENCODING, 'replace') for line in lines)
+            if not PYTHON3:
+                lines = (line.decode(ENCODING, 'replace') for line in lines)
             # Limit the line length in Unicode-space to avoid
             # splitting characters:
             if self.__emailmaxlinelength:
                 lines = limit_linelength(lines, self.__emailmaxlinelength)
-            lines = (line.encode(ENCODING, 'replace') for line in lines)
+            if not PYTHON3:
+                lines = (line.encode(ENCODING, 'replace') for line in lines)
         elif self.__emailmaxlinelength:
             lines = limit_linelength(lines, self.__emailmaxlinelength)
 
@@ -2404,10 +2659,10 @@ def __init__(
         # actual *contents* of the change being reported, we only
         # choose based on the *type* of the change.  Therefore we can
         # compute them once and for all:
-        if not (refchange_recipients
-                or announce_recipients
-                or revision_recipients
-                or scancommitforcc):
+        if not (refchange_recipients or
+                announce_recipients or
+                revision_recipients or
+                scancommitforcc):
             raise ConfigurationException('No email recipients configured!')
         self.__refchange_recipients = refchange_recipients
         self.__announce_recipients = announce_recipients
@@ -2457,13 +2712,104 @@ def _get_recipients(self, config, *names):
         found, raise a ConfigurationException."""
 
         for name in names:
-            retval = config.get_recipients(name)
-            if retval is not None:
-                return retval
+            lines = config.get_all(name)
+            if lines is not None:
+                lines = [line.strip() for line in lines]
+                # Single "none" is a special value equivalen to empty string.
+                if lines == ['none']:
+                    lines = ['']
+                return ', '.join(lines)
         else:
             return ''
 
 
+class StaticRefFilterEnvironmentMixin(Environment):
+    """Set branch filter statically based on constructor parameters."""
+
+    def __init__(self, ref_filter_incl_regex, ref_filter_excl_regex,
+                 ref_filter_do_send_regex, ref_filter_dont_send_regex,
+                 **kw):
+        super(StaticRefFilterEnvironmentMixin, self).__init__(**kw)
+
+        if ref_filter_incl_regex and ref_filter_excl_regex:
+            raise ConfigurationException(
+                "Cannot specify both a ref inclusion and exclusion regex.")
+        self.__is_inclusion_filter = bool(ref_filter_incl_regex)
+        default_exclude = self.get_default_ref_ignore_regex()
+        if ref_filter_incl_regex:
+            ref_filter_regex = ref_filter_incl_regex
+        elif ref_filter_excl_regex:
+            ref_filter_regex = ref_filter_excl_regex + '|' + default_exclude
+        else:
+            ref_filter_regex = default_exclude
+        try:
+            self.__compiled_regex = re.compile(ref_filter_regex)
+        except Exception:
+            raise ConfigurationException(
+                'Invalid Ref Filter Regex "%s": %s' % (ref_filter_regex, sys.exc_info()[1]))
+
+        if ref_filter_do_send_regex and ref_filter_dont_send_regex:
+            raise ConfigurationException(
+                "Cannot specify both a ref doSend and dontSend regex.")
+        if ref_filter_do_send_regex or ref_filter_dont_send_regex:
+            self.__is_do_send_filter = bool(ref_filter_do_send_regex)
+            if ref_filter_incl_regex:
+                ref_filter_send_regex = ref_filter_incl_regex
+            elif ref_filter_excl_regex:
+                ref_filter_send_regex = ref_filter_excl_regex
+            else:
+                ref_filter_send_regex = '.*'
+                self.__is_do_send_filter = True
+            try:
+                self.__send_compiled_regex = re.compile(ref_filter_send_regex)
+            except Exception:
+                raise ConfigurationException(
+                    'Invalid Ref Filter Regex "%s": %s' %
+                    (ref_filter_send_regex, sys.exc_info()[1]))
+        else:
+            self.__send_compiled_regex = self.__compiled_regex
+            self.__is_do_send_filter = self.__is_inclusion_filter
+
+    def get_ref_filter_regex(self, send_filter=False):
+        if send_filter:
+            return self.__send_compiled_regex, self.__is_do_send_filter
+        else:
+            return self.__compiled_regex, self.__is_inclusion_filter
+
+
+class ConfigRefFilterEnvironmentMixin(
+        ConfigEnvironmentMixin,
+        StaticRefFilterEnvironmentMixin
+        ):
+    """Determine branch filtering statically based on config."""
+
+    def _get_regex(self, config, key):
+        """Get a list of whitespace-separated regex. The refFilter* config
+        variables are multivalued (hence the use of get_all), and we
+        allow each entry to be a whitespace-separated list (hence the
+        split on each line). The whole thing is glued into a single regex."""
+        values = config.get_all(key)
+        if values is None:
+            return values
+        items = []
+        for line in values:
+            for i in line.split():
+                items.append(i)
+        if items == []:
+            return None
+        return '|'.join(items)
+
+    def __init__(self, config, **kw):
+        super(ConfigRefFilterEnvironmentMixin, self).__init__(
+            config=config,
+            ref_filter_incl_regex=self._get_regex(config, 'refFilterInclusionRegex'),
+            ref_filter_excl_regex=self._get_regex(config, 'refFilterExclusionRegex'),
+            ref_filter_do_send_regex=self._get_regex(config, 'refFilterDoSendRegex'),
+            ref_filter_dont_send_regex=self._get_regex(config, 'refFilterDontSendRegex'),
+            **kw
+            )
+
+
 class ProjectdescEnvironmentMixin(Environment):
     """Make a "projectdesc" value available for templates.
 
@@ -2499,6 +2845,7 @@ class GenericEnvironment(
         ComputeFQDNEnvironmentMixin,
         ConfigFilterLinesEnvironmentMixin,
         ConfigRecipientsEnvironmentMixin,
+        ConfigRefFilterEnvironmentMixin,
         PusherDomainEnvironmentMixin,
         ConfigOptionsEnvironmentMixin,
         GenericEnvironmentMixin,
@@ -2513,14 +2860,14 @@ def get_repo_shortname(self):
         # repo_shortname (though it's probably not as good as a value
         # the user might have explicitly put in his config).
         return (
-            self.osenv.get('GL_REPO', None)
-            or super(GitoliteEnvironmentMixin, self).get_repo_shortname()
+            self.osenv.get('GL_REPO', None) or
+            super(GitoliteEnvironmentMixin, self).get_repo_shortname()
             )
 
     def get_pusher(self):
         return self.osenv.get('GL_USER', 'unknown user')
 
-    def get_fromaddr(self):
+    def get_fromaddr(self, change=None):
         GL_USER = self.osenv.get('GL_USER')
         if GL_USER is not None:
             # Find the path to gitolite.conf.  Note that gitolite v3
@@ -2536,9 +2883,9 @@ def get_fromaddr(self):
                 f = open(GL_CONF, 'rU')
                 try:
                     in_user_emails_section = False
-                    re_template = r'^\s*#\s*{}\s*$'
+                    re_template = r'^\s*#\s*%s\s*$'
                     re_begin, re_user, re_end = (
-                        re.compile(re_template.format(x))
+                        re.compile(re_template % x)
                         for x in (
                             r'BEGIN\s+USER\s+EMAILS',
                             re.escape(GL_USER) + r'\s+(.*)',
@@ -2557,7 +2904,7 @@ def get_fromaddr(self):
                             return m.group(1)
                 finally:
                     f.close()
-        return super(GitoliteEnvironmentMixin, self).get_fromaddr()
+        return super(GitoliteEnvironmentMixin, self).get_fromaddr(change)
 
 
 class IncrementalDateTime(object):
@@ -2570,8 +2917,9 @@ class IncrementalDateTime(object):
 
     def __init__(self):
         self.time = time.time()
+        self.next = self.__next__  # Python 2 backward compatibility
 
-    def next(self):
+    def __next__(self):
         formatted = formatdate(self.time, True)
         self.time += 1
         return formatted
@@ -2583,6 +2931,7 @@ class GitoliteEnvironment(
         ComputeFQDNEnvironmentMixin,
         ConfigFilterLinesEnvironmentMixin,
         ConfigRecipientsEnvironmentMixin,
+        ConfigRefFilterEnvironmentMixin,
         PusherDomainEnvironmentMixin,
         ConfigOptionsEnvironmentMixin,
         GitoliteEnvironmentMixin,
@@ -2591,6 +2940,117 @@ class GitoliteEnvironment(
     pass
 
 
+class StashEnvironmentMixin(Environment):
+    def __init__(self, user=None, repo=None, **kw):
+        super(StashEnvironmentMixin, self).__init__(**kw)
+        self.__user = user
+        self.__repo = repo
+
+    def get_repo_shortname(self):
+        return self.__repo
+
+    def get_pusher(self):
+        return re.match('(.*?)\s*<', self.__user).group(1)
+
+    def get_pusher_email(self):
+        return self.__user
+
+    def get_fromaddr(self, change=None):
+        return self.__user
+
+
+class StashEnvironment(
+        StashEnvironmentMixin,
+        ProjectdescEnvironmentMixin,
+        ConfigMaxlinesEnvironmentMixin,
+        ComputeFQDNEnvironmentMixin,
+        ConfigFilterLinesEnvironmentMixin,
+        ConfigRecipientsEnvironmentMixin,
+        ConfigRefFilterEnvironmentMixin,
+        PusherDomainEnvironmentMixin,
+        ConfigOptionsEnvironmentMixin,
+        Environment,
+        ):
+    pass
+
+
+class GerritEnvironmentMixin(Environment):
+    def __init__(self, project=None, submitter=None, update_method=None, **kw):
+        super(GerritEnvironmentMixin, self).__init__(**kw)
+        self.__project = project
+        self.__submitter = submitter
+        self.__update_method = update_method
+        "Make an 'update_method' value available for templates."
+        self.COMPUTED_KEYS += ['update_method']
+
+    def get_repo_shortname(self):
+        return self.__project
+
+    def get_pusher(self):
+        if self.__submitter:
+            if self.__submitter.find('<') != -1:
+                # Submitter has a configured email, we transformed
+                # __submitter into an RFC 2822 string already.
+                return re.match('(.*?)\s*<', self.__submitter).group(1)
+            else:
+                # Submitter has no configured email, it's just his name.
+                return self.__submitter
+        else:
+            # If we arrive here, this means someone pushed "Submit" from
+            # the gerrit web UI for the CR (or used one of the programmatic
+            # APIs to do the same, such as gerrit review) and the
+            # merge/push was done by the Gerrit user.  It was technically
+            # triggered by someone else, but sadly we have no way of
+            # determining who that someone else is at this point.
+            return 'Gerrit'  # 'unknown user'?
+
+    def get_pusher_email(self):
+        if self.__submitter:
+            return self.__submitter
+        else:
+            return super(GerritEnvironmentMixin, self).get_pusher_email()
+
+    def get_fromaddr(self, change=None):
+        if self.__submitter and self.__submitter.find('<') != -1:
+            return self.__submitter
+        else:
+            return super(GerritEnvironmentMixin, self).get_fromaddr(change)
+
+    def get_default_ref_ignore_regex(self):
+        default = super(GerritEnvironmentMixin, self).get_default_ref_ignore_regex()
+        return default + '|^refs/changes/|^refs/cache-automerge/|^refs/meta/'
+
+    def get_revision_recipients(self, revision):
+        # Merge commits created by Gerrit when users hit "Submit this patchset"
+        # in the Web UI (or do equivalently with REST APIs or the gerrit review
+        # command) are not something users want to see an individual email for.
+        # Filter them out.
+        committer = read_git_output(['log', '--no-walk', '--format=%cN',
+                                     revision.rev.sha1])
+        if committer == 'Gerrit Code Review':
+            return []
+        else:
+            return super(GerritEnvironmentMixin, self).get_revision_recipients(revision)
+
+    def get_update_method(self):
+        return self.__update_method
+
+
+class GerritEnvironment(
+        GerritEnvironmentMixin,
+        ProjectdescEnvironmentMixin,
+        ConfigMaxlinesEnvironmentMixin,
+        ComputeFQDNEnvironmentMixin,
+        ConfigFilterLinesEnvironmentMixin,
+        ConfigRecipientsEnvironmentMixin,
+        ConfigRefFilterEnvironmentMixin,
+        PusherDomainEnvironmentMixin,
+        ConfigOptionsEnvironmentMixin,
+        Environment,
+        ):
+    pass
+
+
 class Push(object):
     """Represent an entire push (i.e., a group of ReferenceChanges).
 
@@ -2673,10 +3133,11 @@ class is to figure out these things, and to make sure that new
             ])
         )
 
-    def __init__(self, changes, ignore_other_refs=False):
+    def __init__(self, environment, changes, ignore_other_refs=False):
         self.changes = sorted(changes, key=self._sort_key)
         self.__other_ref_sha1s = None
         self.__cached_commits_spec = {}
+        self.environment = environment
 
         if ignore_other_refs:
             self.__other_ref_sha1s = set()
@@ -2703,10 +3164,14 @@ def _other_ref_sha1s(self):
                 '%(objectname) %(objecttype) %(refname)\n'
                 '%(*objectname) %(*objecttype) %(refname)'
                 )
+            ref_filter_regex, is_inclusion_filter = \
+                self.environment.get_ref_filter_regex()
             for line in read_git_lines(
                     ['for-each-ref', '--format=%s' % (fmt,)]):
                 (sha1, type, name) = line.split(' ', 2)
-                if sha1 and type == 'commit' and name not in updated_refs:
+                if (sha1 and type == 'commit' and
+                        name not in updated_refs and
+                        include_ref(name, ref_filter_regex, is_inclusion_filter)):
                     sha1s.add(sha1)
 
             self.__other_ref_sha1s = sha1s
@@ -2856,7 +3321,7 @@ def send_emails(self, mailer, body_filter=None):
                 if not change.environment.quiet:
                     change.environment.log_msg(
                         'Sending notification emails to: %s\n' % (change.recipients,))
-                extra_values = {'send_date': send_date.next()}
+                extra_values = {'send_date': next(send_date)}
 
                 rev = change.send_single_combined_email(sha1s)
                 if rev:
@@ -2876,9 +3341,9 @@ def send_emails(self, mailer, body_filter=None):
             max_emails = change.environment.maxcommitemails
             if max_emails and len(sha1s) > max_emails:
                 change.environment.log_warning(
-                    '*** Too many new commits (%d), not sending commit emails.\n' % len(sha1s)
-                    + '*** Try setting multimailhook.maxCommitEmails to a greater value\n'
-                    '*** Currently, multimailhook.maxCommitEmails=%d\n' % max_emails
+                    '*** Too many new commits (%d), not sending commit emails.\n' % len(sha1s) +
+                    '*** Try setting multimailhook.maxCommitEmails to a greater value\n' +
+                    '*** Currently, multimailhook.maxCommitEmails=%d\n' % max_emails
                     )
                 return
 
@@ -2889,7 +3354,7 @@ def send_emails(self, mailer, body_filter=None):
                     rev.recipients = rev.cc_recipients
                     rev.cc_recipients = None
                 if rev.recipients:
-                    extra_values = {'send_date': send_date.next()}
+                    extra_values = {'send_date': next(send_date)}
                     mailer.send(
                         rev.generate_email(self, body_filter, extra_values),
                         rev.recipients,
@@ -2904,18 +3369,33 @@ def send_emails(self, mailer, body_filter=None):
                 )
 
 
+def include_ref(refname, ref_filter_regex, is_inclusion_filter):
+    does_match = bool(ref_filter_regex.search(refname))
+    if is_inclusion_filter:
+        return does_match
+    else:  # exclusion filter -- we include the ref if the regex doesn't match
+        return not does_match
+
+
 def run_as_post_receive_hook(environment, mailer):
+    ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(True)
     changes = []
     for line in sys.stdin:
         (oldrev, newrev, refname) = line.strip().split(' ', 2)
+        if not include_ref(refname, ref_filter_regex, is_inclusion_filter):
+            continue
         changes.append(
             ReferenceChange.create(environment, oldrev, newrev, refname)
             )
-    push = Push(changes)
-    push.send_emails(mailer, body_filter=environment.filter_body)
+    if changes:
+        push = Push(environment, changes)
+        push.send_emails(mailer, body_filter=environment.filter_body)
 
 
 def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
+    ref_filter_regex, is_inclusion_filter = environment.get_ref_filter_regex(True)
+    if not include_ref(refname, ref_filter_regex, is_inclusion_filter):
+        return
     changes = [
         ReferenceChange.create(
             environment,
@@ -2924,7 +3404,7 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=
             refname,
             ),
         ]
-    push = Push(changes, force_send)
+    push = Push(environment, changes, force_send)
     push.send_emails(mailer, body_filter=environment.filter_body)
 
 
@@ -2953,8 +3433,8 @@ def choose_mailer(config, environment):
         mailer = SendMailer(command=command, envelopesender=environment.get_sender())
     else:
         environment.log_error(
-            'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' % mailer
-            'please use one of "smtp" or "sendmail".\n'
+            'fatal: multimailhook.mailer is set to an incorrect value: "%s"\n' % mailer +
+            'please use one of "smtp" or "sendmail".\n'
             )
         sys.exit(1)
     return mailer
@@ -2963,14 +3443,18 @@ def choose_mailer(config, environment):
 KNOWN_ENVIRONMENTS = {
     'generic': GenericEnvironmentMixin,
     'gitolite': GitoliteEnvironmentMixin,
+    'stash': StashEnvironmentMixin,
+    'gerrit': GerritEnvironmentMixin,
     }
 
 
-def choose_environment(config, osenv=None, env=None, recipients=None):
+def choose_environment(config, osenv=None, env=None, recipients=None,
+                       hook_info=None):
     if not osenv:
         osenv = os.environ
 
     environment_mixins = [
+        ConfigRefFilterEnvironmentMixin,
         ProjectdescEnvironmentMixin,
         ConfigMaxlinesEnvironmentMixin,
         ComputeFQDNEnvironmentMixin,
@@ -2992,7 +3476,15 @@ def choose_environment(config, osenv=None, env=None, recipients=None):
         else:
             env = 'generic'
 
-    environment_mixins.append(KNOWN_ENVIRONMENTS[env])
+    environment_mixins.insert(0, KNOWN_ENVIRONMENTS[env])
+
+    if env == 'stash':
+        environment_kw['user'] = hook_info['stash_user']
+        environment_kw['repo'] = hook_info['stash_repo']
+    elif env == 'gerrit':
+        environment_kw['project'] = hook_info['project']
+        environment_kw['submitter'] = hook_info['submitter']
+        environment_kw['update_method'] = hook_info['update_method']
 
     if recipients:
         environment_mixins.insert(0, StaticRecipientsEnvironmentMixin)
@@ -3011,6 +3503,116 @@ def choose_environment(config, osenv=None, env=None, recipients=None):
     return environment_klass(**environment_kw)
 
 
+def get_version():
+    oldcwd = os.getcwd()
+    try:
+        try:
+            os.chdir(os.path.dirname(os.path.realpath(__file__)))
+            git_version = read_git_output(['describe', '--tags', 'HEAD'])
+            if git_version == __version__:
+                return git_version
+            else:
+                return '%s (%s)' % (__version__, git_version)
+        except:
+            pass
+    finally:
+        os.chdir(oldcwd)
+    return __version__
+
+
+def compute_gerrit_options(options, args, required_gerrit_options):
+    if None in required_gerrit_options:
+        raise SystemExit("Error: Specify all of --oldrev, --newrev, --refname, "
+                         "and --project; or none of them.")
+
+    if options.environment not in (None, 'gerrit'):
+        raise SystemExit("Non-gerrit environments incompatible with --oldrev, "
+                         "--newrev, --refname, and --project")
+    options.environment = 'gerrit'
+
+    if args:
+        raise SystemExit("Error: Positional parameters not allowed with "
+                         "--oldrev, --newrev, and --refname.")
+
+    # Gerrit oddly omits 'refs/heads/' in the refname when calling
+    # ref-updated hook; put it back.
+    git_dir = get_git_dir()
+    if (not os.path.exists(os.path.join(git_dir, options.refname)) and
+        os.path.exists(os.path.join(git_dir, 'refs', 'heads',
+                                    options.refname))):
+        options.refname = 'refs/heads/' + options.refname
+
+    # Convert each string option unicode for Python3.
+    if PYTHON3:
+        opts = ['environment', 'recipients', 'oldrev', 'newrev', 'refname',
+                'project', 'submitter', 'stash-user', 'stash-repo']
+        for opt in opts:
+            if not hasattr(options, opt):
+                continue
+            obj = getattr(options, opt)
+            if obj:
+                enc = obj.encode('utf-8', 'surrogateescape')
+                dec = enc.decode('utf-8', 'replace')
+                setattr(options, opt, dec)
+
+    # New revisions can appear in a gerrit repository either due to someone
+    # pushing directly (in which case options.submitter will be set), or they
+    # can press "Submit this patchset" in the web UI for some CR (in which
+    # case options.submitter will not be set and gerrit will not have provided
+    # us the information about who pressed the button).
+    #
+    # Note for the nit-picky: I'm lumping in REST API calls and the ssh
+    # gerrit review command in with "Submit this patchset" button, since they
+    # have the same effect.
+    if options.submitter:
+        update_method = 'pushed'
+        # The submitter argument is almost an RFC 2822 email address; change it
+        # from 'User Name (email@domain)' to 'User Name <email@domain>' so it is
+        options.submitter = options.submitter.replace('(', '<').replace(')', '>')
+    else:
+        update_method = 'submitted'
+        # Gerrit knew who submitted this patchset, but threw that information
+        # away when it invoked this hook.  However, *IF* Gerrit created a
+        # merge to bring the patchset in (project 'Submit Type' is either
+        # "Always Merge", or is "Merge if Necessary" and happens to be
+        # necessary for this particular CR), then it will have the committer
+        # of that merge be 'Gerrit Code Review' and the author will be the
+        # person who requested the submission of the CR.  Since this is fairly
+        # likely for most gerrit installations (of a reasonable size), it's
+        # worth the extra effort to try to determine the actual submitter.
+        rev_info = read_git_lines(['log', '--no-walk', '--merges',
+                                   '--format=%cN%n%aN <%aE>', options.newrev])
+        if rev_info and rev_info[0] == 'Gerrit Code Review':
+            options.submitter = rev_info[1]
+
+    # We pass back refname, oldrev, newrev as args because then the
+    # gerrit ref-updated hook is much like the git update hook
+    return (options,
+            [options.refname, options.oldrev, options.newrev],
+            {'project': options.project, 'submitter': options.submitter,
+             'update_method': update_method})
+
+
+def check_hook_specific_args(options, args):
+    # First check for stash arguments
+    if (options.stash_user is None) != (options.stash_repo is None):
+        raise SystemExit("Error: Specify both of --stash-user and "
+                         "--stash-repo or neither.")
+    if options.stash_user:
+        options.environment = 'stash'
+        return options, args, {'stash_user': options.stash_user,
+                               'stash_repo': options.stash_repo}
+
+    # Finally, check for gerrit specific arguments
+    required_gerrit_options = (options.oldrev, options.newrev, options.refname,
+                               options.project)
+    if required_gerrit_options != (None,) * 4:
+        return compute_gerrit_options(options, args, required_gerrit_options)
+
+    # No special options in use, just return what we started with
+    return options, args, {}
+
+
 def main(args):
     parser = optparse.OptionParser(
         description=__doc__,
@@ -3019,7 +3621,7 @@ def main(args):
 
     parser.add_option(
         '--environment', '--env', action='store', type='choice',
-        choices=['generic', 'gitolite'], default=None,
+        choices=list(KNOWN_ENVIRONMENTS.keys()), default=None,
         help=(
             'Choose type of environment is in use.  Default is taken from '
             'multimailhook.environment if set; otherwise "generic".'
@@ -3048,8 +3650,58 @@ def main(args):
             'detection in this mode.'
             ),
         )
+    parser.add_option(
+        '-c', metavar="<name>=<value>", action='append',
+        help=(
+            'Pass a configuration parameter through to git.  The value given '
+            'will override values from configuration files.  See the -c option '
+            'of git(1) for more details.  (Only works with git >= 1.7.3)'
+            ),
+        )
+    parser.add_option(
+        '--version', '-v', action='store_true', default=False,
+        help=(
+            "Display git-multimail's version"
+            ),
+        )
+    # The following options permit this script to be run as a gerrit
+    # ref-updated hook.  See e.g.
+    # code.google.com/p/gerrit/source/browse/Documentation/config-hooks.txt
+    # We suppress help for these items, since these are specific to gerrit,
+    # and we don't want users directly using them any way other than how the
+    # gerrit ref-updated hook is called.
+    parser.add_option('--oldrev', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--newrev', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--refname', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--project', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--submitter', action='store', help=optparse.SUPPRESS_HELP)
+
+    # The following allow this to be run as a stash asynchronous post-receive
+    # hook (almost identical to a git post-receive hook but triggered also for
+    # merges of pull requests from the UI).  We suppress help for these items,
+    # since these are specific to stash.
+    parser.add_option('--stash-user', action='store', help=optparse.SUPPRESS_HELP)
+    parser.add_option('--stash-repo', action='store', help=optparse.SUPPRESS_HELP)
 
     (options, args) = parser.parse_args(args)
+    (options, args, hook_info) = check_hook_specific_args(options, args)
+
+    if options.version:
+        sys.stdout.write('git-multimail version ' + get_version() + '\n')
+        return
+
+    if options.c:
+        parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
+        if parameters:
+            parameters += ' '
+        # git expects GIT_CONFIG_PARAMETERS to be of the form
+        #    "'name1=value1' 'name2=value2' 'name3=value3'"
+        # including everything inside the double quotes (but not the double
+        # quotes themselves).  Spacing is critical.  Also, if a value contains
+        # a literal single quote that quote must be represented using the
+        # four character sequence: '\''
+        parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in options.c)
+        os.environ['GIT_CONFIG_PARAMETERS'] = parameters
 
     config = Config('multimailhook')
 
@@ -3058,6 +3710,7 @@ def main(args):
             config, osenv=os.environ,
             env=options.environment,
             recipients=options.recipients,
+            hook_info=hook_info,
             )
 
         if options.show_env:
@@ -3080,9 +3733,20 @@ def main(args):
             run_as_update_hook(environment, mailer, refname, oldrev, newrev, options.force_send)
         else:
             run_as_post_receive_hook(environment, mailer)
-    except ConfigurationException, e:
-        sys.exit(str(e))
-
+    except ConfigurationException:
+        sys.exit(sys.exc_info()[1])
+    except Exception:
+        t, e, tb = sys.exc_info()
+        import traceback
+        sys.stdout.write('\n')
+        sys.stdout.write('Exception \'' + t.__name__ +
+                         '\' raised. Please report this as a bug to\n')
+        sys.stdout.write('https://github.com/git-multimail/git-multimail/issues\n')
+        sys.stdout.write('with the information below:\n\n')
+        sys.stdout.write('git-multimail version ' + get_version() + '\n')
+        sys.stdout.write('Python version ' + sys.version + '\n')
+        traceback.print_exc(file=sys.stdout)
+        sys.exit(1)
 
 if __name__ == '__main__':
     main(sys.argv[1:])
index d0e9b392013b1dc0f8beeaead3a08d91ce5c3f03..992657bbdc3545070496fbb43a287679cba03ca1 100755 (executable)
@@ -1,4 +1,4 @@
-#! /usr/bin/env python2
+#! /usr/bin/env python
 
 """Migrate a post-receive-email configuration to be usable with git_multimail.py.
 
index 43f7b6b635b15b1dd3648d2f91b78d4e31acf504..9975df7107ac29be2b2ac861b90dfc0730ec7b97 100755 (executable)
@@ -1,4 +1,4 @@
-#! /usr/bin/env python2
+#! /usr/bin/env python
 
 """Example post-receive hook based on git-multimail.
 
@@ -42,7 +42,6 @@ import os
 
 import git_multimail
 
-
 # It is possible to modify the output templates here; e.g.:
 
 #git_multimail.FOOTER_TEMPLATE = """\
@@ -61,8 +60,9 @@ config = git_multimail.Config('multimailhook')
 try:
     environment = git_multimail.GenericEnvironment(config=config)
     #environment = git_multimail.GitoliteEnvironment(config=config)
-except git_multimail.ConfigurationException, e:
-    sys.exit(str(e))
+except git_multimail.ConfigurationException:
+    sys.stderr.write('*** %s\n' % sys.exc_info()[1])
+    sys.exit(1)
 
 
 # Choose the method of sending emails based on the git config:
index 9f065718513c5c1e82d355aa9dab40ee421b765e..308b777b0aa43a7466fb3402cc67c1f023f57a7a 100755 (executable)
@@ -648,7 +648,7 @@ cmd_split()
                debug "Merging split branch into HEAD..."
                latest_old=$(cache_get latest_old)
                git merge -s ours \
-                       -m "$(rejoin_msg $dir $latest_old $latest_new)" \
+                       -m "$(rejoin_msg "$dir" $latest_old $latest_new)" \
                        $latest_new >&2 || exit $?
        fi
        if [ -n "$branch" ]; then
@@ -735,7 +735,7 @@ cmd_push()
            refspec=$2
            echo "git push using: " $repository $refspec
            localrev=$(git subtree split --prefix="$prefix") || die
-           git push $repository $localrev:refs/heads/$refspec
+           git push "$repository" $localrev:refs/heads/$refspec
        else
            die "'$dir' must already exist. Try 'git subtree add'."
        fi
index 90519823be381390b7f89a1eb2f6007f1e8c86f4..dfbe443deaf1739e47d3f0f791331a3ee6773174 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 #
 # Copyright (c) 2012 Avery Pennaraum
+# Copyright (c) 2015 Alexey Shumkin
 #
 test_description='Basic porcelain support for subtrees
 
@@ -32,25 +33,6 @@ check_equal()
        fi
 }
 
-fixnl()
-{
-       t=""
-       while read x; do
-               t="$t$x "
-       done
-       echo $t
-}
-
-multiline()
-{
-       while read x; do
-               set -- $x
-               for d in "$@"; do
-                       echo "$d"
-               done
-       done
-}
-
 undo()
 {
        git reset --hard HEAD~
@@ -62,11 +44,11 @@ last_commit_message()
 }
 
 test_expect_success 'init subproj' '
-       test_create_repo subproj
+       test_create_repo "sub proj"
 '
 
 # To the subproject!
-cd subproj
+cd ./"sub proj"
 
 test_expect_success 'add sub1' '
        create sub1 &&
@@ -106,39 +88,39 @@ test_expect_success 'add main4' '
 '
 
 test_expect_success 'fetch subproj history' '
-       git fetch ./subproj sub1 &&
+       git fetch ./"sub proj" sub1 &&
        git branch sub1 FETCH_HEAD
 '
 
 test_expect_success 'no subtree exists in main tree' '
-       test_must_fail git subtree merge --prefix=subdir sub1
+       test_must_fail git subtree merge --prefix="sub dir" sub1
 '
 
 test_expect_success 'no pull from non-existant subtree' '
-       test_must_fail git subtree pull --prefix=subdir ./subproj sub1
+       test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" sub1
 '
 
 test_expect_success 'check if --message works for add' '
-       git subtree add --prefix=subdir --message="Added subproject" sub1 &&
+       git subtree add --prefix="sub dir" --message="Added subproject" sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject" &&
        undo
 '
 
 test_expect_success 'check if --message works as -m and --prefix as -P' '
-       git subtree add -P subdir -m "Added subproject using git subtree" sub1 &&
+       git subtree add -P "sub dir" -m "Added subproject using git subtree" sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
        undo
 '
 
 test_expect_success 'check if --message works with squash too' '
-       git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
+       git subtree add -P "sub dir" -m "Added subproject with squash" --squash sub1 &&
        check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
        undo
 '
 
 test_expect_success 'add subproj to mainline' '
-       git subtree add --prefix=subdir/ FETCH_HEAD &&
-       check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
+       git subtree add --prefix="sub dir"/ FETCH_HEAD &&
+       check_equal ''"$(last_commit_message)"'' "Add '"'sub dir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
 '
 
 # this shouldn't actually do anything, since FETCH_HEAD is already a parent
@@ -147,7 +129,7 @@ test_expect_success 'merge fetched subproj' '
 '
 
 test_expect_success 'add main-sub5' '
-       create subdir/main-sub5 &&
+       create "sub dir/main-sub5" &&
        git commit -m "main-sub5"
 '
 
@@ -157,29 +139,29 @@ test_expect_success 'add main6' '
 '
 
 test_expect_success 'add main-sub7' '
-       create subdir/main-sub7 &&
+       create "sub dir/main-sub7" &&
        git commit -m "main-sub7"
 '
 
 test_expect_success 'fetch new subproj history' '
-       git fetch ./subproj sub2 &&
+       git fetch ./"sub proj" sub2 &&
        git branch sub2 FETCH_HEAD
 '
 
 test_expect_success 'check if --message works for merge' '
-       git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
+       git subtree merge --prefix="sub dir" -m "Merged changes from subproject" sub2 &&
        check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
        undo
 '
 
 test_expect_success 'check if --message for merge works with squash too' '
-       git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 &&
+       git subtree merge --prefix "sub dir" -m "Merged changes from subproject using squash" --squash sub2 &&
        check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
        undo
 '
 
 test_expect_success 'merge new subproj history into subdir' '
-       git subtree merge --prefix=subdir FETCH_HEAD &&
+       git subtree merge --prefix="sub dir" FETCH_HEAD &&
        git branch pre-split &&
        check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline" &&
        undo
@@ -208,53 +190,53 @@ test_expect_success 'Check that the <prefix> exists for a split' '
 '
 
 test_expect_success 'check if --message works for split+rejoin' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        git branch spl1 "$spl1" &&
        check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
        undo
 '
 
 test_expect_success 'check split with --branch' '
-       spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
+       spl1=$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
        undo &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr1 &&
        check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
 '
 
 test_expect_success 'check hash of split' '
-       spl1=$(git subtree split --prefix subdir) &&
-       git subtree split --prefix subdir --branch splitbr1test &&
+       spl1=$(git subtree split --prefix "sub dir") &&
+       git subtree split --prefix "sub dir" --branch splitbr1test &&
        check_equal ''"$(git rev-parse splitbr1test)"'' "$spl1" &&
        new_hash=$(git rev-parse splitbr1test~2) &&
        check_equal ''"$new_hash"'' "$subdir_hash"
 '
 
 test_expect_success 'check split with --branch for an existing branch' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        undo &&
        git branch splitbr2 sub1 &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr2 &&
        check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
 '
 
 test_expect_success 'check split with --branch for an incompatible branch' '
-       test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
+       test_must_fail git subtree split --prefix "sub dir" --onto FETCH_HEAD --branch subdir
 '
 
 test_expect_success 'check split+rejoin' '
-       spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+       spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
        undo &&
-       git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin &&
-       check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'"
+       git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --rejoin &&
+       check_equal ''"$(last_commit_message)"'' "Split '"'"'sub dir/'"'"' into commit '"'"'"$spl1"'"'"'"
 '
 
 test_expect_success 'add main-sub8' '
-       create subdir/main-sub8 &&
+       create "sub dir/main-sub8" &&
        git commit -m "main-sub8"
 '
 
 # To the subproject!
-cd ./subproj
+cd ./"sub proj"
 
 test_expect_success 'merge split into subproj' '
        git fetch .. spl1 &&
@@ -271,22 +253,22 @@ test_expect_success 'add sub9' '
 cd ..
 
 test_expect_success 'split for sub8' '
-       split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"'' &&
+       split2=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir/" --rejoin)"'' &&
        git branch split2 "$split2"
 '
 
 test_expect_success 'add main-sub10' '
-       create subdir/main-sub10 &&
+       create "sub dir/main-sub10" &&
        git commit -m "main-sub10"
 '
 
 test_expect_success 'split for sub10' '
-       spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
+       spl3=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --rejoin)"'' &&
        git branch spl3 "$spl3"
 '
 
 # To the subproject!
-cd ./subproj
+cd ./"sub proj"
 
 test_expect_success 'merge split into subproj' '
        git fetch .. spl3 &&
@@ -295,42 +277,64 @@ test_expect_success 'merge split into subproj' '
        git branch subproj-merge-spl3
 '
 
-chkm="main4 main6"
-chkms="main-sub10 main-sub5 main-sub7 main-sub8"
-chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)
-chks="sub1 sub2 sub3 sub9"
-chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)
+chkm="main4
+main6"
+chkms="main-sub10
+main-sub5
+main-sub7
+main-sub8"
+chkms_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chkms
+TXT
+)
+chks="sub1
+sub2
+sub3
+sub9"
+chks_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chks
+TXT
+)
 
 test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
-       subfiles=''"$(git ls-files | fixnl)"'' &&
-       check_equal "$subfiles" "$chkms $chks"
+       subfiles="$(git ls-files)" &&
+       check_equal "$subfiles" "$chkms
+$chks"
 '
-
 test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' '
-       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
-       check_equal "$allchanges" "$chkms $chks"
+       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' &&
+       check_equal "$allchanges" "$chkms
+$chks"
 '
 
 # Back to mainline
 cd ..
 
 test_expect_success 'pull from subproj' '
-       git fetch ./subproj subproj-merge-spl3 &&
+       git fetch ./"sub proj" subproj-merge-spl3 &&
        git branch subproj-merge-spl3 FETCH_HEAD &&
-       git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
+       git subtree pull --prefix="sub dir" ./"sub proj" subproj-merge-spl3
 '
 
 test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
-       mainfiles=''"$(git ls-files | fixnl)"'' &&
-       check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub"
+       mainfiles=$(git ls-files) &&
+       check_equal "$mainfiles" "$chkm
+$chkms_sub
+$chks_sub"
 '
 
 test_expect_success 'make sure each filename changed exactly once in the entire history' '
        # main-sub?? and /subdir/main-sub?? both change, because those are the
        # changes that were split into their own history.  And subdir/sub?? never
        # change, since they were *only* changed in the subtree branch.
-       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
-       check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"''
+       allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' &&
+       check_equal "$allchanges" ''"$(cat <<TXT | sort
+$chkms
+$chkm
+$chks
+$chkms_sub
+TXT
+)"''
 '
 
 test_expect_success 'make sure the --rejoin commits never make it into subproj' '
@@ -377,7 +381,7 @@ cd ../main
 test_expect_success 'add sub as subdir in main' '
        git fetch ../sub master &&
        git branch sub2 FETCH_HEAD &&
-       git subtree add --prefix subdir sub2
+       git subtree add --prefix "sub dir" sub2
 '
 
 cd ../sub
@@ -392,16 +396,16 @@ cd ../main
 test_expect_success 'merge from sub' '
        git fetch ../sub master &&
        git branch sub3 FETCH_HEAD &&
-       git subtree merge --prefix subdir sub3
+       git subtree merge --prefix "sub dir" sub3
 '
 
 test_expect_success 'add main-sub4' '
-       create subdir/main-sub4 &&
+       create "sub dir/main-sub4" &&
        git commit -m "main-sub4"
 '
 
 test_expect_success 'split for main-sub4 without --onto' '
-       git subtree split --prefix subdir --branch mainsub4
+       git subtree split --prefix "sub dir" --branch mainsub4
 '
 
 # at this point, the new commit parent should be sub3 if it is not,
@@ -468,4 +472,50 @@ test_expect_success 'verify one file change per commit' '
        ))
 '
 
+# test push
+
+cd ../..
+
+mkdir test-push
+
+cd test-push
+
+test_expect_success 'init main' '
+       test_create_repo main
+'
+
+test_expect_success 'init sub' '
+       test_create_repo "sub project"
+'
+
+cd ./"sub project"
+
+test_expect_success 'add subproject' '
+       create "sub project" &&
+       git commit -m "Sub project: 1" &&
+       git branch sub-branch-1
+'
+
+cd ../main
+
+test_expect_success 'make first commit and add subproject' '
+       create "main-1" &&
+       git commit -m "main: 1" &&
+       git subtree add "../sub project" --prefix "sub dir" --message "Added subproject" sub-branch-1 &&
+       check_equal "$(last_commit_message)" "Added subproject"
+'
+
+test_expect_success 'make second commit to a subproject file and push it into a sub project' '
+       create "sub dir/sub1" &&
+       git commit -m "Sub project: 2" &&
+       git subtree push "../sub project" --prefix "sub dir" sub-branch-1
+'
+
+cd ../"sub project"
+
+test_expect_success 'Test second commit is pushed' '
+       git checkout sub-branch-1 &&
+       check_equal "$(last_commit_message)" "Sub project: 2"
+'
+
 test_done
index f3bd3e93fb2ecf95413db3c53d7e686cd03d1e69..814e814438b7c0f4f84850787670766fb4765f08 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1289,7 +1289,8 @@ static struct stream_filter *ident_filter(const unsigned char *sha1)
 {
        struct ident_filter *ident = xmalloc(sizeof(*ident));
 
-       sprintf(ident->ident, ": %s $", sha1_to_hex(sha1));
+       xsnprintf(ident->ident, sizeof(ident->ident),
+                 ": %s $", sha1_to_hex(sha1));
        strbuf_init(&ident->left, 0);
        ident->filter.vtbl = &ident_vtbl;
        ident->state = 0;
index f9eb296888c37c8f2a523f3941be027ddb36df3c..56679a15fee533a29168b92ecc0f18f68f919085 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -811,8 +811,6 @@ static char **cld_argv;
 static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
 {
        struct child_process cld = CHILD_PROCESS_INIT;
-       char addrbuf[300] = "REMOTE_ADDR=", portbuf[300];
-       char *env[] = { addrbuf, portbuf, NULL };
 
        if (max_connections && live_children >= max_connections) {
                kill_some_child();
@@ -826,27 +824,23 @@ static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
        }
 
        if (addr->sa_family == AF_INET) {
+               char buf[128] = "";
                struct sockaddr_in *sin_addr = (void *) addr;
-               inet_ntop(addr->sa_family, &sin_addr->sin_addr, addrbuf + 12,
-                   sizeof(addrbuf) - 12);
-               snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
-                   ntohs(sin_addr->sin_port));
+               inet_ntop(addr->sa_family, &sin_addr->sin_addr, buf, sizeof(buf));
+               argv_array_pushf(&cld.env_array, "REMOTE_ADDR=%s", buf);
+               argv_array_pushf(&cld.env_array, "REMOTE_PORT=%d",
+                                ntohs(sin_addr->sin_port));
 #ifndef NO_IPV6
        } else if (addr->sa_family == AF_INET6) {
+               char buf[128] = "";
                struct sockaddr_in6 *sin6_addr = (void *) addr;
-
-               char *buf = addrbuf + 12;
-               *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
-               inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf,
-                   sizeof(addrbuf) - 13);
-               strcat(buf, "]");
-
-               snprintf(portbuf, sizeof(portbuf), "REMOTE_PORT=%d",
-                   ntohs(sin6_addr->sin6_port));
+               inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(buf));
+               argv_array_pushf(&cld.env_array, "REMOTE_ADDR=[%s]", buf);
+               argv_array_pushf(&cld.env_array, "REMOTE_PORT=%d",
+                                ntohs(sin6_addr->sin6_port));
 #endif
        }
 
-       cld.env = (const char **)env;
        cld.argv = (const char **)cld_argv;
        cld.in = incoming;
        cld.out = dup(incoming);
@@ -901,7 +895,7 @@ static const char *ip2str(int family, struct sockaddr *sin, socklen_t len)
                inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len);
                break;
        default:
-               strcpy(ip, "<unknown>");
+               xsnprintf(ip, sizeof(ip), "<unknown>");
        }
        return ip;
 }
@@ -916,7 +910,7 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
        int gai;
        long flags;
 
-       sprintf(pbuf, "%d", listen_port);
+       xsnprintf(pbuf, sizeof(pbuf), "%d", listen_port);
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
diff --git a/date.c b/date.c
index 8f9156909b8d9d2cad6425aa44deebdcd01b2168..7c9f76998ac7a00fb5f8781dd8e460f899edbfac 100644 (file)
--- a/date.c
+++ b/date.c
@@ -166,6 +166,7 @@ struct date_mode *date_mode_from_type(enum date_mode_type type)
        if (type == DATE_STRFTIME)
                die("BUG: cannot create anonymous strftime date_mode struct");
        mode.type = type;
+       mode.local = 0;
        return &mode;
 }
 
@@ -174,6 +175,9 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
        struct tm *tm;
        static struct strbuf timebuf = STRBUF_INIT;
 
+       if (mode->local)
+               tz = local_tzoffset(time);
+
        if (mode->type == DATE_RAW) {
                strbuf_reset(&timebuf);
                strbuf_addf(&timebuf, "%lu %+05d", time, tz);
@@ -189,9 +193,6 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
                return timebuf.buf;
        }
 
-       if (mode->type == DATE_LOCAL)
-               tz = local_tzoffset(time);
-
        tm = time_to_tm(time, tz);
        if (!tm) {
                tm = time_to_tm(0, 0);
@@ -232,7 +233,7 @@ const char *show_date(unsigned long time, int tz, const struct date_mode *mode)
                                tm->tm_mday,
                                tm->tm_hour, tm->tm_min, tm->tm_sec,
                                tm->tm_year + 1900,
-                               (mode->type == DATE_LOCAL) ? 0 : ' ',
+                               mode->local ? 0 : ' ',
                                tz);
        return timebuf.buf;
 }
@@ -770,31 +771,50 @@ int parse_date(const char *date, struct strbuf *result)
        return 0;
 }
 
+static enum date_mode_type parse_date_type(const char *format, const char **end)
+{
+       if (skip_prefix(format, "relative", end))
+               return DATE_RELATIVE;
+       if (skip_prefix(format, "iso8601-strict", end) ||
+           skip_prefix(format, "iso-strict", end))
+               return DATE_ISO8601_STRICT;
+       if (skip_prefix(format, "iso8601", end) ||
+           skip_prefix(format, "iso", end))
+               return DATE_ISO8601;
+       if (skip_prefix(format, "rfc2822", end) ||
+           skip_prefix(format, "rfc", end))
+               return DATE_RFC2822;
+       if (skip_prefix(format, "short", end))
+               return DATE_SHORT;
+       if (skip_prefix(format, "default", end))
+               return DATE_NORMAL;
+       if (skip_prefix(format, "raw", end))
+               return DATE_RAW;
+       if (skip_prefix(format, "format", end))
+               return DATE_STRFTIME;
+
+       die("unknown date format %s", format);
+}
+
 void parse_date_format(const char *format, struct date_mode *mode)
 {
-       if (!strcmp(format, "relative"))
-               mode->type = DATE_RELATIVE;
-       else if (!strcmp(format, "iso8601") ||
-                !strcmp(format, "iso"))
-               mode->type = DATE_ISO8601;
-       else if (!strcmp(format, "iso8601-strict") ||
-                !strcmp(format, "iso-strict"))
-               mode->type = DATE_ISO8601_STRICT;
-       else if (!strcmp(format, "rfc2822") ||
-                !strcmp(format, "rfc"))
-               mode->type = DATE_RFC2822;
-       else if (!strcmp(format, "short"))
-               mode->type = DATE_SHORT;
-       else if (!strcmp(format, "local"))
-               mode->type = DATE_LOCAL;
-       else if (!strcmp(format, "default"))
-               mode->type = DATE_NORMAL;
-       else if (!strcmp(format, "raw"))
-               mode->type = DATE_RAW;
-       else if (skip_prefix(format, "format:", &format)) {
-               mode->type = DATE_STRFTIME;
-               mode->strftime_fmt = xstrdup(format);
-       } else
+       const char *p;
+
+       /* historical alias */
+       if (!strcmp(format, "local"))
+               format = "default-local";
+
+       mode->type = parse_date_type(format, &p);
+       mode->local = 0;
+
+       if (skip_prefix(p, "-local", &p))
+               mode->local = 1;
+
+       if (mode->type == DATE_STRFTIME) {
+               if (!skip_prefix(p, ":", &p))
+                       die("date format missing colon separator: %s", format);
+               mode->strftime_fmt = xstrdup(p);
+       } else if (*p)
                die("unknown date format %s", format);
 }
 
index 0320605a84178ffb2ab9384b8ee9fbac7df16a73..8e0fd270b5be4d9ab8616ab57202862dd2fc6eda 100644 (file)
@@ -136,15 +136,13 @@ static int queue_diff(struct diff_options *o,
 
                if (name1) {
                        strbuf_addstr(&buffer1, name1);
-                       if (buffer1.len && buffer1.buf[buffer1.len - 1] != '/')
-                               strbuf_addch(&buffer1, '/');
+                       strbuf_complete(&buffer1, '/');
                        len1 = buffer1.len;
                }
 
                if (name2) {
                        strbuf_addstr(&buffer2, name2);
-                       if (buffer2.len && buffer2.buf[buffer2.len - 1] != '/')
-                               strbuf_addch(&buffer2, '/');
+                       strbuf_complete(&buffer2, '/');
                        len2 = buffer2.len;
                }
 
diff --git a/diff.c b/diff.c
index 46260ed7a1d521cf95342caff83a825b34959bdc..835a12e84d65b3fa9d680d55b452f7d748a340b1 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -322,7 +322,7 @@ static struct diff_tempfile {
         */
        const char *name;
 
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
        char mode[10];
 
        /*
@@ -2882,9 +2882,8 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
                die_errno("unable to write temp-file");
        close_tempfile(&temp->tempfile);
        temp->name = get_tempfile_path(&temp->tempfile);
-       strcpy(temp->hex, sha1_to_hex(sha1));
-       temp->hex[40] = 0;
-       sprintf(temp->mode, "%06o", mode);
+       sha1_to_hex_r(temp->hex, sha1);
+       xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
        strbuf_release(&buf);
        strbuf_release(&template);
        free(path_dup);
@@ -2901,8 +2900,8 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                 * a '+' entry produces this for file-1.
                 */
                temp->name = "/dev/null";
-               strcpy(temp->hex, ".");
-               strcpy(temp->mode, ".");
+               xsnprintf(temp->hex, sizeof(temp->hex), ".");
+               xsnprintf(temp->mode, sizeof(temp->mode), ".");
                return temp;
        }
 
@@ -2930,16 +2929,16 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                        /* we can borrow from the file in the work tree */
                        temp->name = name;
                        if (!one->sha1_valid)
-                               strcpy(temp->hex, sha1_to_hex(null_sha1));
+                               sha1_to_hex_r(temp->hex, null_sha1);
                        else
-                               strcpy(temp->hex, sha1_to_hex(one->sha1));
+                               sha1_to_hex_r(temp->hex, one->sha1);
                        /* Even though we may sometimes borrow the
                         * contents from the work tree, we always want
                         * one->mode.  mode is trustworthy even when
                         * !(one->sha1_valid), as long as
                         * DIFF_FILE_VALID(one).
                         */
-                       sprintf(temp->mode, "%06o", one->mode);
+                       xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode);
                }
                return temp;
        }
@@ -4085,9 +4084,9 @@ const char *diff_unique_abbrev(const unsigned char *sha1, int len)
        if (abblen < 37) {
                static char hex[41];
                if (len < abblen && abblen <= len + 2)
-                       sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
+                       xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
                else
-                       sprintf(hex, "%s...", abbrev);
+                       xsnprintf(hex, sizeof(hex), "%s...", abbrev);
                return hex;
        }
        return sha1_to_hex(sha1);
diff --git a/dir.c b/dir.c
index 7b25634832716ade46686d118184c4d43d8c11e7..109ceeaed3fb899c0e98a7abfc2f38a6cfa7fe0e 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -882,6 +882,25 @@ int match_pathname(const char *pathname, int pathlen,
                 */
                if (!patternlen && !namelen)
                        return 1;
+               /*
+                * This can happen when we ignore some exclude rules
+                * on directories in other to see if negative rules
+                * may match. E.g.
+                *
+                * /abc
+                * !/abc/def/ghi
+                *
+                * The pattern of interest is "/abc". On the first
+                * try, we should match path "abc" with this pattern
+                * in the "if" statement right above, but the caller
+                * ignores it.
+                *
+                * On the second try with paths within "abc",
+                * e.g. "abc/xyz", we come here and try to match it
+                * with "/abc".
+                */
+               if (!patternlen && namelen && *name == '/')
+                       return 1;
        }
 
        return fnmatch_icase_mem(pattern, patternlen,
@@ -889,6 +908,48 @@ int match_pathname(const char *pathname, int pathlen,
                                 WM_PATHNAME) == 0;
 }
 
+/*
+ * Return non-zero if pathname is a directory and an ancestor of the
+ * literal path in a (negative) pattern. This is used to keep
+ * descending in "foo" and "foo/bar" when the pattern is
+ * "!foo/bar/.gitignore". "foo/notbar" will not be descended however.
+ */
+static int match_neg_path(const char *pathname, int pathlen, int *dtype,
+                         const char *base, int baselen,
+                         const char *pattern, int prefix, int patternlen,
+                         int flags)
+{
+       assert((flags & EXC_FLAG_NEGATIVE) && !(flags & EXC_FLAG_NODIR));
+
+       if (*dtype == DT_UNKNOWN)
+               *dtype = get_dtype(NULL, pathname, pathlen);
+       if (*dtype != DT_DIR)
+               return 0;
+
+       if (*pattern == '/') {
+               pattern++;
+               patternlen--;
+               prefix--;
+       }
+
+       if (baselen) {
+               if (((pathlen < baselen && base[pathlen] == '/') ||
+                    pathlen == baselen) &&
+                   !strncmp_icase(pathname, base, pathlen))
+                       return 1;
+               pathname += baselen + 1;
+               pathlen  -= baselen + 1;
+       }
+
+
+       if (prefix &&
+           ((pathlen < prefix && pattern[pathlen] == '/') &&
+            !strncmp_icase(pathname, pattern, pathlen)))
+               return 1;
+
+       return 0;
+}
+
 /*
  * Scan the given exclude list in reverse to see whether pathname
  * should be ignored.  The first match (i.e. the last on the list), if
@@ -901,7 +962,8 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
                                                       int *dtype,
                                                       struct exclude_list *el)
 {
-       int i;
+       struct exclude *exc = NULL; /* undecided */
+       int i, matched_negative_path = 0;
 
        if (!el->nr)
                return NULL;    /* undefined */
@@ -922,18 +984,33 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
                        if (match_basename(basename,
                                           pathlen - (basename - pathname),
                                           exclude, prefix, x->patternlen,
-                                          x->flags))
-                               return x;
+                                          x->flags)) {
+                               exc = x;
+                               break;
+                       }
                        continue;
                }
 
                assert(x->baselen == 0 || x->base[x->baselen - 1] == '/');
                if (match_pathname(pathname, pathlen,
                                   x->base, x->baselen ? x->baselen - 1 : 0,
+                                  exclude, prefix, x->patternlen, x->flags)) {
+                       exc = x;
+                       break;
+               }
+
+               if ((x->flags & EXC_FLAG_NEGATIVE) && !matched_negative_path &&
+                   match_neg_path(pathname, pathlen, dtype, x->base,
+                                  x->baselen ? x->baselen - 1 : 0,
                                   exclude, prefix, x->patternlen, x->flags))
-                       return x;
+                       matched_negative_path = 1;
        }
-       return NULL; /* undecided */
+       if (exc &&
+           !(exc->flags & EXC_FLAG_NEGATIVE) &&
+           !(exc->flags & EXC_FLAG_NODIR) &&
+           matched_negative_path)
+               exc = NULL;
+       return exc;
 }
 
 /*
@@ -1519,8 +1596,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
        }
        strbuf_addstr(path, cdir->ucd->name);
        /* treat_one_path() does this before it calls treat_directory() */
-       if (path->buf[path->len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
        if (cdir->ucd->check_only)
                /*
                 * check_only is set as a result of treat_directory() getting
@@ -2030,6 +2106,15 @@ int file_exists(const char *f)
        return lstat(f, &sb) == 0;
 }
 
+static int cmp_icase(char a, char b)
+{
+       if (a == b)
+               return 0;
+       if (ignore_case)
+               return toupper(a) - toupper(b);
+       return a - b;
+}
+
 /*
  * Given two normalized paths (a trailing slash is ok), if subdir is
  * outside dir, return -1.  Otherwise return the offset in subdir that
@@ -2041,7 +2126,7 @@ int dir_inside_of(const char *subdir, const char *dir)
 
        assert(dir && subdir && *dir && *subdir);
 
-       while (*dir && *subdir && *dir == *subdir) {
+       while (*dir && *subdir && !cmp_icase(*dir, *subdir)) {
                dir++;
                subdir++;
                offset++;
@@ -2126,8 +2211,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
                else
                        return -1;
        }
-       if (path->buf[original_len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
 
        len = path->len;
        while ((e = readdir(dir)) != NULL) {
diff --git a/entry.c b/entry.c
index 1eda8e94714bbd6020d61b4b627f721b1042673c..582c40071a3034af9a391cf438dd74ddb34e15ff 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -96,8 +96,8 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf
 {
        int symlink = (ce->ce_mode & S_IFMT) != S_IFREG;
        if (to_tempfile) {
-               strcpy(path, symlink
-                      ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
+               xsnprintf(path, TEMPORARY_FILENAME_LENGTH, "%s",
+                         symlink ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
                return mkstemp(path);
        } else {
                return create_file(path, !symlink ? ce->ce_mode : 0666);
index a533aed630c20a5e0718bcea4375875a44896416..c5b65f5e231e02f315d8a24ee48034aede3ad5e8 100644 (file)
@@ -143,11 +143,8 @@ static char *git_path_from_env(const char *envvar, const char *git_dir,
                               const char *path, int *fromenv)
 {
        const char *value = getenv(envvar);
-       if (!value) {
-               char *buf = xmalloc(strlen(git_dir) + strlen(path) + 2);
-               sprintf(buf, "%s/%s", git_dir, path);
-               return buf;
-       }
+       if (!value)
+               return xstrfmt("%s/%s", git_dir, path);
        if (fromenv)
                *fromenv = 1;
        return xstrdup(value);
index 6c7c3c9b669eb272f4da530aef459c9c39c4d294..e3b421d5149bc3a2ad58ca8df98fdf2c924ed7c0 100644 (file)
@@ -424,7 +424,7 @@ static void write_crash_report(const char *err)
        fprintf(rpt, "fast-import crash report:\n");
        fprintf(rpt, "    fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
        fprintf(rpt, "    parent process     : %"PRIuMAX"\n", (uintmax_t) getppid());
-       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(LOCAL)));
+       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_MODE(ISO8601)));
        fputc('\n', rpt);
 
        fputs("fatal: ", rpt);
@@ -644,8 +644,9 @@ static void *pool_calloc(size_t count, size_t size)
 
 static char *pool_strdup(const char *s)
 {
-       char *r = pool_alloc(strlen(s) + 1);
-       strcpy(r, s);
+       size_t len = strlen(s) + 1;
+       char *r = pool_alloc(len);
+       memcpy(r, s, len);
        return r;
 }
 
@@ -702,7 +703,7 @@ static struct atom_str *to_atom(const char *s, unsigned short len)
 
        c = pool_alloc(sizeof(struct atom_str) + len + 1);
        c->str_len = len;
-       strncpy(c->str_dat, s, len);
+       memcpy(c->str_dat, s, len);
        c->str_dat[len] = 0;
        c->next_atom = atom_table[hc];
        atom_table[hc] = c;
@@ -863,13 +864,15 @@ static void start_packfile(void)
 {
        static char tmp_file[PATH_MAX];
        struct packed_git *p;
+       int namelen;
        struct pack_header hdr;
        int pack_fd;
 
        pack_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
                              "pack/tmp_pack_XXXXXX");
-       p = xcalloc(1, sizeof(*p) + strlen(tmp_file) + 2);
-       strcpy(p->pack_name, tmp_file);
+       namelen = strlen(tmp_file) + 2;
+       p = xcalloc(1, sizeof(*p) + namelen);
+       xsnprintf(p->pack_name, namelen, "%s", tmp_file);
        p->pack_fd = pack_fd;
        p->do_not_close = 1;
        pack_file = sha1fd(pack_fd, p->pack_name);
@@ -1035,8 +1038,8 @@ static int store_object(
        git_SHA_CTX c;
        git_zstream s;
 
-       hdrlen = sprintf((char *)hdr,"%s %lu", typename(type),
-               (unsigned long)dat->len) + 1;
+       hdrlen = xsnprintf((char *)hdr, sizeof(hdr), "%s %lu",
+                          typename(type), (unsigned long)dat->len) + 1;
        git_SHA1_Init(&c);
        git_SHA1_Update(&c, hdr, hdrlen);
        git_SHA1_Update(&c, dat->buf, dat->len);
index 820251a8d80518508514b728b4b2afd8a171e296..2dabee97b28915ccf5c06b58dd0193bf03a01db8 100644 (file)
@@ -681,11 +681,10 @@ static int get_pack(struct fetch_pack_args *args,
                    int xd[2], char **pack_lockfile)
 {
        struct async demux;
-       const char *argv[22];
-       char keep_arg[256];
-       char hdr_arg[256];
-       const char **av, *cmd_name;
        int do_keep = args->keep_pack;
+       const char *cmd_name;
+       struct pack_header header;
+       int pass_header = 0;
        struct child_process cmd = CHILD_PROCESS_INIT;
        int ret;
 
@@ -705,17 +704,11 @@ static int get_pack(struct fetch_pack_args *args,
        else
                demux.out = xd[0];
 
-       cmd.argv = argv;
-       av = argv;
-       *hdr_arg = 0;
        if (!args->keep_pack && unpack_limit) {
-               struct pack_header header;
 
                if (read_pack_header(demux.out, &header))
                        die("protocol error: bad pack header");
-               snprintf(hdr_arg, sizeof(hdr_arg),
-                        "--pack_header=%"PRIu32",%"PRIu32,
-                        ntohl(header.hdr_version), ntohl(header.hdr_entries));
+               pass_header = 1;
                if (ntohl(header.hdr_entries) < unpack_limit)
                        do_keep = 0;
                else
@@ -723,44 +716,49 @@ static int get_pack(struct fetch_pack_args *args,
        }
 
        if (alternate_shallow_file) {
-               *av++ = "--shallow-file";
-               *av++ = alternate_shallow_file;
+               argv_array_push(&cmd.args, "--shallow-file");
+               argv_array_push(&cmd.args, alternate_shallow_file);
        }
 
        if (do_keep) {
                if (pack_lockfile)
                        cmd.out = -1;
-               *av++ = cmd_name = "index-pack";
-               *av++ = "--stdin";
+               cmd_name = "index-pack";
+               argv_array_push(&cmd.args, cmd_name);
+               argv_array_push(&cmd.args, "--stdin");
                if (!args->quiet && !args->no_progress)
-                       *av++ = "-v";
+                       argv_array_push(&cmd.args, "-v");
                if (args->use_thin_pack)
-                       *av++ = "--fix-thin";
+                       argv_array_push(&cmd.args, "--fix-thin");
                if (args->lock_pack || unpack_limit) {
-                       int s = sprintf(keep_arg,
-                                       "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
-                       if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
-                               strcpy(keep_arg + s, "localhost");
-                       *av++ = keep_arg;
+                       char hostname[256];
+                       if (gethostname(hostname, sizeof(hostname)))
+                               xsnprintf(hostname, sizeof(hostname), "localhost");
+                       argv_array_pushf(&cmd.args,
+                                       "--keep=fetch-pack %"PRIuMAX " on %s",
+                                       (uintmax_t)getpid(), hostname);
                }
                if (args->check_self_contained_and_connected)
-                       *av++ = "--check-self-contained-and-connected";
+                       argv_array_push(&cmd.args, "--check-self-contained-and-connected");
        }
        else {
-               *av++ = cmd_name = "unpack-objects";
+               cmd_name = "unpack-objects";
+               argv_array_push(&cmd.args, cmd_name);
                if (args->quiet || args->no_progress)
-                       *av++ = "-q";
+                       argv_array_push(&cmd.args, "-q");
                args->check_self_contained_and_connected = 0;
        }
-       if (*hdr_arg)
-               *av++ = hdr_arg;
+
+       if (pass_header)
+               argv_array_pushf(&cmd.args, "--pack_header=%"PRIu32",%"PRIu32,
+                                ntohl(header.hdr_version),
+                                ntohl(header.hdr_entries));
        if (fetch_fsck_objects >= 0
            ? fetch_fsck_objects
            : transfer_fsck_objects >= 0
            ? transfer_fsck_objects
            : 0)
-               *av++ = "--strict";
-       *av++ = NULL;
+               argv_array_push(&cmd.args, "--strict");
 
        cmd.in = demux.out;
        cmd.git_cmd = 1;
index ea63223ab3b5d4f8fb0a9af45db4278aef010fb7..5d1cb00d86b3700b2012cae1ed3b3b8b744acaf2 100755 (executable)
@@ -1,14 +1,19 @@
 #!/bin/sh
 
-USAGE='[help|start|bad|good|skip|next|reset|visualize|replay|log|run]'
+USAGE='[help|start|bad|good|new|old|terms|skip|next|reset|visualize|replay|log|run]'
 LONG_USAGE='git bisect help
        print this long help message.
-git bisect start [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
+git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
+                [--no-checkout] [<bad> [<good>...]] [--] [<pathspec>...]
        reset bisect state and start bisection.
-git bisect bad [<rev>]
-       mark <rev> a known-bad revision.
-git bisect good [<rev>...]
-       mark <rev>... known-good revisions.
+git bisect (bad|new) [<rev>]
+       mark <rev> a known-bad revision/
+               a revision after change in a given property.
+git bisect (good|old) [<rev>...]
+       mark <rev>... known-good revisions/
+               revisions before change in a given property.
+git bisect terms [--term-good | --term-bad]
+       show the terms used for old and new commits (default: bad, good)
 git bisect skip [(<rev>|<range>)...]
        mark <rev>... untestable revisions.
 git bisect next
@@ -95,6 +100,24 @@ bisect_start() {
                --no-checkout)
                        mode=--no-checkout
                        shift ;;
+               --term-good|--term-old)
+                       shift
+                       must_write_terms=1
+                       TERM_GOOD=$1
+                       shift ;;
+               --term-good=*|--term-old=*)
+                       must_write_terms=1
+                       TERM_GOOD=${1#*=}
+                       shift ;;
+               --term-bad|--term-new)
+                       shift
+                       must_write_terms=1
+                       TERM_BAD=$1
+                       shift ;;
+               --term-bad=*|--term-new=*)
+                       must_write_terms=1
+                       TERM_BAD=${1#*=}
+                       shift ;;
                --*)
                        die "$(eval_gettext "unrecognised option: '\$arg'")" ;;
                *)
@@ -294,7 +317,7 @@ bisect_next_check() {
                false
                ;;
        t,,"$TERM_GOOD")
-               # have bad but not good.  we could bisect although
+               # have bad (or new) but not good (or old).  we could bisect although
                # this is less optimum.
                eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2
                if test -t 0
@@ -451,6 +474,8 @@ bisect_replay () {
                        eval "$cmd" ;;
                "$TERM_GOOD"|"$TERM_BAD"|skip)
                        bisect_write "$command" "$rev" ;;
+               terms)
+                       bisect_terms $rev ;;
                *)
                        die "$(gettext "?? what are you talking about?")" ;;
                esac
@@ -535,9 +560,42 @@ get_terms () {
 write_terms () {
        TERM_BAD=$1
        TERM_GOOD=$2
+       if test "$TERM_BAD" = "$TERM_GOOD"
+       then
+               die "$(gettext "please use two different terms")"
+       fi
+       check_term_format "$TERM_BAD" bad
+       check_term_format "$TERM_GOOD" good
        printf '%s\n%s\n' "$TERM_BAD" "$TERM_GOOD" >"$GIT_DIR/BISECT_TERMS"
 }
 
+check_term_format () {
+       term=$1
+       git check-ref-format refs/bisect/"$term" ||
+       die "$(eval_gettext "'\$term' is not a valid term")"
+       case "$term" in
+       help|start|terms|skip|next|reset|visualize|replay|log|run)
+               die "$(eval_gettext "can't use the builtin command '\$term' as a term")"
+               ;;
+       bad|new)
+               if test "$2" != bad
+               then
+                       # In theory, nothing prevents swapping
+                       # completely good and bad, but this situation
+                       # could be confusing and hasn't been tested
+                       # enough. Forbid it for now.
+                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
+               fi
+               ;;
+       good|old)
+               if test "$2" != good
+               then
+                       die "$(eval_gettext "can't change the meaning of term '\$term'")"
+               fi
+               ;;
+       esac
+}
+
 check_and_set_terms () {
        cmd="$1"
        case "$cmd" in
@@ -554,14 +612,51 @@ check_and_set_terms () {
                                write_terms bad good
                        fi
                        ;;
+               new|old)
+                       if ! test -s "$GIT_DIR/BISECT_TERMS"
+                       then
+                               write_terms new old
+                       fi
+                       ;;
                esac ;;
        esac
 }
 
 bisect_voc () {
        case "$1" in
-       bad) echo "bad" ;;
-       good) echo "good" ;;
+       bad) echo "bad|new" ;;
+       good) echo "good|old" ;;
+       esac
+}
+
+bisect_terms () {
+       get_terms
+       if ! test -s "$GIT_DIR/BISECT_TERMS"
+       then
+               die "$(gettext "no terms defined")"
+       fi
+       case "$#" in
+       0)
+               gettextln "Your current terms are $TERM_GOOD for the old state
+and $TERM_BAD for the new state."
+               ;;
+       1)
+               arg=$1
+               case "$arg" in
+                       --term-good|--term-old)
+                               printf '%s\n' "$TERM_GOOD"
+                               ;;
+                       --term-bad|--term-new)
+                               printf '%s\n' "$TERM_BAD"
+                               ;;
+                       *)
+                               die "$(eval_gettext "invalid argument \$arg for 'git bisect terms'.
+Supported options are: --term-good|--term-old and --term-bad|--term-new.")"
+                               ;;
+               esac
+               ;;
+       *)
+               usage ;;
        esac
 }
 
@@ -577,7 +672,7 @@ case "$#" in
                git bisect -h ;;
        start)
                bisect_start "$@" ;;
-       bad|good|"$TERM_BAD"|"$TERM_GOOD")
+       bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD")
                bisect_state "$cmd" "$@" ;;
        skip)
                bisect_skip "$@" ;;
@@ -594,6 +689,8 @@ case "$#" in
                bisect_log ;;
        run)
                bisect_run "$@" ;;
+       terms)
+               bisect_terms "$@" ;;
        *)
                usage ;;
        esac
index f649e81f1107722f4c7d051201920ae2a0e7846a..88964f7886b1f6d7445b77a26eceb65d48c3b6b8 100644 (file)
@@ -229,7 +229,7 @@ typedef unsigned long uintptr_t;
 #else
 #define precompose_str(in,i_nfd2nfc)
 #define precompose_argv(c,v)
-#define probe_utf8_pathname_composition(a,b)
+#define probe_utf8_pathname_composition()
 #endif
 
 #ifdef MKDIR_WO_TRAILING_SLASH
@@ -744,6 +744,9 @@ static inline size_t xsize_t(off_t len)
        return (size_t)len;
 }
 
+__attribute__((format (printf, 3, 4)))
+extern int xsnprintf(char *dst, size_t max, const char *fmt, ...);
+
 /* in ctype.c, for kwset users */
 extern const unsigned char tolower_trans_tbl[256];
 
@@ -814,6 +817,9 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)
        char *p;
 
        errno = 0;
+       /* negative values would be accepted by strtoul */
+       if (strchr(s, '-'))
+               return -1;
        ul = strtoul(s, &p, base);
        if (errno || *p || p == s || (unsigned int) ul != ul)
                return -1;
index 5b3f63d8bbc65e80d1f4278e1ec6e27df604dc9a..27c9c54fbd24ef3a2995fd90b04911e5a1d8aed9 100755 (executable)
@@ -275,11 +275,41 @@ commits=$(wc -l <../revs | tr -d " ")
 test $commits -eq 0 && die "Found nothing to rewrite"
 
 # Rewrite the commits
+report_progress ()
+{
+       if test -n "$progress" &&
+               test $git_filter_branch__commit_count -gt $next_sample_at
+       then
+               count=$git_filter_branch__commit_count
+
+               now=$(date +%s)
+               elapsed=$(($now - $start_timestamp))
+               remaining=$(( ($commits - $count) * $elapsed / $count ))
+               if test $elapsed -gt 0
+               then
+                       next_sample_at=$(( ($elapsed + 1) * $count / $elapsed ))
+               else
+                       next_sample_at=$(($next_sample_at + 1))
+               fi
+               progress=" ($elapsed seconds passed, remaining $remaining predicted)"
+       fi
+       printf "\rRewrite $commit ($count/$commits)$progress    "
+}
 
 git_filter_branch__commit_count=0
+
+progress= start_timestamp=
+if date '+%s' 2>/dev/null | grep -q '^[0-9][0-9]*$'
+then
+       next_sample_at=0
+       progress="dummy to ensure this is not empty"
+       start_timestamp=$(date '+%s')
+fi
+
 while read commit parents; do
        git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1))
-       printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)"
+
+       report_progress
 
        case "$filter_subdir" in
        "")
@@ -347,7 +377,7 @@ while read commit parents; do
        fi
 
        {
-               while read -r header_line && test -n "$header_line"
+               while IFS='' read -r header_line && test -n "$header_line"
                do
                        # skip header lines...
                        :;
index 0093fa3d8391b4b56c5613b063996cc6dc5c7b60..daa60c60d6703ae76fd5cd168907f598207844d3 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -22,6 +22,8 @@
 import re
 import shutil
 import stat
+import zipfile
+import zlib
 
 try:
     from subprocess import CalledProcessError
@@ -104,6 +106,16 @@ def chdir(path, is_client_path=False):
         path = os.getcwd()
     os.environ['PWD'] = path
 
+def calcDiskFree():
+    """Return free space in bytes on the disk of the given dirname."""
+    if platform.system() == 'Windows':
+        free_bytes = ctypes.c_ulonglong(0)
+        ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(os.getcwd()), None, None, ctypes.pointer(free_bytes))
+        return free_bytes.value
+    else:
+        st = os.statvfs(os.getcwd())
+        return st.f_bavail * st.f_frsize
+
 def die(msg):
     if verbose:
         raise Exception(msg)
@@ -134,13 +146,11 @@ def read_pipe(c, ignore_error=False):
         sys.stderr.write('Reading pipe: %s\n' % str(c))
 
     expand = isinstance(c,basestring)
-    p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
-    pipe = p.stdout
-    val = pipe.read()
-    if p.wait() and not ignore_error:
-        die('Command failed: %s' % str(c))
-
-    return val
+    p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
+    (out, err) = p.communicate()
+    if p.returncode != 0 and not ignore_error:
+        die('Command failed: %s\nError: %s' % (str(c), err))
+    return out
 
 def p4_read_pipe(c, ignore_error=False):
     real_cmd = p4_build_cmd(c)
@@ -604,9 +614,12 @@ def gitBranchExists(branch):
 
 _gitConfig = {}
 
-def gitConfig(key):
+def gitConfig(key, typeSpecifier=None):
     if not _gitConfig.has_key(key):
-        cmd = [ "git", "config", key ]
+        cmd = [ "git", "config" ]
+        if typeSpecifier:
+            cmd += [ typeSpecifier ]
+        cmd += [ key ]
         s = read_pipe(cmd, ignore_error=True)
         _gitConfig[key] = s.strip()
     return _gitConfig[key]
@@ -617,16 +630,26 @@ def gitConfigBool(key):
        in the config."""
 
     if not _gitConfig.has_key(key):
-        cmd = [ "git", "config", "--bool", key ]
+        _gitConfig[key] = gitConfig(key, '--bool') == "true"
+    return _gitConfig[key]
+
+def gitConfigInt(key):
+    if not _gitConfig.has_key(key):
+        cmd = [ "git", "config", "--int", key ]
         s = read_pipe(cmd, ignore_error=True)
         v = s.strip()
-        _gitConfig[key] = v == "true"
+        try:
+            _gitConfig[key] = int(gitConfig(key, '--int'))
+        except ValueError:
+            _gitConfig[key] = None
     return _gitConfig[key]
 
 def gitConfigList(key):
     if not _gitConfig.has_key(key):
         s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
         _gitConfig[key] = s.strip().split(os.linesep)
+        if _gitConfig[key] == ['']:
+            _gitConfig[key] = []
     return _gitConfig[key]
 
 def p4BranchesInGit(branchesAreInRemotes=True):
@@ -909,6 +932,182 @@ def wildcard_present(path):
     m = re.search("[*#@%]", path)
     return m is not None
 
+class LargeFileSystem(object):
+    """Base class for large file system support."""
+
+    def __init__(self, writeToGitStream):
+        self.largeFiles = set()
+        self.writeToGitStream = writeToGitStream
+
+    def generatePointer(self, cloneDestination, contentFile):
+        """Return the content of a pointer file that is stored in Git instead of
+           the actual content."""
+        assert False, "Method 'generatePointer' required in " + self.__class__.__name__
+
+    def pushFile(self, localLargeFile):
+        """Push the actual content which is not stored in the Git repository to
+           a server."""
+        assert False, "Method 'pushFile' required in " + self.__class__.__name__
+
+    def hasLargeFileExtension(self, relPath):
+        return reduce(
+            lambda a, b: a or b,
+            [relPath.endswith('.' + e) for e in gitConfigList('git-p4.largeFileExtensions')],
+            False
+        )
+
+    def generateTempFile(self, contents):
+        contentFile = tempfile.NamedTemporaryFile(prefix='git-p4-large-file', delete=False)
+        for d in contents:
+            contentFile.write(d)
+        contentFile.close()
+        return contentFile.name
+
+    def exceedsLargeFileThreshold(self, relPath, contents):
+        if gitConfigInt('git-p4.largeFileThreshold'):
+            contentsSize = sum(len(d) for d in contents)
+            if contentsSize > gitConfigInt('git-p4.largeFileThreshold'):
+                return True
+        if gitConfigInt('git-p4.largeFileCompressedThreshold'):
+            contentsSize = sum(len(d) for d in contents)
+            if contentsSize <= gitConfigInt('git-p4.largeFileCompressedThreshold'):
+                return False
+            contentTempFile = self.generateTempFile(contents)
+            compressedContentFile = tempfile.NamedTemporaryFile(prefix='git-p4-large-file', delete=False)
+            zf = zipfile.ZipFile(compressedContentFile.name, mode='w')
+            zf.write(contentTempFile, compress_type=zipfile.ZIP_DEFLATED)
+            zf.close()
+            compressedContentsSize = zf.infolist()[0].compress_size
+            os.remove(contentTempFile)
+            os.remove(compressedContentFile.name)
+            if compressedContentsSize > gitConfigInt('git-p4.largeFileCompressedThreshold'):
+                return True
+        return False
+
+    def addLargeFile(self, relPath):
+        self.largeFiles.add(relPath)
+
+    def removeLargeFile(self, relPath):
+        self.largeFiles.remove(relPath)
+
+    def isLargeFile(self, relPath):
+        return relPath in self.largeFiles
+
+    def processContent(self, git_mode, relPath, contents):
+        """Processes the content of git fast import. This method decides if a
+           file is stored in the large file system and handles all necessary
+           steps."""
+        if self.exceedsLargeFileThreshold(relPath, contents) or self.hasLargeFileExtension(relPath):
+            contentTempFile = self.generateTempFile(contents)
+            (git_mode, contents, localLargeFile) = self.generatePointer(contentTempFile)
+
+            # Move temp file to final location in large file system
+            largeFileDir = os.path.dirname(localLargeFile)
+            if not os.path.isdir(largeFileDir):
+                os.makedirs(largeFileDir)
+            shutil.move(contentTempFile, localLargeFile)
+            self.addLargeFile(relPath)
+            if gitConfigBool('git-p4.largeFilePush'):
+                self.pushFile(localLargeFile)
+            if verbose:
+                sys.stderr.write("%s moved to large file system (%s)\n" % (relPath, localLargeFile))
+        return (git_mode, contents)
+
+class MockLFS(LargeFileSystem):
+    """Mock large file system for testing."""
+
+    def generatePointer(self, contentFile):
+        """The pointer content is the original content prefixed with "pointer-".
+           The local filename of the large file storage is derived from the file content.
+           """
+        with open(contentFile, 'r') as f:
+            content = next(f)
+            gitMode = '100644'
+            pointerContents = 'pointer-' + content
+            localLargeFile = os.path.join(os.getcwd(), '.git', 'mock-storage', 'local', content[:-1])
+            return (gitMode, pointerContents, localLargeFile)
+
+    def pushFile(self, localLargeFile):
+        """The remote filename of the large file storage is the same as the local
+           one but in a different directory.
+           """
+        remotePath = os.path.join(os.path.dirname(localLargeFile), '..', 'remote')
+        if not os.path.exists(remotePath):
+            os.makedirs(remotePath)
+        shutil.copyfile(localLargeFile, os.path.join(remotePath, os.path.basename(localLargeFile)))
+
+class GitLFS(LargeFileSystem):
+    """Git LFS as backend for the git-p4 large file system.
+       See https://git-lfs.github.com/ for details."""
+
+    def __init__(self, *args):
+        LargeFileSystem.__init__(self, *args)
+        self.baseGitAttributes = []
+
+    def generatePointer(self, contentFile):
+        """Generate a Git LFS pointer for the content. Return LFS Pointer file
+           mode and content which is stored in the Git repository instead of
+           the actual content. Return also the new location of the actual
+           content.
+           """
+        pointerProcess = subprocess.Popen(
+            ['git', 'lfs', 'pointer', '--file=' + contentFile],
+            stdout=subprocess.PIPE
+        )
+        pointerFile = pointerProcess.stdout.read()
+        if pointerProcess.wait():
+            os.remove(contentFile)
+            die('git-lfs pointer command failed. Did you install the extension?')
+        pointerContents = [i+'\n' for i in pointerFile.split('\n')[2:][:-1]]
+        oid = pointerContents[1].split(' ')[1].split(':')[1][:-1]
+        localLargeFile = os.path.join(
+            os.getcwd(),
+            '.git', 'lfs', 'objects', oid[:2], oid[2:4],
+            oid,
+        )
+        # LFS Spec states that pointer files should not have the executable bit set.
+        gitMode = '100644'
+        return (gitMode, pointerContents, localLargeFile)
+
+    def pushFile(self, localLargeFile):
+        uploadProcess = subprocess.Popen(
+            ['git', 'lfs', 'push', '--object-id', 'origin', os.path.basename(localLargeFile)]
+        )
+        if uploadProcess.wait():
+            die('git-lfs push command failed. Did you define a remote?')
+
+    def generateGitAttributes(self):
+        return (
+            self.baseGitAttributes +
+            [
+                '\n',
+                '#\n',
+                '# Git LFS (see https://git-lfs.github.com/)\n',
+                '#\n',
+            ] +
+            ['*.' + f.replace(' ', '[[:space:]]') + ' filter=lfs -text\n'
+                for f in sorted(gitConfigList('git-p4.largeFileExtensions'))
+            ] +
+            ['/' + f.replace(' ', '[[:space:]]') + ' filter=lfs -text\n'
+                for f in sorted(self.largeFiles) if not self.hasLargeFileExtension(f)
+            ]
+        )
+
+    def addLargeFile(self, relPath):
+        LargeFileSystem.addLargeFile(self, relPath)
+        self.writeToGitStream('100644', '.gitattributes', self.generateGitAttributes())
+
+    def removeLargeFile(self, relPath):
+        LargeFileSystem.removeLargeFile(self, relPath)
+        self.writeToGitStream('100644', '.gitattributes', self.generateGitAttributes())
+
+    def processContent(self, git_mode, relPath, contents):
+        if relPath == '.gitattributes':
+            self.baseGitAttributes = contents
+            return (git_mode, self.generateGitAttributes())
+        else:
+            return LargeFileSystem.processContent(self, git_mode, relPath, contents)
+
 class Command:
     def __init__(self):
         self.usage = "usage: %prog [options]"
@@ -1082,6 +1281,9 @@ def __init__(self):
         self.p4HasMoveCommand = p4_has_move_command()
         self.branch = None
 
+        if gitConfig('git-p4.largeFileSystem'):
+            die("Large file system not supported for git-p4 submit command. Please remove it from config.")
+
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
             die("You have files opened with perforce! Close them before starting the sync.")
@@ -2032,6 +2234,13 @@ def __init__(self):
         self.clientSpecDirs = None
         self.tempBranches = []
         self.tempBranchLocation = "git-p4-tmp"
+        self.largeFileSystem = None
+
+        if gitConfig('git-p4.largeFileSystem'):
+            largeFileSystemConstructor = globals()[gitConfig('git-p4.largeFileSystem')]
+            self.largeFileSystem = largeFileSystemConstructor(
+                lambda git_mode, relPath, contents: self.writeToGitStream(git_mode, relPath, contents)
+            )
 
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
@@ -2152,13 +2361,22 @@ def splitFilesIntoBranches(self, commit):
 
         return branches
 
+    def writeToGitStream(self, gitMode, relPath, contents):
+        self.gitStream.write('M %s inline %s\n' % (gitMode, relPath))
+        self.gitStream.write('data %d\n' % sum(len(d) for d in contents))
+        for d in contents:
+            self.gitStream.write(d)
+        self.gitStream.write('\n')
+
     # output one file from the P4 stream
     # - helper for streamP4Files
 
     def streamOneP4File(self, file, contents):
         relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
         if verbose:
-            sys.stderr.write("%s\n" % relPath)
+            size = int(self.stream_file['fileSize'])
+            sys.stdout.write('\r%s --> %s (%i MB)\n' % (file['depotFile'], relPath, size/1024/1024))
+            sys.stdout.flush()
 
         (type_base, type_mods) = split_p4_type(file["type"])
 
@@ -2193,10 +2411,17 @@ def streamOneP4File(self, file, contents):
             # them back too.  This is not needed to the cygwin windows version,
             # just the native "NT" type.
             #
-            text = p4_read_pipe(['print', '-q', '-o', '-', "%s@%s" % (file['depotFile'], file['change']) ])
-            if p4_version_string().find("/NT") >= 0:
-                text = text.replace("\r\n", "\n")
-            contents = [ text ]
+            try:
+                text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (file['depotFile'], file['change'])])
+            except Exception as e:
+                if 'Translation of file content failed' in str(e):
+                    type_base = 'binary'
+                else:
+                    raise e
+            else:
+                if p4_version_string().find('/NT') >= 0:
+                    text = text.replace('\r\n', '\n')
+                contents = [ text ]
 
         if type_base == "apple":
             # Apple filetype files will be streamed as a concatenation of
@@ -2220,24 +2445,31 @@ def streamOneP4File(self, file, contents):
             text = regexp.sub(r'$\1$', text)
             contents = [ text ]
 
-        self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
+        try:
+            relPath.decode('ascii')
+        except:
+            encoding = 'utf8'
+            if gitConfig('git-p4.pathEncoding'):
+                encoding = gitConfig('git-p4.pathEncoding')
+            relPath = relPath.decode(encoding, 'replace').encode('utf8', 'replace')
+            if self.verbose:
+                print 'Path with non-ASCII characters detected. Used %s to encode: %s ' % (encoding, relPath)
 
-        # total length...
-        length = 0
-        for d in contents:
-            length = length + len(d)
+        if self.largeFileSystem:
+            (git_mode, contents) = self.largeFileSystem.processContent(git_mode, relPath, contents)
 
-        self.gitStream.write("data %d\n" % length)
-        for d in contents:
-            self.gitStream.write(d)
-        self.gitStream.write("\n")
+        self.writeToGitStream(git_mode, relPath, contents)
 
     def streamOneP4Deletion(self, file):
         relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
         if verbose:
-            sys.stderr.write("delete %s\n" % relPath)
+            sys.stdout.write("delete %s\n" % relPath)
+            sys.stdout.flush()
         self.gitStream.write("D %s\n" % relPath)
 
+        if self.largeFileSystem and self.largeFileSystem.isLargeFile(relPath):
+            self.largeFileSystem.removeLargeFile(relPath)
+
     # handle another chunk of streaming data
     def streamP4FilesCb(self, marshalled):
 
@@ -2247,6 +2479,14 @@ def streamP4FilesCb(self, marshalled):
             if marshalled["code"] == "error":
                 if "data" in marshalled:
                     err = marshalled["data"].rstrip()
+
+        if not err and 'fileSize' in self.stream_file:
+            required_bytes = int((4 * int(self.stream_file["fileSize"])) - calcDiskFree())
+            if required_bytes > 0:
+                err = 'Not enough space left on %s! Free at least %i MB.' % (
+                    os.getcwd(), required_bytes/1024/1024
+                )
+
         if err:
             f = None
             if self.stream_have_file_info:
@@ -2275,10 +2515,23 @@ def streamP4FilesCb(self, marshalled):
         # 'data' field we need to append to our array
         for k in marshalled.keys():
             if k == 'data':
+                if 'streamContentSize' not in self.stream_file:
+                    self.stream_file['streamContentSize'] = 0
+                self.stream_file['streamContentSize'] += len(marshalled['data'])
                 self.stream_contents.append(marshalled['data'])
             else:
                 self.stream_file[k] = marshalled[k]
 
+        if (verbose and
+            'streamContentSize' in self.stream_file and
+            'fileSize' in self.stream_file and
+            'depotFile' in self.stream_file):
+            size = int(self.stream_file["fileSize"])
+            if size > 0:
+                progress = 100*self.stream_file['streamContentSize']/size
+                sys.stdout.write('\r%s %d%% (%i MB)' % (self.stream_file['depotFile'], progress, int(size/1024/1024)))
+                sys.stdout.flush()
+
         self.stream_have_file_info = True
 
     # Stream directly from "p4 files" into "git fast-import"
@@ -2329,8 +2582,11 @@ def make_email(self, userid):
         else:
             return "%s <a@b>" % userid
 
-    # Stream a p4 tag
     def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
+        """ Stream a p4 tag.
+        commit is either a git commit, or a fast-import mark, ":<p4commit>"
+        """
+
         if verbose:
             print "writing tag %s for commit %s" % (labelName, commit)
         gitStream.write("tag %s\n" % labelName)
@@ -2381,7 +2637,7 @@ def commit(self, details, files, branch, parent = ""):
             self.clientSpecDirs.update_client_spec_path_cache(files)
 
         self.gitStream.write("commit %s\n" % branch)
-#        gitStream.write("mark :%s\n" % details["change"])
+        self.gitStream.write("mark :%s\n" % details["change"])
         self.committedChanges.add(int(details["change"]))
         committer = ""
         if author not in self.users:
@@ -2500,13 +2756,19 @@ def importP4Labels(self, stream, p4Labels):
             if change.has_key('change'):
                 # find the corresponding git commit; take the oldest commit
                 changelist = int(change['change'])
-                gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
-                     "--reverse", ":/\[git-p4:.*change = %d\]" % changelist])
-                if len(gitCommit) == 0:
-                    print "could not find git commit for changelist %d" % changelist
-                else:
-                    gitCommit = gitCommit.strip()
+                if changelist in self.committedChanges:
+                    gitCommit = ":%d" % changelist       # use a fast-import mark
                     commitFound = True
+                else:
+                    gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
+                        "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True)
+                    if len(gitCommit) == 0:
+                        print "importing label %s: could not find git commit for changelist %d" % (name, changelist)
+                    else:
+                        commitFound = True
+                        gitCommit = gitCommit.strip()
+
+                if commitFound:
                     # Convert from p4 time format
                     try:
                         tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
index 167d79fea809b918e81c2228ee27baa5fab23db4..6d3a88decdeee3f85d9ee43ef8e716ccd1a6328b 100755 (executable)
@@ -6,7 +6,8 @@ git quiltimport [options]
 --
 n,dry-run     dry run
 author=       author name and email address for patches without any
-patches=      path to the quilt series and patches
+patches=      path to the quilt patches
+series=       path to the quilt series file
 "
 SUBDIRECTORY_ON=Yes
 . git-sh-setup
@@ -27,6 +28,10 @@ do
                shift
                QUILT_PATCHES="$1"
                ;;
+       --series)
+               shift
+               QUILT_SERIES="$1"
+               ;;
        --)
                shift
                break;;
@@ -53,6 +58,13 @@ if ! [ -d "$QUILT_PATCHES" ] ; then
        exit 1
 fi
 
+# Quilt series file
+: ${QUILT_SERIES:=$QUILT_PATCHES/series}
+if ! [ -e "$QUILT_SERIES" ] ; then
+       echo "The \"$QUILT_SERIES\" file does not exist."
+       exit 1
+fi
+
 # Temporary directories
 tmp_dir="$GIT_DIR"/rebase-apply
 tmp_msg="$tmp_dir/msg"
@@ -135,5 +147,5 @@ do
                commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
                git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
        fi
-done 3<"$QUILT_PATCHES/series"
+done 3<"$QUILT_SERIES"
 rm -rf $tmp_dir || exit 5
index f01637b1fdeb7f021b74171135ac0e7dba566542..d65c06eff36f09c982d66b11684d8f3ab3784393 100644 (file)
@@ -729,8 +729,8 @@ transform_todo_ids () {
                        # that do not have a SHA-1 at the beginning of $rest.
                        ;;
                *)
-                       sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) &&
-                       rest="$sha1 ${rest#* }"
+                       sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[     ]*}) &&
+                       rest="$sha1 ${rest#*[    ]}"
                        ;;
                esac
                printf '%s\n' "$command${rest:+ }$rest"
@@ -857,7 +857,8 @@ add_exec_commands () {
 # Check if the SHA-1 passed as an argument is a
 # correct one, if not then print $2 in "$todo".badsha
 # $1: the SHA-1 to test
-# $2: the line to display if incorrect SHA-1
+# $2: the line number of the input
+# $3: the input filename
 check_commit_sha () {
        badsha=0
        if test -z $1
@@ -873,9 +874,10 @@ check_commit_sha () {
 
        if test $badsha -ne 0
        then
+               line="$(sed -n -e "${2}p" "$3")"
                warn "Warning: the SHA-1 is missing or isn't" \
                        "a commit in the following line:"
-               warn " - $2"
+               warn " - $line"
                warn
        fi
 
@@ -886,37 +888,31 @@ check_commit_sha () {
 # from the todolist in stdin
 check_bad_cmd_and_sha () {
        retval=0
-       git stripspace --strip-comments |
-       (
-               while read -r line
-               do
-                       IFS=' '
-                       set -- $line
-                       command=$1
-                       sha1=$2
-
-                       case $command in
-                       ''|noop|x|"exec")
-                               # Doesn't expect a SHA-1
-                               ;;
-                       pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
-                               if ! check_commit_sha $sha1 "$line"
-                               then
-                                       retval=1
-                               fi
-                               ;;
-                       *)
-                               warn "Warning: the command isn't recognized" \
-                                       "in the following line:"
-                               warn " - $line"
-                               warn
+       lineno=0
+       while read -r command rest
+       do
+               lineno=$(( $lineno + 1 ))
+               case $command in
+               "$comment_char"*|''|noop|x|exec)
+                       # Doesn't expect a SHA-1
+                       ;;
+               pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
+                       if ! check_commit_sha "${rest%%[        ]*}" "$lineno" "$1"
+                       then
                                retval=1
-                               ;;
-                       esac
-               done
-
-               return $retval
-       )
+                       fi
+                       ;;
+               *)
+                       line="$(sed -n -e "${lineno}p" "$1")"
+                       warn "Warning: the command isn't recognized" \
+                               "in the following line:"
+                       warn " - $line"
+                       warn
+                       retval=1
+                       ;;
+               esac
+       done <"$1"
+       return $retval
 }
 
 # Print the list of the SHA-1 of the commits
@@ -1010,7 +1006,7 @@ check_todo_list () {
                ;;
        esac
 
-       if ! check_bad_cmd_and_sha <"$todo"
+       if ! check_bad_cmd_and_sha "$todo"
        then
                raise_error=t
        fi
index 1757404bc271ba4e1a08eb30fe27542c35e3008e..af7ba5fd90c3000892ed31893e1812514e4f3773 100755 (executable)
@@ -14,7 +14,7 @@ git-rebase --continue | --abort | --skip | --edit-todo
  Available options are
 v,verbose!         display a diffstat of what changed upstream
 q,quiet!           be quiet. implies --no-stat
-autostash!         automatically stash/stash pop before and after
+autostash          automatically stash/stash pop before and after
 fork-point         use 'merge-base --fork-point' to refine upstream
 onto=!             rebase onto given branch instead of upstream
 p,preserve-merges! try to recreate merges instead of ignoring them
@@ -292,6 +292,9 @@ do
        --autostash)
                autostash=true
                ;;
+       --no-autostash)
+               autostash=false
+               ;;
        --verbose)
                verbose=t
                diffstat=t
index e3ff44b4d0cd25f247138d9ead160d2aedff5033..e907e0eacf31c21dcb75fa102eb980d6a51f96c3 100755 (executable)
@@ -1365,7 +1365,11 @@ sub send_message {
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
                $smtp->data or die $smtp->message;
-               $smtp->datasend("$header\n$message") or die $smtp->message;
+               $smtp->datasend("$header\n") or die $smtp->message;
+               my @lines = split /^/, $message;
+               foreach my $line (@lines) {
+                       $smtp->datasend("$line") or die $smtp->message;
+               }
                $smtp->dataend() or die $smtp->message;
                $smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message;
        }
index 1d5ba7a4f935fd08572c235b7c5d4390eb8d6528..c7c65e25f50e7b558952a5180081ad499f2a90fc 100755 (executable)
@@ -305,7 +305,25 @@ show_stash () {
        ALLOW_UNKNOWN_FLAGS=t
        assert_stash_like "$@"
 
-       git diff ${FLAGS:---stat} $b_commit $w_commit
+       if test -z "$FLAGS"
+       then
+               if test "$(git config --bool stash.showStat || echo true)" = "true"
+               then
+                       FLAGS=--stat
+               fi
+
+               if test "$(git config --bool stash.showPatch || echo false)" = "true"
+               then
+                       FLAGS=${FLAGS}${FLAGS:+ }-p
+               fi
+
+               if test -z "$FLAGS"
+               then
+                       return 0
+               fi
+       fi
+
+       git diff ${FLAGS} $b_commit $w_commit
 }
 
 show_help () {
index 82e35582cd8e741280229f377b4f61d7d985bc6a..9bc5c5f94d1d7b24dffae649cca371299540aa46 100755 (executable)
@@ -154,48 +154,6 @@ relative_path ()
        echo "$result$target"
 }
 
-#
-# Get submodule info for registered submodules
-# $@ = path to limit submodule list
-#
-module_list()
-{
-       eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
-       (
-               git ls-files -z --error-unmatch --stage -- "$@" ||
-               echo "unmatched pathspec exists"
-       ) |
-       @@PERL@@ -e '
-       my %unmerged = ();
-       my ($null_sha1) = ("0" x 40);
-       my @out = ();
-       my $unmatched = 0;
-       $/ = "\0";
-       while (<STDIN>) {
-               if (/^unmatched pathspec/) {
-                       $unmatched = 1;
-                       next;
-               }
-               chomp;
-               my ($mode, $sha1, $stage, $path) =
-                       /^([0-7]+) ([0-9a-f]{40}) ([0-3])\t(.*)$/;
-               next unless $mode eq "160000";
-               if ($stage ne "0") {
-                       if (!$unmerged{$path}++) {
-                               push @out, "$mode $null_sha1 U\t$path\n";
-                       }
-                       next;
-               }
-               push @out, "$_\n";
-       }
-       if ($unmatched) {
-               print "#unmatched\n";
-       } else {
-               print for (@out);
-       }
-       '
-}
-
 die_if_unmatched ()
 {
        if test "$1" = "#unmatched"
@@ -229,98 +187,6 @@ get_submodule_config () {
        printf '%s' "${value:-$default}"
 }
 
-
-#
-# Map submodule path to submodule name
-#
-# $1 = path
-#
-module_name()
-{
-       # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
-       sm_path="$1"
-       re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
-       name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
-               sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
-       test -z "$name" &&
-       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")"
-       printf '%s\n' "$name"
-}
-
-#
-# Clone a submodule
-#
-# $1 = submodule path
-# $2 = submodule name
-# $3 = URL to clone
-# $4 = reference repository to reuse (empty for independent)
-# $5 = depth argument for shallow clones (empty for deep)
-#
-# Prior to calling, cmd_update checks that a possibly existing
-# path is not a git repository.
-# Likewise, cmd_add checks that path does not exist at all,
-# since it is the location of a new submodule.
-#
-module_clone()
-{
-       sm_path=$1
-       name=$2
-       url=$3
-       reference="$4"
-       depth="$5"
-       quiet=
-       if test -n "$GIT_QUIET"
-       then
-               quiet=-q
-       fi
-
-       gitdir=
-       gitdir_base=
-       base_name=$(dirname "$name")
-
-       gitdir=$(git rev-parse --git-dir)
-       gitdir_base="$gitdir/modules/$base_name"
-       gitdir="$gitdir/modules/$name"
-
-       if test -d "$gitdir"
-       then
-               mkdir -p "$sm_path"
-               rm -f "$gitdir/index"
-       else
-               mkdir -p "$gitdir_base"
-               (
-                       clear_local_git_env
-                       git clone $quiet ${depth:+"$depth"} -n ${reference:+"$reference"} \
-                               --separate-git-dir "$gitdir" "$url" "$sm_path"
-               ) ||
-               die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
-       fi
-
-       # We already are at the root of the work tree but cd_to_toplevel will
-       # resolve any symlinks that might be present in $PWD
-       a=$(cd_to_toplevel && cd "$gitdir" && pwd)/
-       b=$(cd_to_toplevel && cd "$sm_path" && pwd)/
-       # Remove all common leading directories after a sanity check
-       if test "${a#$b}" != "$a" || test "${b#$a}" != "$b"; then
-               die "$(eval_gettext "Gitdir '\$a' is part of the submodule path '\$b' or vice versa")"
-       fi
-       while test "${a%%/*}" = "${b%%/*}"
-       do
-               a=${a#*/}
-               b=${b#*/}
-       done
-       # Now chop off the trailing '/'s that were added in the beginning
-       a=${a%/}
-       b=${b%/}
-
-       # Turn each leading "*/" component into "../"
-       rel=$(printf '%s\n' "$b" | sed -e 's|[^/][^/]*|..|g')
-       printf '%s\n' "gitdir: $rel/$a" >"$sm_path/.git"
-
-       rel=$(printf '%s\n' "$a" | sed -e 's|[^/][^/]*|..|g')
-       (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
-}
-
 isnumber()
 {
        n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
@@ -481,7 +347,7 @@ Use -f if you really want to add it." >&2
                                echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
                        fi
                fi
-               module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" "$depth" || exit
+               git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" "$reference" "$depth" || exit
                (
                        clear_local_git_env
                        cd "$sm_path" &&
@@ -541,7 +407,7 @@ cmd_foreach()
        # command in the subshell (and a recursive call to this function)
        exec 3<&0
 
-       module_list |
+       git submodule--helper list --prefix "$wt_prefix"|
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
@@ -549,7 +415,7 @@ cmd_foreach()
                then
                        displaypath=$(relative_path "$sm_path")
                        say "$(eval_gettext "Entering '\$prefix\$displaypath'")"
-                       name=$(module_name "$sm_path")
+                       name=$(git submodule--helper name "$sm_path")
                        (
                                prefix="$prefix$sm_path/"
                                clear_local_git_env
@@ -601,11 +467,11 @@ cmd_init()
                shift
        done
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
 
                displaypath=$(relative_path "$sm_path")
 
@@ -683,11 +549,11 @@ cmd_deinit()
                die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")"
        fi
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
 
                displaypath=$(relative_path "$sm_path")
 
@@ -799,7 +665,7 @@ cmd_update()
        fi
 
        cloned_modules=
-       module_list "$@" | {
+       git submodule--helper list --prefix "$wt_prefix" "$@" | {
        err=
        while read mode sha1 stage sm_path
        do
@@ -809,7 +675,7 @@ cmd_update()
                        echo >&2 "Skipping unmerged submodule $prefix$sm_path"
                        continue
                fi
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                branch=$(get_submodule_config "$name" branch master)
                if ! test -z "$update"
@@ -843,7 +709,7 @@ Maybe you want to use 'update --init'?")"
 
                if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git
                then
-                       module_clone "$sm_path" "$name" "$url" "$reference" "$depth" || exit
+                       git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$prefix" --path "$sm_path" --name "$name" --url "$url" "$reference" "$depth" || exit
                        cloned_modules="$cloned_modules;$name"
                        subsha1=
                else
@@ -1073,7 +939,7 @@ cmd_summary() {
                        # Respect the ignore setting for --for-status.
                        if test -n "$for_status"
                        then
-                               name=$(module_name "$sm_path")
+                               name=$(git submodule--helper name "$sm_path")
                                ignore_config=$(get_submodule_config "$name" ignore none)
                                test $status != A && test $ignore_config = all && continue
                        fi
@@ -1231,11 +1097,11 @@ cmd_status()
                shift
        done
 
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path") || exit
+               name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                displaypath=$(relative_path "$prefix$sm_path")
                if test "$stage" = U
@@ -1308,11 +1174,11 @@ cmd_sync()
                esac
        done
        cd_to_toplevel
-       module_list "$@" |
+       git submodule--helper list --prefix "$wt_prefix" "$@" |
        while read mode sha1 stage sm_path
        do
                die_if_unmatched "$mode"
-               name=$(module_name "$sm_path")
+               name=$(git submodule--helper name "$sm_path")
                url=$(git config -f .gitmodules --get submodule."$name".url)
 
                # Possibly a url relative to parent
diff --git a/git.c b/git.c
index 5feba410cab6d95e3b9ff9745db56a9d045f0c20..6ed824cacfccddcb01104835b45ab934ff3f443e 100644 (file)
--- a/git.c
+++ b/git.c
@@ -417,7 +417,7 @@ static struct cmd_struct commands[] = {
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
        { "init", cmd_init_db, NO_SETUP },
        { "init-db", cmd_init_db, NO_SETUP },
-       { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP },
+       { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
        { "log", cmd_log, RUN_SETUP },
        { "ls-files", cmd_ls_files, RUN_SETUP },
        { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
@@ -470,6 +470,7 @@ static struct cmd_struct commands[] = {
        { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
        { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
        { "stripspace", cmd_stripspace },
+       { "submodule--helper", cmd_submodule__helper, RUN_SETUP },
        { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
        { "tag", cmd_tag, RUN_SETUP },
        { "unpack-file", cmd_unpack_file, RUN_SETUP },
diff --git a/grep.c b/grep.c
index b58c7c64342698737f9c11b20457bd30ca1fc727..7b2b96a4376efe51e41f9311c6fa5250a2dcd13d 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -31,14 +31,14 @@ void init_grep_defaults(void)
        opt->max_depth = -1;
        opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED;
        opt->extended_regexp_option = 0;
-       strcpy(opt->color_context, "");
-       strcpy(opt->color_filename, "");
-       strcpy(opt->color_function, "");
-       strcpy(opt->color_lineno, "");
-       strcpy(opt->color_match_context, GIT_COLOR_BOLD_RED);
-       strcpy(opt->color_match_selected, GIT_COLOR_BOLD_RED);
-       strcpy(opt->color_selected, "");
-       strcpy(opt->color_sep, GIT_COLOR_CYAN);
+       color_set(opt->color_context, "");
+       color_set(opt->color_filename, "");
+       color_set(opt->color_function, "");
+       color_set(opt->color_lineno, "");
+       color_set(opt->color_match_context, GIT_COLOR_BOLD_RED);
+       color_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);
+       color_set(opt->color_selected, "");
+       color_set(opt->color_sep, GIT_COLOR_CYAN);
        opt->color = -1;
 }
 
@@ -151,14 +151,14 @@ void grep_init(struct grep_opt *opt, const char *prefix)
        opt->regflags = def->regflags;
        opt->relative = def->relative;
 
-       strcpy(opt->color_context, def->color_context);
-       strcpy(opt->color_filename, def->color_filename);
-       strcpy(opt->color_function, def->color_function);
-       strcpy(opt->color_lineno, def->color_lineno);
-       strcpy(opt->color_match_context, def->color_match_context);
-       strcpy(opt->color_match_selected, def->color_match_selected);
-       strcpy(opt->color_selected, def->color_selected);
-       strcpy(opt->color_sep, def->color_sep);
+       color_set(opt->color_context, def->color_context);
+       color_set(opt->color_filename, def->color_filename);
+       color_set(opt->color_function, def->color_function);
+       color_set(opt->color_lineno, def->color_lineno);
+       color_set(opt->color_match_context, def->color_match_context);
+       color_set(opt->color_match_selected, def->color_match_selected);
+       color_set(opt->color_selected, def->color_selected);
+       color_set(opt->color_sep, def->color_sep);
 }
 
 void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
@@ -306,9 +306,9 @@ static NORETURN void compile_regexp_failed(const struct grep_pat *p,
        char where[1024];
 
        if (p->no)
-               sprintf(where, "In '%s' at %d, ", p->origin, p->no);
+               xsnprintf(where, sizeof(where), "In '%s' at %d, ", p->origin, p->no);
        else if (p->origin)
-               sprintf(where, "%s, ", p->origin);
+               xsnprintf(where, sizeof(where), "%s, ", p->origin);
        else
                where[0] = 0;
 
diff --git a/hex.c b/hex.c
index 899b74a08cb5298d2b0dd3205a98f5912a700e66..0519f853b26e527aa529c75da6c302663881102e 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -61,12 +61,10 @@ int get_oid_hex(const char *hex, struct object_id *oid)
        return get_sha1_hex(hex, oid->hash);
 }
 
-char *sha1_to_hex(const unsigned char *sha1)
+char *sha1_to_hex_r(char *buffer, const unsigned char *sha1)
 {
-       static int bufno;
-       static char hexbuffer[4][GIT_SHA1_HEXSZ + 1];
        static const char hex[] = "0123456789abcdef";
-       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
+       char *buf = buffer;
        int i;
 
        for (i = 0; i < GIT_SHA1_RAWSZ; i++) {
@@ -79,6 +77,13 @@ char *sha1_to_hex(const unsigned char *sha1)
        return buffer;
 }
 
+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);
+}
+
 char *oid_to_hex(const struct object_id *oid)
 {
        return sha1_to_hex(oid->hash);
index c98dad23dfd86875209295fadda39c1f1b4f5224..48f39b7f71c632aae6a42d65be929a9e44874d84 100644 (file)
@@ -10,6 +10,7 @@
 #include "remote.h"
 #include "list-objects.h"
 #include "sigchain.h"
+#include "argv-array.h"
 
 #ifdef EXPAT_NEEDS_XMLPARSE_H
 #include <xmlparse.h>
@@ -361,7 +362,7 @@ static void start_put(struct transfer_request *request)
        git_zstream stream;
 
        unpacked = read_sha1_file(request->obj->sha1, &type, &len);
-       hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
+       hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), len) + 1;
 
        /* Set it up */
        git_deflate_init(&stream, zlib_compression_level);
@@ -786,21 +787,21 @@ xml_start_tag(void *userData, const char *name, const char **atts)
 {
        struct xml_ctx *ctx = (struct xml_ctx *)userData;
        const char *c = strchr(name, ':');
-       int new_len;
+       int old_namelen, new_len;
 
        if (c == NULL)
                c = name;
        else
                c++;
 
-       new_len = strlen(ctx->name) + strlen(c) + 2;
+       old_namelen = strlen(ctx->name);
+       new_len = old_namelen + strlen(c) + 2;
 
        if (new_len > ctx->len) {
                ctx->name = xrealloc(ctx->name, new_len);
                ctx->len = new_len;
        }
-       strcat(ctx->name, ".");
-       strcat(ctx->name, c);
+       xsnprintf(ctx->name + old_namelen, ctx->len - old_namelen, ".%s", c);
 
        free(ctx->cdata);
        ctx->cdata = NULL;
@@ -881,7 +882,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
        strbuf_addf(&out_buffer.buf, LOCK_REQUEST, escaped);
        free(escaped);
 
-       sprintf(timeout_header, "Timeout: Second-%ld", timeout);
+       xsnprintf(timeout_header, sizeof(timeout_header), "Timeout: Second-%ld", timeout);
        dav_headers = curl_slist_append(dav_headers, timeout_header);
        dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
 
@@ -1459,8 +1460,6 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls)
 {
        struct strbuf *buf = (struct strbuf *)ls->userData;
        struct object *o;
-       int len;
-       char *ref_info;
        struct ref *ref;
 
        ref = alloc_ref(ls->dentry_name);
@@ -1484,23 +1483,14 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls)
                return;
        }
 
-       len = strlen(ls->dentry_name) + 42;
-       ref_info = xcalloc(len + 1, 1);
-       sprintf(ref_info, "%s   %s\n",
-               sha1_to_hex(ref->old_sha1), ls->dentry_name);
-       fwrite_buffer(ref_info, 1, len, buf);
-       free(ref_info);
+       strbuf_addf(buf, "%s\t%s\n",
+                   sha1_to_hex(ref->old_sha1), ls->dentry_name);
 
        if (o->type == OBJ_TAG) {
                o = deref_tag(o, ls->dentry_name, 0);
-               if (o) {
-                       len = strlen(ls->dentry_name) + 45;
-                       ref_info = xcalloc(len + 1, 1);
-                       sprintf(ref_info, "%s   %s^{}\n",
-                               sha1_to_hex(o->sha1), ls->dentry_name);
-                       fwrite_buffer(ref_info, 1, len, buf);
-                       free(ref_info);
-               }
+               if (o)
+                       strbuf_addf(buf, "%s\t%s^{}\n",
+                                   sha1_to_hex(o->sha1), ls->dentry_name);
        }
        free(ref);
 }
@@ -1866,10 +1856,7 @@ int main(int argc, char **argv)
 
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
-               char old_hex[60], *new_hex;
-               const char *commit_argv[5];
-               int commit_argc;
-               char *new_sha1_hex, *old_sha1_hex;
+               struct argv_array commit_argv = ARGV_ARRAY_INIT;
 
                if (!ref->peer_ref)
                        continue;
@@ -1923,13 +1910,12 @@ int main(int argc, char **argv)
                }
                hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
                new_refs++;
-               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
-               new_hex = sha1_to_hex(ref->new_sha1);
 
                fprintf(stderr, "updating '%s'", ref->name);
                if (strcmp(ref->name, ref->peer_ref->name))
                        fprintf(stderr, " using '%s'", ref->peer_ref->name);
-               fprintf(stderr, "\n  from %s\n  to   %s\n", old_hex, new_hex);
+               fprintf(stderr, "\n  from %s\n  to   %s\n",
+                       sha1_to_hex(ref->old_sha1), sha1_to_hex(ref->new_sha1));
                if (dry_run) {
                        if (helper_status)
                                printf("ok %s\n", ref->name);
@@ -1948,27 +1934,15 @@ int main(int argc, char **argv)
                }
 
                /* Set up revision info for this refspec */
-               commit_argc = 3;
-               new_sha1_hex = xstrdup(sha1_to_hex(ref->new_sha1));
-               old_sha1_hex = NULL;
-               commit_argv[1] = "--objects";
-               commit_argv[2] = new_sha1_hex;
-               if (!push_all && !is_null_sha1(ref->old_sha1)) {
-                       old_sha1_hex = xmalloc(42);
-                       sprintf(old_sha1_hex, "^%s",
-                               sha1_to_hex(ref->old_sha1));
-                       commit_argv[3] = old_sha1_hex;
-                       commit_argc++;
-               }
-               commit_argv[commit_argc] = NULL;
+               argv_array_push(&commit_argv, ""); /* ignored */
+               argv_array_push(&commit_argv, "--objects");
+               argv_array_push(&commit_argv, sha1_to_hex(ref->new_sha1));
+               if (!push_all && !is_null_sha1(ref->old_sha1))
+                       argv_array_pushf(&commit_argv, "^%s",
+                                        sha1_to_hex(ref->old_sha1));
                init_revisions(&revs, setup_git_directory());
-               setup_revisions(commit_argc, commit_argv, &revs, NULL);
+               setup_revisions(commit_argv.argc, commit_argv.argv, &revs, NULL);
                revs.edge_hint = 0; /* just in case */
-               free(new_sha1_hex);
-               if (old_sha1_hex) {
-                       free(old_sha1_hex);
-                       commit_argv[1] = NULL;
-               }
 
                /* Generate a list of objects that need to be pushed */
                pushing = 0;
@@ -1997,6 +1971,7 @@ int main(int argc, char **argv)
                        printf("%s %s\n", !rc ? "ok" : "error", ref->name);
                unlock_remote(ref_lock);
                check_locks();
+               argv_array_clear(&commit_argv);
        }
 
        /* Update remote server info if appropriate */
index 88da5468e77f5a543edb7583e2846f19eb509653..2c721f0c30d786e043dec819dc421788311c7e4b 100644 (file)
@@ -29,7 +29,7 @@ struct object_request {
 struct alternates_request {
        struct walker *walker;
        const char *base;
-       char *url;
+       struct strbuf *url;
        struct strbuf *buffer;
        struct active_request_slot *slot;
        int http_specific;
@@ -195,10 +195,11 @@ static void process_alternates_response(void *callback_data)
 
                        /* Try reusing the slot to get non-http alternates */
                        alt_req->http_specific = 0;
-                       sprintf(alt_req->url, "%s/objects/info/alternates",
-                               base);
+                       strbuf_reset(alt_req->url);
+                       strbuf_addf(alt_req->url, "%s/objects/info/alternates",
+                                   base);
                        curl_easy_setopt(slot->curl, CURLOPT_URL,
-                                        alt_req->url);
+                                        alt_req->url->buf);
                        active_requests++;
                        slot->in_use = 1;
                        if (slot->finished != NULL)
@@ -312,7 +313,7 @@ static void process_alternates_response(void *callback_data)
 static void fetch_alternates(struct walker *walker, const char *base)
 {
        struct strbuf buffer = STRBUF_INIT;
-       char *url;
+       struct strbuf url = STRBUF_INIT;
        struct active_request_slot *slot;
        struct alternates_request alt_req;
        struct walker_data *cdata = walker->data;
@@ -338,7 +339,7 @@ static void fetch_alternates(struct walker *walker, const char *base)
        if (walker->get_verbosely)
                fprintf(stderr, "Getting alternates list for %s\n", base);
 
-       url = xstrfmt("%s/objects/info/http-alternates", base);
+       strbuf_addf(&url, "%s/objects/info/http-alternates", base);
 
        /*
         * Use a callback to process the result, since another request
@@ -351,10 +352,10 @@ static void fetch_alternates(struct walker *walker, const char *base)
 
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url.buf);
 
        alt_req.base = base;
-       alt_req.url = url;
+       alt_req.url = &url;
        alt_req.buffer = &buffer;
        alt_req.http_specific = 1;
        alt_req.slot = slot;
@@ -365,7 +366,7 @@ static void fetch_alternates(struct walker *walker, const char *base)
                cdata->got_alternates = -1;
 
        strbuf_release(&buffer);
-       free(url);
+       strbuf_release(&url);
 }
 
 static int fetch_indices(struct walker *walker, struct alt_base *repo)
diff --git a/http.c b/http.c
index 0f924a8b48f3e30fd4a646ef48acb8d066988a53..7da76edda16abea8a53299f2770ad2dce55c1b60 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1122,7 +1122,7 @@ static void write_accept_language(struct strbuf *buf)
                     decimal_places++, max_q *= 10)
                        ;
 
-               sprintf(q_format, ";q=0.%%0%dd", decimal_places);
+               xsnprintf(q_format, sizeof(q_format), ";q=0.%%0%dd", decimal_places);
 
                strbuf_addstr(buf, "Accept-Language: ");
 
@@ -1529,6 +1529,7 @@ int finish_http_pack_request(struct http_pack_request *preq)
        struct packed_git **lst;
        struct packed_git *p = preq->target;
        char *tmp_idx;
+       size_t len;
        struct child_process ip = CHILD_PROCESS_INIT;
        const char *ip_argv[8];
 
@@ -1542,9 +1543,9 @@ int finish_http_pack_request(struct http_pack_request *preq)
                lst = &((*lst)->next);
        *lst = (*lst)->next;
 
-       tmp_idx = xstrdup(preq->tmpfile);
-       strcpy(tmp_idx + strlen(tmp_idx) - strlen(".pack.temp"),
-              ".idx.temp");
+       if (!strip_suffix(preq->tmpfile, ".pack.temp", &len))
+               die("BUG: pack tmpfile does not end in .pack.temp?");
+       tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile);
 
        ip_argv[0] = "index-pack";
        ip_argv[1] = "-o";
@@ -1619,7 +1620,7 @@ struct http_pack_request *new_http_pack_request(
                        fprintf(stderr,
                                "Resuming fetch of pack %s at byte %ld\n",
                                sha1_to_hex(target->sha1), prev_posn);
-               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               xsnprintf(range, sizeof(range), "Range: bytes=%ld-", prev_posn);
                preq->range_header = curl_slist_append(NULL, range);
                curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER,
                        preq->range_header);
@@ -1779,7 +1780,7 @@ struct http_object_request *new_http_object_request(const char *base_url,
                        fprintf(stderr,
                                "Resuming fetch of object %s at byte %ld\n",
                                hex, prev_posn);
-               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               xsnprintf(range, sizeof(range), "Range: bytes=%ld-", prev_posn);
                range_header = curl_slist_append(range_header, range);
                curl_easy_setopt(freq->slot->curl,
                                 CURLOPT_HTTPHEADER, range_header);
index 37ac4aa86a740ecc8430afff3b9d3c7a0c753a34..e9faaeaf2ab31413139dad17c6df1032e7e3cffa 100644 (file)
@@ -889,9 +889,8 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)
        }
 
        /* response: "<user> <digest in hex>" */
-       resp_len = strlen(user) + 1 + strlen(hex) + 1;
-       response = xmalloc(resp_len);
-       sprintf(response, "%s %s", user, hex);
+       response = xstrfmt("%s %s", user, hex);
+       resp_len = strlen(response) + 1;
 
        response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1);
        encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
index bf83290793059dc42d89db07a12e3bc3c14a48dc..0338630fc2a5378ed33afa828db4940b34aabe82 100644 (file)
@@ -145,11 +145,11 @@ static struct ll_merge_driver ll_merge_drv[] = {
        { "union", "built-in union merge", ll_union_merge },
 };
 
-static void create_temp(mmfile_t *src, char *path)
+static void create_temp(mmfile_t *src, char *path, size_t len)
 {
        int fd;
 
-       strcpy(path, ".merge_file_XXXXXX");
+       xsnprintf(path, len, ".merge_file_XXXXXX");
        fd = xmkstemp(path);
        if (write_in_full(fd, src->ptr, src->size) != src->size)
                die_errno("unable to write temp-file");
@@ -190,10 +190,10 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
 
        result->ptr = NULL;
        result->size = 0;
-       create_temp(orig, temp[0]);
-       create_temp(src1, temp[1]);
-       create_temp(src2, temp[2]);
-       sprintf(temp[3], "%d", marker_size);
+       create_temp(orig, temp[0], sizeof(temp[0]));
+       create_temp(src1, temp[1], sizeof(temp[1]));
+       create_temp(src2, temp[2], sizeof(temp[2]));
+       xsnprintf(temp[3], sizeof(temp[3]), "%d", marker_size);
 
        strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
 
index 9e9589730fa44c94d122dfdb50172baf95e2578d..f4a0f1cf27bf0a10626cc5b1fd628e12cbda3a7e 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -162,11 +162,10 @@ static void read_mailmap_line(struct string_list *map, char *buffer,
                        char *cp;
 
                        free(*repo_abbrev);
-                       *repo_abbrev = xmalloc(len);
 
                        for (cp = buffer + abblen; isspace(*cp); cp++)
                                ; /* nothing */
-                       strcpy(*repo_abbrev, cp);
+                       *repo_abbrev = xstrdup(cp);
                }
                return;
        }
index 44d85bea4b678b761d737f0983ed8e7757478cc8..a5e74d85fd9c82534cf437d3b615d02289ecd7ba 100644 (file)
@@ -630,25 +630,24 @@ static char *unique_path(struct merge_options *o, const char *path, const char *
 
 static int dir_in_way(const char *path, int check_working_copy)
 {
-       int pos, pathlen = strlen(path);
-       char *dirpath = xmalloc(pathlen + 2);
+       int pos;
+       struct strbuf dirpath = STRBUF_INIT;
        struct stat st;
 
-       strcpy(dirpath, path);
-       dirpath[pathlen] = '/';
-       dirpath[pathlen+1] = '\0';
+       strbuf_addstr(&dirpath, path);
+       strbuf_addch(&dirpath, '/');
 
-       pos = cache_name_pos(dirpath, pathlen+1);
+       pos = cache_name_pos(dirpath.buf, dirpath.len);
 
        if (pos < 0)
                pos = -1 - pos;
        if (pos < active_nr &&
-           !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) {
-               free(dirpath);
+           !strncmp(dirpath.buf, active_cache[pos]->name, dirpath.len)) {
+               strbuf_release(&dirpath);
                return 1;
        }
 
-       free(dirpath);
+       strbuf_release(&dirpath);
        return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);
 }
 
diff --git a/notes.c b/notes.c
index eacd2a61daf9b4133e81a936340abc63c1078a48..db77922130b4f7df6ab72122206a76d6c580faac 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -539,6 +539,9 @@ static unsigned char determine_fanout(struct int_node *tree, unsigned char n,
        return fanout + 1;
 }
 
+/* hex SHA1 + 19 * '/' + NUL */
+#define FANOUT_PATH_MAX 40 + 19 + 1
+
 static void construct_path_with_fanout(const unsigned char *sha1,
                unsigned char fanout, char *path)
 {
@@ -551,7 +554,7 @@ static void construct_path_with_fanout(const unsigned char *sha1,
                path[i++] = '/';
                fanout--;
        }
-       strcpy(path + i, hex_sha1 + j);
+       xsnprintf(path + i, FANOUT_PATH_MAX - i, "%s", hex_sha1 + j);
 }
 
 static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
@@ -562,7 +565,7 @@ static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
        void *p;
        int ret = 0;
        struct leaf_node *l;
-       static char path[40 + 19 + 1];  /* hex SHA1 + 19 * '/' + NUL */
+       static char path[FANOUT_PATH_MAX];
 
        fanout = determine_fanout(tree, n, fanout);
        for (i = 0; i < 16; i++) {
@@ -595,7 +598,7 @@ static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,
                                /* invoke callback with subtree */
                                unsigned int path_len =
                                        l->key_sha1[19] * 2 + fanout;
-                               assert(path_len < 40 + 19);
+                               assert(path_len < FANOUT_PATH_MAX - 1);
                                construct_path_with_fanout(l->key_sha1, fanout,
                                                           path);
                                /* Create trailing slash, if needed */
index 637770af813eee643c8375051e419715f56a2db5..7dfcb341d61aa93e2c655963571ba1809fc93d40 100644 (file)
@@ -252,16 +252,11 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
 
 static char *pack_bitmap_filename(struct packed_git *p)
 {
-       char *idx_name;
-       int len;
-
-       len = strlen(p->pack_name) - strlen(".pack");
-       idx_name = xmalloc(len + strlen(".bitmap") + 1);
-
-       memcpy(idx_name, p->pack_name, len);
-       memcpy(idx_name + len, ".bitmap", strlen(".bitmap") + 1);
+       size_t len;
 
-       return idx_name;
+       if (!strip_suffix(p->pack_name, ".pack", &len))
+               die("BUG: pack_name does not end in .pack");
+       return xstrfmt("%.*s.bitmap", (int)len, p->pack_name);
 }
 
 static int open_pack_bitmap_1(struct packed_git *packfile)
diff --git a/pager.c b/pager.c
index 27d4c8a17aa17bb2cf31484227073e1a3204c17f..e425070528f4f9fbf0953c9056d1ea43686e176f 100644 (file)
--- a/pager.c
+++ b/pager.c
 static const char *pager_argv[] = { NULL, NULL };
 static struct child_process pager_process = CHILD_PROCESS_INIT;
 
-static void wait_for_pager(void)
+static void wait_for_pager(int in_signal)
 {
-       fflush(stdout);
-       fflush(stderr);
+       if (!in_signal) {
+               fflush(stdout);
+               fflush(stderr);
+       }
        /* signal EOF to pager */
        close(1);
        close(2);
-       finish_command(&pager_process);
+       if (in_signal)
+               finish_command_in_signal(&pager_process);
+       else
+               finish_command(&pager_process);
+}
+
+static void wait_for_pager_atexit(void)
+{
+       wait_for_pager(0);
 }
 
 static void wait_for_pager_signal(int signo)
 {
-       wait_for_pager();
+       wait_for_pager(1);
        sigchain_pop(signo);
        raise(signo);
 }
@@ -90,7 +100,7 @@ void setup_pager(void)
 
        /* this makes sure that the parent terminates after the pager */
        sigchain_push_common(wait_for_pager_signal);
-       atexit(wait_for_pager);
+       atexit(wait_for_pager_atexit);
 }
 
 int pager_in_use(void)
index 5ab6ed6b0875ff703e3d55abbd0e9d8b0aa1a3c7..239898d946f06d102030569fd45780f523fdcd5a 100644 (file)
@@ -5,6 +5,7 @@
 #include "color.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "sha1-array.h"
 
 /*----- some often used options -----*/
 
@@ -77,7 +78,7 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
        return 0;
 }
 
-int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
+int parse_opt_commits(const struct option *opt, const char *arg, int unset)
 {
        unsigned char sha1[20];
        struct commit *commit;
@@ -93,6 +94,22 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+int parse_opt_object_name(const struct option *opt, const char *arg, int unset)
+{
+       unsigned char sha1[20];
+
+       if (unset) {
+               sha1_array_clear(opt->value);
+               return 0;
+       }
+       if (!arg)
+               return -1;
+       if (get_sha1(arg, sha1))
+               return error(_("malformed object name '%s'"), arg);
+       sha1_array_append(opt->value, sha1);
+       return 0;
+}
+
 int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 {
        int *target = opt->value;
index 3f1cc3aee0faaed923f736393db45877de14edc3..e8b55ea87aaea6a0e16239fd9b2025c4927d2145 100644 (file)
@@ -223,7 +223,8 @@ extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
 extern int parse_opt_expiry_date_cb(const struct option *, const char *, int);
 extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
 extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
-extern int parse_opt_with_commit(const struct option *, const char *, int);
+extern int parse_opt_object_name(const struct option *, const char *, int);
+extern int parse_opt_commits(const struct option *, const char *, int);
 extern int parse_opt_tertiary(const struct option *, const char *, int);
 extern int parse_opt_string_list(const struct option *, const char *, int);
 extern int parse_opt_noop_cb(const struct option *, const char *, int);
@@ -251,5 +252,12 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int);
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru }
 #define OPT_PASSTHRU_ARGV(s, l, v, a, h, f) \
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru_argv }
+#define _OPT_CONTAINS_OR_WITH(name, variable, help, flag) \
+       { OPTION_CALLBACK, 0, name, (variable), N_("commit"), (help), \
+         PARSE_OPT_LASTARG_DEFAULT | flag, \
+         parse_opt_commits, (intptr_t) "HEAD" \
+       }
+#define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, 0)
+#define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN)
 
 #endif
diff --git a/path.c b/path.c
index 95acbafa6883b4418f19a208cb9889f5642f3925..c740c4ff9403bc92df4c439839bd5596ac62e48d 100644 (file)
--- a/path.c
+++ b/path.c
@@ -91,54 +91,274 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
                buf->buf[newlen] = '/';
 }
 
-static const char *common_list[] = {
-       "/branches", "/hooks", "/info", "!/logs", "/lost-found",
-       "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
-       "config", "!gc.pid", "packed-refs", "shallow",
-       NULL
+struct common_dir {
+       /* Not considered garbage for report_linked_checkout_garbage */
+       unsigned ignore_garbage:1;
+       unsigned is_dir:1;
+       /* Not common even though its parent is */
+       unsigned exclude:1;
+       const char *dirname;
 };
 
-static void update_common_dir(struct strbuf *buf, int git_dir_len)
+static struct common_dir common_list[] = {
+       { 0, 1, 0, "branches" },
+       { 0, 1, 0, "hooks" },
+       { 0, 1, 0, "info" },
+       { 0, 0, 1, "info/sparse-checkout" },
+       { 1, 1, 0, "logs" },
+       { 1, 1, 1, "logs/HEAD" },
+       { 0, 1, 1, "logs/refs/bisect" },
+       { 0, 1, 0, "lost-found" },
+       { 0, 1, 0, "objects" },
+       { 0, 1, 0, "refs" },
+       { 0, 1, 1, "refs/bisect" },
+       { 0, 1, 0, "remotes" },
+       { 0, 1, 0, "worktrees" },
+       { 0, 1, 0, "rr-cache" },
+       { 0, 1, 0, "svn" },
+       { 0, 0, 0, "config" },
+       { 1, 0, 0, "gc.pid" },
+       { 0, 0, 0, "packed-refs" },
+       { 0, 0, 0, "shallow" },
+       { 0, 0, 0, NULL }
+};
+
+/*
+ * A compressed trie.  A trie node consists of zero or more characters that
+ * are common to all elements with this prefix, optionally followed by some
+ * children.  If value is not NULL, the trie node is a terminal node.
+ *
+ * For example, consider the following set of strings:
+ * abc
+ * def
+ * definite
+ * definition
+ *
+ * The trie would look look like:
+ * root: len = 0, children a and d non-NULL, value = NULL.
+ *    a: len = 2, contents = bc, value = (data for "abc")
+ *    d: len = 2, contents = ef, children i non-NULL, value = (data for "def")
+ *       i: len = 3, contents = nit, children e and i non-NULL, value = NULL
+ *           e: len = 0, children all NULL, value = (data for "definite")
+ *           i: len = 2, contents = on, children all NULL,
+ *              value = (data for "definition")
+ */
+struct trie {
+       struct trie *children[256];
+       int len;
+       char *contents;
+       void *value;
+};
+
+static struct trie *make_trie_node(const char *key, void *value)
 {
-       char *base = buf->buf + git_dir_len;
-       const char **p;
-
-       if (is_dir_file(base, "logs", "HEAD") ||
-           is_dir_file(base, "info", "sparse-checkout"))
-               return; /* keep this in $GIT_DIR */
-       for (p = common_list; *p; p++) {
-               const char *path = *p;
-               int is_dir = 0;
-               if (*path == '!')
-                       path++;
-               if (*path == '/') {
-                       path++;
-                       is_dir = 1;
+       struct trie *new_node = xcalloc(1, sizeof(*new_node));
+       new_node->len = strlen(key);
+       if (new_node->len) {
+               new_node->contents = xmalloc(new_node->len);
+               memcpy(new_node->contents, key, new_node->len);
+       }
+       new_node->value = value;
+       return new_node;
+}
+
+/*
+ * Add a key/value pair to a trie.  The key is assumed to be \0-terminated.
+ * If there was an existing value for this key, return it.
+ */
+static void *add_to_trie(struct trie *root, const char *key, void *value)
+{
+       struct trie *child;
+       void *old;
+       int i;
+
+       if (!*key) {
+               /* we have reached the end of the key */
+               old = root->value;
+               root->value = value;
+               return old;
+       }
+
+       for (i = 0; i < root->len; i++) {
+               if (root->contents[i] == key[i])
+                       continue;
+
+               /*
+                * Split this node: child will contain this node's
+                * existing children.
+                */
+               child = malloc(sizeof(*child));
+               memcpy(child->children, root->children, sizeof(root->children));
+
+               child->len = root->len - i - 1;
+               if (child->len) {
+                       child->contents = xstrndup(root->contents + i + 1,
+                                                  child->len);
                }
-               if (is_dir && dir_prefix(base, path)) {
-                       replace_dir(buf, git_dir_len, get_git_common_dir());
-                       return;
+               child->value = root->value;
+               root->value = NULL;
+               root->len = i;
+
+               memset(root->children, 0, sizeof(root->children));
+               root->children[(unsigned char)root->contents[i]] = child;
+
+               /* This is the newly-added child. */
+               root->children[(unsigned char)key[i]] =
+                       make_trie_node(key + i + 1, value);
+               return NULL;
+       }
+
+       /* We have matched the entire compressed section */
+       if (key[i]) {
+               child = root->children[(unsigned char)key[root->len]];
+               if (child) {
+                       return add_to_trie(child, key + root->len + 1, value);
+               } else {
+                       child = make_trie_node(key + root->len + 1, value);
+                       root->children[(unsigned char)key[root->len]] = child;
+                       return NULL;
                }
-               if (!is_dir && !strcmp(base, path)) {
-                       replace_dir(buf, git_dir_len, get_git_common_dir());
-                       return;
+       }
+
+       old = root->value;
+       root->value = value;
+       return old;
+}
+
+typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
+
+/*
+ * Search a trie for some key.  Find the longest /-or-\0-terminated
+ * prefix of the key for which the trie contains a value.  Call fn
+ * with the unmatched portion of the key and the found value, and
+ * return its return value.  If there is no such prefix, return -1.
+ *
+ * The key is partially normalized: consecutive slashes are skipped.
+ *
+ * For example, consider the trie containing only [refs,
+ * refs/worktree] (both with values).
+ *
+ * | key             | unmatched  | val from node | return value |
+ * |-----------------|------------|---------------|--------------|
+ * | a               | not called | n/a           | -1           |
+ * | refs            | \0         | refs          | as per fn    |
+ * | refs/           | /          | refs          | as per fn    |
+ * | refs/w          | /w         | refs          | as per fn    |
+ * | refs/worktree   | \0         | refs/worktree | as per fn    |
+ * | refs/worktree/  | /          | refs/worktree | as per fn    |
+ * | refs/worktree/a | /a         | refs/worktree | as per fn    |
+ * |-----------------|------------|---------------|--------------|
+ *
+ */
+static int trie_find(struct trie *root, const char *key, match_fn fn,
+                    void *baton)
+{
+       int i;
+       int result;
+       struct trie *child;
+
+       if (!*key) {
+               /* we have reached the end of the key */
+               if (root->value && !root->len)
+                       return fn(key, root->value, baton);
+               else
+                       return -1;
+       }
+
+       for (i = 0; i < root->len; i++) {
+               /* Partial path normalization: skip consecutive slashes. */
+               if (key[i] == '/' && key[i+1] == '/') {
+                       key++;
+                       continue;
                }
+               if (root->contents[i] != key[i])
+                       return -1;
        }
+
+       /* Matched the entire compressed section */
+       key += i;
+       if (!*key)
+               /* End of key */
+               return fn(key, root->value, baton);
+
+       /* Partial path normalization: skip consecutive slashes */
+       while (key[0] == '/' && key[1] == '/')
+               key++;
+
+       child = root->children[(unsigned char)*key];
+       if (child)
+               result = trie_find(child, key + 1, fn, baton);
+       else
+               result = -1;
+
+       if (result >= 0 || (*key != '/' && *key != 0))
+               return result;
+       if (root->value)
+               return fn(key, root->value, baton);
+       else
+               return -1;
+}
+
+static struct trie common_trie;
+static int common_trie_done_setup;
+
+static void init_common_trie(void)
+{
+       struct common_dir *p;
+
+       if (common_trie_done_setup)
+               return;
+
+       for (p = common_list; p->dirname; p++)
+               add_to_trie(&common_trie, p->dirname, p);
+
+       common_trie_done_setup = 1;
+}
+
+/*
+ * Helper function for update_common_dir: returns 1 if the dir
+ * prefix is common.
+ */
+static int check_common(const char *unmatched, void *value, void *baton)
+{
+       struct common_dir *dir = value;
+
+       if (!dir)
+               return 0;
+
+       if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
+               return !dir->exclude;
+
+       if (!dir->is_dir && unmatched[0] == 0)
+               return !dir->exclude;
+
+       return 0;
+}
+
+static void update_common_dir(struct strbuf *buf, int git_dir_len,
+                             const char *common_dir)
+{
+       char *base = buf->buf + git_dir_len;
+       init_common_trie();
+       if (!common_dir)
+               common_dir = get_git_common_dir();
+       if (trie_find(&common_trie, base, check_common, NULL) > 0)
+               replace_dir(buf, git_dir_len, common_dir);
 }
 
 void report_linked_checkout_garbage(void)
 {
        struct strbuf sb = STRBUF_INIT;
-       const char **p;
+       const struct common_dir *p;
        int len;
 
        if (!git_common_dir_env)
                return;
        strbuf_addf(&sb, "%s/", get_git_dir());
        len = sb.len;
-       for (p = common_list; *p; p++) {
-               const char *path = *p;
-               if (*path == '!')
+       for (p = common_list; p->dirname; p++) {
+               const char *path = p->dirname;
+               if (p->ignore_garbage)
                        continue;
                strbuf_setlen(&sb, len);
                strbuf_addstr(&sb, path);
@@ -160,7 +380,7 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
        else if (git_db_env && dir_prefix(base, "objects"))
                replace_dir(buf, git_dir_len + 7, get_object_directory());
        else if (git_common_dir_env)
-               update_common_dir(buf, git_dir_len);
+               update_common_dir(buf, git_dir_len, NULL);
 }
 
 static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
@@ -175,6 +395,16 @@ static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
        strbuf_cleanup_path(buf);
 }
 
+char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
+{
+       va_list args;
+       strbuf_reset(buf);
+       va_start(args, fmt);
+       do_git_path(buf, fmt, args);
+       va_end(args);
+       return buf->buf;
+}
+
 void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 {
        va_list args;
@@ -228,10 +458,11 @@ static void do_submodule_path(struct strbuf *buf, const char *path,
                              const char *fmt, va_list args)
 {
        const char *git_dir;
+       struct strbuf git_submodule_common_dir = STRBUF_INIT;
+       struct strbuf git_submodule_dir = STRBUF_INIT;
 
        strbuf_addstr(buf, path);
-       if (buf->len && buf->buf[buf->len - 1] != '/')
-               strbuf_addch(buf, '/');
+       strbuf_complete(buf, '/');
        strbuf_addstr(buf, ".git");
 
        git_dir = read_gitfile(buf->buf);
@@ -240,9 +471,17 @@ static void do_submodule_path(struct strbuf *buf, const char *path,
                strbuf_addstr(buf, git_dir);
        }
        strbuf_addch(buf, '/');
+       strbuf_addstr(&git_submodule_dir, buf->buf);
 
        strbuf_vaddf(buf, fmt, args);
+
+       if (get_common_dir_noenv(&git_submodule_common_dir, git_submodule_dir.buf))
+               update_common_dir(buf, git_submodule_dir.len, git_submodule_common_dir.buf);
+
        strbuf_cleanup_path(buf);
+
+       strbuf_release(&git_submodule_dir);
+       strbuf_release(&git_submodule_common_dir);
 }
 
 char *git_pathdup_submodule(const char *path, const char *fmt, ...)
@@ -381,8 +620,8 @@ char *expand_user_path(const char *path)
  */
 const char *enter_repo(const char *path, int strict)
 {
-       static char used_path[PATH_MAX];
-       static char validated_path[PATH_MAX];
+       static struct strbuf validated_path = STRBUF_INIT;
+       static struct strbuf used_path = STRBUF_INIT;
 
        if (!path)
                return NULL;
@@ -397,52 +636,57 @@ const char *enter_repo(const char *path, int strict)
                while ((1 < len) && (path[len-1] == '/'))
                        len--;
 
+               /*
+                * We can handle arbitrary-sized buffers, but this remains as a
+                * sanity check on untrusted input.
+                */
                if (PATH_MAX <= len)
                        return NULL;
-               strncpy(used_path, path, len); used_path[len] = 0 ;
-               strcpy(validated_path, used_path);
 
-               if (used_path[0] == '~') {
-                       char *newpath = expand_user_path(used_path);
-                       if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
-                               free(newpath);
+               strbuf_reset(&used_path);
+               strbuf_reset(&validated_path);
+               strbuf_add(&used_path, path, len);
+               strbuf_add(&validated_path, path, len);
+
+               if (used_path.buf[0] == '~') {
+                       char *newpath = expand_user_path(used_path.buf);
+                       if (!newpath)
                                return NULL;
-                       }
-                       /*
-                        * Copy back into the static buffer. A pity
-                        * since newpath was not bounded, but other
-                        * branches of the if are limited by PATH_MAX
-                        * anyway.
-                        */
-                       strcpy(used_path, newpath); free(newpath);
+                       strbuf_attach(&used_path, newpath, strlen(newpath),
+                                     strlen(newpath));
                }
-               else if (PATH_MAX - 10 < len)
-                       return NULL;
-               len = strlen(used_path);
                for (i = 0; suffix[i]; i++) {
                        struct stat st;
-                       strcpy(used_path + len, suffix[i]);
-                       if (!stat(used_path, &st) &&
+                       size_t baselen = used_path.len;
+                       strbuf_addstr(&used_path, suffix[i]);
+                       if (!stat(used_path.buf, &st) &&
                            (S_ISREG(st.st_mode) ||
-                           (S_ISDIR(st.st_mode) && is_git_directory(used_path)))) {
-                               strcat(validated_path, suffix[i]);
+                           (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) {
+                               strbuf_addstr(&validated_path, suffix[i]);
                                break;
                        }
+                       strbuf_setlen(&used_path, baselen);
                }
                if (!suffix[i])
                        return NULL;
-               gitfile = read_gitfile(used_path) ;
+               gitfile = read_gitfile(used_path.buf);
+               if (gitfile) {
+                       strbuf_reset(&used_path);
+                       strbuf_addstr(&used_path, gitfile);
+               }
+               if (chdir(used_path.buf))
+                       return NULL;
+               path = validated_path.buf;
+       }
+       else {
+               const char *gitfile = read_gitfile(path);
                if (gitfile)
-                       strcpy(used_path, gitfile);
-               if (chdir(used_path))
+                       path = gitfile;
+               if (chdir(path))
                        return NULL;
-               path = validated_path;
        }
-       else if (chdir(path))
-               return NULL;
 
-       if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
-           validate_headref("HEAD") == 0) {
+       if (is_git_directory(".")) {
                set_git_dir(".");
                check_repository_format();
                return path;
@@ -621,7 +865,7 @@ const char *relative_path(const char *in, const char *prefix,
  */
 const char *remove_leading_path(const char *in, const char *prefix)
 {
-       static char buf[PATH_MAX + 1];
+       static struct strbuf buf = STRBUF_INIT;
        int i = 0, j = 0;
 
        if (!prefix || !prefix[0])
@@ -650,11 +894,13 @@ const char *remove_leading_path(const char *in, const char *prefix)
                return in;
        while (is_dir_sep(in[j]))
                j++;
+
+       strbuf_reset(&buf);
        if (!in[j])
-               strcpy(buf, ".");
+               strbuf_addstr(&buf, ".");
        else
-               strcpy(buf, in + j);
-       return buf;
+               strbuf_addstr(&buf, in + j);
+       return buf.buf;
 }
 
 /*
@@ -676,6 +922,11 @@ const char *remove_leading_path(const char *in, const char *prefix)
  * normalized, any time "../" eats up to the prefix_len part,
  * prefix_len is reduced. In the end prefix_len is the remaining
  * prefix that has not been overridden by user pathspec.
+ *
+ * NEEDSWORK: This function doesn't perform normalization w.r.t. trailing '/'.
+ * For everything but the root folder itself, the normalized path should not
+ * end with a '/', then the callers need to be fixed up accordingly.
+ *
  */
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
index 08a1427c0d4f6743182b235ec9d9c710c3c99b5b..62fdb37079fa31698846044448b5cfb398e45528 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "pkt-line.h"
+#include "run-command.h"
 
 char packet_buffer[LARGE_PACKET_MAX];
 static const char *packet_trace_prefix = "git";
@@ -11,6 +12,11 @@ void packet_trace_identity(const char *prog)
        packet_trace_prefix = xstrdup(prog);
 }
 
+static const char *get_trace_prefix(void)
+{
+       return in_async() ? "sideband" : packet_trace_prefix;
+}
+
 static int packet_trace_pack(const char *buf, unsigned int len, int sideband)
 {
        if (!sideband) {
@@ -57,7 +63,7 @@ static void packet_trace(const char *buf, unsigned int len, int write)
        strbuf_init(&out, len+32);
 
        strbuf_addf(&out, "packet: %12s%c ",
-                   packet_trace_prefix, write ? '>' : '<');
+                   get_trace_prefix(), write ? '>' : '<');
 
        /* XXX we should really handle printable utf8 */
        for (i = 0; i < len; i++) {
index 4f66cee346870259072108f2e6e4af9817a27329..c9e1fb8e0d61e199f6813961667ecc4aad701dec 100644 (file)
--- a/po/ru.po
+++ b/po/ru.po
@@ -10,10 +10,10 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Перевод Git на русский язык\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2015-07-14 07:19+0800\n"
-"PO-Revision-Date: 2015-07-14 13:06+0000\n"
+"POT-Creation-Date: 2015-09-15 06:45+0800\n"
+"PO-Revision-Date: 2015-09-30 14:53+0000\n"
 "Last-Translator: Dimitriy Ryazantcev <DJm00n@mail.ru>\n"
-"Language-Team: Russian (http://www.transifex.com/p/git-po-ru/language/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"
@@ -31,94 +31,106 @@ msgid ""
 "as appropriate to mark resolution and make a commit."
 msgstr "Исправьте их в рабочем каталоге, затем запустите «git add/rm <файл>»,\nчтобы пометить исправление и сделайте коммит."
 
-#: archive.c:11
+#: advice.c:101 builtin/merge.c:1227
+msgid "You have not concluded your merge (MERGE_HEAD exists)."
+msgstr "Вы не завершили слияние (присутствует файл MERGE_HEAD)."
+
+#: advice.c:103
+msgid "Please, commit your changes before you can merge."
+msgstr "Выполните коммит ваших изменений, перед слиянием."
+
+#: advice.c:104
+msgid "Exiting because of unfinished merge."
+msgstr "Выход из-за незавершенного слияния."
+
+#: archive.c:12
 msgid "git archive [<options>] <tree-ish> [<path>...]"
 msgstr "git archive [<опции>] <указатель-дерева> [<путь>…]"
 
-#: archive.c:12
+#: archive.c:13
 msgid "git archive --list"
 msgstr "git archive --list"
 
-#: archive.c:13
+#: archive.c:14
 msgid ""
 "git archive --remote <repo> [--exec <cmd>] [<options>] <tree-ish> "
 "[<path>...]"
 msgstr "git archive --remote <репозиторий> [--exec <команда>] [<опции>] <указатель-дерева> [<путь>…]"
 
-#: archive.c:14
+#: archive.c:15
 msgid "git archive --remote <repo> [--exec <cmd>] --list"
 msgstr "git archive --remote <репозиторий> [--exec <команда>] --list"
 
-#: archive.c:342 builtin/add.c:137 builtin/add.c:428 builtin/rm.c:327
+#: archive.c:343 builtin/add.c:137 builtin/add.c:426 builtin/rm.c:327
 #, c-format
 msgid "pathspec '%s' did not match any files"
 msgstr "спецификация пути «%s» не соответствует ни одному файлу"
 
-#: archive.c:427
+#: archive.c:428
 msgid "fmt"
 msgstr "формат"
 
-#: archive.c:427
+#: archive.c:428
 msgid "archive format"
 msgstr "формат архива"
 
-#: archive.c:428 builtin/log.c:1204
+#: archive.c:429 builtin/log.c:1229
 msgid "prefix"
 msgstr "префикс"
 
-#: archive.c:429
+#: archive.c:430
 msgid "prepend prefix to each pathname in the archive"
 msgstr "добавлять префикс перед каждым путем файла в архиве"
 
-#: archive.c:430 builtin/archive.c:88 builtin/blame.c:2516
-#: builtin/blame.c:2517 builtin/config.c:57 builtin/fast-export.c:986
-#: builtin/fast-export.c:988 builtin/grep.c:712 builtin/hash-object.c:99
-#: builtin/ls-files.c:446 builtin/ls-files.c:449 builtin/notes.c:394
-#: builtin/notes.c:557 builtin/read-tree.c:109 parse-options.h:150
+#: archive.c:431 builtin/archive.c:88 builtin/blame.c:2516
+#: builtin/blame.c:2517 builtin/config.c:58 builtin/fast-export.c:987
+#: builtin/fast-export.c:989 builtin/grep.c:712 builtin/hash-object.c:99
+#: builtin/ls-files.c:446 builtin/ls-files.c:449 builtin/notes.c:395
+#: builtin/notes.c:558 builtin/read-tree.c:109 parse-options.h:153
 msgid "file"
 msgstr "файл"
 
-#: archive.c:431 builtin/archive.c:89
+#: archive.c:432 builtin/archive.c:89
 msgid "write the archive to this file"
 msgstr "запись архива в этот файл"
 
-#: archive.c:433
+#: archive.c:434
 msgid "read .gitattributes in working directory"
 msgstr "читать .gitattributes в рабочем каталоге"
 
-#: archive.c:434
+#: archive.c:435
 msgid "report archived files on stderr"
 msgstr "отчет об архивированных файлах в stderr"
 
-#: archive.c:435
+#: archive.c:436
 msgid "store only"
 msgstr "только хранение"
 
-#: archive.c:436
+#: archive.c:437
 msgid "compress faster"
 msgstr "сжимать быстрее"
 
-#: archive.c:444
+#: archive.c:445
 msgid "compress better"
 msgstr "сжимать лучше"
 
-#: archive.c:447
+#: archive.c:448
 msgid "list supported archive formats"
 msgstr "перечислить поддерживаемые форматы архивов"
 
-#: archive.c:449 builtin/archive.c:90 builtin/clone.c:77
+#: archive.c:450 builtin/archive.c:90 builtin/clone.c:77
 msgid "repo"
 msgstr "репозиторий"
 
-#: archive.c:450 builtin/archive.c:91
+#: archive.c:451 builtin/archive.c:91
 msgid "retrieve the archive from remote repository <repo>"
 msgstr "получить архив из внешнего <репозитория>"
 
-#: archive.c:451 builtin/archive.c:92 builtin/notes.c:478
+#: archive.c:452 builtin/archive.c:92 builtin/notes.c:479
 msgid "command"
 msgstr "комманда"
 
-#: archive.c:452 builtin/archive.c:93
+#: archive.c:453 builtin/archive.c:93
 msgid "path to the remote git-upload-archive command"
 msgstr "путь к команде git-upload-archive на машине с внешним репозиторием"
 
@@ -230,6 +242,11 @@ msgstr "Неоднозначное имя объекта: «%s»."
 msgid "Not a valid branch point: '%s'."
 msgstr "Недопустимая точка ветки: «%s»."
 
+#: branch.c:399
+#, c-format
+msgid "'%s' is already checked out at '%s'"
+msgstr "«%s» уже находится на «%s»"
+
 #: bundle.c:34
 #, c-format
 msgid "'%s' does not look like a v2 bundle file"
@@ -240,7 +257,7 @@ msgstr "«%s» не похож на файл пакета версии 2"
 msgid "unrecognized header: %s%s (%d)"
 msgstr "неопознанный заголовок: %s%s (%d)"
 
-#: bundle.c:87 builtin/commit.c:766
+#: bundle.c:87 builtin/commit.c:765
 #, c-format
 msgid "could not open '%s'"
 msgstr "не удалось открыть «%s»"
@@ -249,9 +266,9 @@ msgstr "не удалось открыть «%s»"
 msgid "Repository lacks these prerequisite commits:"
 msgstr "В репозитории отсутствуют необходимые коммиты:"
 
-#: bundle.c:163 sequencer.c:650 sequencer.c:1105 builtin/blame.c:2705
-#: builtin/branch.c:651 builtin/commit.c:1045 builtin/log.c:330
-#: builtin/log.c:825 builtin/log.c:1432 builtin/log.c:1666 builtin/merge.c:358
+#: bundle.c:163 sequencer.c:636 sequencer.c:1083 builtin/blame.c:2708
+#: builtin/branch.c:652 builtin/commit.c:1044 builtin/log.c:334
+#: builtin/log.c:850 builtin/log.c:1457 builtin/log.c:1690 builtin/merge.c:358
 #: builtin/shortlog.c:158
 msgid "revision walk setup failed"
 msgstr "сбой инициализации прохода по редакциям"
@@ -278,38 +295,38 @@ msgstr[1] "Пакет требует эти %d ссылки:"
 msgstr[2] "Пакет требует эти %d ссылок:"
 msgstr[3] "Пакет требует эти %d ссылок:"
 
-#: bundle.c:251
+#: bundle.c:253
 msgid "Could not spawn pack-objects"
 msgstr "Не удалось создать объекты пакета"
 
-#: bundle.c:269
+#: bundle.c:264
 msgid "pack-objects died"
 msgstr "критическая ошибка pack-objects"
 
-#: bundle.c:309
+#: bundle.c:304
 msgid "rev-list died"
 msgstr "критическая ошибка rev-list"
 
-#: bundle.c:358
+#: bundle.c:353
 #, c-format
 msgid "ref '%s' is excluded by the rev-list options"
 msgstr "ссылка «%s» исключена в соответствии с опциями rev-list"
 
-#: bundle.c:437 builtin/log.c:153 builtin/log.c:1342 builtin/shortlog.c:261
+#: bundle.c:443 builtin/log.c:157 builtin/log.c:1367 builtin/shortlog.c:261
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr "неопознанный аргумент: %s"
 
-#: bundle.c:443
+#: bundle.c:449
 msgid "Refusing to create empty bundle."
 msgstr "Отклонение создания пустого пакета."
 
-#: bundle.c:453
+#: bundle.c:459
 #, c-format
 msgid "cannot create '%s'"
 msgstr "не удалось создать «%s»"
 
-#: bundle.c:474
+#: bundle.c:480
 msgid "index-pack died"
 msgstr "критическая ошибка index-pack"
 
@@ -318,7 +335,8 @@ msgstr "критическая ошибка index-pack"
 msgid "invalid color value: %.*s"
 msgstr "недопустимое значение цвета: %.*s"
 
-#: commit.c:40
+#: commit.c:40 builtin/am.c:451 builtin/am.c:487 builtin/am.c:1516
+#: builtin/am.c:2128
 #, c-format
 msgid "could not parse %s"
 msgstr "не удалось разобрать %s"
@@ -494,75 +512,75 @@ msgstr "сбой чтения orderfile «%s»"
 msgid "Performing inexact rename detection"
 msgstr "Выполняется неточное определение переименования"
 
-#: diff.c:114
+#: diff.c:116
 #, c-format
 msgid "  Failed to parse dirstat cut-off percentage '%s'\n"
 msgstr "  Сбой разбора величины среза (cut-off) у dirstat «%s»\n"
 
-#: diff.c:119
+#: diff.c:121
 #, c-format
 msgid "  Unknown dirstat parameter '%s'\n"
 msgstr "Неизвестный параметр dirstat: «%s»\n"
 
-#: diff.c:214
+#: diff.c:216
 #, c-format
 msgid "Unknown value for 'diff.submodule' config variable: '%s'"
 msgstr "Неизвестное значения для переменной «diff.submodule»: «%s»"
 
-#: diff.c:266
+#: diff.c:268
 #, c-format
 msgid ""
 "Found errors in 'diff.dirstat' config variable:\n"
 "%s"
 msgstr "Найдены ошибки в переменной «diff.dirstat»:\n%s"
 
-#: diff.c:2997
+#: diff.c:2998
 #, c-format
 msgid "external diff died, stopping at %s"
 msgstr "критическая ошибка при внешнем сравнении, останов на %s"
 
-#: diff.c:3393
+#: diff.c:3394
 msgid "--follow requires exactly one pathspec"
 msgstr "--follow требует ровно одной спецификации пути"
 
-#: diff.c:3556
+#: diff.c:3557
 #, c-format
 msgid ""
 "Failed to parse --dirstat/-X option parameter:\n"
 "%s"
 msgstr "Сбой разбора параметра опции --dirstat/-X :\n%s"
 
-#: diff.c:3570
+#: diff.c:3571
 #, c-format
 msgid "Failed to parse --submodule option parameter: '%s'"
 msgstr "Сбой разбора параметра опции --submodule: «%s»"
 
-#: dir.c:1852
+#: dir.c:1853
 msgid "failed to get kernel name and information"
 msgstr "не удалось получить имя ядра и информацию"
 
-#: dir.c:1945
+#: dir.c:1936
 msgid "Untracked cache is disabled on this system."
 msgstr "Кэш неотслеживаемых файлов отключен на этой системе."
 
-#: gpg-interface.c:129 gpg-interface.c:200
+#: gpg-interface.c:166 gpg-interface.c:237
 msgid "could not run gpg."
 msgstr "не удалось запустить gpg."
 
-#: gpg-interface.c:141
+#: gpg-interface.c:178
 msgid "gpg did not accept the data"
 msgstr "gpg не принял данные"
 
-#: gpg-interface.c:152
+#: gpg-interface.c:189
 msgid "gpg failed to sign the data"
 msgstr "gpg не удалось подписать данные"
 
-#: gpg-interface.c:185
+#: gpg-interface.c:222
 #, c-format
 msgid "could not create temporary file '%s': %s"
 msgstr "не удалось создать временный файл «%s»: %s"
 
-#: gpg-interface.c:188
+#: gpg-interface.c:225
 #, c-format
 msgid "failed writing detached signature to '%s': %s"
 msgstr "сбой записи отсоединенной подписи в «%s»: %s"
@@ -640,20 +658,12 @@ msgstr[3] "\nВозможно, вы имели в виду что-то из эт
 msgid "%s: %s - %s"
 msgstr "%s: %s — %s"
 
-#: lockfile.c:345
-msgid "BUG: reopen a lockfile that is still open"
-msgstr "БАГ: повторное открытие файла блокировки, который уже открыт"
-
-#: lockfile.c:347
-msgid "BUG: reopen a lockfile that has been committed"
-msgstr "БАГ: повторное открытие файла блокировки, который уже был закоммичен"
-
 #: merge.c:41
 msgid "failed to read the cache"
 msgstr "сбой чтения кэша"
 
-#: merge.c:94 builtin/checkout.c:376 builtin/checkout.c:587
-#: builtin/clone.c:647
+#: merge.c:94 builtin/am.c:2001 builtin/am.c:2036 builtin/checkout.c:375
+#: builtin/checkout.c:586 builtin/clone.c:715
 msgid "unable to write new index file"
 msgstr "не удалось записать новый файл индекса"
 
@@ -700,7 +710,7 @@ msgstr "невозможно прочитать объект %s «%s»"
 msgid "blob expected for %s '%s'"
 msgstr "ожидается двоичный объект для %s «%s»"
 
-#: merge-recursive.c:788 builtin/clone.c:306
+#: merge-recursive.c:788 builtin/clone.c:364
 #, c-format
 msgid "failed to open '%s'"
 msgstr "не удалось открыть «%s»"
@@ -908,19 +918,19 @@ msgstr "Не удается записать индекс."
 msgid "Cannot commit uninitialized/unreferenced notes tree"
 msgstr "Нельзя закоммитить неинициализированное или не имеющее ссылок дерево заметок"
 
-#: notes-utils.c:82
+#: notes-utils.c:100
 #, c-format
 msgid "Bad notes.rewriteMode value: '%s'"
 msgstr "Неправильное значение notes.rewriteMode: «%s»"
 
-#: notes-utils.c:92
+#: notes-utils.c:110
 #, c-format
 msgid "Refusing to rewrite notes in %s (outside of refs/notes/)"
 msgstr "Отказ в перезаписи заметок в %s (за пределами refs/notes/)"
 
 #. TRANSLATORS: The first %s is the name of the
 #. environment variable, the second %s is its value
-#: notes-utils.c:119
+#: notes-utils.c:137
 #, c-format
 msgid "Bad %s value: '%s'"
 msgstr "Неправильное значение переменной %s: «%s»"
@@ -930,28 +940,28 @@ msgstr "Неправильное значение переменной %s: «%s
 msgid "unable to parse object: %s"
 msgstr "не удалось разобрать объект: %s"
 
-#: parse-options.c:546
+#: parse-options.c:563
 msgid "..."
 msgstr "…"
 
-#: parse-options.c:564
+#: parse-options.c:581
 #, c-format
 msgid "usage: %s"
 msgstr "использование: %s"
 
 #. TRANSLATORS: the colon here should align with the
 #. one in "usage: %s" translation
-#: parse-options.c:568
+#: parse-options.c:585
 #, c-format
 msgid "   or: %s"
 msgstr "          или: %s"
 
-#: parse-options.c:571
+#: parse-options.c:588
 #, c-format
 msgid "    %s"
 msgstr "            %s"
 
-#: parse-options.c:605
+#: parse-options.c:622
 msgid "-NUM"
 msgstr "-КОЛИЧЕСТВО"
 
@@ -1015,7 +1025,7 @@ msgid ""
 "Perhaps you forgot to add either ':/' or '.' ?"
 msgstr "Не указан шаблон для исключения с помощью :(exclude).\nВозможно, вы забыли «:/» или «.» ?"
 
-#: pretty.c:968
+#: pretty.c:969
 msgid "unable to parse --pretty format"
 msgstr "не удалось разобрать формат для --pretty"
 
@@ -1023,20 +1033,45 @@ msgstr "не удалось разобрать формат для --pretty"
 msgid "done"
 msgstr "готово"
 
-#: read-cache.c:1295
+#: read-cache.c:1296
 #, c-format
 msgid ""
 "index.version set, but the value is invalid.\n"
 "Using version %i"
 msgstr "index.version указан, но значение недействительное.\nИспользую версию %i"
 
-#: read-cache.c:1305
+#: read-cache.c:1306
 #, c-format
 msgid ""
 "GIT_INDEX_VERSION set, but the value is invalid.\n"
 "Using version %i"
 msgstr "GIT_INDEX_VERSION указан, но значение недействительное.\nИспользую версию %i"
 
+#: refs.c:2941 builtin/merge.c:760 builtin/merge.c:871 builtin/merge.c:973
+#: builtin/merge.c:983
+#, c-format
+msgid "Could not open '%s' for writing"
+msgstr "Не удалось открыть «%s» для записи"
+
+#: refs.c:3001
+#, c-format
+msgid "could not delete reference %s: %s"
+msgstr "не удалось удалить ссылку %s: %s"
+
+#: refs.c:3004
+#, c-format
+msgid "could not delete references: %s"
+msgstr "не удалось удалить ссылки: %s"
+
+#: refs.c:3013
+#, c-format
+msgid "could not remove reference %s"
+msgstr "не удалось удалить ссылки %s"
+
+#: ref-filter.c:660
+msgid "unable to parse format"
+msgstr "не удалось разобрать формат"
+
 #: remote.c:792
 #, c-format
 msgid "Cannot fetch both %s and %s to %s"
@@ -1156,7 +1191,16 @@ msgstr[3] "Ваша ветка и «%s» разошлись\nи теперь и
 msgid "  (use \"git pull\" to merge the remote branch into yours)\n"
 msgstr "  (используйте «git pull», чтобы слить внешнюю ветку в вашу)\n"
 
-#: revision.c:2366
+#: revision.c:2198
+msgid "your current branch appears to be broken"
+msgstr "похоже, ваша текущая ветка повреждена"
+
+#: revision.c:2201
+#, c-format
+msgid "your current branch '%s' does not have any commits yet"
+msgstr "ваша текущая ветка «%s» еще не содержит ни одного коммита"
+
+#: revision.c:2395
 msgid "--first-parent is incompatible with --bisect"
 msgstr "опцию --first-parent нельзя использовать одновременно с --bisect"
 
@@ -1169,257 +1213,251 @@ msgstr "сбой открытия /dev/null"
 msgid "dup2(%d,%d) failed"
 msgstr "dup2(%d,%d) сбой"
 
-#: send-pack.c:272
+#: send-pack.c:295
 msgid "failed to sign the push certificate"
 msgstr "сбой подписания сертификата отправки"
 
-#: send-pack.c:378
+#: send-pack.c:404
 msgid "the receiving end does not support --signed push"
 msgstr "принимающая сторона не поддерживает отправку с опцией --signed"
 
-#: send-pack.c:389
+#: send-pack.c:406
+msgid ""
+"not sending a push certificate since the receiving end does not support "
+"--signed push"
+msgstr "не отправляем сертификат для отправки, так как принимающая сторона не поддерживает отправку с опцией --signed"
+
+#: send-pack.c:418
 msgid "the receiving end does not support --atomic push"
 msgstr "принимающая сторона не поддерживает отправку с опцией --atomic"
 
-#: sequencer.c:172 builtin/merge.c:760 builtin/merge.c:871 builtin/merge.c:973
-#: builtin/merge.c:983
-#, c-format
-msgid "Could not open '%s' for writing"
-msgstr "Не удалось открыть «%s» для записи"
-
-#: sequencer.c:174 builtin/merge.c:344 builtin/merge.c:763 builtin/merge.c:975
-#: builtin/merge.c:988
-#, c-format
-msgid "Could not write to '%s'"
-msgstr "Не удалось записать в «%s»"
-
-#: sequencer.c:195
+#: sequencer.c:183
 msgid ""
 "after resolving the conflicts, mark the corrected paths\n"
 "with 'git add <paths>' or 'git rm <paths>'"
 msgstr "после разрешения конфликтов, пометьте исправленные пути\nс помощью «git add <пути>» или «git rm <пути>»"
 
-#: sequencer.c:198
+#: sequencer.c:186
 msgid ""
 "after resolving the conflicts, mark the corrected paths\n"
 "with 'git add <paths>' or 'git rm <paths>'\n"
 "and commit the result with 'git commit'"
 msgstr "после разрешения конфликтов, пометьте исправленные пути\nс помощью «git add <пути>» или «git rm <пути>»\nи сделайте коммит с помощью «git commit»"
 
-#: sequencer.c:211 sequencer.c:861 sequencer.c:944
+#: sequencer.c:199 sequencer.c:842 sequencer.c:922
 #, c-format
 msgid "Could not write to %s"
 msgstr "Не удалось записать в %s"
 
-#: sequencer.c:214
+#: sequencer.c:202
 #, c-format
 msgid "Error wrapping up %s"
 msgstr "Ошибка оборачивания %s"
 
-#: sequencer.c:229
+#: sequencer.c:217
 msgid "Your local changes would be overwritten by cherry-pick."
 msgstr "Ваши локальные изменение будут перезаписаны отбором лучшего."
 
-#: sequencer.c:231
+#: sequencer.c:219
 msgid "Your local changes would be overwritten by revert."
 msgstr "Ваши локальные изменение будут перезаписаны возвратом коммита."
 
-#: sequencer.c:234
+#: sequencer.c:222
 msgid "Commit your changes or stash them to proceed."
 msgstr "Сделайте коммит или спрячьте ваши изменения для продолжения."
 
 #. TRANSLATORS: %s will be "revert" or "cherry-pick"
-#: sequencer.c:321
+#: sequencer.c:309
 #, c-format
 msgid "%s: Unable to write new index file"
 msgstr "%s: Не удалось записать файл индекса"
 
-#: sequencer.c:339
+#: sequencer.c:327
 msgid "Could not resolve HEAD commit\n"
 msgstr "Не удалось определить HEAD коммит\n"
 
-#: sequencer.c:359
+#: sequencer.c:347
 msgid "Unable to update cache tree\n"
 msgstr "Не удалось обновить дерево кэша\n"
 
-#: sequencer.c:411
+#: sequencer.c:399
 #, c-format
 msgid "Could not parse commit %s\n"
 msgstr "Не удалось разобрать коммит %s\n"
 
-#: sequencer.c:416
+#: sequencer.c:404
 #, c-format
 msgid "Could not parse parent commit %s\n"
 msgstr "Не удалось разобрать родительскую коммит %s\n"
 
-#: sequencer.c:482
+#: sequencer.c:469
 msgid "Your index file is unmerged."
 msgstr "Ваш файл индекса не слит."
 
-#: sequencer.c:501
+#: sequencer.c:488
 #, c-format
 msgid "Commit %s is a merge but no -m option was given."
 msgstr "Коммит %s — это коммит-слияние, но опция -m не указана."
 
-#: sequencer.c:509
+#: sequencer.c:496
 #, c-format
 msgid "Commit %s does not have parent %d"
 msgstr "У коммита %s нет предка %d"
 
-#: sequencer.c:513
+#: sequencer.c:500
 #, c-format
 msgid "Mainline was specified but commit %s is not a merge."
 msgstr "Основная ветка указана, но коммит %s не является слиянием."
 
 #. TRANSLATORS: The first %s will be "revert" or
 #. "cherry-pick", the second %s a SHA1
-#: sequencer.c:526
+#: sequencer.c:513
 #, c-format
 msgid "%s: cannot parse parent commit %s"
 msgstr "%s: не удалось разобрать родительский коммит для %s"
 
-#: sequencer.c:530
+#: sequencer.c:517
 #, c-format
 msgid "Cannot get commit message for %s"
 msgstr "Не удалось получить сообщение коммита для %s"
 
-#: sequencer.c:616
+#: sequencer.c:603
 #, c-format
 msgid "could not revert %s... %s"
 msgstr "не удалось возвратить коммит %s… %s"
 
-#: sequencer.c:617
+#: sequencer.c:604
 #, c-format
 msgid "could not apply %s... %s"
 msgstr "не удалось применить коммит %s… %s"
 
-#: sequencer.c:653
+#: sequencer.c:639
 msgid "empty commit set passed"
 msgstr "передан пустой набор коммитов"
 
-#: sequencer.c:661
+#: sequencer.c:647
 #, c-format
 msgid "git %s: failed to read the index"
 msgstr "git %s: сбой чтения индекса"
 
-#: sequencer.c:665
+#: sequencer.c:651
 #, c-format
 msgid "git %s: failed to refresh the index"
 msgstr "git %s: сбой обновления индекса"
 
-#: sequencer.c:725
+#: sequencer.c:711
 #, c-format
 msgid "Cannot %s during a %s"
 msgstr "Не удалось %s во время %s"
 
-#: sequencer.c:747
+#: sequencer.c:733
 #, c-format
 msgid "Could not parse line %d."
 msgstr "Не удалось разобрать строку %d."
 
-#: sequencer.c:752
+#: sequencer.c:738
 msgid "No commits parsed."
 msgstr "Коммиты не разобраны."
 
-#: sequencer.c:765
+#: sequencer.c:750
 #, c-format
 msgid "Could not open %s"
 msgstr "Не удалось открыть %s"
 
-#: sequencer.c:769
+#: sequencer.c:754
 #, c-format
 msgid "Could not read %s."
 msgstr "Не удалось прочитать %s."
 
-#: sequencer.c:776
+#: sequencer.c:761
 #, c-format
 msgid "Unusable instruction sheet: %s"
 msgstr "Непригодная для использования карта с инструкциями: %s"
 
-#: sequencer.c:806
+#: sequencer.c:791
 #, c-format
 msgid "Invalid key: %s"
 msgstr "Недействительный ключ: %s"
 
-#: sequencer.c:809
+#: sequencer.c:794 builtin/pull.c:47 builtin/pull.c:49
 #, c-format
 msgid "Invalid value for %s: %s"
 msgstr "Неправильное значение %s: %s"
 
-#: sequencer.c:821
+#: sequencer.c:804
 #, c-format
 msgid "Malformed options sheet: %s"
 msgstr "Испорченная карта с опциями: %s"
 
-#: sequencer.c:842
+#: sequencer.c:823
 msgid "a cherry-pick or revert is already in progress"
 msgstr "отбор лучшего или возврат коммита уже выполняется"
 
-#: sequencer.c:843
+#: sequencer.c:824
 msgid "try \"git cherry-pick (--continue | --quit | --abort)\""
 msgstr "попробуйте «git cherry-pick (--continue | --quit | --abort)»"
 
-#: sequencer.c:847
+#: sequencer.c:828
 #, c-format
 msgid "Could not create sequencer directory %s"
 msgstr "Не удалось создать каталог для указателя следования коммитов %s"
 
-#: sequencer.c:863 sequencer.c:948
+#: sequencer.c:844 sequencer.c:926
 #, c-format
 msgid "Error wrapping up %s."
 msgstr "Ошибка оборачивания %s."
 
-#: sequencer.c:882 sequencer.c:1018
+#: sequencer.c:863 sequencer.c:996
 msgid "no cherry-pick or revert in progress"
 msgstr "отбор лучшего или возврат коммита не выполняется"
 
-#: sequencer.c:884
+#: sequencer.c:865
 msgid "cannot resolve HEAD"
 msgstr "не удалось определить HEAD"
 
-#: sequencer.c:886
+#: sequencer.c:867
 msgid "cannot abort from a branch yet to be born"
 msgstr "нельзя отменить изменения с ветки, которая еще не создана"
 
-#: sequencer.c:908 builtin/apply.c:4291
+#: sequencer.c:887 builtin/apply.c:4291
 #, c-format
 msgid "cannot open %s: %s"
 msgstr "не удалось открыть %s: %s"
 
-#: sequencer.c:911
+#: sequencer.c:890
 #, c-format
 msgid "cannot read %s: %s"
 msgstr "не удалось прочитать %s: %s"
 
-#: sequencer.c:912
+#: sequencer.c:891
 msgid "unexpected end of file"
 msgstr "неожиданный конец файла"
 
-#: sequencer.c:918
+#: sequencer.c:897
 #, c-format
 msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
 msgstr "сохраненный файл с HEAD перед отбором лучшего «%s» поврежден"
 
-#: sequencer.c:941
+#: sequencer.c:919
 #, c-format
 msgid "Could not format %s."
 msgstr "Не удалось отформатировать %s."
 
-#: sequencer.c:1086
+#: sequencer.c:1064
 #, c-format
 msgid "%s: can't cherry-pick a %s"
 msgstr "%s: не удалось отобрать %s"
 
-#: sequencer.c:1089
+#: sequencer.c:1067
 #, c-format
 msgid "%s: bad revision"
 msgstr "%s: плохая редакция"
 
-#: sequencer.c:1123
+#: sequencer.c:1101
 msgid "Can't revert as initial commit"
 msgstr "Нельзя возвратить изначальный коммит"
 
-#: sequencer.c:1124
+#: sequencer.c:1102
 msgid "Can't cherry-pick into empty head"
 msgstr "Нельзя отобрать лучшее в пустой HEAD"
 
@@ -1441,30 +1479,30 @@ msgid ""
 "running \"git config advice.objectNameWarning false\""
 msgstr "Обычно Git не создает ссылки, оканчивающиеся на 40 шестнадцатеричных\nсимволов, потому, что они будут игнорироваться, когда вы просто\nукажете это 40-символьное шестнадцатеричное число. Такие ссылки\nмогли быть созданы по ошибке. Например, с помощью:\n\n  git checkout -b $br $(git rev-parse …)\n\n, если «$br» оказался пустым, то ссылка с 40-символьным\nшестнадцатеричным числом будет создана. Пожалуйста, просмотрите эти\nссылки и, возможно, удалите их. Вы можете отключить это сообщение\nзапустив «git config advice.objectNameWarning false»"
 
-#: submodule.c:64 submodule.c:98
+#: submodule.c:61 submodule.c:95
 msgid "Cannot change unmerged .gitmodules, resolve merge conflicts first"
 msgstr "Не удалось изменить не слитый .gitmodules, сначала разрешите конфликты"
 
-#: submodule.c:68 submodule.c:102
+#: submodule.c:65 submodule.c:99
 #, c-format
 msgid "Could not find section in .gitmodules where path=%s"
 msgstr "Не удалось найти раздел в .gitmodules, где путь равен %s"
 
-#: submodule.c:76
+#: submodule.c:73
 #, c-format
 msgid "Could not update .gitmodules entry %s"
 msgstr " Не удалось обновить .gitmodules запись %s"
 
-#: submodule.c:109
+#: submodule.c:106
 #, c-format
 msgid "Could not remove .gitmodules entry for %s"
 msgstr "Не удалось удалить запись в .gitmodules для %s"
 
-#: submodule.c:120
+#: submodule.c:117
 msgid "staging updated .gitmodules failed"
 msgstr "сбой индексирования обновленного .gitmodules"
 
-#: submodule.c:1115
+#: submodule.c:1045
 #, c-format
 msgid "Could not set core.worktree in %s"
 msgstr "Не удалось установить core.worktree в %s"
@@ -1494,6 +1532,11 @@ msgstr "не удалось прочитать входной файл «%s»"
 msgid "could not read from stdin"
 msgstr "не удалось прочитать из стандартного ввода"
 
+#: transport-helper.c:1025
+#, c-format
+msgid "Could not read ref %s"
+msgstr "Не удалось прочитать ссылку %s"
+
 #: unpack-trees.c:203
 msgid "Checking out files"
 msgstr "Распаковка файлов"
@@ -1527,368 +1570,423 @@ msgstr "неправильный номер порта"
 msgid "invalid '..' path segment"
 msgstr "неправильная часть пути «..»"
 
-#: wrapper.c:523
+#: wrapper.c:219 wrapper.c:362
+#, c-format
+msgid "could not open '%s' for reading and writing"
+msgstr "не удалось открыть «%s» для чтения и записи"
+
+#: wrapper.c:221 wrapper.c:364
+#, c-format
+msgid "could not open '%s' for writing"
+msgstr "не удалось открыть «%s» для записи"
+
+#: wrapper.c:223 wrapper.c:366 builtin/am.c:337 builtin/commit.c:1688
+#: builtin/merge.c:1076 builtin/pull.c:380
+#, c-format
+msgid "could not open '%s' for reading"
+msgstr "не удалось открыть «%s» для чтения"
+
+#: wrapper.c:579
 #, c-format
 msgid "unable to access '%s': %s"
 msgstr "«%s» недоступно: %s"
 
-#: wrapper.c:544
+#: wrapper.c:600
 #, c-format
 msgid "unable to access '%s'"
 msgstr "«%s» недоступно"
 
-#: wrapper.c:555
+#: wrapper.c:611
 #, c-format
 msgid "unable to look up current user in the passwd file: %s"
 msgstr "не удалось запросить текущего пользователя в файле passwd: %s"
 
-#: wrapper.c:556
+#: wrapper.c:612
 msgid "no such user"
 msgstr "нет такого пользователя"
 
-#: wrapper.c:564
+#: wrapper.c:620
 msgid "unable to get current working directory"
 msgstr "не удалось получить текущий рабочий каталог"
 
-#: wrapper.c:575
+#: wrapper.c:631
 #, c-format
 msgid "could not open %s for writing"
 msgstr "не удалось открыть «%s» для записи"
 
-#: wrapper.c:587
+#: wrapper.c:642 builtin/am.c:424
 #, c-format
 msgid "could not write to %s"
 msgstr "не удалось записать в %s"
 
-#: wrapper.c:593
+#: wrapper.c:648
 #, c-format
 msgid "could not close %s"
 msgstr "не удалось закрыть %s"
 
-#: wt-status.c:150
+#: wt-status.c:149
 msgid "Unmerged paths:"
 msgstr "Не слитые пути:"
 
-#: wt-status.c:177 wt-status.c:204
+#: wt-status.c:176 wt-status.c:203
 #, c-format
 msgid "  (use \"git reset %s <file>...\" to unstage)"
 msgstr "  (используйте «git reset %s <файл>…», чтобы убрать из индекса)"
 
-#: wt-status.c:179 wt-status.c:206
+#: wt-status.c:178 wt-status.c:205
 msgid "  (use \"git rm --cached <file>...\" to unstage)"
 msgstr "  (используйте «git rm --cached <файл>…», чтобы убрать из индекса)"
 
-#: wt-status.c:183
+#: wt-status.c:182
 msgid "  (use \"git add <file>...\" to mark resolution)"
 msgstr "  (используйте «git add <файл>…», чтобы пометить разрешение конфликта)"
 
-#: wt-status.c:185 wt-status.c:189
+#: wt-status.c:184 wt-status.c:188
 msgid "  (use \"git add/rm <file>...\" as appropriate to mark resolution)"
 msgstr "  (используйте «git add/rm <файл>…», чтобы пометить выбранное разрешение конфликта)"
 
-#: wt-status.c:187
+#: wt-status.c:186
 msgid "  (use \"git rm <file>...\" to mark resolution)"
 msgstr "  (используйте «git rm <файл>…», чтобы пометить разрешение конфликта)"
 
-#: wt-status.c:198 wt-status.c:881
+#: wt-status.c:197 wt-status.c:880
 msgid "Changes to be committed:"
 msgstr "Изменения, которые будут включены в коммит:"
 
-#: wt-status.c:216 wt-status.c:890
+#: wt-status.c:215 wt-status.c:889
 msgid "Changes not staged for commit:"
 msgstr "Изменения, которые не в индексе для коммита:"
 
-#: wt-status.c:220
+#: wt-status.c:219
 msgid "  (use \"git add <file>...\" to update what will be committed)"
 msgstr "  (используйте «git add <файл>…», чтобы добавить файл в индекс)"
 
-#: wt-status.c:222
+#: wt-status.c:221
 msgid "  (use \"git add/rm <file>...\" to update what will be committed)"
 msgstr "  (используйте «git add/rm <файл>…», чтобы добавить или удалить файл из индекса)"
 
-#: wt-status.c:223
+#: wt-status.c:222
 msgid ""
 "  (use \"git checkout -- <file>...\" to discard changes in working "
 "directory)"
 msgstr "  (используйте «git checkout -- <файл>…», чтобы отменить изменения\n   в рабочем каталоге)"
 
-#: wt-status.c:225
+#: wt-status.c:224
 msgid "  (commit or discard the untracked or modified content in submodules)"
 msgstr "  (сделайте коммит или отмените изменения в неотслеживаемом или измененном содержимом в подмодулях)"
 
-#: wt-status.c:237
+#: wt-status.c:236
 #, c-format
 msgid "  (use \"git %s <file>...\" to include in what will be committed)"
 msgstr "  (используйте «git %s <файл>…», чтобы добавить в то, что будет включено в коммит)"
 
-#: wt-status.c:252
+#: wt-status.c:251
 msgid "both deleted:"
 msgstr "оба удалены:"
 
-#: wt-status.c:254
+#: wt-status.c:253
 msgid "added by us:"
 msgstr "добавлено нами:"
 
-#: wt-status.c:256
+#: wt-status.c:255
 msgid "deleted by them:"
 msgstr "удалено ими:"
 
-#: wt-status.c:258
+#: wt-status.c:257
 msgid "added by them:"
 msgstr "добавлено ими:"
 
-#: wt-status.c:260
+#: wt-status.c:259
 msgid "deleted by us:"
 msgstr "удалено нами:"
 
-#: wt-status.c:262
+#: wt-status.c:261
 msgid "both added:"
 msgstr "оба добавлены:"
 
-#: wt-status.c:264
+#: wt-status.c:263
 msgid "both modified:"
 msgstr "оба измены:"
 
-#: wt-status.c:266
+#: wt-status.c:265
 #, c-format
 msgid "bug: unhandled unmerged status %x"
 msgstr "ошибка: необработанный статус не слитых изменений %x"
 
-#: wt-status.c:274
+#: wt-status.c:273
 msgid "new file:"
 msgstr "новый файл:"
 
-#: wt-status.c:276
+#: wt-status.c:275
 msgid "copied:"
 msgstr "скопировано:"
 
-#: wt-status.c:278
+#: wt-status.c:277
 msgid "deleted:"
 msgstr "удалено:"
 
-#: wt-status.c:280
+#: wt-status.c:279
 msgid "modified:"
 msgstr "изменено:"
 
-#: wt-status.c:282
+#: wt-status.c:281
 msgid "renamed:"
 msgstr "переименовано:"
 
-#: wt-status.c:284
+#: wt-status.c:283
 msgid "typechange:"
 msgstr "изменен тип:"
 
-#: wt-status.c:286
+#: wt-status.c:285
 msgid "unknown:"
 msgstr "неизвестно:"
 
-#: wt-status.c:288
+#: wt-status.c:287
 msgid "unmerged:"
 msgstr "не слитые:"
 
-#: wt-status.c:370
+#: wt-status.c:369
 msgid "new commits, "
 msgstr "новые коммиты, "
 
-#: wt-status.c:372
+#: wt-status.c:371
 msgid "modified content, "
 msgstr "изменено содержимое, "
 
-#: wt-status.c:374
+#: wt-status.c:373
 msgid "untracked content, "
 msgstr "неотслеживаемое содержимое, "
 
-#: wt-status.c:391
+#: wt-status.c:390
 #, c-format
 msgid "bug: unhandled diff status %c"
 msgstr "ошибка: необработанный статус изменений %c"
 
-#: wt-status.c:755
+#: wt-status.c:754
 msgid "Submodules changed but not updated:"
 msgstr "Измененные, но не обновленные подмодули:"
 
-#: wt-status.c:757
+#: wt-status.c:756
 msgid "Submodule changes to be committed:"
 msgstr "Изменения в подмодулях, которые будут закоммичены:"
 
-#: wt-status.c:838
+#: wt-status.c:837
 msgid ""
 "Do not touch the line above.\n"
 "Everything below will be removed."
 msgstr "Не трогайте строку выше этой.\nВсё, что ниже — будет удалено."
 
-#: wt-status.c:949
+#: wt-status.c:948
 msgid "You have unmerged paths."
 msgstr "У вас есть не слитые пути."
 
-#: wt-status.c:952
+#: wt-status.c:951
 msgid "  (fix conflicts and run \"git commit\")"
 msgstr "  (разрешите конфликты, затем запустите «git commit»)"
 
-#: wt-status.c:955
+#: wt-status.c:954
 msgid "All conflicts fixed but you are still merging."
 msgstr "Все конфликты исправлены, но вы все еще в процессе слияния."
 
-#: wt-status.c:958
+#: wt-status.c:957
 msgid "  (use \"git commit\" to conclude merge)"
 msgstr "  (используйте «git commit», чтобы завершить слияние)"
 
-#: wt-status.c:968
+#: wt-status.c:967
 msgid "You are in the middle of an am session."
 msgstr "Вы в процессе сессии am."
 
-#: wt-status.c:971
+#: wt-status.c:970
 msgid "The current patch is empty."
 msgstr "Текущий патч пустой."
 
-#: wt-status.c:975
+#: wt-status.c:974
 msgid "  (fix conflicts and then run \"git am --continue\")"
 msgstr "  (разрешите конфликты, затем запустите «git am --continue»)"
 
-#: wt-status.c:977
+#: wt-status.c:976
 msgid "  (use \"git am --skip\" to skip this patch)"
 msgstr "  (используйте «git am --skip», чтобы пропустить этот патч)"
 
-#: wt-status.c:979
+#: wt-status.c:978
 msgid "  (use \"git am --abort\" to restore the original branch)"
 msgstr "  (используйте «git am --abort», чтобы восстановить оригинальную ветку)"
 
-#: wt-status.c:1039 wt-status.c:1056
+#: wt-status.c:1105
+msgid "No commands done."
+msgstr "Команды не выполнены."
+
+#: wt-status.c:1108
+#, c-format
+msgid "Last command done (%d command done):"
+msgid_plural "Last commands done (%d commands done):"
+msgstr[0] "Последняя команда выполнена (%d команд выполнено):"
+msgstr[1] "Последняя команда выполнена (%d команд выполнено):"
+msgstr[2] "Последняя команда выполнена (%d команд выполнено):"
+msgstr[3] "Последняя команда выполнена (%d команд выполнено):"
+
+#: wt-status.c:1119
+#, c-format
+msgid "  (see more in file %s)"
+msgstr "  (смотрите дополнительно в файле %s)"
+
+#: wt-status.c:1124
+msgid "No commands remaining."
+msgstr "Команд больше не осталось."
+
+#: wt-status.c:1127
+#, c-format
+msgid "Next command to do (%d remaining command):"
+msgid_plural "Next commands to do (%d remaining commands):"
+msgstr[0] "Следующая команда для выполнения (%d команда осталась):"
+msgstr[1] "Следующая команда для выполнения (%d команды осталось):"
+msgstr[2] "Следующая команда для выполнения (%d команд осталось):"
+msgstr[3] "Следующая команда для выполнения (%d команд осталось):"
+
+#: wt-status.c:1135
+msgid "  (use \"git rebase --edit-todo\" to view and edit)"
+msgstr "  (используйте «git rebase --edit-todo», чтобы просмотреть и изменить)"
+
+#: wt-status.c:1148
 #, c-format
 msgid "You are currently rebasing branch '%s' on '%s'."
 msgstr "Вы сейчас перемещаете ветку «%s» над «%s»."
 
-#: wt-status.c:1044 wt-status.c:1061
+#: wt-status.c:1153
 msgid "You are currently rebasing."
 msgstr "Вы сейчас перемещаете ветку."
 
-#: wt-status.c:1047
+#: wt-status.c:1167
 msgid "  (fix conflicts and then run \"git rebase --continue\")"
 msgstr "  (разрешите конфликты, затем запустите «git rebase --continue»)"
 
-#: wt-status.c:1049
+#: wt-status.c:1169
 msgid "  (use \"git rebase --skip\" to skip this patch)"
 msgstr "  (используйте «git rebase --skip», чтобы пропустить этот патч)"
 
-#: wt-status.c:1051
+#: wt-status.c:1171
 msgid "  (use \"git rebase --abort\" to check out the original branch)"
 msgstr "  (используйте «git rebase --abort», чтобы перейти на оригинальную ветку)"
 
-#: wt-status.c:1064
+#: wt-status.c:1177
 msgid "  (all conflicts fixed: run \"git rebase --continue\")"
 msgstr "  (все конфликты разрешены: запустите «git rebase --continue»)"
 
-#: wt-status.c:1068
+#: wt-status.c:1181
 #, c-format
 msgid ""
 "You are currently splitting a commit while rebasing branch '%s' on '%s'."
 msgstr "Вы сейчас разделяете коммит при перемещении ветки  «%s» над «%s»."
 
-#: wt-status.c:1073
+#: wt-status.c:1186
 msgid "You are currently splitting a commit during a rebase."
 msgstr "Вы сейчас разделяете коммит при перемещении ветки."
 
-#: wt-status.c:1076
+#: wt-status.c:1189
 msgid "  (Once your working directory is clean, run \"git rebase --continue\")"
 msgstr "(Как только ваш рабочий каталог будет чистый, запустите «git rebase --continue»)"
 
-#: wt-status.c:1080
+#: wt-status.c:1193
 #, c-format
 msgid "You are currently editing a commit while rebasing branch '%s' on '%s'."
 msgstr "Вы сейчас редактируете коммит при перемещении ветки  «%s» над «%s»."
 
-#: wt-status.c:1085
+#: wt-status.c:1198
 msgid "You are currently editing a commit during a rebase."
 msgstr "Вы сейчас редактируете коммит при перемещении ветки."
 
-#: wt-status.c:1088
+#: wt-status.c:1201
 msgid "  (use \"git commit --amend\" to amend the current commit)"
 msgstr "  (используйте «git commit --amend», чтобы исправить текущий коммит)"
 
-#: wt-status.c:1090
+#: wt-status.c:1203
 msgid "  (use \"git rebase --continue\" once you are satisfied with your changes)"
 msgstr "  (используйте «git rebase --continue», когда будете довольны изменениями)"
 
-#: wt-status.c:1100
+#: wt-status.c:1213
 #, c-format
 msgid "You are currently cherry-picking commit %s."
 msgstr "Вы сейчас отбираете лучший коммит %s."
 
-#: wt-status.c:1105
+#: wt-status.c:1218
 msgid "  (fix conflicts and run \"git cherry-pick --continue\")"
 msgstr "  (разрешите конфликты, затем запустите «git cherry-pick --continue»)"
 
-#: wt-status.c:1108
+#: wt-status.c:1221
 msgid "  (all conflicts fixed: run \"git cherry-pick --continue\")"
 msgstr "  (все конфликты разрешены: запустите «git cherry-pick --continue»)"
 
-#: wt-status.c:1110
+#: wt-status.c:1223
 msgid "  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"
 msgstr "  (используйте «git cherry-pick --abort», чтобы отменить операцию отбора лучшего)"
 
-#: wt-status.c:1119
+#: wt-status.c:1232
 #, c-format
 msgid "You are currently reverting commit %s."
 msgstr "Вы сейчас возвращаете коммит %s."
 
-#: wt-status.c:1124
+#: wt-status.c:1237
 msgid "  (fix conflicts and run \"git revert --continue\")"
 msgstr "  (разрешите конфликты, затем запустите «git revert --continue»)"
 
-#: wt-status.c:1127
+#: wt-status.c:1240
 msgid "  (all conflicts fixed: run \"git revert --continue\")"
 msgstr "  (все конфликты разрешены: запустите «git revert --continue»)"
 
-#: wt-status.c:1129
+#: wt-status.c:1242
 msgid "  (use \"git revert --abort\" to cancel the revert operation)"
 msgstr "  (используйте «git revert --abort», чтобы отменить операцию возврата)"
 
-#: wt-status.c:1140
+#: wt-status.c:1253
 #, c-format
 msgid "You are currently bisecting, started from branch '%s'."
 msgstr "Вы сейчас в процессе двоичного поиска, начатого с ветки «%s»."
 
-#: wt-status.c:1144
+#: wt-status.c:1257
 msgid "You are currently bisecting."
 msgstr "Вы сейчас в процессе двоичного поиска."
 
-#: wt-status.c:1147
+#: wt-status.c:1260
 msgid "  (use \"git bisect reset\" to get back to the original branch)"
 msgstr "  (используйте «git bisect reset», чтобы вернуться на исходную ветку)"
 
-#: wt-status.c:1324
+#: wt-status.c:1437
 msgid "On branch "
 msgstr "На ветке "
 
-#: wt-status.c:1331
+#: wt-status.c:1445
+msgid "interactive rebase in progress; onto "
+msgstr "интерактивное перемещение в процессе; над "
+
+#: wt-status.c:1447
 msgid "rebase in progress; onto "
 msgstr "перемещение в процессе; над "
 
-#: wt-status.c:1336
+#: wt-status.c:1452
 msgid "HEAD detached at "
 msgstr "HEAD отделен на "
 
-#: wt-status.c:1338
+#: wt-status.c:1454
 msgid "HEAD detached from "
 msgstr "HEAD отделен начиная с "
 
-#: wt-status.c:1341
+#: wt-status.c:1457
 msgid "Not currently on any branch."
 msgstr "Сейчас ни на одной из веток"
 
-#: wt-status.c:1358
+#: wt-status.c:1474
 msgid "Initial commit"
 msgstr "Начальный коммит"
 
-#: wt-status.c:1372
+#: wt-status.c:1488
 msgid "Untracked files"
 msgstr "Неотслеживаемые файлы"
 
-#: wt-status.c:1374
+#: wt-status.c:1490
 msgid "Ignored files"
 msgstr "Игнорируемые файлы"
 
-#: wt-status.c:1378
+#: wt-status.c:1494
 #, c-format
 msgid ""
 "It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
@@ -1896,78 +1994,78 @@ msgid ""
 "new files yourself (see 'git help status')."
 msgstr "%.2f секунды занял вывод списка неотслеживаемых файлов. «status -uno» возможно может ускорить это, но будьте внимательны, и не забудьте добавить новые файлы вручную (смотрите «git help status» для подробностей)."
 
-#: wt-status.c:1384
+#: wt-status.c:1500
 #, c-format
 msgid "Untracked files not listed%s"
 msgstr "Неотслеживаемые файлы не показаны%s"
 
-#: wt-status.c:1386
+#: wt-status.c:1502
 msgid " (use -u option to show untracked files)"
 msgstr "(используйте опцию «-u», чтобы показать неотслеживаемые файлы)"
 
-#: wt-status.c:1392
+#: wt-status.c:1508
 msgid "No changes"
 msgstr "Нет изменений"
 
-#: wt-status.c:1397
+#: wt-status.c:1513
 #, c-format
 msgid "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"
 msgstr "нет изменений добавленных для коммита\n(используйте «git add» и/или «git commit -a»)\n"
 
-#: wt-status.c:1400
+#: wt-status.c:1516
 #, c-format
 msgid "no changes added to commit\n"
 msgstr "нет изменений добавленных для коммита\n"
 
-#: wt-status.c:1403
+#: wt-status.c:1519
 #, c-format
 msgid ""
 "nothing added to commit but untracked files present (use \"git add\" to "
 "track)\n"
 msgstr "ничего не добавлено в коммит, но есть неотслеживаемые файлы (используйте \"git add\", чтобы отслеживать их)\n"
 
-#: wt-status.c:1406
+#: wt-status.c:1522
 #, c-format
 msgid "nothing added to commit but untracked files present\n"
 msgstr "ничего не добавлено в коммит, но есть неотслеживаемые файлы\n"
 
-#: wt-status.c:1409
+#: wt-status.c:1525
 #, c-format
 msgid "nothing to commit (create/copy files and use \"git add\" to track)\n"
 msgstr "нечего коммитить (создайте/скопируйте файлы, затем запустите «git add», чтобы отслеживать их)\n"
 
-#: wt-status.c:1412 wt-status.c:1417
+#: wt-status.c:1528 wt-status.c:1533
 #, c-format
 msgid "nothing to commit\n"
 msgstr "нечего коммитить\n"
 
-#: wt-status.c:1415
+#: wt-status.c:1531
 #, c-format
 msgid "nothing to commit (use -u to show untracked files)\n"
 msgstr "нечего коммитить (используйте опцию «-u», чтобы показать неотслеживаемые файлы)\n"
 
-#: wt-status.c:1419
+#: wt-status.c:1535
 #, c-format
 msgid "nothing to commit, working directory clean\n"
 msgstr "нечего коммитить, нет изменений в рабочем каталоге\n"
 
-#: wt-status.c:1528
+#: wt-status.c:1644
 msgid "HEAD (no branch)"
 msgstr "HEAD (нет ветки)"
 
-#: wt-status.c:1534
+#: wt-status.c:1650
 msgid "Initial commit on "
 msgstr "Начальный коммит на "
 
-#: wt-status.c:1561
+#: wt-status.c:1677
 msgid "gone"
 msgstr "исчез"
 
-#: wt-status.c:1563 wt-status.c:1571
+#: wt-status.c:1679 wt-status.c:1687
 msgid "behind "
 msgstr "позади"
 
-#: compat/precompose_utf8.c:55 builtin/clone.c:345
+#: compat/precompose_utf8.c:55 builtin/clone.c:403
 #, c-format
 msgid "failed to unlink '%s'"
 msgstr "сбой отсоединения «%s»"
@@ -1994,7 +2092,7 @@ msgstr "удалить «%s»\n"
 msgid "Unstaged changes after refreshing the index:"
 msgstr "Непроиндексированные изменения после обновления индекса:"
 
-#: builtin/add.c:194 builtin/rev-parse.c:796
+#: builtin/add.c:194 builtin/rev-parse.c:799
 msgid "Could not read the index"
 msgstr "Не удалось прочитать индекс"
 
@@ -2029,15 +2127,15 @@ msgstr "Не удалось применить «%s»"
 msgid "The following paths are ignored by one of your .gitignore files:\n"
 msgstr "Следующие пути игнорируются одним из ваших файлов .gitignore:\n"
 
-#: builtin/add.c:249 builtin/clean.c:874 builtin/fetch.c:107 builtin/mv.c:110
-#: builtin/prune-packed.c:55 builtin/push.c:508 builtin/remote.c:1369
-#: builtin/rm.c:268
+#: builtin/add.c:249 builtin/clean.c:896 builtin/fetch.c:108 builtin/mv.c:110
+#: builtin/prune-packed.c:55 builtin/pull.c:182 builtin/push.c:545
+#: builtin/remote.c:1339 builtin/rm.c:268 builtin/send-pack.c:162
 msgid "dry run"
 msgstr "пробный запуск"
 
 #: builtin/add.c:250 builtin/apply.c:4580 builtin/check-ignore.c:19
-#: builtin/commit.c:1322 builtin/count-objects.c:63 builtin/fsck.c:616
-#: builtin/log.c:1617 builtin/mv.c:109 builtin/read-tree.c:114
+#: builtin/commit.c:1321 builtin/count-objects.c:63 builtin/fsck.c:636
+#: builtin/log.c:1641 builtin/mv.c:109 builtin/read-tree.c:114
 msgid "be verbose"
 msgstr "быть многословнее"
 
@@ -2045,7 +2143,7 @@ msgstr "быть многословнее"
 msgid "interactive picking"
 msgstr "интерактивный выбор"
 
-#: builtin/add.c:253 builtin/checkout.c:1221 builtin/reset.c:286
+#: builtin/add.c:253 builtin/checkout.c:1152 builtin/reset.c:286
 msgid "select hunks interactively"
 msgstr "интерактивный выбор блоков"
 
@@ -2112,15 +2210,403 @@ msgstr "Ничего не указано, ничего не добавлено.\
 msgid "Maybe you wanted to say 'git add .'?\n"
 msgstr "Возможно, вы имели в виду «git add .»?\n"
 
-#: builtin/add.c:364 builtin/check-ignore.c:172 builtin/clean.c:918
-#: builtin/commit.c:335 builtin/mv.c:130 builtin/reset.c:235 builtin/rm.c:298
+#: builtin/add.c:364 builtin/check-ignore.c:172 builtin/clean.c:940
+#: builtin/commit.c:336 builtin/mv.c:130 builtin/reset.c:235 builtin/rm.c:298
 msgid "index file corrupt"
 msgstr "файл индекса поврежден"
 
-#: builtin/add.c:447 builtin/apply.c:4678 builtin/mv.c:279 builtin/rm.c:430
+#: builtin/add.c:445 builtin/apply.c:4678 builtin/mv.c:279 builtin/rm.c:430
 msgid "Unable to write new index file"
 msgstr "Не удалось записать новый файл индекса"
 
+#: builtin/am.c:41
+#, c-format
+msgid "could not stat %s"
+msgstr "не удалось выполнить stat для %s"
+
+#: builtin/am.c:270 builtin/am.c:1345 builtin/commit.c:737
+#: builtin/merge.c:1079
+#, c-format
+msgid "could not read '%s'"
+msgstr "не удалось прочитать «%s»"
+
+#: builtin/am.c:444
+msgid "could not parse author script"
+msgstr "не удалось разобрать сценарий авторства"
+
+#: builtin/am.c:521
+#, c-format
+msgid "'%s' was deleted by the applypatch-msg hook"
+msgstr "«%s» был удален перехватчиком applypatch-msg"
+
+#: builtin/am.c:562 builtin/notes.c:300
+#, c-format
+msgid "Malformed input line: '%s'."
+msgstr "Плохая строка ввода: «%s»."
+
+#: builtin/am.c:599 builtin/notes.c:315
+#, c-format
+msgid "Failed to copy notes from '%s' to '%s'"
+msgstr "Не удалось скопировать заметку из «%s» в «%s»"
+
+#: builtin/am.c:625
+msgid "fseek failed"
+msgstr "сбой при выполнении fseek"
+
+#: builtin/am.c:786 builtin/am.c:874
+#, c-format
+msgid "could not open '%s' for reading: %s"
+msgstr "не удалось открыть «%s» для чтения: %s"
+
+#: builtin/am.c:793
+#, c-format
+msgid "could not open '%s' for writing: %s"
+msgstr "не удалось открыть «%s» для записи: %s"
+
+#: builtin/am.c:802
+#, c-format
+msgid "could not parse patch '%s'"
+msgstr "не удалось разобрать патч «%s»"
+
+#: builtin/am.c:867
+msgid "Only one StGIT patch series can be applied at once"
+msgstr "Только серия патчей StGIT может быть применена за раз"
+
+#: builtin/am.c:915
+msgid "invalid timestamp"
+msgstr "недопустимая метка даты/времени"
+
+#: builtin/am.c:918 builtin/am.c:926
+msgid "invalid Date line"
+msgstr "недопустимая строка даты"
+
+#: builtin/am.c:923
+msgid "invalid timezone offset"
+msgstr "недопустимое смещение часового пояса"
+
+#: builtin/am.c:1010
+msgid "Patch format detection failed."
+msgstr "Сбой определения формата патча."
+
+#: builtin/am.c:1015 builtin/clone.c:368
+#, c-format
+msgid "failed to create directory '%s'"
+msgstr "не удалось создать каталог «%s»"
+
+#: builtin/am.c:1019
+msgid "Failed to split patches."
+msgstr "Не удалось разделить патчи на части."
+
+#: builtin/am.c:1151 builtin/commit.c:362
+msgid "unable to write index file"
+msgstr "не удалось записать индекс"
+
+#: builtin/am.c:1202
+#, c-format
+msgid "When you have resolved this problem, run \"%s --continue\"."
+msgstr "Когда вы устраните эту проблему, запустите «%s --continue»."
+
+#: builtin/am.c:1203
+#, c-format
+msgid "If you prefer to skip this patch, run \"%s --skip\" instead."
+msgstr "Если вы хотите пропустить этот патч, то запустите «%s --skip»."
+
+#: builtin/am.c:1204
+#, c-format
+msgid "To restore the original branch and stop patching, run \"%s --abort\"."
+msgstr "Чтобы вернуться на предыдущую ветку и остановить применение изменений, запустите «%s --abort»."
+
+#: builtin/am.c:1339
+msgid "Patch is empty. Was it split wrong?"
+msgstr "Патч пуст. Возможно, он был неправильно разделён?"
+
+#: builtin/am.c:1413 builtin/log.c:1345
+#, c-format
+msgid "invalid ident line: %s"
+msgstr "неправильная строка идентификации: %s"
+
+#: builtin/am.c:1440
+#, c-format
+msgid "unable to parse commit %s"
+msgstr "не удалось разобрать коммит %s"
+
+#: builtin/am.c:1614
+msgid "Repository lacks necessary blobs to fall back on 3-way merge."
+msgstr "В репозитории отсутствуют двоичные объекты, необходимые для отката к трехходовому слиянию."
+
+#: builtin/am.c:1616
+msgid "Using index info to reconstruct a base tree..."
+msgstr "Использую индекс для реконструкции базового дерева…"
+
+#: builtin/am.c:1635
+msgid ""
+"Did you hand edit your patch?\n"
+"It does not apply to blobs recorded in its index."
+msgstr "Вы вручную изменяли патч?\nОн не накладывается без ошибок на двоичные объекты, записанные в его заголовке."
+
+#: builtin/am.c:1641
+msgid "Falling back to patching base and 3-way merge..."
+msgstr "Откат к применению изменений к базовому коммиту с помощью трехходового слияния…"
+
+#: builtin/am.c:1666
+msgid "Failed to merge in the changes."
+msgstr "Не удалось слить изменения."
+
+#: builtin/am.c:1691 builtin/merge.c:632
+msgid "git write-tree failed to write a tree"
+msgstr "git write-tree не удалось записать дерево"
+
+#: builtin/am.c:1698
+msgid "applying to an empty history"
+msgstr "применение к пустой истории"
+
+#: builtin/am.c:1711 builtin/commit.c:1752 builtin/merge.c:829
+#: builtin/merge.c:854
+msgid "failed to write commit object"
+msgstr "сбой записи объекта коммита"
+
+#: builtin/am.c:1743 builtin/am.c:1747
+#, c-format
+msgid "cannot resume: %s does not exist."
+msgstr "нельзя продолжнить: %s не существует "
+
+#: builtin/am.c:1763
+msgid "cannot be interactive without stdin connected to a terminal."
+msgstr "не удалось использовать интерактивное поведение, без stdin подключенного к терминалу."
+
+#: builtin/am.c:1768
+msgid "Commit Body is:"
+msgstr "Тело коммита:"
+
+#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+#. in your translation. The program will only accept English
+#. input at this point.
+#: builtin/am.c:1778
+msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "
+msgstr "Применить? [y] - да/[n] - нет/[e] - редактировать/[v] - просмотреть патч/[a] - применить всё: "
+
+#: builtin/am.c:1828
+#, c-format
+msgid "Dirty index: cannot apply patches (dirty: %s)"
+msgstr "Индекс не пустой: нельзя применять патчи (в индексе: %s)"
+
+#: builtin/am.c:1863 builtin/am.c:1934
+#, c-format
+msgid "Applying: %.*s"
+msgstr "Применение: %.*s"
+
+#: builtin/am.c:1879
+msgid "No changes -- Patch already applied."
+msgstr "Нет изменений — Патч уже применен."
+
+#: builtin/am.c:1887
+#, c-format
+msgid "Patch failed at %s %.*s"
+msgstr "Ошибка применения изменений на %s %.*s"
+
+#: builtin/am.c:1893
+#, c-format
+msgid "The copy of the patch that failed is found in: %s"
+msgstr "Копию изменений, которые не удалось применить, вы можете найти в: %s"
+
+#: builtin/am.c:1937
+msgid ""
+"No changes - did you forget to use 'git add'?\n"
+"If there is nothing left to stage, chances are that something else\n"
+"already introduced the same changes; you might want to skip this patch."
+msgstr "Нет изменений — возможно, вы забыли вызвать «git add»?\nЕсли ничего не осталось для индексации, то, скорее всего, что-то другое уже сделало те же изменения; возможно, вам следует пропустить этот патч."
+
+#: builtin/am.c:1944
+msgid ""
+"You still have unmerged paths in your index.\n"
+"Did you forget to use 'git add'?"
+msgstr "У вас все еще имеются не слитые пути в индексе.\nВозможно, вы забыли вызвать «git add»?"
+
+#: builtin/am.c:2052 builtin/am.c:2056 builtin/am.c:2068 builtin/reset.c:308
+#: builtin/reset.c:316
+#, c-format
+msgid "Could not parse object '%s'."
+msgstr "Не удалось разобрать объект «%s»."
+
+#: builtin/am.c:2104
+msgid "failed to clean index"
+msgstr "не удалось очистить индекс"
+
+#: builtin/am.c:2138
+msgid ""
+"You seem to have moved HEAD since the last 'am' failure.\n"
+"Not rewinding to ORIG_HEAD"
+msgstr "Похоже, что вы переместили HEAD с момента последней ошибки выполнения «am».\nПеремотка на ORIG_HEAD не выполняется"
+
+#: builtin/am.c:2199
+#, c-format
+msgid "Invalid value for --patch-format: %s"
+msgstr "Неправильное значение для --patch-format: %s"
+
+#: builtin/am.c:2221
+msgid "git am [options] [(<mbox>|<Maildir>)...]"
+msgstr "git am [опции] [(<mbox>|<Maildir>)…]"
+
+#: builtin/am.c:2222
+msgid "git am [options] (--continue | --skip | --abort)"
+msgstr "git am [опции] (--continue | --skip | --abort)"
+
+#: builtin/am.c:2228
+msgid "run interactively"
+msgstr "запустить в интерактивном режиме"
+
+#: builtin/am.c:2230
+msgid "historical option -- no-op"
+msgstr "историческая опция — ничего не делает"
+
+#: builtin/am.c:2232
+msgid "allow fall back on 3way merging if needed"
+msgstr "разрешить откатиться к трехходовому слиянию, если нужно"
+
+#: builtin/am.c:2233 builtin/init-db.c:509 builtin/prune-packed.c:57
+#: builtin/repack.c:171
+msgid "be quiet"
+msgstr "тихий режим"
+
+#: builtin/am.c:2235
+msgid "add a Signed-off-by line to the commit message"
+msgstr "добавить строку Signed-off-by к сообщению коммита"
+
+#: builtin/am.c:2238
+msgid "recode into utf8 (default)"
+msgstr "перекодировать в utf8 (по умолчанию)"
+
+#: builtin/am.c:2240
+msgid "pass -k flag to git-mailinfo"
+msgstr "передать флаг -k в git-mailinfo"
+
+#: builtin/am.c:2242
+msgid "pass -b flag to git-mailinfo"
+msgstr "передать флаг -b в git-mailinfo"
+
+#: builtin/am.c:2244
+msgid "pass -m flag to git-mailinfo"
+msgstr "передать флаг -m в git-mailinfo"
+
+#: builtin/am.c:2246
+msgid "pass --keep-cr flag to git-mailsplit for mbox format"
+msgstr "передать флаг --keep-cr в git-mailsplit для формата mbox"
+
+#: builtin/am.c:2249
+msgid "do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"
+msgstr "не передавать --keep-cr флаг в git-mailsplit вне зависимости от am.keepcr"
+
+#: builtin/am.c:2252
+msgid "strip everything before a scissors line"
+msgstr "обрезать все до строки обрезки"
+
+#: builtin/am.c:2253 builtin/apply.c:4563
+msgid "action"
+msgstr "действие"
+
+#: builtin/am.c:2254 builtin/am.c:2257 builtin/am.c:2260 builtin/am.c:2263
+#: builtin/am.c:2266 builtin/am.c:2269 builtin/am.c:2272 builtin/am.c:2275
+#: builtin/am.c:2281
+msgid "pass it through git-apply"
+msgstr "передать его в git-apply"
+
+#: builtin/am.c:2262 builtin/apply.c:4587
+msgid "root"
+msgstr "корень"
+
+#: builtin/am.c:2265 builtin/am.c:2268 builtin/apply.c:4525
+#: builtin/apply.c:4528 builtin/clone.c:85 builtin/fetch.c:93
+#: builtin/pull.c:167
+msgid "path"
+msgstr "путь"
+
+#: builtin/am.c:2271 builtin/fmt-merge-msg.c:669 builtin/fmt-merge-msg.c:672
+#: builtin/grep.c:698 builtin/merge.c:198 builtin/pull.c:127
+#: builtin/repack.c:178 builtin/repack.c:182 builtin/show-branch.c:664
+#: builtin/show-ref.c:180 builtin/tag.c:591 parse-options.h:132
+#: parse-options.h:134 parse-options.h:243
+msgid "n"
+msgstr "n"
+
+#: builtin/am.c:2274 builtin/apply.c:4531
+msgid "num"
+msgstr "количество"
+
+#: builtin/am.c:2277 builtin/for-each-ref.c:34 builtin/replace.c:438
+msgid "format"
+msgstr "формат"
+
+#: builtin/am.c:2278
+msgid "format the patch(es) are in"
+msgstr "формат, в котором находятся патчи"
+
+#: builtin/am.c:2284
+msgid "override error message when patch failure occurs"
+msgstr "переопределить сообщение об ошибке, если не удалось наложить изменения"
+
+#: builtin/am.c:2286
+msgid "continue applying patches after resolving a conflict"
+msgstr "продолжить применение изменений после разрешения конфиликта"
+
+#: builtin/am.c:2289
+msgid "synonyms for --continue"
+msgstr "синонимы для --continue"
+
+#: builtin/am.c:2292
+msgid "skip the current patch"
+msgstr "пропустить текущий патч"
+
+#: builtin/am.c:2295
+msgid "restore the original branch and abort the patching operation."
+msgstr "восстановить оригинальную ветку и отменить операцию применения изменений."
+
+#: builtin/am.c:2299
+msgid "lie about committer date"
+msgstr "соврать о дате коммитера"
+
+#: builtin/am.c:2301
+msgid "use current timestamp for author date"
+msgstr "использовать текущее время как время авторства"
+
+#: builtin/am.c:2303 builtin/commit.c:1590 builtin/merge.c:225
+#: builtin/pull.c:155 builtin/revert.c:92 builtin/tag.c:606
+msgid "key-id"
+msgstr "key-id"
+
+#: builtin/am.c:2304
+msgid "GPG-sign commits"
+msgstr "подписать коммиты с помощью GPG"
+
+#: builtin/am.c:2307
+msgid "(internal use for git-rebase)"
+msgstr "(внутреннее использование для git-rebase)"
+
+#: builtin/am.c:2322
+msgid ""
+"The -b/--binary option has been a no-op for long time, and\n"
+"it will be removed. Please do not use it anymore."
+msgstr "Опция -b/--binary уже долгое время ничего не делает и будет удалена с следующих версиях Git. Пожалуйста, не используйте ее."
+
+#: builtin/am.c:2329
+msgid "failed to read the index"
+msgstr "сбой чтения индекса"
+
+#: builtin/am.c:2344
+#, c-format
+msgid "previous rebase directory %s still exists but mbox given."
+msgstr "предыдущий каталог перемещения %s еще существует, но передан mbox."
+
+#: builtin/am.c:2368
+#, c-format
+msgid ""
+"Stray %s directory found.\n"
+"Use \"git am --abort\" to remove it."
+msgstr "Найден забытый каталог %s.\nИспользуйте «git am --abort», чтобы удалить его."
+
+#: builtin/am.c:2374
+msgid "Resolve operation not in progress, we are not resuming."
+msgstr "Операция разрешения конфликтов не в процессе выполнения, не продолжаем."
+
 #: builtin/apply.c:59
 msgid "git apply [<options>] [<patch>...]"
 msgstr "git apply [<опции>] [<патч>…]"
@@ -2381,7 +2867,7 @@ msgstr "%s: не удалось применить патч"
 msgid "Checking patch %s..."
 msgstr "Проверка патча %s…"
 
-#: builtin/apply.c:3909 builtin/checkout.c:233 builtin/reset.c:135
+#: builtin/apply.c:3909 builtin/checkout.c:232 builtin/reset.c:135
 #, c-format
 msgid "make_cache_entry failed for path '%s'"
 msgstr "сбой make_cache_entry для пути «%s»"
@@ -2462,11 +2948,6 @@ msgstr "не распознанный ввод"
 msgid "unable to read index file"
 msgstr "не удалось прочитать файл индекса"
 
-#: builtin/apply.c:4525 builtin/apply.c:4528 builtin/clone.c:85
-#: builtin/fetch.c:92
-msgid "path"
-msgstr "путь"
-
 #: builtin/apply.c:4526
 msgid "don't apply changes matching the given path"
 msgstr "не применять изменения по указанному пути"
@@ -2475,10 +2956,6 @@ msgstr "не применять изменения по указанному п
 msgid "apply changes matching the given path"
 msgstr "применять изменения по указанному пути"
 
-#: builtin/apply.c:4531
-msgid "num"
-msgstr "количество"
-
 #: builtin/apply.c:4532
 msgid "remove <num> leading slashes from traditional diff paths"
 msgstr "удалить <количество> ведущих косых черт из традиционных путей списка изменений"
@@ -2535,10 +3012,6 @@ msgstr "пути, отделенные НУЛЕВЫМ символом"
 msgid "ensure at least <n> lines of context match"
 msgstr "удостовериться, что по крайней мере <n> строк контекста совпадают"
 
-#: builtin/apply.c:4563
-msgid "action"
-msgstr "действие"
-
 #: builtin/apply.c:4564
 msgid "detect new or modified lines that have whitespace errors"
 msgstr "определять новые или модифицированные строки, у которых есть ошибки в пробельных символах"
@@ -2571,10 +3044,6 @@ msgstr "разрешить некорректно определенные пр
 msgid "do not trust the line counts in the hunk headers"
 msgstr "не доверять количеству строк из заголовка блока изменений"
 
-#: builtin/apply.c:4587
-msgid "root"
-msgstr "корень"
-
 #: builtin/apply.c:4588
 msgid "prepend <root> to all filenames"
 msgstr "добавить <корень> спереди ко всем именам файлов"
@@ -2661,11 +3130,11 @@ msgstr "выполнить «git bisect next»"
 msgid "update BISECT_HEAD instead of checking out the current commit"
 msgstr "обновить BISECT_HEAD вместо перехода на текущий коммит"
 
-#: builtin/blame.c:31
+#: builtin/blame.c:32
 msgid "git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"
 msgstr "git blame [<опции>] [<опции-редакции>] [<редакция>] [--] <файл>"
 
-#: builtin/blame.c:36
+#: builtin/blame.c:37
 msgid "<rev-opts> are documented in git-rev-list(1)"
 msgstr "<опции-rev-list> документированы в git-rev-list(1)"
 
@@ -2840,323 +3309,323 @@ msgstr "внешняя отслеживаемая ветка «%s» не най
 msgid "branch '%s' not found."
 msgstr "ветка «%s» не найдена."
 
-#: builtin/branch.c:258
+#: builtin/branch.c:259
 #, c-format
 msgid "Error deleting remote-tracking branch '%s'"
 msgstr "Ошибка удаления внешней отслеживаемой ветки «%s»"
 
-#: builtin/branch.c:259
+#: builtin/branch.c:260
 #, c-format
 msgid "Error deleting branch '%s'"
 msgstr "Ошибка удаления ветки «%s»"
 
-#: builtin/branch.c:266
+#: builtin/branch.c:267
 #, c-format
 msgid "Deleted remote-tracking branch %s (was %s).\n"
 msgstr "Внешняя отслеживаемая ветка %s удалена (была %s).\n"
 
-#: builtin/branch.c:267
+#: builtin/branch.c:268
 #, c-format
 msgid "Deleted branch %s (was %s).\n"
 msgstr "Ветка %s удалена (была %s).\n"
 
-#: builtin/branch.c:368
+#: builtin/branch.c:369
 #, c-format
 msgid "branch '%s' does not point at a commit"
 msgstr "ветка «%s» не указывает на коммит"
 
-#: builtin/branch.c:451
+#: builtin/branch.c:452
 #, c-format
 msgid "[%s: gone]"
 msgstr "[%s: пропал]"
 
-#: builtin/branch.c:456
+#: builtin/branch.c:457
 #, c-format
 msgid "[%s]"
 msgstr "[%s]"
 
-#: builtin/branch.c:461
+#: builtin/branch.c:462
 #, c-format
 msgid "[%s: behind %d]"
 msgstr "[%s: позади %d]"
 
-#: builtin/branch.c:463
+#: builtin/branch.c:464
 #, c-format
 msgid "[behind %d]"
 msgstr "[позади %d]"
 
-#: builtin/branch.c:467
+#: builtin/branch.c:468
 #, c-format
 msgid "[%s: ahead %d]"
 msgstr "[%s: впереди %d]"
 
-#: builtin/branch.c:469
+#: builtin/branch.c:470
 #, c-format
 msgid "[ahead %d]"
 msgstr "[впереди %d]"
 
-#: builtin/branch.c:472
+#: builtin/branch.c:473
 #, c-format
 msgid "[%s: ahead %d, behind %d]"
 msgstr "[%s: впереди %d, позади %d]"
 
-#: builtin/branch.c:475
+#: builtin/branch.c:476
 #, c-format
 msgid "[ahead %d, behind %d]"
 msgstr "[впереди %d, позади %d]"
 
-#: builtin/branch.c:488
+#: builtin/branch.c:489
 msgid " **** invalid ref ****"
 msgstr " **** недействительная ссылка ****"
 
-#: builtin/branch.c:579
+#: builtin/branch.c:580
 #, c-format
 msgid "(no branch, rebasing %s)"
 msgstr "(нет ветки, перемещение %s)"
 
-#: builtin/branch.c:582
+#: builtin/branch.c:583
 #, c-format
 msgid "(no branch, bisect started on %s)"
 msgstr "(нет ветки, двоичный поиск начат на %s)"
 
-#: builtin/branch.c:588
+#: builtin/branch.c:589
 #, c-format
 msgid "(HEAD detached at %s)"
 msgstr "(HEAD отделён на %s)"
 
-#: builtin/branch.c:591
+#: builtin/branch.c:592
 #, c-format
 msgid "(HEAD detached from %s)"
 msgstr "(HEAD отделён начиная с %s)"
 
-#: builtin/branch.c:595
+#: builtin/branch.c:596
 msgid "(no branch)"
 msgstr "(нет ветки)"
 
-#: builtin/branch.c:642
+#: builtin/branch.c:643
 #, c-format
 msgid "object '%s' does not point to a commit"
 msgstr "объект «%s» не указывает на коммит"
 
-#: builtin/branch.c:690
+#: builtin/branch.c:691
 msgid "some refs could not be read"
 msgstr "не удается прочитать некоторые ссылки"
 
-#: builtin/branch.c:703
+#: builtin/branch.c:704
 msgid "cannot rename the current branch while not on any."
 msgstr "невозможно переименовать текущую ветку, если вы не находитесь ни на одной из них."
 
-#: builtin/branch.c:713
+#: builtin/branch.c:714
 #, c-format
 msgid "Invalid branch name: '%s'"
 msgstr "Недействительное имя ветки: «%s»"
 
-#: builtin/branch.c:728
+#: builtin/branch.c:729
 msgid "Branch rename failed"
 msgstr "Сбой переименования ветки"
 
-#: builtin/branch.c:732
+#: builtin/branch.c:733
 #, c-format
 msgid "Renamed a misnamed branch '%s' away"
 msgstr "Переименована неправильно названная ветка «%s»"
 
-#: builtin/branch.c:736
+#: builtin/branch.c:737
 #, c-format
 msgid "Branch renamed to %s, but HEAD is not updated!"
 msgstr "Ветка переименована в %s, но HEAD не обновлен!"
 
-#: builtin/branch.c:743
+#: builtin/branch.c:744
 msgid "Branch is renamed, but update of config-file failed"
 msgstr "Ветка переименована, но произошел сбой обновления файла конфигурации"
 
-#: builtin/branch.c:758
+#: builtin/branch.c:759
 #, c-format
 msgid "malformed object name %s"
 msgstr "плохое имя объекта %s"
 
-#: builtin/branch.c:780
+#: builtin/branch.c:781
 #, c-format
 msgid "could not write branch description template: %s"
 msgstr "не удалось записать шаблон описания ветки: %s"
 
-#: builtin/branch.c:810
+#: builtin/branch.c:811
 msgid "Generic options"
 msgstr "Общие параметры"
 
-#: builtin/branch.c:812
+#: builtin/branch.c:813
 msgid "show hash and subject, give twice for upstream branch"
 msgstr "показывать хеш-сумму и тему, укажите дважды для вышестоящей ветки"
 
-#: builtin/branch.c:813
+#: builtin/branch.c:814
 msgid "suppress informational messages"
 msgstr "не выводить информационные сообщения"
 
-#: builtin/branch.c:814
+#: builtin/branch.c:815
 msgid "set up tracking mode (see git-pull(1))"
 msgstr "установить режим отслеживания вышестоящей ветки (см. git-pull(1))"
 
-#: builtin/branch.c:816
+#: builtin/branch.c:817
 msgid "change upstream info"
 msgstr "изменить информацию о вышестоящей ветке"
 
-#: builtin/branch.c:820
+#: builtin/branch.c:821
 msgid "use colored output"
 msgstr "использовать цветной вывод"
 
-#: builtin/branch.c:821
+#: builtin/branch.c:822
 msgid "act on remote-tracking branches"
 msgstr "выполнить действия на отслеживаемых внешних ветках"
 
-#: builtin/branch.c:824 builtin/branch.c:830 builtin/branch.c:851
-#: builtin/branch.c:857 builtin/commit.c:1581 builtin/commit.c:1582
-#: builtin/commit.c:1583 builtin/commit.c:1584 builtin/tag.c:616
-#: builtin/tag.c:622
+#: builtin/branch.c:825 builtin/branch.c:831 builtin/branch.c:852
+#: builtin/branch.c:858 builtin/commit.c:1580 builtin/commit.c:1581
+#: builtin/commit.c:1582 builtin/commit.c:1583 builtin/tag.c:618
+#: builtin/tag.c:624
 msgid "commit"
 msgstr "коммит"
 
-#: builtin/branch.c:825 builtin/branch.c:831
+#: builtin/branch.c:826 builtin/branch.c:832
 msgid "print only branches that contain the commit"
 msgstr "вывод только веток, которые содержат коммит"
 
-#: builtin/branch.c:837
+#: builtin/branch.c:838
 msgid "Specific git-branch actions:"
 msgstr "Специфичные для git-branch действия:"
 
-#: builtin/branch.c:838
+#: builtin/branch.c:839
 msgid "list both remote-tracking and local branches"
 msgstr "показать список и отслеживаемых и локальных веток"
 
-#: builtin/branch.c:840
+#: builtin/branch.c:841
 msgid "delete fully merged branch"
 msgstr "удалить полностью слитую ветку"
 
-#: builtin/branch.c:841
+#: builtin/branch.c:842
 msgid "delete branch (even if not merged)"
 msgstr "удалить ветку (даже никуда не слитую)"
 
-#: builtin/branch.c:842
+#: builtin/branch.c:843
 msgid "move/rename a branch and its reflog"
 msgstr "переместить/переименовать ветки и ее журнал ссылок"
 
-#: builtin/branch.c:843
+#: builtin/branch.c:844
 msgid "move/rename a branch, even if target exists"
 msgstr "переместить/переименовать ветку, даже если целевое имя уже существует"
 
-#: builtin/branch.c:844
+#: builtin/branch.c:845
 msgid "list branch names"
 msgstr "показать список имен веток"
 
-#: builtin/branch.c:845
+#: builtin/branch.c:846
 msgid "create the branch's reflog"
 msgstr "создать журнал ссылок ветки"
 
-#: builtin/branch.c:847
+#: builtin/branch.c:848
 msgid "edit the description for the branch"
 msgstr "изменить описание ветки"
 
-#: builtin/branch.c:848
+#: builtin/branch.c:849
 msgid "force creation, move/rename, deletion"
 msgstr "принудительное создание, перемещение или удаление ветки"
 
-#: builtin/branch.c:851
+#: builtin/branch.c:852
 msgid "print only not merged branches"
 msgstr "вывод только не слитых веток"
 
-#: builtin/branch.c:857
+#: builtin/branch.c:858
 msgid "print only merged branches"
 msgstr "вывод только слитых веток"
 
-#: builtin/branch.c:861
+#: builtin/branch.c:862
 msgid "list branches in columns"
 msgstr "показать список веток по столбцам"
 
-#: builtin/branch.c:874
+#: builtin/branch.c:875
 msgid "Failed to resolve HEAD as a valid ref."
 msgstr "Не удалось определить HEAD как действительную ссылку."
 
-#: builtin/branch.c:878 builtin/clone.c:622
+#: builtin/branch.c:879 builtin/clone.c:690
 msgid "HEAD not found below refs/heads!"
 msgstr "HEAD не найден в refs/heads!"
 
-#: builtin/branch.c:900
+#: builtin/branch.c:901
 msgid "--column and --verbose are incompatible"
 msgstr "--column и --verbose нельзя использовать одновременно"
 
-#: builtin/branch.c:911 builtin/branch.c:950
+#: builtin/branch.c:912 builtin/branch.c:951
 msgid "branch name required"
 msgstr "требуется имя ветки"
 
-#: builtin/branch.c:926
+#: builtin/branch.c:927
 msgid "Cannot give description to detached HEAD"
 msgstr "Нельзя дать описание отделенному HEAD"
 
-#: builtin/branch.c:931
+#: builtin/branch.c:932
 msgid "cannot edit description of more than one branch"
 msgstr "нельзя изменить описание более одной ветки за раз"
 
-#: builtin/branch.c:938
+#: builtin/branch.c:939
 #, c-format
 msgid "No commit on branch '%s' yet."
 msgstr "Еще нет коммита на ветке «%s»."
 
-#: builtin/branch.c:941
+#: builtin/branch.c:942
 #, c-format
 msgid "No branch named '%s'."
 msgstr "Нет ветки с именем «%s»."
 
-#: builtin/branch.c:956
+#: builtin/branch.c:957
 msgid "too many branches for a rename operation"
 msgstr "слишком много веток для операции переименования"
 
-#: builtin/branch.c:961
+#: builtin/branch.c:962
 msgid "too many branches to set new upstream"
 msgstr "слишком много веток для указания новых вышестоящих"
 
-#: builtin/branch.c:965
+#: builtin/branch.c:966
 #, c-format
 msgid ""
 "could not set upstream of HEAD to %s when it does not point to any branch."
 msgstr "невозможно установить вышестоящий репозиторий для HEAD на %s, когда он не указывает ни на одну ветку."
 
-#: builtin/branch.c:968 builtin/branch.c:990 builtin/branch.c:1011
+#: builtin/branch.c:969 builtin/branch.c:991 builtin/branch.c:1012
 #, c-format
 msgid "no such branch '%s'"
 msgstr "нет такой ветки «%s»"
 
-#: builtin/branch.c:972
+#: builtin/branch.c:973
 #, c-format
 msgid "branch '%s' does not exist"
 msgstr "ветка «%s» не существует"
 
-#: builtin/branch.c:984
+#: builtin/branch.c:985
 msgid "too many branches to unset upstream"
 msgstr "слишком много веток для убирания вышестоящих"
 
-#: builtin/branch.c:988
+#: builtin/branch.c:989
 msgid "could not unset upstream of HEAD when it does not point to any branch."
 msgstr "невозможно убрать вышестоящий репозиторий для HEAD, когда он не указывает ни на одну ветку."
 
-#: builtin/branch.c:994
+#: builtin/branch.c:995
 #, c-format
 msgid "Branch '%s' has no upstream information"
 msgstr "Ветка «%s» не имеет информации о вышестоящей ветке"
 
-#: builtin/branch.c:1008
+#: builtin/branch.c:1009
 msgid "it does not make sense to create 'HEAD' manually"
 msgstr "не имеет смысла создавать «HEAD» вручную"
 
-#: builtin/branch.c:1014
+#: builtin/branch.c:1015
 msgid "-a and -r options to 'git branch' do not make sense with a branch name"
 msgstr "параметры -a и -r для «git branch» не имеют смысла с указанием имени ветки"
 
-#: builtin/branch.c:1017
+#: builtin/branch.c:1018
 #, c-format
 msgid ""
 "The --set-upstream flag is deprecated and will be removed. Consider using "
 "--track or --set-upstream-to\n"
 msgstr "Флаг --set-upstream устарел и будет удален в будущем. Вместо него используйте --track или --set-upstream-to\n"
 
-#: builtin/branch.c:1034
+#: builtin/branch.c:1035
 #, c-format
 msgid ""
 "\n"
@@ -3164,12 +3633,12 @@ msgid ""
 "\n"
 msgstr "\nЕсли вы хотите, чтобы «%s» отслеживала «%s», сделайте следующее:\n\n"
 
-#: builtin/branch.c:1035
+#: builtin/branch.c:1036
 #, c-format
 msgid "    git branch -d %s\n"
 msgstr "git branch -d %s\n"
 
-#: builtin/branch.c:1036
+#: builtin/branch.c:1037
 #, c-format
 msgid "    git branch --set-upstream-to %s\n"
 msgstr "    git branch --set-upstream-to %s\n"
@@ -3187,58 +3656,66 @@ msgstr "Требуется репозиторий для создания пак
 msgid "Need a repository to unbundle."
 msgstr "Требуется репозиторий для распаковки."
 
-#: builtin/cat-file.c:369
+#: builtin/cat-file.c:428
 msgid ""
 "git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-"
 "type]|-e|-p|<type>|--textconv) <object>"
 msgstr "git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<тип>|--textconv) <объект>"
 
-#: builtin/cat-file.c:370
+#: builtin/cat-file.c:429
 msgid ""
 "git cat-file (--batch | --batch-check) [--follow-symlinks] < <list-of-"
 "objects>"
 msgstr "git cat-file (--batch | --batch-check) [--follow-symlinks] < <список-объектов>"
 
-#: builtin/cat-file.c:407
+#: builtin/cat-file.c:466
 msgid "<type> can be one of: blob, tree, commit, tag"
 msgstr "<тип> может быть одним из: blob, tree, commit, tag"
 
-#: builtin/cat-file.c:408
+#: builtin/cat-file.c:467
 msgid "show object type"
 msgstr "показать тип объекта"
 
-#: builtin/cat-file.c:409
+#: builtin/cat-file.c:468
 msgid "show object size"
 msgstr "показать размер объекта"
 
-#: builtin/cat-file.c:411
+#: builtin/cat-file.c:470
 msgid "exit with zero when there's no error"
 msgstr "выйти с нулевым кодом возврата, если нет ошибки"
 
-#: builtin/cat-file.c:412
+#: builtin/cat-file.c:471
 msgid "pretty-print object's content"
 msgstr "структурированный вывод содержимого объекта"
 
-#: builtin/cat-file.c:414
+#: builtin/cat-file.c:473
 msgid "for blob objects, run textconv on object's content"
 msgstr "запустить texconv на содержимом двоичных объектов "
 
-#: builtin/cat-file.c:416
+#: builtin/cat-file.c:475
 msgid "allow -s and -t to work with broken/corrupt objects"
 msgstr "разрешить -s и -t работать с повреждёнными объектами"
 
-#: builtin/cat-file.c:418
+#: builtin/cat-file.c:476
+msgid "buffer --batch output"
+msgstr "буфферировать вывод --batch"
+
+#: builtin/cat-file.c:478
 msgid "show info and content of objects fed from the standard input"
 msgstr "показать информацию и содержимое объектов, переданных из стандартного ввода"
 
-#: builtin/cat-file.c:421
+#: builtin/cat-file.c:481
 msgid "show info about objects fed from the standard input"
 msgstr "показать информацию об объектах, переданных из стандартного ввода"
 
-#: builtin/cat-file.c:424
+#: builtin/cat-file.c:484
 msgid "follow in-tree symlinks (used with --batch or --batch-check)"
 msgstr "переходить по символьным ссылкам внутри дерева (используется с опциями --batch и --batch-check)"
 
+#: builtin/cat-file.c:486
+msgid "show all objects with --batch or --batch-check"
+msgstr "показать все объекты с опциями --batch или --batch-check"
+
 #: builtin/check-attr.c:11
 msgid "git check-attr [-a | --all | <attr>...] [--] <pathname>..."
 msgstr "git check-attr [-a | --all | <атрибут>…] [--] <путь>…"
@@ -3263,7 +3740,7 @@ msgstr "прочитать имена файлов из стандартного
 msgid "terminate input and output records by a NUL character"
 msgstr "окончание ввода и вывода записей по НУЛЕВОМУ символу"
 
-#: builtin/check-ignore.c:18 builtin/checkout.c:1202 builtin/gc.c:279
+#: builtin/check-ignore.c:18 builtin/checkout.c:1133 builtin/gc.c:267
 msgid "suppress progress reporting"
 msgstr "не выводить прогресс выполнения"
 
@@ -3360,113 +3837,113 @@ msgstr "добавить спереди <строку> при создании 
 msgid "copy out the files from named stage"
 msgstr "копировать файлы из указанного индекса"
 
-#: builtin/checkout.c:24
+#: builtin/checkout.c:25
 msgid "git checkout [<options>] <branch>"
 msgstr "git checkout [<опции>] <ветка>"
 
-#: builtin/checkout.c:25
+#: builtin/checkout.c:26
 msgid "git checkout [<options>] [<branch>] -- <file>..."
 msgstr "git checkout [<опции>] [<ветка>] -- <файл>…"
 
-#: builtin/checkout.c:134 builtin/checkout.c:167
+#: builtin/checkout.c:133 builtin/checkout.c:166
 #, c-format
 msgid "path '%s' does not have our version"
 msgstr "путь «%s» не имеет нашей версии"
 
-#: builtin/checkout.c:136 builtin/checkout.c:169
+#: builtin/checkout.c:135 builtin/checkout.c:168
 #, c-format
 msgid "path '%s' does not have their version"
 msgstr "путь «%s» не имеет их версии"
 
-#: builtin/checkout.c:152
+#: builtin/checkout.c:151
 #, c-format
 msgid "path '%s' does not have all necessary versions"
 msgstr "путь «%s» не имеет всех необходимых версий"
 
-#: builtin/checkout.c:196
+#: builtin/checkout.c:195
 #, c-format
 msgid "path '%s' does not have necessary versions"
 msgstr "путь «%s» не имеет необходимых версий"
 
-#: builtin/checkout.c:213
+#: builtin/checkout.c:212
 #, c-format
 msgid "path '%s': cannot merge"
 msgstr "путь «%s»: не удалось слить"
 
-#: builtin/checkout.c:230
+#: builtin/checkout.c:229
 #, c-format
 msgid "Unable to add merge result for '%s'"
 msgstr "Не удалось добавить результат слияния «%s»"
 
-#: builtin/checkout.c:251 builtin/checkout.c:254 builtin/checkout.c:257
-#: builtin/checkout.c:260
+#: builtin/checkout.c:250 builtin/checkout.c:253 builtin/checkout.c:256
+#: builtin/checkout.c:259
 #, c-format
 msgid "'%s' cannot be used with updating paths"
 msgstr "«%s» нельзя использовать при обновлении путей"
 
-#: builtin/checkout.c:263 builtin/checkout.c:266
+#: builtin/checkout.c:262 builtin/checkout.c:265
 #, c-format
 msgid "'%s' cannot be used with %s"
 msgstr "«%s» нельзя использовать одновременно с %s"
 
-#: builtin/checkout.c:269
+#: builtin/checkout.c:268
 #, c-format
 msgid "Cannot update paths and switch to branch '%s' at the same time."
 msgstr "Нельзя обновлять пути и переключаться на ветку «%s» одновременно."
 
-#: builtin/checkout.c:280 builtin/checkout.c:474
+#: builtin/checkout.c:279 builtin/checkout.c:473
 msgid "corrupt index file"
 msgstr "файл индекса поврежден"
 
-#: builtin/checkout.c:340 builtin/checkout.c:347
+#: builtin/checkout.c:339 builtin/checkout.c:346
 #, c-format
 msgid "path '%s' is unmerged"
 msgstr "путь «%s» не слит"
 
-#: builtin/checkout.c:496
+#: builtin/checkout.c:495
 msgid "you need to resolve your current index first"
 msgstr "сначала нужно разрешить конфликты в вашем текущем индексе"
 
-#: builtin/checkout.c:627
+#: builtin/checkout.c:622
 #, c-format
-msgid "Can not do reflog for '%s'\n"
-msgstr "Не удалось создать журнал ссылок для «%s»\n"
+msgid "Can not do reflog for '%s': %s\n"
+msgstr "Не удалось создать журнал ссылок для «%s»': %s\n"
 
-#: builtin/checkout.c:663
+#: builtin/checkout.c:660
 msgid "HEAD is now at"
 msgstr "HEAD сейчас на"
 
-#: builtin/checkout.c:670
+#: builtin/checkout.c:667
 #, c-format
 msgid "Reset branch '%s'\n"
 msgstr "Сброс ветки «%s»\n"
 
-#: builtin/checkout.c:673
+#: builtin/checkout.c:670
 #, c-format
 msgid "Already on '%s'\n"
 msgstr "Уже на «%s»\n"
 
-#: builtin/checkout.c:677
+#: builtin/checkout.c:674
 #, c-format
 msgid "Switched to and reset branch '%s'\n"
 msgstr "Переключение и сброс ветки «%s»\n"
 
-#: builtin/checkout.c:679 builtin/checkout.c:1134
+#: builtin/checkout.c:676 builtin/checkout.c:1065
 #, c-format
 msgid "Switched to a new branch '%s'\n"
 msgstr "Переключено на новую ветку «%s»\n"
 
-#: builtin/checkout.c:681
+#: builtin/checkout.c:678
 #, c-format
 msgid "Switched to branch '%s'\n"
 msgstr "Переключено на ветку «%s»\n"
 
-#: builtin/checkout.c:733
+#: builtin/checkout.c:730
 #, c-format
 msgid " ... and %d more.\n"
 msgstr " … и еще %d.\n"
 
-#: builtin/checkout.c:739
+#: builtin/checkout.c:736
 #, c-format
 msgid ""
 "Warning: you are leaving %d commit behind, not connected to\n"
@@ -3483,7 +3960,7 @@ msgstr[1] "Предупреждение: вы оставляете позади
 msgstr[2] "Предупреждение: вы оставляете позади %d коммитов не соединенные ни с одной из ваших веток:\n\n%s\n"
 msgstr[3] "Предупреждение: вы оставляете позади %d коммитов не соединенные ни с одной из ваших веток:\n\n%s\n"
 
-#: builtin/checkout.c:758
+#: builtin/checkout.c:755
 #, c-format
 msgid ""
 "If you want to keep it by creating a new branch, this may be a good time\n"
@@ -3502,197 +3979,192 @@ msgstr[1] "Если вы хотите сохранить их с помощью
 msgstr[2] "Если вы хотите сохранить их с помощью создания новой ветки, то сейчас самое время\nсделать это с помощью:\n\n git branch <имя-новой-ветки> %s\n\n"
 msgstr[3] "Если вы хотите сохранить их с помощью создания новой ветки, то сейчас самое время\nсделать это с помощью:\n\n git branch <имя-новой-ветки> %s\n\n"
 
-#: builtin/checkout.c:794
+#: builtin/checkout.c:791
 msgid "internal error in revision walk"
 msgstr "внутренняя ошибка при хождении по редакциям"
 
-#: builtin/checkout.c:798
+#: builtin/checkout.c:795
 msgid "Previous HEAD position was"
 msgstr "Предыдущая позиция HEAD была"
 
-#: builtin/checkout.c:825 builtin/checkout.c:1129
+#: builtin/checkout.c:822 builtin/checkout.c:1060
 msgid "You are on a branch yet to be born"
 msgstr "Вы находитесь на еще не созданной ветке"
 
-#: builtin/checkout.c:931
-#, c-format
-msgid "'%s' is already checked out at '%s'"
-msgstr "«%s» уже находится на «%s»"
-
-#: builtin/checkout.c:1036
+#: builtin/checkout.c:967
 #, c-format
 msgid "only one reference expected, %d given."
 msgstr "ожидается только одна ссылка, а передано %d."
 
-#: builtin/checkout.c:1075
+#: builtin/checkout.c:1006 builtin/worktree.c:210
 #, c-format
 msgid "invalid reference: %s"
 msgstr "неправильная ссылка: %s"
 
-#: builtin/checkout.c:1104
+#: builtin/checkout.c:1035
 #, c-format
 msgid "reference is not a tree: %s"
 msgstr "в дереве нет такой ссылки: %s"
 
-#: builtin/checkout.c:1143
+#: builtin/checkout.c:1074
 msgid "paths cannot be used with switching branches"
 msgstr "нельзя использовать пути при переключении веток"
 
-#: builtin/checkout.c:1146 builtin/checkout.c:1150
+#: builtin/checkout.c:1077 builtin/checkout.c:1081
 #, c-format
 msgid "'%s' cannot be used with switching branches"
 msgstr "нельзя использовать «%s» при переключении веток"
 
-#: builtin/checkout.c:1154 builtin/checkout.c:1157 builtin/checkout.c:1162
-#: builtin/checkout.c:1165
+#: builtin/checkout.c:1085 builtin/checkout.c:1088 builtin/checkout.c:1093
+#: builtin/checkout.c:1096
 #, c-format
 msgid "'%s' cannot be used with '%s'"
 msgstr "«%s» нельзя использовать одновременно с «%s»"
 
-#: builtin/checkout.c:1170
+#: builtin/checkout.c:1101
 #, c-format
 msgid "Cannot switch branch to a non-commit '%s'"
 msgstr "Нельзя переключить ветку на не коммит «%s»"
 
-#: builtin/checkout.c:1203 builtin/checkout.c:1205 builtin/clone.c:83
-#: builtin/remote.c:159 builtin/remote.c:161 builtin/worktree.c:282
-#: builtin/worktree.c:284
+#: builtin/checkout.c:1134 builtin/checkout.c:1136 builtin/clone.c:83
+#: builtin/remote.c:159 builtin/remote.c:161 builtin/worktree.c:317
+#: builtin/worktree.c:319
 msgid "branch"
 msgstr "ветка"
 
-#: builtin/checkout.c:1204
+#: builtin/checkout.c:1135
 msgid "create and checkout a new branch"
 msgstr "создать и перейти на новую ветку"
 
-#: builtin/checkout.c:1206
+#: builtin/checkout.c:1137
 msgid "create/reset and checkout a branch"
 msgstr "создать/сбросить и перейти на новую ветку"
 
-#: builtin/checkout.c:1207
+#: builtin/checkout.c:1138
 msgid "create reflog for new branch"
 msgstr "создать журнал ссылок для новой ветки"
 
-#: builtin/checkout.c:1208
+#: builtin/checkout.c:1139
 msgid "detach the HEAD at named commit"
 msgstr "отсоединить HEAD на указанном коммите"
 
-#: builtin/checkout.c:1209
+#: builtin/checkout.c:1140
 msgid "set upstream info for new branch"
 msgstr "установить информацию о вышестоящей ветке для новой ветки"
 
-#: builtin/checkout.c:1211
+#: builtin/checkout.c:1142
 msgid "new-branch"
 msgstr "новая-ветка"
 
-#: builtin/checkout.c:1211
+#: builtin/checkout.c:1142
 msgid "new unparented branch"
 msgstr "новая ветка без родителей"
 
-#: builtin/checkout.c:1212
+#: builtin/checkout.c:1143
 msgid "checkout our version for unmerged files"
 msgstr "перейти на нашу версию для не слитых файлов"
 
-#: builtin/checkout.c:1214
+#: builtin/checkout.c:1145
 msgid "checkout their version for unmerged files"
 msgstr "перейти на их версию для не слитых файлов"
 
-#: builtin/checkout.c:1216
+#: builtin/checkout.c:1147
 msgid "force checkout (throw away local modifications)"
 msgstr "принудительный переход (отбрасывает все локальные изменения)"
 
-#: builtin/checkout.c:1217
+#: builtin/checkout.c:1148
 msgid "perform a 3-way merge with the new branch"
 msgstr "выполнить трехходовое слияние с новой веткой"
 
-#: builtin/checkout.c:1218 builtin/merge.c:227
+#: builtin/checkout.c:1149 builtin/merge.c:227
 msgid "update ignored files (default)"
 msgstr "обновить игнорируемые файлы (по умолчанию)"
 
-#: builtin/checkout.c:1219 builtin/log.c:1239 parse-options.h:244
+#: builtin/checkout.c:1150 builtin/log.c:1264 parse-options.h:249
 msgid "style"
 msgstr "стиль"
 
-#: builtin/checkout.c:1220
+#: builtin/checkout.c:1151
 msgid "conflict style (merge or diff3)"
 msgstr "стиль конфликтов слияния (merge или diff3)"
 
-#: builtin/checkout.c:1223
+#: builtin/checkout.c:1154
 msgid "do not limit pathspecs to sparse entries only"
 msgstr "не ограничивать спецификаторы пути только частичными записями"
 
-#: builtin/checkout.c:1225
+#: builtin/checkout.c:1156
 msgid "second guess 'git checkout <no-such-branch>'"
 msgstr "пересмотр «git checkout <no-such-branch>»"
 
-#: builtin/checkout.c:1227
+#: builtin/checkout.c:1158
 msgid "do not check if another worktree is holding the given ref"
 msgstr "не проверять, что другое дерево уже содержит указанную ссылку"
 
-#: builtin/checkout.c:1252
+#: builtin/checkout.c:1181
 msgid "-b, -B and --orphan are mutually exclusive"
 msgstr "-b, -B и --orphan нельзя использовать одновременно"
 
-#: builtin/checkout.c:1269
+#: builtin/checkout.c:1198
 msgid "--track needs a branch name"
 msgstr "--track требует имя ветки"
 
-#: builtin/checkout.c:1274
+#: builtin/checkout.c:1203
 msgid "Missing branch name; try -b"
 msgstr "Пропущено имя ветки; попробуйте -b"
 
-#: builtin/checkout.c:1310
+#: builtin/checkout.c:1239
 msgid "invalid path specification"
 msgstr "неправильная спецификация пути"
 
-#: builtin/checkout.c:1317
+#: builtin/checkout.c:1246
 #, c-format
 msgid ""
 "Cannot update paths and switch to branch '%s' at the same time.\n"
 "Did you intend to checkout '%s' which can not be resolved as commit?"
 msgstr "Нельзя обновить пути и одновременно переключить на ветку «%s».\nВы хотели переключиться на «%s», что не может быть определено как коммит?"
 
-#: builtin/checkout.c:1322
+#: builtin/checkout.c:1251
 #, c-format
 msgid "git checkout: --detach does not take a path argument '%s'"
 msgstr "git checkout: --detach не принимает путь «%s» как аргумент"
 
-#: builtin/checkout.c:1326
+#: builtin/checkout.c:1255
 msgid ""
 "git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
 "checking out of the index."
 msgstr "git checkout: --ours/--theirs, --force and --merge нельзя использовать одновременно при применении состояния индекса."
 
-#: builtin/clean.c:26
+#: builtin/clean.c:25
 msgid ""
 "git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."
 msgstr "git clean [-d] [-f] [-i] [-n] [-q] [-e <шаблон>] [-x | -X] [--] <пути>…"
 
-#: builtin/clean.c:30
+#: builtin/clean.c:29
 #, c-format
 msgid "Removing %s\n"
 msgstr "Удаление %s\n"
 
-#: builtin/clean.c:31
+#: builtin/clean.c:30
 #, c-format
 msgid "Would remove %s\n"
 msgstr "Будет удалено %s\n"
 
-#: builtin/clean.c:32
+#: builtin/clean.c:31
 #, c-format
 msgid "Skipping repository %s\n"
 msgstr "Пропуск репозитория %s\n"
 
-#: builtin/clean.c:33
+#: builtin/clean.c:32
 #, c-format
 msgid "Would skip repository %s\n"
 msgstr "Будет пропущен репозиторий %s\n"
 
-#: builtin/clean.c:34
+#: builtin/clean.c:33
 #, c-format
 msgid "failed to remove %s"
 msgstr "сбой удаления %s"
 
-#: builtin/clean.c:295
+#: builtin/clean.c:317
 msgid ""
 "Prompt help:\n"
 "1          - select a numbered item\n"
@@ -3700,7 +4172,7 @@ msgid ""
 "           - (empty) select nothing"
 msgstr "Справка по выделению:\n1          - выбрать указанный элемент\nfoo        - выбрать элемент с указанным префиксом\n           - (пусто) не выбирать ничего"
 
-#: builtin/clean.c:299
+#: builtin/clean.c:321
 msgid ""
 "Prompt help:\n"
 "1          - select a single item\n"
@@ -3712,36 +4184,36 @@ msgid ""
 "           - (empty) finish selecting"
 msgstr "Справка по выделению:\n1          - выбрать один элемент\n3-5        - выбрать диапазон элементов\n2-3,6-9    - выбрать несколько диапазонов\nfoo        - выбрать элемент с указанным префиксом\n-…       - убрать выделение с указанных элементов\n*          - выбрать все элементы\n           - (пусто) завершить выделение"
 
-#: builtin/clean.c:515
+#: builtin/clean.c:537
 #, c-format
 msgid "Huh (%s)?"
 msgstr "Хм (%s)?"
 
-#: builtin/clean.c:657
+#: builtin/clean.c:679
 #, c-format
 msgid "Input ignore patterns>> "
 msgstr "Шаблоны игнорирования ввода>> "
 
-#: builtin/clean.c:694
+#: builtin/clean.c:716
 #, c-format
 msgid "WARNING: Cannot find items matched by: %s"
 msgstr "ПРЕДУПРЕЖДЕНИЕ: Не удалось найти элементы соответствующие: %s"
 
-#: builtin/clean.c:715
+#: builtin/clean.c:737
 msgid "Select items to delete"
 msgstr "Укажите элементы для удаления"
 
 #. TRANSLATORS: Make sure to keep [y/N] as is
-#: builtin/clean.c:756
+#: builtin/clean.c:778
 #, c-format
 msgid "Remove %s [y/N]? "
 msgstr "Удалить %s [y - да/N - нет]? "
 
-#: builtin/clean.c:781
+#: builtin/clean.c:803
 msgid "Bye."
 msgstr "До свидания."
 
-#: builtin/clean.c:789
+#: builtin/clean.c:811
 msgid ""
 "clean               - start cleaning\n"
 "filter by pattern   - exclude items from deletion\n"
@@ -3752,15 +4224,15 @@ msgid ""
 "?                   - help for prompt selection"
 msgstr "clean               - начать очистку\nfilter by pattern   - исключить удаление элементов\nselect by numbers   - исключить удаление элементов по номерам\nask each            - запрашивать подтверждение на удаление каждого элемента (как «rm -i»)\nquit                - прекратить очистку\nhelp                - этот экран\n?                   - справка по выделению"
 
-#: builtin/clean.c:816
+#: builtin/clean.c:838
 msgid "*** Commands ***"
 msgstr "*** Команды ***"
 
-#: builtin/clean.c:817
+#: builtin/clean.c:839
 msgid "What now"
 msgstr "Что теперь"
 
-#: builtin/clean.c:825
+#: builtin/clean.c:847
 msgid "Would remove the following item:"
 msgid_plural "Would remove the following items:"
 msgstr[0] "Удалить следующие элементы:"
@@ -3768,54 +4240,54 @@ msgstr[1] "Удалить следующие элементы:"
 msgstr[2] "Удалить следующие элементы:"
 msgstr[3] "Удалить следующие элементы:"
 
-#: builtin/clean.c:842
+#: builtin/clean.c:864
 msgid "No more files to clean, exiting."
 msgstr "Больше нет файлов для очистки, выходим."
 
-#: builtin/clean.c:873
+#: builtin/clean.c:895
 msgid "do not print names of files removed"
 msgstr "не выводить имена удаляемых файлов"
 
-#: builtin/clean.c:875
+#: builtin/clean.c:897
 msgid "force"
 msgstr "принудительно"
 
-#: builtin/clean.c:876
+#: builtin/clean.c:898
 msgid "interactive cleaning"
 msgstr "интерактивная очистка"
 
-#: builtin/clean.c:878
+#: builtin/clean.c:900
 msgid "remove whole directories"
 msgstr "удалить каталоги полностью"
 
-#: builtin/clean.c:879 builtin/describe.c:407 builtin/grep.c:714
+#: builtin/clean.c:901 builtin/describe.c:407 builtin/grep.c:714
 #: builtin/ls-files.c:443 builtin/name-rev.c:311 builtin/show-ref.c:187
 msgid "pattern"
 msgstr "шаблон"
 
-#: builtin/clean.c:880
+#: builtin/clean.c:902
 msgid "add <pattern> to ignore rules"
 msgstr "добавить <шаблон> в правила игнорирования"
 
-#: builtin/clean.c:881
+#: builtin/clean.c:903
 msgid "remove ignored files, too"
 msgstr "также удалить игнорируемые файлы"
 
-#: builtin/clean.c:883
+#: builtin/clean.c:905
 msgid "remove only ignored files"
 msgstr "удалить только игнорируемые файлы"
 
-#: builtin/clean.c:901
+#: builtin/clean.c:923
 msgid "-x and -X cannot be used together"
 msgstr "нельзя использовать одновременно -x и -X"
 
-#: builtin/clean.c:905
+#: builtin/clean.c:927
 msgid ""
 "clean.requireForce set to true and neither -i, -n, nor -f given; refusing to"
 " clean"
 msgstr "clean.requireForce установлен как true и ни одна из опций -i, -n или -f не указана; отказ очистки"
 
-#: builtin/clean.c:908
+#: builtin/clean.c:930
 msgid ""
 "clean.requireForce defaults to true and neither -i, -n, nor -f given; "
 "refusing to clean"
@@ -3825,8 +4297,8 @@ msgstr "clean.requireForce установлен по умолчанию как t
 msgid "git clone [<options>] [--] <repo> [<dir>]"
 msgstr "git clone [<опции>] [--] <репозиторий> [<каталог>]"
 
-#: builtin/clone.c:57 builtin/fetch.c:111 builtin/merge.c:224
-#: builtin/push.c:523
+#: builtin/clone.c:57 builtin/fetch.c:112 builtin/merge.c:224
+#: builtin/pull.c:109 builtin/push.c:560 builtin/send-pack.c:168
 msgid "force progress reporting"
 msgstr "принудительно выводить прогресс"
 
@@ -3834,7 +4306,7 @@ msgstr "принудительно выводить прогресс"
 msgid "don't create a checkout"
 msgstr "не переключать рабочую копию на HEAD"
 
-#: builtin/clone.c:60 builtin/clone.c:62 builtin/init-db.c:503
+#: builtin/clone.c:60 builtin/clone.c:62 builtin/init-db.c:504
 msgid "create a bare repository"
 msgstr "создать голый репозиторий"
 
@@ -3858,11 +4330,11 @@ msgstr "настроить как общедоступный репозитор
 msgid "initialize submodules in the clone"
 msgstr "инициализировать подмодули в клоне"
 
-#: builtin/clone.c:75 builtin/init-db.c:500
+#: builtin/clone.c:75 builtin/init-db.c:501
 msgid "template-directory"
 msgstr "каталог-шаблонов"
 
-#: builtin/clone.c:76 builtin/init-db.c:501
+#: builtin/clone.c:76 builtin/init-db.c:502
 msgid "directory from which templates will be used"
 msgstr "каталог, шаблоны из которого будут использованы"
 
@@ -3890,7 +4362,8 @@ msgstr "перейти на <ветку>, вместо HEAD внешнего р
 msgid "path to git-upload-pack on the remote"
 msgstr "путь к git-upload-pack на внешнем репозитории"
 
-#: builtin/clone.c:87 builtin/fetch.c:112 builtin/grep.c:659
+#: builtin/clone.c:87 builtin/fetch.c:113 builtin/grep.c:659
+#: builtin/pull.c:186
 msgid "depth"
 msgstr "глубина"
 
@@ -3902,11 +4375,11 @@ msgstr "сделать частичный клон указанной глуби
 msgid "clone only one branch, HEAD or --branch"
 msgstr "клонировать только одну ветку, HEAD или --branch"
 
-#: builtin/clone.c:91 builtin/init-db.c:509
+#: builtin/clone.c:91 builtin/init-db.c:510
 msgid "gitdir"
 msgstr "каталог-git"
 
-#: builtin/clone.c:92 builtin/init-db.c:510
+#: builtin/clone.c:92 builtin/init-db.c:511
 msgid "separate git dir from working tree"
 msgstr "разместить каталог git отдельно от рабочей копии"
 
@@ -3918,178 +4391,173 @@ msgstr "ключ=значение"
 msgid "set config inside the new repository"
 msgstr "установить параметры внутри нового репозитория"
 
-#: builtin/clone.c:240
+#: builtin/clone.c:298
 #, c-format
 msgid "reference repository '%s' is not a local repository."
 msgstr "ссылаемый репозиторий «%s» не является локальным."
 
-#: builtin/clone.c:244
+#: builtin/clone.c:302
 #, c-format
 msgid "reference repository '%s' is shallow"
 msgstr "ссылаемый репозиторий «%s» является частичным"
 
-#: builtin/clone.c:247
+#: builtin/clone.c:305
 #, c-format
 msgid "reference repository '%s' is grafted"
 msgstr "ссылаемый репозиторий «%s» является сращенным"
 
-#: builtin/clone.c:310
-#, c-format
-msgid "failed to create directory '%s'"
-msgstr "не удалось создать каталог «%s»"
-
-#: builtin/clone.c:312 builtin/diff.c:84
+#: builtin/clone.c:370 builtin/diff.c:84
 #, c-format
 msgid "failed to stat '%s'"
 msgstr "не удалось выполнить stat «%s»"
 
-#: builtin/clone.c:314
+#: builtin/clone.c:372
 #, c-format
 msgid "%s exists and is not a directory"
 msgstr "%s уже существует и не является каталогом"
 
-#: builtin/clone.c:328
+#: builtin/clone.c:386
 #, c-format
 msgid "failed to stat %s\n"
 msgstr "не удалось выполнить stat %s\n"
 
-#: builtin/clone.c:350
+#: builtin/clone.c:408
 #, c-format
 msgid "failed to create link '%s'"
 msgstr "не удалось создать ссылку «%s»"
 
-#: builtin/clone.c:354
+#: builtin/clone.c:412
 #, c-format
 msgid "failed to copy file to '%s'"
 msgstr "не удалось копировать файл в «%s»"
 
-#: builtin/clone.c:377 builtin/clone.c:551
+#: builtin/clone.c:435 builtin/clone.c:619
 #, c-format
 msgid "done.\n"
 msgstr "готово.\n"
 
-#: builtin/clone.c:389
+#: builtin/clone.c:447
 msgid ""
 "Clone succeeded, but checkout failed.\n"
 "You can inspect what was checked out with 'git status'\n"
 "and retry the checkout with 'git checkout -f HEAD'\n"
 msgstr "Клонирование прошло успешно, но во время перехода на версию произошла ошибка.\nС помощь команды «git status» вы можете просмотреть, какие файлы были обновлены, а повторить попытку перехода на версию с помощью «git checkout -f HEAD»\n"
 
-#: builtin/clone.c:466
+#: builtin/clone.c:524
 #, c-format
 msgid "Could not find remote branch %s to clone."
 msgstr "Не удалось найти внешнюю ветку %s для клонирования."
 
-#: builtin/clone.c:546
+#: builtin/clone.c:614
 #, c-format
 msgid "Checking connectivity... "
 msgstr "Проверка соединения… "
 
-#: builtin/clone.c:549
+#: builtin/clone.c:617
 msgid "remote did not send all necessary objects"
 msgstr "внешний репозиторий прислал не все необходимые объекты"
 
-#: builtin/clone.c:613
+#: builtin/clone.c:681
 msgid "remote HEAD refers to nonexistent ref, unable to checkout.\n"
 msgstr "внешний HEAD ссылается на несуществующую ссылку, нельзя перейти на такую версию.\n"
 
-#: builtin/clone.c:644
+#: builtin/clone.c:712
 msgid "unable to checkout working tree"
 msgstr "не удалось перейти на версию в рабочем каталоге"
 
-#: builtin/clone.c:731
+#: builtin/clone.c:799
 msgid "cannot repack to clean up"
 msgstr "не удалось выполнить перепаковку для очистки"
 
-#: builtin/clone.c:733
+#: builtin/clone.c:801
 msgid "cannot unlink temporary alternates file"
 msgstr "не удалось отсоединить временные альтернативные файлы"
 
-#: builtin/clone.c:763
+#: builtin/clone.c:831
 msgid "Too many arguments."
 msgstr "Слишком много аргументов."
 
-#: builtin/clone.c:767
+#: builtin/clone.c:835
 msgid "You must specify a repository to clone."
 msgstr "Вы должны указать репозиторий для клонирования."
 
-#: builtin/clone.c:778
+#: builtin/clone.c:846
 #, c-format
 msgid "--bare and --origin %s options are incompatible."
 msgstr "--bare и --origin %s нельзя использовать одновременно."
 
-#: builtin/clone.c:781
+#: builtin/clone.c:849
 msgid "--bare and --separate-git-dir are incompatible."
 msgstr "--bare и --separate-git-dir нельзя использовать одновременно."
 
-#: builtin/clone.c:794
+#: builtin/clone.c:862
 #, c-format
 msgid "repository '%s' does not exist"
 msgstr "репозиторий «%s» не существует"
 
-#: builtin/clone.c:800 builtin/fetch.c:1160
+#: builtin/clone.c:868 builtin/fetch.c:1168
 #, c-format
 msgid "depth %s is not a positive number"
 msgstr "глубина %s не является положительным числом"
 
-#: builtin/clone.c:810
+#: builtin/clone.c:878
 #, c-format
 msgid "destination path '%s' already exists and is not an empty directory."
 msgstr "целевой путь «%s» уже существует и не является пустым каталогом."
 
-#: builtin/clone.c:820
+#: builtin/clone.c:888
 #, c-format
 msgid "working tree '%s' already exists."
 msgstr "рабочий каталог «%s» уже существует."
 
-#: builtin/clone.c:835 builtin/clone.c:846 builtin/worktree.c:193
-#: builtin/worktree.c:220
+#: builtin/clone.c:903 builtin/clone.c:914 builtin/worktree.c:218
+#: builtin/worktree.c:245
 #, c-format
 msgid "could not create leading directories of '%s'"
 msgstr "не удалось создать родительские каталоги для «%s»"
 
-#: builtin/clone.c:838
+#: builtin/clone.c:906
 #, c-format
 msgid "could not create work tree dir '%s'"
 msgstr "не удалось создать рабочий каталог «%s»"
 
-#: builtin/clone.c:856
+#: builtin/clone.c:924
 #, c-format
 msgid "Cloning into bare repository '%s'...\n"
 msgstr "Клонирование в голый репозиторий «%s»…\n"
 
-#: builtin/clone.c:858
+#: builtin/clone.c:926
 #, c-format
 msgid "Cloning into '%s'...\n"
 msgstr "Клонирование в «%s»…\n"
 
-#: builtin/clone.c:883
+#: builtin/clone.c:951
 msgid "--dissociate given, but there is no --reference"
 msgstr "передана опция --dissociate, но не передана --reference"
 
-#: builtin/clone.c:900
+#: builtin/clone.c:968
 msgid "--depth is ignored in local clones; use file:// instead."
 msgstr "--depth игнорируется на локальных клонах; вместо этого используйте file://."
 
-#: builtin/clone.c:903
+#: builtin/clone.c:971
 msgid "source repository is shallow, ignoring --local"
 msgstr "исходный репозиторий является частичным, --local игнорируется"
 
-#: builtin/clone.c:908
+#: builtin/clone.c:976
 msgid "--local is ignored"
 msgstr "--local игнорируется"
 
-#: builtin/clone.c:912
+#: builtin/clone.c:980
 #, c-format
 msgid "Don't know how to clone %s"
 msgstr "Не знаю как клонировать %s"
 
-#: builtin/clone.c:961 builtin/clone.c:969
+#: builtin/clone.c:1029 builtin/clone.c:1037
 #, c-format
 msgid "Remote branch %s not found in upstream %s"
 msgstr "Внешняя ветка %s не найдена в вышестоящем репозитории %s"
 
-#: builtin/clone.c:972
+#: builtin/clone.c:1040
 msgid "You appear to have cloned an empty repository."
 msgstr "Похоже, что вы клонировали пустой репозиторий."
 
@@ -4196,108 +4664,99 @@ msgstr "Если вы хотите пропустит этот коммит, и
 msgid "failed to unpack HEAD tree object"
 msgstr "сбой распаковки объекта дерева HEAD"
 
-#: builtin/commit.c:344
+#: builtin/commit.c:345
 msgid "unable to create temporary index"
 msgstr "не удалось создать временный индекс"
 
-#: builtin/commit.c:350
+#: builtin/commit.c:351
 msgid "interactive add failed"
 msgstr "сбой интерактивного добавления"
 
-#: builtin/commit.c:361
-msgid "unable to write index file"
-msgstr "не удалось записать индекс"
-
-#: builtin/commit.c:363
+#: builtin/commit.c:364
 msgid "unable to update temporary index"
 msgstr "не удалось обновить временный индекс"
 
-#: builtin/commit.c:365
+#: builtin/commit.c:366
 msgid "Failed to update main cache tree"
 msgstr "Сбой при обновлении основного кэша дерева"
 
-#: builtin/commit.c:389 builtin/commit.c:414 builtin/commit.c:463
+#: builtin/commit.c:390 builtin/commit.c:413 builtin/commit.c:462
 msgid "unable to write new_index file"
 msgstr "не удалось записать файл new_index"
 
-#: builtin/commit.c:445
+#: builtin/commit.c:444
 msgid "cannot do a partial commit during a merge."
 msgstr "нельзя создать частичный коммит во время слияния."
 
-#: builtin/commit.c:447
+#: builtin/commit.c:446
 msgid "cannot do a partial commit during a cherry-pick."
 msgstr "нельзя создать частичный коммит во время отбора лучшего коммита."
 
-#: builtin/commit.c:456
+#: builtin/commit.c:455
 msgid "cannot read the index"
 msgstr "не удалось прочитать индекс"
 
-#: builtin/commit.c:475
+#: builtin/commit.c:474
 msgid "unable to write temporary index file"
 msgstr "не удалось записать временный файл индекса"
 
-#: builtin/commit.c:580
+#: builtin/commit.c:579
 #, c-format
 msgid "commit '%s' lacks author header"
 msgstr "у коммита «%s» отсутствует автор в заголовке"
 
-#: builtin/commit.c:582
+#: builtin/commit.c:581
 #, c-format
 msgid "commit '%s' has malformed author line"
 msgstr "у коммита «%s» автор в неверном формате"
 
-#: builtin/commit.c:601
+#: builtin/commit.c:600
 msgid "malformed --author parameter"
 msgstr "параметр --author в неверном формате"
 
-#: builtin/commit.c:609
+#: builtin/commit.c:608
 #, c-format
 msgid "invalid date format: %s"
 msgstr "неправильный формат даты: %s"
 
-#: builtin/commit.c:653
+#: builtin/commit.c:652
 msgid ""
 "unable to select a comment character that is not used\n"
 "in the current commit message"
 msgstr "нельзя выбрать символ комментария, который\nне используется в текущем сообщении коммита"
 
-#: builtin/commit.c:690 builtin/commit.c:723 builtin/commit.c:1080
+#: builtin/commit.c:689 builtin/commit.c:722 builtin/commit.c:1079
 #, c-format
 msgid "could not lookup commit %s"
 msgstr "не удалось запросить коммит %s"
 
-#: builtin/commit.c:702 builtin/shortlog.c:273
+#: builtin/commit.c:701 builtin/shortlog.c:273
 #, c-format
 msgid "(reading log message from standard input)\n"
 msgstr "(чтение файла журнала из стандартного ввода)\n"
 
-#: builtin/commit.c:704
+#: builtin/commit.c:703
 msgid "could not read log from standard input"
 msgstr "не удалось прочитать файл журнала из стандартного ввода"
 
-#: builtin/commit.c:708
+#: builtin/commit.c:707
 #, c-format
 msgid "could not read log file '%s'"
 msgstr "не удалось прочитать файл журнала «%s»"
 
-#: builtin/commit.c:730
+#: builtin/commit.c:729
 msgid "could not read MERGE_MSG"
 msgstr "не удалось прочитать MERGE_MSG"
 
-#: builtin/commit.c:734
+#: builtin/commit.c:733
 msgid "could not read SQUASH_MSG"
 msgstr "не удалось прочитать SQUASH_MSG"
 
-#: builtin/commit.c:738 builtin/merge.c:1079
-#, c-format
-msgid "could not read '%s'"
-msgstr "не удалось прочитать «%s»"
-
-#: builtin/commit.c:785
+#: builtin/commit.c:784
 msgid "could not write commit template"
 msgstr "не удалось записать шаблон описания коммита"
 
-#: builtin/commit.c:803
+#: builtin/commit.c:802
 #, c-format
 msgid ""
 "\n"
@@ -4307,7 +4766,7 @@ msgid ""
 "and try again.\n"
 msgstr "\nПохоже, что вы пытаетесь закоммитить слияние.\nЕсли это ошибка, пожалуйста удалите файл\n\t%s\nи попробуйте снова.\n"
 
-#: builtin/commit.c:808
+#: builtin/commit.c:807
 #, c-format
 msgid ""
 "\n"
@@ -4317,14 +4776,14 @@ msgid ""
 "and try again.\n"
 msgstr "\nПохоже, что вы пытаетесь закоммитить отбор лучшего.\nЕсли это ошибка, пожалуйста удалите файл\n\t%s\nи попробуйте снова.\n"
 
-#: builtin/commit.c:821
+#: builtin/commit.c:820
 #, c-format
 msgid ""
 "Please enter the commit message for your changes. Lines starting\n"
 "with '%c' will be ignored, and an empty message aborts the commit.\n"
 msgstr "Пожалуйста, введите сообщение коммита для ваших изменений. Строки,\nначинающиеся с «%c» будут проигнорированы, а пустое сообщение\nотменяет процесс коммита.\n"
 
-#: builtin/commit.c:828
+#: builtin/commit.c:827
 #, c-format
 msgid ""
 "Please enter the commit message for your changes. Lines starting\n"
@@ -4332,348 +4791,335 @@ msgid ""
 "An empty message aborts the commit.\n"
 msgstr "Пожалуйста, введите сообщение коммита для ваших изменений. Строки,\nначинающиеся с «%c» будут оставлены; вы можете удалить их вручную,\nесли хотите. Пустое сообщение отменяет процесс коммита.\n"
 
-#: builtin/commit.c:848
+#: builtin/commit.c:847
 #, c-format
 msgid "%sAuthor:    %.*s <%.*s>"
 msgstr "%sАвтор:     %.*s <%.*s>"
 
-#: builtin/commit.c:856
+#: builtin/commit.c:855
 #, c-format
 msgid "%sDate:      %s"
 msgstr "%sДата:      %s"
 
-#: builtin/commit.c:863
+#: builtin/commit.c:862
 #, c-format
 msgid "%sCommitter: %.*s <%.*s>"
 msgstr "%sКоммитер:  %.*s <%.*s>"
 
-#: builtin/commit.c:881
+#: builtin/commit.c:880
 msgid "Cannot read index"
 msgstr "Не удалось прочитать индекс"
 
-#: builtin/commit.c:938
+#: builtin/commit.c:937
 msgid "Error building trees"
 msgstr "Ошибка при построении деревьев"
 
-#: builtin/commit.c:953 builtin/tag.c:495
+#: builtin/commit.c:952 builtin/tag.c:495
 #, c-format
 msgid "Please supply the message using either -m or -F option.\n"
 msgstr "Пожалуйста, укажите сообщение, при указании опций -m или -F.\n"
 
-#: builtin/commit.c:1055
+#: builtin/commit.c:1054
 #, c-format
 msgid "--author '%s' is not 'Name <email>' and matches no existing author"
 msgstr "--author «%s» не в формате «Имя <почта>» и не совпадает с существующим автором"
 
-#: builtin/commit.c:1070 builtin/commit.c:1310
+#: builtin/commit.c:1069 builtin/commit.c:1309
 #, c-format
 msgid "Invalid untracked files mode '%s'"
 msgstr "Неправильный режим неотслеживаемых файлов «%s»"
 
-#: builtin/commit.c:1107
+#: builtin/commit.c:1106
 msgid "--long and -z are incompatible"
 msgstr "--long и -z нельзя использовать одновременно"
 
-#: builtin/commit.c:1137
+#: builtin/commit.c:1136
 msgid "Using both --reset-author and --author does not make sense"
 msgstr "Указание одновременно опций --reset-author и --author не имеет смысла"
 
-#: builtin/commit.c:1146
+#: builtin/commit.c:1145
 msgid "You have nothing to amend."
 msgstr "Нечего исправлять."
 
-#: builtin/commit.c:1149
+#: builtin/commit.c:1148
 msgid "You are in the middle of a merge -- cannot amend."
 msgstr "Вы в процессе слияния —  сейчас нельзя исправлять."
 
-#: builtin/commit.c:1151
+#: builtin/commit.c:1150
 msgid "You are in the middle of a cherry-pick -- cannot amend."
 msgstr "Вы в процессе отбора лучшего —  сейчас нельзя исправлять."
 
-#: builtin/commit.c:1154
+#: builtin/commit.c:1153
 msgid "Options --squash and --fixup cannot be used together"
 msgstr "Опции --squash и --fixup не могут использоваться одновременно"
 
-#: builtin/commit.c:1164
+#: builtin/commit.c:1163
 msgid "Only one of -c/-C/-F/--fixup can be used."
 msgstr "Может использоваться только одна из опций -c/-C/-F/--fixup."
 
-#: builtin/commit.c:1166
+#: builtin/commit.c:1165
 msgid "Option -m cannot be combined with -c/-C/-F/--fixup."
 msgstr "Опция -m не может использоваться с -c/-C/-F/--fixup."
 
-#: builtin/commit.c:1174
+#: builtin/commit.c:1173
 msgid "--reset-author can be used only with -C, -c or --amend."
 msgstr "--reset-author может использоваться только одновременно с опциями -C, -c или --amend."
 
-#: builtin/commit.c:1191
+#: builtin/commit.c:1190
 msgid "Only one of --include/--only/--all/--interactive/--patch can be used."
 msgstr "Может использоваться только одна из опций --include/--only/--all/--interactive/--patch."
 
-#: builtin/commit.c:1193
+#: builtin/commit.c:1192
 msgid "No paths with --include/--only does not make sense."
 msgstr "Указание путей каталогов с опциями --include/--only не имеет смысла."
 
-#: builtin/commit.c:1195
+#: builtin/commit.c:1194
 msgid "Clever... amending the last one with dirty index."
 msgstr "Умно… отмена последнего с измененным индексом."
 
-#: builtin/commit.c:1197
+#: builtin/commit.c:1196
 msgid "Explicit paths specified without -i or -o; assuming --only paths..."
 msgstr "Пути явно указаны пути без опций -i или -o; предполагаю опцию --only…"
 
-#: builtin/commit.c:1209 builtin/tag.c:728
+#: builtin/commit.c:1208 builtin/tag.c:730
 #, c-format
 msgid "Invalid cleanup mode %s"
 msgstr "Неправильное значение режима очистки %s"
 
-#: builtin/commit.c:1214
+#: builtin/commit.c:1213
 msgid "Paths with -a does not make sense."
 msgstr "С опцией -a указание пути не имеет смысла."
 
-#: builtin/commit.c:1324 builtin/commit.c:1603
+#: builtin/commit.c:1323 builtin/commit.c:1602
 msgid "show status concisely"
 msgstr "кратко показать статус"
 
-#: builtin/commit.c:1326 builtin/commit.c:1605
+#: builtin/commit.c:1325 builtin/commit.c:1604
 msgid "show branch information"
 msgstr "показать информацию о версии"
 
-#: builtin/commit.c:1328 builtin/commit.c:1607 builtin/push.c:509
+#: builtin/commit.c:1327 builtin/commit.c:1606 builtin/push.c:546
 msgid "machine-readable output"
 msgstr "машиночитаемый вывод"
 
-#: builtin/commit.c:1331 builtin/commit.c:1609
+#: builtin/commit.c:1330 builtin/commit.c:1608
 msgid "show status in long format (default)"
 msgstr "показать статус в длинном формате (по умолчанию)"
 
-#: builtin/commit.c:1334 builtin/commit.c:1612
+#: builtin/commit.c:1333 builtin/commit.c:1611
 msgid "terminate entries with NUL"
 msgstr "завершать записи НУЛЕВЫМ байтом"
 
-#: builtin/commit.c:1336 builtin/commit.c:1615 builtin/fast-export.c:980
-#: builtin/fast-export.c:983 builtin/tag.c:603
+#: builtin/commit.c:1335 builtin/commit.c:1614 builtin/fast-export.c:981
+#: builtin/fast-export.c:984 builtin/tag.c:604
 msgid "mode"
 msgstr "режим"
 
-#: builtin/commit.c:1337 builtin/commit.c:1615
+#: builtin/commit.c:1336 builtin/commit.c:1614
 msgid "show untracked files, optional modes: all, normal, no. (Default: all)"
 msgstr "показать неотслеживаемые файлы, опциональные режимы: all (все), normal (как обычно), no (нет). (По умолчанию: all)"
 
-#: builtin/commit.c:1340
+#: builtin/commit.c:1339
 msgid "show ignored files"
 msgstr "показать игнорируемые файлы"
 
-#: builtin/commit.c:1341 parse-options.h:152
+#: builtin/commit.c:1340 parse-options.h:155
 msgid "when"
 msgstr "когда"
 
-#: builtin/commit.c:1342
+#: builtin/commit.c:1341
 msgid ""
 "ignore changes to submodules, optional when: all, dirty, untracked. "
 "(Default: all)"
 msgstr "игнорировать изменения в подмодулях, опционально когда: all (всегда), dirty (измененные), untracked (неотслеживаемые). (По умолчанию: all)"
 
-#: builtin/commit.c:1344
+#: builtin/commit.c:1343
 msgid "list untracked files in columns"
 msgstr "показать неотслеживаемые файлы по столбцам"
 
-#: builtin/commit.c:1430
+#: builtin/commit.c:1429
 msgid "couldn't look up newly created commit"
 msgstr "нельзя запросить новосозданный коммит"
 
-#: builtin/commit.c:1432
+#: builtin/commit.c:1431
 msgid "could not parse newly created commit"
 msgstr "нельзя разобрать новосозданный коммит"
 
-#: builtin/commit.c:1477
+#: builtin/commit.c:1476
 msgid "detached HEAD"
 msgstr "отделенный HEAD"
 
-#: builtin/commit.c:1480
+#: builtin/commit.c:1479
 msgid " (root-commit)"
 msgstr " (корневой коммит)"
 
-#: builtin/commit.c:1573
+#: builtin/commit.c:1572
 msgid "suppress summary after successful commit"
 msgstr "не выводить сводку после успешного коммита"
 
-#: builtin/commit.c:1574
+#: builtin/commit.c:1573
 msgid "show diff in commit message template"
 msgstr "добавить список изменений в шаблон сообщения коммита"
 
-#: builtin/commit.c:1576
+#: builtin/commit.c:1575
 msgid "Commit message options"
 msgstr "Опции сообщения коммита"
 
-#: builtin/commit.c:1577 builtin/tag.c:601
+#: builtin/commit.c:1576 builtin/tag.c:602
 msgid "read message from file"
 msgstr "прочитать сообщение из файла"
 
-#: builtin/commit.c:1578
+#: builtin/commit.c:1577
 msgid "author"
 msgstr "автор"
 
-#: builtin/commit.c:1578
+#: builtin/commit.c:1577
 msgid "override author for commit"
 msgstr "подменить автора коммита"
 
-#: builtin/commit.c:1579 builtin/gc.c:280
+#: builtin/commit.c:1578 builtin/gc.c:268
 msgid "date"
 msgstr "дата"
 
-#: builtin/commit.c:1579
+#: builtin/commit.c:1578
 msgid "override date for commit"
 msgstr "подменить дату коммита"
 
-#: builtin/commit.c:1580 builtin/merge.c:218 builtin/notes.c:391
-#: builtin/notes.c:554 builtin/tag.c:599
+#: builtin/commit.c:1579 builtin/merge.c:218 builtin/notes.c:392
+#: builtin/notes.c:555 builtin/tag.c:600
 msgid "message"
 msgstr "сообщение"
 
-#: builtin/commit.c:1580
+#: builtin/commit.c:1579
 msgid "commit message"
 msgstr "сообщение коммита"
 
-#: builtin/commit.c:1581
+#: builtin/commit.c:1580
 msgid "reuse and edit message from specified commit"
 msgstr "использовать и отредактировать сообщение от указанного коммита"
 
-#: builtin/commit.c:1582
+#: builtin/commit.c:1581
 msgid "reuse message from specified commit"
 msgstr "использовать сообщение указанного коммита"
 
-#: builtin/commit.c:1583
+#: builtin/commit.c:1582
 msgid "use autosquash formatted message to fixup specified commit"
 msgstr "использовать форматированное сообщение автоуплотнения для исправления указанного коммита"
 
-#: builtin/commit.c:1584
+#: builtin/commit.c:1583
 msgid "use autosquash formatted message to squash specified commit"
 msgstr "использовать форматированное сообщение автоуплотнения для уплотнения указанного коммита"
 
-#: builtin/commit.c:1585
+#: builtin/commit.c:1584
 msgid "the commit is authored by me now (used with -C/-c/--amend)"
 msgstr "коммит теперь за моим авторством (с использованием -C/-c/--amend)"
 
-#: builtin/commit.c:1586 builtin/log.c:1191 builtin/revert.c:86
+#: builtin/commit.c:1585 builtin/log.c:1216 builtin/revert.c:86
 msgid "add Signed-off-by:"
 msgstr "добавить Signed-off-by:"
 
-#: builtin/commit.c:1587
+#: builtin/commit.c:1586
 msgid "use specified template file"
 msgstr "использовать указанный файл шаблона"
 
-#: builtin/commit.c:1588
+#: builtin/commit.c:1587
 msgid "force edit of commit"
 msgstr "принудительно редактировать коммит"
 
-#: builtin/commit.c:1589
+#: builtin/commit.c:1588
 msgid "default"
 msgstr "по-умолчанию"
 
-#: builtin/commit.c:1589 builtin/tag.c:604
+#: builtin/commit.c:1588 builtin/tag.c:605
 msgid "how to strip spaces and #comments from message"
 msgstr "как удалять пробелы и #комментарии из сообщения коммита"
 
-#: builtin/commit.c:1590
+#: builtin/commit.c:1589
 msgid "include status in commit message template"
 msgstr "включить статус файлов в шаблон сообщения коммита"
 
-#: builtin/commit.c:1591 builtin/merge.c:225 builtin/revert.c:92
-#: builtin/tag.c:605
-msgid "key-id"
-msgstr "key-id"
-
-#: builtin/commit.c:1592 builtin/merge.c:226 builtin/revert.c:93
+#: builtin/commit.c:1591 builtin/merge.c:226 builtin/pull.c:156
+#: builtin/revert.c:93
 msgid "GPG sign commit"
 msgstr "подписать коммит с помощью GPG"
 
-#: builtin/commit.c:1595
+#: builtin/commit.c:1594
 msgid "Commit contents options"
 msgstr "Опции содержимого коммита"
 
-#: builtin/commit.c:1596
+#: builtin/commit.c:1595
 msgid "commit all changed files"
 msgstr "закоммитить все измененные файлы"
 
-#: builtin/commit.c:1597
+#: builtin/commit.c:1596
 msgid "add specified files to index for commit"
 msgstr "добавить указанные файлы в индекс для коммита"
 
-#: builtin/commit.c:1598
+#: builtin/commit.c:1597
 msgid "interactively add files"
 msgstr "интерактивное добавление файлов"
 
-#: builtin/commit.c:1599
+#: builtin/commit.c:1598
 msgid "interactively add changes"
 msgstr "интерактивное добавление изменений"
 
-#: builtin/commit.c:1600
+#: builtin/commit.c:1599
 msgid "commit only specified files"
 msgstr "закоммитить только указанные файлы"
 
-#: builtin/commit.c:1601
+#: builtin/commit.c:1600
 msgid "bypass pre-commit hook"
 msgstr "пропустить перехватчик перед-коммитом"
 
-#: builtin/commit.c:1602
+#: builtin/commit.c:1601
 msgid "show what would be committed"
 msgstr "показать, что будет закоммичено"
 
-#: builtin/commit.c:1613
+#: builtin/commit.c:1612
 msgid "amend previous commit"
 msgstr "исправить предыдущий коммит"
 
-#: builtin/commit.c:1614
+#: builtin/commit.c:1613
 msgid "bypass post-rewrite hook"
 msgstr "пропустить перехватчик после-перезаписи"
 
-#: builtin/commit.c:1619
+#: builtin/commit.c:1618
 msgid "ok to record an empty change"
 msgstr "разрешить запись пустого коммита"
 
-#: builtin/commit.c:1621
+#: builtin/commit.c:1620
 msgid "ok to record a change with an empty message"
 msgstr "разрешить запись изменений с пустым сообщением"
 
-#: builtin/commit.c:1650
+#: builtin/commit.c:1649
 msgid "could not parse HEAD commit"
 msgstr "не удалось разобрать HEAD коммит"
 
-#: builtin/commit.c:1689 builtin/merge.c:1076
-#, c-format
-msgid "could not open '%s' for reading"
-msgstr "не удалось открыть «%s» для чтения"
-
-#: builtin/commit.c:1696
+#: builtin/commit.c:1695
 #, c-format
 msgid "Corrupt MERGE_HEAD file (%s)"
 msgstr "Файл MERGE_HEAD поврежден (%s)"
 
-#: builtin/commit.c:1703
+#: builtin/commit.c:1702
 msgid "could not read MERGE_MODE"
 msgstr "не удалось прочитать MERGE_MODE"
 
-#: builtin/commit.c:1722
+#: builtin/commit.c:1721
 #, c-format
 msgid "could not read commit message: %s"
 msgstr "не удалось открыть сообщение коммита: %s"
 
-#: builtin/commit.c:1733
+#: builtin/commit.c:1732
 #, c-format
 msgid "Aborting commit; you did not edit the message.\n"
 msgstr "Отмена коммита; вы не изменили сообщение.\n"
 
-#: builtin/commit.c:1738
+#: builtin/commit.c:1737
 #, c-format
 msgid "Aborting commit due to empty commit message.\n"
 msgstr "Отмена коммита из-за пустого сообщения коммита.\n"
 
-#: builtin/commit.c:1753 builtin/merge.c:829 builtin/merge.c:854
-msgid "failed to write commit object"
-msgstr "сбой записи объекта коммита"
-
-#: builtin/commit.c:1786
+#: builtin/commit.c:1785
 msgid ""
 "Repository has been updated, but unable to write\n"
 "new_index file. Check that disk is not full and quota is\n"
@@ -4684,131 +5130,135 @@ msgstr "Репозиторий был обновлен, но не удалось
 msgid "git config [<options>]"
 msgstr "git config [<опции>]"
 
-#: builtin/config.c:53
+#: builtin/config.c:54
 msgid "Config file location"
 msgstr "Размещение файла конфигурации"
 
-#: builtin/config.c:54
+#: builtin/config.c:55
 msgid "use global config file"
 msgstr "использовать глобальный файл конфигурации"
 
-#: builtin/config.c:55
+#: builtin/config.c:56
 msgid "use system config file"
 msgstr "использовать системный файл конфигурации"
 
-#: builtin/config.c:56
+#: builtin/config.c:57
 msgid "use repository config file"
 msgstr "использовать файл конфигурации репозитория"
 
-#: builtin/config.c:57
+#: builtin/config.c:58
 msgid "use given config file"
 msgstr "использовать указанный файл конфигурации"
 
-#: builtin/config.c:58
+#: builtin/config.c:59
 msgid "blob-id"
 msgstr "идент-двоичн-объекта"
 
-#: builtin/config.c:58
+#: builtin/config.c:59
 msgid "read config from given blob object"
 msgstr "прочитать настройки из указанного двоичного объекта"
 
-#: builtin/config.c:59
+#: builtin/config.c:60
 msgid "Action"
 msgstr "Действие"
 
-#: builtin/config.c:60
+#: builtin/config.c:61
 msgid "get value: name [value-regex]"
 msgstr "получить значение: имя [шаблон-значений]"
 
-#: builtin/config.c:61
+#: builtin/config.c:62
 msgid "get all values: key [value-regex]"
 msgstr "получить все значения: ключ [шаблон-значений]"
 
-#: builtin/config.c:62
+#: builtin/config.c:63
 msgid "get values for regexp: name-regex [value-regex]"
 msgstr "получить значения по шаблону: шаблон-имен [шаблон-значений]"
 
-#: builtin/config.c:63
+#: builtin/config.c:64
 msgid "get value specific for the URL: section[.var] URL"
 msgstr "получить значение, специфичное для URL: раздел[.переменная] URL"
 
-#: builtin/config.c:64
+#: builtin/config.c:65
 msgid "replace all matching variables: name value [value_regex]"
 msgstr "заменить все соответствующие переменные: имя значение [шаблон-значений]"
 
-#: builtin/config.c:65
+#: builtin/config.c:66
 msgid "add a new variable: name value"
 msgstr "добавить новую переменную: имя значение"
 
-#: builtin/config.c:66
+#: builtin/config.c:67
 msgid "remove a variable: name [value-regex]"
 msgstr "удалить переменную: имя [шаблон-значений]"
 
-#: builtin/config.c:67
+#: builtin/config.c:68
 msgid "remove all matches: name [value-regex]"
 msgstr "удалить все совпадающие: имя [шаблон-значений]"
 
-#: builtin/config.c:68
+#: builtin/config.c:69
 msgid "rename section: old-name new-name"
 msgstr "переименовать раздел: старое-имя новое-имя"
 
-#: builtin/config.c:69
+#: builtin/config.c:70
 msgid "remove a section: name"
 msgstr "удалить раздел: имя"
 
-#: builtin/config.c:70
+#: builtin/config.c:71
 msgid "list all"
 msgstr "показать весь список"
 
-#: builtin/config.c:71
+#: builtin/config.c:72
 msgid "open an editor"
 msgstr "открыть в редакторе"
 
-#: builtin/config.c:72
+#: builtin/config.c:73
 msgid "find the color configured: slot [default]"
 msgstr "найти настроенный цвет: раздел [по-умолчанию]"
 
-#: builtin/config.c:73
+#: builtin/config.c:74
 msgid "find the color setting: slot [stdout-is-tty]"
 msgstr "проверить, существует ли настроенный цвет: раздел [stdout-есть-tty]"
 
-#: builtin/config.c:74
+#: builtin/config.c:75
 msgid "Type"
 msgstr "Тип"
 
-#: builtin/config.c:75
+#: builtin/config.c:76
 msgid "value is \"true\" or \"false\""
 msgstr "значение — это «true» (правда) или «false» (ложь)"
 
-#: builtin/config.c:76
+#: builtin/config.c:77
 msgid "value is decimal number"
 msgstr "значение — это десятичное число"
 
-#: builtin/config.c:77
+#: builtin/config.c:78
 msgid "value is --bool or --int"
 msgstr "значение — это --bool или --int"
 
-#: builtin/config.c:78
+#: builtin/config.c:79
 msgid "value is a path (file or directory name)"
 msgstr "значение — это путь (к файлу или каталогу)"
 
-#: builtin/config.c:79
+#: builtin/config.c:80
 msgid "Other"
 msgstr "Другое"
 
-#: builtin/config.c:80
+#: builtin/config.c:81
 msgid "terminate values with NUL byte"
 msgstr "завершать значения НУЛЕВЫМ байтом"
 
-#: builtin/config.c:81
+#: builtin/config.c:82
+msgid "show variable names only"
+msgstr "показывать только имена переменных"
+
+#: builtin/config.c:83
 msgid "respect include directives on lookup"
 msgstr "учитывать директивы include (включения файлов) при запросе"
 
-#: builtin/config.c:316
+#: builtin/config.c:311
 msgid "unable to parse default color value"
 msgstr "не удалось разобрать значение цвета по умолчанию"
 
-#: builtin/config.c:457
+#: builtin/config.c:449
 #, c-format
 msgid ""
 "# This is Git's per-user configuration file.\n"
@@ -4818,7 +5268,7 @@ msgid ""
 "#\temail = %s\n"
 msgstr "# Это файл конфигурации пользователя Git.\n[user]\n# Пожалуйста, адаптируйте и раскомментируйте следующие строки:\n#\tuser = %s\n#\temail = %s\n"
 
-#: builtin/config.c:587
+#: builtin/config.c:583
 #, c-format
 msgid "cannot create configuration file %s"
 msgstr "не удалось создать файл конфигурации %s"
@@ -4854,7 +5304,7 @@ msgstr "аннотированная метка %s не содержит вст
 msgid "tag '%s' is really '%s' here"
 msgstr "метка «%s» уже здесь «%s»"
 
-#: builtin/describe.c:250 builtin/log.c:452
+#: builtin/describe.c:250 builtin/log.c:459
 #, c-format
 msgid "Not a valid object name %s"
 msgstr "Недействительное имя объекта %s"
@@ -4994,502 +5444,491 @@ msgstr "передано больше двух двоичных объектов
 msgid "unhandled object '%s' given."
 msgstr "передан необработанный объект «%s»."
 
-#: builtin/fast-export.c:24
+#: builtin/fast-export.c:25
 msgid "git fast-export [rev-list-opts]"
 msgstr "git fast-export [опции-rev-list]"
 
-#: builtin/fast-export.c:979
+#: builtin/fast-export.c:980
 msgid "show progress after <n> objects"
 msgstr "показать прогресс после <n> объектов"
 
-#: builtin/fast-export.c:981
+#: builtin/fast-export.c:982
 msgid "select handling of signed tags"
 msgstr "выбор обработки подписанных меток"
 
-#: builtin/fast-export.c:984
+#: builtin/fast-export.c:985
 msgid "select handling of tags that tag filtered objects"
 msgstr "выбор обработки меток, которыми помечены отфильтрованные объекты"
 
-#: builtin/fast-export.c:987
+#: builtin/fast-export.c:988
 msgid "Dump marks to this file"
 msgstr "Записать пометки в этот файл"
 
-#: builtin/fast-export.c:989
+#: builtin/fast-export.c:990
 msgid "Import marks from this file"
 msgstr "Импортировать пометки из этого файла"
 
-#: builtin/fast-export.c:991
+#: builtin/fast-export.c:992
 msgid "Fake a tagger when tags lack one"
 msgstr "Подделать автора метки, если у метки он отсутствует"
 
-#: builtin/fast-export.c:993
+#: builtin/fast-export.c:994
 msgid "Output full tree for each commit"
 msgstr "Вывести полное дерево для каждого коммита"
 
-#: builtin/fast-export.c:995
+#: builtin/fast-export.c:996
 msgid "Use the done feature to terminate the stream"
 msgstr "Использовать пометку завершения в конце потока"
 
-#: builtin/fast-export.c:996
+#: builtin/fast-export.c:997
 msgid "Skip output of blob data"
 msgstr "Пропустить вывод данных двоичных объектов"
 
-#: builtin/fast-export.c:997
+#: builtin/fast-export.c:998
 msgid "refspec"
 msgstr "спецификация ссылки"
 
-#: builtin/fast-export.c:998
+#: builtin/fast-export.c:999
 msgid "Apply refspec to exported refs"
 msgstr "Применить спецификацию ссылки к экспортируемым ссылкам"
 
-#: builtin/fast-export.c:999
+#: builtin/fast-export.c:1000
 msgid "anonymize output"
 msgstr "сделать вывод анонимным"
 
-#: builtin/fetch.c:19
+#: builtin/fetch.c:20
 msgid "git fetch [<options>] [<repository> [<refspec>...]]"
 msgstr "git fetch [<опции>] [<репозиторий> [<спецификация-ссылки>…]]"
 
-#: builtin/fetch.c:20
+#: builtin/fetch.c:21
 msgid "git fetch [<options>] <group>"
 msgstr "git fetch [<опции>] <группа>"
 
-#: builtin/fetch.c:21
+#: builtin/fetch.c:22
 msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]"
 msgstr "git fetch --multiple [<опции>] [(<репозиторий> | <группа>)…]"
 
-#: builtin/fetch.c:22
+#: builtin/fetch.c:23
 msgid "git fetch --all [<options>]"
 msgstr "git fetch --all [<опции>]"
 
-#: builtin/fetch.c:89
+#: builtin/fetch.c:90 builtin/pull.c:162
 msgid "fetch from all remotes"
 msgstr "извлечь со всех внешних репозиториев"
 
-#: builtin/fetch.c:91
+#: builtin/fetch.c:92 builtin/pull.c:165
 msgid "append to .git/FETCH_HEAD instead of overwriting"
 msgstr "дописать к .git/FETCH_HEAD вместо перезаписи"
 
-#: builtin/fetch.c:93
+#: builtin/fetch.c:94 builtin/pull.c:168
 msgid "path to upload pack on remote end"
 msgstr "путь к программе упаковки пакета на машине с внешним репозиторием"
 
-#: builtin/fetch.c:94
+#: builtin/fetch.c:95 builtin/pull.c:170
 msgid "force overwrite of local branch"
 msgstr "принудительная перезапись локальной ветки"
 
-#: builtin/fetch.c:96
+#: builtin/fetch.c:97
 msgid "fetch from multiple remotes"
 msgstr "извлечь с нескольких внешних репозиториев"
 
-#: builtin/fetch.c:98
+#: builtin/fetch.c:99 builtin/pull.c:172
 msgid "fetch all tags and associated objects"
 msgstr "извлечь все метки и связанные объекты"
 
-#: builtin/fetch.c:100
+#: builtin/fetch.c:101
 msgid "do not fetch all tags (--no-tags)"
 msgstr "не извлекать все метки (--no-tags)"
 
-#: builtin/fetch.c:102
+#: builtin/fetch.c:103 builtin/pull.c:175
 msgid "prune remote-tracking branches no longer on remote"
 msgstr "почистить отслеживаемые внешние ветки, которых уже нет на внешнем репозитории"
 
-#: builtin/fetch.c:103
+#: builtin/fetch.c:104 builtin/pull.c:178
 msgid "on-demand"
 msgstr "по требованию"
 
-#: builtin/fetch.c:104
+#: builtin/fetch.c:105 builtin/pull.c:179
 msgid "control recursive fetching of submodules"
 msgstr "управление рекурсивным извлечением подмодулей"
 
-#: builtin/fetch.c:108
+#: builtin/fetch.c:109 builtin/pull.c:184
 msgid "keep downloaded pack"
 msgstr "оставить загруженный пакет данных"
 
-#: builtin/fetch.c:110
+#: builtin/fetch.c:111
 msgid "allow updating of HEAD ref"
 msgstr "разрешить обновление ссылки HEAD"
 
-#: builtin/fetch.c:113
+#: builtin/fetch.c:114 builtin/pull.c:187
 msgid "deepen history of shallow clone"
 msgstr "глубокая история частичного клона"
 
-#: builtin/fetch.c:115
+#: builtin/fetch.c:116 builtin/pull.c:190
 msgid "convert to a complete repository"
 msgstr "преобразовать в полный репозиторий"
 
-#: builtin/fetch.c:117 builtin/log.c:1208
+#: builtin/fetch.c:118 builtin/log.c:1233
 msgid "dir"
 msgstr "каталог"
 
-#: builtin/fetch.c:118
+#: builtin/fetch.c:119
 msgid "prepend this to submodule path output"
 msgstr "присоединять это спереди к выводу путей подмодуля"
 
-#: builtin/fetch.c:121
+#: builtin/fetch.c:122
 msgid "default mode for recursion"
 msgstr "режим по умолчанию для рекурсии"
 
-#: builtin/fetch.c:123
+#: builtin/fetch.c:124 builtin/pull.c:193
 msgid "accept refs that update .git/shallow"
 msgstr "принимать ссылки, которые обновляют .git/shallow"
 
-#: builtin/fetch.c:124
+#: builtin/fetch.c:125 builtin/pull.c:195
 msgid "refmap"
 msgstr "соответствие-ссылок"
 
-#: builtin/fetch.c:125
+#: builtin/fetch.c:126 builtin/pull.c:196
 msgid "specify fetch refmap"
 msgstr "указать соответствие ссылок при извлечении"
 
-#: builtin/fetch.c:377
+#: builtin/fetch.c:378
 msgid "Couldn't find remote ref HEAD"
 msgstr "Не удалось найти ссылку HEAD на внешнем репозитории"
 
-#: builtin/fetch.c:457
+#: builtin/fetch.c:458
 #, c-format
 msgid "object %s not found"
 msgstr "объект %s не найден"
 
-#: builtin/fetch.c:462
+#: builtin/fetch.c:463
 msgid "[up to date]"
 msgstr "[актуально]"
 
-#: builtin/fetch.c:476
+#: builtin/fetch.c:477
 #, c-format
 msgid "! %-*s %-*s -> %s  (can't fetch in current branch)"
 msgstr "! %-*s %-*s → %s  (не удалось извлечь в текущую ветку)"
 
-#: builtin/fetch.c:477 builtin/fetch.c:563
+#: builtin/fetch.c:478 builtin/fetch.c:564
 msgid "[rejected]"
 msgstr "[отклонено]"
 
-#: builtin/fetch.c:488
+#: builtin/fetch.c:489
 msgid "[tag update]"
 msgstr "[обновление метки]"
 
-#: builtin/fetch.c:490 builtin/fetch.c:525 builtin/fetch.c:543
+#: builtin/fetch.c:491 builtin/fetch.c:526 builtin/fetch.c:544
 msgid "  (unable to update local ref)"
 msgstr "  (не удалось обновить локальную ссылку)"
 
-#: builtin/fetch.c:508
+#: builtin/fetch.c:509
 msgid "[new tag]"
 msgstr "[новая метка]"
 
-#: builtin/fetch.c:511
+#: builtin/fetch.c:512
 msgid "[new branch]"
 msgstr "[новая ветка]"
 
-#: builtin/fetch.c:514
+#: builtin/fetch.c:515
 msgid "[new ref]"
 msgstr "[новая ссылка]"
 
-#: builtin/fetch.c:559
+#: builtin/fetch.c:560
 msgid "unable to update local ref"
 msgstr "не удалось обновить локальную ссылку"
 
-#: builtin/fetch.c:559
+#: builtin/fetch.c:560
 msgid "forced update"
 msgstr "принудительное обновление"
 
-#: builtin/fetch.c:565
+#: builtin/fetch.c:566
 msgid "(non-fast-forward)"
 msgstr "(без перемотки вперед)"
 
-#: builtin/fetch.c:599 builtin/fetch.c:832
+#: builtin/fetch.c:600 builtin/fetch.c:842
 #, c-format
 msgid "cannot open %s: %s\n"
 msgstr "не удалось открыть %s: %s\n"
 
-#: builtin/fetch.c:608
+#: builtin/fetch.c:609
 #, c-format
 msgid "%s did not send all necessary objects\n"
 msgstr "%s не отправил все необходимые объекты\n"
 
-#: builtin/fetch.c:626
+#: builtin/fetch.c:627
 #, c-format
 msgid "reject %s because shallow roots are not allowed to be updated"
 msgstr "%s отклонено из-за того, что частичные корни не разрешено обновлять"
 
-#: builtin/fetch.c:714 builtin/fetch.c:797
+#: builtin/fetch.c:715 builtin/fetch.c:807
 #, c-format
 msgid "From %.*s\n"
 msgstr "Из %.*s\n"
 
-#: builtin/fetch.c:725
+#: builtin/fetch.c:726
 #, c-format
 msgid ""
 "some local refs could not be updated; try running\n"
 " 'git remote prune %s' to remove any old, conflicting branches"
 msgstr "не удалось обновить некоторые локальные ссылки; попробуйте запустить «git remote prune %s», чтобы почистить старые, конфликтующие ветки"
 
-#: builtin/fetch.c:777
+#: builtin/fetch.c:778
 #, c-format
 msgid "   (%s will become dangling)"
 msgstr "   (%s будет висящей веткой)"
 
-#: builtin/fetch.c:778
+#: builtin/fetch.c:779
 #, c-format
 msgid "   (%s has become dangling)"
 msgstr "   (%s стала висящей веткой)"
 
-#: builtin/fetch.c:802
+#: builtin/fetch.c:811
 msgid "[deleted]"
 msgstr "[удалено]"
 
-#: builtin/fetch.c:803 builtin/remote.c:1057
+#: builtin/fetch.c:812 builtin/remote.c:1034
 msgid "(none)"
 msgstr "(нет)"
 
-#: builtin/fetch.c:822
+#: builtin/fetch.c:832
 #, c-format
 msgid "Refusing to fetch into current branch %s of non-bare repository"
 msgstr "Отказ получения в текущую ветку %s не голого репозитория"
 
-#: builtin/fetch.c:841
+#: builtin/fetch.c:851
 #, c-format
 msgid "Option \"%s\" value \"%s\" is not valid for %s"
 msgstr "Неправильное значение «%2$s» для параметра «%1$s» для %3$s"
 
-#: builtin/fetch.c:844
+#: builtin/fetch.c:854
 #, c-format
 msgid "Option \"%s\" is ignored for %s\n"
 msgstr "Параметр «%s» игнорируется для %s\n"
 
-#: builtin/fetch.c:900
+#: builtin/fetch.c:910
 #, c-format
 msgid "Don't know how to fetch from %s"
 msgstr "Не знаю как извлечь с %s"
 
-#: builtin/fetch.c:1063
+#: builtin/fetch.c:1071
 #, c-format
 msgid "Fetching %s\n"
 msgstr "Извлечение из %s\n"
 
-#: builtin/fetch.c:1065 builtin/remote.c:90
+#: builtin/fetch.c:1073 builtin/remote.c:90
 #, c-format
 msgid "Could not fetch %s"
 msgstr "Не удалось извлечь %s"
 
-#: builtin/fetch.c:1083
+#: builtin/fetch.c:1091
 msgid ""
 "No remote repository specified.  Please, specify either a URL or a\n"
 "remote name from which new revisions should be fetched."
 msgstr "Не указан внешний репозиторий. Укажите URL или имя внешнего репозитория из которого должны извлекаться новые редакции."
 
-#: builtin/fetch.c:1106
+#: builtin/fetch.c:1114
 msgid "You need to specify a tag name."
 msgstr "Вам нужно указать имя метки."
 
-#: builtin/fetch.c:1148
+#: builtin/fetch.c:1156
 msgid "--depth and --unshallow cannot be used together"
 msgstr "нельзя использовать одновременно --depth и --unshallow"
 
-#: builtin/fetch.c:1150
+#: builtin/fetch.c:1158
 msgid "--unshallow on a complete repository does not make sense"
 msgstr "--unshallow не имеет смысла на полном репозитории"
 
-#: builtin/fetch.c:1173
+#: builtin/fetch.c:1181
 msgid "fetch --all does not take a repository argument"
 msgstr "fetch --all не принимает имя репозитория как аргумент"
 
-#: builtin/fetch.c:1175
+#: builtin/fetch.c:1183
 msgid "fetch --all does not make sense with refspecs"
 msgstr "fetch --all не имеет смысла при указании спецификаций ссылок"
 
-#: builtin/fetch.c:1186
+#: builtin/fetch.c:1194
 #, c-format
 msgid "No such remote or remote group: %s"
 msgstr "Нет такого внешнего репозитория или группы: %s"
 
-#: builtin/fetch.c:1194
+#: builtin/fetch.c:1202
 msgid "Fetching a group and specifying refspecs does not make sense"
 msgstr "Получение группы и указание спецификаций ссылок не имеет смысла"
 
-#: builtin/fmt-merge-msg.c:13
+#: builtin/fmt-merge-msg.c:14
 msgid ""
 "git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"
 msgstr "git fmt-merge-msg [-m <сообщение>] [--log[=<n>] | --no-log] [--file <файл>]"
 
-#: builtin/fmt-merge-msg.c:668 builtin/fmt-merge-msg.c:671 builtin/grep.c:698
-#: builtin/merge.c:198 builtin/repack.c:178 builtin/repack.c:182
-#: builtin/show-branch.c:664 builtin/show-ref.c:180 builtin/tag.c:590
-#: parse-options.h:131 parse-options.h:238
-msgid "n"
-msgstr "n"
-
-#: builtin/fmt-merge-msg.c:669
+#: builtin/fmt-merge-msg.c:670
 msgid "populate log with at most <n> entries from shortlog"
 msgstr "отправить в журнал <n> записей из короткого журнала"
 
-#: builtin/fmt-merge-msg.c:672
+#: builtin/fmt-merge-msg.c:673
 msgid "alias for --log (deprecated)"
 msgstr "сокращение для --log (устаревшее)"
 
-#: builtin/fmt-merge-msg.c:675
+#: builtin/fmt-merge-msg.c:676
 msgid "text"
 msgstr "текст"
 
-#: builtin/fmt-merge-msg.c:676
+#: builtin/fmt-merge-msg.c:677
 msgid "use <text> as start of message"
 msgstr "использовать <текст> как начальное сообщение"
 
-#: builtin/fmt-merge-msg.c:677
+#: builtin/fmt-merge-msg.c:678
 msgid "file to read from"
 msgstr "файл для чтения"
 
-#: builtin/for-each-ref.c:687
-msgid "unable to parse format"
-msgstr "не удалось разобрать формат"
-
-#: builtin/for-each-ref.c:1083
+#: builtin/for-each-ref.c:9
 msgid "git for-each-ref [<options>] [<pattern>]"
 msgstr "git for-each-ref [<опции>] [<шаблон>]"
 
-#: builtin/for-each-ref.c:1098
+#: builtin/for-each-ref.c:24
 msgid "quote placeholders suitably for shells"
 msgstr "выводить указатели места заполнения в подходящем формате для командного процессора"
 
-#: builtin/for-each-ref.c:1100
+#: builtin/for-each-ref.c:26
 msgid "quote placeholders suitably for perl"
 msgstr "выводить указатели места заполнения в подходящем формате для perl"
 
-#: builtin/for-each-ref.c:1102
+#: builtin/for-each-ref.c:28
 msgid "quote placeholders suitably for python"
 msgstr "выводить указатели места заполнения в подходящем формате для python"
 
-#: builtin/for-each-ref.c:1104
+#: builtin/for-each-ref.c:30
 msgid "quote placeholders suitably for Tcl"
 msgstr "выводить указатели места заполнения в подходящем формате для Tcl"
 
-#: builtin/for-each-ref.c:1107
+#: builtin/for-each-ref.c:33
 msgid "show only <n> matched refs"
 msgstr "показать только <n> совпадающих ссылок"
 
-#: builtin/for-each-ref.c:1108 builtin/replace.c:438
-msgid "format"
-msgstr "формат"
-
-#: builtin/for-each-ref.c:1108
+#: builtin/for-each-ref.c:34
 msgid "format to use for the output"
 msgstr "использовать формат для вывода"
 
-#: builtin/for-each-ref.c:1109
+#: builtin/for-each-ref.c:35
 msgid "key"
 msgstr "ключ"
 
-#: builtin/for-each-ref.c:1110
+#: builtin/for-each-ref.c:36
 msgid "field name to sort on"
 msgstr "имя поля, по которому выполнить сортировку"
 
-#: builtin/fsck.c:147 builtin/prune.c:137
+#: builtin/fsck.c:163 builtin/prune.c:137
 msgid "Checking connectivity"
 msgstr "Проверка соединения"
 
-#: builtin/fsck.c:548
+#: builtin/fsck.c:568
 msgid "Checking object directories"
 msgstr "Проверка каталогов объектов"
 
-#: builtin/fsck.c:611
+#: builtin/fsck.c:631
 msgid "git fsck [<options>] [<object>...]"
 msgstr "git fsck [<опции>] [<объект>…]"
 
-#: builtin/fsck.c:617
+#: builtin/fsck.c:637
 msgid "show unreachable objects"
 msgstr "показать недоступные объекты"
 
-#: builtin/fsck.c:618
+#: builtin/fsck.c:638
 msgid "show dangling objects"
 msgstr "показать объекты, на которые нет ссылок"
 
-#: builtin/fsck.c:619
+#: builtin/fsck.c:639
 msgid "report tags"
 msgstr "вывести отчет по меткам"
 
-#: builtin/fsck.c:620
+#: builtin/fsck.c:640
 msgid "report root nodes"
 msgstr "вывести отчет по корневым узлам"
 
-#: builtin/fsck.c:621
+#: builtin/fsck.c:641
 msgid "make index objects head nodes"
 msgstr "воспринимать объекты в индексе как корневые узлы"
 
-#: builtin/fsck.c:622
+#: builtin/fsck.c:642
 msgid "make reflogs head nodes (default)"
 msgstr "создать корневые узлы журналов ссылок (по умолчанию)"
 
-#: builtin/fsck.c:623
+#: builtin/fsck.c:643
 msgid "also consider packs and alternate objects"
 msgstr "также проверять пакеты и альтернативные объекты"
 
-#: builtin/fsck.c:624
+#: builtin/fsck.c:644
+msgid "check only connectivity"
+msgstr "только проверить соединение"
+
+#: builtin/fsck.c:645
 msgid "enable more strict checking"
 msgstr "использовать более строгую проверку"
 
-#: builtin/fsck.c:626
+#: builtin/fsck.c:647
 msgid "write dangling objects in .git/lost-found"
 msgstr "записать объекты на которые нет ссылок в .git/lost-found"
 
-#: builtin/fsck.c:627 builtin/prune.c:107
+#: builtin/fsck.c:648 builtin/prune.c:107
 msgid "show progress"
 msgstr "показать прогресс выполнения"
 
-#: builtin/fsck.c:677
+#: builtin/fsck.c:707
 msgid "Checking objects"
 msgstr "Проверка объектов"
 
-#: builtin/gc.c:24
+#: builtin/gc.c:25
 msgid "git gc [<options>]"
 msgstr "git gc [<опции>]"
 
-#: builtin/gc.c:67
+#: builtin/gc.c:55
 #, c-format
 msgid "Invalid %s: '%s'"
 msgstr "Недействительный %s: «%s»"
 
-#: builtin/gc.c:112
+#: builtin/gc.c:100
 #, c-format
 msgid "insanely long object directory %.*s"
 msgstr "слишком длинный путь к каталогу объекта %.*s"
 
-#: builtin/gc.c:281
+#: builtin/gc.c:269
 msgid "prune unreferenced objects"
 msgstr "почистить объекты, на которые нет ссылок"
 
-#: builtin/gc.c:283
+#: builtin/gc.c:271
 msgid "be more thorough (increased runtime)"
 msgstr "проверять более внимательно (занимает больше времени)"
 
-#: builtin/gc.c:284
+#: builtin/gc.c:272
 msgid "enable auto-gc mode"
 msgstr "включить режим auto-gc"
 
-#: builtin/gc.c:285
+#: builtin/gc.c:273
 msgid "force running gc even if there may be another gc running"
 msgstr "принудительно запустить gc, даже есть другая копия gc уже запущена"
 
-#: builtin/gc.c:327
+#: builtin/gc.c:315
 #, c-format
 msgid "Auto packing the repository in background for optimum performance.\n"
 msgstr "Автоматическая упаковка репозитория в фоне, для оптимальной производительности.\n"
 
-#: builtin/gc.c:329
+#: builtin/gc.c:317
 #, c-format
 msgid "Auto packing the repository for optimum performance.\n"
 msgstr "Автоматическая упаковка репозитория, для оптимальной производительности.\n"
 
-#: builtin/gc.c:330
+#: builtin/gc.c:318
 #, c-format
 msgid "See \"git help gc\" for manual housekeeping.\n"
 msgstr "Смотрите «git help gc» руководства по ручной очистке.\n"
 
-#: builtin/gc.c:348
+#: builtin/gc.c:336
 #, c-format
 msgid ""
 "gc is already running on machine '%s' pid %<PRIuMAX> (use --force if not)"
 msgstr "gc уже запущен на этом компьютере «%s» pid %<PRIuMAX> (если нет, используйте --force)"
 
-#: builtin/gc.c:376
+#: builtin/gc.c:364
 msgid ""
 "There are too many unreachable loose objects; run 'git prune' to remove "
 "them."
@@ -5730,7 +6169,7 @@ msgstr "git hash-object [-t <тип>] [-w] [--path=<файл> | --no-filters] [-
 msgid "git hash-object  --stdin-paths < <list-of-paths>"
 msgstr "git hash-object  --stdin-paths < <список-путей>"
 
-#: builtin/hash-object.c:92 builtin/tag.c:612
+#: builtin/hash-object.c:92 builtin/tag.c:614
 msgid "type"
 msgstr "тип"
 
@@ -5879,27 +6318,27 @@ msgstr "использование: %s%s"
 msgid "`git %s' is aliased to `%s'"
 msgstr "«git %s» — это сокращение для «%s»"
 
-#: builtin/index-pack.c:151
+#: builtin/index-pack.c:152
 #, c-format
 msgid "unable to open %s"
 msgstr "не удалось открыть %s"
 
-#: builtin/index-pack.c:201
+#: builtin/index-pack.c:202
 #, c-format
 msgid "object type mismatch at %s"
 msgstr "несоответствие типа объекта на %s"
 
-#: builtin/index-pack.c:221
+#: builtin/index-pack.c:222
 #, c-format
 msgid "did not receive expected object %s"
 msgstr "ожидаемый объект не получен на %s"
 
-#: builtin/index-pack.c:224
+#: builtin/index-pack.c:225
 #, c-format
 msgid "object %s: expected type %s, found %s"
 msgstr "объект %s: ожидаемый тип %s, получен %s"
 
-#: builtin/index-pack.c:266
+#: builtin/index-pack.c:267
 #, c-format
 msgid "cannot fill %d byte"
 msgid_plural "cannot fill %d bytes"
@@ -5908,69 +6347,69 @@ msgstr[1] "не удалось заполнить %d байта"
 msgstr[2] "не удалось заполнить %d байтов"
 msgstr[3] "не удалось заполнить %d байтов"
 
-#: builtin/index-pack.c:276
+#: builtin/index-pack.c:277
 msgid "early EOF"
 msgstr "неожиданный конец файла"
 
-#: builtin/index-pack.c:277
+#: builtin/index-pack.c:278
 msgid "read error on input"
 msgstr "ошибка чтения ввода"
 
-#: builtin/index-pack.c:289
+#: builtin/index-pack.c:290
 msgid "used more bytes than were available"
 msgstr "использовано больше байт, чем было доступно"
 
-#: builtin/index-pack.c:296
+#: builtin/index-pack.c:297
 msgid "pack too large for current definition of off_t"
 msgstr "пакет слишком большой для текущего определения off_t"
 
-#: builtin/index-pack.c:312
+#: builtin/index-pack.c:313
 #, c-format
 msgid "unable to create '%s'"
 msgstr "не удалось создать «%s»"
 
-#: builtin/index-pack.c:317
+#: builtin/index-pack.c:318
 #, c-format
 msgid "cannot open packfile '%s'"
 msgstr "не удалось открыть файл пакета «%s»"
 
-#: builtin/index-pack.c:331
+#: builtin/index-pack.c:332
 msgid "pack signature mismatch"
 msgstr "несоответствие подписи пакета"
 
-#: builtin/index-pack.c:333
+#: builtin/index-pack.c:334
 #, c-format
 msgid "pack version %<PRIu32> unsupported"
 msgstr "версия пакета %<PRIu32> не поддерживается"
 
-#: builtin/index-pack.c:351
+#: builtin/index-pack.c:352
 #, c-format
 msgid "pack has bad object at offset %lu: %s"
 msgstr "в пакете содержится поврежденный объект по смещению %lu: %s"
 
-#: builtin/index-pack.c:472
+#: builtin/index-pack.c:473
 #, c-format
 msgid "inflate returned %d"
 msgstr "программа сжатия вернула %d"
 
-#: builtin/index-pack.c:521
+#: builtin/index-pack.c:522
 msgid "offset value overflow for delta base object"
 msgstr "переполнение значения смещения у базового объекта дельты"
 
-#: builtin/index-pack.c:529
+#: builtin/index-pack.c:530
 msgid "delta base offset is out of bound"
 msgstr "смещение базовой дельты вышло за допустимые пределы"
 
-#: builtin/index-pack.c:537
+#: builtin/index-pack.c:538
 #, c-format
 msgid "unknown object type %d"
 msgstr "неизвестный тип объекта %d"
 
-#: builtin/index-pack.c:568
+#: builtin/index-pack.c:569
 msgid "cannot pread pack file"
 msgstr "не удалось выполнить pread для файла пакета"
 
-#: builtin/index-pack.c:570
+#: builtin/index-pack.c:571
 #, c-format
 msgid "premature end of pack file, %lu byte missing"
 msgid_plural "premature end of pack file, %lu bytes missing"
@@ -5979,33 +6418,33 @@ msgstr[1] "преждевременное окончание файла паке
 msgstr[2] "преждевременное окончание файла пакета, %lu байтов отсутствует"
 msgstr[3] "преждевременное окончание файла пакета, %lu байтов отсутствует"
 
-#: builtin/index-pack.c:596
+#: builtin/index-pack.c:597
 msgid "serious inflate inconsistency"
 msgstr "серьезное несоответствие при распаковке"
 
-#: builtin/index-pack.c:742 builtin/index-pack.c:748 builtin/index-pack.c:771
-#: builtin/index-pack.c:805 builtin/index-pack.c:814
+#: builtin/index-pack.c:743 builtin/index-pack.c:749 builtin/index-pack.c:772
+#: builtin/index-pack.c:806 builtin/index-pack.c:815
 #, c-format
 msgid "SHA1 COLLISION FOUND WITH %s !"
 msgstr "НАЙДЕНА КОЛЛИЗИЯ SHA1 С %s !"
 
-#: builtin/index-pack.c:745 builtin/pack-objects.c:162
+#: builtin/index-pack.c:746 builtin/pack-objects.c:162
 #: builtin/pack-objects.c:254
 #, c-format
 msgid "unable to read %s"
 msgstr "не удалось прочитать %s"
 
-#: builtin/index-pack.c:811
+#: builtin/index-pack.c:812
 #, c-format
 msgid "cannot read existing object %s"
 msgstr "не удалось прочитать существующий объект %s"
 
-#: builtin/index-pack.c:825
+#: builtin/index-pack.c:826
 #, c-format
 msgid "invalid blob object %s"
 msgstr "неправильный файл двоичного объекта %s"
 
-#: builtin/index-pack.c:839
+#: builtin/index-pack.c:840
 #, c-format
 msgid "invalid %s"
 msgstr "неправильный %s"
@@ -6121,7 +6560,7 @@ msgstr "плохой pack.indexversion=%<PRIu32>"
 msgid "invalid number of threads specified (%d)"
 msgstr "указано неправильное количество потоков (%d)"
 
-#: builtin/index-pack.c:1479 builtin/index-pack.c:1658
+#: builtin/index-pack.c:1479 builtin/index-pack.c:1663
 #, c-format
 msgid "no threads support, ignoring %s"
 msgstr "нет поддержки потоков, игнорирование %s"
@@ -6154,110 +6593,110 @@ msgstr[1] "длина цепочки = %d: %lu объекта"
 msgstr[2] "длина цепочки = %d: %lu объектов"
 msgstr[3] "длина цепочки = %d: %lu объектов"
 
-#: builtin/index-pack.c:1622
+#: builtin/index-pack.c:1623
 msgid "Cannot come back to cwd"
 msgstr "Не удалось вернуться в текущий рабочий каталог"
 
-#: builtin/index-pack.c:1670 builtin/index-pack.c:1673
-#: builtin/index-pack.c:1685 builtin/index-pack.c:1689
+#: builtin/index-pack.c:1675 builtin/index-pack.c:1678
+#: builtin/index-pack.c:1690 builtin/index-pack.c:1694
 #, c-format
 msgid "bad %s"
 msgstr "плохой %s"
 
-#: builtin/index-pack.c:1703
+#: builtin/index-pack.c:1708
 msgid "--fix-thin cannot be used without --stdin"
 msgstr "--fix-thin нельзя использовать без --stdin"
 
-#: builtin/index-pack.c:1707 builtin/index-pack.c:1716
+#: builtin/index-pack.c:1712 builtin/index-pack.c:1721
 #, c-format
 msgid "packfile name '%s' does not end with '.pack'"
 msgstr "имя пакета «%s» не оканчивается на «.pack»"
 
-#: builtin/index-pack.c:1724
+#: builtin/index-pack.c:1729
 msgid "--verify with no packfile name given"
 msgstr "--verify без указания имени файла пакета"
 
-#: builtin/init-db.c:35
+#: builtin/init-db.c:36
 #, c-format
 msgid "Could not make %s writable by group"
 msgstr "Не удалось предоставить доступ к %s на запись"
 
-#: builtin/init-db.c:62
+#: builtin/init-db.c:63
 #, c-format
 msgid "insanely long template name %s"
 msgstr "слишком длинное имя шаблона %s"
 
-#: builtin/init-db.c:67
+#: builtin/init-db.c:68
 #, c-format
 msgid "cannot stat '%s'"
 msgstr "не удалось выполнить stat для «%s»"
 
-#: builtin/init-db.c:73
+#: builtin/init-db.c:74
 #, c-format
 msgid "cannot stat template '%s'"
 msgstr "не удалось выполнить stat для шаблона «%s»"
 
-#: builtin/init-db.c:80
+#: builtin/init-db.c:81
 #, c-format
 msgid "cannot opendir '%s'"
 msgstr "не удалось выполнить opendir для «%s»"
 
-#: builtin/init-db.c:97
+#: builtin/init-db.c:98
 #, c-format
 msgid "cannot readlink '%s'"
 msgstr "не удалось выполнить readlink для «%s»"
 
-#: builtin/init-db.c:99
+#: builtin/init-db.c:100
 #, c-format
 msgid "insanely long symlink %s"
 msgstr "слишком длинная символьная ссылка %s"
 
-#: builtin/init-db.c:102
+#: builtin/init-db.c:103
 #, c-format
 msgid "cannot symlink '%s' '%s'"
 msgstr "не удалось создать символьную ссылку «%s» на «%s»"
 
-#: builtin/init-db.c:106
+#: builtin/init-db.c:107
 #, c-format
 msgid "cannot copy '%s' to '%s'"
 msgstr "не удалось скопировать файл «%s» в «%s»"
 
-#: builtin/init-db.c:110
+#: builtin/init-db.c:111
 #, c-format
 msgid "ignoring template %s"
 msgstr "игнорирование шаблона %s"
 
-#: builtin/init-db.c:136
+#: builtin/init-db.c:137
 #, c-format
 msgid "insanely long template path %s"
 msgstr "слишком длинный путь шаблона %s"
 
-#: builtin/init-db.c:144
+#: builtin/init-db.c:145
 #, c-format
 msgid "templates not found %s"
 msgstr "шаблоны не найдены %s"
 
-#: builtin/init-db.c:157
+#: builtin/init-db.c:158
 #, c-format
 msgid "not copying templates of a wrong format version %d from '%s'"
 msgstr "не копирую шаблоны в неправильной версии формата %d из «%s»"
 
-#: builtin/init-db.c:211
+#: builtin/init-db.c:212
 #, c-format
 msgid "insane git directory %s"
 msgstr "слишком длинный путь к каталогу git %s"
 
-#: builtin/init-db.c:343 builtin/init-db.c:346
+#: builtin/init-db.c:344 builtin/init-db.c:347
 #, c-format
 msgid "%s already exists"
 msgstr "%s уже существует"
 
-#: builtin/init-db.c:374
+#: builtin/init-db.c:375
 #, c-format
 msgid "unable to handle file type %d"
 msgstr "не удается обработать файл типа %d"
 
-#: builtin/init-db.c:377
+#: builtin/init-db.c:378
 #, c-format
 msgid "unable to move %s to %s"
 msgstr "не удается переместить файл %s в %s"
@@ -6265,59 +6704,55 @@ msgstr "не удается переместить файл %s в %s"
 #. TRANSLATORS: The first '%s' is either "Reinitialized
 #. existing" or "Initialized empty", the second " shared" or
 #. "", and the last '%s%s' is the verbatim directory name.
-#: builtin/init-db.c:433
+#: builtin/init-db.c:434
 #, c-format
 msgid "%s%s Git repository in %s%s\n"
 msgstr "%s%s репозиторий Git в %s%s\n"
 
-#: builtin/init-db.c:434
+#: builtin/init-db.c:435
 msgid "Reinitialized existing"
 msgstr "Реинициализация существующего"
 
-#: builtin/init-db.c:434
+#: builtin/init-db.c:435
 msgid "Initialized empty"
 msgstr "Инициализирован пустой"
 
-#: builtin/init-db.c:435
+#: builtin/init-db.c:436
 msgid " shared"
 msgstr " общий"
 
-#: builtin/init-db.c:482
+#: builtin/init-db.c:483
 msgid ""
 "git init [-q | --quiet] [--bare] [--template=<template-directory>] "
 "[--shared[=<permissions>]] [<directory>]"
 msgstr "git init [-q | --quiet] [--bare] [--template=<каталог-шаблонов>] [--shared[=<права-доступа>]] [<каталог>]"
 
-#: builtin/init-db.c:505
+#: builtin/init-db.c:506
 msgid "permissions"
 msgstr "права-доступа"
 
-#: builtin/init-db.c:506
+#: builtin/init-db.c:507
 msgid "specify that the git repository is to be shared amongst several users"
 msgstr "укажите, если репозиторий git будет использоваться несколькими пользователями"
 
-#: builtin/init-db.c:508 builtin/prune-packed.c:57 builtin/repack.c:171
-msgid "be quiet"
-msgstr "тихий режим"
-
-#: builtin/init-db.c:540 builtin/init-db.c:545
+#: builtin/init-db.c:541 builtin/init-db.c:546
 #, c-format
 msgid "cannot mkdir %s"
 msgstr "не удалось выполнить mkdir %s"
 
-#: builtin/init-db.c:549
+#: builtin/init-db.c:550
 #, c-format
 msgid "cannot chdir to %s"
 msgstr "не удалось выполнить chdir в %s"
 
-#: builtin/init-db.c:570
+#: builtin/init-db.c:571
 #, c-format
 msgid ""
 "%s (or --work-tree=<directory>) not allowed without specifying %s (or --git-"
 "dir=<directory>)"
 msgstr "%s (или --work-tree=<каталог>) нельзя использовать без указания %s (или --git-dir=<каталог>)"
 
-#: builtin/init-db.c:598
+#: builtin/init-db.c:599
 #, c-format
 msgid "Cannot access work tree '%s'"
 msgstr "Не удалось получить доступ к рабочему каталогу «%s»"
@@ -6340,284 +6775,279 @@ msgstr "завершитель"
 msgid "trailer(s) to add"
 msgstr "завершители для добавления"
 
-#: builtin/log.c:41
+#: builtin/log.c:43
 msgid "git log [<options>] [<revision-range>] [[--] <path>...]"
 msgstr "git log [<опции>] [<диапазон-редакций>] [[--] <путь>…]"
 
-#: builtin/log.c:42
+#: builtin/log.c:44
 msgid "git show [<options>] <object>..."
 msgstr "git show [<опции>] <объект>…"
 
-#: builtin/log.c:81
+#: builtin/log.c:83
 #, c-format
 msgid "invalid --decorate option: %s"
 msgstr "неправильный параметр для --decorate: %s"
 
-#: builtin/log.c:127
+#: builtin/log.c:131
 msgid "suppress diff output"
 msgstr "не выводить различия"
 
-#: builtin/log.c:128
+#: builtin/log.c:132
 msgid "show source"
 msgstr "показать источник"
 
-#: builtin/log.c:129
+#: builtin/log.c:133
 msgid "Use mail map file"
 msgstr "Использовать файл соответствия почтовых адресов"
 
-#: builtin/log.c:130
+#: builtin/log.c:134
 msgid "decorate options"
 msgstr "опции формата вывода ссылок"
 
-#: builtin/log.c:133
+#: builtin/log.c:137
 msgid "Process line range n,m in file, counting from 1"
 msgstr "Обработать диапазон строк n,m из файла, начиная с 1"
 
-#: builtin/log.c:229
+#: builtin/log.c:233
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr "Финальный вывод: %d %s\n"
 
-#: builtin/log.c:458
+#: builtin/log.c:465
 #, c-format
 msgid "git show %s: bad file"
 msgstr "git show %s: плохой файл"
 
-#: builtin/log.c:472 builtin/log.c:564
+#: builtin/log.c:479 builtin/log.c:572
 #, c-format
 msgid "Could not read object %s"
 msgstr "Не удалось прочитать объект %s"
 
-#: builtin/log.c:588
+#: builtin/log.c:596
 #, c-format
 msgid "Unknown type: %d"
 msgstr "Неизвестный тип объекта: %d"
 
-#: builtin/log.c:689
+#: builtin/log.c:714
 msgid "format.headers without value"
 msgstr "в format.headers не указано значение"
 
-#: builtin/log.c:773
+#: builtin/log.c:798
 msgid "name of output directory is too long"
 msgstr "слишком длинное имя выходного каталога"
 
-#: builtin/log.c:789
+#: builtin/log.c:814
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr "Ну удалось открыть файл изменений %s"
 
-#: builtin/log.c:803
+#: builtin/log.c:828
 msgid "Need exactly one range."
 msgstr "Нужен только один диапазон."
 
-#: builtin/log.c:813
+#: builtin/log.c:838
 msgid "Not a range."
 msgstr "Не является диапазоном."
 
-#: builtin/log.c:919
+#: builtin/log.c:944
 msgid "Cover letter needs email format"
 msgstr "Сопроводительное письмо должно быть в формате электронной почты"
 
-#: builtin/log.c:998
+#: builtin/log.c:1023
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr "ошибка в поле in-reply-to: %s"
 
-#: builtin/log.c:1026
+#: builtin/log.c:1051
 msgid "git format-patch [<options>] [<since> | <revision-range>]"
 msgstr "git format-patch [<опции>] [<начиная-с> | <диапазон-редакций>]"
 
-#: builtin/log.c:1071
+#: builtin/log.c:1096
 msgid "Two output directories?"
 msgstr "Два выходных каталога?"
 
-#: builtin/log.c:1186
+#: builtin/log.c:1211
 msgid "use [PATCH n/m] even with a single patch"
 msgstr "выводить [PATCH n/m] даже когда один патч"
 
-#: builtin/log.c:1189
+#: builtin/log.c:1214
 msgid "use [PATCH] even with multiple patches"
 msgstr "выводить [PATCH] даже когда несколько патчей"
 
-#: builtin/log.c:1193
+#: builtin/log.c:1218
 msgid "print patches to standard out"
 msgstr "выводить патчи на стандартный вывод"
 
-#: builtin/log.c:1195
+#: builtin/log.c:1220
 msgid "generate a cover letter"
 msgstr "генерировать сопроводительное письмо"
 
-#: builtin/log.c:1197
+#: builtin/log.c:1222
 msgid "use simple number sequence for output file names"
 msgstr "использовать простую последовательность чисел для имен выходных файлов"
 
-#: builtin/log.c:1198
+#: builtin/log.c:1223
 msgid "sfx"
 msgstr "суффикс"
 
-#: builtin/log.c:1199
+#: builtin/log.c:1224
 msgid "use <sfx> instead of '.patch'"
 msgstr "использовать суффикс <суффикс> вместо «.patch»"
 
-#: builtin/log.c:1201
+#: builtin/log.c:1226
 msgid "start numbering patches at <n> instead of 1"
 msgstr "начать нумерацию патчей с <n>, а не с 1"
 
-#: builtin/log.c:1203
+#: builtin/log.c:1228
 msgid "mark the series as Nth re-roll"
 msgstr "пометить серию как энную попытку"
 
-#: builtin/log.c:1205
+#: builtin/log.c:1230
 msgid "Use [<prefix>] instead of [PATCH]"
 msgstr "Использовать [<префикс>] вместо [PATCH]"
 
-#: builtin/log.c:1208
+#: builtin/log.c:1233
 msgid "store resulting files in <dir>"
 msgstr "сохранить результирующие файлы в <каталог>"
 
-#: builtin/log.c:1211
+#: builtin/log.c:1236
 msgid "don't strip/add [PATCH]"
 msgstr "не обрезать/добавлять [PATCH]"
 
-#: builtin/log.c:1214
+#: builtin/log.c:1239
 msgid "don't output binary diffs"
 msgstr "не выводить двоичные различия"
 
-#: builtin/log.c:1216
+#: builtin/log.c:1241
 msgid "don't include a patch matching a commit upstream"
 msgstr "не включать патч, если коммит уже есть в вышестоящей ветке"
 
-#: builtin/log.c:1218
+#: builtin/log.c:1243
 msgid "show patch format instead of default (patch + stat)"
 msgstr "выводить в формате патча, а не в стандартном (патч + статистика)"
 
-#: builtin/log.c:1220
+#: builtin/log.c:1245
 msgid "Messaging"
 msgstr "Передача сообщений"
 
-#: builtin/log.c:1221
+#: builtin/log.c:1246
 msgid "header"
 msgstr "заголовок"
 
-#: builtin/log.c:1222
+#: builtin/log.c:1247
 msgid "add email header"
 msgstr "добавить заголовок сообщения"
 
-#: builtin/log.c:1223 builtin/log.c:1225
+#: builtin/log.c:1248 builtin/log.c:1250
 msgid "email"
 msgstr "почта"
 
-#: builtin/log.c:1223
+#: builtin/log.c:1248
 msgid "add To: header"
 msgstr "добавить заголовок To:"
 
-#: builtin/log.c:1225
+#: builtin/log.c:1250
 msgid "add Cc: header"
 msgstr "добавить заголовок Cc:"
 
-#: builtin/log.c:1227
+#: builtin/log.c:1252
 msgid "ident"
 msgstr "идентификатор"
 
-#: builtin/log.c:1228
+#: builtin/log.c:1253
 msgid "set From address to <ident> (or committer ident if absent)"
 msgstr "установить адрес отправителя на <идентификатор> (или на идентификатор коммитера, если отсутствует)"
 
-#: builtin/log.c:1230
+#: builtin/log.c:1255
 msgid "message-id"
 msgstr "идентификатор-сообщения"
 
-#: builtin/log.c:1231
+#: builtin/log.c:1256
 msgid "make first mail a reply to <message-id>"
 msgstr "сделать первое письмо ответом на <идентификатор-сообщения>"
 
-#: builtin/log.c:1232 builtin/log.c:1235
+#: builtin/log.c:1257 builtin/log.c:1260
 msgid "boundary"
 msgstr "вложение"
 
-#: builtin/log.c:1233
+#: builtin/log.c:1258
 msgid "attach the patch"
 msgstr "приложить патч"
 
-#: builtin/log.c:1236
+#: builtin/log.c:1261
 msgid "inline the patch"
 msgstr "включить патч в текст письма"
 
-#: builtin/log.c:1240
+#: builtin/log.c:1265
 msgid "enable message threading, styles: shallow, deep"
 msgstr "включить в письмах иерархичность, стили: shallow (частичную), deep (глубокую)"
 
-#: builtin/log.c:1242
+#: builtin/log.c:1267
 msgid "signature"
 msgstr "подпись"
 
-#: builtin/log.c:1243
+#: builtin/log.c:1268
 msgid "add a signature"
 msgstr "добавить подпись"
 
-#: builtin/log.c:1245
+#: builtin/log.c:1270
 msgid "add a signature from a file"
 msgstr "добавить подпись из файла"
 
-#: builtin/log.c:1246
+#: builtin/log.c:1271
 msgid "don't print the patch filenames"
 msgstr "не выводить имена файлов патчей"
 
-#: builtin/log.c:1320
-#, c-format
-msgid "invalid ident line: %s"
-msgstr "неправильная строка идентификации: %s"
-
-#: builtin/log.c:1335
+#: builtin/log.c:1360
 msgid "-n and -k are mutually exclusive."
 msgstr "-n и -k нельзя использовать одновременно"
 
-#: builtin/log.c:1337
+#: builtin/log.c:1362
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr "--subject-prefix и -k нельзя использовать одновременно."
 
-#: builtin/log.c:1345
+#: builtin/log.c:1370
 msgid "--name-only does not make sense"
 msgstr "--name-only не имеет смысла"
 
-#: builtin/log.c:1347
+#: builtin/log.c:1372
 msgid "--name-status does not make sense"
 msgstr "--name-status не имеет смысла"
 
-#: builtin/log.c:1349
+#: builtin/log.c:1374
 msgid "--check does not make sense"
 msgstr "--check не имеет смысла"
 
-#: builtin/log.c:1372
+#: builtin/log.c:1397
 msgid "standard output, or directory, which one?"
 msgstr "стандартный вывод или каталог?"
 
-#: builtin/log.c:1374
+#: builtin/log.c:1399
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "Не удалось создать каталог «%s»"
 
-#: builtin/log.c:1472
+#: builtin/log.c:1496
 #, c-format
 msgid "unable to read signature file '%s'"
 msgstr "не удалось прочитать файл подписи «%s»"
 
-#: builtin/log.c:1535
+#: builtin/log.c:1559
 msgid "Failed to create output files"
 msgstr "Сбой при создании выходных файлов"
 
-#: builtin/log.c:1583
+#: builtin/log.c:1607
 msgid "git cherry [-v] [<upstream> [<head> [<limit>]]]"
 msgstr "git cherry [-v] [<вышестоящая-ветка> [<голова> [<ограничение>]]]"
 
-#: builtin/log.c:1637
+#: builtin/log.c:1661
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> "
 "manually.\n"
 msgstr "Не удалось найти отслеживаемую внешнюю ветку, укажите <вышестоящую-ветку> вручную.\n"
 
-#: builtin/log.c:1648 builtin/log.c:1650 builtin/log.c:1662
+#: builtin/log.c:1672 builtin/log.c:1674 builtin/log.c:1686
 #, c-format
 msgid "Unknown commit %s"
 msgstr "Неизвестный коммит %s"
@@ -6781,31 +7211,31 @@ msgstr "Доступные стратегии:"
 msgid "Available custom strategies are:"
 msgstr "Доступные пользовательские стратегии:"
 
-#: builtin/merge.c:193
+#: builtin/merge.c:193 builtin/pull.c:119
 msgid "do not show a diffstat at the end of the merge"
 msgstr "не выводить статистику изменений после окончания слияния"
 
-#: builtin/merge.c:196
+#: builtin/merge.c:196 builtin/pull.c:122
 msgid "show a diffstat at the end of the merge"
 msgstr "вывести статистику изменений после окончания слияния"
 
-#: builtin/merge.c:197
+#: builtin/merge.c:197 builtin/pull.c:125
 msgid "(synonym to --stat)"
 msgstr "(синоним для --stat)"
 
-#: builtin/merge.c:199
+#: builtin/merge.c:199 builtin/pull.c:128
 msgid "add (at most <n>) entries from shortlog to merge commit message"
 msgstr "добавить (максимум <n>) записей из короткого журнала в сообщение коммита у слияния"
 
-#: builtin/merge.c:202
+#: builtin/merge.c:202 builtin/pull.c:131
 msgid "create a single commit instead of doing a merge"
 msgstr "создать один коммит, вместо выполнения слияния"
 
-#: builtin/merge.c:204
+#: builtin/merge.c:204 builtin/pull.c:134
 msgid "perform a commit if the merge succeeds (default)"
 msgstr "сделать коммит, если слияние прошло успешно (по умолчанию)"
 
-#: builtin/merge.c:206
+#: builtin/merge.c:206 builtin/pull.c:137
 msgid "edit message before committing"
 msgstr "отредактировать сообщение перед выполнением коммита"
 
@@ -6813,7 +7243,7 @@ msgstr "отредактировать сообщение перед выпол
 msgid "allow fast-forward (default)"
 msgstr "разрешить перемотку вперед (по умолчанию)"
 
-#: builtin/merge.c:209
+#: builtin/merge.c:209 builtin/pull.c:143
 msgid "abort if fast-forward is not possible"
 msgstr "отменить выполнение слияния, если перемотка вперед не возможна"
 
@@ -6821,19 +7251,20 @@ msgstr "отменить выполнение слияния, если пере
 msgid "Verify that the named commit has a valid GPG signature"
 msgstr "Проверить, что указанный коммит имеет верную электронную подпись GPG"
 
-#: builtin/merge.c:214 builtin/notes.c:753 builtin/revert.c:89
+#: builtin/merge.c:214 builtin/notes.c:767 builtin/pull.c:148
+#: builtin/revert.c:89
 msgid "strategy"
 msgstr "стратегия"
 
-#: builtin/merge.c:215
+#: builtin/merge.c:215 builtin/pull.c:149
 msgid "merge strategy to use"
 msgstr "используемая стратегия слияния"
 
-#: builtin/merge.c:216
+#: builtin/merge.c:216 builtin/pull.c:152
 msgid "option=value"
 msgstr "опция=значение"
 
-#: builtin/merge.c:217
+#: builtin/merge.c:217 builtin/pull.c:153
 msgid "option for selected merge strategy"
 msgstr "опции для выбранной стратегии слияния"
 
@@ -6871,6 +7302,12 @@ msgstr " (нечего уплотнять)"
 msgid "Squash commit -- not updating HEAD\n"
 msgstr "Уплотнение коммита — не обновляя HEAD\n"
 
+#: builtin/merge.c:344 builtin/merge.c:763 builtin/merge.c:975
+#: builtin/merge.c:988
+#, c-format
+msgid "Could not write to '%s'"
+msgstr "Не удалось записать в «%s»"
+
 #: builtin/merge.c:372
 msgid "Writing SQUASH_MSG"
 msgstr "Запись SQUASH_MSG"
@@ -6894,10 +7331,6 @@ msgstr "«%s» не указывает на коммит"
 msgid "Bad branch.%s.mergeoptions string: %s"
 msgstr "Неправильная строка branch.%s.mergeoptions: %s"
 
-#: builtin/merge.c:632
-msgid "git write-tree failed to write a tree"
-msgstr "git write-tree не удалось записать дерево"
-
 #: builtin/merge.c:656
 msgid "Not handling anything other than two heads merge."
 msgstr "Не обрабатываю ничего, кроме слияния двух указателей на коммиты."
@@ -6983,10 +7416,6 @@ msgid ""
 "Please, commit your changes before you merge."
 msgstr "Вы не завершили слияние (присутствует файл MERGE_HEAD).\nВыполните коммит ваших изменений, перед слиянием."
 
-#: builtin/merge.c:1227 git-pull.sh:74
-msgid "You have not concluded your merge (MERGE_HEAD exists)."
-msgstr "Вы не завершили слияние (присутствует файл MERGE_HEAD)."
-
 #: builtin/merge.c:1231
 msgid ""
 "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
@@ -7272,7 +7701,7 @@ msgstr "%s, откуда=%s, куда=%s"
 msgid "Renaming %s to %s\n"
 msgstr "Переименование %s в %s\n"
 
-#: builtin/mv.c:256 builtin/remote.c:725 builtin/repack.c:361
+#: builtin/mv.c:256 builtin/remote.c:722 builtin/repack.c:362
 #, c-format
 msgid "renaming '%s' failed"
 msgstr "сбой при переименовании «%s»"
@@ -7317,332 +7746,329 @@ msgstr "разрешить вывод «undefined», если не найден
 msgid "dereference tags in the input (internal use)"
 msgstr "разыменовывать введенные метки (для внутреннего использования)"
 
-#: builtin/notes.c:24
+#: builtin/notes.c:25
 msgid "git notes [--ref <notes-ref>] [list [<object>]]"
 msgstr "git notes [--ref <ссылка-на-заметку>] [list [<объект>]]"
 
-#: builtin/notes.c:25
+#: builtin/notes.c:26
 msgid ""
 "git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file>"
 " | (-c | -C) <object>] [<object>]"
 msgstr "git notes [--ref <ссылка-на-заметку>] add [-f] [--allow-empty] [-m <сообщение> | -F <файл> | (-c | -C) <объект>] [<объект>]"
 
-#: builtin/notes.c:26
+#: builtin/notes.c:27
 msgid "git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"
 msgstr "git notes [--ref <ссылка-на-заметку>] copy [-f] <из-объекта> <в-объект>"
 
-#: builtin/notes.c:27
+#: builtin/notes.c:28
 msgid ""
 "git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> |"
 " (-c | -C) <object>] [<object>]"
 msgstr "git notes [--ref <ссылка-на-заметку>] append [--allow-empty] [-m <сообщение> | -F <файл> | (-c | -C) <объект>] [<объект>]"
 
-#: builtin/notes.c:28
+#: builtin/notes.c:29
 msgid "git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"
 msgstr "git notes [--ref <ссылка-на-заметку>] edit [--allow-empty] [<объект>]"
 
-#: builtin/notes.c:29
+#: builtin/notes.c:30
 msgid "git notes [--ref <notes-ref>] show [<object>]"
 msgstr "git notes [--ref <ссылка-на-заметку>] show [<объект>]"
 
-#: builtin/notes.c:30
+#: builtin/notes.c:31
 msgid ""
 "git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"
 msgstr "git notes [--ref <ссылка-на-заметку>] merge [-v | -q] [-s <стратегия>] <ссылка-на-заметку>"
 
-#: builtin/notes.c:31
+#: builtin/notes.c:32
 msgid "git notes merge --commit [-v | -q]"
 msgstr "git notes merge --commit [-v | -q]"
 
-#: builtin/notes.c:32
+#: builtin/notes.c:33
 msgid "git notes merge --abort [-v | -q]"
 msgstr "git notes merge --abort [-v | -q]"
 
-#: builtin/notes.c:33
+#: builtin/notes.c:34
 msgid "git notes [--ref <notes-ref>] remove [<object>...]"
 msgstr "git notes [--ref <ссылка-на-заметку>] remove [<объект>…]"
 
-#: builtin/notes.c:34
+#: builtin/notes.c:35
 msgid "git notes [--ref <notes-ref>] prune [-n | -v]"
 msgstr "git notes [--ref <ссылка-на-заметку>] prune [-n | -v]"
 
-#: builtin/notes.c:35
+#: builtin/notes.c:36
 msgid "git notes [--ref <notes-ref>] get-ref"
 msgstr "git notes [--ref <ссылка-на-заметку>] get-ref"
 
-#: builtin/notes.c:40
+#: builtin/notes.c:41
 msgid "git notes [list [<object>]]"
 msgstr "git notes [list [<объект>]]"
 
-#: builtin/notes.c:45
+#: builtin/notes.c:46
 msgid "git notes add [<options>] [<object>]"
 msgstr "git notes add [<опции>] [<объект>]"
 
-#: builtin/notes.c:50
+#: builtin/notes.c:51
 msgid "git notes copy [<options>] <from-object> <to-object>"
 msgstr "git notes copy [<опции>] <из-объекта> <в-объект>"
 
-#: builtin/notes.c:51
+#: builtin/notes.c:52
 msgid "git notes copy --stdin [<from-object> <to-object>]..."
 msgstr "git notes copy --stdin [<из-объекта> <в-объект>]…"
 
-#: builtin/notes.c:56
+#: builtin/notes.c:57
 msgid "git notes append [<options>] [<object>]"
 msgstr "git notes append [<опции>] [<объект>]"
 
-#: builtin/notes.c:61
+#: builtin/notes.c:62
 msgid "git notes edit [<object>]"
 msgstr "git notes edit [<объект>]"
 
-#: builtin/notes.c:66
+#: builtin/notes.c:67
 msgid "git notes show [<object>]"
 msgstr "git notes show [<объект>]"
 
-#: builtin/notes.c:71
+#: builtin/notes.c:72
 msgid "git notes merge [<options>] <notes-ref>"
 msgstr "git notes merge [<опции>] <ссылка-на-заметку>"
 
-#: builtin/notes.c:72
+#: builtin/notes.c:73
 msgid "git notes merge --commit [<options>]"
 msgstr "git notes merge --commit [<опции>]"
 
-#: builtin/notes.c:73
+#: builtin/notes.c:74
 msgid "git notes merge --abort [<options>]"
 msgstr "git notes merge --abort [<опции>]"
 
-#: builtin/notes.c:78
+#: builtin/notes.c:79
 msgid "git notes remove [<object>]"
 msgstr "git notes remove [<опции>]"
 
-#: builtin/notes.c:83
+#: builtin/notes.c:84
 msgid "git notes prune [<options>]"
 msgstr "git notes prune [<опции>]"
 
-#: builtin/notes.c:88
+#: builtin/notes.c:89
 msgid "git notes get-ref"
 msgstr "git notes get-ref"
 
-#: builtin/notes.c:146
+#: builtin/notes.c:147
 #, c-format
 msgid "unable to start 'show' for object '%s'"
 msgstr "не удалось запустить «show» для объекта «%s»"
 
-#: builtin/notes.c:150
+#: builtin/notes.c:151
 msgid "could not read 'show' output"
 msgstr "не удалось прочитать вывод «show»"
 
-#: builtin/notes.c:158
+#: builtin/notes.c:159
 #, c-format
 msgid "failed to finish 'show' for object '%s'"
 msgstr "не удалось завершить «show» для объекта «%s»"
 
-#: builtin/notes.c:173 builtin/tag.c:477
+#: builtin/notes.c:174 builtin/tag.c:477
 #, c-format
 msgid "could not create file '%s'"
 msgstr "не удалось создать файл «%s»"
 
-#: builtin/notes.c:192
+#: builtin/notes.c:193
 msgid "Please supply the note contents using either -m or -F option"
 msgstr "Пожалуйста, укажите содержимое заметки, используя опцию -m или -F"
 
-#: builtin/notes.c:201
+#: builtin/notes.c:202
 msgid "unable to write note object"
 msgstr "не удалось записать объект заметки"
 
-#: builtin/notes.c:203
+#: builtin/notes.c:204
 #, c-format
 msgid "The note contents have been left in %s"
 msgstr "Содержимое заметки осталось в %s"
 
-#: builtin/notes.c:231 builtin/tag.c:693
+#: builtin/notes.c:232 builtin/tag.c:695
 #, c-format
 msgid "cannot read '%s'"
 msgstr "не удалось прочитать «%s»"
 
-#: builtin/notes.c:233 builtin/tag.c:696
+#: builtin/notes.c:234 builtin/tag.c:698
 #, c-format
 msgid "could not open or read '%s'"
 msgstr "не удалось открыть или прочитать «%s»"
 
-#: builtin/notes.c:252 builtin/notes.c:303 builtin/notes.c:305
-#: builtin/notes.c:365 builtin/notes.c:420 builtin/notes.c:506
-#: builtin/notes.c:511 builtin/notes.c:589 builtin/notes.c:652
-#: builtin/notes.c:854 builtin/tag.c:709
+#: builtin/notes.c:253 builtin/notes.c:304 builtin/notes.c:306
+#: builtin/notes.c:366 builtin/notes.c:421 builtin/notes.c:507
+#: builtin/notes.c:512 builtin/notes.c:590 builtin/notes.c:653
+#: builtin/notes.c:877 builtin/tag.c:711
 #, c-format
 msgid "Failed to resolve '%s' as a valid ref."
 msgstr "Не удалось разрешить «%s» как ссылку."
 
-#: builtin/notes.c:255
+#: builtin/notes.c:256
 #, c-format
 msgid "Failed to read object '%s'."
 msgstr "Не удалось прочитать объект «%s»."
 
-#: builtin/notes.c:259
+#: builtin/notes.c:260
 #, c-format
 msgid "Cannot read note data from non-blob object '%s'."
 msgstr "Не удалось прочитать данные заметки из недвоичного объекта «%s»."
 
-#: builtin/notes.c:299
-#, c-format
-msgid "Malformed input line: '%s'."
-msgstr "Плохая строка ввода: «%s»."
-
-#: builtin/notes.c:314
-#, c-format
-msgid "Failed to copy notes from '%s' to '%s'"
-msgstr "Не удалось скопировать заметку из «%s» в «%s»"
-
-#: builtin/notes.c:358 builtin/notes.c:413 builtin/notes.c:489
-#: builtin/notes.c:501 builtin/notes.c:577 builtin/notes.c:645
-#: builtin/notes.c:919
+#: builtin/notes.c:359 builtin/notes.c:414 builtin/notes.c:490
+#: builtin/notes.c:502 builtin/notes.c:578 builtin/notes.c:646
+#: builtin/notes.c:942
 msgid "too many parameters"
 msgstr "передано слишком много параметров"
 
-#: builtin/notes.c:371 builtin/notes.c:658
+#: builtin/notes.c:372 builtin/notes.c:659
 #, c-format
 msgid "No note found for object %s."
 msgstr "Не найдена заметка для объекта %s."
 
-#: builtin/notes.c:392 builtin/notes.c:555
+#: builtin/notes.c:393 builtin/notes.c:556
 msgid "note contents as a string"
 msgstr "текстовое содержимое заметки"
 
-#: builtin/notes.c:395 builtin/notes.c:558
+#: builtin/notes.c:396 builtin/notes.c:559
 msgid "note contents in a file"
 msgstr "содержимое заметки в файле"
 
-#: builtin/notes.c:397 builtin/notes.c:400 builtin/notes.c:560
-#: builtin/notes.c:563 builtin/tag.c:628
+#: builtin/notes.c:398 builtin/notes.c:401 builtin/notes.c:561
+#: builtin/notes.c:564 builtin/tag.c:630
 msgid "object"
 msgstr "объект"
 
-#: builtin/notes.c:398 builtin/notes.c:561
+#: builtin/notes.c:399 builtin/notes.c:562
 msgid "reuse and edit specified note object"
 msgstr "использовать и отредактировать указанный объект заметки"
 
-#: builtin/notes.c:401 builtin/notes.c:564
+#: builtin/notes.c:402 builtin/notes.c:565
 msgid "reuse specified note object"
 msgstr "использовать указанный объект заметки"
 
-#: builtin/notes.c:404 builtin/notes.c:567
+#: builtin/notes.c:405 builtin/notes.c:568
 msgid "allow storing empty note"
 msgstr "разрешить сохранение пустой заметки"
 
-#: builtin/notes.c:405 builtin/notes.c:476
+#: builtin/notes.c:406 builtin/notes.c:477
 msgid "replace existing notes"
 msgstr "заменить существующие заметки"
 
-#: builtin/notes.c:430
+#: builtin/notes.c:431
 #, c-format
 msgid ""
 "Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite "
 "existing notes"
 msgstr "Не удалось добавить заметку. Найдена существующая заметка у объекта %s. Используйте параметр «-f» для перезаписи существующих заметок."
 
-#: builtin/notes.c:445 builtin/notes.c:524
+#: builtin/notes.c:446 builtin/notes.c:525
 #, c-format
 msgid "Overwriting existing notes for object %s\n"
 msgstr "Перезапись существующих заметок у объекта %s\n"
 
-#: builtin/notes.c:456 builtin/notes.c:617 builtin/notes.c:859
+#: builtin/notes.c:457 builtin/notes.c:618 builtin/notes.c:882
 #, c-format
 msgid "Removing note for object %s\n"
 msgstr "Удаление заметки у объекта %s\n"
 
-#: builtin/notes.c:477
+#: builtin/notes.c:478
 msgid "read objects from stdin"
 msgstr "прочитать объекты из стандартного ввода"
 
-#: builtin/notes.c:479
+#: builtin/notes.c:480
 msgid "load rewriting config for <command> (implies --stdin)"
 msgstr "загрузить настройки перезаписи для команды <команда> (включает в себя --stdin)"
 
-#: builtin/notes.c:497
+#: builtin/notes.c:498
 msgid "too few parameters"
 msgstr "передано слишком мало параметров"
 
-#: builtin/notes.c:518
+#: builtin/notes.c:519
 #, c-format
 msgid ""
 "Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite"
 " existing notes"
 msgstr "Не удалось скопировать заметку. Найдена существующая заметка у объекта %s. Используйте параметр «-f» для перезаписи существующих заметок."
 
-#: builtin/notes.c:530
+#: builtin/notes.c:531
 #, c-format
 msgid "Missing notes on source object %s. Cannot copy."
 msgstr "Нет заметок у исходного объекта %s. Нельзя скопировать."
 
-#: builtin/notes.c:582
+#: builtin/notes.c:583
 #, c-format
 msgid ""
 "The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n"
 "Please use 'git notes add -f -m/-F/-c/-C' instead.\n"
 msgstr "Опции -m/-F/-c/-C для подкоманды «edit» устарели.\nИспользуйте вместо них «git notes add -f -m/-F/-c/-C».\n"
 
-#: builtin/notes.c:750
+#: builtin/notes.c:764
 msgid "General options"
 msgstr "Общие опции"
 
-#: builtin/notes.c:752
+#: builtin/notes.c:766
 msgid "Merge options"
 msgstr "Опции слияния"
 
-#: builtin/notes.c:754
+#: builtin/notes.c:768
 msgid ""
 "resolve notes conflicts using the given strategy "
 "(manual/ours/theirs/union/cat_sort_uniq)"
 msgstr "разрешить конфликты заметок с помощью указанной стратегии (manual/ours/theirs/union/cat_sort_uniq)"
 
-#: builtin/notes.c:756
+#: builtin/notes.c:770
 msgid "Committing unmerged notes"
 msgstr "Коммит не слитых заметок"
 
-#: builtin/notes.c:758
+#: builtin/notes.c:772
 msgid "finalize notes merge by committing unmerged notes"
 msgstr "завершить слияние заметок коммитом не слитых заметок"
 
-#: builtin/notes.c:760
+#: builtin/notes.c:774
 msgid "Aborting notes merge resolution"
 msgstr "Отмена разрешения слияния заметок"
 
-#: builtin/notes.c:762
+#: builtin/notes.c:776
 msgid "abort notes merge"
 msgstr "отменить слияние заметок"
 
-#: builtin/notes.c:857
+#: builtin/notes.c:853
+#, c-format
+msgid "A notes merge into %s is already in-progress at %s"
+msgstr "Слияние заметок в %s уже выполняется на %s"
+
+#: builtin/notes.c:880
 #, c-format
 msgid "Object %s has no note\n"
 msgstr "У объекта %s нет заметки\n"
 
-#: builtin/notes.c:869
+#: builtin/notes.c:892
 msgid "attempt to remove non-existent note is not an error"
 msgstr "попытка удаления несуществующей заметки не является ошибкой"
 
-#: builtin/notes.c:872
+#: builtin/notes.c:895
 msgid "read object names from the standard input"
 msgstr "прочитать имена объектов из стандартного ввода"
 
-#: builtin/notes.c:953
+#: builtin/notes.c:976
 msgid "notes-ref"
 msgstr "ссылка-на-заметку"
 
-#: builtin/notes.c:954
+#: builtin/notes.c:977
 msgid "use notes from <notes-ref>"
 msgstr "использовать заметку из <ссылка-на-заметку>"
 
-#: builtin/notes.c:989 builtin/remote.c:1618
+#: builtin/notes.c:1012 builtin/remote.c:1588
 #, c-format
 msgid "Unknown subcommand: %s"
 msgstr "Неизвестная подкоманда: %s"
 
 #: builtin/pack-objects.c:28
-msgid "git pack-objects --stdout [options...] [< ref-list | < object-list]"
-msgstr "git pack-objects --stdout [опции…] [< список-ссылок | < список-объектов]"
+msgid ""
+"git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]"
+msgstr "git pack-objects --stdout [<опции>…] [< <список-ссылок> | < <список-объектов>]"
 
 #: builtin/pack-objects.c:29
-msgid "git pack-objects [options...] base-name [< ref-list | < object-list]"
-msgstr "git pack-objects [опции…] имя-базы [< список-ссылок | < список-объектов]"
+msgid ""
+"git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]"
+msgstr "git pack-objects [<опции>…] <имя-базы> [< <список-ссылок> | < <список-объектов>]"
 
 #: builtin/pack-objects.c:175 builtin/pack-objects.c:178
 #, c-format
@@ -7671,153 +8097,143 @@ msgstr "неподдерживаемая версия индекса %s"
 msgid "bad index version '%s'"
 msgstr "плохая версия индекса «%s»"
 
-#: builtin/pack-objects.c:2595
-#, c-format
-msgid "option %s does not accept negative form"
-msgstr "опция %s не принимает отрицательные значения"
-
-#: builtin/pack-objects.c:2599
-#, c-format
-msgid "unable to parse value '%s' for option %s"
-msgstr "не удалось разобрать значение «%s» для опции %s"
-
-#: builtin/pack-objects.c:2619
+#: builtin/pack-objects.c:2602
 msgid "do not show progress meter"
 msgstr "не выводить прогресс выполнения"
 
-#: builtin/pack-objects.c:2621
+#: builtin/pack-objects.c:2604
 msgid "show progress meter"
 msgstr "показать прогресс выполнения"
 
-#: builtin/pack-objects.c:2623
+#: builtin/pack-objects.c:2606
 msgid "show progress meter during object writing phase"
 msgstr "показать прогресс выполнения во время записи объектов"
 
-#: builtin/pack-objects.c:2626
+#: builtin/pack-objects.c:2609
 msgid "similar to --all-progress when progress meter is shown"
 msgstr "похоже на --all-progress при включенном прогрессе выполнения"
 
-#: builtin/pack-objects.c:2627
+#: builtin/pack-objects.c:2610
 msgid "version[,offset]"
 msgstr "версия[,смещение]"
 
-#: builtin/pack-objects.c:2628
+#: builtin/pack-objects.c:2611
 msgid "write the pack index file in the specified idx format version"
 msgstr "записать файл индекса пакета в указанной версии формата"
 
-#: builtin/pack-objects.c:2631
+#: builtin/pack-objects.c:2614
 msgid "maximum size of each output pack file"
 msgstr "максимальный размер каждого выходного файла пакета"
 
-#: builtin/pack-objects.c:2633
+#: builtin/pack-objects.c:2616
 msgid "ignore borrowed objects from alternate object store"
 msgstr "игнорировать чужие объекты, взятые из альтернативного хранилища объектов"
 
-#: builtin/pack-objects.c:2635
+#: builtin/pack-objects.c:2618
 msgid "ignore packed objects"
 msgstr "игнорировать упакованные объекты"
 
-#: builtin/pack-objects.c:2637
+#: builtin/pack-objects.c:2620
 msgid "limit pack window by objects"
 msgstr "ограничить окно пакета по количеству объектов"
 
-#: builtin/pack-objects.c:2639
+#: builtin/pack-objects.c:2622
 msgid "limit pack window by memory in addition to object limit"
 msgstr "дополнительно к количеству объектов ограничить окно пакета по памяти"
 
-#: builtin/pack-objects.c:2641
+#: builtin/pack-objects.c:2624
 msgid "maximum length of delta chain allowed in the resulting pack"
 msgstr "максимальная разрешенная длина цепочки дельт в результирующем пакете"
 
-#: builtin/pack-objects.c:2643
+#: builtin/pack-objects.c:2626
 msgid "reuse existing deltas"
 msgstr "использовать повторно существующие дельты"
 
-#: builtin/pack-objects.c:2645
+#: builtin/pack-objects.c:2628
 msgid "reuse existing objects"
 msgstr "использовать повторно существующие объекты"
 
-#: builtin/pack-objects.c:2647
+#: builtin/pack-objects.c:2630
 msgid "use OFS_DELTA objects"
 msgstr "использовать объекты OFS_DELTA"
 
-#: builtin/pack-objects.c:2649
+#: builtin/pack-objects.c:2632
 msgid "use threads when searching for best delta matches"
 msgstr "использовать многопоточность при поиске лучших совпадений дельт"
 
-#: builtin/pack-objects.c:2651
+#: builtin/pack-objects.c:2634
 msgid "do not create an empty pack output"
 msgstr "не создавать пустые выходные пакеты"
 
-#: builtin/pack-objects.c:2653
+#: builtin/pack-objects.c:2636
 msgid "read revision arguments from standard input"
 msgstr "прочитать аргументы редакций из стандартного ввода"
 
-#: builtin/pack-objects.c:2655
+#: builtin/pack-objects.c:2638
 msgid "limit the objects to those that are not yet packed"
 msgstr "ограничиться объектами, которые еще не упакованы"
 
-#: builtin/pack-objects.c:2658
+#: builtin/pack-objects.c:2641
 msgid "include objects reachable from any reference"
 msgstr "включить объекты, которые достижимы по любой из ссылок"
 
-#: builtin/pack-objects.c:2661
+#: builtin/pack-objects.c:2644
 msgid "include objects referred by reflog entries"
 msgstr "включить объекты, на которые ссылаются записи журнала ссылок"
 
-#: builtin/pack-objects.c:2664
+#: builtin/pack-objects.c:2647
 msgid "include objects referred to by the index"
 msgstr "включить объекты, на которые ссылается индекс"
 
-#: builtin/pack-objects.c:2667
+#: builtin/pack-objects.c:2650
 msgid "output pack to stdout"
 msgstr "вывести пакет на  стандартный вывод"
 
-#: builtin/pack-objects.c:2669
+#: builtin/pack-objects.c:2652
 msgid "include tag objects that refer to objects to be packed"
 msgstr "включить объекты меток, которые ссылаются на упаковываемые объекты"
 
-#: builtin/pack-objects.c:2671
+#: builtin/pack-objects.c:2654
 msgid "keep unreachable objects"
 msgstr "сохранять ссылки на недоступные объекты"
 
-#: builtin/pack-objects.c:2672 parse-options.h:139
+#: builtin/pack-objects.c:2655 parse-options.h:142
 msgid "time"
 msgstr "время"
 
-#: builtin/pack-objects.c:2673
+#: builtin/pack-objects.c:2656
 msgid "unpack unreachable objects newer than <time>"
 msgstr "распаковать недоступные объекты, которые новее, чем <время>"
 
-#: builtin/pack-objects.c:2676
+#: builtin/pack-objects.c:2659
 msgid "create thin packs"
 msgstr "создавать тонкие пакеты"
 
-#: builtin/pack-objects.c:2678
+#: builtin/pack-objects.c:2661
 msgid "create packs suitable for shallow fetches"
 msgstr "создавать пакеты, подходящие для частичных извлечений"
 
-#: builtin/pack-objects.c:2680
+#: builtin/pack-objects.c:2663
 msgid "ignore packs that have companion .keep file"
 msgstr "игнорировать пакеты, рядом с которыми лежит .keep файл"
 
-#: builtin/pack-objects.c:2682
+#: builtin/pack-objects.c:2665
 msgid "pack compression level"
 msgstr "уровень сжатия пакета"
 
-#: builtin/pack-objects.c:2684
+#: builtin/pack-objects.c:2667
 msgid "do not hide commits by grafts"
 msgstr "не скрывать коммиты сращениями"
 
-#: builtin/pack-objects.c:2686
+#: builtin/pack-objects.c:2669
 msgid "use a bitmap index if available to speed up counting objects"
 msgstr "по возможности использовать индекс в битовых картах, для ускорения подсчета объектов"
 
-#: builtin/pack-objects.c:2688
+#: builtin/pack-objects.c:2671
 msgid "write a bitmap index together with the pack index"
 msgstr "запись индекса в битовых картах вместе с индексом пакета"
 
-#: builtin/pack-objects.c:2779
+#: builtin/pack-objects.c:2762
 msgid "Counting objects"
 msgstr "Подсчет объектов"
 
@@ -7845,37 +8261,169 @@ msgstr "Удаление дублирующихся объектов"
 msgid "git prune [-n] [-v] [--expire <time>] [--] [<head>...]"
 msgstr "git prune [-n] [-v] [--expire <время>] [--] [<имя-ветки>…]"
 
-#: builtin/prune.c:105 builtin/worktree.c:112
+#: builtin/prune.c:105 builtin/worktree.c:121
 msgid "do not remove, show only"
 msgstr "не удалять, только показать список"
 
-#: builtin/prune.c:106 builtin/worktree.c:113
+#: builtin/prune.c:106 builtin/worktree.c:122
 msgid "report pruned objects"
 msgstr "вывести список удаленных объектов"
 
-#: builtin/prune.c:109 builtin/worktree.c:115
+#: builtin/prune.c:109 builtin/worktree.c:124
 msgid "expire objects older than <time>"
 msgstr "удалить объекты старее чем <дата-окончания>"
 
-#: builtin/push.c:14
+#: builtin/pull.c:69
+msgid "git pull [options] [<repository> [<refspec>...]]"
+msgstr "git pull [опции] [<репозиторий> [<спецификация-ссылки>…]]"
+
+#: builtin/pull.c:113
+msgid "Options related to merging"
+msgstr "Опции, связанные со слиянием"
+
+#: builtin/pull.c:116
+msgid "incorporate changes by rebasing rather than merging"
+msgstr "забрать изменения с помощью перебазирования, а не слияния"
+
+#: builtin/pull.c:140 builtin/revert.c:105
+msgid "allow fast-forward"
+msgstr "разрешить перемотку вперед"
+
+#: builtin/pull.c:146
+msgid "verify that the named commit has a valid GPG signature"
+msgstr "проверить, что указанный коммит имеет верную электронную подпись GPG"
+
+#: builtin/pull.c:160
+msgid "Options related to fetching"
+msgstr "Опции, связанные с извлечением"
+
+#: builtin/pull.c:268
+#, c-format
+msgid "Invalid value for pull.ff: %s"
+msgstr "Неправильное значение для pull.ff: %s"
+
+#: builtin/pull.c:352
+msgid "Cannot pull with rebase: You have unstaged changes."
+msgstr "Не удалось получить с перемещением: У вас есть непроиндексированные изменения."
+
+#: builtin/pull.c:358
+msgid "Additionally, your index contains uncommitted changes."
+msgstr "К тому же, в вашем индексе есть незакоммиченные изменения."
+
+#: builtin/pull.c:360
+msgid "Cannot pull with rebase: Your index contains uncommitted changes."
+msgstr "Не удалось получить с перемещением: В вашем индексе есть незакоммиченные изменения."
+
+#: builtin/pull.c:436
+msgid ""
+"There is no candidate for rebasing against among the refs that you just "
+"fetched."
+msgstr "Нет претендентов для перемещения среди ссылок, которые вы только что получили."
+
+#: builtin/pull.c:438
+msgid ""
+"There are no candidates for merging among the refs that you just fetched."
+msgstr "Нет претендентов для слияния среди ссылок, которые вы только что получили."
+
+#: builtin/pull.c:439
+msgid ""
+"Generally this means that you provided a wildcard refspec which had no\n"
+"matches on the remote end."
+msgstr "Обычно это означает, что вы передали спецификацию ссылки с помощью шаблона и этот шаблон не совпал ни с одной из ссылок на внешнем репозитории."
+
+#: builtin/pull.c:442
+#, c-format
+msgid ""
+"You asked to pull from the remote '%s', but did not specify\n"
+"a branch. Because this is not the default configured remote\n"
+"for your current branch, you must specify a branch on the command line."
+msgstr "Вы попросили получить изменения со внешнего репозитория «%s», но не указали ветку. Так как это не репозиторий по умолчанию для  вашей текущей ветки, вы должны указать ветку в командной строке."
+
+#: builtin/pull.c:447
+msgid "You are not currently on a branch."
+msgstr "Вы сейчас ни на одной из веток."
+
+#: builtin/pull.c:449 builtin/pull.c:464
+msgid "Please specify which branch you want to rebase against."
+msgstr "Пожалуйста, укажите на какую ветку вы хотите переместить изменения."
+
+#: builtin/pull.c:451 builtin/pull.c:466
+msgid "Please specify which branch you want to merge with."
+msgstr "Пожалуйста, укажите с какой веткой вы хотите слить изменения."
+
+#: builtin/pull.c:452 builtin/pull.c:467
+msgid "See git-pull(1) for details."
+msgstr "Для дополнительной информации смотрите git-pull(1)."
+
+#: builtin/pull.c:462
+msgid "There is no tracking information for the current branch."
+msgstr "У текущей ветки нет информации об отслеживании."
+
+#: builtin/pull.c:471
+#, c-format
+msgid ""
+"If you wish to set tracking information for this branch you can do so with:\n"
+"\n"
+"    git branch --set-upstream-to=%s/<branch> %s\n"
+msgstr "Если вы хотите указать информацию о отслеживаемой ветке, выполните:\n\n    git branch --set-upstream-to=%s/<branch> %s\n"
+
+#: builtin/pull.c:476
+#, c-format
+msgid ""
+"Your configuration specifies to merge with the ref '%s'\n"
+"from the remote, but no such ref was fetched."
+msgstr "Ваша конфигурация указывает, что нужно слить изменения со ссылкой\n«%s» из внешнего репозитория, но такая ссылка не была получена."
+
+#: builtin/pull.c:830
+msgid "Updating an unborn branch with changes added to the index."
+msgstr "Обновление еще не начавшейся ветки с изменениями, добавленными в индекс."
+
+#: builtin/pull.c:859
+#, c-format
+msgid ""
+"fetch updated the current branch head.\n"
+"fast-forwarding your working tree from\n"
+"commit %s."
+msgstr "извлечение обновило голову вашей текущей ветки.\nперемотка вашего рабочего каталога\nс коммита %s."
+
+#: builtin/pull.c:864
+#, c-format
+msgid ""
+"Cannot fast-forward your working tree.\n"
+"After making sure that you saved anything precious from\n"
+"$ git diff %s\n"
+"output, run\n"
+"$ git reset --hard\n"
+"to recover."
+msgstr "Не удалось перемотать вперёд изменения в вашем рабочем каталоге.\nПосле того, как вы убедитесь, что вы сохранили всё необходимое из вывода\n$ git diff %s\n, запустите\n$ git reset --hard\nдля восстановления исходного состояния."
+
+#: builtin/pull.c:879
+msgid "Cannot merge multiple branches into empty head."
+msgstr "Нельзя слить несколько веток в пустой указатель на коммит."
+
+#: builtin/pull.c:883
+msgid "Cannot rebase onto multiple branches."
+msgstr "Невозможно переместить над несколькими ветками."
+
+#: builtin/push.c:15
 msgid "git push [<options>] [<repository> [<refspec>...]]"
 msgstr "git push [<опции>] [<репозиторий> [<спецификация-ссылки>…]]"
 
-#: builtin/push.c:85
+#: builtin/push.c:86
 msgid "tag shorthand without <tag>"
 msgstr "указано сокращение tag, но не указана сама <метка>"
 
-#: builtin/push.c:95
+#: builtin/push.c:96
 msgid "--delete only accepts plain target ref names"
 msgstr "опция --delete принимает только простые целевые имена ссылок"
 
-#: builtin/push.c:139
+#: builtin/push.c:140
 msgid ""
 "\n"
 "To choose either option permanently, see push.default in 'git help config'."
 msgstr "\nЧтобы выбрать любую из опций на постоянной основе, смотрите push.default в «git help config»."
 
-#: builtin/push.c:142
+#: builtin/push.c:143
 #, c-format
 msgid ""
 "The upstream branch of your current branch does not match\n"
@@ -7890,7 +8438,7 @@ msgid ""
 "%s"
 msgstr "Имя вышестоящей ветки и вашей текущей ветки различаются. Чтобы отправить изменения в вышестоящую ветку на внешнем репозитории, используйте:\n\n    git push %s HEAD:%s\n\nЧтобы отправить изменения в ветку с таким же именем на внешнем репозитории, используйте:\n\n    git push %s %s\n%s"
 
-#: builtin/push.c:157
+#: builtin/push.c:158
 #, c-format
 msgid ""
 "You are not currently on a branch.\n"
@@ -7900,7 +8448,7 @@ msgid ""
 "    git push %s HEAD:<name-of-remote-branch>\n"
 msgstr "Вы сейчас не находитесь ни на одной из веток.\nЧтобы отправить историю, ведущую к текущему (отделенный HEAD) состоянию, используйте\n\n    git push %s HEAD:<имя-внешней-ветки>\n"
 
-#: builtin/push.c:171
+#: builtin/push.c:172
 #, c-format
 msgid ""
 "The current branch %s has no upstream branch.\n"
@@ -7909,13 +8457,13 @@ msgid ""
 "    git push --set-upstream %s %s\n"
 msgstr "Текущая ветка %s не имеет вышестоящей ветки.\nЧтобы отправить текущую ветку и установить внешнюю ветку как вышестоящую для этой ветки, используйте\n\n    git push --set-upstream %s %s\n"
 
-#: builtin/push.c:179
+#: builtin/push.c:180
 #, c-format
 msgid ""
 "The current branch %s has multiple upstream branches, refusing to push."
 msgstr "Ваша текущая ветка %s имеет несколько вышестоящих веток, отказ в отправке изменений."
 
-#: builtin/push.c:182
+#: builtin/push.c:183
 #, c-format
 msgid ""
 "You are pushing to remote '%s', which is not the upstream of\n"
@@ -7923,7 +8471,7 @@ msgid ""
 "to update which remote branch."
 msgstr "Вы сейчас отправляете изменения на внешний репозиторий «%s», который не является вышестоящим для вашей текущей ветки «%s», без указания того, что отправлять и в какую внешнюю ветку."
 
-#: builtin/push.c:205
+#: builtin/push.c:206
 msgid ""
 "push.default is unset; its implicit value has changed in\n"
 "Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
@@ -7947,11 +8495,11 @@ msgid ""
 "'current' instead of 'simple' if you sometimes use older versions of Git)"
 msgstr "push.default не установлен; его неявное значение было изменено в Git версии 2.0 с «matching» на «simple». Чтобы прекратить вывод этого сообщения и сохранить старое поведение, используйте:\n\n  git config --global push.default matching\n\nЧтобы прекратить вывод этого сообщения и использовать новое поведение, используйте:\n\n  git config --global push.default simple\n\nКогда push.default установлено в «matching», git будет отправлять изменения локальных веток в существующие внешние ветки с таким же именем.\n\nНачиная с Git версии 2.0, по умолчанию используется более консервативное поведение «simple», которое отправляет изменения текущей ветки в соответствующую внешнюю ветку, из которой «git pull» забирает изменения.\n\nСмотрите «git help config» и ищите «push.default» для дополнительной информации.\n(режим «simple» появился в Git версии 1.7.11. Используйте похожий режим «current» вместо «simple», если вы иногда используете старые версии Git)"
 
-#: builtin/push.c:272
+#: builtin/push.c:273
 msgid "You didn't specify any refspecs to push, and push.default is \"nothing\"."
 msgstr "Вы не указали спецификацию ссылки для отправки, а push.default указан как \"nothing\"."
 
-#: builtin/push.c:279
+#: builtin/push.c:280
 msgid ""
 "Updates were rejected because the tip of your current branch is behind\n"
 "its remote counterpart. Integrate the remote changes (e.g.\n"
@@ -7959,7 +8507,7 @@ msgid ""
 "See the 'Note about fast-forwards' in 'git push --help' for details."
 msgstr "Обновления были отклонены, так как верхушка вашей текущей ветки\nпозади ее внешней части. Заберите и слейте внешние изменения \n(например, с помощью «git pull …») перед повторной попыткой отправки\nизменений.\nДля дополнительной информации смотрите «Note about fast-forwards»\nв «git push --help»."
 
-#: builtin/push.c:285
+#: builtin/push.c:286
 msgid ""
 "Updates were rejected because a pushed branch tip is behind its remote\n"
 "counterpart. Check out this branch and integrate the remote changes\n"
@@ -7967,7 +8515,7 @@ msgid ""
 "See the 'Note about fast-forwards' in 'git push --help' for details."
 msgstr "Обновления были отклонены, так как верхушка отправляемой ветки\nпозади ее внешней части. Переключитесь на ветку и заберите внешние\nизменения (например, с помощью «git pull …») перед повторной\nпопыткой отправки изменений.\nДля дополнительной информации смотрите «Note about fast-forwards»\nв «git push --help»."
 
-#: builtin/push.c:291
+#: builtin/push.c:292
 msgid ""
 "Updates were rejected because the remote contains work that you do\n"
 "not have locally. This is usually caused by another repository pushing\n"
@@ -7976,33 +8524,33 @@ msgid ""
 "See the 'Note about fast-forwards' in 'git push --help' for details."
 msgstr "Обновления были отклонены, так как внешний репозиторий содержит\nизменения, которых у вас нет в вашем локальном репозитории.\nОбычно, это связанно с тем, что кто-то уже отправил изменения в \nто же место. Перед повторной отправкой ваших изменений, вам нужно\nзабрать и слить изменения из внешнего репозитория себе\n(например, с помощью «git pull …»).\nДля дополнительной информации смотрите «Note about fast-forwards»\nв «git push --help»."
 
-#: builtin/push.c:298
+#: builtin/push.c:299
 msgid "Updates were rejected because the tag already exists in the remote."
 msgstr "Обновления были отклонены, так как метка уже существует во внешнем репозитории."
 
-#: builtin/push.c:301
+#: builtin/push.c:302
 msgid ""
 "You cannot update a remote ref that points at a non-commit object,\n"
 "or update a remote ref to make it point at a non-commit object,\n"
 "without using the '--force' option.\n"
 msgstr "Вы не можете обновить внешнюю ссылку, которая указывает на объект, не являющийся коммитом или обновить внешнюю ссылку так, чтобы она указывала на объект, не являющийся коммитом, без указания опции «--force».\n"
 
-#: builtin/push.c:360
+#: builtin/push.c:361
 #, c-format
 msgid "Pushing to %s\n"
 msgstr "Отправка в %s\n"
 
-#: builtin/push.c:364
+#: builtin/push.c:365
 #, c-format
 msgid "failed to push some refs to '%s'"
 msgstr "не удалось отправить некоторые ссылки в «%s»"
 
-#: builtin/push.c:394
+#: builtin/push.c:395
 #, c-format
 msgid "bad repository '%s'"
 msgstr "плохой репозитория «%s»"
 
-#: builtin/push.c:395
+#: builtin/push.c:396
 msgid ""
 "No configured push destination.\n"
 "Either specify the URL from the command-line or configure a remote repository using\n"
@@ -8014,108 +8562,109 @@ msgid ""
 "    git push <name>\n"
 msgstr "Не настроена точка назначения для отправки.\nЛибо укажите URL с помощью коммандной строки, либо настройте внешний репозиторий с помощью\n\n    git remote add <имя> <адрес>\n\nа затем отправьте изменения с помощью имени внешнего репозитория\n\n    git push <имя>\n"
 
-#: builtin/push.c:410
+#: builtin/push.c:411
 msgid "--all and --tags are incompatible"
 msgstr "--all и --tags нельзя использовать одновременно"
 
-#: builtin/push.c:411
+#: builtin/push.c:412
 msgid "--all can't be combined with refspecs"
 msgstr "--all нельзя использовать вместе со спецификациями ссылок"
 
-#: builtin/push.c:416
+#: builtin/push.c:417
 msgid "--mirror and --tags are incompatible"
 msgstr "--mirror и --tags нельзя использовать одновременно"
 
-#: builtin/push.c:417
+#: builtin/push.c:418
 msgid "--mirror can't be combined with refspecs"
 msgstr "--mirror нельзя использовать вместе со спецификациями ссылок"
 
-#: builtin/push.c:422
+#: builtin/push.c:423
 msgid "--all and --mirror are incompatible"
 msgstr "--all и --mirror нельзя использовать одновременно"
 
-#: builtin/push.c:502
+#: builtin/push.c:539
 msgid "repository"
 msgstr "репозиторий"
 
-#: builtin/push.c:503
+#: builtin/push.c:540 builtin/send-pack.c:161
 msgid "push all refs"
 msgstr "отправить все ссылки"
 
-#: builtin/push.c:504
+#: builtin/push.c:541 builtin/send-pack.c:163
 msgid "mirror all refs"
 msgstr "сделать зеркало всех ссылок"
 
-#: builtin/push.c:506
+#: builtin/push.c:543
 msgid "delete refs"
 msgstr "удалить ссылки"
 
-#: builtin/push.c:507
+#: builtin/push.c:544
 msgid "push tags (can't be used with --all or --mirror)"
 msgstr "отправить метки (нельзя использовать вместе с --all или --mirror)"
 
-#: builtin/push.c:510
+#: builtin/push.c:547 builtin/send-pack.c:164
 msgid "force updates"
 msgstr "принудительное обновление"
 
-#: builtin/push.c:512
+#: builtin/push.c:549 builtin/send-pack.c:175
 msgid "refname>:<expect"
 msgstr "имя-ссылки>:<ожидается"
 
-#: builtin/push.c:513
+#: builtin/push.c:550 builtin/send-pack.c:176
 msgid "require old value of ref to be at this value"
 msgstr "требовать, чтобы старое значение ссылки было ожидаемым"
 
-#: builtin/push.c:516
+#: builtin/push.c:553
 msgid "control recursive pushing of submodules"
 msgstr "управление рекурсивной отправкой подмодулей"
 
-#: builtin/push.c:518
+#: builtin/push.c:555 builtin/send-pack.c:169
 msgid "use thin pack"
 msgstr "использовать тонкие пакеты"
 
-#: builtin/push.c:519 builtin/push.c:520
+#: builtin/push.c:556 builtin/push.c:557 builtin/send-pack.c:158
+#: builtin/send-pack.c:159
 msgid "receive pack program"
 msgstr "путь к программе упаковки на сервере"
 
-#: builtin/push.c:521
+#: builtin/push.c:558
 msgid "set upstream for git pull/status"
 msgstr "установить вышестоящую ветку для git pull/status"
 
-#: builtin/push.c:524
+#: builtin/push.c:561
 msgid "prune locally removed refs"
 msgstr "почистить локально удаленные ссылки"
 
-#: builtin/push.c:526
+#: builtin/push.c:563
 msgid "bypass pre-push hook"
 msgstr "пропустить перехватчик перед-отправкой"
 
-#: builtin/push.c:527
+#: builtin/push.c:564
 msgid "push missing but relevant tags"
 msgstr "отправить пропущенные, но нужные метки"
 
-#: builtin/push.c:529
+#: builtin/push.c:567 builtin/send-pack.c:166
 msgid "GPG sign the push"
 msgstr "подписать отправку с помощью GPG"
 
-#: builtin/push.c:530
+#: builtin/push.c:569 builtin/send-pack.c:170
 msgid "request atomic transaction on remote side"
 msgstr "запросить выполнение атомарной транзакции на внешней стороне"
 
-#: builtin/push.c:539
+#: builtin/push.c:579
 msgid "--delete is incompatible with --all, --mirror and --tags"
 msgstr "--delete несовместимо с  --all, --mirror и --tags"
 
-#: builtin/push.c:541
+#: builtin/push.c:581
 msgid "--delete doesn't make sense without any refs"
 msgstr "--delete не имеет смысла без указания ссылок"
 
 #: builtin/read-tree.c:37
 msgid ""
-"git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]"
+"git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>)"
 " [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] "
 "[--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"
-msgstr "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<префикс>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<файл>] (--empty | <указатель-дерева-1> [<указатель-дерева-2> [<указатель-дерева-3>]])"
+msgstr "git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<префикс>) [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<файл>] (--empty | <указатель-дерева-1> [<указатель-дерева-2> [<указатель-дерева-3>]])"
 
 #: builtin/read-tree.c:110
 msgid "write resulting index to <file>"
@@ -8181,12 +8730,12 @@ msgstr "пропустить применение фильтра частичн
 msgid "debug unpack-trees"
 msgstr "отладка unpack-trees"
 
-#: builtin/reflog.c:430
+#: builtin/reflog.c:432
 #, c-format
-msgid "%s' for '%s' is not a valid timestamp"
+msgid "'%s' for '%s' is not a valid timestamp"
 msgstr "«%s» для «%s» не является допустимой меткой даты/времени"
 
-#: builtin/reflog.c:547 builtin/reflog.c:552
+#: builtin/reflog.c:549 builtin/reflog.c:554
 #, c-format
 msgid "'%s' is not a valid timestamp"
 msgstr "«%s» не является допустимой меткой даты/времени"
@@ -8318,12 +8867,12 @@ msgstr "указание мастер ветки не имеет смысла с
 msgid "specifying branches to track makes sense only with fetch mirrors"
 msgstr "указание отслеживаемых веток имеет смысл только при зеркальном извлечении"
 
-#: builtin/remote.c:187 builtin/remote.c:640
+#: builtin/remote.c:187 builtin/remote.c:637
 #, c-format
 msgid "remote %s already exists."
 msgstr "внешний репозиторий %s уже существует"
 
-#: builtin/remote.c:191 builtin/remote.c:644
+#: builtin/remote.c:191 builtin/remote.c:641
 #, c-format
 msgid "'%s' is not a valid remote name"
 msgstr "«%s» не является допустимым именем внешнего репозитория."
@@ -8346,27 +8895,27 @@ msgstr "(соответствующая)"
 msgid "(delete)"
 msgstr "(удаленная)"
 
-#: builtin/remote.c:589 builtin/remote.c:595 builtin/remote.c:601
+#: builtin/remote.c:588 builtin/remote.c:594 builtin/remote.c:600
 #, c-format
 msgid "Could not append '%s' to '%s'"
 msgstr "Не удалось добавить «%s» к «%s»"
 
-#: builtin/remote.c:633 builtin/remote.c:792 builtin/remote.c:892
+#: builtin/remote.c:630 builtin/remote.c:769 builtin/remote.c:869
 #, c-format
 msgid "No such remote: %s"
 msgstr "Нет такого внешнего репозитория: %s"
 
-#: builtin/remote.c:650
+#: builtin/remote.c:647
 #, c-format
 msgid "Could not rename config section '%s' to '%s'"
 msgstr "Не удалось переименовать секцию конфигурации с «%s» на «%s»"
 
-#: builtin/remote.c:656 builtin/remote.c:844
+#: builtin/remote.c:653 builtin/remote.c:821
 #, c-format
 msgid "Could not remove config section '%s'"
 msgstr "Не удалось удалить секцию файла конфигурации «%s»"
 
-#: builtin/remote.c:671
+#: builtin/remote.c:668
 #, c-format
 msgid ""
 "Not updating non-default fetch refspec\n"
@@ -8374,32 +8923,27 @@ msgid ""
 "\tPlease update the configuration manually if necessary."
 msgstr "Не обновляю нестандартную спецификацию ссылки для извлечения\n\t%s\n\tПожалуйста, если требуется, обновите конфигурацию вручную."
 
-#: builtin/remote.c:677
+#: builtin/remote.c:674
 #, c-format
 msgid "Could not append '%s'"
 msgstr "Не удалось добавить «%s»"
 
-#: builtin/remote.c:688
+#: builtin/remote.c:685
 #, c-format
 msgid "Could not set '%s'"
 msgstr "Не удалось установить «%s»"
 
-#: builtin/remote.c:710
+#: builtin/remote.c:707
 #, c-format
 msgid "deleting '%s' failed"
 msgstr "не удалось удалить «%s»"
 
-#: builtin/remote.c:744
+#: builtin/remote.c:741
 #, c-format
 msgid "creating '%s' failed"
 msgstr "не удалось создать «%s»"
 
-#: builtin/remote.c:763
-#, c-format
-msgid "Could not remove branch %s"
-msgstr "Не удалось удалить ветку «%s»"
-
-#: builtin/remote.c:830
+#: builtin/remote.c:807
 msgid ""
 "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
 "to delete it, use:"
@@ -8411,125 +8955,125 @@ msgstr[1] "Примечание: Некоторые ветки вне иерар
 msgstr[2] "Примечание: Некоторые ветки вне иерархии refs/remotes/ не будут удалены;\nчтобы удалить их, используйте:"
 msgstr[3] "Примечание: Некоторые ветки вне иерархии refs/remotes/ не будут удалены;\nчтобы удалить их, используйте:"
 
-#: builtin/remote.c:945
+#: builtin/remote.c:922
 #, c-format
 msgid " new (next fetch will store in remotes/%s)"
 msgstr " новая (следующее извлечение сохранит ее в remotes/%s)"
 
-#: builtin/remote.c:948
+#: builtin/remote.c:925
 msgid " tracked"
 msgstr " отслеживается"
 
-#: builtin/remote.c:950
+#: builtin/remote.c:927
 msgid " stale (use 'git remote prune' to remove)"
 msgstr " недействительна (используйте «git remote prune», чтобы удалить)"
 
-#: builtin/remote.c:952
+#: builtin/remote.c:929
 msgid " ???"
 msgstr " ???"
 
-#: builtin/remote.c:993
+#: builtin/remote.c:970
 #, c-format
 msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch"
 msgstr "неправильный параметр конфигурации branch.%s.merge; невозможно переместить более чем над 1 веткой"
 
-#: builtin/remote.c:1000
+#: builtin/remote.c:977
 #, c-format
 msgid "rebases onto remote %s"
 msgstr "будет перемещена над внешней веткой %s"
 
-#: builtin/remote.c:1003
+#: builtin/remote.c:980
 #, c-format
 msgid " merges with remote %s"
 msgstr " будет слита с внешней веткой %s"
 
-#: builtin/remote.c:1004
+#: builtin/remote.c:981
 msgid "    and with remote"
 msgstr "    и с внешней веткой"
 
-#: builtin/remote.c:1006
+#: builtin/remote.c:983
 #, c-format
 msgid "merges with remote %s"
 msgstr "будет слита с внешней веткой %s"
 
-#: builtin/remote.c:1007
+#: builtin/remote.c:984
 msgid "   and with remote"
 msgstr "   и с внешней веткой"
 
-#: builtin/remote.c:1053
+#: builtin/remote.c:1030
 msgid "create"
 msgstr "создана"
 
-#: builtin/remote.c:1056
+#: builtin/remote.c:1033
 msgid "delete"
 msgstr "удалена"
 
-#: builtin/remote.c:1060
+#: builtin/remote.c:1037
 msgid "up to date"
 msgstr "уже актуальна"
 
-#: builtin/remote.c:1063
+#: builtin/remote.c:1040
 msgid "fast-forwardable"
 msgstr "возможна перемотка вперед"
 
-#: builtin/remote.c:1066
+#: builtin/remote.c:1043
 msgid "local out of date"
 msgstr "локальная ветка устарела"
 
-#: builtin/remote.c:1073
+#: builtin/remote.c:1050
 #, c-format
 msgid "    %-*s forces to %-*s (%s)"
 msgstr "    %-*s будет принудительно отправлена в %-*s (%s)"
 
-#: builtin/remote.c:1076
+#: builtin/remote.c:1053
 #, c-format
 msgid "    %-*s pushes to %-*s (%s)"
 msgstr "    %-*s будет отправлена в %-*s (%s)"
 
-#: builtin/remote.c:1080
+#: builtin/remote.c:1057
 #, c-format
 msgid "    %-*s forces to %s"
 msgstr "    %-*s будет принудительно отправлена в %s"
 
-#: builtin/remote.c:1083
+#: builtin/remote.c:1060
 #, c-format
 msgid "    %-*s pushes to %s"
 msgstr "    %-*s будет отправлена в %s"
 
-#: builtin/remote.c:1151
+#: builtin/remote.c:1128
 msgid "do not query remotes"
 msgstr "не опрашивать внешние репозитории"
 
-#: builtin/remote.c:1178
+#: builtin/remote.c:1155
 #, c-format
 msgid "* remote %s"
 msgstr "* внешний репозиторий %s"
 
-#: builtin/remote.c:1179
+#: builtin/remote.c:1156
 #, c-format
 msgid "  Fetch URL: %s"
 msgstr "  URL для извлечения: %s"
 
-#: builtin/remote.c:1180 builtin/remote.c:1331
+#: builtin/remote.c:1157 builtin/remote.c:1308
 msgid "(no URL)"
 msgstr "(нет URL)"
 
-#: builtin/remote.c:1189 builtin/remote.c:1191
+#: builtin/remote.c:1166 builtin/remote.c:1168
 #, c-format
 msgid "  Push  URL: %s"
 msgstr "  URL для отправки: %s"
 
-#: builtin/remote.c:1193 builtin/remote.c:1195 builtin/remote.c:1197
+#: builtin/remote.c:1170 builtin/remote.c:1172 builtin/remote.c:1174
 #, c-format
 msgid "  HEAD branch: %s"
 msgstr "  HEAD ветка: %s"
 
-#: builtin/remote.c:1199
+#: builtin/remote.c:1176
 #, c-format
 msgid "  HEAD branch (remote HEAD is ambiguous, may be one of the following):\n"
 msgstr "  HEAD ветка (HEAD внешнего репозитория неоднозначный, может быть одним из):\n"
 
-#: builtin/remote.c:1211
+#: builtin/remote.c:1188
 #, c-format
 msgid "  Remote branch:%s"
 msgid_plural "  Remote branches:%s"
@@ -8538,11 +9082,11 @@ msgstr[1] "    Внешние ветки:%s"
 msgstr[2] "    Внешние ветки:%s"
 msgstr[3] "    Внешние ветки:%s"
 
-#: builtin/remote.c:1214 builtin/remote.c:1241
+#: builtin/remote.c:1191 builtin/remote.c:1218
 msgid " (status not queried)"
 msgstr " (статус не запрошен)"
 
-#: builtin/remote.c:1223
+#: builtin/remote.c:1200
 msgid "  Local branch configured for 'git pull':"
 msgid_plural "  Local branches configured for 'git pull':"
 msgstr[0] "  Локальная ветка, настроенная для «git pull»:"
@@ -8550,11 +9094,11 @@ msgstr[1] "  Локальные ветки, настроенные для «git
 msgstr[2] "  Локальные ветки, настроенные для «git pull»:"
 msgstr[3] "  Локальные ветки, настроенные для «git pull»:"
 
-#: builtin/remote.c:1231
+#: builtin/remote.c:1208
 msgid "  Local refs will be mirrored by 'git push'"
 msgstr " Локальные ссылки, зеркалируемые с помощью «git push»"
 
-#: builtin/remote.c:1238
+#: builtin/remote.c:1215
 #, c-format
 msgid "  Local ref configured for 'git push'%s:"
 msgid_plural "  Local refs configured for 'git push'%s:"
@@ -8563,115 +9107,115 @@ msgstr[1] "  Локальные ссылки, настроенные для «gi
 msgstr[2] "  Локальные ссылки, настроенные для «git push»%s:"
 msgstr[3] "  Локальные ссылки, настроенные для «git push»%s:"
 
-#: builtin/remote.c:1259
+#: builtin/remote.c:1236
 msgid "set refs/remotes/<name>/HEAD according to remote"
 msgstr "установить refs/remotes/<имя>/HEAD в зависимости от внешнего репозитория"
 
-#: builtin/remote.c:1261
+#: builtin/remote.c:1238
 msgid "delete refs/remotes/<name>/HEAD"
 msgstr "удалить refs/remotes/<имя>/HEAD"
 
-#: builtin/remote.c:1276
+#: builtin/remote.c:1253
 msgid "Cannot determine remote HEAD"
 msgstr "Не удалось определить внешний HEAD"
 
-#: builtin/remote.c:1278
+#: builtin/remote.c:1255
 msgid "Multiple remote HEAD branches. Please choose one explicitly with:"
 msgstr "Несколько внешних HEAD веток. Укажите явно одну из них:"
 
-#: builtin/remote.c:1288
+#: builtin/remote.c:1265
 #, c-format
 msgid "Could not delete %s"
 msgstr "Не удалось удалить %s"
 
-#: builtin/remote.c:1296
+#: builtin/remote.c:1273
 #, c-format
 msgid "Not a valid ref: %s"
 msgstr "Неправильная ссылка: %s"
 
-#: builtin/remote.c:1298
+#: builtin/remote.c:1275
 #, c-format
 msgid "Could not setup %s"
 msgstr "Не удалось настроить %s"
 
-#: builtin/remote.c:1316
+#: builtin/remote.c:1293
 #, c-format
 msgid " %s will become dangling!"
 msgstr " %s будет висящей веткой!"
 
-#: builtin/remote.c:1317
+#: builtin/remote.c:1294
 #, c-format
 msgid " %s has become dangling!"
 msgstr " %s стала висящей веткой!"
 
-#: builtin/remote.c:1327
+#: builtin/remote.c:1304
 #, c-format
 msgid "Pruning %s"
 msgstr "Удаление %s"
 
-#: builtin/remote.c:1328
+#: builtin/remote.c:1305
 #, c-format
 msgid "URL: %s"
 msgstr "URL: %s"
 
-#: builtin/remote.c:1351
+#: builtin/remote.c:1321
 #, c-format
 msgid " * [would prune] %s"
 msgstr " * [будет удалена] %s"
 
-#: builtin/remote.c:1354
+#: builtin/remote.c:1324
 #, c-format
 msgid " * [pruned] %s"
 msgstr " * [удалена] %s"
 
-#: builtin/remote.c:1399
+#: builtin/remote.c:1369
 msgid "prune remotes after fetching"
 msgstr "почистить внешние репозитории после извлечения"
 
-#: builtin/remote.c:1465 builtin/remote.c:1539
+#: builtin/remote.c:1435 builtin/remote.c:1509
 #, c-format
 msgid "No such remote '%s'"
 msgstr "Нет такого внешнего репозитория «%s»"
 
-#: builtin/remote.c:1485
+#: builtin/remote.c:1455
 msgid "add branch"
 msgstr "добавить ветку"
 
-#: builtin/remote.c:1492
+#: builtin/remote.c:1462
 msgid "no remote specified"
 msgstr "не указан внешний репозиторий"
 
-#: builtin/remote.c:1514
+#: builtin/remote.c:1484
 msgid "manipulate push URLs"
 msgstr "управление URL отправки"
 
-#: builtin/remote.c:1516
+#: builtin/remote.c:1486
 msgid "add URL"
 msgstr "добавить URL"
 
-#: builtin/remote.c:1518
+#: builtin/remote.c:1488
 msgid "delete URLs"
 msgstr "удалить URL"
 
-#: builtin/remote.c:1525
+#: builtin/remote.c:1495
 msgid "--add --delete doesn't make sense"
 msgstr "--add нельзя использовать одновременно с --delete"
 
-#: builtin/remote.c:1565
+#: builtin/remote.c:1535
 #, c-format
 msgid "Invalid old URL pattern: %s"
 msgstr "Неправильный шаблон старого URL: %s"
 
-#: builtin/remote.c:1573
+#: builtin/remote.c:1543
 #, c-format
 msgid "No such URL found: %s"
 msgstr "Не найдены совпадения URL: %s"
 
-#: builtin/remote.c:1575
+#: builtin/remote.c:1545
 msgid "Will not delete all non-push URLs"
 msgstr "Нельзя удалить все URL не-отправки"
 
-#: builtin/remote.c:1589
+#: builtin/remote.c:1559
 msgid "be verbose; must be placed before a subcommand"
 msgstr "быть многословнее; должно стоять перед подкомандой"
 
@@ -8743,7 +9287,7 @@ msgstr "максимальный размер каждого из файлов 
 msgid "repack objects in packs marked with .keep"
 msgstr "переупаковать объекты в пакеты, помеченные файлом .keep"
 
-#: builtin/repack.c:377
+#: builtin/repack.c:378
 #, c-format
 msgid "removing '%s' failed"
 msgstr "не удалось удалить «%s»"
@@ -8925,11 +9469,6 @@ msgstr "записать только факт того, что удаленны
 msgid "Failed to resolve '%s' as a valid revision."
 msgstr "Не удалось распознать «%s» как действительную редакцию."
 
-#: builtin/reset.c:308 builtin/reset.c:316
-#, c-format
-msgid "Could not parse object '%s'."
-msgstr "Не удалось разобрать объект «%s»."
-
 #: builtin/reset.c:313
 #, c-format
 msgid "Failed to resolve '%s' as a valid tree."
@@ -8970,6 +9509,10 @@ msgstr "Не удалось сбросить файл индекса на ред
 msgid "Could not write new index file."
 msgstr "Не удалось записать новый файл индекса."
 
+#: builtin/rev-list.c:354
+msgid "rev-list does not support display of notes"
+msgstr "rev-list не поддерживает отображение заметок"
+
 #: builtin/rev-parse.c:361
 msgid "git rev-parse --parseopt [<options>] -- [<args>...]"
 msgstr "git rev-parse --parseopt [<опции>] -- [<аргументы>…]"
@@ -8986,7 +9529,7 @@ msgstr "остановить разбор после первого аргуме
 msgid "output in stuck long form"
 msgstr "выводить аргументы в длинном формате"
 
-#: builtin/rev-parse.c:499
+#: builtin/rev-parse.c:502
 msgid ""
 "git rev-parse --parseopt [<options>] -- [<args>...]\n"
 "   or: git rev-parse --sq-quote [<arg>...]\n"
@@ -9056,10 +9599,6 @@ msgstr "опция для стратегии слияния"
 msgid "append commit name"
 msgstr "добавить имя коммита"
 
-#: builtin/revert.c:105
-msgid "allow fast-forward"
-msgstr "разрешить перемотку вперед"
-
 #: builtin/revert.c:106
 msgid "preserve initially empty commits"
 msgstr "сохранить изначально пустые коммиты"
@@ -9180,6 +9719,28 @@ msgstr "не удаляю рекурсивно «%s» без указания о
 msgid "git rm: unable to remove %s"
 msgstr "git rm: не удалось удалить %s"
 
+#: builtin/send-pack.c:18
+msgid ""
+"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n"
+"  --all and explicit <ref> specification are mutually exclusive."
+msgstr "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<сервер>:]<каталог> [<ссылка>…]\n  --all и явная спецификация <ссылки> взаимно исключающие."
+
+#: builtin/send-pack.c:160
+msgid "remote name"
+msgstr "имя внешнего репозитория"
+
+#: builtin/send-pack.c:171
+msgid "use stateless RPC protocol"
+msgstr "протокол без сохранения состояния для RPC"
+
+#: builtin/send-pack.c:172
+msgid "read refs from stdin"
+msgstr "прочитать ссылки из стандартного ввода"
+
+#: builtin/send-pack.c:173
+msgid "print status from remote helper"
+msgstr "вывести статус от скрипта внешнего сервера"
+
 #: builtin/shortlog.c:13
 msgid "git shortlog [<options>] [<revision-range>] [[--] [<path>...]]"
 msgstr "git shortlog [<опции>] [<диапазон-редакций>] [[--] [<путь>…]]"
@@ -9292,8 +9853,8 @@ msgid ""
 msgstr "git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [<шаблон>…]"
 
 #: builtin/show-ref.c:11
-msgid "git show-ref --exclude-existing[=pattern] < ref-list"
-msgstr "git show-ref --exclude-existing[=шаблон] < ref-list"
+msgid "git show-ref --exclude-existing[=<pattern>] < <ref-list>"
+msgstr "git show-ref --exclude-existing[=<шаблон>] < <список-ссылок>"
 
 #: builtin/show-ref.c:170
 msgid "only show tags (can be combined with heads)"
@@ -9347,11 +9908,11 @@ msgstr "удалить символьные ссылки"
 msgid "shorten ref output"
 msgstr "укороченный вывод ссылок"
 
-#: builtin/symbolic-ref.c:43 builtin/update-ref.c:358
+#: builtin/symbolic-ref.c:43 builtin/update-ref.c:362
 msgid "reason"
 msgstr "причина"
 
-#: builtin/symbolic-ref.c:43 builtin/update-ref.c:358
+#: builtin/symbolic-ref.c:43 builtin/update-ref.c:362
 msgid "reason of the update"
 msgstr "причина обновления"
 
@@ -9462,110 +10023,114 @@ msgstr "при указании параметра «points-at» требует
 msgid "malformed object name '%s'"
 msgstr "Поврежденное имя объекта «%s»"
 
-#: builtin/tag.c:589
+#: builtin/tag.c:590
 msgid "list tag names"
 msgstr "список названий меток"
 
-#: builtin/tag.c:591
+#: builtin/tag.c:592
 msgid "print <n> lines of each tag message"
 msgstr "печатать <n> строк описания от каждой метки"
 
-#: builtin/tag.c:593
+#: builtin/tag.c:594
 msgid "delete tags"
 msgstr "удалить метки"
 
-#: builtin/tag.c:594
+#: builtin/tag.c:595
 msgid "verify tags"
 msgstr "проверить метки"
 
-#: builtin/tag.c:596
+#: builtin/tag.c:597
 msgid "Tag creation options"
 msgstr "Настройки создания метки"
 
-#: builtin/tag.c:598
+#: builtin/tag.c:599
 msgid "annotated tag, needs a message"
 msgstr "для аннотированной метки нужно сообщение"
 
-#: builtin/tag.c:600
+#: builtin/tag.c:601
 msgid "tag message"
 msgstr "описание метки"
 
-#: builtin/tag.c:602
+#: builtin/tag.c:603
 msgid "annotated and GPG-signed tag"
 msgstr "аннотированная и подписанная с помощью GPG метка"
 
-#: builtin/tag.c:606
+#: builtin/tag.c:607
 msgid "use another key to sign the tag"
 msgstr "использовать другой ключ для подписания метки"
 
-#: builtin/tag.c:607
+#: builtin/tag.c:608
 msgid "replace the tag if exists"
 msgstr "замена метки, если она существует"
 
-#: builtin/tag.c:609
+#: builtin/tag.c:609 builtin/update-ref.c:368
+msgid "create a reflog"
+msgstr "создать журнал ссылок"
+
+#: builtin/tag.c:611
 msgid "Tag listing options"
 msgstr "Настройки вывода списка меток"
 
-#: builtin/tag.c:610
+#: builtin/tag.c:612
 msgid "show tag list in columns"
 msgstr "показать список меток по столбцам"
 
-#: builtin/tag.c:612
+#: builtin/tag.c:614
 msgid "sort tags"
 msgstr "отсортировать метки"
 
-#: builtin/tag.c:617 builtin/tag.c:623
+#: builtin/tag.c:619 builtin/tag.c:625
 msgid "print only tags that contain the commit"
 msgstr "вывод только меток, которые содержат коммит"
 
-#: builtin/tag.c:629
+#: builtin/tag.c:631
 msgid "print only tags of the object"
 msgstr "вывод только меток, определенного объекта"
 
-#: builtin/tag.c:655
+#: builtin/tag.c:657
 msgid "--column and -n are incompatible"
 msgstr "--column и -n нельзя использовать одновременно"
 
-#: builtin/tag.c:667
+#: builtin/tag.c:669
 msgid "--sort and -n are incompatible"
 msgstr "--sort и -n нельзя использовать одновременно"
 
-#: builtin/tag.c:674
+#: builtin/tag.c:676
 msgid "-n option is only allowed with -l."
 msgstr "опцию -n можно использовать только вместе с -l."
 
-#: builtin/tag.c:676
+#: builtin/tag.c:678
 msgid "--contains option is only allowed with -l."
 msgstr "опцию --contains можно использовать только вместе с -l."
 
-#: builtin/tag.c:678
+#: builtin/tag.c:680
 msgid "--points-at option is only allowed with -l."
 msgstr "опцию --points-at можно использовать только вместе с -l."
 
-#: builtin/tag.c:686
+#: builtin/tag.c:688
 msgid "only one -F or -m option is allowed."
 msgstr "-F и -m нельзя использовать одновременно."
 
-#: builtin/tag.c:706
+#: builtin/tag.c:708
 msgid "too many params"
 msgstr "передано слишком много параметров"
 
-#: builtin/tag.c:712
+#: builtin/tag.c:714
 #, c-format
 msgid "'%s' is not a valid tag name."
 msgstr "«%s» не является допустимым именем метки."
 
-#: builtin/tag.c:717
+#: builtin/tag.c:719
 #, c-format
 msgid "tag '%s' already exists"
 msgstr "метка «%s» уже существует"
 
-#: builtin/tag.c:741
+#: builtin/tag.c:744
 #, c-format
 msgid "Updated tag '%s' (was %s)\n"
 msgstr "Метка «%s» обновлена (была %s)\n"
 
-#: builtin/unpack-objects.c:489
+#: builtin/unpack-objects.c:490
 msgid "Unpacking objects"
 msgstr "Распаковка объектов"
 
@@ -9763,19 +10328,19 @@ msgstr "git update-ref [<опции>]    <имя-ссылки> <новое-зн
 msgid "git update-ref [<options>] --stdin [-z]"
 msgstr "git update-ref [<опции>] --stdin [-z]"
 
-#: builtin/update-ref.c:359
+#: builtin/update-ref.c:363
 msgid "delete the reference"
 msgstr "удалить ссылку"
 
-#: builtin/update-ref.c:361
+#: builtin/update-ref.c:365
 msgid "update <refname> not the one it points to"
 msgstr "обновить <имя-ссылки> а не то, на что она указывает"
 
-#: builtin/update-ref.c:362
+#: builtin/update-ref.c:366
 msgid "stdin has NUL-terminated arguments"
 msgstr "ввод отделенный НУЛЕВЫМИ символами"
 
-#: builtin/update-ref.c:363
+#: builtin/update-ref.c:367
 msgid "read updates from stdin"
 msgstr "прочитать обновления из стандартного ввода"
 
@@ -9791,10 +10356,14 @@ msgstr "обновить информацию о серверах с нуля"
 msgid "git verify-commit [-v | --verbose] <commit>..."
 msgstr "git verify-commit [-v | --verbose] <коммит>…"
 
-#: builtin/verify-commit.c:75
+#: builtin/verify-commit.c:72
 msgid "print commit contents"
 msgstr "вывести содержимое коммита"
 
+#: builtin/verify-commit.c:73 builtin/verify-tag.c:84
+msgid "print raw gpg status output"
+msgstr "выводить сырой вывод статуса от gpg"
+
 #: builtin/verify-pack.c:54
 msgid "git verify-pack [-v | --verbose] [-s | --stat-only] <pack>..."
 msgstr "git verify-pack [-v | --verbose] [-s | --stat-only] <пакет>…"
@@ -9811,86 +10380,82 @@ msgstr "вывести только статистику"
 msgid "git verify-tag [-v | --verbose] <tag>..."
 msgstr "git verify-tag [-v | --verbose] <метка>…"
 
-#: builtin/verify-tag.c:73
+#: builtin/verify-tag.c:83
 msgid "print tag contents"
 msgstr "вывести содержимое метки"
 
-#: builtin/worktree.c:11
+#: builtin/worktree.c:13
 msgid "git worktree add [<options>] <path> <branch>"
 msgstr "git worktree add [<опции>] <путь> <ветка>"
 
-#: builtin/worktree.c:12
+#: builtin/worktree.c:14
 msgid "git worktree prune [<options>]"
 msgstr "git worktree prune [<опции>]"
 
-#: builtin/worktree.c:27
+#: builtin/worktree.c:36
 #, c-format
 msgid "Removing worktrees/%s: not a valid directory"
 msgstr "Удаление рабочих каталогов/%s: не является каталогом"
 
-#: builtin/worktree.c:33
+#: builtin/worktree.c:42
 #, c-format
 msgid "Removing worktrees/%s: gitdir file does not exist"
 msgstr "Удаление рабочих каталогов/%s: файл gitdir не существует"
 
-#: builtin/worktree.c:38
+#: builtin/worktree.c:47
 #, c-format
 msgid "Removing worktrees/%s: unable to read gitdir file (%s)"
 msgstr "Удаление рабочих каталогов/%s: не удалось прочитать файл gitdir (%s)"
 
-#: builtin/worktree.c:49
+#: builtin/worktree.c:58
 #, c-format
 msgid "Removing worktrees/%s: invalid gitdir file"
 msgstr "Удаление рабочих каталогов/%s: недействительный файл gitdir"
 
-#: builtin/worktree.c:65
+#: builtin/worktree.c:74
 #, c-format
 msgid "Removing worktrees/%s: gitdir file points to non-existent location"
 msgstr "Удаление рабочих каталогов/%s: gitdir указывает на несуществующее расположение"
 
-#: builtin/worktree.c:100
+#: builtin/worktree.c:109
 #, c-format
 msgid "failed to remove: %s"
 msgstr "не удалось удалить: %s"
 
-#: builtin/worktree.c:186
+#: builtin/worktree.c:198
 #, c-format
 msgid "'%s' already exists"
 msgstr "«%s» уже существует"
 
-#: builtin/worktree.c:207
+#: builtin/worktree.c:232
 #, c-format
 msgid "could not create directory of '%s'"
 msgstr "не удалось создать каталог «%s»"
 
-#: builtin/worktree.c:241
-msgid "unable to resolve HEAD"
-msgstr "не удалось определить HEAD"
-
-#: builtin/worktree.c:249
+#: builtin/worktree.c:268
 #, c-format
-msgid "Enter %s (identifier %s)"
-msgstr "Ð\92Ñ\85од Ð² %s (идентификатор %s)"
+msgid "Preparing %s (identifier %s)"
+msgstr "Ð\9fодгоÑ\82овка %s (идентификатор %s)"
 
-#: builtin/worktree.c:281
+#: builtin/worktree.c:316
 msgid "checkout <branch> even if already checked out in other worktree"
 msgstr "перейти на <ветка> даже если она уже активна в другом рабочесм каталоге"
 
-#: builtin/worktree.c:283
+#: builtin/worktree.c:318
 msgid "create a new branch"
 msgstr "создать новую ветку"
 
-#: builtin/worktree.c:285
+#: builtin/worktree.c:320
 msgid "create or reset a branch"
 msgstr "создать или перейти на ветку"
 
-#: builtin/worktree.c:286
+#: builtin/worktree.c:321
 msgid "detach HEAD at named commit"
 msgstr "отсоединить HEAD на указанном коммите"
 
-#: builtin/worktree.c:292
-msgid "-b and -B are mutually exclusive"
-msgstr "-b и -B нельзя использовать одновременно"
+#: builtin/worktree.c:328
+msgid "-b, -B, and --detach are mutually exclusive"
+msgstr "-b, -B и --detach нельзя использовать одновременно"
 
 #: builtin/write-tree.c:13
 msgid "git write-tree [--missing-ok] [--prefix=<prefix>/]"
@@ -9908,7 +10473,7 @@ msgstr "вывести объект дерева для подкаталога 
 msgid "only useful for debugging"
 msgstr "используется только при отладке"
 
-#: credential-cache--daemon.c:267
+#: credential-cache--daemon.c:255
 msgid "print debugging messages to stderr"
 msgstr "вывод отладочных сообщений на stderr"
 
@@ -9919,465 +10484,284 @@ msgid ""
 "to read about a specific subcommand or concept."
 msgstr "«git help -а» и «git help -g» выводит список доступных подкоманд и\nнекоторые руководства по темам. Запустите «git help <команда>» или\n«git help <термин>», чтобы прочесть о конкретных подкоманде или теме."
 
-#: common-cmds.h:10
+#: common-cmds.h:9
 msgid "start a working area (see also: git help tutorial)"
 msgstr "создать рабочую область (смотрите также: git help tutorial)"
 
-#: common-cmds.h:11
+#: common-cmds.h:10
 msgid "work on the current change (see also: git help everyday)"
 msgstr "работа с текущими изменениями (смотрите также: git help everyday)"
 
-#: common-cmds.h:12
+#: common-cmds.h:11
 msgid "examine the history and state (see also: git help revisions)"
 msgstr "просмотр истории и текущего состояния (смотрите также: git help revisions)"
 
-#: common-cmds.h:13
+#: common-cmds.h:12
 msgid "grow, mark and tweak your common history"
 msgstr "выращивание, отметка и настройка вашей общей истории"
 
-#: common-cmds.h:14
+#: common-cmds.h:13
 msgid "collaborate (see also: git help workflows)"
 msgstr "совместная работа (смотрите также: git help workflows)"
 
-#: common-cmds.h:18
+#: common-cmds.h:17
 msgid "Add file contents to the index"
 msgstr "Добавление содержимого файла в индекс"
 
-#: common-cmds.h:19
-msgid "Find by binary search the change that introduced a bug"
-msgstr "Ð\94воичный поиск изменения, которое вносит ошибку"
+#: common-cmds.h:18
+msgid "Use binary search to find the commit that introduced a bug"
+msgstr "Ð\98Ñ\81полÑ\8cзоваÑ\82Ñ\8c Ð´воичный поиск изменения, которое вносит ошибку"
 
-#: common-cmds.h:20
+#: common-cmds.h:19
 msgid "List, create, or delete branches"
 msgstr "Вывод списка, создание или удаление веток"
 
-#: common-cmds.h:21
+#: common-cmds.h:20
 msgid "Switch branches or restore working tree files"
 msgstr "Переключение веток или восстановление файлов в рабочем каталоге"
 
-#: common-cmds.h:22
+#: common-cmds.h:21
 msgid "Clone a repository into a new directory"
 msgstr "Клонирование репозитория в новый каталог"
 
-#: common-cmds.h:23
+#: common-cmds.h:22
 msgid "Record changes to the repository"
 msgstr "Запись изменений в репозиторий"
 
-#: common-cmds.h:24
+#: common-cmds.h:23
 msgid "Show changes between commits, commit and working tree, etc"
 msgstr "Вывод разницы между коммитами, коммитом и рабочим каталогом и т.д."
 
-#: common-cmds.h:25
+#: common-cmds.h:24
 msgid "Download objects and refs from another repository"
 msgstr "Загрузка объектов и ссылок из другого репозитория"
 
-#: common-cmds.h:26
+#: common-cmds.h:25
 msgid "Print lines matching a pattern"
 msgstr "Вывод строк, соответствующих шаблону"
 
-#: common-cmds.h:27
+#: common-cmds.h:26
 msgid "Create an empty Git repository or reinitialize an existing one"
 msgstr "Создание пустого репозитория Git или реинициализация существующего"
 
-#: common-cmds.h:28
+#: common-cmds.h:27
 msgid "Show commit logs"
 msgstr "Вывод истории коммитов"
 
-#: common-cmds.h:29
+#: common-cmds.h:28
 msgid "Join two or more development histories together"
 msgstr "Объединение одной или нескольких историй разработки вместе"
 
-#: common-cmds.h:30
+#: common-cmds.h:29
 msgid "Move or rename a file, a directory, or a symlink"
 msgstr "Перемещение или переименование файла, каталога или символьной ссылки"
 
-#: common-cmds.h:31
+#: common-cmds.h:30
 msgid "Fetch from and integrate with another repository or a local branch"
 msgstr "Извлечение изменений и объединение с другим репозиторием или локальной веткой"
 
-#: common-cmds.h:32
+#: common-cmds.h:31
 msgid "Update remote refs along with associated objects"
 msgstr "Обновление внешних ссылок и связанных объектов"
 
-#: common-cmds.h:33
+#: common-cmds.h:32
 msgid "Forward-port local commits to the updated upstream head"
 msgstr "Перемещение локальных коммитов над обновленной вышестоящей веткой"
 
-#: common-cmds.h:34
+#: common-cmds.h:33
 msgid "Reset current HEAD to the specified state"
 msgstr "Сброс текущего состояния HEAD на указанное состояние"
 
-#: common-cmds.h:35
+#: common-cmds.h:34
 msgid "Remove files from the working tree and from the index"
 msgstr "Удаление файлов из рабочего каталога и индекса"
 
-#: common-cmds.h:36
+#: common-cmds.h:35
 msgid "Show various types of objects"
 msgstr "Вывод различных типов объектов"
 
-#: common-cmds.h:37
+#: common-cmds.h:36
 msgid "Show the working tree status"
 msgstr "Вывод состояния рабочего каталога"
 
-#: common-cmds.h:38
+#: common-cmds.h:37
 msgid "Create, list, delete or verify a tag object signed with GPG"
 msgstr "Создание, вывод списка, удаление или проверка метки, подписанной с помощью GPG"
 
-#: parse-options.h:142
+#: parse-options.h:145
 msgid "expiry-date"
 msgstr "дата-окончания"
 
-#: parse-options.h:157
+#: parse-options.h:160
 msgid "no-op (backward compatibility)"
 msgstr "ничего не делает (оставлено для обратной совместимости)"
 
-#: parse-options.h:231
+#: parse-options.h:236
 msgid "be more verbose"
 msgstr "быть многословнее"
 
-#: parse-options.h:233
+#: parse-options.h:238
 msgid "be more quiet"
 msgstr "тихий режим"
 
-#: parse-options.h:239
+#: parse-options.h:244
 msgid "use <n> digits to display SHA-1s"
 msgstr "использовать <n> цифр для вывода SHA-1"
 
-#: rerere.h:27
+#: rerere.h:28
 msgid "update the index with reused conflict resolution if possible"
 msgstr "обновить индекс с помощью переиспользования разрешения конфликта, если возможно"
 
-#: git-am.sh:53
-msgid "You need to set your committer info first"
-msgstr "Сначала нужно указать вашу информацию о коммитере"
-
-#: git-am.sh:100
-msgid ""
-"You seem to have moved HEAD since the last 'am' failure.\n"
-"Not rewinding to ORIG_HEAD"
-msgstr "Похоже, что вы переместили HEAD с момента последней ошибки выполнения «am».\nПеремотка на ORIG_HEAD не выполняется"
-
-#: git-am.sh:110
-#, sh-format
-msgid ""
-"When you have resolved this problem, run \"$cmdline --continue\".\n"
-"If you prefer to skip this patch, run \"$cmdline --skip\" instead.\n"
-"To restore the original branch and stop patching, run \"$cmdline --abort\"."
-msgstr "Когда вы разрешите этот конфликт, запустите «$cmdline --continue».\nЕсли вы хотите пропустить этот патч, то запустите «$cmdline --skip».\nЧтобы перейти на оригинальную ветку и остановить применение изменений, запустите «$cmdline --abort»."
-
-#: git-am.sh:126
-msgid "Cannot fall back to three-way merge."
-msgstr "Не удалось откатиться к трехходовому слиянию."
-
-#: git-am.sh:142
-msgid "Repository lacks necessary blobs to fall back on 3-way merge."
-msgstr "В репозитории отсутствуют двоичные объекты, необходимые для отката к трехходовому слиянию."
-
-#: git-am.sh:144
-msgid "Using index info to reconstruct a base tree..."
-msgstr "Использую индекс для реконструкции базового дерева…"
-
-#: git-am.sh:159
-msgid ""
-"Did you hand edit your patch?\n"
-"It does not apply to blobs recorded in its index."
-msgstr "Вы вручную изменяли патч?\nОн не накладывается без ошибок на двоичные объекты, записанные в его заголовке."
-
-#: git-am.sh:168
-msgid "Falling back to patching base and 3-way merge..."
-msgstr "Откат к применению изменений к базовому коммиту с помощью трехходового слияния…"
-
-#: git-am.sh:185
-msgid "Failed to merge in the changes."
-msgstr "Не удалось слить изменения."
-
-#: git-am.sh:280
-msgid "Only one StGIT patch series can be applied at once"
-msgstr "Только серия патчей StGIT может быть применена за раз"
-
-#: git-am.sh:367
-#, sh-format
-msgid "Patch format $patch_format is not supported."
-msgstr "Неподдерживаемый формат патча $patch_format."
-
-#: git-am.sh:369
-msgid "Patch format detection failed."
-msgstr "Сбой определения формата патча."
-
-#: git-am.sh:407
-msgid ""
-"The -b/--binary option has been a no-op for long time, and\n"
-"it will be removed. Please do not use it anymore."
-msgstr "Опция -b/--binary уже долгое время ничего не делает и будет удалена с следующих версиях Git. Пожалуйста, не используйте ее."
-
-#: git-am.sh:507
-#, sh-format
-msgid "previous rebase directory $dotest still exists but mbox given."
-msgstr "предыдущий каталог перемещения $dotest еще существует, но передан mbox."
-
-#: git-am.sh:512
-msgid "Please make up your mind. --skip or --abort?"
-msgstr "Пожалуйста, определитесь: --skip или --abort?"
-
-#: git-am.sh:560
-#, sh-format
-msgid ""
-"Stray $dotest directory found.\n"
-"Use \"git am --abort\" to remove it."
-msgstr "Найден забытый каталог $dotest.\nИспользуйте «git am --abort», чтобы удалить его."
-
-#: git-am.sh:568
-msgid "Resolve operation not in progress, we are not resuming."
-msgstr "Операция разрешения конфликтов не в процессе выполнения, не продолжаем."
-
-#: git-am.sh:635
-#, sh-format
-msgid "Dirty index: cannot apply patches (dirty: $files)"
-msgstr "Индекс не пустой: нельзя применять патчи (в индексе: $files)"
-
-#: git-am.sh:747
-#, sh-format
-msgid ""
-"Patch is empty.  Was it split wrong?\n"
-"If you would prefer to skip this patch, instead run \"$cmdline --skip\".\n"
-"To restore the original branch and stop patching run \"$cmdline --abort\"."
-msgstr "Патч пуст. Возможно, он был неправильно разделен?\nЕсли вы хотите пропустить этот патч, то вместо этого запустите «$cmdline --skip».\nЧтобы перейти на оригинальную ветку и остановить применение изменений, запустите «$cmdline --abort»."
-
-#: git-am.sh:774
-msgid "Patch does not have a valid e-mail address."
-msgstr "Патч не содержит действительный адрес электронной почты."
-
-#: git-am.sh:821
-msgid "cannot be interactive without stdin connected to a terminal."
-msgstr "не удалось использовать интерактивное поведение, без stdin подключенного к терминалу."
-
-#: git-am.sh:825
-msgid "Commit Body is:"
-msgstr "Тело коммита:"
-
-#. TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
-#. in your translation. The program will only accept English
-#. input at this point.
-#: git-am.sh:832
-msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
-msgstr "Применить? [y] - да/[n] - нет/[e] - редактировать/[v] - просмотреть патч/[a] - применить все "
-
-#: git-am.sh:868
-#, sh-format
-msgid "Applying: $FIRSTLINE"
-msgstr "Применение: $FIRSTLINE"
-
-#: git-am.sh:889
-msgid ""
-"No changes - did you forget to use 'git add'?\n"
-"If there is nothing left to stage, chances are that something else\n"
-"already introduced the same changes; you might want to skip this patch."
-msgstr "Нет изменений — возможно, вы забыли вызвать «git add»?\nЕсли ничего не осталось для индексации, то, скорее всего, что-то другое уже сделало те же изменения; возможно, вам следует пропустить этот патч."
-
-#: git-am.sh:897
-msgid ""
-"You still have unmerged paths in your index\n"
-"did you forget to use 'git add'?"
-msgstr "У вас все еще имеются не слитые пути в индексе.\nВозможно, вы забыли вызвать «git add»?"
-
-#: git-am.sh:913
-msgid "No changes -- Patch already applied."
-msgstr "Нет изменений — Патч уже применен."
-
-#: git-am.sh:923
-#, sh-format
-msgid "Patch failed at $msgnum $FIRSTLINE"
-msgstr "Ошибка применения изменений на строке $msgnum $FIRSTLINE"
-
-#: git-am.sh:926
-#, sh-format
-msgid ""
-"The copy of the patch that failed is found in:\n"
-"   $dotest/patch"
-msgstr "Копию изменений, которые не удалось применить, вы можете найти в:\n   $dotest/patch"
-
-#: git-am.sh:945
-msgid "applying to an empty history"
-msgstr "применение к пустой истории"
-
-#: git-bisect.sh:48
+#: git-bisect.sh:50
 msgid "You need to start by \"git bisect start\""
 msgstr "Вам нужно начать с помощью «git bisect start»"
 
 #. TRANSLATORS: Make sure to include [Y] and [n] in your
 #. translation. The program will only accept English input
 #. at this point.
-#: git-bisect.sh:54
+#: git-bisect.sh:56
 msgid "Do you want me to do it for you [Y/n]? "
 msgstr "Вы уверены, что хотите, чтобы я сделал это [Y - да/n - нет]? "
 
-#: git-bisect.sh:95
+#: git-bisect.sh:99
 #, sh-format
 msgid "unrecognised option: '$arg'"
 msgstr "неопознанная опция: «$arg»"
 
-#: git-bisect.sh:99
+#: git-bisect.sh:103
 #, sh-format
 msgid "'$arg' does not appear to be a valid revision"
 msgstr "«$arg» не похоже на действительную редакцию"
 
-#: git-bisect.sh:117
+#: git-bisect.sh:132
 msgid "Bad HEAD - I need a HEAD"
 msgstr "Плохой указатель HEAD — Необходим указатель HEAD"
 
-#: git-bisect.sh:130
+#: git-bisect.sh:145
 #, sh-format
 msgid ""
 "Checking out '$start_head' failed. Try 'git bisect reset <valid-branch>'."
 msgstr "Сбой перехода на «$start_head». Попробуйте выполнить «git bisect reset <существующая-ветка>»."
 
-#: git-bisect.sh:140
+#: git-bisect.sh:155
 msgid "won't bisect on cg-seek'ed tree"
 msgstr "нельзя выполнить двоичный поиск на дереве после cg-seek"
 
-#: git-bisect.sh:144
+#: git-bisect.sh:159
 msgid "Bad HEAD - strange symbolic ref"
 msgstr "Плохой указатель HEAD — странная символьная ссылка"
 
-#: git-bisect.sh:189
+#: git-bisect.sh:211
 #, sh-format
 msgid "Bad bisect_write argument: $state"
 msgstr "Плохой аргумент bisect_write: $state"
 
-#: git-bisect.sh:218
+#: git-bisect.sh:240
 #, sh-format
 msgid "Bad rev input: $arg"
 msgstr "Плохой ввод номера редакции: $arg"
 
-#: git-bisect.sh:232
+#: git-bisect.sh:255
 msgid "Please call 'bisect_state' with at least one argument."
 msgstr "Пожалуйста, вызывайте «bisect_state» как минимум с одним аргументом."
 
-#: git-bisect.sh:244
+#: git-bisect.sh:267
 #, sh-format
 msgid "Bad rev input: $rev"
 msgstr "Плохой ввод номера редакции: $rev"
 
-#: git-bisect.sh:253
-msgid "'git bisect bad' can take only one argument."
-msgstr "«git bisect bad» может принимать только один аргумент."
-
 #: git-bisect.sh:276
-msgid "Warning: bisecting only with a bad commit."
-msgstr "Предупреждение: попытка двоичного поиска с указанием только плохого коммита."
+#, sh-format
+msgid "'git bisect $TERM_BAD' can take only one argument."
+msgstr "«git bisect $TERM_BAD» может принимать только один аргумент."
+
+#: git-bisect.sh:299
+#, sh-format
+msgid "Warning: bisecting only with a $TERM_BAD commit."
+msgstr "Предупреждение: попытка двоичного поиска с указанием только $TERM_BAD коммита."
 
 #. TRANSLATORS: Make sure to include [Y] and [n] in your
 #. translation. The program will only accept English input
 #. at this point.
-#: git-bisect.sh:282
+#: git-bisect.sh:305
 msgid "Are you sure [Y/n]? "
 msgstr "Вы уверены [Y - да/n - нет]? "
 
-#: git-bisect.sh:292
+#: git-bisect.sh:317
+#, sh-format
 msgid ""
-"You need to give me at least one good and one bad revision.\n"
-"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
-msgstr "Вам нужно передать мне как минимум одну хорошую и одну плохую редакцию.\n(Для этого вы можете использовать команды «git bisect bad» и «git bisect good».)"
+"You need to give me at least one $bad_syn and one $good_syn revision.\n"
+"(You can use \"git bisect $bad_syn\" and \"git bisect $good_syn\" for that.)"
+msgstr "Вам нужно передать мне как минимум одну $bad_syn и одну $good_syn редакцию.\n(Для этого вы можете использовать команды «git bisect $bad_syn» и «git bisect $good_syn».)"
 
-#: git-bisect.sh:295
+#: git-bisect.sh:320
+#, sh-format
 msgid ""
 "You need to start by \"git bisect start\".\n"
-"You then need to give me at least one good and one bad revision.\n"
-"(You can use \"git bisect bad\" and \"git bisect good\" for that.)"
-msgstr "Для начала нужно запустить «git bisect start».\nПосле этого, вам нужно передать мне как минимум одну хорошую и одну плохую редакцию.\n(Для этого вы можете использовать команды «git bisect bad» и «git bisect good».)"
+"You then need to give me at least one $good_syn and one $bad_syn revision.\n"
+"(You can use \"git bisect $bad_syn\" and \"git bisect $good_syn\" for that.)"
+msgstr "Для начала нужно запустить «git bisect start».\nПосле этого, вам нужно передать мне как минимум одну $good_syn и одну $bad_syn редакцию.\n(Для этого вы можете использовать команды «git bisect $good_syn» и «git bisect $good_syn».)"
 
-#: git-bisect.sh:366 git-bisect.sh:493
+#: git-bisect.sh:391 git-bisect.sh:521
 msgid "We are not bisecting."
 msgstr "Вы сейчас не в процессе бинарного поиска."
 
-#: git-bisect.sh:373
+#: git-bisect.sh:398
 #, sh-format
 msgid "'$invalid' is not a valid commit"
 msgstr "«$invalid» не является действительным коммитом"
 
-#: git-bisect.sh:382
+#: git-bisect.sh:407
 #, sh-format
 msgid ""
 "Could not check out original HEAD '$branch'.\n"
 "Try 'git bisect reset <commit>'."
 msgstr "Не удалось перейти на оригинальную ветку HEAD «$branch».\nПопробуйте запустить «git bisect reset <коммит>»."
 
-#: git-bisect.sh:409
+#: git-bisect.sh:435
 msgid "No logfile given"
 msgstr "Не передан файл журнала"
 
-#: git-bisect.sh:410
+#: git-bisect.sh:436
 #, sh-format
 msgid "cannot read $file for replaying"
 msgstr "не удалось прочитать $file для повтора изменений"
 
-#: git-bisect.sh:427
+#: git-bisect.sh:455
 msgid "?? what are you talking about?"
 msgstr "?? вы о чем?"
 
-#: git-bisect.sh:439
+#: git-bisect.sh:467
 #, sh-format
 msgid "running $command"
 msgstr "запускаю $command"
 
-#: git-bisect.sh:446
+#: git-bisect.sh:474
 #, sh-format
 msgid ""
 "bisect run failed:\n"
 "exit code $res from '$command' is < 0 or >= 128"
 msgstr "не удалось выполнить двоичный поиск:\nкод завершения $res от «$command» оказался < 0 или >= 128"
 
-#: git-bisect.sh:472
+#: git-bisect.sh:500
 msgid "bisect run cannot continue any more"
 msgstr "bisect run больше не может продолжать"
 
-#: git-bisect.sh:478
+#: git-bisect.sh:506
 #, sh-format
 msgid ""
 "bisect run failed:\n"
 "'bisect_state $state' exited with error code $res"
 msgstr "не удалось выполнить двоичный поиск:\n«bisect_state $state» завершился с кодом ошибки $res"
 
-#: git-bisect.sh:485
+#: git-bisect.sh:513
 msgid "bisect run success"
 msgstr "bisect run выполнен успешно"
 
-#: git-pull.sh:61
-msgid ""
-"Pull is not possible because you have unmerged files.\n"
-"Please, fix them up in the work tree, and then use 'git add/rm <file>'\n"
-"as appropriate to mark resolution and make a commit."
-msgstr "Невозможно выполнить получение, так как у вас имеются не слитые файлы.\nИсправьте их в рабочем каталоге, затем запустите «git add/rm <файл>»,\nчтобы пометить исправление и сделайте коммит."
-
-#: git-pull.sh:65
-msgid "Pull is not possible because you have unmerged files."
-msgstr "Невозможно выполнить получение, так как у вас имеются не слитые файлы."
-
-#: git-pull.sh:71
-msgid ""
-"You have not concluded your merge (MERGE_HEAD exists).\n"
-"Please, commit your changes before you can merge."
-msgstr "Вы не завершили слияние (присутствует файл MERGE_HEAD).\nВыполните коммит ваших изменений, перед слиянием."
-
-#: git-pull.sh:285
-msgid "updating an unborn branch with changes added to the index"
-msgstr "обновление еще не начавшейся ветки с изменениями, добавленными в индекс"
-
-#: git-pull.sh:311
+#: git-bisect.sh:548
 #, sh-format
-msgid ""
-"Warning: fetch updated the current branch head.\n"
-"Warning: fast-forwarding your working tree from\n"
-"Warning: commit $orig_head."
-msgstr "Предупреждение: извлечение обновило голову вашей текущей ветки.\nПредупреждение: перемотка вашего рабочего каталога\nПредупреждение: с коммита $orig_head."
-
-#: git-pull.sh:336
-msgid "Cannot merge multiple branches into empty head"
-msgstr "Нельзя слить несколько веток в пустую указатель на коммит"
-
-#: git-pull.sh:340
-msgid "Cannot rebase onto multiple branches"
-msgstr "Невозможно переместить над несколькими ветками"
+msgid "Invalid command: you're currently in a $TERM_BAD/$TERM_GOOD bisect."
+msgstr "Недопустимая команда: вы сейчас находитесь на $TERM_BAD/$TERM_GOOD двоичном поиске."
 
 #: git-rebase.sh:57
 msgid ""
@@ -10540,7 +10924,7 @@ msgstr "Не удалось удалить временный индекс (не
 msgid "Cannot record working tree state"
 msgstr "Не удалось записать состояние рабочего каталога"
 
-#: git-stash.sh:191
+#: git-stash.sh:189
 #, sh-format
 msgid "Cannot update $ref_stash with $w_commit"
 msgstr "Не удалось обновить $ref_stash с помощью $w_commit"
@@ -10554,97 +10938,97 @@ msgstr "Не удалось обновить $ref_stash с помощью $w_com
 #. $ git stash save --blah-blah 2>&1 | head -n 2
 #. error: unknown option for 'stash save': --blah-blah
 #. To provide a message, use git stash save -- '--blah-blah'
-#: git-stash.sh:241
+#: git-stash.sh:239
 #, sh-format
 msgid ""
 "error: unknown option for 'stash save': $option\n"
 "       To provide a message, use git stash save -- '$option'"
 msgstr "ошибка: неизвестная опция для «stash save»: $option\n        Для предоставления сообщения, используйте git stash save -- «$option»"
 
-#: git-stash.sh:262
+#: git-stash.sh:260
 msgid "No local changes to save"
 msgstr "Нет локальных изменений для сохранения"
 
-#: git-stash.sh:266
+#: git-stash.sh:264
 msgid "Cannot initialize stash"
 msgstr "Не удалось инициализировать спрятанные изменения"
 
-#: git-stash.sh:270
+#: git-stash.sh:268
 msgid "Cannot save the current status"
 msgstr "Не удалось сохранить текущий статус"
 
-#: git-stash.sh:288
+#: git-stash.sh:286
 msgid "Cannot remove worktree changes"
 msgstr "Не удалось удалить изменения рабочего каталога"
 
-#: git-stash.sh:389
+#: git-stash.sh:387
 #, sh-format
 msgid "unknown option: $opt"
 msgstr "неизвестная опция: $opt"
 
-#: git-stash.sh:399
+#: git-stash.sh:397
 msgid "No stash found."
 msgstr "Не найдены спрятанные изменения."
 
-#: git-stash.sh:406
+#: git-stash.sh:404
 #, sh-format
 msgid "Too many revisions specified: $REV"
 msgstr "Передано слишком много редакций: $REV"
 
-#: git-stash.sh:412
+#: git-stash.sh:410
 #, sh-format
 msgid "$reference is not a valid reference"
 msgstr "$reference не является действительной ссылкой"
 
-#: git-stash.sh:440
+#: git-stash.sh:438
 #, sh-format
 msgid "'$args' is not a stash-like commit"
 msgstr "«$args» не похоже на коммит со спрятанными изменениями"
 
-#: git-stash.sh:451
+#: git-stash.sh:449
 #, sh-format
 msgid "'$args' is not a stash reference"
 msgstr "«$args» не является ссылкой на спрятанные изменения"
 
-#: git-stash.sh:459
+#: git-stash.sh:457
 msgid "unable to refresh index"
 msgstr "не удалось обновить индекс"
 
-#: git-stash.sh:463
+#: git-stash.sh:461
 msgid "Cannot apply a stash in the middle of a merge"
 msgstr "Нельзя применить спрятанные изменения во время выполнения слияния"
 
-#: git-stash.sh:471
+#: git-stash.sh:469
 msgid "Conflicts in index. Try without --index."
 msgstr "Конфликты в индексе. Попробуйте без --index."
 
-#: git-stash.sh:473
+#: git-stash.sh:471
 msgid "Could not save index tree"
 msgstr "Не удалось сохранить дерево индекса"
 
-#: git-stash.sh:507
+#: git-stash.sh:505
 msgid "Cannot unstage modified files"
 msgstr "Невозможно убрать из индекса измененные файлы"
 
-#: git-stash.sh:522
+#: git-stash.sh:520
 msgid "Index was not unstashed."
 msgstr "Индекс не был достат из спрятанных изменений."
 
-#: git-stash.sh:545
+#: git-stash.sh:543
 #, sh-format
 msgid "Dropped ${REV} ($s)"
 msgstr "Отброшено ${REV} ($s)"
 
-#: git-stash.sh:546
+#: git-stash.sh:544
 #, sh-format
 msgid "${REV}: Could not drop stash entry"
 msgstr "${REV}: Не удалось отбросить запись из спрятанных изменений"
 
-#: git-stash.sh:554
+#: git-stash.sh:552
 msgid "No branch name specified"
 msgstr "Не указано имя ветки"
 
-#: git-stash.sh:626
+#: git-stash.sh:624
 msgid "(To restore them type \"git stash apply\")"
 msgstr "(Чтобы восстановить их, наберите «git stash apply»)"
 
@@ -10872,7 +11256,7 @@ msgstr "Подмодуль по пути «$displaypath»: слито с «$sha1
 #: git-submodule.sh:907
 #, sh-format
 msgid ""
-"Execution of '$command $sha1' failed in submodule  path '$prefix$sm_path'"
+"Execution of '$command $sha1' failed in submodule path '$prefix$sm_path'"
 msgstr "Сбой выполнения «$command $sha1» для подмодуля по пути «$prefix$sm_path»"
 
 #: git-submodule.sh:908
index 2e31bec60f5c98dc7dac69227759cbf2ba5f9974..353bd37416e65f6dba733ac9d14999f1e293b4b9 100644 (file)
@@ -25,7 +25,7 @@ struct throughput {
        unsigned int last_bytes[TP_IDX_MAX];
        unsigned int last_misecs[TP_IDX_MAX];
        unsigned int idx;
-       char display[32];
+       struct strbuf display;
 };
 
 struct progress {
@@ -98,7 +98,7 @@ static int display(struct progress *progress, unsigned n, const char *done)
        }
 
        progress->last_value = n;
-       tp = (progress->throughput) ? progress->throughput->display : "";
+       tp = (progress->throughput) ? progress->throughput->display.buf : "";
        eol = done ? done : "   \r";
        if (progress->total) {
                unsigned percent = n * 100 / progress->total;
@@ -129,6 +129,7 @@ static int display(struct progress *progress, unsigned n, const char *done)
 static void throughput_string(struct strbuf *buf, off_t total,
                              unsigned int rate)
 {
+       strbuf_reset(buf);
        strbuf_addstr(buf, ", ");
        strbuf_humanise_bytes(buf, total);
        strbuf_addstr(buf, " | ");
@@ -141,7 +142,6 @@ void display_throughput(struct progress *progress, off_t total)
        struct throughput *tp;
        uint64_t now_ns;
        unsigned int misecs, count, rate;
-       struct strbuf buf = STRBUF_INIT;
 
        if (!progress)
                return;
@@ -154,6 +154,7 @@ void display_throughput(struct progress *progress, off_t total)
                if (tp) {
                        tp->prev_total = tp->curr_total = total;
                        tp->prev_ns = now_ns;
+                       strbuf_init(&tp->display, 0);
                }
                return;
        }
@@ -193,9 +194,7 @@ void display_throughput(struct progress *progress, off_t total)
        tp->last_misecs[tp->idx] = misecs;
        tp->idx = (tp->idx + 1) % TP_IDX_MAX;
 
-       throughput_string(&buf, total, rate);
-       strncpy(tp->display, buf.buf, sizeof(tp->display));
-       strbuf_release(&buf);
+       throughput_string(&tp->display, total, rate);
        if (progress->last_value != -1 && progress_update)
                display(progress, progress->last_value, NULL);
 }
@@ -250,20 +249,19 @@ void stop_progress_msg(struct progress **p_progress, const char *msg)
 
                bufp = (len < sizeof(buf)) ? buf : xmalloc(len + 1);
                if (tp) {
-                       struct strbuf strbuf = STRBUF_INIT;
                        unsigned int rate = !tp->avg_misecs ? 0 :
                                        tp->avg_bytes / tp->avg_misecs;
-                       throughput_string(&strbuf, tp->curr_total, rate);
-                       strncpy(tp->display, strbuf.buf, sizeof(tp->display));
-                       strbuf_release(&strbuf);
+                       throughput_string(&tp->display, tp->curr_total, rate);
                }
                progress_update = 1;
-               sprintf(bufp, ", %s.\n", msg);
+               xsnprintf(bufp, len + 1, ", %s.\n", msg);
                display(progress, progress->last_value, bufp);
                if (buf != bufp)
                        free(bufp);
        }
        clear_progress_signal();
+       if (progress->throughput)
+               strbuf_release(&progress->throughput->display);
        free(progress->throughput);
        free(progress);
 }
diff --git a/quote.c b/quote.c
index 7920e18e44834f1f86c218c172ae10227858ed66..fe884d24521f91a0f1f30b3cf5f08734bab7bd31 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -4,9 +4,15 @@
 
 int quote_path_fully = 1;
 
+static inline int need_bs_quote(char c)
+{
+       return (c == '\'' || c == '!');
+}
+
 /* Help to copy the thing properly quoted for the shell safety.
  * any single quote is replaced with '\'', any exclamation point
  * is replaced with '\!', and the whole thing is enclosed in a
+ * single quote pair.
  *
  * E.g.
  *  original     sq_quote     result
@@ -15,11 +21,6 @@ int quote_path_fully = 1;
  *  a'b      ==> a'\''b    ==> 'a'\''b'
  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
  */
-static inline int need_bs_quote(char c)
-{
-       return (c == '\'' || c == '!');
-}
-
 void sq_quote_buf(struct strbuf *dst, const char *src)
 {
        char *to_free = NULL;
index 9cff25b490f7ec7c5535647afe017326a6984520..43616d49c7f88166d2fa2f009ca5b926d751ab0b 100644 (file)
@@ -25,9 +25,15 @@ static void update_progress(struct connectivity_progress *cp)
 static int add_one_ref(const char *path, const struct object_id *oid,
                       int flag, void *cb_data)
 {
-       struct object *object = parse_object_or_die(oid->hash, path);
        struct rev_info *revs = (struct rev_info *)cb_data;
+       struct object *object;
 
+       if ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {
+               warning("symbolic ref is dangling: %s", path);
+               return 0;
+       }
+
+       object = parse_object_or_die(oid->hash, path);
        add_pending_object(revs, object, "");
 
        return 0;
index f38dee4f605df344315e0202487aeebc623f6582..1194f10ed60f2bb476e1d95d0cc11b4ad4265c7f 100644 (file)
@@ -9,6 +9,10 @@
 #include "tag.h"
 #include "quote.h"
 #include "ref-filter.h"
+#include "revision.h"
+#include "utf8.h"
+#include "git-compat-util.h"
+#include "version.h"
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 
@@ -43,15 +47,48 @@ static struct {
        { "subject" },
        { "body" },
        { "contents" },
-       { "contents:subject" },
-       { "contents:body" },
-       { "contents:signature" },
        { "upstream" },
        { "push" },
        { "symref" },
        { "flag" },
        { "HEAD" },
        { "color" },
+       { "align" },
+       { "end" },
+};
+
+#define REF_FORMATTING_STATE_INIT  { 0, NULL }
+
+struct align {
+       align_type position;
+       unsigned int width;
+};
+
+struct contents {
+       unsigned int lines;
+       struct object_id oid;
+};
+
+struct ref_formatting_stack {
+       struct ref_formatting_stack *prev;
+       struct strbuf output;
+       void (*at_end)(struct ref_formatting_stack *stack);
+       void *at_end_data;
+};
+
+struct ref_formatting_state {
+       int quote_style;
+       struct ref_formatting_stack *stack;
+};
+
+struct atom_value {
+       const char *s;
+       union {
+               struct align align;
+               struct contents contents;
+       } u;
+       void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
+       unsigned long ul; /* used for sorting when not FIELD_STR */
 };
 
 /*
@@ -123,6 +160,120 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
        return at;
 }
 
+static void quote_formatting(struct strbuf *s, const char *str, int quote_style)
+{
+       switch (quote_style) {
+       case QUOTE_NONE:
+               strbuf_addstr(s, str);
+               break;
+       case QUOTE_SHELL:
+               sq_quote_buf(s, str);
+               break;
+       case QUOTE_PERL:
+               perl_quote_buf(s, str);
+               break;
+       case QUOTE_PYTHON:
+               python_quote_buf(s, str);
+               break;
+       case QUOTE_TCL:
+               tcl_quote_buf(s, str);
+               break;
+       }
+}
+
+static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
+{
+       /*
+        * Quote formatting is only done when the stack has a single
+        * element. Otherwise quote formatting is done on the
+        * element's entire output strbuf when the %(end) atom is
+        * encountered.
+        */
+       if (!state->stack->prev)
+               quote_formatting(&state->stack->output, v->s, state->quote_style);
+       else
+               strbuf_addstr(&state->stack->output, v->s);
+}
+
+static void push_stack_element(struct ref_formatting_stack **stack)
+{
+       struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack));
+
+       strbuf_init(&s->output, 0);
+       s->prev = *stack;
+       *stack = s;
+}
+
+static void pop_stack_element(struct ref_formatting_stack **stack)
+{
+       struct ref_formatting_stack *current = *stack;
+       struct ref_formatting_stack *prev = current->prev;
+
+       if (prev)
+               strbuf_addbuf(&prev->output, &current->output);
+       strbuf_release(&current->output);
+       free(current);
+       *stack = prev;
+}
+
+static void end_align_handler(struct ref_formatting_stack *stack)
+{
+       struct align *align = (struct align *)stack->at_end_data;
+       struct strbuf s = STRBUF_INIT;
+
+       strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
+       strbuf_swap(&stack->output, &s);
+       strbuf_release(&s);
+}
+
+static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *new;
+
+       push_stack_element(&state->stack);
+       new = state->stack;
+       new->at_end = end_align_handler;
+       new->at_end_data = &atomv->u.align;
+}
+
+static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+       struct ref_formatting_stack *current = state->stack;
+       struct strbuf s = STRBUF_INIT;
+
+       if (!current->at_end)
+               die(_("format: %%(end) atom used without corresponding atom"));
+       current->at_end(current);
+
+       /*
+        * Perform quote formatting when the stack element is that of
+        * a supporting atom. If nested then perform quote formatting
+        * only on the topmost supporting atom.
+        */
+       if (!state->stack->prev->prev) {
+               quote_formatting(&s, current->output.buf, state->quote_style);
+               strbuf_swap(&current->output, &s);
+       }
+       strbuf_release(&s);
+       pop_stack_element(&state->stack);
+}
+
+static int match_atom_name(const char *name, const char *atom_name, const char **val)
+{
+       const char *body;
+
+       if (!skip_prefix(name, atom_name, &body))
+               return 0; /* doesn't even begin with "atom_name" */
+       if (!body[0]) {
+               *val = NULL; /* %(atom_name) and no customization */
+               return 1;
+       }
+       if (body[0] != ':')
+               return 0; /* "atom_namefoo" is not "atom_name" or "atom_name:..." */
+       *val = body + 1; /* "atom_name:val" */
+       return 1;
+}
+
 /*
  * In a format string, find the next occurrence of %(atom).
  */
@@ -192,9 +343,7 @@ static int grab_objectname(const char *name, const unsigned char *sha1,
                            struct atom_value *v)
 {
        if (!strcmp(name, "objectname")) {
-               char *s = xmalloc(41);
-               strcpy(s, sha1_to_hex(sha1));
-               v->s = s;
+               v->s = xstrdup(sha1_to_hex(sha1));
                return 1;
        }
        if (!strcmp(name, "objectname:short")) {
@@ -219,10 +368,8 @@ static void grab_common_values(struct atom_value *val, int deref, struct object
                if (!strcmp(name, "objecttype"))
                        v->s = typename(obj->type);
                else if (!strcmp(name, "objectsize")) {
-                       char *s = xmalloc(40);
-                       sprintf(s, "%lu", sz);
                        v->ul = sz;
-                       v->s = s;
+                       v->s = xstrfmt("%lu", sz);
                }
                else if (deref)
                        grab_objectname(name, obj->sha1, v);
@@ -246,11 +393,8 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob
                        v->s = tag->tag;
                else if (!strcmp(name, "type") && tag->tagged)
                        v->s = typename(tag->tagged->type);
-               else if (!strcmp(name, "object") && tag->tagged) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(tag->tagged->sha1));
-                       v->s = s;
-               }
+               else if (!strcmp(name, "object") && tag->tagged)
+                       v->s = xstrdup(sha1_to_hex(tag->tagged->sha1));
        }
 }
 
@@ -268,32 +412,22 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
                if (deref)
                        name++;
                if (!strcmp(name, "tree")) {
-                       char *s = xmalloc(41);
-                       strcpy(s, sha1_to_hex(commit->tree->object.sha1));
-                       v->s = s;
+                       v->s = xstrdup(sha1_to_hex(commit->tree->object.sha1));
                }
-               if (!strcmp(name, "numparent")) {
-                       char *s = xmalloc(40);
+               else if (!strcmp(name, "numparent")) {
                        v->ul = commit_list_count(commit->parents);
-                       sprintf(s, "%lu", v->ul);
-                       v->s = s;
+                       v->s = xstrfmt("%lu", v->ul);
                }
                else if (!strcmp(name, "parent")) {
-                       int num = commit_list_count(commit->parents);
-                       int i;
                        struct commit_list *parents;
-                       char *s = xmalloc(41 * num + 1);
-                       v->s = s;
-                       for (i = 0, parents = commit->parents;
-                            parents;
-                            parents = parents->next, i = i + 41) {
+                       struct strbuf s = STRBUF_INIT;
+                       for (parents = commit->parents; parents; parents = parents->next) {
                                struct commit *parent = parents->item;
-                               strcpy(s+i, sha1_to_hex(parent->object.sha1));
-                               if (parents->next)
-                                       s[i+40] = ' ';
+                               if (parents != commit->parents)
+                                       strbuf_addch(&s, ' ');
+                               strbuf_addstr(&s, sha1_to_hex(parent->object.sha1));
                        }
-                       if (!i)
-                               *s = '\0';
+                       v->s = strbuf_detach(&s, NULL);
                }
        }
 }
@@ -497,6 +631,30 @@ static void find_subpos(const char *buf, unsigned long sz,
        *nonsiglen = *sig - buf;
 }
 
+/*
+ * If 'lines' is greater than 0, append that many lines from the given
+ * 'buf' of length 'size' to the given strbuf.
+ */
+static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines)
+{
+       int i;
+       const char *sp, *eol;
+       size_t len;
+
+       sp = buf;
+
+       for (i = 0; i < lines && sp < buf + size; i++) {
+               if (i)
+                       strbuf_addstr(out, "\n    ");
+               eol = memchr(sp, '\n', size - (sp - buf));
+               len = eol ? eol - sp : size - (sp - buf);
+               strbuf_add(out, sp, len);
+               if (!eol)
+                       break;
+               sp = eol + 1;
+       }
+}
+
 /* See grab_values */
 static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 {
@@ -507,6 +665,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
        for (i = 0; i < used_atom_cnt; i++) {
                const char *name = used_atom[i];
                struct atom_value *v = &val[i];
+               const char *valp = NULL;
                if (!!deref != (*name == '*'))
                        continue;
                if (deref)
@@ -516,7 +675,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                    strcmp(name, "contents") &&
                    strcmp(name, "contents:subject") &&
                    strcmp(name, "contents:body") &&
-                   strcmp(name, "contents:signature"))
+                   strcmp(name, "contents:signature") &&
+                   !starts_with(name, "contents:lines="))
                        continue;
                if (!subpos)
                        find_subpos(buf, sz,
@@ -536,6 +696,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                        v->s = xmemdupz(sigpos, siglen);
                else if (!strcmp(name, "contents"))
                        v->s = xstrdup(subpos);
+               else if (skip_prefix(name, "contents:lines=", &valp)) {
+                       struct strbuf s = STRBUF_INIT;
+                       const char *contents_end = bodylen + bodypos - siglen;
+
+                       if (strtoul_ui(valp, 10, &v->u.contents.lines))
+                               die(_("positive value expected contents:lines=%s"), valp);
+                       /*  Size is the length of the message after removing the signature */
+                       append_lines(&s, subpos, contents_end - subpos, v->u.contents.lines);
+                       v->s = strbuf_detach(&s, NULL);
+               }
        }
 }
 
@@ -621,8 +791,11 @@ static void populate_value(struct ref_array_item *ref)
                int deref = 0;
                const char *refname;
                const char *formatp;
+               const char *valp;
                struct branch *branch = NULL;
 
+               v->handler = append_atom;
+
                if (*name == '*') {
                        deref = 1;
                        name++;
@@ -653,10 +826,12 @@ static void populate_value(struct ref_array_item *ref)
                        refname = branch_get_push(branch, NULL);
                        if (!refname)
                                continue;
-               } else if (starts_with(name, "color:")) {
+               } else if (match_atom_name(name, "color", &valp)) {
                        char color[COLOR_MAXLEN] = "";
 
-                       if (color_parse(name + 6, color) < 0)
+                       if (!valp)
+                               die(_("expected format: %%(color:<color>)"));
+                       if (color_parse(valp, color) < 0)
                                die(_("unable to parse format"));
                        v->s = xstrdup(color);
                        continue;
@@ -686,6 +861,48 @@ static void populate_value(struct ref_array_item *ref)
                        else
                                v->s = " ";
                        continue;
+               } else if (match_atom_name(name, "align", &valp)) {
+                       struct align *align = &v->u.align;
+                       struct strbuf **s, **to_free;
+                       int width = -1;
+
+                       if (!valp)
+                               die(_("expected format: %%(align:<width>,<position>)"));
+
+                       /*
+                        * TODO: Implement a function similar to strbuf_split_str()
+                        * which would omit the separator from the end of each value.
+                        */
+                       s = to_free = strbuf_split_str(valp, ',', 0);
+
+                       align->position = ALIGN_LEFT;
+
+                       while (*s) {
+                               /*  Strip trailing comma */
+                               if (s[1])
+                                       strbuf_setlen(s[0], s[0]->len - 1);
+                               if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))
+                                       ;
+                               else if (!strcmp(s[0]->buf, "left"))
+                                       align->position = ALIGN_LEFT;
+                               else if (!strcmp(s[0]->buf, "right"))
+                                       align->position = ALIGN_RIGHT;
+                               else if (!strcmp(s[0]->buf, "middle"))
+                                       align->position = ALIGN_MIDDLE;
+                               else
+                                       die(_("improper format entered align:%s"), s[0]->buf);
+                               s++;
+                       }
+
+                       if (width < 0)
+                               die(_("positive width expected with the %%(align) atom"));
+                       align->width = width;
+                       strbuf_list_free(to_free);
+                       v->handler = align_atom_handler;
+                       continue;
+               } else if (!strcmp(name, "end")) {
+                       v->handler = end_atom_handler;
+                       continue;
                } else
                        continue;
 
@@ -700,7 +917,6 @@ static void populate_value(struct ref_array_item *ref)
                        else if (!strcmp(formatp, "track") &&
                                 (starts_with(name, "upstream") ||
                                  starts_with(name, "push"))) {
-                               char buf[40];
 
                                if (stat_tracking_info(branch, &num_ours,
                                                       &num_theirs, NULL))
@@ -708,17 +924,13 @@ static void populate_value(struct ref_array_item *ref)
 
                                if (!num_ours && !num_theirs)
                                        v->s = "";
-                               else if (!num_ours) {
-                                       sprintf(buf, "[behind %d]", num_theirs);
-                                       v->s = xstrdup(buf);
-                               } else if (!num_theirs) {
-                                       sprintf(buf, "[ahead %d]", num_ours);
-                                       v->s = xstrdup(buf);
-                               } else {
-                                       sprintf(buf, "[ahead %d, behind %d]",
-                                               num_ours, num_theirs);
-                                       v->s = xstrdup(buf);
-                               }
+                               else if (!num_ours)
+                                       v->s = xstrfmt("[behind %d]", num_theirs);
+                               else if (!num_theirs)
+                                       v->s = xstrfmt("[ahead %d]", num_ours);
+                               else
+                                       v->s = xstrfmt("[ahead %d, behind %d]",
+                                                      num_ours, num_theirs);
                                continue;
                        } else if (!strcmp(formatp, "trackshort") &&
                                   (starts_with(name, "upstream") ||
@@ -745,12 +957,8 @@ static void populate_value(struct ref_array_item *ref)
 
                if (!deref)
                        v->s = refname;
-               else {
-                       int len = strlen(refname);
-                       char *s = xmalloc(len + 4);
-                       sprintf(s, "%s^{}", refname);
-                       v->s = s;
-               }
+               else
+                       v->s = xstrfmt("%s^{}", refname);
        }
 
        for (i = 0; i < used_atom_cnt; i++) {
@@ -817,11 +1025,143 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
        *v = &ref->value[atom];
 }
 
+enum contains_result {
+       CONTAINS_UNKNOWN = -1,
+       CONTAINS_NO = 0,
+       CONTAINS_YES = 1
+};
+
+/*
+ * Mimicking the real stack, this stack lives on the heap, avoiding stack
+ * overflows.
+ *
+ * At each recursion step, the stack items points to the commits whose
+ * ancestors are to be inspected.
+ */
+struct contains_stack {
+       int nr, alloc;
+       struct contains_stack_entry {
+               struct commit *commit;
+               struct commit_list *parents;
+       } *contains_stack;
+};
+
+static int in_commit_list(const struct commit_list *want, struct commit *c)
+{
+       for (; want; want = want->next)
+               if (!hashcmp(want->item->object.sha1, c->object.sha1))
+                       return 1;
+       return 0;
+}
+
+/*
+ * Test whether the candidate or one of its parents is contained in the list.
+ * Do not recurse to find out, though, but return -1 if inconclusive.
+ */
+static enum contains_result contains_test(struct commit *candidate,
+                           const struct commit_list *want)
+{
+       /* was it previously marked as containing a want commit? */
+       if (candidate->object.flags & TMP_MARK)
+               return 1;
+       /* or marked as not possibly containing a want commit? */
+       if (candidate->object.flags & UNINTERESTING)
+               return 0;
+       /* or are we it? */
+       if (in_commit_list(want, candidate)) {
+               candidate->object.flags |= TMP_MARK;
+               return 1;
+       }
+
+       if (parse_commit(candidate) < 0)
+               return 0;
+
+       return -1;
+}
+
+static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
+{
+       ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
+       contains_stack->contains_stack[contains_stack->nr].commit = candidate;
+       contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
+}
+
+static enum contains_result contains_tag_algo(struct commit *candidate,
+               const struct commit_list *want)
+{
+       struct contains_stack contains_stack = { 0, 0, NULL };
+       int result = contains_test(candidate, want);
+
+       if (result != CONTAINS_UNKNOWN)
+               return result;
+
+       push_to_contains_stack(candidate, &contains_stack);
+       while (contains_stack.nr) {
+               struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
+               struct commit *commit = entry->commit;
+               struct commit_list *parents = entry->parents;
+
+               if (!parents) {
+                       commit->object.flags |= UNINTERESTING;
+                       contains_stack.nr--;
+               }
+               /*
+                * If we just popped the stack, parents->item has been marked,
+                * therefore contains_test will return a meaningful 0 or 1.
+                */
+               else switch (contains_test(parents->item, want)) {
+               case CONTAINS_YES:
+                       commit->object.flags |= TMP_MARK;
+                       contains_stack.nr--;
+                       break;
+               case CONTAINS_NO:
+                       entry->parents = parents->next;
+                       break;
+               case CONTAINS_UNKNOWN:
+                       push_to_contains_stack(parents->item, &contains_stack);
+                       break;
+               }
+       }
+       free(contains_stack.contains_stack);
+       return contains_test(candidate, want);
+}
+
+static int commit_contains(struct ref_filter *filter, struct commit *commit)
+{
+       if (filter->with_commit_tag_algo)
+               return contains_tag_algo(commit, filter->with_commit);
+       return is_descendant_of(commit, filter->with_commit);
+}
+
+/*
+ * Return 1 if the refname matches one of the patterns, otherwise 0.
+ * A pattern can be a literal prefix (e.g. a refname "refs/heads/master"
+ * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref
+ * matches "refs/heads/mas*", too).
+ */
+static int match_pattern(const char **patterns, const char *refname)
+{
+       /*
+        * When no '--format' option is given we need to skip the prefix
+        * for matching refs of tags and branches.
+        */
+       (void)(skip_prefix(refname, "refs/tags/", &refname) ||
+              skip_prefix(refname, "refs/heads/", &refname) ||
+              skip_prefix(refname, "refs/remotes/", &refname) ||
+              skip_prefix(refname, "refs/", &refname));
+
+       for (; *patterns; patterns++) {
+               if (!wildmatch(*patterns, refname, 0, NULL))
+                       return 1;
+       }
+       return 0;
+}
+
 /*
  * Return 1 if the refname matches one of the patterns, otherwise 0.
  * A pattern can be path prefix (e.g. a refname "refs/heads/master"
- * matches a pattern "refs/heads/") or a wildcard (e.g. the same ref
- * matches "refs/heads/m*",too).
+ * matches a pattern "refs/heads/" but not "refs/heads/m") or a
+ * wildcard (e.g. the same ref matches "refs/heads/m*", too).
  */
 static int match_name_as_path(const char **pattern, const char *refname)
 {
@@ -842,6 +1182,48 @@ static int match_name_as_path(const char **pattern, const char *refname)
        return 0;
 }
 
+/* Return 1 if the refname matches one of the patterns, otherwise 0. */
+static int filter_pattern_match(struct ref_filter *filter, const char *refname)
+{
+       if (!*filter->name_patterns)
+               return 1; /* No pattern always matches */
+       if (filter->match_as_path)
+               return match_name_as_path(filter->name_patterns, refname);
+       return match_pattern(filter->name_patterns, refname);
+}
+
+/*
+ * Given a ref (sha1, refname), check if the ref belongs to the array
+ * of sha1s. If the given ref is a tag, check if the given tag points
+ * at one of the sha1s in the given sha1 array.
+ * the given sha1_array.
+ * NEEDSWORK:
+ * 1. Only a single level of inderection is obtained, we might want to
+ * change this to account for multiple levels (e.g. annotated tags
+ * pointing to annotated tags pointing to a commit.)
+ * 2. As the refs are cached we might know what refname peels to without
+ * the need to parse the object via parse_object(). peel_ref() might be a
+ * more efficient alternative to obtain the pointee.
+ */
+static const unsigned char *match_points_at(struct sha1_array *points_at,
+                                           const unsigned char *sha1,
+                                           const char *refname)
+{
+       const unsigned char *tagged_sha1 = NULL;
+       struct object *obj;
+
+       if (sha1_array_lookup(points_at, sha1) >= 0)
+               return sha1;
+       obj = parse_object(sha1);
+       if (!obj)
+               die(_("malformed object at '%s'"), refname);
+       if (obj->type == OBJ_TAG)
+               tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
+       if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
+               return tagged_sha1;
+       return NULL;
+}
+
 /* Allocate space for a new ref_array_item and copy the objectname and flag to it */
 static struct ref_array_item *new_ref_array_item(const char *refname,
                                                 const unsigned char *objectname,
@@ -857,6 +1239,34 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
        return ref;
 }
 
+static int filter_ref_kind(struct ref_filter *filter, const char *refname)
+{
+       unsigned int i;
+
+       static struct {
+               const char *prefix;
+               unsigned int kind;
+       } ref_kind[] = {
+               { "refs/heads/" , FILTER_REFS_BRANCHES },
+               { "refs/remotes/" , FILTER_REFS_REMOTES },
+               { "refs/tags/", FILTER_REFS_TAGS}
+       };
+
+       if (filter->kind == FILTER_REFS_BRANCHES ||
+           filter->kind == FILTER_REFS_REMOTES ||
+           filter->kind == FILTER_REFS_TAGS)
+               return filter->kind;
+       else if (!strcmp(refname, "HEAD"))
+               return FILTER_REFS_DETACHED_HEAD;
+
+       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+               if (starts_with(refname, ref_kind[i].prefix))
+                       return ref_kind[i].kind;
+       }
+
+       return FILTER_REFS_OTHERS;
+}
+
 /*
  * A call-back given to for_each_ref().  Filter refs and keep them for
  * later object processing.
@@ -866,6 +1276,8 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
        struct ref_filter_cbdata *ref_cbdata = cb_data;
        struct ref_filter *filter = ref_cbdata->filter;
        struct ref_array_item *ref;
+       struct commit *commit = NULL;
+       unsigned int kind;
 
        if (flag & REF_BAD_NAME) {
                warning("ignoring ref with broken name %s", refname);
@@ -877,18 +1289,43 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
                return 0;
        }
 
-       if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
+       /* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
+       kind = filter_ref_kind(filter, refname);
+       if (!(kind & filter->kind))
+               return 0;
+
+       if (!filter_pattern_match(filter, refname))
+               return 0;
+
+       if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
                return 0;
 
+       /*
+        * A merge filter is applied on refs pointing to commits. Hence
+        * obtain the commit using the 'oid' available and discard all
+        * non-commits early. The actual filtering is done later.
+        */
+       if (filter->merge_commit || filter->with_commit || filter->verbose) {
+               commit = lookup_commit_reference_gently(oid->hash, 1);
+               if (!commit)
+                       return 0;
+               /* We perform the filtering for the '--contains' option */
+               if (filter->with_commit &&
+                   !commit_contains(filter, commit))
+                       return 0;
+       }
+
        /*
         * We do not open the object yet; sort may only need refname
         * to do its job and the resulting list may yet to be pruned
         * by maxcount logic.
         */
        ref = new_ref_array_item(refname, oid->hash, flag);
+       ref->commit = commit;
 
        REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
        ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
+       ref->kind = kind;
        return 0;
 }
 
@@ -911,6 +1348,50 @@ void ref_array_clear(struct ref_array *array)
        array->nr = array->alloc = 0;
 }
 
+static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
+{
+       struct rev_info revs;
+       int i, old_nr;
+       struct ref_filter *filter = ref_cbdata->filter;
+       struct ref_array *array = ref_cbdata->array;
+       struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
+
+       init_revisions(&revs, NULL);
+
+       for (i = 0; i < array->nr; i++) {
+               struct ref_array_item *item = array->items[i];
+               add_pending_object(&revs, &item->commit->object, item->refname);
+               to_clear[i] = item->commit;
+       }
+
+       filter->merge_commit->object.flags |= UNINTERESTING;
+       add_pending_object(&revs, &filter->merge_commit->object, "");
+
+       revs.limited = 1;
+       if (prepare_revision_walk(&revs))
+               die(_("revision walk setup failed"));
+
+       old_nr = array->nr;
+       array->nr = 0;
+
+       for (i = 0; i < old_nr; i++) {
+               struct ref_array_item *item = array->items[i];
+               struct commit *commit = item->commit;
+
+               int is_merged = !!(commit->object.flags & UNINTERESTING);
+
+               if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
+                       array->items[array->nr++] = array->items[i];
+               else
+                       free_array_item(item);
+       }
+
+       for (i = 0; i < old_nr; i++)
+               clear_commit_marks(to_clear[i], ALL_REV_FLAGS);
+       clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
+       free(to_clear);
+}
+
 /*
  * API for filtering a set of refs. Based on the type of refs the user
  * has requested, we iterate through those refs and apply filters
@@ -920,17 +1401,44 @@ void ref_array_clear(struct ref_array *array)
 int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
 {
        struct ref_filter_cbdata ref_cbdata;
+       int ret = 0;
+       unsigned int broken = 0;
 
        ref_cbdata.array = array;
        ref_cbdata.filter = filter;
 
-       if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
-               return for_each_rawref(ref_filter_handler, &ref_cbdata);
-       else if (type & FILTER_REFS_ALL)
-               return for_each_ref(ref_filter_handler, &ref_cbdata);
-       else
+       if (type & FILTER_REFS_INCLUDE_BROKEN)
+               broken = 1;
+       filter->kind = type & FILTER_REFS_KIND_MASK;
+
+       /*  Simple per-ref filtering */
+       if (!filter->kind)
                die("filter_refs: invalid type");
-       return 0;
+       else {
+               /*
+                * For common cases where we need only branches or remotes or tags,
+                * we only iterate through those refs. If a mix of refs is needed,
+                * we iterate over all refs and filter out required refs with the help
+                * of filter_ref_kind().
+                */
+               if (filter->kind == FILTER_REFS_BRANCHES)
+                       ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind == FILTER_REFS_REMOTES)
+                       ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind == FILTER_REFS_TAGS)
+                       ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
+               else if (filter->kind & FILTER_REFS_ALL)
+                       ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken);
+               if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
+                       head_ref(ref_filter_handler, &ref_cbdata);
+       }
+
+
+       /*  Filters that need revision walking */
+       if (filter->merge_commit)
+               do_merge_filter(&ref_cbdata);
+
+       return ret;
 }
 
 static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
@@ -941,19 +1449,19 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
 
        get_ref_atom_value(a, s->atom, &va);
        get_ref_atom_value(b, s->atom, &vb);
-       switch (cmp_type) {
-       case FIELD_STR:
+       if (s->version)
+               cmp = versioncmp(va->s, vb->s);
+       else if (cmp_type == FIELD_STR)
                cmp = strcmp(va->s, vb->s);
-               break;
-       default:
+       else {
                if (va->ul < vb->ul)
                        cmp = -1;
                else if (va->ul == vb->ul)
                        cmp = 0;
                else
                        cmp = 1;
-               break;
        }
+
        return (s->reverse) ? -cmp : cmp;
 }
 
@@ -978,32 +1486,6 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
        qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
 }
 
-static void print_value(struct atom_value *v, int quote_style)
-{
-       struct strbuf sb = STRBUF_INIT;
-       switch (quote_style) {
-       case QUOTE_NONE:
-               fputs(v->s, stdout);
-               break;
-       case QUOTE_SHELL:
-               sq_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_PERL:
-               perl_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_PYTHON:
-               python_quote_buf(&sb, v->s);
-               break;
-       case QUOTE_TCL:
-               tcl_quote_buf(&sb, v->s);
-               break;
-       }
-       if (quote_style != QUOTE_NONE) {
-               fputs(sb.buf, stdout);
-               strbuf_release(&sb);
-       }
-}
-
 static int hex1(char ch)
 {
        if ('0' <= ch && ch <= '9')
@@ -1022,8 +1504,10 @@ static int hex2(const char *cp)
                return -1;
 }
 
-static void emit(const char *cp, const char *ep)
+static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
 {
+       struct strbuf *s = &state->stack->output;
+
        while (*cp && (!ep || cp < ep)) {
                if (*cp == '%') {
                        if (cp[1] == '%')
@@ -1031,13 +1515,13 @@ static void emit(const char *cp, const char *ep)
                        else {
                                int ch = hex2(cp + 1);
                                if (0 <= ch) {
-                                       putchar(ch);
+                                       strbuf_addch(s, ch);
                                        cp += 3;
                                        continue;
                                }
                        }
                }
-               putchar(*cp);
+               strbuf_addch(s, *cp);
                cp++;
        }
 }
@@ -1045,19 +1529,24 @@ static void emit(const char *cp, const char *ep)
 void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
 {
        const char *cp, *sp, *ep;
+       struct strbuf *final_buf;
+       struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
+
+       state.quote_style = quote_style;
+       push_stack_element(&state.stack);
 
        for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
                struct atom_value *atomv;
 
                ep = strchr(sp, ')');
                if (cp < sp)
-                       emit(cp, sp);
+                       append_literal(cp, sp, &state);
                get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
-               print_value(atomv, quote_style);
+               atomv->handler(atomv, &state);
        }
        if (*cp) {
                sp = cp + strlen(cp);
-               emit(cp, sp);
+               append_literal(cp, sp, &state);
        }
        if (need_color_reset_at_eol) {
                struct atom_value resetv;
@@ -1066,8 +1555,13 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
                if (color_parse("reset", color) < 0)
                        die("BUG: couldn't parse 'reset' as a color");
                resetv.s = color;
-               print_value(&resetv, quote_style);
+               append_atom(&resetv, &state);
        }
+       if (state.stack->prev)
+               die(_("format: %%(end) atom missing"));
+       final_buf = &state.stack->output;
+       fwrite(final_buf->buf, 1, final_buf->len, stdout);
+       pop_stack_element(&state.stack);
        putchar('\n');
 }
 
@@ -1100,7 +1594,29 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
                s->reverse = 1;
                arg++;
        }
+       if (skip_prefix(arg, "version:", &arg) ||
+           skip_prefix(arg, "v:", &arg))
+               s->version = 1;
        len = strlen(arg);
        s->atom = parse_ref_filter_atom(arg, arg+len);
        return 0;
 }
+
+int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
+{
+       struct ref_filter *rf = opt->value;
+       unsigned char sha1[20];
+
+       rf->merge = starts_with(opt->long_name, "no")
+               ? REF_FILTER_MERGED_OMIT
+               : REF_FILTER_MERGED_INCLUDE;
+
+       if (get_sha1(arg, sha1))
+               die(_("malformed object name %s"), arg);
+
+       rf->merge_commit = lookup_commit_reference_gently(sha1, 0);
+       if (!rf->merge_commit)
+               return opterror(opt, "must point to a commit", 0);
+
+       return 0;
+}
index 699798400b329a12bc5dfa09590853a18616f876..14d435e2ccf0204312d1f72555f1c2c36fabd215 100644 (file)
 #define QUOTE_PYTHON 4
 #define QUOTE_TCL 8
 
-#define FILTER_REFS_INCLUDE_BROKEN 0x1
-#define FILTER_REFS_ALL 0x2
+#define FILTER_REFS_INCLUDE_BROKEN 0x0001
+#define FILTER_REFS_TAGS           0x0002
+#define FILTER_REFS_BRANCHES       0x0004
+#define FILTER_REFS_REMOTES        0x0008
+#define FILTER_REFS_OTHERS         0x0010
+#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \
+                                   FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
+#define FILTER_REFS_DETACHED_HEAD  0x0020
+#define FILTER_REFS_KIND_MASK      (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD)
 
-struct atom_value {
-       const char *s;
-       unsigned long ul; /* used for sorting when not FIELD_STR */
-};
+struct atom_value;
 
 struct ref_sorting {
        struct ref_sorting *next;
        int atom; /* index into used_atom array (internal) */
-       unsigned reverse : 1;
+       unsigned reverse : 1,
+               version : 1;
 };
 
 struct ref_array_item {
        unsigned char objectname[20];
        int flag;
+       unsigned int kind;
        const char *symref;
+       struct commit *commit;
        struct atom_value *value;
        char refname[FLEX_ARRAY];
 };
@@ -38,10 +45,28 @@ struct ref_array_item {
 struct ref_array {
        int nr, alloc;
        struct ref_array_item **items;
+       struct rev_info *revs;
 };
 
 struct ref_filter {
        const char **name_patterns;
+       struct sha1_array points_at;
+       struct commit_list *with_commit;
+
+       enum {
+               REF_FILTER_MERGED_NONE = 0,
+               REF_FILTER_MERGED_INCLUDE,
+               REF_FILTER_MERGED_OMIT
+       } merge;
+       struct commit *merge_commit;
+
+       unsigned int with_commit_tag_algo : 1,
+               match_as_path : 1,
+               detached : 1;
+       unsigned int kind,
+               lines;
+       int abbrev,
+               verbose;
 };
 
 struct ref_filter_cbdata {
@@ -49,6 +74,15 @@ struct ref_filter_cbdata {
        struct ref_filter *filter;
 };
 
+/*  Macros for checking --merged and --no-merged options */
+#define _OPT_MERGED_NO_MERGED(option, filter, h) \
+       { OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \
+         PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \
+         parse_opt_merge_filter, (intptr_t) "HEAD" \
+       }
+#define OPT_MERGED(f, h) _OPT_MERGED_NO_MERGED("merged", f, h)
+#define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h)
+
 /*
  * API for filtering a set of refs. Based on the type of refs the user
  * has requested, we iterate through those refs and apply filters
@@ -70,5 +104,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
 int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
 /*  Default sort option based on refname */
 struct ref_sorting *ref_default_sorting(void);
+/*  Function to parse --merged and --no-merged options */
+int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset);
 
 #endif /*  REF_FILTER_H  */
index f8e743a23bef06bbf144339dc79f2186ec40805b..85b8a54241048bb2cc037c2c114b49e7c7e7968b 100644 (file)
@@ -56,12 +56,11 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)
                }
        }
        if (reflogs->nr == 0) {
-               int len = strlen(ref);
-               char *refname = xmalloc(len + 12);
-               sprintf(refname, "refs/%s", ref);
+               char *refname = xstrfmt("refs/%s", ref);
                for_each_reflog_ent(refname, read_one_reflog, reflogs);
                if (reflogs->nr == 0) {
-                       sprintf(refname, "refs/heads/%s", ref);
+                       free(refname);
+                       refname = xstrfmt("refs/heads/%s", ref);
                        for_each_reflog_ent(refname, read_one_reflog, reflogs);
                }
                free(refname);
diff --git a/refs.c b/refs.c
index 4e15f60d98ea8affdef226bce199935fa694b195..132eff52ca4092eae4c2e1c0f86a8c380f88778a 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -304,6 +304,11 @@ struct ref_entry {
 };
 
 static void read_loose_refs(const char *dirname, struct ref_dir *dir);
+static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len);
+static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache,
+                                         const char *dirname, size_t len,
+                                         int incomplete);
+static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
 
 static struct ref_dir *get_ref_dir(struct ref_entry *entry)
 {
@@ -312,6 +317,24 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
        dir = &entry->u.subdir;
        if (entry->flag & REF_INCOMPLETE) {
                read_loose_refs(entry->name, dir);
+
+               /*
+                * Manually add refs/bisect, which, being
+                * per-worktree, might not appear in the directory
+                * listing for refs/ in the main repo.
+                */
+               if (!strcmp(entry->name, "refs/")) {
+                       int pos = search_ref_dir(dir, "refs/bisect/", 12);
+                       if (pos < 0) {
+                               struct ref_entry *child_entry;
+                               child_entry = create_dir_entry(dir->ref_cache,
+                                                              "refs/bisect/",
+                                                              12, 1);
+                               add_entry_to_dir(dir, child_entry);
+                               read_loose_refs("refs/bisect",
+                                               &child_entry->u.subdir);
+                       }
+               }
                entry->flag &= ~REF_INCOMPLETE;
        }
        return dir;
@@ -1579,16 +1602,15 @@ static int resolve_missing_loose_ref(const char *refname,
 }
 
 /* This function needs to return a meaningful errno on failure */
-static const char *resolve_ref_unsafe_1(const char *refname,
-                                       int resolve_flags,
-                                       unsigned char *sha1,
-                                       int *flags,
-                                       struct strbuf *sb_path)
+static const char *resolve_ref_1(const char *refname,
+                                int resolve_flags,
+                                unsigned char *sha1,
+                                int *flags,
+                                struct strbuf *sb_refname,
+                                struct strbuf *sb_path,
+                                struct strbuf *sb_contents)
 {
        int depth = MAXDEPTH;
-       ssize_t len;
-       char buffer[256];
-       static char refname_buffer[256];
        int bad_name = 0;
 
        if (flags)
@@ -1654,19 +1676,18 @@ static const char *resolve_ref_unsafe_1(const char *refname,
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
-                       len = readlink(path, buffer, sizeof(buffer)-1);
-                       if (len < 0) {
+                       strbuf_reset(sb_contents);
+                       if (strbuf_readlink(sb_contents, path, 0) < 0) {
                                if (errno == ENOENT || errno == EINVAL)
                                        /* inconsistent with lstat; retry */
                                        goto stat_ref;
                                else
                                        return NULL;
                        }
-                       buffer[len] = 0;
-                       if (starts_with(buffer, "refs/") &&
-                                       !check_refname_format(buffer, 0)) {
-                               strcpy(refname_buffer, buffer);
-                               refname = refname_buffer;
+                       if (starts_with(sb_contents->buf, "refs/") &&
+                           !check_refname_format(sb_contents->buf, 0)) {
+                               strbuf_swap(sb_refname, sb_contents);
+                               refname = sb_refname->buf;
                                if (flags)
                                        *flags |= REF_ISSYMREF;
                                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
@@ -1695,28 +1716,26 @@ static const char *resolve_ref_unsafe_1(const char *refname,
                        else
                                return NULL;
                }
-               len = read_in_full(fd, buffer, sizeof(buffer)-1);
-               if (len < 0) {
+               strbuf_reset(sb_contents);
+               if (strbuf_read(sb_contents, fd, 256) < 0) {
                        int save_errno = errno;
                        close(fd);
                        errno = save_errno;
                        return NULL;
                }
                close(fd);
-               while (len && isspace(buffer[len-1]))
-                       len--;
-               buffer[len] = '\0';
+               strbuf_rtrim(sb_contents);
 
                /*
                 * Is it a symbolic ref?
                 */
-               if (!starts_with(buffer, "ref:")) {
+               if (!starts_with(sb_contents->buf, "ref:")) {
                        /*
                         * Please note that FETCH_HEAD has a second
                         * line containing other data.
                         */
-                       if (get_sha1_hex(buffer, sha1) ||
-                           (buffer[40] != '\0' && !isspace(buffer[40]))) {
+                       if (get_sha1_hex(sb_contents->buf, sha1) ||
+                           (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) {
                                if (flags)
                                        *flags |= REF_ISBROKEN;
                                errno = EINVAL;
@@ -1731,10 +1750,12 @@ static const char *resolve_ref_unsafe_1(const char *refname,
                }
                if (flags)
                        *flags |= REF_ISSYMREF;
-               buf = buffer + 4;
+               buf = sb_contents->buf + 4;
                while (isspace(*buf))
                        buf++;
-               refname = strcpy(refname_buffer, buf);
+               strbuf_reset(sb_refname);
+               strbuf_addstr(sb_refname, buf);
+               refname = sb_refname->buf;
                if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
                        hashclr(sha1);
                        return refname;
@@ -1756,10 +1777,15 @@ static const char *resolve_ref_unsafe_1(const char *refname,
 const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               unsigned char *sha1, int *flags)
 {
+       static struct strbuf sb_refname = STRBUF_INIT;
+       struct strbuf sb_contents = STRBUF_INIT;
        struct strbuf sb_path = STRBUF_INIT;
-       const char *ret = resolve_ref_unsafe_1(refname, resolve_flags,
-                                              sha1, flags, &sb_path);
+       const char *ret;
+
+       ret = resolve_ref_1(refname, resolve_flags, sha1, flags,
+                           &sb_refname, &sb_path, &sb_contents);
        strbuf_release(&sb_path);
+       strbuf_release(&sb_contents);
        return ret;
 }
 
@@ -2108,6 +2134,15 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
        return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
 }
 
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
+{
+       unsigned int flag = 0;
+
+       if (broken)
+               flag = DO_FOR_EACH_INCLUDE_BROKEN;
+       return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);
+}
+
 int for_each_ref_in_submodule(const char *submodule, const char *prefix,
                each_ref_fn fn, void *cb_data)
 {
@@ -2190,8 +2225,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 
        if (!has_glob_specials(pattern)) {
                /* Append implied '/' '*' if not present. */
-               if (real_pattern.buf[real_pattern.len - 1] != '/')
-                       strbuf_addch(&real_pattern, '/');
+               strbuf_complete(&real_pattern, '/');
                /* No need to check for '*', there is none. */
                strbuf_addch(&real_pattern, '*');
        }
@@ -2649,6 +2683,8 @@ struct pack_refs_cb_data {
        struct ref_to_prune *ref_to_prune;
 };
 
+static int is_per_worktree_ref(const char *refname);
+
 /*
  * An each_ref_entry_fn that is run over loose references only.  If
  * the loose reference can be packed, add an entry in the packed ref
@@ -2662,6 +2698,10 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
        struct ref_entry *packed_entry;
        int is_tag_ref = starts_with(entry->name, "refs/tags/");
 
+       /* Do not pack per-worktree refs: */
+       if (is_per_worktree_ref(entry->name))
+               return 0;
+
        /* ALWAYS pack tags */
        if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
                return 0;
@@ -2692,7 +2732,7 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
                int namelen = strlen(entry->name) + 1;
                struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
                hashcpy(n->sha1, entry->u.value.oid.hash);
-               strcpy(n->name, entry->name);
+               memcpy(n->name, entry->name, namelen); /* includes NUL */
                n->next = cb->ref_to_prune;
                cb->ref_to_prune = n;
        }
@@ -2856,7 +2896,8 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
 
 static int is_per_worktree_ref(const char *refname)
 {
-       return !strcmp(refname, "HEAD");
+       return !strcmp(refname, "HEAD") ||
+               starts_with(refname, "refs/bisect/");
 }
 
 static int is_pseudoref_syntax(const char *refname)
@@ -3326,10 +3367,10 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
        msglen = msg ? strlen(msg) : 0;
        maxlen = strlen(committer) + msglen + 100;
        logrec = xmalloc(maxlen);
-       len = sprintf(logrec, "%s %s %s\n",
-                     sha1_to_hex(old_sha1),
-                     sha1_to_hex(new_sha1),
-                     committer);
+       len = xsnprintf(logrec, maxlen, "%s %s %s\n",
+                       sha1_to_hex(old_sha1),
+                       sha1_to_hex(new_sha1),
+                       committer);
        if (msglen)
                len += copy_msg(logrec + len - 1, msg) - 1;
 
@@ -3981,10 +4022,10 @@ void ref_transaction_free(struct ref_transaction *transaction)
 static struct ref_update *add_update(struct ref_transaction *transaction,
                                     const char *refname)
 {
-       size_t len = strlen(refname);
-       struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
+       size_t len = strlen(refname) + 1;
+       struct ref_update *update = xcalloc(1, sizeof(*update) + len);
 
-       strcpy((char *)update->refname, refname);
+       memcpy((char *)update->refname, refname, len); /* includes NUL */
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
        transaction->updates[transaction->nr++] = update;
        return update;
diff --git a/refs.h b/refs.h
index e9a5f3230ab09b667e7ae28b2b0bcd26bd2f067e..6d30c980d182f27ab78d43342c0865df7906693c 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -173,6 +173,7 @@ typedef int each_ref_fn(const char *refname,
 extern int head_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
+extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken);
 extern int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
index 71fbbb694fc78471343f568a5e93b8013a7bb9cb..cc7a8a66faa96ba9bc8ae82e72d0202c0a802ba1 100644 (file)
@@ -168,10 +168,7 @@ static struct ref *parse_info_refs(struct discovery *heads)
                                    url.buf);
                        data[i] = 0;
                        ref_name = mid + 1;
-                       ref = xmalloc(sizeof(struct ref) +
-                                     strlen(ref_name) + 1);
-                       memset(ref, 0, sizeof(struct ref));
-                       strcpy(ref->name, ref_name);
+                       ref = alloc_ref(ref_name);
                        get_sha1_hex(start, ref->old_sha1);
                        if (!refs)
                                refs = ref;
index 26504b744786c65ea4d6e1e0abbf5c6409af5358..1101f82eaf34b936d51e0d61837c568b3691a468 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -8,6 +8,7 @@
 #include "tag.h"
 #include "string-list.h"
 #include "mergesort.h"
+#include "argv-array.h"
 
 enum map_direction { FROM_SRC, FROM_DST };
 
@@ -54,9 +55,6 @@ static const char *pushremote_name;
 static struct rewrites rewrites;
 static struct rewrites rewrites_push;
 
-#define BUF_SIZE (2048)
-static char buffer[BUF_SIZE];
-
 static int valid_remote(const struct remote *remote)
 {
        return (!!remote->url) || (!!remote->foreign_vcs);
@@ -65,7 +63,6 @@ static int valid_remote(const struct remote *remote)
 static const char *alias_url(const char *url, struct rewrites *r)
 {
        int i, j;
-       char *ret;
        struct counted_string *longest;
        int longest_i;
 
@@ -86,11 +83,7 @@ static const char *alias_url(const char *url, struct rewrites *r)
        if (!longest)
                return url;
 
-       ret = xmalloc(r->rewrite[longest_i]->baselen +
-                    (strlen(url) - longest->len) + 1);
-       strcpy(ret, r->rewrite[longest_i]->base);
-       strcpy(ret + r->rewrite[longest_i]->baselen, url + longest->len);
-       return ret;
+       return xstrfmt("%s%s", r->rewrite[longest_i]->base, url + longest->len);
 }
 
 static void add_push_refspec(struct remote *remote, const char *ref)
@@ -248,106 +241,76 @@ static void add_instead_of(struct rewrite *rewrite, const char *instead_of)
        rewrite->instead_of_nr++;
 }
 
+static const char *skip_spaces(const char *s)
+{
+       while (isspace(*s))
+               s++;
+       return s;
+}
+
 static void read_remotes_file(struct remote *remote)
 {
+       struct strbuf buf = STRBUF_INIT;
        FILE *f = fopen(git_path("remotes/%s", remote->name), "r");
 
        if (!f)
                return;
        remote->origin = REMOTE_REMOTES;
-       while (fgets(buffer, BUF_SIZE, f)) {
-               int value_list;
-               char *s, *p;
-
-               if (starts_with(buffer, "URL:")) {
-                       value_list = 0;
-                       s = buffer + 4;
-               } else if (starts_with(buffer, "Push:")) {
-                       value_list = 1;
-                       s = buffer + 5;
-               } else if (starts_with(buffer, "Pull:")) {
-                       value_list = 2;
-                       s = buffer + 5;
-               } else
-                       continue;
-
-               while (isspace(*s))
-                       s++;
-               if (!*s)
-                       continue;
+       while (strbuf_getline(&buf, f, '\n') != EOF) {
+               const char *v;
 
-               p = s + strlen(s);
-               while (isspace(p[-1]))
-                       *--p = 0;
+               strbuf_rtrim(&buf);
 
-               switch (value_list) {
-               case 0:
-                       add_url_alias(remote, xstrdup(s));
-                       break;
-               case 1:
-                       add_push_refspec(remote, xstrdup(s));
-                       break;
-               case 2:
-                       add_fetch_refspec(remote, xstrdup(s));
-                       break;
-               }
+               if (skip_prefix(buf.buf, "URL:", &v))
+                       add_url_alias(remote, xstrdup(skip_spaces(v)));
+               else if (skip_prefix(buf.buf, "Push:", &v))
+                       add_push_refspec(remote, xstrdup(skip_spaces(v)));
+               else if (skip_prefix(buf.buf, "Pull:", &v))
+                       add_fetch_refspec(remote, xstrdup(skip_spaces(v)));
        }
+       strbuf_release(&buf);
        fclose(f);
 }
 
 static void read_branches_file(struct remote *remote)
 {
        char *frag;
-       struct strbuf branch = STRBUF_INIT;
-       int n = 1000;
-       FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
-       char *s, *p;
-       int len;
+       struct strbuf buf = STRBUF_INIT;
+       FILE *f = fopen(git_path("branches/%s", remote->name), "r");
 
        if (!f)
                return;
-       s = fgets(buffer, BUF_SIZE, f);
-       fclose(f);
-       if (!s)
-               return;
-       while (isspace(*s))
-               s++;
-       if (!*s)
+
+       strbuf_getline(&buf, f, '\n');
+       strbuf_trim(&buf);
+       if (!buf.len) {
+               strbuf_release(&buf);
                return;
+       }
+
        remote->origin = REMOTE_BRANCHES;
-       p = s + strlen(s);
-       while (isspace(p[-1]))
-               *--p = 0;
-       len = p - s;
-       p = xmalloc(len + 1);
-       strcpy(p, s);
 
        /*
         * The branches file would have URL and optionally
         * #branch specified.  The "master" (or specified) branch is
-        * fetched and stored in the local branch of the same name.
+        * fetched and stored in the local branch matching the
+        * remote name.
         */
-       frag = strchr(p, '#');
-       if (frag) {
+       frag = strchr(buf.buf, '#');
+       if (frag)
                *(frag++) = '\0';
-               strbuf_addf(&branch, "refs/heads/%s", frag);
-       } else
-               strbuf_addstr(&branch, "refs/heads/master");
+       else
+               frag = "master";
+
+       add_url_alias(remote, strbuf_detach(&buf, NULL));
+       add_fetch_refspec(remote, xstrfmt("refs/heads/%s:refs/heads/%s",
+                                         frag, remote->name));
 
-       strbuf_addf(&branch, ":refs/heads/%s", remote->name);
-       add_url_alias(remote, p);
-       add_fetch_refspec(remote, strbuf_detach(&branch, NULL));
        /*
         * Cogito compatible push: push current HEAD to remote #branch
         * (master if missing)
         */
-       strbuf_init(&branch, 0);
-       strbuf_addstr(&branch, "HEAD");
-       if (frag)
-               strbuf_addf(&branch, ":refs/heads/%s", frag);
-       else
-               strbuf_addstr(&branch, ":refs/heads/master");
-       add_push_refspec(remote, strbuf_detach(&branch, NULL));
+       add_push_refspec(remote, xstrfmt("HEAD:refs/heads/%s", frag));
        remote->fetch_tags = 1; /* always auto-follow */
 }
 
@@ -2035,10 +1998,9 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 {
        unsigned char sha1[20];
        struct commit *ours, *theirs;
-       char symmetric[84];
        struct rev_info revs;
-       const char *rev_argv[10], *base;
-       int rev_argc;
+       const char *base;
+       struct argv_array argv = ARGV_ARRAY_INIT;
 
        /* Cannot stat unless we are marked to build on top of somebody else. */
        base = branch_get_upstream(branch, NULL);
@@ -2067,19 +2029,15 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
        }
 
        /* Run "rev-list --left-right ours...theirs" internally... */
-       rev_argc = 0;
-       rev_argv[rev_argc++] = NULL;
-       rev_argv[rev_argc++] = "--left-right";
-       rev_argv[rev_argc++] = symmetric;
-       rev_argv[rev_argc++] = "--";
-       rev_argv[rev_argc] = NULL;
-
-       strcpy(symmetric, sha1_to_hex(ours->object.sha1));
-       strcpy(symmetric + 40, "...");
-       strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
+       argv_array_push(&argv, ""); /* ignored */
+       argv_array_push(&argv, "--left-right");
+       argv_array_pushf(&argv, "%s...%s",
+                        sha1_to_hex(ours->object.sha1),
+                        sha1_to_hex(theirs->object.sha1));
+       argv_array_push(&argv, "--");
 
        init_revisions(&revs, NULL);
-       setup_revisions(rev_argc, rev_argv, &revs, NULL);
+       setup_revisions(argv.argc, argv.argv, &revs, NULL);
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
 
@@ -2099,6 +2057,8 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
        /* clear object flags smudged by the above traversal */
        clear_commit_marks(ours, ALL_REV_FLAGS);
        clear_commit_marks(theirs, ALL_REV_FLAGS);
+
+       argv_array_clear(&argv);
        return 0;
 }
 
index d90057b7695918e260e1778a522011dabaa3a37b..403c700c326e4551aea183523af6ed46e1476432 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -20,45 +20,74 @@ static int rerere_enabled = -1;
 /* automatically update cleanly resolved paths to the index */
 static int rerere_autoupdate;
 
-const char *rerere_path(const char *hex, const char *file)
+static void free_rerere_id(struct string_list_item *item)
 {
-       return git_path("rr-cache/%s/%s", hex, file);
+       free(item->util);
 }
 
-static int has_rerere_resolution(const char *hex)
+static const char *rerere_id_hex(const struct rerere_id *id)
+{
+       return id->hex;
+}
+
+const char *rerere_path(const struct rerere_id *id, const char *file)
+{
+       if (!file)
+               return git_path("rr-cache/%s", rerere_id_hex(id));
+
+       return git_path("rr-cache/%s/%s", rerere_id_hex(id), file);
+}
+
+static int has_rerere_resolution(const struct rerere_id *id)
 {
        struct stat st;
-       return !stat(rerere_path(hex, "postimage"), &st);
+
+       return !stat(rerere_path(id, "postimage"), &st);
+}
+
+static struct rerere_id *new_rerere_id_hex(char *hex)
+{
+       struct rerere_id *id = xmalloc(sizeof(*id));
+       strcpy(id->hex, hex);
+       return id;
+}
+
+static struct rerere_id *new_rerere_id(unsigned char *sha1)
+{
+       return new_rerere_id_hex(sha1_to_hex(sha1));
 }
 
+/*
+ * $GIT_DIR/MERGE_RR file is a collection of records, each of which is
+ * "conflict ID", a HT and pathname, terminated with a NUL, and is
+ * used to keep track of the set of paths that "rerere" may need to
+ * work on (i.e. what is left by the previous invocation of "git
+ * rerere" during the current conflict resolution session).
+ */
 static void read_rr(struct string_list *rr)
 {
-       unsigned char sha1[20];
-       char buf[PATH_MAX];
+       struct strbuf buf = STRBUF_INIT;
        FILE *in = fopen(git_path_merge_rr(), "r");
+
        if (!in)
                return;
-       while (fread(buf, 40, 1, in) == 1) {
-               int i;
-               char *name;
-               if (get_sha1_hex(buf, sha1))
+       while (!strbuf_getwholeline(&buf, in, '\0')) {
+               char *path;
+               unsigned char sha1[20];
+               struct rerere_id *id;
+
+               /* There has to be the hash, tab, path and then NUL */
+               if (buf.len < 42 || get_sha1_hex(buf.buf, sha1))
                        die("corrupt MERGE_RR");
-               buf[40] = '\0';
-               name = xstrdup(buf);
-               if (fgetc(in) != '\t')
+
+               if (buf.buf[40] != '\t')
                        die("corrupt MERGE_RR");
-               for (i = 0; i < sizeof(buf); i++) {
-                       int c = fgetc(in);
-                       if (c < 0)
-                               die("corrupt MERGE_RR");
-                       buf[i] = c;
-                       if (c == 0)
-                                break;
-               }
-               if (i == sizeof(buf))
-                       die("filename too long");
-               string_list_insert(rr, buf)->util = name;
+               buf.buf[40] = '\0';
+               path = buf.buf + 41;
+               id = new_rerere_id_hex(buf.buf);
+               string_list_insert(rr, path)->util = id;
        }
+       strbuf_release(&buf);
        fclose(in);
 }
 
@@ -68,22 +97,42 @@ static int write_rr(struct string_list *rr, int out_fd)
 {
        int i;
        for (i = 0; i < rr->nr; i++) {
-               const char *path;
-               int length;
-               if (!rr->items[i].util)
+               struct strbuf buf = STRBUF_INIT;
+               struct rerere_id *id;
+
+               assert(rr->items[i].util != RERERE_RESOLVED);
+
+               id = rr->items[i].util;
+               if (!id)
                        continue;
-               path = rr->items[i].string;
-               length = strlen(path) + 1;
-               if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
-                   write_str_in_full(out_fd, "\t") != 1 ||
-                   write_in_full(out_fd, path, length) != length)
+               strbuf_addf(&buf, "%s\t%s%c",
+                           rerere_id_hex(id),
+                           rr->items[i].string, 0);
+               if (write_in_full(out_fd, buf.buf, buf.len) != buf.len)
                        die("unable to write rerere record");
+
+               strbuf_release(&buf);
        }
        if (commit_lock_file(&write_lock) != 0)
                die("unable to write rerere record");
        return 0;
 }
 
+/*
+ * "rerere" interacts with conflicted file contents using this I/O
+ * abstraction.  It reads a conflicted contents from one place via
+ * "getline()" method, and optionally can write it out after
+ * normalizing the conflicted hunks to the "output".  Subclasses of
+ * rerere_io embed this structure at the beginning of their own
+ * rerere_io object.
+ */
+struct rerere_io {
+       int (*getline)(struct strbuf *, struct rerere_io *);
+       FILE *output;
+       int wrerror;
+       /* some more stuff */
+};
+
 static void ferr_write(const void *p, size_t count, FILE *fp, int *err)
 {
        if (!count || *err)
@@ -97,31 +146,34 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err)
        ferr_write(s, strlen(s), fp, err);
 }
 
-struct rerere_io {
-       int (*getline)(struct strbuf *, struct rerere_io *);
-       FILE *output;
-       int wrerror;
-       /* some more stuff */
-};
-
 static void rerere_io_putstr(const char *str, struct rerere_io *io)
 {
        if (io->output)
                ferr_puts(str, io->output, &io->wrerror);
 }
 
+/*
+ * Write a conflict marker to io->output (if defined).
+ */
 static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
 {
        char buf[64];
 
        while (size) {
-               if (size < sizeof(buf) - 2) {
+               if (size <= sizeof(buf) - 2) {
                        memset(buf, ch, size);
                        buf[size] = '\n';
                        buf[size + 1] = '\0';
                        size = 0;
                } else {
                        int sz = sizeof(buf) - 1;
+
+                       /*
+                        * Make sure we will not write everything out
+                        * in this round by leaving at least 1 byte
+                        * for the next round, giving the next round
+                        * a chance to add the terminating LF.  Yuck.
+                        */
                        if (size <= sz)
                                sz -= (sz - size) + 1;
                        memset(buf, ch, sz);
@@ -138,19 +190,42 @@ static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
                ferr_write(mem, sz, io->output, &io->wrerror);
 }
 
+/*
+ * Subclass of rerere_io that reads from an on-disk file
+ */
 struct rerere_io_file {
        struct rerere_io io;
        FILE *input;
 };
 
+/*
+ * ... and its getline() method implementation
+ */
 static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
 {
        struct rerere_io_file *io = (struct rerere_io_file *)io_;
        return strbuf_getwholeline(sb, io->input, '\n');
 }
 
-static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
+/*
+ * Require the exact number of conflict marker letters, no more, no
+ * less, followed by SP or any whitespace
+ * (including LF).
+ */
+static int is_cmarker(char *buf, int marker_char, int marker_size)
 {
+       int want_sp;
+
+       /*
+        * The beginning of our version and the end of their version
+        * always are labeled like "<<<<< ours" or ">>>>> theirs",
+        * hence we set want_sp for them.  Note that the version from
+        * the common ancestor in diff3-style output is not always
+        * labelled (e.g. "||||| common" is often seen but "|||||"
+        * alone is also valid), so we do not set want_sp.
+        */
+       want_sp = (marker_char == '<') || (marker_char == '>');
+
        while (marker_size--)
                if (*buf++ != marker_char)
                        return 0;
@@ -159,6 +234,21 @@ static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
        return isspace(*buf);
 }
 
+/*
+ * Read contents a file with conflicts, normalize the conflicts
+ * by (1) discarding the common ancestor version in diff3-style,
+ * (2) reordering our side and their side so that whichever sorts
+ * alphabetically earlier comes before the other one, while
+ * computing the "conflict ID", which is just an SHA-1 hash of
+ * one side of the conflict, NUL, the other side of the conflict,
+ * and NUL concatenated together.
+ *
+ * Return the number of conflict hunks found.
+ *
+ * NEEDSWORK: the logic and theory of operation behind this conflict
+ * normalization may deserve to be documented somewhere, perhaps in
+ * Documentation/technical/rerere.txt.
+ */
 static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
 {
        git_SHA_CTX ctx;
@@ -173,19 +263,19 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz
                git_SHA1_Init(&ctx);
 
        while (!io->getline(&buf, io)) {
-               if (is_cmarker(buf.buf, '<', marker_size, 1)) {
+               if (is_cmarker(buf.buf, '<', marker_size)) {
                        if (hunk != RR_CONTEXT)
                                goto bad;
                        hunk = RR_SIDE_1;
-               } else if (is_cmarker(buf.buf, '|', marker_size, 0)) {
+               } else if (is_cmarker(buf.buf, '|', marker_size)) {
                        if (hunk != RR_SIDE_1)
                                goto bad;
                        hunk = RR_ORIGINAL;
-               } else if (is_cmarker(buf.buf, '=', marker_size, 0)) {
+               } else if (is_cmarker(buf.buf, '=', marker_size)) {
                        if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
                                goto bad;
                        hunk = RR_SIDE_2;
-               } else if (is_cmarker(buf.buf, '>', marker_size, 1)) {
+               } else if (is_cmarker(buf.buf, '>', marker_size)) {
                        if (hunk != RR_SIDE_2)
                                goto bad;
                        if (strbuf_cmp(&one, &two) > 0)
@@ -229,6 +319,10 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz
        return hunk_no;
 }
 
+/*
+ * Scan the path for conflicts, do the "handle_path()" thing above, and
+ * return the number of conflict hunks found.
+ */
 static int handle_file(const char *path, unsigned char *sha1, const char *output)
 {
        int hunk_no = 0;
@@ -270,11 +364,18 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output
        return hunk_no;
 }
 
+/*
+ * Subclass of rerere_io that reads from an in-core buffer that is a
+ * strbuf
+ */
 struct rerere_io_mem {
        struct rerere_io io;
        struct strbuf input;
 };
 
+/*
+ * ... and its getline() method implementation
+ */
 static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
 {
        struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
@@ -313,24 +414,23 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
                return -1;
        pos = -pos - 1;
 
-       for (i = 0; i < 3; i++) {
+       while (pos < active_nr) {
                enum object_type type;
                unsigned long size;
-               int j;
 
-               if (active_nr <= pos)
-                       break;
                ce = active_cache[pos++];
                if (ce_namelen(ce) != len || memcmp(ce->name, path, len))
-                       continue;
-               j = ce_stage(ce) - 1;
-               mmfile[j].ptr = read_sha1_file(ce->sha1, &type, &size);
-               mmfile[j].size = size;
+                       break;
+               i = ce_stage(ce) - 1;
+               if (!mmfile[i].ptr) {
+                       mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
+                       mmfile[i].size = size;
+               }
        }
-       for (i = 0; i < 3; i++) {
+       for (i = 0; i < 3; i++)
                if (!mmfile[i].ptr && !mmfile[i].size)
                        mmfile[i].ptr = xstrdup("");
-       }
+
        /*
         * NEEDSWORK: handle conflicts from merges with
         * merge.renormalize set, too
@@ -350,6 +450,10 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
        strbuf_init(&io.input, 0);
        strbuf_attach(&io.input, result.ptr, result.size, result.size);
 
+       /*
+        * Grab the conflict ID and optionally write the original
+        * contents with conflict markers out.
+        */
        hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
        strbuf_release(&io.input);
        if (io.io.output)
@@ -357,6 +461,14 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
        return hunk_no;
 }
 
+/*
+ * Look at a cache entry at "i" and see if it is not conflicting,
+ * conflicting and we are willing to handle, or conflicting and
+ * we are unable to handle, and return the determination in *type.
+ * Return the cache index to be looked at next, by skipping the
+ * stages we have already looked at in this invocation of this
+ * function.
+ */
 static int check_one_conflict(int i, int *type)
 {
        const struct cache_entry *e = active_cache[i];
@@ -367,10 +479,8 @@ static int check_one_conflict(int i, int *type)
        }
 
        *type = PUNTED;
-       if (ce_stage(e) == 1) {
-               if (active_nr <= ++i)
-                       return i + 1;
-       }
+       while (ce_stage(active_cache[i]) == 1)
+               i++;
 
        /* Only handle regular files with both stages #2 and #3 */
        if (i + 1 < active_nr) {
@@ -390,6 +500,17 @@ static int check_one_conflict(int i, int *type)
        return i;
 }
 
+/*
+ * Scan the index and find paths that have conflicts that rerere can
+ * handle, i.e. the ones that has both stages #2 and #3.
+ *
+ * NEEDSWORK: we do not record or replay a previous "resolve by
+ * deletion" for a delete-modify conflict, as that is inherently risky
+ * without knowing what modification is being discarded.  The only
+ * safe case, i.e. both side doing the deletion and modification that
+ * are identical to the previous round, might want to be handled,
+ * though.
+ */
 static int find_conflict(struct string_list *conflict)
 {
        int i;
@@ -406,6 +527,21 @@ static int find_conflict(struct string_list *conflict)
        return 0;
 }
 
+/*
+ * The merge_rr list is meant to hold outstanding conflicted paths
+ * that rerere could handle.  Abuse the list by adding other types of
+ * entries to allow the caller to show "rerere remaining".
+ *
+ * - Conflicted paths that rerere does not handle are added
+ * - Conflicted paths that have been resolved are marked as such
+ *   by storing RERERE_RESOLVED to .util field (where conflict ID
+ *   is expected to be stored).
+ *
+ * Do *not* write MERGE_RR file out after calling this function.
+ *
+ * NEEDSWORK: we may want to fix the caller that implements "rerere
+ * remaining" to do this without abusing merge_rr.
+ */
 int rerere_remaining(struct string_list *merge_rr)
 {
        int i;
@@ -424,7 +560,7 @@ int rerere_remaining(struct string_list *merge_rr)
                        struct string_list_item *it;
                        it = string_list_lookup(merge_rr, (const char *)e->name);
                        if (it != NULL) {
-                               free(it->util);
+                               free_rerere_id(it);
                                it->util = RERERE_RESOLVED;
                        }
                }
@@ -432,39 +568,66 @@ int rerere_remaining(struct string_list *merge_rr)
        return 0;
 }
 
-static int merge(const char *name, const char *path)
+/*
+ * Find the conflict identified by "id"; the change between its
+ * "preimage" (i.e. a previous contents with conflict markers) and its
+ * "postimage" (i.e. the corresponding contents with conflicts
+ * resolved) may apply cleanly to the contents stored in "path", i.e.
+ * the conflict this time around.
+ *
+ * Returns 0 for successful replay of recorded resolution, or non-zero
+ * for failure.
+ */
+static int merge(const struct rerere_id *id, const char *path)
 {
+       FILE *f;
        int ret;
        mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0};
        mmbuffer_t result = {NULL, 0};
 
-       if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
-               return 1;
+       /*
+        * Normalize the conflicts in path and write it out to
+        * "thisimage" temporary file.
+        */
+       if (handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) {
+               ret = 1;
+               goto out;
+       }
 
-       if (read_mmfile(&cur, rerere_path(name, "thisimage")) ||
-                       read_mmfile(&base, rerere_path(name, "preimage")) ||
-                       read_mmfile(&other, rerere_path(name, "postimage"))) {
+       if (read_mmfile(&cur, rerere_path(id, "thisimage")) ||
+           read_mmfile(&base, rerere_path(id, "preimage")) ||
+           read_mmfile(&other, rerere_path(id, "postimage"))) {
                ret = 1;
                goto out;
        }
+
+       /*
+        * A three-way merge. Note that this honors user-customizable
+        * low-level merge driver settings.
+        */
        ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL);
-       if (!ret) {
-               FILE *f;
-
-               if (utime(rerere_path(name, "postimage"), NULL) < 0)
-                       warning("failed utime() on %s: %s",
-                                       rerere_path(name, "postimage"),
-                                       strerror(errno));
-               f = fopen(path, "w");
-               if (!f)
-                       return error("Could not open %s: %s", path,
-                                    strerror(errno));
-               if (fwrite(result.ptr, result.size, 1, f) != 1)
-                       error("Could not write %s: %s", path, strerror(errno));
-               if (fclose(f))
-                       return error("Writing %s failed: %s", path,
-                                    strerror(errno));
-       }
+       if (ret)
+               goto out;
+
+       /*
+        * A successful replay of recorded resolution.
+        * Mark that "postimage" was used to help gc.
+        */
+       if (utime(rerere_path(id, "postimage"), NULL) < 0)
+               warning("failed utime() on %s: %s",
+                       rerere_path(id, "postimage"),
+                       strerror(errno));
+
+       /* Update "path" with the resolution */
+       f = fopen(path, "w");
+       if (!f)
+               return error("Could not open %s: %s", path,
+                            strerror(errno));
+       if (fwrite(result.ptr, result.size, 1, f) != 1)
+               error("Could not write %s: %s", path, strerror(errno));
+       if (fclose(f))
+               return error("Writing %s failed: %s", path,
+                            strerror(errno));
 
 out:
        free(cur.ptr);
@@ -487,6 +650,8 @@ static void update_paths(struct string_list *update)
                struct string_list_item *item = &update->items[i];
                if (add_file_to_cache(item->string, 0))
                        exit(128);
+               fprintf(stderr, "Staged '%s' using previous resolution.\n",
+                       item->string);
        }
 
        if (active_cache_changed) {
@@ -496,6 +661,41 @@ static void update_paths(struct string_list *update)
                rollback_lock_file(&index_lock);
 }
 
+/*
+ * The path indicated by rr_item may still have conflict for which we
+ * have a recorded resolution, in which case replay it and optionally
+ * update it.  Or it may have been resolved by the user and we may
+ * only have the preimage for that conflict, in which case the result
+ * needs to be recorded as a resolution in a postimage file.
+ */
+static void do_rerere_one_path(struct string_list_item *rr_item,
+                              struct string_list *update)
+{
+       const char *path = rr_item->string;
+       const struct rerere_id *id = rr_item->util;
+
+       /* Is there a recorded resolution we could attempt to apply? */
+       if (has_rerere_resolution(id)) {
+               if (merge(id, path))
+                       return; /* failed to replay */
+
+               if (rerere_autoupdate)
+                       string_list_insert(update, path);
+               else
+                       fprintf(stderr,
+                               "Resolved '%s' using previous resolution.\n",
+                               path);
+       } else if (!handle_file(path, NULL, NULL)) {
+               /* The user has resolved it. */
+               copy_file(rerere_path(id, "postimage"), path, 0666);
+               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+       } else {
+               return;
+       }
+       free_rerere_id(rr_item);
+       rr_item->util = NULL;
+}
+
 static int do_plain_rerere(struct string_list *rr, int fd)
 {
        struct string_list conflict = STRING_LIST_INIT_DUP;
@@ -505,65 +705,55 @@ static int do_plain_rerere(struct string_list *rr, int fd)
        find_conflict(&conflict);
 
        /*
-        * MERGE_RR records paths with conflicts immediately after merge
-        * failed.  Some of the conflicted paths might have been hand resolved
-        * in the working tree since then, but the initial run would catch all
-        * and register their preimages.
+        * MERGE_RR records paths with conflicts immediately after
+        * merge failed.  Some of the conflicted paths might have been
+        * hand resolved in the working tree since then, but the
+        * initial run would catch all and register their preimages.
         */
-
        for (i = 0; i < conflict.nr; i++) {
+               struct rerere_id *id;
+               unsigned char sha1[20];
                const char *path = conflict.items[i].string;
-               if (!string_list_has_string(rr, path)) {
-                       unsigned char sha1[20];
-                       char *hex;
-                       int ret;
-                       ret = handle_file(path, sha1, NULL);
-                       if (ret < 1)
-                               continue;
-                       hex = xstrdup(sha1_to_hex(sha1));
-                       string_list_insert(rr, path)->util = hex;
-                       if (mkdir_in_gitdir(git_path("rr-cache/%s", hex)))
-                               continue;
-                       handle_file(path, NULL, rerere_path(hex, "preimage"));
-                       fprintf(stderr, "Recorded preimage for '%s'\n", path);
-               }
-       }
+               int ret;
 
-       /*
-        * Now some of the paths that had conflicts earlier might have been
-        * hand resolved.  Others may be similar to a conflict already that
-        * was resolved before.
-        */
+               if (string_list_has_string(rr, path))
+                       continue;
 
-       for (i = 0; i < rr->nr; i++) {
-               int ret;
-               const char *path = rr->items[i].string;
-               const char *name = (const char *)rr->items[i].util;
-
-               if (has_rerere_resolution(name)) {
-                       if (!merge(name, path)) {
-                               const char *msg;
-                               if (rerere_autoupdate) {
-                                       string_list_insert(&update, path);
-                                       msg = "Staged '%s' using previous resolution.\n";
-                               } else
-                                       msg = "Resolved '%s' using previous resolution.\n";
-                               fprintf(stderr, msg, path);
-                               goto mark_resolved;
-                       }
-               }
+               /*
+                * Ask handle_file() to scan and assign a
+                * conflict ID.  No need to write anything out
+                * yet.
+                */
+               ret = handle_file(path, sha1, NULL);
+               if (ret < 1)
+                       continue;
 
-               /* Let's see if we have resolved it. */
-               ret = handle_file(path, NULL, NULL);
-               if (ret)
+               id = new_rerere_id(sha1);
+               string_list_insert(rr, path)->util = id;
+
+               /*
+                * If the directory does not exist, create
+                * it.  mkdir_in_gitdir() will fail with
+                * EEXIST if there already is one.
+                *
+                * NEEDSWORK: make sure "gc" does not remove
+                * preimage without removing the directory.
+                */
+               if (mkdir_in_gitdir(rerere_path(id, NULL)))
                        continue;
 
-               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(rerere_path(name, "postimage"), path, 0666);
-       mark_resolved:
-               rr->items[i].util = NULL;
+               /*
+                * We are the first to encounter this
+                * conflict.  Ask handle_file() to write the
+                * normalized contents to the "preimage" file.
+                */
+               handle_file(path, NULL, rerere_path(id, "preimage"));
+               fprintf(stderr, "Recorded preimage for '%s'\n", path);
        }
 
+       for (i = 0; i < rr->nr; i++)
+               do_rerere_one_path(&rr->items[i], &update);
+
        if (update.nr)
                update_paths(&update);
 
@@ -614,6 +804,11 @@ int setup_rerere(struct string_list *merge_rr, int flags)
        return fd;
 }
 
+/*
+ * The main entry point that is called internally from codepaths that
+ * perform mergy operations, possibly leaving conflicted index entries
+ * and working tree files.
+ */
 int rerere(int flags)
 {
        struct string_list merge_rr = STRING_LIST_INIT_DUP;
@@ -628,25 +823,42 @@ int rerere(int flags)
 static int rerere_forget_one_path(const char *path, struct string_list *rr)
 {
        const char *filename;
-       char *hex;
+       struct rerere_id *id;
        unsigned char sha1[20];
        int ret;
+       struct string_list_item *item;
 
+       /*
+        * Recreate the original conflict from the stages in the
+        * index and compute the conflict ID
+        */
        ret = handle_cache(path, sha1, NULL);
        if (ret < 1)
                return error("Could not parse conflict hunks in '%s'", path);
-       hex = xstrdup(sha1_to_hex(sha1));
-       filename = rerere_path(hex, "postimage");
+
+       /* Nuke the recorded resolution for the conflict */
+       id = new_rerere_id(sha1);
+       filename = rerere_path(id, "postimage");
        if (unlink(filename))
                return (errno == ENOENT
                        ? error("no remembered resolution for %s", path)
                        : error("cannot unlink %s: %s", filename, strerror(errno)));
 
-       handle_cache(path, sha1, rerere_path(hex, "preimage"));
+       /*
+        * Update the preimage so that the user can resolve the
+        * conflict in the working tree, run us again to record
+        * the postimage.
+        */
+       handle_cache(path, sha1, rerere_path(id, "preimage"));
        fprintf(stderr, "Updated preimage for '%s'\n", path);
 
-
-       string_list_insert(rr, path)->util = hex;
+       /*
+        * And remember that we can record resolution for this
+        * conflict when the user is done.
+        */
+       item = string_list_insert(rr, path);
+       free_rerere_id(item);
+       item->util = id;
        fprintf(stderr, "Forgot resolution for %s\n", path);
        return 0;
 }
@@ -664,6 +876,11 @@ int rerere_forget(struct pathspec *pathspec)
        if (fd < 0)
                return 0;
 
+       /*
+        * The paths may have been resolved (incorrectly);
+        * recover the original conflicted state and then
+        * find the conflicted paths.
+        */
        unmerge_cache(pathspec);
        find_conflict(&conflict);
        for (i = 0; i < conflict.nr; i++) {
@@ -676,24 +893,51 @@ int rerere_forget(struct pathspec *pathspec)
        return write_rr(&merge_rr, fd);
 }
 
-static time_t rerere_created_at(const char *name)
+/*
+ * Garbage collection support
+ */
+
+/*
+ * Note that this is not reentrant but is used only one-at-a-time
+ * so it does not matter right now.
+ */
+static struct rerere_id *dirname_to_id(const char *name)
+{
+       static struct rerere_id id;
+       strcpy(id.hex, name);
+       return &id;
+}
+
+static time_t rerere_created_at(const char *dir_name)
 {
        struct stat st;
-       return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
+       struct rerere_id *id = dirname_to_id(dir_name);
+
+       return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
-static time_t rerere_last_used_at(const char *name)
+static time_t rerere_last_used_at(const char *dir_name)
 {
        struct stat st;
-       return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
+       struct rerere_id *id = dirname_to_id(dir_name);
+
+       return stat(rerere_path(id, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
-static void unlink_rr_item(const char *name)
+/*
+ * Remove the recorded resolution for a given conflict ID
+ */
+static void unlink_rr_item(struct rerere_id *id)
 {
-       unlink(rerere_path(name, "thisimage"));
-       unlink(rerere_path(name, "preimage"));
-       unlink(rerere_path(name, "postimage"));
-       rmdir(git_path("rr-cache/%s", name));
+       unlink(rerere_path(id, "thisimage"));
+       unlink(rerere_path(id, "preimage"));
+       unlink(rerere_path(id, "postimage"));
+       /*
+        * NEEDSWORK: what if this rmdir() fails?  Wouldn't we then
+        * assume that we already have preimage recorded in
+        * do_plain_rerere()?
+        */
+       rmdir(rerere_path(id, NULL));
 }
 
 void rerere_gc(struct string_list *rr)
@@ -715,6 +959,7 @@ void rerere_gc(struct string_list *rr)
        dir = opendir(git_path("rr-cache"));
        if (!dir)
                die_errno("unable to open rr-cache directory");
+       /* Collect stale conflict IDs ... */
        while ((e = readdir(dir))) {
                if (is_dot_or_dotdot(e->d_name))
                        continue;
@@ -732,12 +977,20 @@ void rerere_gc(struct string_list *rr)
                        string_list_append(&to_remove, e->d_name);
        }
        closedir(dir);
+       /* ... and then remove them one-by-one */
        for (i = 0; i < to_remove.nr; i++)
-               unlink_rr_item(to_remove.items[i].string);
+               unlink_rr_item(dirname_to_id(to_remove.items[i].string));
        string_list_clear(&to_remove, 0);
        rollback_lock_file(&write_lock);
 }
 
+/*
+ * During a conflict resolution, after "rerere" recorded the
+ * preimages, abandon them if the user did not resolve them or
+ * record their resolutions.  And drop $GIT_DIR/MERGE_RR.
+ *
+ * NEEDSWORK: shouldn't we be calling this from "reset --hard"?
+ */
 void rerere_clear(struct string_list *merge_rr)
 {
        int i;
@@ -746,9 +999,9 @@ void rerere_clear(struct string_list *merge_rr)
                return;
 
        for (i = 0; i < merge_rr->nr; i++) {
-               const char *name = (const char *)merge_rr->items[i].util;
-               if (!has_rerere_resolution(name))
-                       unlink_rr_item(name);
+               struct rerere_id *id = merge_rr->items[i].util;
+               if (!has_rerere_resolution(id))
+                       unlink_rr_item(id);
        }
        unlink_or_warn(git_path_merge_rr());
        rollback_lock_file(&write_lock);
index 407d59996d97ae307d5b4a124f78ef79666e1feb..1222e91921882b41ac9c1648960eca9305b27d8d 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -16,9 +16,19 @@ struct pathspec;
  */
 extern void *RERERE_RESOLVED;
 
+struct rerere_id {
+       char hex[41];
+};
+
 extern int setup_rerere(struct string_list *, int);
 extern int rerere(int);
-extern const char *rerere_path(const char *hex, const char *file);
+/*
+ * Given the conflict ID and the name of a "file" used for replaying
+ * the recorded resolution (e.g. "preimage", "postimage"), return the
+ * path to that filesystem entity.  With "file" specified with NULL,
+ * return the path to the directory that houses these files.
+ */
+extern const char *rerere_path(const struct rerere_id *, const char *file);
 extern int rerere_forget(struct pathspec *);
 extern int rerere_remaining(struct string_list *);
 extern void rerere_clear(struct string_list *);
index af2a18ed7485ea83170409ce55f81473ee606221..22364636d1456795dd858b8039da3b90d32c6633 100644 (file)
@@ -38,7 +38,7 @@ char *path_name(const struct name_path *path, const char *name)
        }
        n = xmalloc(len);
        m = n + len - (nlen + 1);
-       strcpy(m, name);
+       memcpy(m, name, nlen + 1);
        for (p = path; p; p = p->up) {
                if (p->elem_len) {
                        m -= p->elem_len + 1;
index 3277cf797ed41e5834b3b94fa8d2e9e9d5b4a317..e17e456cdaef63d3fa587b8e216bf1fe5c372317 100644 (file)
@@ -18,26 +18,27 @@ struct child_to_clean {
 static struct child_to_clean *children_to_clean;
 static int installed_child_cleanup_handler;
 
-static void cleanup_children(int sig)
+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;
                kill(p->pid, sig);
-               free(p);
+               if (!in_signal)
+                       free(p);
        }
 }
 
 static void cleanup_children_on_signal(int sig)
 {
-       cleanup_children(sig);
+       cleanup_children(sig, 1);
        sigchain_pop(sig);
        raise(sig);
 }
 
 static void cleanup_children_on_exit(void)
 {
-       cleanup_children(SIGTERM);
+       cleanup_children(SIGTERM, 0);
 }
 
 static void mark_child_for_cleanup(pid_t pid)
@@ -220,7 +221,7 @@ static inline void set_cloexec(int fd)
                fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
 }
 
-static int wait_or_whine(pid_t pid, const char *argv0)
+static int wait_or_whine(pid_t pid, const char *argv0, int in_signal)
 {
        int status, code = -1;
        pid_t waiting;
@@ -228,6 +229,8 @@ static int wait_or_whine(pid_t pid, const char *argv0)
 
        while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
                ;       /* nothing */
+       if (in_signal)
+               return 0;
 
        if (waiting < 0) {
                failed_errno = errno;
@@ -437,7 +440,7 @@ int start_command(struct child_process *cmd)
                 * At this point we know that fork() succeeded, but execvp()
                 * failed. Errors have been reported to our stderr.
                 */
-               wait_or_whine(cmd->pid, cmd->argv[0]);
+               wait_or_whine(cmd->pid, cmd->argv[0], 0);
                failed_errno = errno;
                cmd->pid = -1;
        }
@@ -536,12 +539,18 @@ int start_command(struct child_process *cmd)
 
 int finish_command(struct child_process *cmd)
 {
-       int ret = wait_or_whine(cmd->pid, cmd->argv[0]);
+       int ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);
        argv_array_clear(&cmd->args);
        argv_array_clear(&cmd->env_array);
        return ret;
 }
 
+int finish_command_in_signal(struct child_process *cmd)
+{
+       return wait_or_whine(cmd->pid, cmd->argv[0], 1);
+}
+
+
 int run_command(struct child_process *cmd)
 {
        int code;
@@ -595,7 +604,7 @@ static NORETURN void die_async(const char *err, va_list params)
 {
        vreportf("fatal: ", err, params);
 
-       if (!pthread_equal(main_thread, pthread_self())) {
+       if (in_async()) {
                struct async *async = pthread_getspecific(async_key);
                if (async->proc_in >= 0)
                        close(async->proc_in);
@@ -614,6 +623,13 @@ static int async_die_is_recursing(void)
        return ret != NULL;
 }
 
+int in_async(void)
+{
+       if (!main_thread_set)
+               return 0; /* no asyncs started yet */
+       return !pthread_equal(main_thread, pthread_self());
+}
+
 #else
 
 static struct {
@@ -653,6 +669,12 @@ int git_atexit(void (*handler)(void))
 }
 #define atexit git_atexit
 
+static int process_is_async;
+int in_async(void)
+{
+       return process_is_async;
+}
+
 #endif
 
 int start_async(struct async *async)
@@ -712,6 +734,7 @@ int start_async(struct async *async)
                if (need_out)
                        close(fdout[0]);
                git_atexit_clear();
+               process_is_async = 1;
                exit(!!async->proc(proc_in, proc_out, async->data));
        }
 
@@ -772,7 +795,7 @@ int start_async(struct async *async)
 int finish_async(struct async *async)
 {
 #ifdef NO_PTHREADS
-       return wait_or_whine(async->pid, "child process");
+       return wait_or_whine(async->pid, "child process", 0);
 #else
        void *ret = (void *)(intptr_t)(-1);
 
index 5b4425a3cbe1aea2bae40c4e060e45ee2d7a29a5..5428b048e2c49dec3e322ac2a46ffddd012e4c8d 100644 (file)
@@ -50,6 +50,7 @@ void child_process_init(struct child_process *);
 
 int start_command(struct child_process *);
 int finish_command(struct child_process *);
+int finish_command_in_signal(struct child_process *);
 int run_command(struct child_process *);
 
 /*
@@ -118,5 +119,6 @@ struct async {
 
 int start_async(struct async *async);
 int finish_async(struct async *async);
+int in_async(void);
 
 #endif
diff --git a/setup.c b/setup.c
index a17c51e61d75ac8280bf04d95c50d7bdfd6d7a0e..b2644716911c3ee9594ca1e72d5fb30900d9331e 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -99,10 +99,7 @@ char *prefix_path_gently(const char *prefix, int len,
                        return NULL;
                }
        } else {
-               sanitized = xmalloc(len + strlen(path) + 1);
-               if (len)
-                       memcpy(sanitized, prefix, len);
-               strcpy(sanitized + len, path);
+               sanitized = xstrfmt("%.*s%s", len, prefix, path);
                if (remaining_prefix)
                        *remaining_prefix = len;
                if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) {
@@ -228,15 +225,22 @@ void verify_non_filename(const char *prefix, const char *arg)
 }
 
 int get_common_dir(struct strbuf *sb, const char *gitdir)
+{
+       const char *git_env_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
+       if (git_env_common_dir) {
+               strbuf_addstr(sb, git_env_common_dir);
+               return 1;
+       } else {
+               return get_common_dir_noenv(sb, gitdir);
+       }
+}
+
+int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)
 {
        struct strbuf data = STRBUF_INIT;
        struct strbuf path = STRBUF_INIT;
-       const char *git_common_dir = getenv(GIT_COMMON_DIR_ENVIRONMENT);
        int ret = 0;
-       if (git_common_dir) {
-               strbuf_addstr(sb, git_common_dir);
-               return 1;
-       }
+
        strbuf_addf(&path, "%s/commondir", gitdir);
        if (file_exists(path.buf)) {
                if (strbuf_read_file(&data, path.buf, 0) <= 0)
@@ -468,11 +472,8 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
 
        if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) {
                size_t pathlen = slash+1 - path;
-               size_t dirlen = pathlen + len - 8;
-               dir = xmalloc(dirlen + 1);
-               strncpy(dir, path, pathlen);
-               strncpy(dir + pathlen, buf + 8, len - 8);
-               dir[dirlen] = '\0';
+               dir = xstrfmt("%.*s%.*s", (int)pathlen, path,
+                             (int)(len - 8), buf + 8);
                free(buf);
                buf = dir;
        }
index d295a3225a1e083708812f99746773d0ec8fdff4..50896ff1eb5ce3f216b31658b4a03d6e7fdc5adb 100644 (file)
@@ -208,44 +208,25 @@ const char *sha1_file_name(const unsigned char *sha1)
  * provided by the caller.  which should be "pack" or "idx".
  */
 static char *sha1_get_pack_name(const unsigned char *sha1,
-                               char **name, char **base, const char *which)
+                               struct strbuf *buf,
+                               const char *which)
 {
-       static const char hex[] = "0123456789abcdef";
-       char *buf;
-       int i;
-
-       if (!*base) {
-               const char *sha1_file_directory = get_object_directory();
-               int len = strlen(sha1_file_directory);
-               *base = xmalloc(len + 60);
-               sprintf(*base, "%s/pack/pack-1234567890123456789012345678901234567890.%s",
-                       sha1_file_directory, which);
-               *name = *base + len + 11;
-       }
-
-       buf = *name;
-
-       for (i = 0; i < 20; i++) {
-               unsigned int val = *sha1++;
-               *buf++ = hex[val >> 4];
-               *buf++ = hex[val & 0xf];
-       }
-
-       return *base;
+       strbuf_reset(buf);
+       strbuf_addf(buf, "%s/pack/pack-%s.%s", get_object_directory(),
+                   sha1_to_hex(sha1), which);
+       return buf->buf;
 }
 
 char *sha1_pack_name(const unsigned char *sha1)
 {
-       static char *name, *base;
-
-       return sha1_get_pack_name(sha1, &name, &base, "pack");
+       static struct strbuf buf = STRBUF_INIT;
+       return sha1_get_pack_name(sha1, &buf, "pack");
 }
 
 char *sha1_pack_index_name(const unsigned char *sha1)
 {
-       static char *name, *base;
-
-       return sha1_get_pack_name(sha1, &name, &base, "idx");
+       static struct strbuf buf = STRBUF_INIT;
+       return sha1_get_pack_name(sha1, &buf, "idx");
 }
 
 struct alternate_object_database *alt_odb_list;
@@ -671,13 +652,15 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
 int open_pack_index(struct packed_git *p)
 {
        char *idx_name;
+       size_t len;
        int ret;
 
        if (p->index_data)
                return 0;
 
-       idx_name = xstrdup(p->pack_name);
-       strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx");
+       if (!strip_suffix(p->pack_name, ".pack", &len))
+               die("BUG: pack_name does not end in .pack");
+       idx_name = xstrfmt("%.*s.idx", (int)len, p->pack_name);
        ret = check_packed_git_idx(idx_name, p);
        free(idx_name);
        return ret;
@@ -786,6 +769,37 @@ void close_pack_windows(struct packed_git *p)
        }
 }
 
+static int close_pack_fd(struct packed_git *p)
+{
+       if (p->pack_fd < 0)
+               return 0;
+
+       close(p->pack_fd);
+       pack_open_fds--;
+       p->pack_fd = -1;
+
+       return 1;
+}
+
+static void close_pack(struct packed_git *p)
+{
+       close_pack_windows(p);
+       close_pack_fd(p);
+       close_pack_index(p);
+}
+
+void close_all_packs(void)
+{
+       struct packed_git *p;
+
+       for (p = packed_git; p; p = p->next)
+               if (p->do_not_close)
+                       die("BUG! Want to close pack marked 'do-not-close'");
+               else
+                       close_pack(p);
+}
+
+
 /*
  * The LRU pack is the one with the oldest MRU window, preferring packs
  * with no used windows, or the oldest mtime if it has no windows allocated.
@@ -853,12 +867,8 @@ static int close_one_pack(void)
                find_lru_pack(p, &lru_p, &mru_w, &accept_windows_inuse);
        }
 
-       if (lru_p) {
-               close(lru_p->pack_fd);
-               pack_open_fds--;
-               lru_p->pack_fd = -1;
-               return 1;
-       }
+       if (lru_p)
+               return close_pack_fd(lru_p);
 
        return 0;
 }
@@ -898,12 +908,7 @@ void free_pack_by_name(const char *pack_name)
                p = *pp;
                if (strcmp(pack_name, p->pack_name) == 0) {
                        clear_delta_base_cache();
-                       close_pack_windows(p);
-                       if (p->pack_fd != -1) {
-                               close(p->pack_fd);
-                               pack_open_fds--;
-                       }
-                       close_pack_index(p);
+                       close_pack(p);
                        free(p->bad_object_sha1);
                        *pp = p->next;
                        if (last_found_pack == p)
@@ -1037,11 +1042,7 @@ static int open_packed_git(struct packed_git *p)
 {
        if (!open_packed_git_1(p))
                return 0;
-       if (p->pack_fd != -1) {
-               close(p->pack_fd);
-               pack_open_fds--;
-               p->pack_fd = -1;
-       }
+       close_pack_fd(p);
        return -1;
 }
 
@@ -1107,11 +1108,8 @@ unsigned char *use_pack(struct packed_git *p,
                                        p->pack_name,
                                        strerror(errno));
                        if (!win->offset && win->len == p->pack_size
-                               && !p->do_not_close) {
-                               close(p->pack_fd);
-                               pack_open_fds--;
-                               p->pack_fd = -1;
-                       }
+                               && !p->do_not_close)
+                               close_pack_fd(p);
                        pack_mmap_calls++;
                        pack_open_windows++;
                        if (pack_mapped > peak_pack_mapped)
@@ -1146,11 +1144,12 @@ static void try_to_free_pack_memory(size_t size)
        release_pack_memory(size);
 }
 
-struct packed_git *add_packed_git(const char *path, int path_len, int local)
+struct packed_git *add_packed_git(const char *path, size_t path_len, int local)
 {
        static int have_set_try_to_free_routine;
        struct stat st;
-       struct packed_git *p = alloc_packed_git(path_len + 2);
+       size_t alloc;
+       struct packed_git *p;
 
        if (!have_set_try_to_free_routine) {
                have_set_try_to_free_routine = 1;
@@ -1161,18 +1160,22 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local)
         * Make sure a corresponding .pack file exists and that
         * the index looks sane.
         */
-       path_len -= strlen(".idx");
-       if (path_len < 1) {
-               free(p);
+       if (!strip_suffix_mem(path, &path_len, ".idx"))
                return NULL;
-       }
+
+       /*
+        * ".pack" is long enough to hold any suffix we're adding (and
+        * the use xsnprintf double-checks that)
+        */
+       alloc = path_len + strlen(".pack") + 1;
+       p = alloc_packed_git(alloc);
        memcpy(p->pack_name, path, path_len);
 
-       strcpy(p->pack_name + path_len, ".keep");
+       xsnprintf(p->pack_name + path_len, alloc - path_len, ".keep");
        if (!access(p->pack_name, F_OK))
                p->pack_keep = 1;
 
-       strcpy(p->pack_name + path_len, ".pack");
+       xsnprintf(p->pack_name + path_len, alloc - path_len, ".pack");
        if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) {
                free(p);
                return NULL;
@@ -1192,9 +1195,10 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local)
 struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path)
 {
        const char *path = sha1_pack_name(sha1);
-       struct packed_git *p = alloc_packed_git(strlen(path) + 1);
+       int alloc = strlen(path) + 1;
+       struct packed_git *p = alloc_packed_git(alloc);
 
-       strcpy(p->pack_name, path);
+       memcpy(p->pack_name, path, alloc); /* includes NUL */
        hashcpy(p->sha1, sha1);
        if (check_packed_git_idx(idx_path, p)) {
                free(p);
@@ -1464,7 +1468,7 @@ int check_sha1_signature(const unsigned char *sha1, void *map,
                return -1;
 
        /* Generate the header */
-       hdrlen = sprintf(hdr, "%s %lu", typename(obj_type), size) + 1;
+       hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(obj_type), size) + 1;
 
        /* Sha1.. */
        git_SHA1_Init(&c);
@@ -2930,7 +2934,7 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len,
        git_SHA_CTX c;
 
        /* Generate the header */
-       *hdrlen = sprintf(hdr, "%s %lu", type, len)+1;
+       *hdrlen = xsnprintf(hdr, *hdrlen, "%s %lu", type, len)+1;
 
        /* Sha1.. */
        git_SHA1_Init(&c);
@@ -2993,7 +2997,7 @@ int hash_sha1_file(const void *buf, unsigned long len, const char *type,
                    unsigned char *sha1)
 {
        char hdr[32];
-       int hdrlen;
+       int hdrlen = sizeof(hdr);
        write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
        return 0;
 }
@@ -3023,29 +3027,31 @@ static inline int directory_size(const char *filename)
  * We want to avoid cross-directory filename renames, because those
  * can have problems on various filesystems (FAT, NFS, Coda).
  */
-static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename)
+static int create_tmpfile(struct strbuf *tmp, const char *filename)
 {
        int fd, dirlen = directory_size(filename);
 
-       if (dirlen + 20 > bufsiz) {
-               errno = ENAMETOOLONG;
-               return -1;
-       }
-       memcpy(buffer, filename, dirlen);
-       strcpy(buffer + dirlen, "tmp_obj_XXXXXX");
-       fd = git_mkstemp_mode(buffer, 0444);
+       strbuf_reset(tmp);
+       strbuf_add(tmp, filename, dirlen);
+       strbuf_addstr(tmp, "tmp_obj_XXXXXX");
+       fd = git_mkstemp_mode(tmp->buf, 0444);
        if (fd < 0 && dirlen && errno == ENOENT) {
-               /* Make sure the directory exists */
-               memcpy(buffer, filename, dirlen);
-               buffer[dirlen-1] = 0;
-               if (mkdir(buffer, 0777) && errno != EEXIST)
+               /*
+                * Make sure the directory exists; note that the contents
+                * of the buffer are undefined after mkstemp returns an
+                * error, so we have to rewrite the whole buffer from
+                * scratch.
+                */
+               strbuf_reset(tmp);
+               strbuf_add(tmp, filename, dirlen - 1);
+               if (mkdir(tmp->buf, 0777) && errno != EEXIST)
                        return -1;
-               if (adjust_shared_perm(buffer))
+               if (adjust_shared_perm(tmp->buf))
                        return -1;
 
                /* Try again */
-               strcpy(buffer + dirlen - 1, "/tmp_obj_XXXXXX");
-               fd = git_mkstemp_mode(buffer, 0444);
+               strbuf_addstr(tmp, "/tmp_obj_XXXXXX");
+               fd = git_mkstemp_mode(tmp->buf, 0444);
        }
        return fd;
 }
@@ -3058,10 +3064,10 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
        git_zstream stream;
        git_SHA_CTX c;
        unsigned char parano_sha1[20];
-       static char tmp_file[PATH_MAX];
+       static struct strbuf tmp_file = STRBUF_INIT;
        const char *filename = sha1_file_name(sha1);
 
-       fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
+       fd = create_tmpfile(&tmp_file, filename);
        if (fd < 0) {
                if (errno == EACCES)
                        return error("insufficient permission for adding an object to repository database %s", get_object_directory());
@@ -3110,12 +3116,12 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
                struct utimbuf utb;
                utb.actime = mtime;
                utb.modtime = mtime;
-               if (utime(tmp_file, &utb) < 0)
+               if (utime(tmp_file.buf, &utb) < 0)
                        warning("failed utime() on %s: %s",
-                               tmp_file, strerror(errno));
+                               tmp_file.buf, strerror(errno));
        }
 
-       return finalize_object_file(tmp_file, filename);
+       return finalize_object_file(tmp_file.buf, filename);
 }
 
 static int freshen_loose_object(const unsigned char *sha1)
@@ -3139,7 +3145,7 @@ static int freshen_packed_object(const unsigned char *sha1)
 int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1)
 {
        char hdr[32];
-       int hdrlen;
+       int hdrlen = sizeof(hdr);
 
        /* Normally if we have it in the pack then we do not bother writing
         * it out into .git/objects/??/?{38} file.
@@ -3157,7 +3163,8 @@ int hash_sha1_file_literally(const void *buf, unsigned long len, const char *typ
        int hdrlen, status = 0;
 
        /* type string, SP, %lu of the length plus NUL must fit this */
-       header = xmalloc(strlen(type) + 32);
+       hdrlen = strlen(type) + 32;
+       header = xmalloc(hdrlen);
        write_sha1_file_prepare(buf, len, type, sha1, header, &hdrlen);
 
        if (!(flags & HASH_WRITE_OBJECT))
@@ -3185,7 +3192,7 @@ int force_object_loose(const unsigned char *sha1, time_t mtime)
        buf = read_packed_sha1(sha1, &type, &len);
        if (!buf)
                return error("cannot read sha1_file for %s", sha1_to_hex(sha1));
-       hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
+       hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), len) + 1;
        ret = write_loose_object(sha1, hdr, hdrlen, buf, len, mtime);
        free(buf);
 
index da6874c15ec51964b6044ae96fc88c282decb0cc..3242c5ea462ce246bbc1b90c4aeb2183b2bcbecb 100644 (file)
@@ -96,11 +96,15 @@ static void find_short_object_filename(int len, const char *hex_pfx, struct disa
        }
        fakeent->next = alt_odb_list;
 
-       sprintf(hex, "%.2s", hex_pfx);
+       xsnprintf(hex, sizeof(hex), "%.2s", hex_pfx);
        for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) {
                struct dirent *de;
                DIR *dir;
-               sprintf(alt->name, "%.2s/", hex_pfx);
+               /*
+                * every alt_odb struct has 42 extra bytes after the base
+                * for exactly this purpose
+                */
+               xsnprintf(alt->name, 42, "%.2s/", hex_pfx);
                dir = opendir(alt->base);
                if (!dir)
                        continue;
@@ -368,14 +372,13 @@ int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data)
        return ds.ambiguous;
 }
 
-const char *find_unique_abbrev(const unsigned char *sha1, int len)
+int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len)
 {
        int status, exists;
-       static char hex[41];
 
-       memcpy(hex, sha1_to_hex(sha1), 40);
+       sha1_to_hex_r(hex, sha1);
        if (len == 40 || !len)
-               return hex;
+               return 40;
        exists = has_sha1_file(sha1);
        while (len < 40) {
                unsigned char sha1_ret[20];
@@ -384,10 +387,17 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len)
                    ? !status
                    : status == SHORT_NAME_NOT_FOUND) {
                        hex[len] = 0;
-                       return hex;
+                       return len;
                }
                len++;
        }
+       return len;
+}
+
+const char *find_unique_abbrev(const unsigned char *sha1, int len)
+{
+       static char hex[GIT_SHA1_HEXSZ + 1];
+       find_unique_abbrev_r(hex, sha1, len);
        return hex;
 }
 
@@ -1283,8 +1293,7 @@ static void diagnose_invalid_index_path(int stage,
        const struct cache_entry *ce;
        int pos;
        unsigned namelen = strlen(filename);
-       unsigned fullnamelen;
-       char *fullname;
+       struct strbuf fullname = STRBUF_INIT;
 
        if (!prefix)
                prefix = "";
@@ -1304,21 +1313,19 @@ static void diagnose_invalid_index_path(int stage,
        }
 
        /* Confusion between relative and absolute filenames? */
-       fullnamelen = namelen + strlen(prefix);
-       fullname = xmalloc(fullnamelen + 1);
-       strcpy(fullname, prefix);
-       strcat(fullname, filename);
-       pos = cache_name_pos(fullname, fullnamelen);
+       strbuf_addstr(&fullname, prefix);
+       strbuf_addstr(&fullname, filename);
+       pos = cache_name_pos(fullname.buf, fullname.len);
        if (pos < 0)
                pos = -pos - 1;
        if (pos < active_nr) {
                ce = active_cache[pos];
-               if (ce_namelen(ce) == fullnamelen &&
-                   !memcmp(ce->name, fullname, fullnamelen))
+               if (ce_namelen(ce) == fullname.len &&
+                   !memcmp(ce->name, fullname.buf, fullname.len))
                        die("Path '%s' is in the index, but not '%s'.\n"
                            "Did you mean ':%d:%s' aka ':%d:./%s'?",
-                           fullname, filename,
-                           ce_stage(ce), fullname,
+                           fullname.buf, filename,
+                           ce_stage(ce), fullname.buf,
                            ce_stage(ce), filename);
        }
 
@@ -1328,7 +1335,7 @@ static void diagnose_invalid_index_path(int stage,
                die("Path '%s' does not exist (neither on disk nor in the index).",
                    filename);
 
-       free(fullname);
+       strbuf_release(&fullname);
 }
 
 
index 7f9dc229fbc82d005b5c2b417c2af2fc9f6b497a..fde8adc000f3167bba7fdf2d7cb3a4e7db612af4 100644 (file)
@@ -137,11 +137,11 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet
                if (packet_max - 5 < n)
                        n = packet_max - 5;
                if (0 <= band) {
-                       sprintf(hdr, "%04x", n + 5);
+                       xsnprintf(hdr, sizeof(hdr), "%04x", n + 5);
                        hdr[4] = band;
                        write_or_die(fd, hdr, 5);
                } else {
-                       sprintf(hdr, "%04x", n + 4);
+                       xsnprintf(hdr, sizeof(hdr), "%04x", n + 4);
                        write_or_die(fd, hdr, 4);
                }
                write_or_die(fd, p, n);
index 29df55b1b0f36522be7e141e9f29ba2bfcc978c1..107c45d29111a011a04daaa8fd0323ed9d973398 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -245,8 +245,8 @@ void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size
        static char prefix2[2];
 
        if (prefix1[0] != comment_line_char) {
-               sprintf(prefix1, "%c ", comment_line_char);
-               sprintf(prefix2, "%c", comment_line_char);
+               xsnprintf(prefix1, sizeof(prefix1), "%c ", comment_line_char);
+               xsnprintf(prefix2, sizeof(prefix2), "%c", comment_line_char);
        }
        add_lines(out, prefix1, prefix2, buf, size);
 }
@@ -743,3 +743,12 @@ void strbuf_addftime(struct strbuf *sb, const char *fmt, const struct tm *tm)
        }
        strbuf_setlen(sb, sb->len + len);
 }
+
+void strbuf_add_unique_abbrev(struct strbuf *sb, const unsigned char *sha1,
+                             int abbrev_len)
+{
+       int r;
+       strbuf_grow(sb, GIT_SHA1_HEXSZ + 1);
+       r = find_unique_abbrev_r(sb->buf + sb->len, sha1, abbrev_len);
+       strbuf_setlen(sb, sb->len + r);
+}
index aef2794651985b5c4177690f69a1ff0aac612d18..0f9c8a72ba7cdb7adb34c96a8e04db776c18d743 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -474,6 +474,14 @@ static inline struct strbuf **strbuf_split(const struct strbuf *sb,
  */
 extern void strbuf_list_free(struct strbuf **);
 
+/**
+ * Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to
+ * the strbuf `sb`.
+ */
+extern void strbuf_add_unique_abbrev(struct strbuf *sb,
+                                    const unsigned char *sha1,
+                                    int abbrev_len);
+
 /**
  * Launch the user preferred editor to edit a file and fill the buffer
  * with the file's contents upon the user completing their editing. The
@@ -491,10 +499,21 @@ extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *
  */
 extern void strbuf_addstr_xml_quoted(struct strbuf *sb, const char *s);
 
+/**
+ * "Complete" the contents of `sb` by ensuring that either it ends with the
+ * character `term`, or it is empty.  This can be used, for example,
+ * to ensure that text ends with a newline, but without creating an empty
+ * blank line if there is no content in the first place.
+ */
+static inline void strbuf_complete(struct strbuf *sb, char term)
+{
+       if (sb->len && sb->buf[sb->len - 1] != term)
+               strbuf_addch(sb, term);
+}
+
 static inline void strbuf_complete_line(struct strbuf *sb)
 {
-       if (sb->len && sb->buf[sb->len - 1] != '\n')
-               strbuf_addch(sb, '\n');
+       strbuf_complete(sb, '\n');
 }
 
 extern int strbuf_branchname(struct strbuf *sb, const char *name);
index 393de5357eb486d8fb9ee3be1d75c4b80771bd02..afe0ea81565a5675bcc3eff3199059240bbf95ee 100644 (file)
@@ -257,78 +257,62 @@ static int parse_config(const char *var, const char *value, void *data)
        if (!name_and_item_from_var(var, &name, &item))
                return 0;
 
-       submodule = lookup_or_create_by_name(me->cache, me->gitmodules_sha1,
-                       name.buf);
+       submodule = lookup_or_create_by_name(me->cache,
+                                            me->gitmodules_sha1,
+                                            name.buf);
 
        if (!strcmp(item.buf, "path")) {
-               struct strbuf path = STRBUF_INIT;
-               if (!value) {
+               if (!value)
                        ret = config_error_nonbool(var);
-                       goto release_return;
-               }
-               if (!me->overwrite && submodule->path != NULL) {
+               else if (!me->overwrite && submodule->path != NULL)
                        warn_multiple_config(me->commit_sha1, submodule->name,
                                        "path");
-                       goto release_return;
+               else {
+                       if (submodule->path)
+                               cache_remove_path(me->cache, submodule);
+                       free((void *) submodule->path);
+                       submodule->path = xstrdup(value);
+                       cache_put_path(me->cache, submodule);
                }
-
-               if (submodule->path)
-                       cache_remove_path(me->cache, submodule);
-               free((void *) submodule->path);
-               strbuf_addstr(&path, value);
-               submodule->path = strbuf_detach(&path, NULL);
-               cache_put_path(me->cache, submodule);
        } else if (!strcmp(item.buf, "fetchrecursesubmodules")) {
                /* when parsing worktree configurations we can die early */
                int die_on_error = is_null_sha1(me->gitmodules_sha1);
                if (!me->overwrite &&
-                   submodule->fetch_recurse != RECURSE_SUBMODULES_NONE) {
+                   submodule->fetch_recurse != RECURSE_SUBMODULES_NONE)
                        warn_multiple_config(me->commit_sha1, submodule->name,
                                        "fetchrecursesubmodules");
-                       goto release_return;
-               }
-
-               submodule->fetch_recurse = parse_fetch_recurse(var, value,
+               else
+                       submodule->fetch_recurse = parse_fetch_recurse(
+                                                               var, value,
                                                                die_on_error);
        } else if (!strcmp(item.buf, "ignore")) {
-               struct strbuf ignore = STRBUF_INIT;
-               if (!me->overwrite && submodule->ignore != NULL) {
+               if (!value)
+                       ret = config_error_nonbool(var);
+               else if (!me->overwrite && submodule->ignore != NULL)
                        warn_multiple_config(me->commit_sha1, submodule->name,
                                        "ignore");
-                       goto release_return;
-               }
-               if (!value) {
-                       ret = config_error_nonbool(var);
-                       goto release_return;
-               }
-               if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
-                   strcmp(value, "all") && strcmp(value, "none")) {
+               else if (strcmp(value, "untracked") &&
+                        strcmp(value, "dirty") &&
+                        strcmp(value, "all") &&
+                        strcmp(value, "none"))
                        warning("Invalid parameter '%s' for config option "
                                        "'submodule.%s.ignore'", value, var);
-                       goto release_return;
+               else {
+                       free((void *) submodule->ignore);
+                       submodule->ignore = xstrdup(value);
                }
-
-               free((void *) submodule->ignore);
-               strbuf_addstr(&ignore, value);
-               submodule->ignore = strbuf_detach(&ignore, NULL);
        } else if (!strcmp(item.buf, "url")) {
-               struct strbuf url = STRBUF_INIT;
                if (!value) {
                        ret = config_error_nonbool(var);
-                       goto release_return;
-               }
-               if (!me->overwrite && submodule->url != NULL) {
+               } else if (!me->overwrite && submodule->url != NULL) {
                        warn_multiple_config(me->commit_sha1, submodule->name,
                                        "url");
-                       goto release_return;
+               } else {
+                       free((void *) submodule->url);
+                       submodule->url = xstrdup(value);
                }
-
-               free((void *) submodule->url);
-               strbuf_addstr(&url, value);
-               submodule->url = strbuf_detach(&url, NULL);
        }
 
-release_return:
        strbuf_release(&name);
        strbuf_release(&item);
 
index 245ed4dfbb6a3d54adbd41d5de0ea64cf38dc8a3..5879cfb158f0b581a54a56de5de44af5c2d0a700 100644 (file)
@@ -122,15 +122,9 @@ static int add_submodule_odb(const char *path)
        struct strbuf objects_directory = STRBUF_INIT;
        struct alternate_object_database *alt_odb;
        int ret = 0;
-       const char *git_dir;
+       int alloc;
 
-       strbuf_addf(&objects_directory, "%s/.git", path);
-       git_dir = read_gitfile(objects_directory.buf);
-       if (git_dir) {
-               strbuf_reset(&objects_directory);
-               strbuf_addstr(&objects_directory, git_dir);
-       }
-       strbuf_addstr(&objects_directory, "/objects/");
+       strbuf_git_path_submodule(&objects_directory, path, "objects/");
        if (!is_directory(objects_directory.buf)) {
                ret = -1;
                goto done;
@@ -142,9 +136,10 @@ static int add_submodule_odb(const char *path)
                                        objects_directory.len))
                        goto done;
 
-       alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb));
+       alloc = objects_directory.len + 42; /* for "12/345..." sha1 */
+       alt_odb = xmalloc(sizeof(*alt_odb) + alloc);
        alt_odb->next = alt_odb_list;
-       strcpy(alt_odb->base, objects_directory.buf);
+       xsnprintf(alt_odb->base, alloc, "%s", objects_directory.buf);
        alt_odb->name = alt_odb->base + objects_directory.len;
        alt_odb->name[2] = '/';
        alt_odb->name[40] = '\0';
index f5c01758ca38b58fc10e30cfac81be5becb0316b..b1673b3e8f38d9961b8b2783e1f5c47a865638fb 100644 (file)
@@ -111,6 +111,10 @@ test_expect_success 'blame 2 authors + 2 merged-in authors' '
        check_count A 2 B 1 B1 2 B2 1
 '
 
+test_expect_success 'blame --first-parent blames merge for branch1' '
+       check_count --first-parent A 2 B 1 "A U Thor" 2 B2 1
+'
+
 test_expect_success 'blame ancestor' '
        check_count -h master A 2 B 2
 '
index 15f7fc1b8015114883bc36d1e35dffd730721e52..924b19dab4122a36112bd908a6252e754ecc531a 100755 (executable)
@@ -1,5 +1,6 @@
 #!/usr/bin/perl
 
+use lib '../../perl/blib/lib';
 use strict;
 use warnings;
 use Git;
index 7de8d85ee85e2387463e8d98175c9d8f66ca8959..f91bbcfc853a47e0a314160b79fc4816178f1723 100755 (executable)
@@ -202,8 +202,8 @@ test_expect_success 'init honors global core.sharedRepository' '
        x$(git config -f shared-honor-global/.git/config core.sharedRepository)
 '
 
-test_expect_success 'init rejects insanely long --template' '
-       test_must_fail git init --template=$(printf "x%09999dx" 1) test
+test_expect_success 'init allows insanely long --template' '
+       git init --template=$(printf "x%09999dx" 1) test
 '
 
 test_expect_success 'init creates a new directory' '
index 9393322c3e7028ea7074d2b1ec50fbb293c6728f..9670e8cbe6cb9a9faa3519b0f11dc16713496188 100755 (executable)
@@ -116,4 +116,46 @@ test_expect_success 'setup_git_dir twice in subdir' '
        )
 '
 
+test_expect_success 'enter_repo non-strict mode' '
+       test_create_repo enter_repo &&
+       (
+               cd enter_repo &&
+               test_tick &&
+               test_commit foo &&
+               mv .git .realgit &&
+               echo "gitdir: .realgit" >.git
+       ) &&
+       git ls-remote enter_repo >actual &&
+       cat >expected <<-\EOF &&
+       946e985ab20de757ca5b872b16d64e92ff3803a9        HEAD
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/heads/master
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/tags/foo
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'enter_repo linked checkout' '
+       (
+               cd enter_repo &&
+               git worktree add  ../foo refs/tags/foo
+       ) &&
+       git ls-remote foo >actual &&
+       cat >expected <<-\EOF &&
+       946e985ab20de757ca5b872b16d64e92ff3803a9        HEAD
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/heads/master
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/tags/foo
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'enter_repo strict mode' '
+       git ls-remote --upload-pack="git upload-pack --strict" foo/.git >actual &&
+       cat >expected <<-\EOF &&
+       946e985ab20de757ca5b872b16d64e92ff3803a9        HEAD
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/heads/master
+       946e985ab20de757ca5b872b16d64e92ff3803a9        refs/tags/foo
+       EOF
+       test_cmp expected actual
+'
+
 test_done
index 93605f42f27cef9ef3ffe381c84aea9f6f4be426..627ef854d5c804c0248a728862fdf9d4ea344568 100755 (executable)
@@ -266,15 +266,21 @@ test_expect_success 'setup common repository' 'git --git-dir=bar init'
 test_git_path GIT_COMMON_DIR=bar index                    .git/index
 test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo     .git/logs/refs/bisect/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo      bar/logs/refs/bisec/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisec          bar/logs/refs/bisec
+test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo      bar/logs/refs/bisectfoo
 test_git_path GIT_COMMON_DIR=bar objects                  bar/objects
 test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
 test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
 test_git_path GIT_COMMON_DIR=bar info/grafts              bar/info/grafts
 test_git_path GIT_COMMON_DIR=bar info/sparse-checkout     .git/info/sparse-checkout
+test_git_path GIT_COMMON_DIR=bar info//sparse-checkout    .git/info//sparse-checkout
 test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
 test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
 test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
 test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/bisect/foo          .git/refs/bisect/foo
 test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me
 test_git_path GIT_COMMON_DIR=bar config                   bar/config
 test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs
index 97406fa4b137afdd99e64172d956718005844120..af1b20dd5c6763e58eccd3f93722987f5f9ce233 100755 (executable)
@@ -1130,4 +1130,23 @@ test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches
 )
 '
 
+test_expect_success 'handle per-worktree refs in refs/bisect' '
+       git commit --allow-empty -m "initial commit" &&
+       git worktree add -b branch worktree &&
+       (
+               cd worktree &&
+               git commit --allow-empty -m "test commit"  &&
+               git for-each-ref >for-each-ref.out &&
+               ! grep refs/bisect for-each-ref.out &&
+               git update-ref refs/bisect/something HEAD &&
+               git rev-parse refs/bisect/something >../worktree-head &&
+               git for-each-ref | grep refs/bisect/something
+       ) &&
+       test_path_is_missing .git/refs/bisect &&
+       test_must_fail git rev-parse refs/bisect/something &&
+       git update-ref refs/bisect/something HEAD &&
+       git rev-parse refs/bisect/something >main-head &&
+       ! test_cmp main-head worktree-head
+'
+
 test_done
index 36378b0e3f5b271388fd0935149eea406caea0d0..20b022ae3397552fd3cd4e42553f4cd3575049f5 100755 (executable)
@@ -63,4 +63,33 @@ test_expect_success 'symbolic-ref fails to delete real ref' '
 '
 reset_to_sane
 
+test_expect_success 'create large ref name' '
+       # make 256+ character ref; some systems may not handle that,
+       # so be gentle
+       long=0123456789abcdef &&
+       long=$long/$long/$long/$long &&
+       long=$long/$long/$long/$long &&
+       long_ref=refs/heads/$long &&
+       tree=$(git write-tree) &&
+       commit=$(echo foo | git commit-tree $tree) &&
+       if git update-ref $long_ref $commit; then
+               test_set_prereq LONG_REF
+       else
+               echo >&2 "long refs not supported"
+       fi
+'
+
+test_expect_success LONG_REF 'symbolic-ref can point to large ref name' '
+       git symbolic-ref HEAD $long_ref &&
+       echo $long_ref >expect &&
+       git symbolic-ref HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success LONG_REF 'we can parse long symbolic ref' '
+       echo $commit >expect &&
+       git rev-parse --verify HEAD >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 16d0b8bd1a5b6b6a2701fea1c4f35858cec9528b..c465abe8e34936db135c136a6831975b2763e623 100755 (executable)
@@ -38,18 +38,20 @@ test_expect_success 'fast-import: fail on invalid branch name "bad[branch]name"'
        test_must_fail git fast-import <input
 '
 
-test_expect_success 'git branch shows badly named ref' '
+test_expect_success 'git branch shows badly named ref as warning' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
-       git branch >output &&
-       grep -e "broken\.\.\.ref" output
+       git branch >output 2>error &&
+       grep -e "broken\.\.\.ref" error &&
+       ! grep -e "broken\.\.\.ref" output
 '
 
 test_expect_success 'branch -d can delete badly named ref' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git branch -d broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -57,7 +59,8 @@ test_expect_success 'branch -D can delete badly named ref' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git branch -D broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -85,7 +88,8 @@ test_expect_success 'branch -D cannot delete absolute path' '
 test_expect_success 'git branch cannot create a badly named ref' '
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        test_must_fail git branch broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -95,7 +99,8 @@ test_expect_success 'branch -m cannot rename to a bad ref name' '
        git branch goodref &&
        test_must_fail git branch -m goodref broken...ref &&
        test_cmp_rev master goodref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -104,14 +109,16 @@ test_expect_failure 'branch -m can rename from a bad ref name' '
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git branch -m broken...ref renamed &&
        test_cmp_rev master renamed &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
 test_expect_success 'push cannot create a badly named ref' '
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        test_must_fail git push "file://$(pwd)" HEAD:refs/heads/broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -131,7 +138,8 @@ test_expect_failure 'push --mirror can delete badly named ref' '
                cp .git/refs/heads/master .git/refs/heads/broken...ref
        ) &&
        git -C src push --mirror "file://$top/dest" &&
-       git -C dest branch >output &&
+       git -C dest branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -159,7 +167,8 @@ test_expect_success 'update-ref -d can delete broken name' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git update-ref -d refs/heads/broken...ref &&
-       git branch >output &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
index 956673b8a14d39ef6bece7629b2407b0c10a071a..dc09797021a6a5fc8eed6da4412164673ab484a1 100755 (executable)
@@ -77,11 +77,31 @@ test_expect_success 'object with bad sha1' '
 test_expect_success 'branch pointing to non-commit' '
        git rev-parse HEAD^{tree} >.git/refs/heads/invalid &&
        test_when_finished "git update-ref -d refs/heads/invalid" &&
-       git fsck 2>out &&
+       test_must_fail git fsck 2>out &&
        cat out &&
        grep "not a commit" out
 '
 
+test_expect_success 'HEAD link pointing at a funny object' '
+       test_when_finished "mv .git/SAVED_HEAD .git/HEAD" &&
+       mv .git/HEAD .git/SAVED_HEAD &&
+       echo 0000000000000000000000000000000000000000 >.git/HEAD &&
+       # avoid corrupt/broken HEAD from interfering with repo discovery
+       test_must_fail env GIT_DIR=.git git fsck 2>out &&
+       cat out &&
+       grep "detached HEAD points" out
+'
+
+test_expect_success 'HEAD link pointing at a funny place' '
+       test_when_finished "mv .git/SAVED_HEAD .git/HEAD" &&
+       mv .git/HEAD .git/SAVED_HEAD &&
+       echo "ref: refs/funny/place" >.git/HEAD &&
+       # avoid corrupt/broken HEAD from interfering with repo discovery
+       test_must_fail env GIT_DIR=.git git fsck 2>out &&
+       cat out &&
+       grep "HEAD points to something strange" out
+'
+
 test_expect_success 'email without @ is okay' '
        git cat-file commit HEAD >basis &&
        sed "s/@/AT/" basis >okay &&
index 8267411a0ec129a67cde5381cfd79824471d8fd2..369417498946fcebb901d8593bb584f3767b1e67 100755 (executable)
@@ -193,4 +193,9 @@ test_expect_success '"add" -B/--detach mutually exclusive' '
        test_must_fail git worktree add -B poodle --detach bamboo master
 '
 
+test_expect_success 'local clone from linked checkout' '
+       git clone --local here here-clone &&
+       ( cd here-clone && git fsck )
+'
+
 test_done
diff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh
deleted file mode 100755 (executable)
index a0f1e3b..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/bin/sh
-
-test_description='prune $GIT_DIR/worktrees'
-
-. ./test-lib.sh
-
-test_expect_success initialize '
-       git commit --allow-empty -m init
-'
-
-test_expect_success 'worktree prune on normal repo' '
-       git worktree prune &&
-       test_must_fail git worktree prune abc
-'
-
-test_expect_success 'prune files inside $GIT_DIR/worktrees' '
-       mkdir .git/worktrees &&
-       : >.git/worktrees/abc &&
-       git worktree prune --verbose >actual &&
-       cat >expect <<EOF &&
-Removing worktrees/abc: not a valid directory
-EOF
-       test_i18ncmp expect actual &&
-       ! test -f .git/worktrees/abc &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success 'prune directories without gitdir' '
-       mkdir -p .git/worktrees/def/abc &&
-       : >.git/worktrees/def/def &&
-       cat >expect <<EOF &&
-Removing worktrees/def: gitdir file does not exist
-EOF
-       git worktree prune --verbose >actual &&
-       test_i18ncmp expect actual &&
-       ! test -d .git/worktrees/def &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success SANITY 'prune directories with unreadable gitdir' '
-       mkdir -p .git/worktrees/def/abc &&
-       : >.git/worktrees/def/def &&
-       : >.git/worktrees/def/gitdir &&
-       chmod u-r .git/worktrees/def/gitdir &&
-       git worktree prune --verbose >actual &&
-       test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
-       ! test -d .git/worktrees/def &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success 'prune directories with invalid gitdir' '
-       mkdir -p .git/worktrees/def/abc &&
-       : >.git/worktrees/def/def &&
-       : >.git/worktrees/def/gitdir &&
-       git worktree prune --verbose >actual &&
-       test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
-       ! test -d .git/worktrees/def &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success 'prune directories with gitdir pointing to nowhere' '
-       mkdir -p .git/worktrees/def/abc &&
-       : >.git/worktrees/def/def &&
-       echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
-       git worktree prune --verbose >actual &&
-       test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
-       ! test -d .git/worktrees/def &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success 'not prune locked checkout' '
-       test_when_finished rm -r .git/worktrees &&
-       mkdir -p .git/worktrees/ghi &&
-       : >.git/worktrees/ghi/locked &&
-       git worktree prune &&
-       test -d .git/worktrees/ghi
-'
-
-test_expect_success 'not prune recent checkouts' '
-       test_when_finished rm -r .git/worktrees &&
-       mkdir zz &&
-       mkdir -p .git/worktrees/jlm &&
-       echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
-       rmdir zz &&
-       git worktree prune --verbose --expire=2.days.ago &&
-       test -d .git/worktrees/jlm
-'
-
-test_expect_success 'not prune proper checkouts' '
-       test_when_finished rm -r .git/worktrees &&
-       git worktree add --detach "$PWD/nop" master &&
-       git worktree prune &&
-       test -d .git/worktrees/nop
-'
-
-test_done
diff --git a/t/t2026-worktree-prune.sh b/t/t2026-worktree-prune.sh
new file mode 100755 (executable)
index 0000000..a0f1e3b
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+test_description='prune $GIT_DIR/worktrees'
+
+. ./test-lib.sh
+
+test_expect_success initialize '
+       git commit --allow-empty -m init
+'
+
+test_expect_success 'worktree prune on normal repo' '
+       git worktree prune &&
+       test_must_fail git worktree prune abc
+'
+
+test_expect_success 'prune files inside $GIT_DIR/worktrees' '
+       mkdir .git/worktrees &&
+       : >.git/worktrees/abc &&
+       git worktree prune --verbose >actual &&
+       cat >expect <<EOF &&
+Removing worktrees/abc: not a valid directory
+EOF
+       test_i18ncmp expect actual &&
+       ! test -f .git/worktrees/abc &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories without gitdir' '
+       mkdir -p .git/worktrees/def/abc &&
+       : >.git/worktrees/def/def &&
+       cat >expect <<EOF &&
+Removing worktrees/def: gitdir file does not exist
+EOF
+       git worktree prune --verbose >actual &&
+       test_i18ncmp expect actual &&
+       ! test -d .git/worktrees/def &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success SANITY 'prune directories with unreadable gitdir' '
+       mkdir -p .git/worktrees/def/abc &&
+       : >.git/worktrees/def/def &&
+       : >.git/worktrees/def/gitdir &&
+       chmod u-r .git/worktrees/def/gitdir &&
+       git worktree prune --verbose >actual &&
+       test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
+       ! test -d .git/worktrees/def &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories with invalid gitdir' '
+       mkdir -p .git/worktrees/def/abc &&
+       : >.git/worktrees/def/def &&
+       : >.git/worktrees/def/gitdir &&
+       git worktree prune --verbose >actual &&
+       test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
+       ! test -d .git/worktrees/def &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories with gitdir pointing to nowhere' '
+       mkdir -p .git/worktrees/def/abc &&
+       : >.git/worktrees/def/def &&
+       echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
+       git worktree prune --verbose >actual &&
+       test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
+       ! test -d .git/worktrees/def &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success 'not prune locked checkout' '
+       test_when_finished rm -r .git/worktrees &&
+       mkdir -p .git/worktrees/ghi &&
+       : >.git/worktrees/ghi/locked &&
+       git worktree prune &&
+       test -d .git/worktrees/ghi
+'
+
+test_expect_success 'not prune recent checkouts' '
+       test_when_finished rm -r .git/worktrees &&
+       mkdir zz &&
+       mkdir -p .git/worktrees/jlm &&
+       echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
+       rmdir zz &&
+       git worktree prune --verbose --expire=2.days.ago &&
+       test -d .git/worktrees/jlm
+'
+
+test_expect_success 'not prune proper checkouts' '
+       test_when_finished rm -r .git/worktrees &&
+       git worktree add --detach "$PWD/nop" master &&
+       git worktree prune &&
+       test -d .git/worktrees/nop
+'
+
+test_done
index 3fc484e8c3f8d910f02e409baf7bf1d5682b4f2f..da257c020fb64d6b8af6b482339afd87f04fea75 100755 (executable)
@@ -305,4 +305,29 @@ test_expect_success 'ls-files with "**" patterns and no slashes' '
        test_cmp expect actual
 '
 
+test_expect_success 'negative patterns' '
+       git init reinclude &&
+       (
+               cd reinclude &&
+               cat >.gitignore <<-\EOF &&
+               /fooo
+               /foo
+               !foo/bar/bar
+               EOF
+               mkdir fooo &&
+               cat >fooo/.gitignore <<-\EOF &&
+               !/*
+               EOF
+               mkdir -p foo/bar &&
+               touch abc foo/def foo/bar/ghi foo/bar/bar &&
+               git ls-files -o --exclude-standard >../actual &&
+               cat >../expected <<-\EOF &&
+               .gitignore
+               abc
+               foo/bar/bar
+               EOF
+               test_cmp ../expected ../actual
+       )
+'
+
 test_done
index f51d0f3cadcb4f97f4cf9f2d79aad9ed401781d8..9454423ca02af98ad50a089e0c0dc2bc1e952bd0 100755 (executable)
@@ -106,6 +106,19 @@ EOF
        test_i18ncmp expect actual
 '
 
+test_expect_success 'git branch shows detached HEAD properly after checkout --detach' '
+       git checkout master &&
+       cat >expect <<EOF &&
+* (HEAD detached at $(git rev-parse --short HEAD^0))
+  branch-one
+  branch-two
+  master
+EOF
+       git checkout --detach &&
+       git branch >actual &&
+       test_i18ncmp expect actual
+'
+
 test_expect_success 'git branch shows detached HEAD properly after moving' '
        cat >expect <<EOF &&
 * (HEAD detached from $(git rev-parse --short HEAD))
@@ -143,4 +156,24 @@ EOF
        test_i18ncmp expect actual
 '
 
+test_expect_success 'git branch `--sort` option' '
+       cat >expect <<-\EOF &&
+         branch-two
+       * (HEAD detached from fromtag)
+         branch-one
+         master
+       EOF
+       git branch --sort=objectsize >actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'git branch --points-at option' '
+       cat >expect <<-\EOF &&
+         branch-one
+         master
+       EOF
+       git branch --points-at=branch-one >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 7b5b6d452e3762c9d0e311602bf39317200c21a7..db244d2f8820cf63fd77e91460631009b20e4029 100755 (executable)
@@ -160,6 +160,13 @@ test_expect_success 'pack ref directly below refs/' '
        test_path_is_missing .git/refs/top
 '
 
+test_expect_success 'do not pack ref in refs/bisect' '
+       git update-ref refs/bisect/local HEAD &&
+       git pack-refs --all --prune &&
+       ! grep refs/bisect/local .git/packed-refs >/dev/null &&
+       test_path_is_file .git/refs/bisect/local
+'
+
 test_expect_success 'disable reflogs' '
        git config core.logallrefupdates false &&
        rm -rf .git/logs
index 8cffd35fb03d7ca5abbd1a229c6c8533f8c5e917..cd70274ea51ac5c5ba6669495b944da5b7b97431 100755 (executable)
@@ -1122,6 +1122,12 @@ test_expect_success 'git notes copy diagnoses too many or too few parameters' '
        test_must_fail git notes copy one two three
 '
 
+test_expect_success 'git notes get-ref expands refs/heads/master to refs/notes/refs/heads/master' '
+       test_unconfig core.notesRef &&
+       sane_unset GIT_NOTES_REF &&
+       test "$(git notes --ref=refs/heads/master get-ref)" = "refs/notes/refs/heads/master"
+'
+
 test_expect_success 'git notes get-ref (no overrides)' '
        test_unconfig core.notesRef &&
        sane_unset GIT_NOTES_REF &&
index d26e3f57dcbc18585c360ee78a725708f5a69207..3de0b1dcfdc9c5948c10e5e8c107a3820df9f357 100755 (executable)
@@ -1227,6 +1227,21 @@ test_expect_success 'static check of bad command' '
        test C = $(git cat-file commit HEAD^ | sed -ne \$p)
 '
 
+test_expect_success 'tabs and spaces are accepted in the todolist' '
+       rebase_setup_and_clean indented-comment &&
+       write_script add-indent.sh <<-\EOF &&
+       (
+               # Turn single spaces into space/tab mix
+               sed "1s/ /      /g; 2s/ /  /g; 3s/ /    /g" "$1"
+               printf "\n\t# comment\n #more\n\t # comment\n"
+       ) >$1.new
+       mv "$1.new" "$1"
+       EOF
+       test_set_editor "$(pwd)/add-indent.sh" &&
+       git rebase -i HEAD^^^ &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p)
+'
+
 cat >expect <<EOF
 Warning: the SHA-1 is missing or isn't a commit in the following line:
  - edit XXXXXXX False commit
index d783f03d3fc581eed08d8d2593e20ced3cbae200..944154b2e0ad7da5ddacdde5c22847babf1909c9 100755 (executable)
@@ -37,6 +37,16 @@ testrebase() {
        type=$1
        dotest=$2
 
+       test_expect_success "rebase$type: dirty worktree, --no-autostash" '
+               test_config rebase.autostash true &&
+               git reset --hard &&
+               git checkout -b rebased-feature-branch feature-branch &&
+               test_when_finished git branch -D rebased-feature-branch &&
+               test_when_finished git checkout feature-branch &&
+               echo dirty >>file3 &&
+               test_must_fail git rebase$type --no-autostash unrelated-onto-branch
+       '
+
        test_expect_success "rebase$type: dirty worktree, non-conflicting rebase" '
                test_config rebase.autostash true &&
                git reset --hard &&
index 7a8499ce665c74961fdbf6713c0b70c37f95f5e3..dfaf9d9f68939f0c39a512c478265b4aa227c973 100755 (executable)
@@ -919,6 +919,19 @@ test_expect_success 'new remote' '
        cmp expect actual
 '
 
+get_url_test () {
+       cat >expect &&
+       git remote get-url "$@" >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'get-url on new remote' '
+       echo foo | get_url_test someremote &&
+       echo foo | get_url_test --all someremote &&
+       echo foo | get_url_test --push someremote &&
+       echo foo | get_url_test --push --all someremote
+'
+
 test_expect_success 'remote set-url bar' '
        git remote set-url someremote bar &&
        echo bar >expect &&
@@ -961,6 +974,13 @@ test_expect_success 'remote set-url --push zot' '
        cmp expect actual
 '
 
+test_expect_success 'get-url with different urls' '
+       echo baz | get_url_test someremote &&
+       echo baz | get_url_test --all someremote &&
+       echo zot | get_url_test --push someremote &&
+       echo zot | get_url_test --push --all someremote
+'
+
 test_expect_success 'remote set-url --push qux zot' '
        git remote set-url --push someremote qux zot &&
        echo qux >expect &&
@@ -995,6 +1015,14 @@ test_expect_success 'remote set-url --push --add aaa' '
        cmp expect actual
 '
 
+test_expect_success 'get-url on multi push remote' '
+       echo foo | get_url_test --push someremote &&
+       get_url_test --push --all someremote <<-\EOF
+       foo
+       aaa
+       EOF
+'
+
 test_expect_success 'remote set-url --push bar aaa' '
        git remote set-url --push someremote bar aaa &&
        echo foo >expect &&
@@ -1039,6 +1067,14 @@ test_expect_success 'remote set-url --add bbb' '
        cmp expect actual
 '
 
+test_expect_success 'get-url on multi fetch remote' '
+       echo baz | get_url_test someremote &&
+       get_url_test --all someremote <<-\EOF
+       baz
+       bbb
+       EOF
+'
+
 test_expect_success 'remote set-url --delete .*' '
        test_must_fail git remote set-url --delete someremote .\* &&
        echo "YYY" >expect &&
@@ -1108,6 +1144,7 @@ test_extra_arg rename origin newname
 test_extra_arg remove origin
 test_extra_arg set-head origin master
 # set-branches takes any number of args
+test_extra_arg get-url origin newurl
 test_extra_arg set-url origin newurl oldurl
 # show takes any number of args
 # prune takes any number of args
diff --git a/t/t5507-remote-environment.sh b/t/t5507-remote-environment.sh
new file mode 100755 (executable)
index 0000000..e614929
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+test_description='check environment showed to remote side of transports'
+. ./test-lib.sh
+
+test_expect_success 'set up "remote" push situation' '
+       test_commit one &&
+       git config push.default current &&
+       git init remote
+'
+
+test_expect_success 'set up fake ssh' '
+       GIT_SSH_COMMAND="f() {
+               cd \"\$TRASH_DIRECTORY\" &&
+               eval \"\$2\"
+       }; f" &&
+       export GIT_SSH_COMMAND &&
+       export TRASH_DIRECTORY
+'
+
+# due to receive.denyCurrentBranch=true
+test_expect_success 'confirm default push fails' '
+       test_must_fail git push remote
+'
+
+test_expect_success 'config does not travel over same-machine push' '
+       test_must_fail git -c receive.denyCurrentBranch=false push remote
+'
+
+test_expect_success 'config does not travel over ssh push' '
+       test_must_fail git -c receive.denyCurrentBranch=false push host:remote
+'
+
+test_done
index aa73eeaef82adff56083fdc2d5d35d599b73edee..9fafcf194589218e28b894a7bd9e83c9a1893fbf 100755 (executable)
@@ -44,10 +44,6 @@ POST() {
        test_cmp exp act
 }
 
-log_div() {
-       return 0
-}
-
 . "$TEST_DIRECTORY"/t556x_common
 
 expect_aliased() {
index 19afe966986cdc72cf8fc23f113bd77a8128f382..90e0d6f0fe935970c0941bfef2af39bb15d2f959 100755 (executable)
@@ -29,15 +29,9 @@ POST() {
        test_cmp exp act
 }
 
-log_div() {
-       echo >>"$HTTPD_ROOT_PATH"/access.log
-       echo "###  $1" >>"$HTTPD_ROOT_PATH"/access.log
-       echo "###" >>"$HTTPD_ROOT_PATH"/access.log
-}
-
 . "$TEST_DIRECTORY"/t556x_common
 
-cat >exp <<EOF
+grep '^[^#]' >exp <<EOF
 
 ###  refs/heads/master
 ###
index 82926cfdb7b7c6a86ab7d73efdfa327a6be28c83..359fcfe32b66a2cd597778b58a45a937fa7656fa 100755 (executable)
@@ -52,21 +52,17 @@ get_static_files() {
 SMART=smart
 GIT_HTTP_EXPORT_ALL=1 && export GIT_HTTP_EXPORT_ALL
 test_expect_success 'direct refs/heads/master not found' '
-       log_div "refs/heads/master" &&
        GET refs/heads/master "404 Not Found"
 '
 test_expect_success 'static file is ok' '
-       log_div "getanyfile default" &&
        get_static_files "200 OK"
 '
 SMART=smart_noexport
 unset GIT_HTTP_EXPORT_ALL
 test_expect_success 'no export by default' '
-       log_div "no git-daemon-export-ok" &&
        get_static_files "404 Not Found"
 '
 test_expect_success 'export if git-daemon-export-ok' '
-       log_div "git-daemon-export-ok" &&
         (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
         touch git-daemon-export-ok
        ) &&
@@ -75,47 +71,39 @@ test_expect_success 'export if git-daemon-export-ok' '
 SMART=smart
 GIT_HTTP_EXPORT_ALL=1 && export GIT_HTTP_EXPORT_ALL
 test_expect_success 'static file if http.getanyfile true is ok' '
-       log_div "getanyfile true" &&
        config http.getanyfile true &&
        get_static_files "200 OK"
 '
 test_expect_success 'static file if http.getanyfile false fails' '
-       log_div "getanyfile false" &&
        config http.getanyfile false &&
        get_static_files "403 Forbidden"
 '
 
 test_expect_success 'http.uploadpack default enabled' '
-       log_div "uploadpack default" &&
        GET info/refs?service=git-upload-pack "200 OK"  &&
        POST git-upload-pack 0000 "200 OK"
 '
 test_expect_success 'http.uploadpack true' '
-       log_div "uploadpack true" &&
        config http.uploadpack true &&
        GET info/refs?service=git-upload-pack "200 OK" &&
        POST git-upload-pack 0000 "200 OK"
 '
 test_expect_success 'http.uploadpack false' '
-       log_div "uploadpack false" &&
        config http.uploadpack false &&
        GET info/refs?service=git-upload-pack "403 Forbidden" &&
        POST git-upload-pack 0000 "403 Forbidden"
 '
 
 test_expect_success 'http.receivepack default disabled' '
-       log_div "receivepack default" &&
        GET info/refs?service=git-receive-pack "403 Forbidden"  &&
        POST git-receive-pack 0000 "403 Forbidden"
 '
 test_expect_success 'http.receivepack true' '
-       log_div "receivepack true" &&
        config http.receivepack true &&
        GET info/refs?service=git-receive-pack "200 OK" &&
        POST git-receive-pack 0000 "200 OK"
 '
 test_expect_success 'http.receivepack false' '
-       log_div "receivepack false" &&
        config http.receivepack false &&
        GET info/refs?service=git-receive-pack "403 Forbidden" &&
        POST git-receive-pack 0000 "403 Forbidden"
index ef1779f5cae5642c65c83a9e18b55488005ccf12..2250ef4fe245bda17328d46844a4caf49fbde8d9 100755 (executable)
@@ -188,5 +188,26 @@ test_expect_success 'clone and dissociate from reference' '
        test_must_fail git -C R fsck &&
        git -C S fsck
 '
+test_expect_success 'clone, dissociate from partial reference and repack' '
+       rm -fr P Q R &&
+       git init P &&
+       (
+               cd P &&
+               test_commit one &&
+               git repack &&
+               test_commit two &&
+               git repack
+       ) &&
+       git clone --bare P Q &&
+       (
+               cd P &&
+               git checkout -b second &&
+               test_commit three &&
+               git repack
+       ) &&
+       git clone --bare --dissociate --reference=P Q R &&
+       ls R/objects/pack/*.pack >packs.txt &&
+       test_line_count = 1 packs.txt
+'
 
 test_done
index c9d3ed14c3a3238208b44ce815e37123298dea77..362b1581e092826400d78515cdc8d5cb0f7cfc29 100755 (executable)
@@ -242,13 +242,6 @@ clean_mark () {
        sort >$(basename "$1")
 }
 
-cmp_marks () {
-       test_when_finished "rm -rf git.marks testgit.marks" &&
-       clean_mark ".git/testgit/$1/git.marks" &&
-       clean_mark ".git/testgit/$1/testgit.marks" &&
-       test_cmp git.marks testgit.marks
-}
-
 test_expect_success 'proper failure checks for fetching' '
        (cd local &&
        test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git fetch 2>error &&
@@ -258,12 +251,15 @@ test_expect_success 'proper failure checks for fetching' '
 '
 
 test_expect_success 'proper failure checks for pushing' '
+       test_when_finished "rm -rf local/git.marks local/testgit.marks" &&
        (cd local &&
        git checkout -b crash master &&
        echo crash >>file &&
        git commit -a -m crash &&
        test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git push --all &&
-       cmp_marks origin
+       clean_mark ".git/testgit/origin/git.marks" &&
+       clean_mark ".git/testgit/origin/testgit.marks" &&
+       test_cmp git.marks testgit.marks
        )
 '
 
index 878faf2b6318412df756f5b8cd2c00410f476e12..b7a7f9d5886f1e2f3fc2d65bfcf1785b890638ac 100755 (executable)
@@ -69,4 +69,32 @@ test_expect_success 'update backfilled tag without primary transfer' '
        test_cmp expect actual
 '
 
+
+test_expect_success 'set up fake git-daemon' '
+       mkdir remote &&
+       git init --bare remote/one.git &&
+       mkdir remote/host &&
+       git init --bare remote/host/two.git &&
+       write_script fake-daemon <<-\EOF &&
+       git daemon --inetd \
+               --informative-errors \
+               --export-all \
+               --base-path="$TRASH_DIRECTORY/remote" \
+               --interpolated-path="$TRASH_DIRECTORY/remote/%H%D" \
+               "$TRASH_DIRECTORY/remote"
+       EOF
+       export TRASH_DIRECTORY &&
+       PATH=$TRASH_DIRECTORY:$PATH
+'
+
+test_expect_success 'ext command can connect to git daemon (no vhost)' '
+       rm -rf dst &&
+       git clone "ext::fake-daemon %G/one.git" dst
+'
+
+test_expect_success 'ext command can connect to git daemon (vhost)' '
+       rm -rf dst &&
+       git clone "ext::fake-daemon %G/two.git %Vhost" dst
+'
+
 test_done
index 9e2c20374732d790edefc4cd87aa28223fbb24c4..e74662ba5c638de5acb4188e2cd0c5ff2456ec13 100755 (executable)
@@ -759,4 +759,139 @@ test_expect_success '"git bisect bad HEAD" behaves as "git bisect bad"' '
        git bisect reset
 '
 
+test_expect_success 'bisect starts with only one new' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect new $HASH4 &&
+       git bisect next
+'
+
+test_expect_success 'bisect does not start with only one old' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect old $HASH1 &&
+       test_must_fail git bisect next
+'
+
+test_expect_success 'bisect start with one new and old' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect old $HASH1 &&
+       git bisect new $HASH4 &&
+       git bisect new &&
+       git bisect new >bisect_result &&
+       grep "$HASH2 is the first new commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect replay with old and new' '
+       git bisect replay log_to_replay.txt >bisect_result &&
+       grep "$HASH2 is the first new commit" bisect_result &&
+       git bisect reset
+'
+
+test_expect_success 'bisect cannot mix old/new and good/bad' '
+       git bisect start &&
+       git bisect bad $HASH4 &&
+       test_must_fail git bisect old $HASH1
+'
+
+test_expect_success 'bisect terms needs 0 or 1 argument' '
+       git bisect reset &&
+       test_must_fail git bisect terms only-one &&
+       test_must_fail git bisect terms 1 2 &&
+       test_must_fail git bisect terms 2>actual &&
+       echo "no terms defined" >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect terms shows good/bad after start' '
+       git bisect reset &&
+       git bisect start HEAD $HASH1 &&
+       git bisect terms --term-good >actual &&
+       echo good >expected &&
+       test_cmp expected actual &&
+       git bisect terms --term-bad >actual &&
+       echo bad >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect start with one term1 and term2' '
+       git bisect reset &&
+       git bisect start --term-old term2 --term-new term1 &&
+       git bisect term2 $HASH1 &&
+       git bisect term1 $HASH4 &&
+       git bisect term1 &&
+       git bisect term1 >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect replay with term1 and term2' '
+       git bisect replay log_to_replay.txt >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect reset
+'
+
+test_expect_success 'bisect start term1 term2' '
+       git bisect reset &&
+       git bisect start --term-new term1 --term-old term2 $HASH4 $HASH1 &&
+       git bisect term1 &&
+       git bisect term1 >bisect_result &&
+       grep "$HASH2 is the first term1 commit" bisect_result &&
+       git bisect log >log_to_replay.txt &&
+       git bisect reset
+'
+
+test_expect_success 'bisect cannot mix terms' '
+       git bisect reset &&
+       git bisect start --term-good term1 --term-bad term2 $HASH4 $HASH1 &&
+       test_must_fail git bisect a &&
+       test_must_fail git bisect b &&
+       test_must_fail git bisect bad &&
+       test_must_fail git bisect good &&
+       test_must_fail git bisect new &&
+       test_must_fail git bisect old
+'
+
+test_expect_success 'bisect terms rejects invalid terms' '
+       git bisect reset &&
+       test_must_fail git bisect start --term-good invalid..term &&
+       test_must_fail git bisect terms --term-bad invalid..term &&
+       test_must_fail git bisect terms --term-good bad &&
+       test_must_fail git bisect terms --term-good old &&
+       test_must_fail git bisect terms --term-good skip &&
+       test_must_fail git bisect terms --term-good reset &&
+       test_path_is_missing .git/BISECT_TERMS
+'
+
+test_expect_success 'bisect start --term-* does store terms' '
+       git bisect reset &&
+       git bisect start --term-bad=one --term-good=two &&
+       git bisect terms >actual &&
+       cat <<-EOF >expected &&
+       Your current terms are two for the old state
+       and one for the new state.
+       EOF
+       test_cmp expected actual &&
+       git bisect terms --term-bad >actual &&
+       echo one >expected &&
+       test_cmp expected actual &&
+       git bisect terms --term-good >actual &&
+       echo two >expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'bisect start takes options and revs in any order' '
+       git bisect reset &&
+       git bisect start --term-good one $HASH4 \
+               --term-good two --term-bad bad-term \
+               $HASH1 --term-good three -- &&
+       (git bisect terms --term-bad && git bisect terms --term-good) >actual &&
+       printf "%s\n%s\n" bad-term three >expected &&
+       test_cmp expected actual
+'
+
 test_done
index 7c9bec76302259beebeea7521b9c74acfe6de264..03873b09d1a4480549e1b8ab92fc3780fb874d89 100755 (executable)
@@ -8,8 +8,8 @@ test_description='for-each-ref test'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-gpg.sh
 
-# Mon Jul 3 15:18:43 2006 +0000
-datestamp=1151939923
+# Mon Jul 3 23:18:43 2006 +0000
+datestamp=1151968723
 setdate_and_increment () {
     GIT_COMMITTER_DATE="$datestamp +0200"
     datestamp=$(expr "$datestamp" + 1)
@@ -61,21 +61,21 @@ test_atom head object ''
 test_atom head type ''
 test_atom head '*objectname' ''
 test_atom head '*objecttype' ''
-test_atom head author 'A U Thor <author@example.com> 1151939924 +0200'
+test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
 test_atom head authorname 'A U Thor'
 test_atom head authoremail '<author@example.com>'
-test_atom head authordate 'Mon Jul 3 17:18:44 2006 +0200'
-test_atom head committer 'C O Mitter <committer@example.com> 1151939923 +0200'
+test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
+test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
 test_atom head committername 'C O Mitter'
 test_atom head committeremail '<committer@example.com>'
-test_atom head committerdate 'Mon Jul 3 17:18:43 2006 +0200'
+test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head tag ''
 test_atom head tagger ''
 test_atom head taggername ''
 test_atom head taggeremail ''
 test_atom head taggerdate ''
-test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200'
-test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200'
+test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head subject 'Initial'
 test_atom head contents:subject 'Initial'
 test_atom head body ''
@@ -96,7 +96,7 @@ test_atom tag parent ''
 test_atom tag numparent ''
 test_atom tag object $(git rev-parse refs/tags/testtag^0)
 test_atom tag type 'commit'
-test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581'
+test_atom tag '*objectname' 'ea122842f48be4afb2d1fc6a4b96c05885ab7463'
 test_atom tag '*objecttype' 'commit'
 test_atom tag author ''
 test_atom tag authorname ''
@@ -107,18 +107,18 @@ test_atom tag committername ''
 test_atom tag committeremail ''
 test_atom tag committerdate ''
 test_atom tag tag 'testtag'
-test_atom tag tagger 'C O Mitter <committer@example.com> 1151939925 +0200'
+test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
 test_atom tag taggername 'C O Mitter'
 test_atom tag taggeremail '<committer@example.com>'
-test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200'
-test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200'
-test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200'
-test_atom tag subject 'Tagging at 1151939927'
-test_atom tag contents:subject 'Tagging at 1151939927'
+test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag subject 'Tagging at 1151968727'
+test_atom tag contents:subject 'Tagging at 1151968727'
 test_atom tag body ''
 test_atom tag contents:body ''
 test_atom tag contents:signature ''
-test_atom tag contents 'Tagging at 1151939927
+test_atom tag contents 'Tagging at 1151968727
 '
 test_atom tag HEAD ' '
 
@@ -146,95 +146,123 @@ test_expect_success 'Check invalid format specifiers are errors' '
        test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon Jul 3 17:18:43 2006 +0200' 'Mon Jul 3 17:18:44 2006 +0200'
-'refs/tags/testtag' 'Mon Jul 3 17:18:45 2006 +0200'
-EOF
+test_date () {
+       f=$1 &&
+       committer_date=$2 &&
+       author_date=$3 &&
+       tagger_date=$4 &&
+       cat >expected <<-EOF &&
+       'refs/heads/master' '$committer_date' '$author_date'
+       'refs/tags/testtag' '$tagger_date'
+       EOF
+       (
+               git for-each-ref --shell \
+                       --format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
+                       refs/heads &&
+               git for-each-ref --shell \
+                       --format="%(refname) %(taggerdate${f:+:$f})" \
+                       refs/tags
+       ) >actual &&
+       test_cmp expected actual
+}
 
 test_expect_success 'Check unformatted date fields output' '
-       (git for-each-ref --shell --format="%(refname) %(committerdate) %(authordate)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date "" \
+               "Tue Jul 4 01:18:43 2006 +0200" \
+               "Tue Jul 4 01:18:44 2006 +0200" \
+               "Tue Jul 4 01:18:45 2006 +0200"
 '
 
 test_expect_success 'Check format "default" formatted date fields output' '
-       f=default &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date default \
+               "Tue Jul 4 01:18:43 2006 +0200" \
+               "Tue Jul 4 01:18:44 2006 +0200" \
+               "Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default-local" date fields output' '
+       test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
 '
 
 # Don't know how to do relative check because I can't know when this script
 # is going to be run and can't fake the current time to git, and hence can't
 # provide expected output.  Instead, I'll just make sure that "relative"
 # doesn't exit in error
-#
-#cat >expected <<\EOF
-#
-#EOF
-#
 test_expect_success 'Check format "relative" date fields output' '
        f=relative &&
        (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
        git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
 '
 
-cat >expected <<\EOF
-'refs/heads/master' '2006-07-03' '2006-07-03'
-'refs/tags/testtag' '2006-07-03'
-EOF
+# We just check that this is the same as "relative" for now.
+test_expect_success 'Check format "relative-local" date fields output' '
+       test_date relative-local \
+               "$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
+               "$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
+               "$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
+'
 
 test_expect_success 'Check format "short" date fields output' '
-       f=short &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date short 2006-07-04 2006-07-04 2006-07-04
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon Jul 3 15:18:43 2006' 'Mon Jul 3 15:18:44 2006'
-'refs/tags/testtag' 'Mon Jul 3 15:18:45 2006'
-EOF
+test_expect_success 'Check format "short-local" date fields output' '
+       test_date short-local 2006-07-03 2006-07-03 2006-07-03
+'
 
 test_expect_success 'Check format "local" date fields output' '
-       f=local &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date local \
+               "Mon Jul 3 23:18:43 2006" \
+               "Mon Jul 3 23:18:44 2006" \
+               "Mon Jul 3 23:18:45 2006"
 '
 
-cat >expected <<\EOF
-'refs/heads/master' '2006-07-03 17:18:43 +0200' '2006-07-03 17:18:44 +0200'
-'refs/tags/testtag' '2006-07-03 17:18:45 +0200'
-EOF
-
 test_expect_success 'Check format "iso8601" date fields output' '
-       f=iso8601 &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date iso8601 \
+               "2006-07-04 01:18:43 +0200" \
+               "2006-07-04 01:18:44 +0200" \
+               "2006-07-04 01:18:45 +0200"
 '
 
-cat >expected <<\EOF
-'refs/heads/master' 'Mon, 3 Jul 2006 17:18:43 +0200' 'Mon, 3 Jul 2006 17:18:44 +0200'
-'refs/tags/testtag' 'Mon, 3 Jul 2006 17:18:45 +0200'
-EOF
+test_expect_success 'Check format "iso8601-local" date fields output' '
+       test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
+'
 
 test_expect_success 'Check format "rfc2822" date fields output' '
-       f=rfc2822 &&
-       (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-       git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual &&
-       test_cmp expected actual
+       test_date rfc2822 \
+               "Tue, 4 Jul 2006 01:18:43 +0200" \
+               "Tue, 4 Jul 2006 01:18:44 +0200" \
+               "Tue, 4 Jul 2006 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "rfc2822-local" date fields output' '
+       test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "raw" date fields output' '
+       test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
+'
+
+test_expect_success 'Check format "raw-local" date fields output' '
+       test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
 '
 
 test_expect_success 'Check format of strftime date fields' '
-       echo "my date is 2006-07-03" >expected &&
+       echo "my date is 2006-07-04" >expected &&
        git for-each-ref \
          --format="%(authordate:format:my date is %Y-%m-%d)" \
          refs/heads >actual &&
        test_cmp expected actual
 '
 
+test_expect_success 'Check format of strftime-local date fields' '
+       echo "my date is 2006-07-03" >expected &&
+       git for-each-ref \
+         --format="%(authordate:format-local:my date is %Y-%m-%d)" \
+         refs/heads >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'exercise strftime with odd fields' '
        echo >expected &&
        git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
@@ -546,8 +574,8 @@ body contents
 $sig"
 
 cat >expected <<EOF
-$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
 $(git rev-parse refs/tags/bogo) <committer@example.com> refs/tags/bogo
+$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master
 EOF
 
 test_expect_success 'Verify sort with multiple keys' '
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
new file mode 100755 (executable)
index 0000000..fe4796c
--- /dev/null
@@ -0,0 +1,258 @@
+#!/bin/sh
+
+test_description='test for-each-refs usage of ref-filter APIs'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
+
+if ! test_have_prereq GPG
+then
+       skip_all="skipping for-each-ref tests, GPG not available"
+       test_done
+fi
+
+test_expect_success 'setup some history and refs' '
+       test_commit one &&
+       test_commit two &&
+       test_commit three &&
+       git checkout -b side &&
+       test_commit four &&
+       git tag -s -m "A signed tag message" signed-tag &&
+       git tag -s -m "Annonated doubly" double-tag signed-tag &&
+       git checkout master &&
+       git update-ref refs/odd/spot master
+'
+
+test_expect_success 'filtering with --points-at' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/odd/spot
+       refs/tags/three
+       EOF
+       git for-each-ref --format="%(refname)" --points-at=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check signed tags with --points-at' '
+       sed -e "s/Z$//" >expect <<-\EOF &&
+       refs/heads/side Z
+       refs/tags/four Z
+       refs/tags/signed-tag four
+       EOF
+       git for-each-ref --format="%(refname) %(*subject)" --points-at=side >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --merged' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/odd/spot
+       refs/tags/one
+       refs/tags/three
+       refs/tags/two
+       EOF
+       git for-each-ref --format="%(refname)" --merged=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --no-merged' '
+       cat >expect <<-\EOF &&
+       refs/heads/side
+       refs/tags/double-tag
+       refs/tags/four
+       refs/tags/signed-tag
+       EOF
+       git for-each-ref --format="%(refname)" --no-merged=master >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'filtering with --contains' '
+       cat >expect <<-\EOF &&
+       refs/heads/master
+       refs/heads/side
+       refs/odd/spot
+       refs/tags/double-tag
+       refs/tags/four
+       refs/tags/signed-tag
+       refs/tags/three
+       refs/tags/two
+       EOF
+       git for-each-ref --format="%(refname)" --contains=two >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(color) must fail' '
+       test_must_fail git for-each-ref --format="%(color)%(refname)"
+'
+
+test_expect_success 'left alignment is default' '
+       cat >expect <<-\EOF &&
+       refname is refs/heads/master  |refs/heads/master
+       refname is refs/heads/side    |refs/heads/side
+       refname is refs/odd/spot      |refs/odd/spot
+       refname is refs/tags/double-tag|refs/tags/double-tag
+       refname is refs/tags/four     |refs/tags/four
+       refname is refs/tags/one      |refs/tags/one
+       refname is refs/tags/signed-tag|refs/tags/signed-tag
+       refname is refs/tags/three    |refs/tags/three
+       refname is refs/tags/two      |refs/tags/two
+       EOF
+       git for-each-ref --format="%(align:30)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'middle alignment' '
+       cat >expect <<-\EOF &&
+       | refname is refs/heads/master |refs/heads/master
+       |  refname is refs/heads/side  |refs/heads/side
+       |   refname is refs/odd/spot   |refs/odd/spot
+       |refname is refs/tags/double-tag|refs/tags/double-tag
+       |  refname is refs/tags/four   |refs/tags/four
+       |   refname is refs/tags/one   |refs/tags/one
+       |refname is refs/tags/signed-tag|refs/tags/signed-tag
+       |  refname is refs/tags/three  |refs/tags/three
+       |   refname is refs/tags/two   |refs/tags/two
+       EOF
+       git for-each-ref --format="|%(align:middle,30)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'right alignment' '
+       cat >expect <<-\EOF &&
+       |  refname is refs/heads/master|refs/heads/master
+       |    refname is refs/heads/side|refs/heads/side
+       |      refname is refs/odd/spot|refs/odd/spot
+       |refname is refs/tags/double-tag|refs/tags/double-tag
+       |     refname is refs/tags/four|refs/tags/four
+       |      refname is refs/tags/one|refs/tags/one
+       |refname is refs/tags/signed-tag|refs/tags/signed-tag
+       |    refname is refs/tags/three|refs/tags/three
+       |      refname is refs/tags/two|refs/tags/two
+       EOF
+       git for-each-ref --format="|%(align:30,right)refname is %(refname)%(end)|%(refname)" >actual &&
+       test_cmp expect actual
+'
+
+# Individual atoms inside %(align:...) and %(end) must not be quoted.
+
+test_expect_success 'alignment with format quote' "
+       cat >expect <<-\EOF &&
+       |'      '\''master| A U Thor'\''      '|
+       |'       '\''side| A U Thor'\''       '|
+       |'     '\''odd/spot| A U Thor'\''     '|
+       |'        '\''double-tag| '\''        '|
+       |'       '\''four| A U Thor'\''       '|
+       |'       '\''one| A U Thor'\''        '|
+       |'        '\''signed-tag| '\''        '|
+       |'      '\''three| A U Thor'\''       '|
+       |'       '\''two| A U Thor'\''        '|
+       EOF
+       git for-each-ref --shell --format=\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\" >actual &&
+       test_cmp expect actual
+"
+
+test_expect_success 'nested alignment with quote formatting' "
+       cat >expect <<-\EOF &&
+       |'         master               '|
+       |'           side               '|
+       |'       odd/spot               '|
+       |'     double-tag               '|
+       |'           four               '|
+       |'            one               '|
+       |'     signed-tag               '|
+       |'          three               '|
+       |'            two               '|
+       EOF
+       git for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual &&
+       test_cmp expect actual
+"
+
+test_expect_success 'check `%(contents:lines=1)`' '
+       cat >expect <<-\EOF &&
+       master |three
+       side |four
+       odd/spot |three
+       double-tag |Annonated doubly
+       four |four
+       one |one
+       signed-tag |A signed tag message
+       three |three
+       two |two
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=1)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check `%(contents:lines=0)`' '
+       cat >expect <<-\EOF &&
+       master |
+       side |
+       odd/spot |
+       double-tag |
+       four |
+       one |
+       signed-tag |
+       three |
+       two |
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=0)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'check `%(contents:lines=99999)`' '
+       cat >expect <<-\EOF &&
+       master |three
+       side |four
+       odd/spot |three
+       double-tag |Annonated doubly
+       four |four
+       one |one
+       signed-tag |A signed tag message
+       three |three
+       two |two
+       EOF
+       git for-each-ref --format="%(refname:short) |%(contents:lines=99999)" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '`%(contents:lines=-1)` should fail' '
+       test_must_fail git for-each-ref --format="%(refname:short) |%(contents:lines=-1)"
+'
+
+test_expect_success 'setup for version sort' '
+       test_commit foo1.3 &&
+       test_commit foo1.6 &&
+       test_commit foo1.10
+'
+
+test_expect_success 'version sort' '
+       git for-each-ref --sort=version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.3
+       foo1.6
+       foo1.10
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'version sort (shortened)' '
+       git for-each-ref --sort=v:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.3
+       foo1.6
+       foo1.10
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'reverse version sort' '
+       git for-each-ref --sort=-version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.10
+       foo1.6
+       foo1.3
+       EOF
+       test_cmp expect actual
+'
+
+test_done
index 63194d819efe07af241573662c660e6e92b503f5..5d7d4146179bb43184f74dd646aacbba11cc8c8b 100755 (executable)
@@ -30,4 +30,17 @@ test_expect_success 'gc -h with invalid configuration' '
        test_i18ngrep "[Uu]sage" broken/usage
 '
 
+test_expect_success 'gc is not aborted due to a stale symref' '
+       git init remote &&
+       (
+               cd remote &&
+               test_commit initial &&
+               git clone . ../client &&
+               git branch -m develop &&
+               cd ../client &&
+               git fetch --prune &&
+               git gc
+       )
+'
+
 test_done
index 855afda80a1b0dea6d74aa18a96018f59cb2e50b..377c648e04f55359eefa87cdf2ad0c06be0df171 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='git filter-branch'
 . ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
 
 test_expect_success 'setup' '
        test_commit A &&
@@ -292,6 +293,19 @@ test_expect_success 'Tag name filtering strips gpg signature' '
        test_cmp expect actual
 '
 
+test_expect_success GPG 'Filtering retains message of gpg signed commit' '
+       mkdir gpg &&
+       touch gpg/foo &&
+       git add gpg &&
+       test_tick &&
+       git commit -S -m "Adding gpg" &&
+
+       git log -1 --format="%s" > expect &&
+       git filter-branch -f --msg-filter "cat" &&
+       git log -1 --format="%s" > actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'Tag name filtering allows slashes in tag names' '
        git tag -m tag-with-slash X/1 &&
        git cat-file tag X/1 | sed -e s,X/1,X/2, > expect &&
index d31788cc6ce6a5698c34ffe6ee8a59fccacab90c..3dd2f51e49d7e6824382ac415cf64842fa3b757f 100755 (executable)
@@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' '
 
 test_expect_success 'invalid sort parameter in configuratoin' '
        git config tag.sort "v:notvalid" &&
-       git tag -l "foo*" >actual &&
-       cat >expect <<-\EOF &&
-       foo1.10
-       foo1.3
-       foo1.6
-       EOF
-       test_cmp expect actual
+       test_must_fail git tag -l "foo*"
 '
 
 test_expect_success 'version sort with prerelease reordering' '
@@ -1525,4 +1519,43 @@ EOF"
        test_cmp expect actual
 '
 
+test_expect_success '--format should list tags as per format given' '
+       cat >expect <<-\EOF &&
+       refname : refs/tags/foo1.10
+       refname : refs/tags/foo1.3
+       refname : refs/tags/foo1.6
+       refname : refs/tags/foo1.6-rc1
+       refname : refs/tags/foo1.6-rc2
+       EOF
+       git tag -l --format="refname : %(refname)" "foo*" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'setup --merged test tags' '
+       git tag mergetest-1 HEAD~2 &&
+       git tag mergetest-2 HEAD~1 &&
+       git tag mergetest-3 HEAD
+'
+
+test_expect_success '--merged cannot be used in non-list mode' '
+       test_must_fail git tag --merged=mergetest-2 foo
+'
+
+test_expect_success '--merged shows merged tags' '
+       cat >expect <<-\EOF &&
+       mergetest-1
+       mergetest-2
+       EOF
+       git tag -l --merged=mergetest-2 mergetest-* >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--no-merged show unmerged tags' '
+       cat >expect <<-\EOF &&
+       mergetest-3
+       EOF
+       git tag -l --no-merged=mergetest-2 mergetest-* >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 3f609e8909c463e7d396915d273d82785b238000..1acef32647aee6ef33638fd35be4d3e3ad61c726 100755 (executable)
@@ -47,4 +47,14 @@ test_expect_success 'checkout main and initialize independed clones' \
 test_expect_success 'can see submodule diffs after independed cloning' \
     '(cd fully_cloned_submodule/main && git diff --submodule master"^!" | grep "file1 updated")'
 
+test_expect_success 'checkout sub manually' \
+    'mkdir linked_submodule &&
+    (cd clone/main &&
+       git worktree add "$base_path/linked_submodule/main" "$rev1_hash_main") &&
+    (cd clone/main/sub &&
+       git worktree add "$base_path/linked_submodule/main/sub" "$rev1_hash_sub")'
+
+test_expect_success 'can see submodule diffs after manual checkout of linked submodule' \
+    '(cd linked_submodule/main && git diff --submodule master"^!" | grep "file1 updated")'
+
 test_done
index 7eeb207b32b48041037488f42e645b8b12742690..6f12b235b3a9330497e814d5e8f564aec94d0c44 100755 (executable)
@@ -174,9 +174,9 @@ test_expect_success 'mergetool skips autoresolved' '
 '
 
 test_expect_success 'mergetool merges all from subdir' '
+       test_config rerere.enabled false &&
        (
                cd subdir &&
-               test_config rerere.enabled false &&
                test_must_fail git merge master &&
                ( yes "r" | git mergetool ../submod ) &&
                ( yes "d" "d" | git mergetool --no-prompt ) &&
index ea35a0241c201ac12c29603a21421199dd097600..48c6e2bc830e74ab8d242e49c08f38609b5ef585 100755 (executable)
@@ -492,12 +492,12 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' '
 
 test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
        git submodule add ./. submod/ule &&
+       test_config -C submod/ule diff.tool checktrees &&
+       test_config -C submod/ule difftool.checktrees.cmd '\''
+               test -d "$LOCAL" && test -d "$REMOTE" && echo good
+               '\'' &&
        (
                cd submod/ule &&
-               test_config diff.tool checktrees &&
-               test_config difftool.checktrees.cmd '\''
-                       test -d "$LOCAL" && test -d "$REMOTE" && echo good
-               '\'' &&
                echo good >expect &&
                git difftool --tool=checktrees --dir-diff HEAD~ >actual &&
                test_cmp expect actual
index 095238fffe2757a5bf1c5c2f877791117fbf09a3..decb66ba30871573eb9c252e56599d47095f0fff 100755 (executable)
@@ -214,6 +214,51 @@ test_expect_success 'use git config to enable import/export of tags' '
        )
 '
 
+p4_head_revision() {
+       p4 changes -m 1 "$@" | awk '{print $2}'
+}
+
+# Importing a label that references a P4 commit that
+# has not been seen. The presence of a label on a commit
+# we haven't seen should not cause git-p4 to fail. It should
+# merely skip that label, and still import other labels.
+test_expect_success 'importing labels with missing revisions' '
+       test_when_finished cleanup_git &&
+       (
+               rm -fr "$cli" "$git" &&
+               mkdir "$cli" &&
+               P4CLIENT=missing-revision &&
+               client_view "//depot/missing-revision/... //missing-revision/..." &&
+               cd "$cli" &&
+               >f1 && p4 add f1 && p4 submit -d "start" &&
+
+               p4 tag -l TAG_S0 ... &&
+
+               >f2 && p4 add f2 && p4 submit -d "second" &&
+
+               startrev=$(p4_head_revision //depot/missing-revision/...) &&
+
+               >f3 && p4 add f3 && p4 submit -d "third" &&
+
+               p4 edit f2 && date >f2 && p4 submit -d "change" f2 &&
+
+               endrev=$(p4_head_revision //depot/missing-revision/...) &&
+
+               p4 tag -l TAG_S1 ... &&
+
+               # we should skip TAG_S0 since it is before our startpoint,
+               # but pick up TAG_S1.
+
+               git p4 clone --dest="$git" --import-labels -v \
+                       //depot/missing-revision/...@$startrev,$endrev &&
+               (
+                       cd "$git" &&
+                       git rev-parse TAG_S1 &&
+                       ! git rev-parse TAG_S0
+               )
+       )
+'
+
 
 test_expect_success 'kill p4d' '
        kill_p4d
diff --git a/t/t9822-git-p4-path-encoding.sh b/t/t9822-git-p4-path-encoding.sh
new file mode 100755 (executable)
index 0000000..7b83e69
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+test_description='Clone repositories with non ASCII paths'
+
+. ./lib-git-p4.sh
+
+UTF8_ESCAPED="a-\303\244_o-\303\266_u-\303\274.txt"
+ISO8859_ESCAPED="a-\344_o-\366_u-\374.txt"
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'Create a repo containing iso8859-1 encoded paths' '
+       (
+               cd "$cli" &&
+               ISO8859="$(printf "$ISO8859_ESCAPED")" &&
+               echo content123 >"$ISO8859" &&
+               p4 add "$ISO8859" &&
+               p4 submit -d "test commit"
+       )
+'
+
+test_expect_failure 'Clone auto-detects depot with iso8859-1 paths' '
+       git p4 clone --destination="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               UTF8="$(printf "$UTF8_ESCAPED")" &&
+               echo "$UTF8" >expect &&
+               git -c core.quotepath=false ls-files >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'Clone repo containing iso8859-1 encoded paths with git-p4.pathEncoding' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.pathEncoding iso8859-1 &&
+               git p4 clone --use-client-spec --destination="$git" //depot &&
+               UTF8="$(printf "$UTF8_ESCAPED")" &&
+               echo "$UTF8" >expect &&
+               git -c core.quotepath=false ls-files >actual &&
+               test_cmp expect actual &&
+
+               echo content123 >expect &&
+               cat "$UTF8" >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9823-git-p4-mock-lfs.sh b/t/t9823-git-p4-mock-lfs.sh
new file mode 100755 (executable)
index 0000000..1f2dc36
--- /dev/null
@@ -0,0 +1,192 @@
+#!/bin/sh
+
+test_description='Clone repositories and store files in Mock LFS'
+
+. ./lib-git-p4.sh
+
+test_file_is_not_in_mock_lfs () {
+       FILE="$1" &&
+       CONTENT="$2" &&
+       echo "$CONTENT" >expect_content &&
+       test_path_is_file "$FILE" &&
+       test_cmp expect_content "$FILE"
+}
+
+test_file_is_in_mock_lfs () {
+       FILE="$1" &&
+       CONTENT="$2" &&
+       LOCAL_STORAGE=".git/mock-storage/local/$CONTENT" &&
+       SERVER_STORAGE=".git/mock-storage/remote/$CONTENT" &&
+       echo "pointer-$CONTENT" >expect_pointer &&
+       echo "$CONTENT" >expect_content &&
+       test_path_is_file "$FILE" &&
+       test_path_is_file "$LOCAL_STORAGE" &&
+       test_path_is_file "$SERVER_STORAGE" &&
+       test_cmp expect_pointer "$FILE" &&
+       test_cmp expect_content "$LOCAL_STORAGE" &&
+       test_cmp expect_content "$SERVER_STORAGE"
+}
+
+test_file_is_deleted_in_mock_lfs () {
+       FILE="$1" &&
+       CONTENT="$2" &&
+       LOCAL_STORAGE=".git/mock-storage/local/$CONTENT" &&
+       SERVER_STORAGE=".git/mock-storage/remote/$CONTENT" &&
+       echo "pointer-$CONTENT" >expect_pointer &&
+       echo "$CONTENT" >expect_content &&
+       test_path_is_missing "$FILE" &&
+       test_path_is_file "$LOCAL_STORAGE" &&
+       test_path_is_file "$SERVER_STORAGE" &&
+       test_cmp expect_content "$LOCAL_STORAGE" &&
+       test_cmp expect_content "$SERVER_STORAGE"
+}
+
+test_file_count_in_dir () {
+       DIR="$1" &&
+       EXPECTED_COUNT="$2" &&
+       find "$DIR" -type f >actual &&
+       test_line_count = $EXPECTED_COUNT actual
+}
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'Create repo with binary files' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+
+               echo "content 1 txt 23 bytes" >file1.txt &&
+               p4 add file1.txt &&
+               echo "content 2-3 bin 25 bytes" >file2.dat &&
+               p4 add file2.dat &&
+               p4 submit -d "Add text and binary file" &&
+
+               mkdir "path with spaces" &&
+               echo "content 2-3 bin 25 bytes" >"path with spaces/file3.bin" &&
+               p4 add "path with spaces/file3.bin" &&
+               p4 submit -d "Add another binary file with same content and spaces in path" &&
+
+               echo "content 4 bin 26 bytes XX" >file4.bin &&
+               p4 add file4.bin &&
+               p4 submit -d "Add another binary file with different content"
+       )
+'
+
+test_expect_success 'Store files in Mock LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git config git-p4.largeFilePush True &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" &&
+               test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" &&
+               test_file_is_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" &&
+               test_file_is_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/mock-storage/local" 2 &&
+               test_file_count_in_dir ".git/mock-storage/remote" 2
+       )
+'
+
+test_expect_success 'Store files in Mock LFS based on extension (dat)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileExtensions dat &&
+               git config git-p4.largeFilePush True &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" &&
+               test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" &&
+               test_file_is_not_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" &&
+               test_file_is_not_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/mock-storage/local" 1 &&
+               test_file_count_in_dir ".git/mock-storage/remote" 1
+       )
+'
+
+test_expect_success 'Store files in Mock LFS based on extension (dat) and use git p4 sync and no client spec' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileExtensions dat &&
+               git config git-p4.largeFilePush True &&
+               git p4 sync //depot &&
+               git checkout p4/master &&
+
+               test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" &&
+               test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" &&
+               test_file_is_not_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" &&
+               test_file_is_not_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/mock-storage/local" 1 &&
+               test_file_count_in_dir ".git/mock-storage/remote" 1
+       )
+'
+
+test_expect_success 'Remove file from repo and store files in Mock LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 delete file4.bin &&
+               p4 submit -d "Remove file"
+       ) &&
+
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git config git-p4.largeFilePush True &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" &&
+               test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" &&
+               test_file_is_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" &&
+               test_file_is_deleted_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/mock-storage/local" 2 &&
+               test_file_count_in_dir ".git/mock-storage/remote" 2
+       )
+'
+
+test_expect_success 'Run git p4 submit in repo configured with large file system' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem MockLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git config git-p4.largeFilePush True &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_must_fail git p4 submit
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh
new file mode 100755 (executable)
index 0000000..0b664a3
--- /dev/null
@@ -0,0 +1,288 @@
+#!/bin/sh
+
+test_description='Clone repositories and store files in Git LFS'
+
+. ./lib-git-p4.sh
+
+git lfs help >/dev/null 2>&1 || {
+       skip_all='skipping git p4 Git LFS tests; Git LFS not found'
+       test_done
+}
+
+test_file_in_lfs () {
+       FILE="$1" &&
+       SIZE="$2" &&
+       EXPECTED_CONTENT="$3" &&
+       cat "$FILE" | grep "size $SIZE" &&
+       HASH=$(cat "$FILE" | grep "oid sha256:" | sed -e "s/oid sha256://g") &&
+       LFS_FILE=".git/lfs/objects/$(echo "$HASH" | cut -c1-2)/$(echo "$HASH" | cut -c3-4)/$HASH" &&
+       echo $EXPECTED_CONTENT >expect &&
+       test_path_is_file "$FILE" &&
+       test_path_is_file "$LFS_FILE" &&
+       test_cmp expect "$LFS_FILE"
+}
+
+test_file_count_in_dir () {
+       DIR="$1" &&
+       EXPECTED_COUNT="$2" &&
+       find "$DIR" -type f >actual &&
+       test_line_count = $EXPECTED_COUNT actual
+}
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'Create repo with binary files' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+
+               echo "content 1 txt 23 bytes" >file1.txt &&
+               p4 add file1.txt &&
+               echo "content 2-3 bin 25 bytes" >file2.dat &&
+               p4 add file2.dat &&
+               p4 submit -d "Add text and binary file" &&
+
+               mkdir "path with spaces" &&
+               echo "content 2-3 bin 25 bytes" >"path with spaces/file3.bin" &&
+               p4 add "path with spaces/file3.bin" &&
+               p4 submit -d "Add another binary file with same content and spaces in path" &&
+
+               echo "content 4 bin 26 bytes XX" >file4.bin &&
+               p4 add file4.bin &&
+               p4 submit -d "Add another binary file with different content"
+       )
+'
+
+test_expect_success 'Store files in LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" &&
+
+               test_file_count_in_dir ".git/lfs/objects" 2 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file2.dat filter=lfs -text
+               /file4.bin filter=lfs -text
+               /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Store files in LFS based on size (>25 bytes)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileThreshold 25 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" &&
+               test_file_count_in_dir ".git/lfs/objects" 1 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file4.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Store files in LFS based on extension (dat)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileExtensions dat &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_count_in_dir ".git/lfs/objects" 1 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               *.dat filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Store files in LFS based on size (>25 bytes) and extension (dat)' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileExtensions dat &&
+               git config git-p4.largeFileThreshold 25 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" &&
+               test_file_count_in_dir ".git/lfs/objects" 2 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               *.dat filter=lfs -text
+               /file4.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Remove file from repo and store files in LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               p4 delete file4.bin &&
+               p4 submit -d "Remove file"
+       ) &&
+
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" &&
+               test_path_is_missing file4.bin &&
+               test_file_count_in_dir ".git/lfs/objects" 2 &&
+
+               cat >expect <<-\EOF &&
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file2.dat filter=lfs -text
+               /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Add .gitattributes and store files in LFS based on size (>24 bytes)' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               echo "*.txt text" >.gitattributes &&
+               p4 add .gitattributes &&
+               p4 submit -d "Add .gitattributes"
+       ) &&
+
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileThreshold 24 &&
+               git p4 clone --destination="$git" //depot@all &&
+
+               test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" &&
+               test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" &&
+               test_path_is_missing file4.bin &&
+               test_file_count_in_dir ".git/lfs/objects" 2 &&
+
+               cat >expect <<-\EOF &&
+               *.txt text
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file2.dat filter=lfs -text
+               /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'Add big files to repo and store files in LFS based on compressed size (>28 bytes)' '
+       client_view "//depot/... //client/..." &&
+       (
+               cd "$cli" &&
+               echo "content 5 bin 40 bytes XXXXXXXXXXXXXXXX" >file5.bin &&
+               p4 add file5.bin &&
+               p4 submit -d "Add file with small footprint after compression" &&
+
+               echo "content 6 bin 39 bytes XXXXXYYYYYZZZZZ" >file6.bin &&
+               p4 add file6.bin &&
+               p4 submit -d "Add file with large footprint after compression"
+       ) &&
+
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git init . &&
+               git config git-p4.useClientSpec true &&
+               git config git-p4.largeFileSystem GitLFS &&
+               git config git-p4.largeFileCompressedThreshold 28 &&
+               # We only import HEAD here ("@all" is missing!)
+               git p4 clone --destination="$git" //depot &&
+
+               test_file_in_lfs file6.bin 13 "content 6 bin 39 bytes XXXXXYYYYYZZZZZ"
+               test_file_count_in_dir ".git/lfs/objects" 1 &&
+
+               cat >expect <<-\EOF &&
+               *.txt text
+
+               #
+               # Git LFS (see https://git-lfs.github.com/)
+               #
+               /file6.bin filter=lfs -text
+               EOF
+               test_path_is_file .gitattributes &&
+               test_cmp expect .gitattributes
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9825-git-p4-handle-utf16-without-bom.sh b/t/t9825-git-p4-handle-utf16-without-bom.sh
new file mode 100755 (executable)
index 0000000..1551845
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='git p4 handling of UTF-16 files without BOM'
+
+. ./lib-git-p4.sh
+
+UTF16="\227\000\227\000"
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot with UTF-16 encoded file and artificially remove BOM' '
+       (
+               cd "$cli" &&
+               printf "$UTF16" >file1 &&
+               p4 add -t utf16 file1 &&
+               p4 submit -d "file1"
+       ) &&
+
+       (
+               cd db &&
+               p4d -jc &&
+               # P4D automatically adds a BOM. Remove it here to make the file invalid.
+               sed -e "\$d" depot/file1,v >depot/file1,v.new &&
+               mv depot/file1,v.new depot/file1,v &&
+               printf "@$UTF16@" >>depot/file1,v &&
+               p4d -jrF checkpoint.1
+       )
+'
+
+test_expect_success 'clone depot with invalid UTF-16 file in verbose mode' '
+       git p4 clone --dest="$git" --verbose //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               printf "$UTF16" >expect &&
+               test_cmp_bin expect file1
+       )
+'
+
+test_expect_failure 'clone depot with invalid UTF-16 file in non-verbose mode' '
+       git p4 clone --dest="$git" //depot
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
index e8d3c0fdbc76d93ea18f6da1c869fc963e7ca81d..6dffb8bcde83b82fa40b05dd8ee815f4e6a3cdd3 100644 (file)
@@ -201,7 +201,14 @@ test_chmod () {
 
 # Unset a configuration variable, but don't fail if it doesn't exist.
 test_unconfig () {
-       git config --unset-all "$@"
+       config_dir=
+       if test "$1" = -C
+       then
+               shift
+               config_dir=$1
+               shift
+       fi
+       git ${config_dir:+-C "$config_dir"} config --unset-all "$@"
        config_status=$?
        case "$config_status" in
        5) # ok, nothing to unset
@@ -213,8 +220,15 @@ test_unconfig () {
 
 # Set git config, automatically unsetting it after the test is over.
 test_config () {
-       test_when_finished "test_unconfig '$1'" &&
-       git config "$@"
+       config_dir=
+       if test "$1" = -C
+       then
+               shift
+               config_dir=$1
+               shift
+       fi
+       test_when_finished "test_unconfig ${config_dir:+-C '$config_dir'} '$1'" &&
+       git ${config_dir:+-C "$config_dir"} config "$@"
 }
 
 test_config_global () {
@@ -722,6 +736,11 @@ test_seq () {
 # what went wrong.
 
 test_when_finished () {
+       # We cannot detect when we are in a subshell in general, but by
+       # doing so on Bash is better than nothing (the test will
+       # silently pass on other shells).
+       test "${BASH_SUBSHELL-0}" = 0 ||
+       error "bug in test script: test_when_finished does nothing in a subshell"
        test_cleanup="{ $*
                } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
 }
diff --git a/tag.c b/tag.c
index 5b0ac62ed846188ad44ceac41d278ff37ec8bd9d..5b2a06d92b75c7aea3f8cfb1cadb33d9da27f3c6 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -82,7 +82,7 @@ int parse_tag_buffer(struct tag *item, const void *data, unsigned long size)
        nl = memchr(bufptr, '\n', tail - bufptr);
        if (!nl || sizeof(type) <= (nl - bufptr))
                return -1;
-       strncpy(type, bufptr, nl - bufptr);
+       memcpy(type, bufptr, nl - bufptr);
        type[nl - bufptr] = '\0';
        bufptr = nl + 1;
 
index 54c0872fcb18edb4ca28a63fdfd4be3a17df388e..bb53c0aa655c7a2df09638024b304a79e8b07fc6 100644 (file)
@@ -47,7 +47,7 @@ static int dump_cache_tree(struct cache_tree *it,
                struct cache_tree_sub *rdwn;
 
                rdwn = cache_tree_sub(ref, down->name);
-               sprintf(path, "%s%.*s/", pfx, down->namelen, down->name);
+               xsnprintf(path, sizeof(path), "%s%.*s/", pfx, down->namelen, down->name);
                if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
                        errs = 1;
        }
index 3dd3744a57cffd2a98be5cd551cda475c746e646..c67bf65b347810c576f80a0ac33e3b84f42e1516 100644 (file)
@@ -21,8 +21,6 @@ static int normalize_ceiling_entry(struct string_list_item *item, void *unused)
        if (normalize_path_copy(buf, ceil) < 0)
                die("Path \"%s\" could not be normalized", ceil);
        len = strlen(buf);
-       if (len > 1 && buf[len-1] == '/')
-               die("Normalized path \"%s\" ended with slash", buf);
        free(item->string);
        item->string = xstrdup(buf);
        return 1;
diff --git a/trace.c b/trace.c
index 7393926ebcd95404fe16cdd84e40fd4f1bb02f40..4aeea60973100bfed212842cb46a5a81154b7781 100644 (file)
--- a/trace.c
+++ b/trace.c
@@ -277,25 +277,24 @@ void trace_performance_fl(const char *file, int line, uint64_t nanos,
 
 static const char *quote_crnl(const char *path)
 {
-       static char new_path[PATH_MAX];
-       const char *p2 = path;
-       char *p1 = new_path;
+       static struct strbuf new_path = STRBUF_INIT;
 
        if (!path)
                return NULL;
 
-       while (*p2) {
-               switch (*p2) {
-               case '\\': *p1++ = '\\'; *p1++ = '\\'; break;
-               case '\n': *p1++ = '\\'; *p1++ = 'n'; break;
-               case '\r': *p1++ = '\\'; *p1++ = 'r'; break;
+       strbuf_reset(&new_path);
+
+       while (*path) {
+               switch (*path) {
+               case '\\': strbuf_addstr(&new_path, "\\\\"); break;
+               case '\n': strbuf_addstr(&new_path, "\\n"); break;
+               case '\r': strbuf_addstr(&new_path, "\\r"); break;
                default:
-                       *p1++ = *p2;
+                       strbuf_addch(&new_path, *path);
                }
-               p2++;
+               path++;
        }
-       *p1 = '\0';
-       return new_path;
+       return new_path.buf;
 }
 
 /* FIXME: move prefix to startup_info struct and get rid of this arg */
index 863eb524f9087302be6b99c89f071195dd1258b8..23b2ed6f0cf6f521c4d5afa6c1cffe0786d29a5f 100644 (file)
@@ -654,23 +654,24 @@ static void print_ok_ref_status(struct ref *ref, int porcelain)
                        "[new branch]"),
                        ref, ref->peer_ref, NULL, porcelain);
        else {
-               char quickref[84];
+               struct strbuf quickref = STRBUF_INIT;
                char type;
                const char *msg;
 
-               strcpy(quickref, status_abbrev(ref->old_sha1));
+               strbuf_addstr(&quickref, status_abbrev(ref->old_sha1));
                if (ref->forced_update) {
-                       strcat(quickref, "...");
+                       strbuf_addstr(&quickref, "...");
                        type = '+';
                        msg = "forced update";
                } else {
-                       strcat(quickref, "..");
+                       strbuf_addstr(&quickref, "..");
                        type = ' ';
                        msg = NULL;
                }
-               strcat(quickref, status_abbrev(ref->new_sha1));
+               strbuf_addstr(&quickref, status_abbrev(ref->new_sha1));
 
-               print_ref_status(type, quickref, ref, ref->peer_ref, msg, porcelain);
+               print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg, porcelain);
+               strbuf_release(&quickref);
        }
 }
 
index f932e80e862cfafb7b909059b460dcb688c6dc0b..8e2032f4e592910d6f3336f95019647992113877 100644 (file)
@@ -1350,9 +1350,7 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,
         * Then we need to make sure that we do not lose a locally
         * present file that is not ignored.
         */
-       pathbuf = xmalloc(namelen + 2);
-       memcpy(pathbuf, ce->name, namelen);
-       strcpy(pathbuf+namelen, "/");
+       pathbuf = xstrfmt("%.*s/", namelen, ce->name);
 
        memset(&d, 0, sizeof(d));
        if (o->dir)
diff --git a/url.c b/url.c
index 7ca2a69e1091fc57cb292052015c920499fe3379..2d89ad190cfe1c57cd20194661329551ff229993 100644 (file)
--- a/url.c
+++ b/url.c
@@ -120,8 +120,7 @@ char *url_decode_parameter_value(const char **query)
 void end_url_with_slash(struct strbuf *buf, const char *url)
 {
        strbuf_addstr(buf, url);
-       if (buf->len && buf->buf[buf->len - 1] != '/')
-               strbuf_addch(buf, '/');
+       strbuf_complete(buf, '/');
 }
 
 void str_end_url_with_slash(const char *url, char **dest) {
diff --git a/utf8.c b/utf8.c
index 28e6d76a425db4c16a8cc32e6f17c7aa72c2b1f0..00e10c86ad7ea7bcc2d53a90a2f608dd66d47462 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -644,3 +644,24 @@ int skip_utf8_bom(char **text, size_t len)
        *text += strlen(utf8_bom);
        return 1;
 }
+
+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+                      const char *s)
+{
+       int slen = strlen(s);
+       int display_len = utf8_strnwidth(s, slen, 0);
+       int utf8_compensation = slen - display_len;
+
+       if (display_len >= width) {
+               strbuf_addstr(buf, s);
+               return;
+       }
+
+       if (position == ALIGN_LEFT)
+               strbuf_addf(buf, "%-*s", width + utf8_compensation, s);
+       else if (position == ALIGN_MIDDLE) {
+               int left = (width - display_len) / 2;
+               strbuf_addf(buf, "%*s%-*s", left, "", width - left + utf8_compensation, s);
+       } else if (position == ALIGN_RIGHT)
+               strbuf_addf(buf, "%*s", width + utf8_compensation, s);
+}
diff --git a/utf8.h b/utf8.h
index 5a9e94bee62fd2ed62dcbc82aee12bfc6d75e254..7930b44f19c701cc671d9639289782abb1812034 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -55,4 +55,19 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
  */
 int is_hfs_dotgit(const char *path);
 
+typedef enum {
+       ALIGN_LEFT,
+       ALIGN_MIDDLE,
+       ALIGN_RIGHT
+} align_type;
+
+/*
+ * Align the string given and store it into a strbuf as per the
+ * 'position' and 'width'. If the given string length is larger than
+ * 'width' than then the input string is not truncated and no
+ * alignment is done.
+ */
+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+                      const char *s);
+
 #endif
index 44a936c1cfddfac538afa66e76cea295577b136d..cdeb63f319abc088fe54e4291ed765900d16f1fd 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -17,10 +17,9 @@ void walker_say(struct walker *walker, const char *fmt, const char *hex)
 
 static void report_missing(const struct object *obj)
 {
-       char missing_hex[41];
-       strcpy(missing_hex, sha1_to_hex(obj->sha1));
        fprintf(stderr, "Cannot obtain needed %s %s\n",
-               obj->type ? typename(obj->type): "object", missing_hex);
+               obj->type ? typename(obj->type): "object",
+               sha1_to_hex(obj->sha1));
        if (!is_null_sha1(current_commit_sha1))
                fprintf(stderr, "while processing commit %s.\n",
                        sha1_to_hex(current_commit_sha1));
index 0e22d4381438b2c262e1cd5ee0cc4effc2fb3c3d..6fcaa4dc62b504078ea5202cfd7a096e79a83c5e 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -621,6 +621,22 @@ char *xgetcwd(void)
        return strbuf_detach(&sb, NULL);
 }
 
+int xsnprintf(char *dst, size_t max, const char *fmt, ...)
+{
+       va_list ap;
+       int len;
+
+       va_start(ap, fmt);
+       len = vsnprintf(dst, max, fmt, ap);
+       va_end(ap);
+
+       if (len < 0)
+               die("BUG: your snprintf is broken");
+       if (len >= max)
+               die("BUG: attempt to snprintf into too-small buffer");
+       return len;
+}
+
 static int write_file_v(const char *path, int fatal,
                        const char *fmt, va_list params)
 {
index c327fe8128fdb2e380db15f526056a22862ac33f..3e3b8c098924d655bccb395e95cee832abb1d39f 100644 (file)
@@ -1319,6 +1319,12 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
        hashcpy(cb->nsha1, nsha1);
        for (end = target; *end && *end != '\n'; end++)
                ;
+       if (!memcmp(target, "HEAD", end - target)) {
+               /* HEAD is relative. Resolve it to the right reflog entry. */
+               strbuf_addstr(&cb->buf,
+                             find_unique_abbrev(nsha1, DEFAULT_ABBREV));
+               return 1;
+       }
        strbuf_add(&cb->buf, target, end - target);
        return 1;
 }