]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jk/maint-status-porcelain-z-b' into HEAD
authorJeff King <peff@peff.net>
Tue, 8 May 2012 08:55:35 +0000 (04:55 -0400)
committerJeff King <peff@peff.net>
Tue, 8 May 2012 08:55:35 +0000 (04:55 -0400)
* jk/maint-status-porcelain-z-b:
  status: respect "-b" for porcelain format
  status: fix null termination with "-b"
  status: refactor null_termination option
  commit: refactor option parsing

Conflicts:
wt-status.h

357 files changed:
.gitignore
Documentation/Makefile
Documentation/RelNotes/1.7.10.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.11.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-generate-patch.txt
Documentation/diff-options.txt
Documentation/everyday.txt
Documentation/git-am.txt
Documentation/git-archive.txt
Documentation/git-blame.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-check-ref-format.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-column.txt [new file with mode: 0644]
Documentation/git-commit.txt
Documentation/git-cvsserver.txt
Documentation/git-difftool.txt
Documentation/git-fast-export.txt
Documentation/git-fast-import.txt
Documentation/git-filter-branch.txt
Documentation/git-format-patch.txt
Documentation/git-gc.txt
Documentation/git-grep.txt
Documentation/git-log.txt
Documentation/git-notes.txt
Documentation/git-p4.txt
Documentation/git-pack-refs.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-rebase.txt
Documentation/git-reflog.txt
Documentation/git-remote-helpers.txt
Documentation/git-remote.txt
Documentation/git-rerere.txt
Documentation/git-reset.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-rm.txt
Documentation/git-shortlog.txt
Documentation/git-show-ref.txt
Documentation/git-show.txt
Documentation/git-stash.txt
Documentation/git-status.txt
Documentation/git-tag.txt
Documentation/git-tar-tree.txt
Documentation/git-update-index.txt
Documentation/git-var.txt
Documentation/git-whatchanged.txt
Documentation/gitcli.txt
Documentation/gitcore-tutorial.txt
Documentation/gitcredentials.txt
Documentation/gitdiffcore.txt
Documentation/githooks.txt
Documentation/gitweb.conf.txt
Documentation/gitworkflows.txt
Documentation/pretty-formats.txt
Documentation/pull-fetch-param.txt
Documentation/rev-list-options.txt
Documentation/technical/api-argv-array.txt
Documentation/technical/api-parse-options.txt
Documentation/technical/api-revision-walking.txt
Documentation/technical/index-format.txt
Documentation/technical/protocol-common.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
INSTALL
Makefile
RelNotes
advice.c
advice.h
argv-array.c
argv-array.h
branch.c
branch.h
builtin.h
builtin/apply.c
builtin/branch.c
builtin/cat-file.c
builtin/checkout.c
builtin/column.c [new file with mode: 0644]
builtin/commit.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/fsck.c
builtin/gc.c
builtin/help.c
builtin/index-pack.c
builtin/log.c
builtin/merge-file.c
builtin/pack-objects.c
builtin/push.c
builtin/remote.c
builtin/rev-parse.c
builtin/revert.c
builtin/send-pack.c
builtin/tag.c
builtin/unpack-objects.c
builtin/update-index.c
builtin/update-server-info.c
bundle.c
cache.h
column.c [new file with mode: 0644]
column.h [new file with mode: 0644]
command-list.txt
commit.c
commit.h
compat/mingw.h
compat/win32mmap.c
config.c
configure.ac
contrib/completion/git-completion.bash
contrib/examples/builtin-fetch--tool.c
contrib/fast-import/git-p4.README [new file with mode: 0644]
contrib/fast-import/git-p4.bat [deleted file]
contrib/rerere-train.sh
contrib/subtree/.gitignore [new file with mode: 0644]
contrib/subtree/COPYING [new file with mode: 0644]
contrib/subtree/INSTALL [new file with mode: 0644]
contrib/subtree/Makefile [new file with mode: 0644]
contrib/subtree/README [new file with mode: 0644]
contrib/subtree/git-subtree.sh [new file with mode: 0755]
contrib/subtree/git-subtree.txt [new file with mode: 0644]
contrib/subtree/t/Makefile [new file with mode: 0644]
contrib/subtree/t/t7900-subtree.sh [new file with mode: 0755]
contrib/subtree/todo [new file with mode: 0644]
date.c
diff.c
diff.h
entry.c
environment.c
fsck.c
generate-cmdlist.sh
git-am.sh
git-difftool--helper.sh
git-difftool.perl
git-p4.py [moved from contrib/fast-import/git-p4 with 88% similarity]
git-rebase--am.sh
git-rebase--interactive.sh
git-rebase.sh
git-relink.perl
git-remote-testgit.py
git-repack.sh
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git.c
gitweb/gitweb.perl
gitweb/static/gitweb.css
graph.c
help.c
help.h
http-backend.c
http-push.c
http.c
imap-send.c
ll-merge.c
log-tree.c
merge-recursive.c
mergesort.c [new file with mode: 0644]
mergesort.h [new file with mode: 0644]
object.c
object.h
pager.c
parse-options.h
po/de.po
po/git.pot
po/zh_CN.po
read-cache.c
refs.c
refs.h
remote-curl.c
revision.c
revision.h
run-command.c
sequencer.c
sequencer.h
setup.c
sha1_file.c
sha1_name.c
strbuf.c
strbuf.h
streaming.c
streaming.h
submodule.c
submodule.h
t/gitweb-lib.sh
t/lib-git-daemon.sh
t/lib-git-p4.sh
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/t0023-crlf-am.sh
t/t0062-revision-walking.sh [new file with mode: 0755]
t/t0303-credential-external.sh
t/t1050-large.sh
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1305-config-include.sh
t/t1410-reflog.sh
t/t1501-worktree.sh
t/t1507-rev-parse-upstream.sh
t/t2004-checkout-cache-temp.sh
t/t2020-checkout-detach.sh
t/t2030-unresolve-info.sh
t/t3200-branch.sh
t/t3300-funny-names.sh
t/t3404-rebase-interactive.sh
t/t3415-rebase-autosquash.sh
t/t3505-cherry-pick-empty.sh
t/t3508-cherry-pick-many-commits.sh
t/t3900-i18n-commit.sh
t/t3903-stash.sh
t/t4006-diff-mode.sh
t/t4012-diff-binary.sh
t/t4013-diff-various.sh
t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side
t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
t/t4013/diff.diff-tree_--cc_--stat_--summary_master
t/t4013/diff.diff-tree_--cc_--stat_--summary_side
t/t4013/diff.diff-tree_--cc_--stat_master
t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side
t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial
t/t4013/diff.diff-tree_--pretty_--root_--stat_initial
t/t4013/diff.diff-tree_--root_--patch-with-stat_initial
t/t4013/diff.diff-tree_-c_--stat_--summary_master
t/t4013/diff.diff-tree_-c_--stat_--summary_side
t/t4013/diff.diff-tree_-c_--stat_master
t/t4013/diff.diff_--patch-with-stat_-r_initial..side
t/t4013/diff.diff_--patch-with-stat_initial..side
t/t4013/diff.diff_--stat_initial..side
t/t4013/diff.diff_-r_--stat_initial..side
t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side
t/t4013/diff.format-patch_--attach_--stdout_initial..master
t/t4013/diff.format-patch_--attach_--stdout_initial..master^
t/t4013/diff.format-patch_--attach_--stdout_initial..side
t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
t/t4013/diff.format-patch_--inline_--stdout_initial..master
t/t4013/diff.format-patch_--inline_--stdout_initial..master^
t/t4013/diff.format-patch_--inline_--stdout_initial..master^^
t/t4013/diff.format-patch_--inline_--stdout_initial..side
t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
t/t4013/diff.format-patch_--stdout_--no-numbered_initial..master
t/t4013/diff.format-patch_--stdout_--numbered_initial..master
t/t4013/diff.format-patch_--stdout_initial..master
t/t4013/diff.format-patch_--stdout_initial..master^
t/t4013/diff.format-patch_--stdout_initial..side
t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.log_--patch-with-stat_master
t/t4013/diff.log_--patch-with-stat_master_--_dir_
t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_master
t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
t/t4013/diff.show_--patch-with-stat_--summary_side
t/t4013/diff.show_--patch-with-stat_side
t/t4013/diff.show_--stat_--summary_side
t/t4013/diff.show_--stat_side
t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.whatchanged_--patch-with-stat_master
t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_--patch-with-stat_master
t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
t/t4014-format-patch.sh
t/t4016-diff-quote.sh
t/t4030-diff-textconv.sh
t/t4031-diff-rewrite-binary.sh
t/t4035-diff-quiet.sh
t/t4043-diff-rename-binary.sh
t/t4045-diff-relative.sh
t/t4047-diff-dirstat.sh
t/t4049-diff-stat-count.sh
t/t4052-stat-output.sh
t/t4100-apply-stat.sh
t/t4150-am.sh
t/t4202-log.sh
t/t4205-log-pretty-formats.sh
t/t5100-mailinfo.sh
t/t5100/patch0001
t/t5100/patch0002
t/t5100/patch0003
t/t5100/patch0005
t/t5100/patch0006
t/t5100/patch0010
t/t5100/patch0011
t/t5100/patch0014
t/t5100/patch0014--scissors
t/t5100/sample.mbox
t/t5150-request-pull.sh
t/t5528-push-default.sh
t/t5531-deep-submodule-push.sh
t/t5541-http-push.sh
t/t5570-git-daemon.sh
t/t5700-clone-reference.sh
t/t5710-info-alternate.sh
t/t5800-remote-helpers.sh
t/t6006-rev-list-format.sh
t/t6030-bisect-porcelain.sh
t/t6032-merge-large-rename.sh
t/t6040-tracking-info.sh
t/t6042-merge-rename-corner-cases.sh
t/t6200-fmt-merge-msg.sh
t/t7004-tag.sh
t/t7201-co.sh
t/t7400-submodule-basic.sh
t/t7408-submodule-reference.sh
t/t7502-commit.sh
t/t7508-status.sh
t/t7602-merge-octopus-many.sh
t/t7607-merge-overwrite.sh
t/t7701-repack-unpack-unreachable.sh
t/t7800-difftool.sh
t/t9002-column.sh [new file with mode: 0755]
t/t9300-fast-import.sh
t/t9350-fast-export.sh
t/t9400-git-cvsserver-server.sh
t/t9501-gitweb-standalone-http-status.sh
t/t9800-git-p4-basic.sh
t/t9801-git-p4-branch.sh
t/t9802-git-p4-filetype.sh
t/t9803-git-p4-shell-metachars.sh
t/t9804-git-p4-label.sh
t/t9805-git-p4-skip-submit-edit.sh
t/t9806-git-p4-options.sh
t/t9807-git-p4-submit.sh
t/t9808-git-p4-chdir.sh
t/t9809-git-p4-client-view.sh
t/t9810-git-p4-rcs.sh
t/t9811-git-p4-label-import.sh [new file with mode: 0755]
t/t9902-completion.sh [new file with mode: 0755]
t/test-lib.sh
test-date.c
test-mergesort.c [new file with mode: 0644]
test-revision-walking.c [new file with mode: 0644]
transport-helper.c
transport.c
transport.h
unpack-trees.c
varint.c [new file with mode: 0644]
varint.h [new file with mode: 0644]
vcs-svn/svndump.c
wrapper.c
wt-status.c
wt-status.h
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xhistogram.c
xdiff/xpatience.c
xdiff/xprepare.c
xdiff/xutils.c

index 87fcc5f6ff2e180280ff767fd291247739c7d0fa..bf66648e2c5f59cb92470dbda546c5348bcc85ef 100644 (file)
@@ -26,6 +26,7 @@
 /git-cherry-pick
 /git-clean
 /git-clone
+/git-column
 /git-commit
 /git-commit-tree
 /git-config
@@ -92,6 +93,7 @@
 /git-name-rev
 /git-mv
 /git-notes
+/git-p4
 /git-pack-redundant
 /git-pack-objects
 /git-pack-refs
 /test-index-version
 /test-line-buffer
 /test-match-trees
+/test-mergesort
 /test-mktemp
 /test-parse-options
 /test-path-utils
+/test-revision-walking
 /test-run-command
 /test-sha1
 /test-sigchain
index d40e211f22dbe6c102a102041ac9f0779c7cb837..14286cb65761ae93cb34195c1ce60f85caa510c1 100644 (file)
@@ -82,7 +82,7 @@ endif
 #
 
 ifndef ASCIIDOC7
-ASCIIDOC_EXTRA += -a asciidoc7compatible -a no-inline-literal
+ASCIIDOC_EXTRA += -a asciidoc7compatible
 endif
 ifdef DOCBOOK_XSL_172
 ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
@@ -124,6 +124,16 @@ SHELL_PATH ?= $(SHELL)
 # Shell quote;
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 
+ifdef DEFAULT_PAGER
+DEFAULT_PAGER_SQ = $(subst ','\'',$(DEFAULT_PAGER))
+ASCIIDOC_EXTRA += -a 'git-default-pager=$(DEFAULT_PAGER_SQ)'
+endif
+
+ifdef DEFAULT_EDITOR
+DEFAULT_EDITOR_SQ = $(subst ','\'',$(DEFAULT_EDITOR))
+ASCIIDOC_EXTRA += -a 'git-default-editor=$(DEFAULT_EDITOR_SQ)'
+endif
+
 #
 # Please note that there is a minor bug in asciidoc.
 # The version after 6.0.3 _will_ include the patch found here:
diff --git a/Documentation/RelNotes/1.7.10.2.txt b/Documentation/RelNotes/1.7.10.2.txt
new file mode 100644 (file)
index 0000000..04fe294
--- /dev/null
@@ -0,0 +1,46 @@
+Git v1.7.10.2 Release Notes
+===========================
+
+Fixes since v1.7.10.1
+---------------------
+
+ * The test scaffolding for git-daemon was flaky.
+
+ * The test scaffolding for fast-import was flaky.
+
+ * The filesystem boundary was not correctly reported when .git directory
+   discovery stopped at a mount point.
+
+ * HTTP transport that requires authentication did not work correctly when
+   multiple connections are used simultaneously.
+
+ * In the older days, the header "Conflicts:" in "cherry-pick" and "merge"
+   was separated by a blank line from the list of paths that follow for
+   readability, but when "merge" was rewritten in C, we lost it by
+   mistake. Remove the newline from "cherry-pick" to make them match
+   again.
+
+ * The command line parser choked "git cherry-pick $name" when $name can
+   be both revision name and a pathname, even though $name can never be a
+   path in the context of the command.
+
+ * "git config --rename-section" to rename an existing section into a
+   bogus one did not check the new name.
+
+ * The "diff --no-index" codepath used limited-length buffers, risking
+   pathnames getting truncated.  Update it to use the strbuf API.
+
+ * The report from "git fetch" said "new branch" even for a non branch
+   ref.
+
+ * Octopus merge strategy did not reduce heads that are recorded in the
+   final commit correctly.
+
+ * The i18n of error message "git stash save" was not properly done.
+
+ * When using a Perl script on a system where "perl" found on user's
+   $PATH could be ancient or otherwise broken, we allow builders to
+   specify the path to a good copy of Perl with $PERL_PATH.  The
+   gitweb test forgot to use that Perl when running its test.
+
+Also contains minor fixes and documentation updates.
diff --git a/Documentation/RelNotes/1.7.11.txt b/Documentation/RelNotes/1.7.11.txt
new file mode 100644 (file)
index 0000000..8dfeddc
--- /dev/null
@@ -0,0 +1,145 @@
+Git v1.7.11 Release Notes
+=========================
+
+Updates since v1.7.10
+---------------------
+
+UI, Workflows & Features
+
+ * A new mode for push, "simple", which is a cross between "current"
+   and "upstream", has been introduced. "git push" without any refspec
+   will push the current branch out to the same name at the remote
+   repository only when it is set to track the branch with the same
+   name over there.  The plan is to make this mode the new default
+   value when push.default is not configured.
+
+ * A couple of commands learned the "--column" option to produce
+   columnar output.
+
+ * A third-party tool "git subtree" is distributed in contrib/
+
+ * Error messages given when @{u} is used for a branch without its
+   upstream configured have been clatified.
+
+ * Even with "-q"uiet option, "checkout" used to report setting up
+   tracking.  Also "branch" learned the "-q"uiet option to squelch
+   informational message.
+
+ * The smart-http backend used to always override GIT_COMMITTER_*
+   variables with REMOTE_USER and REMOTE_ADDR, but these variables are
+   now preserved when set.
+
+ * "include.path" mechanism of the configuration files learned to
+   understand "~/path" and "~user/path".
+
+ * "git am" learned the "--include" option, which is an opposite of
+   existing the "--exclude" option.
+
+ * When "git am -3" needs to fall back to an application to a
+   synthesized preimage followed by a 3-way merge, the paths that
+   needed such treatment are now reported to the end user, so that the
+   result in them can be eyeballed with extra care.
+
+ * The output from "diff/log --stat" used to always allocate 4 columns
+   to show the number of modified lines, but not anymore.
+
+ * "git difftool" learned the "--dir-diff" option to spawn external
+   diff tools that can compare two directory hierarchies at a time
+   after populating two temporary directories, instead of running an
+   instance of the external tool once per a file pair.
+
+ * The "fmt-merge-msg" command learns to list the primary contributors
+   involved in the side topic you are merging.
+
+ * The cases "git push" fails due to non-ff can be broken into three
+   categories; each case is given a separate advise message.
+
+ * "git rebase" learned to optionally keep commits that do not
+   introduce any change in the original history.
+
+ * "git push --recurse-submodules" learned to optionally look into the
+   histories of submodules bound to the superproject and push them
+   out.
+
+ * A 'snapshot' request to "gitweb" honors If-Modified-Since: header,
+   based on the commit date.
+
+ * "gitweb" learned to highlight the patch it outputs even more.
+
+Foreign Interface
+
+ * "git svn" used to die with unwanted SIGPIPE when talking with HTTP
+   server that uses keep-alive.
+
+ * "git svn" learned to use platform specific authentication
+   providers, e.g. gnome-keyring, kwallet, etc.
+
+ * "git p4" has been moved out of contrib/ area and has seen more work
+   on importing labels as tags from (and exporting tags as labels to)
+   p4.
+
+Performance and Internal Implementation (please report possible regressions)
+
+ * An experimental "version 4" format of the index file has been
+   introduced to reduce on-disk footprint and I/O overhead.
+
+ * The code to compute hash values for lines used by the internal diff
+   engine was optimized on little-endian machines, using the same
+   trick the kernel folks came up with.
+
+ * "git apply" had some memory leaks plugged.
+
+ * "git repack" used to write out unreachable objects as loose objects
+   when repacking, even if such loose objects will immediately pruned
+   due to its age.
+
+ * Setting up a revision traversal with many starting points was
+   inefficient as these were placed in a date-order priority queue
+   one-by-one.  Now they are collected in the queue unordered first,
+   and sorted immediately before getting used.
+
+ * "git rev-parse --show-prefix" used to emit nothing when run at the
+   top-level of the working tree, but now it gives a blank line.
+
+ * Minor memory leak during unpack_trees (hence "merge" and "checkout"
+   to check out another branch) has been plugged.
+
+ * More lower-level commands learned to use the streaming API to read
+   from the object store without keeping everything in core.
+
+ * Because "sh" on the user's PATH may be utterly broken on some
+   systems, run-command API now uses SHELL_PATH, not /bin/sh, when
+   spawning an external command (not applicable to Windows port).
+
+ * The API to iterate over refs/ hierarchy has been tweaked to allow
+   walking only a subset of it more efficiently.
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.10
+-------------------
+
+Unless otherwise noted, all the fixes since v1.7.10 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
+
+ * "git diff --stat" used to fully count a binary file with modified
+   execution bits whose contents is unmodified, which was not quite
+   right.
+
+ * "log -z --pretty=tformat:..." did not terminate each record with
+   NUL.  The fix is not entirely correct when the output also asks for
+   --patch and/or --stat, though.
+   (merge fafd382 jk/maint-tformat-with-z later to maint).
+
+ * "git push" over smart-http lost progress output a few releases ago.
+   (merge e304aeb jk/maint-push-progress later to maint).
+
+ * A contrib script "rerere-train" did not work out of the box unless
+   user futzed with her $PATH.
+   (merge 53876fc jc/rerere-train later to maint).
+
+ * "log --graph" was not very friendly with "--stat" option and its
+   output had line breaks at wrong places.
+   (merge bafa16e lp/diffstat-with-graph later to maint).
index c081657be774a70b453f493be11fbfb670452e86..915cb5a547896966377e9f39059527e3142b27c8 100644 (file)
@@ -95,7 +95,9 @@ included file is expanded immediately, as if its contents had been
 found at the location of the include directive. If the value of the
 `include.path` variable is a relative path, the path is considered to be
 relative to the configuration file in which the include directive was
-found. See below for examples.
+found. The value of `include.path` is subject to tilde expansion: `~/`
+is expanded to the value of `$HOME`, and `~user/` to the specified
+user's home directory. See below for examples.
 
 Example
 ~~~~~~~
@@ -122,6 +124,7 @@ Example
        [include]
                path = /path/to/foo.inc ; include by absolute path
                path = foo ; expand "foo" relative to the current file
+               path = ~/foo ; expand "foo" in your $HOME directory
 
 Variables
 ~~~~~~~~~
@@ -138,8 +141,23 @@ advice.*::
 +
 --
        pushNonFastForward::
-               Advice shown when linkgit:git-push[1] refuses
-               non-fast-forward refs.
+               Set this variable to 'false' if you want to disable
+               'pushNonFFCurrent', 'pushNonFFDefault', and
+               'pushNonFFMatching' simultaneously.
+       pushNonFFCurrent::
+               Advice shown when linkgit:git-push[1] fails due to a
+               non-fast-forward update to the current branch.
+       pushNonFFDefault::
+               Advice to set 'push.default' to 'upstream' or 'current'
+               when you ran linkgit:git-push[1] and pushed 'matching
+               refs' by default (i.e. you did not provide an explicit
+               refspec, and no 'push.default' configuration was set)
+               and it resulted in a non-fast-forward error.
+       pushNonFFMatching::
+               Advice shown when you ran linkgit:git-push[1] and pushed
+               'matching refs' explicitly (i.e. you used ':', or
+               specified a refspec that isn't your current branch) and
+               it resulted in a non-fast-forward error.
        statusHints::
                Directions on how to stage/unstage/add shown in the
                output of linkgit:git-status[1] and the template shown
@@ -463,8 +481,8 @@ Common unit suffixes of 'k', 'm', or 'g' are supported.
 core.excludesfile::
        In addition to '.gitignore' (per-directory) and
        '.git/info/exclude', git looks into this file for patterns
-       of files which are not meant to be tracked.  "{tilde}/" is expanded
-       to the value of `$HOME` and "{tilde}user/" to the specified user's
+       of files which are not meant to be tracked.  "`~/`" is expanded
+       to the value of `$HOME` and "`~user/`" to the specified user's
        home directory.  See linkgit:gitignore[5].
 
 core.askpass::
@@ -838,6 +856,44 @@ color.ui::
        `never` if you prefer git commands not to use color unless enabled
        explicitly with some other configuration or the `--color` option.
 
+column.ui::
+       Specify whether supported commands should output in columns.
+       This variable consists of a list of tokens separated by spaces
+       or commas:
++
+--
+`always`;;
+       always show in columns
+`never`;;
+       never show in columns
+`auto`;;
+       show in columns if the output is to the terminal
+`column`;;
+       fill columns before rows (default)
+`row`;;
+       fill rows before columns
+`plain`;;
+       show in one column
+`dense`;;
+       make unequal size columns to utilize more space
+`nodense`;;
+       make equal size columns
+--
++
+       This option defaults to 'never'.
+
+column.branch::
+       Specify whether to output branch listing in `git branch` in columns.
+       See `column.ui` for details.
+
+column.status::
+       Specify whether to output untracked files in `git status` in columns.
+       See `column.ui` for details.
+
+column.tag::
+       Specify whether to output tag listing in `git tag` in columns.
+       See `column.ui` for details.
+
 commit.status::
        A boolean to enable/disable inclusion of status information in the
        commit message template when using an editor to prepare the commit
@@ -845,7 +901,7 @@ commit.status::
 
 commit.template::
        Specify a file to use as the template for new commit messages.
-       "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
+       "`~/`" is expanded to the value of `$HOME` and "`~user/`" to the
        specified user's home directory.
 
 credential.helper::
@@ -970,7 +1026,7 @@ format.thread::
        a boolean value, or `shallow` or `deep`.  `shallow` threading
        makes every mail a reply to the head of the series,
        where the head is chosen from the cover letter, the
-       `\--in-reply-to`, and the first patch mail, in this order.
+       `--in-reply-to`, and the first patch mail, in this order.
        `deep` threading makes every mail a reply to the previous one.
        A true boolean value is the same as `shallow`, and a false
        value disables threading.
@@ -1401,7 +1457,7 @@ instaweb.port::
 interactive.singlekey::
        In interactive commands, allow the user to provide one-letter
        input with a single key (i.e., without hitting enter).
-       Currently this is used by the `\--patch` mode of
+       Currently this is used by the `--patch` mode of
        linkgit:git-add[1], linkgit:git-checkout[1], linkgit:git-commit[1],
        linkgit:git-reset[1], and linkgit:git-stash[1]. Note that this
        setting is silently ignored if portable keystroke input
@@ -1409,13 +1465,13 @@ interactive.singlekey::
 
 log.abbrevCommit::
        If true, makes linkgit:git-log[1], linkgit:git-show[1], and
-       linkgit:git-whatchanged[1] assume `\--abbrev-commit`. You may
-       override this option with `\--no-abbrev-commit`.
+       linkgit:git-whatchanged[1] assume `--abbrev-commit`. You may
+       override this option with `--no-abbrev-commit`.
 
 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`,
+       `--date` option.  Possible values are `relative`, `local`,
        `default`, `iso`, `rfc`, and `short`; see linkgit:git-log[1]
        for details.
 
@@ -1605,18 +1661,18 @@ pack.indexVersion::
        and this config option ignored whenever the corresponding pack is
        larger than 2 GB.
 +
-If you have an old git that does not understand the version 2 `{asterisk}.idx` file,
+If you have an old git that does not understand the version 2 `*.idx` file,
 cloning or fetching over a non native protocol (e.g. "http" and "rsync")
-that will copy both `{asterisk}.pack` file and corresponding `{asterisk}.idx` file from the
+that will copy both `*.pack` file and corresponding `*.idx` file from the
 other side may give you a repository that cannot be accessed with your
-older version of git. If the `{asterisk}.pack` file is smaller than 2 GB, however,
+older version of git. If the `*.pack` file is smaller than 2 GB, however,
 you can use linkgit:git-index-pack[1] on the *.pack file to regenerate
-the `{asterisk}.idx` file.
+the `*.idx` file.
 
 pack.packSizeLimit::
        The maximum size of a pack.  This setting only affects
        packing to a file when repacking, i.e. the git:// protocol
-       is unaffected.  It can be overridden by the `\--max-pack-size`
+       is unaffected.  It can be overridden by the `--max-pack-size`
        option of linkgit:git-repack[1]. The minimum size allowed is
        limited to 1 MiB. The default is unlimited.
        Common unit suffixes of 'k', 'm', or 'g' are
@@ -1626,8 +1682,8 @@ pager.<cmd>::
        If the value is boolean, turns on or off pagination of the
        output of a particular git subcommand when writing to a tty.
        Otherwise, turns on pagination for the subcommand using the
-       pager specified by the value of `pager.<cmd>`.  If `\--paginate`
-       or `\--no-pager` is specified on the command line, it takes
+       pager specified by the value of `pager.<cmd>`.  If `--paginate`
+       or `--no-pager` is specified on the command line, it takes
        precedence over this option.  To disable pagination for all
        commands, set `core.pager` or `GIT_PAGER` to `cat`.
 
@@ -1635,9 +1691,9 @@ pretty.<name>::
        Alias for a --pretty= format string, as specified in
        linkgit:git-log[1]. Any aliases defined here can be used just
        as the built-in pretty formats could. For example,
-       running `git config pretty.changelog "format:{asterisk} %H %s"`
+       running `git config pretty.changelog "format:* %H %s"`
        would cause the invocation `git log --pretty=changelog`
-       to be equivalent to running `git log "--pretty=format:{asterisk} %H %s"`.
+       to be equivalent to running `git log "--pretty=format:* %H %s"`.
        Note that an alias with the same name as a built-in format
        will be silently ignored.
 
@@ -1665,12 +1721,30 @@ push.default::
        line. Possible values are:
 +
 * `nothing` - do not push anything.
-* `matching` - push all matching branches.
-  All branches having the same name in both ends are considered to be
-  matching. This is the default.
+* `matching` - push all branches having the same name in both ends.
+  This is for those who prepare all the branches into a publishable
+  shape and then push them out with a single command.  It is not
+  appropriate for pushing into a repository shared by multiple users,
+  since locally stalled branches will attempt a non-fast forward push
+  if other users updated the branch.
+  +
+  This is currently the default, but Git 2.0 will change the default
+  to `simple`.
 * `upstream` - push the current branch to its upstream branch.
-* `tracking` - deprecated synonym for `upstream`.
+  With this, `git push` will update the same remote ref as the one which
+  is merged by `git pull`, making `push` and `pull` symmetrical.
+  See "branch.<name>.merge" for how to configure the upstream branch.
+* `simple` - like `upstream`, but refuses to push if the upstream
+  branch's name is different from the local one. This is the safest
+  option and is well-suited for beginners. It will become the default
+  in Git 2.0.
 * `current` - push the current branch to a branch of the same name.
+  +
+  The `simple`, `current` and `upstream` modes are for those who want to
+  push out a single branch after finishing work, even when the other
+  branches are not yet ready to be pushed out. If you are working with
+  other people to push into the same shared repository, you would want
+  to use one of these.
 
 rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
@@ -1750,7 +1824,7 @@ remote.<name>.push::
 
 remote.<name>.mirror::
        If true, pushing to this remote will automatically behave
-       as if the `\--mirror` option was given on the command line.
+       as if the `--mirror` option was given on the command line.
 
 remote.<name>.skipDefaultUpdate::
        If true, this remote will be skipped by default when updating
index c57460c03dd1512e1e3644d3321e87d38316befe..55f499a160094d3135ba2e09fc558ef186fdaef2 100644 (file)
@@ -175,7 +175,7 @@ In the above example output, the function signature was changed
 from both files (hence two `-` removals from both file1 and
 file2, plus `++` to mean one line that was added does not appear
 in either file1 nor file2).  Also eight other lines are the same
-from file1 but do not appear in file2 (hence prefixed with `{plus}`).
+from file1 but do not appear in file2 (hence prefixed with `+`).
 
 When shown by `git diff-tree -c`, it compares the parents of a
 merge commit with the merge result (i.e. file1..fileN are the
index 378f19f0e21d57378cd1913445552456e09b4e51..6cfedd85dc890809a08e0236925a04b4f0d4b26d 100644 (file)
@@ -74,7 +74,7 @@ These parameters can also be set individually with `--stat-width=<width>`,
 `--stat-name-width=<name-width>` and `--stat-count=<count>`.
 
 --numstat::
-       Similar to `\--stat`, but shows number of added and
+       Similar to `--stat`, but shows number of added and
        deleted lines in decimal notation and pathname without
        abbreviation, to make it more machine friendly.  For
        binary files, outputs two `-` instead of saying
index ae413e52a52618503aa8d569f72ae688ec95a8fa..048337b40f8ebdaf4adda90a243f8d89c0aa8a00 100644 (file)
@@ -98,8 +98,8 @@ you originally wrote.
 <9> switch to the master branch.
 <10> merge a topic branch into your master branch.
 <11> review commit logs; other forms to limit output can be
-combined and include `\--max-count=10` (show 10 commits),
-`\--until=2005-12-10`, etc.
+combined and include `--max-count=10` (show 10 commits),
+`--until=2005-12-10`, etc.
 <12> view only the changes that touch what's in `curses/`
 directory, since `v2.43` tag.
 
index ee6cca2e1333eb26b0913eb5c4eaf9f38e5855d9..19d57a80f572c221c6a4d678a0673996416c5208 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
         [--3way] [--interactive] [--committer-date-is-author-date]
         [--ignore-date] [--ignore-space-change | --ignore-whitespace]
         [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
-        [--exclude=<path>] [--reject] [-q | --quiet]
+        [--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
         [--scissors | --no-scissors]
         [(<mbox> | <Maildir>)...]
 'git am' (--continue | --skip | --abort)
@@ -92,6 +92,7 @@ default.   You can use `--no-utf8` to override this.
 -p<n>::
 --directory=<dir>::
 --exclude=<path>::
+--include=<path>::
 --reject::
        These flags are passed to the 'git apply' (see linkgit:git-apply[1])
        program that applies
index ac7006e6400d85c31bf31a3022a38225373fe3f7..59d73e532fc4eaaec0c9d6c01d36b1d2da0cda38 100644 (file)
@@ -160,7 +160,7 @@ EXAMPLES
 
        Same as above, but the format is inferred from the output file.
 
-`git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz`::
+`git archive --format=tar --prefix=git-1.4.0/ v1.4.0^{tree} | gzip >git-1.4.0.tar.gz`::
 
        Create a compressed tarball for v1.4.0 release, but without a
        global extended pax header.
index 9516914236bbfa675006994620b1c8f61855de1d..7ee923629ecc0dbf12a845f18d02c317b6266066 100644 (file)
@@ -160,7 +160,7 @@ introduced the file with:
        git log --diff-filter=A --pretty=short -- foo
 
 and then annotate the change between the commit and its
-parents, using `commit{caret}!` notation:
+parents, using `commit^!` notation:
 
        git blame -C -C -f $commit^! -- foo
 
index 6410c3d34545ce9bf191ffe91bbf8fd77cfc9b7e..47235bea0403bf25fa73529a37a7c5f87d0d7eb9 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 [verse]
 'git branch' [--color[=<when>] | --no-color] [-r | -a]
        [--list] [-v [--abbrev=<length> | --no-abbrev]]
+       [--column[=<options>] | --no-column]
        [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -107,6 +108,14 @@ OPTIONS
        default to color output.
        Same as `--color=never`.
 
+--column[=<options>]::
+--no-column::
+       Display branch listing in columns. See configuration variable
+       column.branch for option syntax.`--column` and `--no-column`
+       without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable in non-verbose mode.
+
 -r::
 --remotes::
        List or delete (if used with -d) the remote-tracking branches.
@@ -126,6 +135,11 @@ OPTIONS
        relationship to upstream branch (if any). If given twice, print
        the name of the upstream branch, as well.
 
+-q::
+--quiet::
+       Be more quiet when creating or deleting a branch, suppressing
+       non-error messages.
+
 --abbrev=<length>::
        Alter the sha1's minimum display length in the output listing.
        The default value is 7 and can be overridden by the `core.abbrev`
index 92b01ec25d147831afaa0ffa43e11549af69e18c..16a6b0acebc7e15bfae0850d6d6c716fd5a32eaa 100644 (file)
@@ -61,7 +61,7 @@ unbundle <file>::
        A list of arguments, acceptable to 'git rev-parse' and
        'git rev-list' (and containing a named ref, see SPECIFYING REFERENCES
        below), that specifies the specific objects and references
-       to transport.  For example, `master{tilde}10..master` causes the
+       to transport.  For example, `master~10..master` causes the
        current master reference to be packaged along with all objects
        added since its 10th ancestor commit.  There is no explicit
        limit to the number of references and objects that may be
@@ -80,12 +80,12 @@ SPECIFYING REFERENCES
 
 'git bundle' will only package references that are shown by
 'git show-ref': this includes heads, tags, and remote heads.  References
-such as `master{tilde}1` cannot be packaged, but are perfectly suitable for
+such as `master~1` cannot be packaged, but are perfectly suitable for
 defining the basis.  More than one reference may be packaged, and more
 than one basis can be specified.  The objects packaged are those not
 contained in the union of the given bases.  Each basis can be
-specified explicitly (e.g. `^master{tilde}10`), or implicitly (e.g.
-`master{tilde}10..master`, `--since=10.days.ago master`).
+specified explicitly (e.g. `^master~10`), or implicitly (e.g.
+`master~10..master`, `--since=10.days.ago master`).
 
 It is very important that the basis used be held by the destination.
 It is okay to err on the side of caution, causing the bundle file
index 103e7b128d3a0d2b1c06da89d4a577f9548ba4f4..98009d1bd50271945637e813cc1fe8ec4161f5a5 100644 (file)
@@ -40,9 +40,9 @@ git imposes the following rules on how references are named:
 
 . They cannot have ASCII control characters (i.e. bytes whose
   values are lower than \040, or \177 `DEL`), space, tilde `~`,
-  caret `{caret}`, or colon `:` anywhere.
+  caret `^`, or colon `:` anywhere.
 
-. They cannot have question-mark `?`, asterisk `{asterisk}`, or open
+. They cannot have question-mark `?`, asterisk `*`, or open
   bracket `[` anywhere.  See the `--refspec-pattern` option below for
   an exception to this rule.
 
@@ -62,10 +62,10 @@ unquoted (by mistake), and also avoids ambiguities in certain
 reference name expressions (see linkgit:gitrevisions[7]):
 
 . A double-dot `..` is often used as in `ref1..ref2`, and in some
-  contexts this notation means `{caret}ref1 ref2` (i.e. not in
+  contexts this notation means `^ref1 ref2` (i.e. not in
   `ref1` and in `ref2`).
 
-. A tilde `~` and caret `{caret}` are used to introduce the postfix
+. A tilde `~` and caret `^` are used to introduce the postfix
   'nth parent' and 'peel onion' operation.
 
 . A colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
@@ -92,9 +92,9 @@ OPTIONS
 --refspec-pattern::
        Interpret <refname> as a reference name pattern for a refspec
        (as used with remote repositories).  If this option is
-       enabled, <refname> is allowed to contain a single `{asterisk}`
+       enabled, <refname> is allowed to contain a single `*`
        in place of a one full pathname component (e.g.,
-       `foo/{asterisk}/bar` but not `foo/bar{asterisk}`).
+       `foo/*/bar` but not `foo/bar*`).
 
 --normalize::
        Normalize 'refname' by removing any leading slash (`/`)
index c0a96e6c1eede3b511689f35e9130a26ea81e003..63a251612ab9a25b798c8cdbb3a69e8a76241e36 100644 (file)
@@ -184,7 +184,7 @@ the conflicted merge in the specified paths.
 +
 This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
-section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 
 <branch>::
        Branch to checkout; if it refers to a branch (i.e., a name that,
@@ -193,11 +193,11 @@ section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
        commit, your HEAD becomes "detached" and you are no longer on
        any branch (see below for details).
 +
-As a special case, the `"@\{-N\}"` syntax for the N-th last branch
+As a special case, the `"@{-N}"` syntax for the N-th last branch
 checks out the branch (instead of detaching).  You may also specify
-`-` which is synonymous with `"@\{-1\}"`.
+`-` which is synonymous with `"@{-1}"`.
 +
-As a further special case, you may use `"A\...B"` as a shortcut for the
+As a further special case, you may use `"A...B"` as a shortcut for the
 merge base of `A` and `B` if there is exactly one merge base. You can
 leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
 
index fed5097e00b4a031c2992ac3d421f6df975e6152..9f3dae631e5437662e9fb3583b24f414c93bb140 100644 (file)
@@ -103,6 +103,25 @@ effect to your index in a row.
        cherry-pick'ed commit, then a fast forward to this commit will
        be performed.
 
+--allow-empty::
+       By default, cherry-picking an empty commit will fail,
+       indicating that an explicit invocation of `git commit
+       --allow-empty` is required. This option overrides that
+       behavior, allowing empty commits to be preserved automatically
+       in a cherry-pick. Note that when "--ff" is in effect, empty
+       commits that meet the "fast-forward" requirement will be kept
+       even without this option.  Note also, that use of this option only
+       keeps commits that were initially empty (i.e. the commit recorded the
+       same tree as its parent).  Commits which are made empty due to a
+       previous commit are dropped.  To force the inclusion of those commits
+       use `--keep-redundant-commits`.
+
+--keep-redundant-commits::
+       If a commit being cherry picked duplicates a commit already in the
+       current history, it will become empty.  By default these
+       redundant commits are ignored.  This option overrides that behavior and
+       creates an empty commit object.  Implies `--allow-empty`.
+
 --strategy=<strategy>::
        Use the given merge strategy.  Should only be used once.
        See the MERGE STRATEGIES section in linkgit:git-merge[1]
@@ -130,7 +149,7 @@ EXAMPLES
        Apply the changes introduced by all commits that are ancestors
        of master but not of HEAD to produce new commits.
 
-`git cherry-pick master{tilde}4 master{tilde}2`::
+`git cherry-pick master~4 master~2`::
 
        Apply the changes introduced by the fifth and third last
        commits pointed to by master and create 2 new commits with
@@ -151,7 +170,7 @@ EXAMPLES
        are in next but not HEAD to the current branch, creating a new
        commit for each new change.
 
-`git rev-list --reverse master \-- README | git cherry-pick -n --stdin`::
+`git rev-list --reverse master -- README | git cherry-pick -n --stdin`::
 
        Apply the changes introduced by all commits on the master
        branch that touched README to the working tree and index,
diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt
new file mode 100644 (file)
index 0000000..9be16ee
--- /dev/null
@@ -0,0 +1,53 @@
+git-column(1)
+=============
+
+NAME
+----
+git-column - Display data in columns
+
+SYNOPSIS
+--------
+[verse]
+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]
+            [--indent=<string>] [--nl=<string>] [--pading=<n>]
+
+DESCRIPTION
+-----------
+This command formats its input into multiple columns.
+
+OPTIONS
+-------
+--command=<name>::
+       Look up layout mode using configuration variable column.<name> and
+       column.ui.
+
+--mode=<mode>::
+       Specify layout mode. See configuration variable column.ui for option
+       syntax.
+
+--raw-mode=<n>::
+       Same as --mode but take mode encoded as a number. This is mainly used
+       by other commands that have already parsed layout mode.
+
+--width=<width>::
+       Specify the terminal width. By default 'git column' will detect the
+       terminal width, or fall back to 80 if it is unable to do so.
+
+--indent=<string>::
+       String to be printed at the beginning of each line.
+
+--nl=<N>::
+       String to be printed at the end of each line,
+       including newline character.
+
+--padding=<N>::
+       The number of spaces between columns. One space by default.
+
+
+Author
+------
+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 68abfcacca8f3b0dc7c2daec54e539547ff6b5f9..2d695f619ceff358a3bc7c1e66532cc077ca3065 100644 (file)
@@ -42,7 +42,7 @@ The content to be added can be specified in several ways:
 
 5. by using the --interactive or --patch switches with the 'commit' command
    to decide one by one which files or hunks should be part of the commit,
-   before finalizing the operation. See the ``Interactive Mode`` section of
+   before finalizing the operation. See the ``Interactive Mode'' section of
    linkgit:git-add[1] to learn how to operate these modes.
 
 The `--dry-run` option can be used to obtain a
@@ -287,7 +287,7 @@ When recording your own work, the contents of modified files in
 your working tree are temporarily stored to a staging area
 called the "index" with 'git add'.  A file can be
 reverted back, only in the index but not in the working tree,
-to that of the last commit with `git reset HEAD \-- <file>`,
+to that of the last commit with `git reset HEAD -- <file>`,
 which effectively reverts 'git add' and prevents the changes to
 this file from participating in the next commit.  After building
 the state to be committed incrementally with these commands,
index 827bc988ed5562c2dbc6566d6732b01f20bb4f05..88d814af0e050eb3b31f5763cbf12f03a5f6cbc3 100644 (file)
@@ -252,7 +252,7 @@ Configuring database backend
 
 'git-cvsserver' uses the Perl DBI module. Please also read
 its documentation if changing these variables, especially
-about `DBI\->connect()`.
+about `DBI->connect()`.
 
 gitcvs.dbname::
        Database name. The exact meaning depends on the
index fe38f667f9cd232c3c7a8bc7653648995b3ce4aa..31fc2e3aed613acd8cb6e4a56ae66c378fb07a0d 100644 (file)
@@ -19,6 +19,12 @@ linkgit:git-diff[1].
 
 OPTIONS
 -------
+-d::
+--dir-diff::
+       Copy the modified files to a temporary location and perform
+       a directory diff on them. This mode never prompts before
+       launching the diff tool.
+
 -y::
 --no-prompt::
        Do not prompt before launching a diff tool.
@@ -30,11 +36,9 @@ OPTIONS
 
 -t <tool>::
 --tool=<tool>::
-       Use the diff tool specified by <tool>.
-       Valid diff tools are:
-       araxis, bc3, deltawalker, diffuse, emerge, ecmerge, gvimdiff,
-       kdiff3, kompare, meld, opendiff, p4merge, tkdiff, vimdiff and
-       xxdiff.
+       Use the diff tool specified by <tool>.  Valid values include
+       emerge, kompare, meld, and vimdiff. Run `git difftool --tool-help`
+       for the list of valid <tool> settings.
 +
 If a diff tool is not specified, 'git difftool'
 will use the configuration variable `diff.tool`.  If the
@@ -62,6 +66,9 @@ of the diff post-image.  `$MERGED` is the name of the file which is
 being compared. `$BASE` is provided for compatibility
 with custom merge tool commands and has the same value as `$MERGED`.
 
+--tool-help::
+       Print a list of diff tools that may be used with `--tool`.
+
 -x <command>::
 --extcmd=<command>::
        Specify a custom command for viewing diffs.
index f37eada63a449995861f41e5020c04f26d40c6f2..d6487e1ce03a9c060ae41b7b7455c442c6ea58f3 100644 (file)
@@ -104,7 +104,7 @@ marks the same across runs.
 [<git-rev-list-args>...]::
        A list of arguments, acceptable to 'git rev-parse' and
        'git rev-list', that specifies the specific objects and references
-       to export.  For example, `master{tilde}10..master` causes the
+       to export.  For example, `master~10..master` causes the
        current master reference to be exported along with all objects
        added since its 10th ancestor commit.
 
index ec6ef3119792a9e66a3a46bf6f0754458ea6a061..2620d28b4b38ed3f57ee426e27a2115db8160343 100644 (file)
@@ -98,9 +98,10 @@ OPTIONS
        options.
 
 --cat-blob-fd=<fd>::
-       Specify the file descriptor that will be written to
-       when the `cat-blob` command is encountered in the stream.
-       The default behaviour is to write to `stdout`.
+       Write responses to `cat-blob` and `ls` queries to the
+       file descriptor <fd> instead of `stdout`.  Allows `progress`
+       output intended for the end-user to be separated from other
+       output.
 
 --done::
        Require a `done` command at the end of the stream.
@@ -478,9 +479,9 @@ current branch value should be written as:
 ----
        from refs/heads/branch^0
 ----
-The `{caret}0` suffix is necessary as fast-import does not permit a branch to
+The `^0` suffix is necessary as fast-import does not permit a branch to
 start from itself, and the branch is created in memory before the
-`from` command is even read from the input.  Adding `{caret}0` will force
+`from` command is even read from the input.  Adding `^0` will force
 fast-import to resolve the commit through Git's revision parsing library,
 rather than its internal branch table, thereby loading in the
 existing value of the branch.
@@ -942,6 +943,9 @@ This command can be used anywhere in the stream that comments are
 accepted.  In particular, the `cat-blob` command can be used in the
 middle of a commit but not in the middle of a `data` command.
 
+See ``Responses To Commands'' below for details about how to read
+this output safely.
+
 `ls`
 ~~~~
 Prints information about the object at a path to a file descriptor
@@ -975,7 +979,7 @@ Reading from a named tree::
 
 See `filemodify` above for a detailed description of `<path>`.
 
-Output uses the same format as `git ls-tree <tree> {litdd} <path>`:
+Output uses the same format as `git ls-tree <tree> -- <path>`:
 
 ====
        <mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF
@@ -991,6 +995,9 @@ instead report
        missing SP <path> LF
 ====
 
+See ``Responses To Commands'' below for details about how to read
+this output safely.
+
 `feature`
 ~~~~~~~~~
 Require that fast-import supports the specified feature, or abort if
@@ -1079,6 +1086,35 @@ If the `--done` command line option or `feature done` command is
 in use, the `done` command is mandatory and marks the end of the
 stream.
 
+Responses To Commands
+---------------------
+New objects written by fast-import are not available immediately.
+Most fast-import commands have no visible effect until the next
+checkpoint (or completion).  The frontend can send commands to
+fill fast-import's input pipe without worrying about how quickly
+they will take effect, which improves performance by simplifying
+scheduling.
+
+For some frontends, though, it is useful to be able to read back
+data from the current repository as it is being updated (for
+example when the source material describes objects in terms of
+patches to be applied to previously imported objects).  This can
+be accomplished by connecting the frontend and fast-import via
+bidirectional pipes:
+
+====
+       mkfifo fast-import-output
+       frontend <fast-import-output |
+       git fast-import >fast-import-output
+====
+
+A frontend set up this way can use `progress`, `ls`, and `cat-blob`
+commands to read information from the import in progress.
+
+To avoid deadlock, such frontends must completely consume any
+pending output from `progress`, `ls`, and `cat-blob` before
+performing writes to fast-import that might block.
+
 Crash Reports
 -------------
 If fast-import is supplied invalid input it will terminate with a
index 0f2f1173834bdee77bd1d8d77e070af07a7b3580..81f58234a79a94ef5e9b60e1985b096cff6fcb5c 100644 (file)
@@ -96,8 +96,8 @@ OPTIONS
 --index-filter <command>::
        This is the filter for rewriting the index.  It is similar to the
        tree filter but does not check out the tree, which makes it much
-       faster.  Frequently used with `git rm \--cached
-       \--ignore-unmatch ...`, see EXAMPLES below.  For hairy
+       faster.  Frequently used with `git rm --cached
+       --ignore-unmatch ...`, see EXAMPLES below.  For hairy
        cases, see linkgit:git-update-index[1].
 
 --parent-filter <command>::
@@ -222,11 +222,11 @@ However, if the file is absent from the tree of some commit,
 a simple `rm filename` will fail for that tree and commit.
 Thus you may instead want to use `rm -f filename` as the script.
 
-Using `\--index-filter` with 'git rm' yields a significantly faster
+Using `--index-filter` with 'git rm' yields a significantly faster
 version.  Like with using `rm filename`, `git rm --cached filename`
 will fail if the file is absent from the tree of a commit.  If you
 want to "completely forget" a file, it does not matter when it entered
-history, so we also add `\--ignore-unmatch`:
+history, so we also add `--ignore-unmatch`:
 
 --------------------------------------------------------------------------
 git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
@@ -242,8 +242,8 @@ git filter-branch --subdirectory-filter foodir -- --all
 -------------------------------------------------------
 
 Thus you can, e.g., turn a library subdirectory into a repository of
-its own.  Note the `\--` that separates 'filter-branch' options from
-revision options, and the `\--all` to rewrite all branches and tags.
+its own.  Note the `--` that separates 'filter-branch' options from
+revision options, and the `--all` to rewrite all branches and tags.
 
 To set a commit (which typically is at the tip of another
 history) to be the parent of the current initial commit, in
@@ -371,23 +371,23 @@ Checklist for Shrinking a Repository
 ------------------------------------
 
 git-filter-branch is often used to get rid of a subset of files,
-usually with some combination of `\--index-filter` and
-`\--subdirectory-filter`.  People expect the resulting repository to
+usually with some combination of `--index-filter` and
+`--subdirectory-filter`.  People expect the resulting repository to
 be smaller than the original, but you need a few more steps to
 actually make it smaller, because git tries hard not to lose your
 objects until you tell it to.  First make sure that:
 
 * You really removed all variants of a filename, if a blob was moved
-  over its lifetime.  `git log \--name-only \--follow \--all \--
-  filename` can help you find renames.
+  over its lifetime.  `git log --name-only --follow --all -- filename`
+  can help you find renames.
 
-* You really filtered all refs: use `\--tag-name-filter cat \--
-  \--all` when calling git-filter-branch.
+* You really filtered all refs: use `--tag-name-filter cat -- --all`
+  when calling git-filter-branch.
 
 Then there are two ways to get a smaller repository.  A safer way is
 to clone, that keeps your original intact.
 
-* Clone it with `git clone +++file:///path/to/repo+++`.  The clone
+* Clone it with `git clone file:///path/to/repo`.  The clone
   will not have the removed objects.  See linkgit:git-clone[1].  (Note
   that cloning with a plain path just hardlinks everything!)
 
@@ -397,14 +397,14 @@ approach, so *make a backup* or go back to cloning it.  You have been
 warned.
 
 * Remove the original refs backed up by git-filter-branch: say `git
-  for-each-ref \--format="%(refname)" refs/original/ | xargs -n 1 git
+  for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git
   update-ref -d`.
 
-* Expire all reflogs with `git reflog expire \--expire=now \--all`.
+* Expire all reflogs with `git reflog expire --expire=now --all`.
 
-* Garbage collect all unreferenced objects with `git gc \--prune=now`
+* Garbage collect all unreferenced objects with `git gc --prune=now`
   (or if your git-gc is not new enough to support arguments to
-  `\--prune`, use `git repack -ad; git prune` instead).
+  `--prune`, use `git repack -ad; git prune` instead).
 
 GIT
 ---
index 6ea9be775c09111c14668e0de390a04a301f3235..04c7346e3e8ba12465e71ad1197ed6a109a473b1 100644 (file)
@@ -45,7 +45,7 @@ There are two ways to specify which commits to operate on.
 The first rule takes precedence in the case of a single <commit>.  To
 apply the second rule, i.e., format everything since the beginning of
 history up until <commit>, use the '\--root' option: `git format-patch
-\--root <commit>`.  If you want to format only <commit> itself, you
+--root <commit>`.  If you want to format only <commit> itself, you
 can do this with `git format-patch -1 <commit>`.
 
 By default, each output file is numbered sequentially from 1, and uses the
@@ -134,7 +134,7 @@ include::diff-options.txt[]
 The optional <style> argument can be either `shallow` or `deep`.
 'shallow' threading makes every mail a reply to the head of the
 series, where the head is chosen from the cover letter, the
-`\--in-reply-to`, and the first patch mail, in this order.  'deep'
+`--in-reply-to`, and the first patch mail, in this order.  'deep'
 threading makes every mail a reply to the previous one.
 +
 The default is `--no-thread`, unless the 'format.thread' configuration
index 815afcb9228aada96a143a4e5dcdec0e0ee807e5..b370b025b89ffad0664171a03527b9a775172dee 100644 (file)
@@ -84,7 +84,7 @@ The optional configuration variable 'gc.reflogExpireUnreachable'
 can be set to indicate how long historical reflog entries which
 are not part of the current branch should remain available in
 this repository.  These types of entries are generally created as
-a result of using `git commit \--amend` or `git rebase` and are the
+a result of using `git commit --amend` or `git rebase` and are the
 commits prior to the amend or rebase occurring.  Since these changes
 are not part of the current project most users will want to expire
 them sooner.  This option defaults to '30 days'.
index 343eadd4076664cc66c9d22db22d800e92cc6dc6..4785f1c5c657ec42baa48d6a657f535e86df0595 100644 (file)
@@ -247,11 +247,11 @@ OPTIONS
 Examples
 --------
 
-`git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}`::
+`git grep 'time_t' -- '*.[ch]'`::
        Looks for `time_t` in all tracked .c and .h files in the working
        directory and its subdirectories.
 
-`git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \)`::
+`git grep -e '#define' --and \( -e MAX_PATH -e PATH_MAX \)`::
        Looks for a line that has `#define` and either `MAX_PATH` or
        `PATH_MAX`.
 
index 249fc878ec2058f9fcfc6655ecaeb299ba5622a7..1f906208f9514180fe9f20293ab28ce8180b5357 100644 (file)
@@ -100,7 +100,7 @@ Examples
        Show all commits since version 'v2.6.12' that changed any file
        in the include/scsi or drivers/scsi subdirectories
 
-`git log --since="2 weeks ago" \-- gitk`::
+`git log --since="2 weeks ago" -- gitk`::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index e8319eac6928300d1eb12070874e705b9be060b6..b95aafae2d7540d86d1b804871ef26dfbc41664e 100644 (file)
@@ -70,7 +70,7 @@ copy::
        second object). This subcommand is equivalent to:
        `git notes add [-f] -C $(git notes list <from-object>) <to-object>`
 +
-In `\--stdin` mode, take lines in the format
+In `--stdin` mode, take lines in the format
 +
 ----------
 <from-object> SP <to-object> [ SP <rest> ] LF
index b7c7929716adbad2e27f2d38b83a3c8f74604a59..fe1f49bc6f26950987bc5520267663f4f335dab0 100644 (file)
@@ -31,13 +31,6 @@ the updated p4 remote branch.
 
 EXAMPLE
 -------
-* Create an alias for 'git p4', using the full path to the 'git-p4'
-  script if needed:
-+
-------------
-$ git config --global alias.p4 '!git-p4'
-------------
-
 * Clone a repository:
 +
 ------------
@@ -165,11 +158,14 @@ OPTIONS
 
 General options
 ~~~~~~~~~~~~~~~
-All commands except clone accept this option.
+All commands except clone accept these options.
 
 --git-dir <dir>::
        Set the 'GIT_DIR' environment variable.  See linkgit:git[1].
 
+--verbose::
+       Provide more progress information.
+
 Sync options
 ~~~~~~~~~~~~
 These options can be used in the initial 'clone' as well as in
@@ -183,6 +179,7 @@ subsequent 'sync' operations.
 +
 This example imports a new remote "p4/proj2" into an existing
 git repository:
++
 ----
     $ git init
     $ git p4 sync --branch=refs/remotes/p4/proj2 //depot/proj2
@@ -200,12 +197,13 @@ git repository:
 --silent::
        Do not print any progress information.
 
---verbose::
-       Provide more progress information.
-
 --detect-labels::
        Query p4 for labels associated with the depot paths, and add
-       them as tags in git.
+       them as tags in git. Limited usefulness as only imports labels
+       associated with new changelists. Deprecated.
+
+--import-labels::
+       Import labels from p4 into git.
 
 --import-local::
        By default, p4 branches are stored in 'refs/remotes/p4/',
@@ -252,9 +250,6 @@ Submit options
 ~~~~~~~~~~~~~~
 These options can be used to modify 'git p4 submit' behavior.
 
---verbose::
-       Provide more progress information.
-
 --origin <commit>::
        Upstream location from which commits are identified to submit to
        p4.  By default, this is the most recent p4 commit reachable
@@ -270,6 +265,16 @@ These options can be used to modify 'git p4 submit' behavior.
        Re-author p4 changes before submitting to p4.  This option
        requires p4 admin privileges.
 
+--export-labels::
+       Export tags from git as p4 labels. Tags found in git are applied
+       to the perforce working directory.
+
+Rebase options
+~~~~~~~~~~~~~~
+These options can be used to modify 'git p4 rebase' behavior.
+
+--import-labels::
+       Import p4 labels.
 
 DEPOT PATH SYNTAX
 -----------------
@@ -311,19 +316,19 @@ configuration file.  This allows future 'git p4 submit' commands to
 work properly; the submit command looks only at the variable and does
 not have a command-line option.
 
-The full syntax for a p4 view is documented in 'p4 help views'.  Git-p4
+The full syntax for a p4 view is documented in 'p4 help views'.  'Git p4'
 knows only a subset of the view syntax.  It understands multi-line
 mappings, overlays with '+', exclusions with '-' and double-quotes
-around whitespace.  Of the possible wildcards, git-p4 only handles
-'...', and only when it is at the end of the path.  Git-p4 will complain
+around whitespace.  Of the possible wildcards, 'git p4' only handles
+'...', and only when it is at the end of the path.  'Git p4' will complain
 if it encounters an unhandled wildcard.
 
 Bugs in the implementation of overlap mappings exist.  If multiple depot
 paths map through overlays to the same location in the repository,
-git-p4 can choose the wrong one.  This is hard to solve without
-dedicating a client spec just for git-p4.
+'git p4' can choose the wrong one.  This is hard to solve without
+dedicating a client spec just for 'git p4'.
 
-The name of the client can be given to git-p4 in multiple ways.  The
+The name of the client can be given to 'git p4' in multiple ways.  The
 variable 'git-p4.client' takes precedence if it exists.  Otherwise,
 normal p4 mechanisms of determining the client are used:  environment
 variable P4CLIENT, a file referenced by P4CONFIG, or the local host name.
@@ -434,11 +439,23 @@ git-p4.branchList::
        enabled.  Each entry should be a pair of branch names separated
        by a colon (:).  This example declares that both branchA and
        branchB were created from main:
++
 -------------
 git config       git-p4.branchList main:branchA
 git config --add git-p4.branchList main:branchB
 -------------
 
+git-p4.ignoredP4Labels::
+       List of p4 labels to ignore. This is built automatically as
+       unimportable labels are discovered.
+
+git-p4.importLabels::
+       Import p4 labels into git, as per --import-labels.
+
+git-p4.labelImportRegexp::
+       Only p4 labels matching this regular expression will be imported. The
+       default value is '[a-zA-Z0-9_\-.]+$'.
+
 git-p4.useClientSpec::
        Specify that the p4 client spec should be used to identify p4
        depot paths of interest.  This is equivalent to specifying the
@@ -487,11 +504,18 @@ git-p4.skipUserNameCheck::
        user map, 'git p4' exits.  This option can be used to force
        submission regardless.
 
-git-p4.attemptRCSCleanup:
-    If enabled, 'git p4 submit' will attempt to cleanup RCS keywords
-    ($Header$, etc). These would otherwise cause merge conflicts and prevent
-    the submit going ahead. This option should be considered experimental at
-    present.
+git-p4.attemptRCSCleanup::
+       If enabled, 'git p4 submit' will attempt to cleanup RCS keywords
+       ($Header$, etc). These would otherwise cause merge conflicts and prevent
+       the submit going ahead. This option should be considered experimental at
+       present.
+
+git-p4.exportLabels::
+       Export git tags to p4 labels, as per --export-labels.
+
+git-p4.labelExportRegexp::
+       Only p4 labels matching this regular expression will be exported. The
+       default value is '[a-zA-Z0-9_\-.]+$'.
 
 IMPLEMENTATION DETAILS
 ----------------------
index a3c6677bfaaf628410eb85ce06f58d3c2b4df966..10afd4edfe515013bd92ad18d842536a537142a5 100644 (file)
@@ -32,7 +32,7 @@ Subsequent updates to branches always create new files under
 
 A recommended practice to deal with a repository with too many
 refs is to pack its refs with `--all --prune` once, and
-occasionally run `git pack-refs \--prune`.  Tags are by
+occasionally run `git pack-refs --prune`.  Tags are by
 definition stationary and are not expected to change.  Branch
 heads will be packed with the initial `pack-refs --all`, but
 only the currently active branch heads will become unpacked,
index 0f18ec891ac75effb102a445a93bdcf4432bb697..defb544ed0af407a2e6360355bad34b1cb43fed9 100644 (file)
@@ -110,7 +110,7 @@ include::merge-options.txt[]
 +
 See `pull.rebase`, `branch.<name>.rebase` and `branch.autosetuprebase` in
 linkgit:git-config[1] if you want to make `git pull` always use
-`{litdd}rebase` instead of merging.
+`--rebase` instead of merging.
 +
 [NOTE]
 This is a potentially _dangerous_ mode of operation.
index 48760db3371ef762fe6e0f099045c208206742f1..cb97cc1c3b44d2b5201af8593e593a08bd36855e 100644 (file)
@@ -34,7 +34,7 @@ OPTIONS[[OPTIONS]]
 
 <refspec>...::
        The format of a <refspec> parameter is an optional plus
-       `{plus}`, followed by the source ref <src>, followed
+       `+`, followed by the source ref <src>, followed
        by a colon `:`, followed by the destination ref <dst>.
        It is used to specify with what <src> object the <dst> ref
        in the remote repository is to be updated.
@@ -50,7 +50,7 @@ updated.
 +
 The object referenced by <src> is used to update the <dst> reference
 on the remote side, but by default this is only allowed if the
-update can fast-forward <dst>.  By having the optional leading `{plus}`,
+update can fast-forward <dst>.  By having the optional leading `+`,
 you can tell git to update the <dst> ref even when the update is not a
 fast-forward.  This does *not* attempt to merge <src> into <dst>.  See
 EXAMPLES below for details.
@@ -60,7 +60,7 @@ EXAMPLES below for details.
 Pushing an empty <src> allows you to delete the <dst> ref from
 the remote repository.
 +
-The special refspec `:` (or `{plus}:` to allow non-fast-forward updates)
+The special refspec `:` (or `+:` to allow non-fast-forward updates)
 directs git to push "matching" branches: for every branch that exists on
 the local side, the remote side is updated if a branch of the same name
 already exists on the remote side.  This is the default operation mode
@@ -75,7 +75,7 @@ nor in any Push line of the corresponding remotes file---see below).
        Remove remote branches that don't have a local counterpart. For example
        a remote branch `tmp` will be removed if a local branch with the same
        name doesn't exist any more. This also respects refspecs, e.g.
-       `git push --prune remote refs/heads/{asterisk}:refs/tmp/{asterisk}` would
+       `git push --prune remote refs/heads/*:refs/tmp/*` would
        make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo`
        doesn't exist.
 
@@ -170,10 +170,16 @@ useful if you write an alias or script around 'git push'.
        is specified. This flag forces progress status even if the
        standard error stream is not directed to a terminal.
 
---recurse-submodules=check::
-       Check whether all submodule commits used by the revisions to be
-       pushed are available on a remote tracking branch. Otherwise the
-       push will be aborted and the command will exit with non-zero status.
+--recurse-submodules=check|on-demand::
+       Make sure all submodule commits used by the revisions to be
+       pushed are available on a remote tracking branch. If 'check' is
+       used git will verify that all submodule commits that changed in
+       the revisions to be pushed are available on at least one remote
+       of the submodule. If any commits are missing the push will be
+       aborted and exit with non-zero status. If 'on-demand' is used
+       all submodules that changed in the revisions to be pushed will
+       be pushed. If on-demand was not able to push all necessary
+       revisions it will also be aborted and exit with non-zero status.
 
 
 include::urls-remotes.txt[]
@@ -204,7 +210,7 @@ option is used.
 flag::
        A single character indicating the status of the ref:
 (space);; for a successfully pushed fast-forward;
-`{plus}`;; for a successful forced update;
+`+`;; for a successful forced update;
 `-`;; for a successfully deleted ref;
 `*`;; for a successfully pushed new ref;
 `!`;; for a ref that was rejected or failed to push; and
@@ -214,7 +220,7 @@ summary::
        For a successfully pushed ref, the summary shows the old and new
        values of the ref in a form suitable for using as an argument to
        `git log` (this is `<old>..<new>` in most cases, and
-       `<old>\...<new>` for forced non-fast-forward updates).
+       `<old>...<new>` for forced non-fast-forward updates).
 +
 For a failed update, more details are given:
 +
@@ -396,7 +402,7 @@ the ones in the examples below) can be configured as the default for
        Find a ref that matches `experimental` in the `origin` repository
        (e.g. `refs/heads/experimental`), and delete it.
 
-`git push origin {plus}dev:master`::
+`git push origin +dev:master`::
        Update the origin repository's master branch with the dev branch,
        allowing non-fast-forward updates.  *This can leave unreferenced
        commits dangling in the origin repository.*  Consider the
index 520aaa94fb7b2be7c11e7eed013acf36055a41ab..147fa1a8e002daba2a465ed1f56c88838500cb53 100644 (file)
@@ -238,6 +238,10 @@ leave out at most one of A and B, in which case it defaults to HEAD.
        will be reset to where it was when the rebase operation was
        started.
 
+--keep-empty::
+       Keep the commits that do not change anything from its
+       parents in the result.
+
 --skip::
        Restart the rebasing process by skipping the current patch.
 
@@ -267,7 +271,7 @@ which makes little sense.
 -X <strategy-option>::
 --strategy-option=<strategy-option>::
        Pass the <strategy-option> through to the merge strategy.
-       This implies `\--merge` and, if no strategy has been
+       This implies `--merge` and, if no strategy has been
        specified, `-s recursive`.  Note the reversal of 'ours' and
        'theirs' as noted in above for the `-m` option.
 
@@ -611,8 +615,8 @@ Easy case: The changes are literally the same.::
 Hard case: The changes are not the same.::
 
        This happens if the 'subsystem' rebase had conflicts, or used
-       `\--interactive` to omit, edit, squash, or fixup commits; or
-       if the upstream used one of `commit \--amend`, `reset`, or
+       `--interactive` to omit, edit, squash, or fixup commits; or
+       if the upstream used one of `commit --amend`, `reset`, or
        `filter-branch`.
 
 
@@ -648,7 +652,7 @@ correspond to the ones before the rebase.
 NOTE: While an "easy case recovery" sometimes appears to be successful
       even in the hard case, it may have unintended consequences.  For
       example, a commit that was removed via `git rebase
-      \--interactive` will be **resurrected**!
+      --interactive` will be **resurrected**!
 
 The idea is to manually tell 'git rebase' "where the old 'subsystem'
 ended and your 'topic' began", that is, what the old merge-base
@@ -656,7 +660,7 @@ between them was.  You will have to find a way to name the last commit
 of the old 'subsystem', for example:
 
 * With the 'subsystem' reflog: after 'git fetch', the old tip of
-  'subsystem' is at `subsystem@\{1}`.  Subsequent fetches will
+  'subsystem' is at `subsystem@{1}`.  Subsequent fetches will
   increase the number.  (See linkgit:git-reflog[1].)
 
 * Relative to the tip of 'topic': knowing that your 'topic' has three
index 976dc1493799c46bc2a390fde36063ae4e7a0e11..7fe2d2247bf4197c6212b6cab56e7f14606ff3b5 100644 (file)
@@ -39,13 +39,13 @@ as well).  It is an alias for `git log -g --abbrev-commit --pretty=oneline`;
 see linkgit:git-log[1].
 
 The reflog is useful in various git commands, to specify the old value
-of a reference. For example, `HEAD@\{2\}` means "where HEAD used to be
-two moves ago", `master@\{one.week.ago\}` means "where master used to
+of a reference. For example, `HEAD@{2}` means "where HEAD used to be
+two moves ago", `master@{one.week.ago}` means "where master used to
 point to one week ago", and so on. See linkgit:gitrevisions[7] for
 more details.
 
 To delete single entries from the reflog, use the subcommand "delete"
-and specify the _exact_ entry (e.g. "`git reflog delete master@\{2\}`").
+and specify the _exact_ entry (e.g. "`git reflog delete master@{2}`").
 
 
 OPTIONS
index 674797cd8308801dc3e3b4f3d6f581005a0a0d2a..f5836e46d080d8f1f9931a8cd1aa22d0654341ba 100644 (file)
@@ -87,7 +87,7 @@ to the `capabilities` command (see COMMANDS, below).
        capability use this.
 +
 A helper advertising the capability
-`refspec refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}`
+`refspec refs/heads/*:refs/svn/origin/branches/*`
 is saying that, when it is asked to `import refs/heads/topic`, the
 stream it outputs will update the `refs/svn/origin/branches/topic`
 ref.
@@ -96,7 +96,7 @@ This capability can be advertised multiple times.  The first
 applicable refspec takes precedence.  The left-hand of refspecs
 advertised with this capability must cover all refs reported by
 the list command.  If no 'refspec' capability is advertised,
-there is an implied `refspec {asterisk}:{asterisk}`.
+there is an implied `refspec *:*`.
 
 Capabilities for Pushing
 ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -148,7 +148,7 @@ Other frontends may have some other order of preference.
        This modifies the 'import' capability.
 +
 A helper advertising
-`refspec refs/heads/{asterisk}:refs/svn/origin/branches/{asterisk}`
+`refspec refs/heads/*:refs/svn/origin/branches/*`
 in its capabilities is saying that, when it handles
 `import refs/heads/topic`, the stream it outputs will update the
 `refs/svn/origin/branches/topic` ref.
@@ -157,7 +157,7 @@ This capability can be advertised multiple times.  The first
 applicable refspec takes precedence.  The left-hand of refspecs
 advertised with this capability must cover all refs reported by
 the list command.  If no 'refspec' capability is advertised,
-there is an implied `refspec {asterisk}:{asterisk}`.
+there is an implied `refspec *:*`.
 
 INVOCATION
 ----------
index d376d19ef79962c5755e958eec7a491c96479a1f..a308f4c79f1a879e124486ef6cffbb2e9f8e606c 100644 (file)
@@ -67,14 +67,14 @@ multiple branches without grabbing all branches.
 With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set
 up to point at remote's `<master>` branch. See also the set-head command.
 +
-When a fetch mirror is created with `\--mirror=fetch`, the refs will not
+When a fetch mirror is created with `--mirror=fetch`, the refs will not
 be stored in the 'refs/remotes/' namespace, but rather everything in
 'refs/' on the remote will be directly mirrored into 'refs/' in the
 local repository. This option only makes sense in bare repositories,
 because a fetch would overwrite any local commits.
 +
-When a push mirror is created with `\--mirror=push`, then `git push`
-will always behave as if `\--mirror` was passed.
+When a push mirror is created with `--mirror=push`, then `git push`
+will always behave as if `--mirror` was passed.
 
 'rename'::
 
index b43b7c8c0ed67fa37e20c41f35a703c28c4d9844..a62227f84e88a5fae13af18e66aec6452693d354 100644 (file)
@@ -101,15 +101,15 @@ One way to do it is to pull master into the topic branch:
 
 The commits marked with `*` touch the same area in the same
 file; you need to resolve the conflicts when creating the commit
-marked with `{plus}`.  Then you can test the result to make sure your
+marked with `+`.  Then you can test the result to make sure your
 work-in-progress still works with what is in the latest master.
 
 After this test merge, there are two ways to continue your work
 on the topic.  The easiest is to build on top of the test merge
-commit `{plus}`, and when your work in the topic branch is finally
+commit `+`, and when your work in the topic branch is finally
 ready, pull the topic branch into master, and/or ask the
 upstream to pull from you.  By that time, however, the master or
-the upstream might have been advanced since the test merge `{plus}`,
+the upstream might have been advanced since the test merge `+`,
 in which case the final commit graph would look like this:
 
 ------------
index b674866e6d166ccce096b58f7cecdbdb183c0194..117e3743a62f59a1b6b57f8a36f4ee938f6e1848 100644 (file)
@@ -41,7 +41,7 @@ working tree in one go.
 +
 This means that `git reset -p` is the opposite of `git add -p`, i.e.
 you can use it to selectively reset hunks. See the ``Interactive Mode''
-section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 
 'git reset' --<mode> [<commit>]::
        This form resets the current branch head to <commit> and
index 8023dc086d044d0ac2cab685ac1c8b050adda95b..f63b81aad6dd8d0fc7de0c6d9dac5f8e8dc65d6a 100644 (file)
@@ -113,15 +113,14 @@ OPTIONS
 +
 If a `pattern` is given, only refs matching the given shell glob are
 shown.  If the pattern does not contain a globbing character (`?`,
-`{asterisk}`, or `[`), it is turned into a prefix match by
-appending `/{asterisk}`.
+`*`, or `[`), it is turned into a prefix match by appending `/*`.
 
 --glob=pattern::
        Show all refs matching the shell glob pattern `pattern`. If
        the pattern does not start with `refs/`, this is automatically
        prepended.  If the pattern does not contain a globbing
-       character (`?`, `{asterisk}`, or `[`), it is turned into a prefix
-       match by appending `/{asterisk}`.
+       character (`?`, `*`, or `[`), it is turned into a prefix
+       match by appending `/*`.
 
 --show-toplevel::
        Show the absolute path of the top-level directory.
index b699a3458eff439b05049dab94610a57ac62fc0a..70152e8b1eb31e5ed6e7da889a424bc65c1f16c2 100644 (file)
@@ -27,7 +27,7 @@ throw away all uncommitted changes in your working directory, you
 should see linkgit:git-reset[1], particularly the '--hard' option.  If
 you want to extract specific files as they were in another commit, you
 should see linkgit:git-checkout[1], specifically the `git checkout
-<commit> \-- <filename>` syntax.  Take care with these alternatives as
+<commit> -- <filename>` syntax.  Take care with these alternatives as
 both will discard uncommitted changes in your working directory.
 
 OPTIONS
@@ -105,7 +105,7 @@ EXAMPLES
        Revert the changes specified by the fourth last commit in HEAD
        and create a new commit with the reverted changes.
 
-`git revert -n master{tilde}5..master{tilde}2`::
+`git revert -n master~5..master~2`::
 
        Revert the changes done by commits from the fifth last commit
        in master (included) to the third last commit in master
index 665ad4ddab14da173142914407680605dad9dae6..5d31860eb1ddf57251d61d16b34693cebc4fd297 100644 (file)
@@ -79,8 +79,7 @@ a file that you have not told git about does not remove that file.
 
 File globbing matches across directory boundaries.  Thus, given
 two directories `d` and `d2`, there is a difference between
-using `git rm {apostrophe}d{asterisk}{apostrophe}` and
-`git rm {apostrophe}d/{asterisk}{apostrophe}`, as the former will
+using `git rm 'd*'` and `git rm 'd/*'`, as the former will
 also remove all of directory `d2`.
 
 REMOVING FILES THAT HAVE DISAPPEARED FROM THE FILESYSTEM
index ff3755b4c72a52595be805e92a9f3b62d1477af2..01d8417316e23bc74f2c86df9004091710437ba7 100644 (file)
@@ -47,7 +47,7 @@ OPTIONS
 --format[=<format>]::
        Instead of the commit subject, use some other information to
        describe each commit.  '<format>' can be any string accepted
-       by the `--format` option of 'git log', such as '{asterisk} [%h] %s'.
+       by the `--format` option of 'git log', such as '* [%h] %s'.
        (See the "PRETTY FORMATS" section of linkgit:git-log[1].)
 
        Each pretty-printed commit will be rewrapped before it is shown.
index fcee0008a9d868bb27c9eacbcfe72a3a3f8ed60f..5dbcd47fec3b605d0b4b3e6ed47c06cc57f1dd6d 100644 (file)
@@ -73,7 +73,7 @@ OPTIONS
 --exclude-existing[=<pattern>]::
 
        Make 'git show-ref' act as a filter that reads refs from stdin of the
-       form "`{caret}(?:<anything>\s)?<refname>(?:{backslash}{caret}{})?$`"
+       form "`^(?:<anything>\s)?<refname>(?:\^{})?$`"
        and performs the following actions on each:
        (1) strip "{caret}{}" at the end of line if any;
        (2) ignore if pattern is provided and does not head-match refname;
index 1e38819e67cda4282ec7ede004ee5668d0a478c8..ae4edcccfbc2c862f4a1f77a5d58c348fbee2def 100644 (file)
@@ -52,10 +52,10 @@ EXAMPLES
        Shows the tag `v1.0.0`, along with the object the tags
        points at.
 
-`git show v1.0.0^\{tree\}`::
+`git show v1.0.0^{tree}`::
        Shows the tree pointed to by the tag `v1.0.0`.
 
-`git show -s --format=%s v1.0.0^\{commit\}`::
+`git show -s --format=%s v1.0.0^{commit}`::
        Shows the subject of the commit pointed to by the
        tag `v1.0.0`.
 
index 43af38aa4bd7673bef2127058f171da039732c71..0aa4e20eaed524ee01ae4bee11873efaedf44124 100644 (file)
@@ -36,8 +36,8 @@ you create one.
 
 The latest stash you created is stored in `refs/stash`; older
 stashes are found in the reflog of this reference and can be named using
-the usual reflog syntax (e.g. `stash@\{0}` is the most recently
-created stash, `stash@\{1}` is the one before it, `stash@\{2.hours.ago}`
+the usual reflog syntax (e.g. `stash@{0}` is the most recently
+created stash, `stash@{1}` is the one before it, `stash@{2.hours.ago}`
 is also possible).
 
 OPTIONS
@@ -66,7 +66,7 @@ constructed such that its index state is the same as the index state
 of your repository, and its worktree contains only the changes you
 selected interactively.  The selected changes are then rolled back
 from your worktree. See the ``Interactive Mode'' section of
-linkgit:git-add[1] to learn how to operate the `\--patch` mode.
+linkgit:git-add[1] to learn how to operate the `--patch` mode.
 +
 The `--patch` option implies `--keep-index`.  You can use
 `--no-keep-index` to override this.
@@ -74,7 +74,7 @@ The `--patch` option implies `--keep-index`.  You can use
 list [<options>]::
 
        List the stashes that you currently have.  Each 'stash' is listed
-       with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1}` is
+       with its name (e.g. `stash@{0}` is the latest stash, `stash@{1}` is
        the one before, etc.), the name of the branch that was current when the
        stash was made, and a short description of the commit the stash was
        based on.
@@ -93,7 +93,7 @@ show [<stash>]::
        stashed state and its original parent. When no `<stash>` is given,
        shows the latest one. By default, the command shows the diffstat, but
        it will accept any format known to 'git diff' (e.g., `git stash show
-       -p stash@\{1}` to view the second most recent stash in patch form).
+       -p stash@{1}` to view the second most recent stash in patch form).
 
 pop [--index] [-q|--quiet] [<stash>]::
 
@@ -111,8 +111,8 @@ tree's changes, but also the index's ones. However, this can fail, when you
 have conflicts (which are stored in the index, where you therefore can no
 longer apply the changes as they were originally).
 +
-When no `<stash>` is given, `stash@\{0}` is assumed, otherwise `<stash>` must
-be a reference of the form `stash@\{<revision>}`.
+When no `<stash>` is given, `stash@{0}` is assumed, otherwise `<stash>` must
+be a reference of the form `stash@{<revision>}`.
 
 apply [--index] [-q|--quiet] [<stash>]::
 
@@ -143,9 +143,9 @@ clear::
 drop [-q|--quiet] [<stash>]::
 
        Remove a single stashed state from the stash list. When no `<stash>`
-       is given, it removes the latest one. i.e. `stash@\{0}`, otherwise
+       is given, it removes the latest one. i.e. `stash@{0}`, otherwise
        `<stash>` must a valid stash log reference of the form
-       `stash@\{<revision>}`.
+       `stash@{<revision>}`.
 
 create::
 
index 16ae5c3f27fdde4c1dd99d301c6ec166d2507ab8..67e5f53a9ef00991954b6bf95286a418c7e4af23 100644 (file)
@@ -77,6 +77,13 @@ configuration variable documented in linkgit:git-config[1].
        Terminate entries with NUL, instead of LF.  This implies
        the `--porcelain` output format if no other format is given.
 
+--column[=<options>]::
+--no-column::
+       Display untracked files in columns. See configuration variable
+       column.status for option syntax.`--column` and `--no-column`
+       without options are equivalent to 'always' and 'never'
+       respectively.
+
 
 OUTPUT
 ------
@@ -98,12 +105,12 @@ In the short-format, the status of each path is shown as
 
        XY PATH1 -> PATH2
 
-where `PATH1` is the path in the `HEAD`, and the ` \-> PATH2` part is
+where `PATH1` is the path in the `HEAD`, and the " `-> PATH2`" part is
 shown only when `PATH1` corresponds to a different path in the
 index/worktree (i.e. the file is renamed). The 'XY' is a two-letter
 status code.
 
-The fields (including the `\->`) are separated from each other by a
+The fields (including the `->`) are separated from each other by a
 single space. If a filename contains whitespace or other nonprintable
 characters, that field will be quoted in the manner of a C string
 literal: surrounded by ASCII double quote (34) characters, and with
index 8d32b9a814675c9ebb58c25615a7f647dae20c93..e36a7c3d1e11e38950c74b32ab4f1ac9a4683a41 100644 (file)
@@ -13,6 +13,7 @@ SYNOPSIS
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
+       [--column[=<options>] | --no-column] [<pattern>...]
        [<pattern>...]
 'git tag' -v <tagname>...
 
@@ -84,6 +85,14 @@ OPTIONS
        using fnmatch(3)).  Multiple patterns may be given; if any of
        them matches, the tag is shown.
 
+--column[=<options>]::
+--no-column::
+       Display tag listing in columns. See configuration variable
+       column.tag for option syntax.`--column` and `--no-column`
+       without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable when listing tags without annotation lines.
+
 --contains <commit>::
        Only list tags which contain the specified commit.
 
index 346e7a20797c22a29733f09e5fe241c84a46bfa9..f7362dc2d1736a087bc897d870f22859e74dc8a6 100644 (file)
@@ -63,7 +63,7 @@ EXAMPLES
 
        Create a tarball for v1.4.0 release.
 
-`git tar-tree v1.4.0{caret}\{tree\} git-1.4.0 | gzip >git-1.4.0.tar.gz`::
+`git tar-tree v1.4.0^{tree} git-1.4.0 | gzip >git-1.4.0.tar.gz`::
 
        Create a tarball for v1.4.0 release, but without a
        global extended pax header.
index a3081f4e237747dc858fd105302cbb9a489de41b..9d0b1515c58dc825beef6b4507542572c51ca988 100644 (file)
@@ -19,7 +19,7 @@ SYNOPSIS
             [--ignore-submodules]
             [--really-refresh] [--unresolve] [--again | -g]
             [--info-only] [--index-info]
-            [-z] [--stdin]
+            [-z] [--stdin] [--index-version <n>]
             [--verbose]
             [--] [<file>...]
 
@@ -143,6 +143,10 @@ you will need to handle the situation manually.
 --verbose::
         Report what is being added and removed from index.
 
+--index-version <n>::
+       Write the resulting index out in the named on-disk format version.
+       The current default version is 2.
+
 -z::
        Only meaningful with `--stdin` or `--index-info`; paths are
        separated with NUL character instead of LF.
index 5317cc247454b1a080b2609139befeba487d5ff5..988a3234f435ccd55d20f9d0e48c146b55691167 100644 (file)
@@ -43,13 +43,21 @@ GIT_EDITOR::
     `$SOME_ENVIRONMENT_VARIABLE`, `"C:\Program Files\Vim\gvim.exe"
     --nofork`.  The order of preference is the `$GIT_EDITOR`
     environment variable, then `core.editor` configuration, then
-    `$VISUAL`, then `$EDITOR`, and then finally 'vi'.
+    `$VISUAL`, then `$EDITOR`, and then the default chosen at compile
+    time, which is usually 'vi'.
+ifdef::git-default-editor[]
+    The build you are using chose '{git-default-editor}' as the default.
+endif::git-default-editor[]
 
 GIT_PAGER::
     Text viewer for use by git commands (e.g., 'less').  The value
     is meant to be interpreted by the shell.  The order of preference
     is the `$GIT_PAGER` environment variable, then `core.pager`
-    configuration, then `$PAGER`, and then finally 'less'.
+    configuration, then `$PAGER`, and then the default chosen at
+    compile time (usually 'less').
+ifdef::git-default-pager[]
+    The build you are using chose '{git-default-pager}' as the default.
+endif::git-default-pager[]
 
 Diagnostics
 -----------
index 76c7f7eec5bb8ba01262762909939070b55ef5c9..6c8f510c3f6398630da248fd356a00f69a6d9471 100644 (file)
@@ -58,7 +58,7 @@ Examples
        Show as patches the commits since version 'v2.6.12' that changed
        any file in the include/scsi or drivers/scsi subdirectories
 
-`git whatchanged --since="2 weeks ago" \-- gitk`::
+`git whatchanged --since="2 weeks ago" -- gitk`::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index f734f97b8e1b64f2d3bfc926e92e9226ba716289..ea17f7a53b664d5e9e86ceb814c5cc7430035125 100644 (file)
@@ -25,22 +25,22 @@ arguments.  Here are the rules:
    are paths.
 
  * When an argument can be misunderstood as either a revision or a path,
-   they can be disambiguated by placing `\--` between them.
-   E.g. `git diff \-- HEAD` is, "I have a file called HEAD in my work
+   they can be disambiguated by placing `--` between them.
+   E.g. `git diff -- HEAD` is, "I have a file called HEAD in my work
    tree.  Please show changes between the version I staged in the index
    and what I have in the work tree for that file". not "show difference
    between the HEAD commit and the work tree as a whole".  You can say
-   `git diff HEAD \--` to ask for the latter.
+   `git diff HEAD --` to ask for the latter.
 
- * Without disambiguating `\--`, git makes a reasonable guess, but errors
+ * Without disambiguating `--`, git makes a reasonable guess, but errors
    out and asking you to disambiguate when ambiguous.  E.g. if you have a
    file called HEAD in your work tree, `git diff HEAD` is ambiguous, and
-   you have to say either `git diff HEAD \--` or `git diff \-- HEAD` to
+   you have to say either `git diff HEAD --` or `git diff -- HEAD` to
    disambiguate.
 
 When writing a script that is expected to handle random user-input, it is
 a good practice to make it explicit which arguments are which by placing
-disambiguating `\--` at appropriate places.
+disambiguating `--` at appropriate places.
 
 Here are the rules regarding the "flags" that you should follow when you are
 scripting git:
index fb0d5692a4d2e898da0dea9c346420467722badc..9d893369a00b6a924876fb234c1082db2021abf3 100644 (file)
@@ -151,8 +151,8 @@ to your working tree, you use the 'git update-index' program. That
 program normally just takes a list of filenames you want to update, but
 to avoid trivial mistakes, it refuses to add new entries to the index
 (or remove existing ones) unless you explicitly tell it that you're
-adding a new entry with the `\--add` flag (or removing an entry with the
-`\--remove`) flag.
+adding a new entry with the `--add` flag (or removing an entry with the
+`--remove`) flag.
 
 So to populate the index with the two files you just created, you can do
 
@@ -399,10 +399,10 @@ $ git diff HEAD
 which ends up doing the above for you.
 
 In other words, 'git diff-index' normally compares a tree against the
-working tree, but when given the `\--cached` flag, it is told to
+working tree, but when given the `--cached` flag, it is told to
 instead compare against just the index cache contents, and ignore the
 current working tree state entirely. Since we just wrote the index
-file to HEAD, doing `git diff-index \--cached -p HEAD` should thus return
+file to HEAD, doing `git diff-index --cached -p HEAD` should thus return
 an empty set of differences, and that's exactly what it does.
 
 [NOTE]
@@ -411,7 +411,7 @@ an empty set of differences, and that's exactly what it does.
 comparisons, and saying that it compares a tree against the working
 tree is thus not strictly accurate. In particular, the list of
 files to compare (the "meta-data") *always* comes from the index file,
-regardless of whether the `\--cached` flag is used or not. The `\--cached`
+regardless of whether the `--cached` flag is used or not. The `--cached`
 flag really only determines whether the file *contents* to be compared
 come from the working tree or not.
 
@@ -433,7 +433,7 @@ update the index cache:
 $ git update-index hello
 ------------------------------------------------
 
-(note how we didn't need the `\--add` flag this time, since git knew
+(note how we didn't need the `--add` flag this time, since git knew
 about the file already).
 
 Note what happens to the different 'git diff-{asterisk}' versions here.
@@ -560,7 +560,7 @@ short history.
 When using the above two commands, the initial commit will be shown.
 If this is a problem because it is huge, you can hide it by setting
 the log.showroot configuration variable to false. Having this, you
-can still show it for each command just adding the `\--root` option,
+can still show it for each command just adding the `--root` option,
 which is a flag for 'git diff-tree' accepted by both commands.
 
 With that, you should now be having some inkling of what git does, and
@@ -881,7 +881,7 @@ helps you view what's going on:
 $ gitk --all
 ----------------
 
-will show you graphically both of your branches (that's what the `\--all`
+will show you graphically both of your branches (that's what the `--all`
 means: normally it will just show you your current `HEAD`) and their
 histories. You can also see exactly how they came to be from a common
 source.
@@ -935,7 +935,7 @@ which will very loudly warn you that you're now committing a merge
 (which is correct, so never mind), and you can write a small merge
 message about your adventures in 'git merge'-land.
 
-After you're done, start up `gitk \--all` to see graphically what the
+After you're done, start up `gitk --all` to see graphically what the
 history looks like. Notice that `mybranch` still exists, and you can
 switch to it, and continue to work with it if you want to. The
 `mybranch` branch will not contain the merge, but next time you merge it
@@ -958,11 +958,11 @@ $ git show-branch --topo-order --more=1 master mybranch
 The first two lines indicate that it is showing the two branches
 and the first line of the commit log message from their
 top-of-the-tree commits, you are currently on `master` branch
-(notice the asterisk `{asterisk}` character), and the first column for
+(notice the asterisk `*` character), and the first column for
 the later output lines is used to show commits contained in the
 `master` branch, and the second column for the `mybranch`
 branch. Three commits are shown along with their log messages.
-All of them have non blank characters in the first column (`{asterisk}`
+All of them have non blank characters in the first column (`*`
 shows an ordinary commit on the current branch, `-` is a merge commit), which
 means they are now part of the `master` branch. Only the "Some
 work" commit has the plus `+` character in the second column,
@@ -1002,8 +1002,8 @@ would be different)
 ----------------
 Updating from ae3a2da... to a80b4aa....
 Fast-forward (no commit created; -m option ignored)
- example |    1 +
- hello   |    1 +
+ example | 1 +
+ hello   | 1 +
  2 files changed, 2 insertions(+)
 ----------------
 
@@ -1013,7 +1013,7 @@ not actually do a merge. Instead, it just updated the top of
 the tree of your branch to that of the `master` branch. This is
 often called 'fast-forward' merge.
 
-You can run `gitk \--all` again to see how the commit ancestry
+You can run `gitk --all` again to see how the commit ancestry
 looks like, or run 'show-branch', which tells you this.
 
 ------------------------------------------------
@@ -1257,7 +1257,7 @@ this 'collapsing' tends to trivially merge most of the paths
 fairly quickly, leaving only a handful of real changes in non-zero
 stages.
 
-To look at only non-zero stages, use `\--unmerged` flag:
+To look at only non-zero stages, use `--unmerged` flag:
 
 ------------
 $ git ls-files --unmerged
@@ -1420,7 +1420,7 @@ packed, and stores the packed file in `.git/objects/pack`
 directory.
 
 [NOTE]
-You will see two files, `pack-{asterisk}.pack` and `pack-{asterisk}.idx`,
+You will see two files, `pack-*.pack` and `pack-*.idx`,
 in `.git/objects/pack` directory. They are closely related to
 each other, and if you ever copy them by hand to a different
 repository for whatever reason, you should make sure you copy
index 066f825f2e116becb90404246c0bdadebc72c1f7..7dfffc00461856924625aa3af1951103da0b7367 100644 (file)
@@ -143,8 +143,8 @@ CONFIGURATION OPTIONS
 ---------------------
 
 Options for a credential context can be configured either in
-`credential.\*` (which applies to all credentials), or
-`credential.<url>.\*`, where <url> matches the context as described
+`credential.*` (which applies to all credentials), or
+`credential.<url>.*`, where <url> matches the context as described
 above.
 
 The following options are available in either location:
index 370624c17174ddc67d4d29fd54127096f4f5b070..daf1782a31cb2dee704a6895939afa638d3736fc 100644 (file)
@@ -168,11 +168,11 @@ a similarity score different from the default of 50% by giving a
 number after the "-M" or "-C" option (e.g. "-M8" to tell it to use
 8/10 = 80%).
 
-Note.  When the "-C" option is used with `\--find-copies-harder`
+Note.  When the "-C" option is used with `--find-copies-harder`
 option, 'git diff-{asterisk}' commands feed unmodified filepairs to
 diffcore mechanism as well as modified ones.  This lets the copy
 detector consider unmodified files as copy source candidates at
-the expense of making it slower.  Without `\--find-copies-harder`,
+the expense of making it slower.  Without `--find-copies-harder`,
 'git diff-{asterisk}' commands can detect copies only if the file that was
 copied happened to have been modified in the same changeset.
 
@@ -224,7 +224,7 @@ diffcore-pickaxe: For Detecting Addition/Deletion of Specified String
 
 This transformation is used to find filepairs that represent
 changes that touch a specified string, and is controlled by the
--S option and the `\--pickaxe-all` option to the 'git diff-{asterisk}'
+-S option and the `--pickaxe-all` option to the 'git diff-*'
 commands.
 
 When diffcore-pickaxe is in use, it checks if there are
@@ -233,9 +233,9 @@ different number of specified string.  Such a filepair represents
 "the string appeared in this changeset".  It also checks for the
 opposite case that loses the specified string.
 
-When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves
+When `--pickaxe-all` is not in effect, diffcore-pickaxe leaves
 only such filepairs that touch the specified string in its
-output.  When `\--pickaxe-all` is used, diffcore-pickaxe leaves all
+output.  When `--pickaxe-all` is used, diffcore-pickaxe leaves all
 filepairs intact if there is such a filepair, or makes the
 output empty otherwise.  The latter behaviour is designed to
 make reviewing of the changes in the context of the whole
index 28edefa202fb0d1065f161f59787ceae39439eb3..b9003fed248651a7783e042c2e3668cf3ff90d0f 100644 (file)
@@ -73,7 +73,7 @@ pre-commit
 ~~~~~~~~~~
 
 This hook is invoked by 'git commit', and can be bypassed
-with `\--no-verify` option.  It takes no parameter, and is
+with `--no-verify` option.  It takes no parameter, and is
 invoked before obtaining the proposed commit log message and
 making a commit.  Exiting with non-zero status from this script
 causes the 'git commit' to abort.
@@ -99,12 +99,12 @@ given); `template` (if a `-t` option was given or the
 configuration option `commit.template` is set); `merge` (if the
 commit is a merge or a `.git/MERGE_MSG` file exists); `squash`
 (if a `.git/SQUASH_MSG` file exists); or `commit`, followed by
-a commit SHA1 (if a `-c`, `-C` or `\--amend` option was given).
+a commit SHA1 (if a `-c`, `-C` or `--amend` option was given).
 
 If the exit status is non-zero, 'git commit' will abort.
 
 The purpose of the hook is to edit the message file in place, and
-it is not suppressed by the `\--no-verify` option.  A non-zero exit
+it is not suppressed by the `--no-verify` option.  A non-zero exit
 means a failure of the hook and aborts the commit.  It should not
 be used as replacement for pre-commit hook.
 
@@ -115,7 +115,7 @@ commit-msg
 ~~~~~~~~~~
 
 This hook is invoked by 'git commit', and can be bypassed
-with `\--no-verify` option.  It takes a single parameter, the
+with `--no-verify` option.  It takes a single parameter, the
 name of the file that holds the proposed commit log message.
 Exiting with non-zero status causes the 'git commit' to
 abort.
index 7aba497b74540499f45ca135bc094ecdffff240a..b9dd56753a5e45487d1dc0fde81f8a9aaef47e43 100644 (file)
@@ -499,6 +499,13 @@ $maxload::
 Set `$maxload` to undefined value (`undef`) to turn this feature off.
 The default value is 300.
 
+$omit_age_column::
+       If true, omit the column with date of the most current commit on the
+       projects list page. It can save a bit of I/O and a fork per repository.
+
+$omit_owner::
+       If true prevents displaying information about repository owner.
+
 $per_request_config::
        If this is set to code reference, it will be run once for each request.
        You can set parts of configuration that change per session this way.
@@ -749,14 +756,14 @@ Project specific override is not supported.
 forks::
        If this feature is enabled, gitweb considers projects in
        subdirectories of project root (basename) to be forks of existing
-       projects.  For each project `$projname.git`, projects in the
-       `$projname/` directory and its subdirectories will not be
-       shown in the main projects list.  Instead, a \'+' mark is shown
-       next to `$projname`, which links to a "forks" view that lists all
-       the forks (all projects in `$projname/` subdirectory).  Additionally
+       projects.  For each project +$projname.git+, projects in the
+       +$projname/+ directory and its subdirectories will not be
+       shown in the main projects list.  Instead, a \'\+' mark is shown
+       next to +$projname+, which links to a "forks" view that lists all
+       the forks (all projects in +$projname/+ subdirectory).  Additionally
        a "forks" view for a project is linked from project summary page.
 +
-If the project list is taken from a file (`$projects_list` points to a
+If the project list is taken from a file (+$projects_list+ points to a
 file), forks are only recognized if they are listed after the main project
 in that file.
 +
index 5e4f362ff8073bc5fc5f651f5219834d6173517b..8b8c6ae5d3f3a3a12b875b106a3bbb5642c8e172 100644 (file)
@@ -39,8 +39,8 @@ To achieve this, try to split your work into small steps from the very
 beginning. It is always easier to squash a few commits together than
 to split one big commit into several.  Don't be afraid of making too
 small or imperfect steps along the way. You can always go back later
-and edit the commits with `git rebase \--interactive` before you
-publish them.  You can use `git stash save \--keep-index` to run the
+and edit the commits with `git rebase --interactive` before you
+publish them.  You can use `git stash save --keep-index` to run the
 test suite independent of other uncommitted changes; see the EXAMPLES
 section of linkgit:git-stash[1].
 
index 880b6f2e6f946ab6fc0ff4850759908364d50854..e3d8a83b23aff51c8dd4281f18de8661514b62b1 100644 (file)
@@ -130,8 +130,8 @@ The placeholders are:
 - '%b': body
 - '%B': raw body (unwrapped subject and body)
 - '%N': commit notes
-- '%gD': reflog selector, e.g., `refs/stash@\{1\}`
-- '%gd': shortened reflog selector, e.g., `stash@\{1\}`
+- '%gD': reflog selector, e.g., `refs/stash@{1}`
+- '%gd': shortened reflog selector, e.g., `stash@{1}`
 - '%gn': reflog identity name
 - '%gN': reflog identity name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
 - '%ge': reflog identity email
@@ -155,7 +155,7 @@ insert an empty string unless we are traversing reflog entries (e.g., by
 `git log -g`). The `%d` placeholder will use the "short" decoration
 format if `--decorate` was not already provided on the command line.
 
-If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed
+If you add a `+` (plus sign) after '%' of a placeholder, a line-feed
 is inserted immediately before the expansion if and only if the
 placeholder expands to a non-empty string.
 
index 5dd6e5a0c7e7f215d4de783a2751e98e41c69948..94a9d32f1d5bd66ac359103d21e671e08140b745 100644 (file)
@@ -13,7 +13,7 @@ endif::git-pull[]
 
 <refspec>::
        The format of a <refspec> parameter is an optional plus
-       `{plus}`, followed by the source ref <src>, followed
+       `+`, followed by the source ref <src>, followed
        by a colon `:`, followed by the destination ref <dst>.
 +
 The remote ref that matches <src>
index 6a4b6355ba91d60ebf2279903a25032256f2063b..1ae3c899ef4bb6d37bc5cbcb25ed927011378d90 100644 (file)
@@ -198,7 +198,7 @@ excluded from the output.
 +
 For example, `--cherry-pick --right-only A...B` omits those
 commits from `B` which are in `A` or are patch-equivalent to a commit in
-`A`. In other words, this lists the `{plus}` commits from `git cherry A B`.
+`A`. In other words, this lists the `+` commits from `git cherry A B`.
 More precisely, `--cherry-pick --right-only --no-merges` gives the exact
 list.
 
@@ -455,7 +455,7 @@ The effect of this is best shown by way of comparing to
          `---------'
 -----------------------------------------------------------------------
 +
-Note the major differences in `N` and `P` over '\--full-history':
+Note the major differences in `N` and `P` over '--full-history':
 +
 --
 * `N`'s parent list had `I` removed, because it is an ancestor of the
@@ -494,7 +494,7 @@ of course).
 When we want to find out what commits in `M` are contaminated with the
 bug introduced by `D` and need fixing, however, we might want to view
 only the subset of 'D..M' that are actually descendants of `D`, i.e.
-excluding `C` and `K`. This is exactly what the '\--ancestry-path'
+excluding `C` and `K`. This is exactly what the '--ancestry-path'
 option does. Applied to the 'D..M' range, it results in:
 +
 -----------------------------------------------------------------------
index 49b3d529526ebc109b380c9cb203a38571c5b15c..1b7d8f140c27d76cfa460c0839c44c6742110df7 100644 (file)
@@ -37,6 +37,11 @@ Functions
 `argv_array_push`::
        Push a copy of a string onto the end of the array.
 
+`argv_array_pushl`::
+       Push a list of strings onto the end of the array. The arguments
+       should be a list of `const char *` strings, terminated by a NULL
+       argument.
+
 `argv_array_pushf`::
        Format a string and push it onto the end of the array. This is a
        convenience wrapper combining `strbuf_addf` and `argv_array_push`.
index 2527b7e8d7516285aee4a85bf145f391e89463a7..3062389404e2d4b5f45fa7f5a324e65f933e08b0 100644 (file)
@@ -21,7 +21,7 @@ that allow to change the behavior of a command.
 * There are basically two forms of options:
   'Short options' consist of one dash (`-`) and one alphanumeric
   character.
-  'Long options' begin with two dashes (`\--`) and some
+  'Long options' begin with two dashes (`--`) and some
   alphanumeric characters.
 
 * Options are case-sensitive.
@@ -31,7 +31,7 @@ The parse-options API allows:
 
 * 'sticked' and 'separate form' of options with arguments.
   `-oArg` is sticked, `-o Arg` is separate form.
-  `\--option=Arg` is sticked, `\--option Arg` is separate form.
+  `--option=Arg` is sticked, `--option Arg` is separate form.
 
 * Long options may be 'abbreviated', as long as the abbreviation
   is unambiguous.
@@ -39,12 +39,12 @@ The parse-options API allows:
 * Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
 
 * Boolean long options can be 'negated' (or 'unset') by prepending
-  `no-`, e.g. `\--no-abbrev` instead of `\--abbrev`. Conversely,
+  `no-`, e.g. `--no-abbrev` instead of `--abbrev`. Conversely,
   options that begin with `no-` can be 'negated' by removing it.
 
-* Options and non-option arguments can clearly be separated using the `\--`
-  option, e.g. `-a -b \--option \-- \--this-is-a-file` indicates that
-  `\--this-is-a-file` must not be processed as an option.
+* Options and non-option arguments can clearly be separated using the `--`
+  option, e.g. `-a -b --option -- --this-is-a-file` indicates that
+  `--this-is-a-file` must not be processed as an option.
 
 Steps to parse options
 ----------------------
@@ -76,7 +76,7 @@ before the full parser, which in turn shows the full help message.
 Flags are the bitwise-or of:
 
 `PARSE_OPT_KEEP_DASHDASH`::
-       Keep the `\--` that usually separates options from
+       Keep the `--` that usually separates options from
        non-option arguments.
 
 `PARSE_OPT_STOP_AT_NON_OPTION`::
@@ -114,22 +114,22 @@ say `static struct option builtin_add_options[]`.
 There are some macros to easily define options:
 
 `OPT__ABBREV(&int_var)`::
-       Add `\--abbrev[=<n>]`.
+       Add `--abbrev[=<n>]`.
 
 `OPT__COLOR(&int_var, description)`::
-       Add `\--color[=<when>]` and `--no-color`.
+       Add `--color[=<when>]` and `--no-color`.
 
 `OPT__DRY_RUN(&int_var, description)`::
-       Add `-n, \--dry-run`.
+       Add `-n, --dry-run`.
 
 `OPT__FORCE(&int_var, description)`::
-       Add `-f, \--force`.
+       Add `-f, --force`.
 
 `OPT__QUIET(&int_var, description)`::
-       Add `-q, \--quiet`.
+       Add `-q, --quiet`.
 
 `OPT__VERBOSE(&int_var, description)`::
-       Add `-v, \--verbose`.
+       Add `-v, --verbose`.
 
 `OPT_GROUP(description)`::
        Start an option group. `description` is a short string that
@@ -216,10 +216,10 @@ The last element of the array must be `OPT_END()`.
 If not stated otherwise, interpret the arguments as follows:
 
 * `short` is a character for the short option
-  (e.g. `{apostrophe}e{apostrophe}` for `-e`, use `0` to omit),
+  (e.g. `'e'` for `-e`, use `0` to omit),
 
 * `long` is a string for the long option
-  (e.g. `"example"` for `\--example`, use `NULL` to omit),
+  (e.g. `"example"` for `--example`, use `NULL` to omit),
 
 * `int_var` is an integer variable,
 
@@ -243,10 +243,10 @@ The function must be defined in this form:
 The callback mechanism is as follows:
 
 * Inside `func`, the only interesting member of the structure
-  given by `opt` is the void pointer `opt\->value`.
-  `\*opt\->value` will be the value that is saved into `var`, if you
+  given by `opt` is the void pointer `opt->value`.
+  `*opt->value` will be the value that is saved into `var`, if you
   use `OPT_CALLBACK()`.
-  For example, do `*(unsigned long *)opt\->value = 42;` to get 42
+  For example, do `*(unsigned long *)opt->value = 42;` to get 42
   into an `unsigned long` variable.
 
 * Return value `0` indicates success and non-zero return
index 996da0503acfa3e3a0ed0f57a951d0fbc1500fb8..b7d0d9a8a7b45f4988c0ee8170fec25c415cc918 100644 (file)
@@ -56,6 +56,11 @@ function.
        returning a `struct commit *` each time you call it. The end of the
        revision list is indicated by returning a NULL pointer.
 
+`reset_revision_walk`::
+
+       Reset the flags used by the revision walking api. You can use
+       this to do multiple sequencial revision walks.
+
 Data structures
 ---------------
 
index 8930b3fabceebfc7e03ddabdf5f7bfb228babf6b..9d25b30178e30458deded19a77f66e7dd0df1227 100644 (file)
@@ -113,9 +113,22 @@ GIT index format
     are encoded in 7-bit ASCII and the encoding cannot contain a NUL
     byte (iow, this is a UNIX pathname).
 
+  (Version 4) In version 4, the entry path name is prefix-compressed
+    relative to the path name for the previous entry (the very first
+    entry is encoded as if the path name for the previous entry is an
+    empty string).  At the beginning of an entry, an integer N in the
+    variable width encoding (the same encoding as the offset is encoded
+    for OFS_DELTA pack entries; see pack-format.txt) is stored, followed
+    by a NUL-terminated string S.  Removing N bytes from the end of the
+    path name for the previous entry, and replacing it with the string S
+    yields the path name for this entry.
+
   1-8 nul bytes as necessary to pad the entry to a multiple of eight bytes
   while keeping the name NUL-terminated.
 
+  (Version 4) In version 4, the padding after the pathname does not
+  exist.
+
 == Extensions
 
 === Cached tree
index d30a1b9510c55403c94e7b9ecfe211df9b463832..fb7ff084f8e55a06b349db6f7e47c3f64a886a0e 100644 (file)
@@ -36,7 +36,7 @@ More specifically, they:
 
 . They cannot have ASCII control characters (i.e. bytes whose
   values are lower than \040, or \177 `DEL`), space, tilde `~`,
-  caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
+  caret `^`, colon `:`, question-mark `?`, asterisk `*`,
   or open bracket `[` anywhere.
 
 . They cannot end with a slash `/` nor a dot `.`.
index 6c7fee7ef7669c7e8789ad0c7273fd190f63c18c..1b942074b69fa3d23b96738bab952a3dbb9c9174 100644 (file)
@@ -1611,7 +1611,7 @@ Recovering lost changes
 Reflogs
 ^^^^^^^
 
-Say you modify a branch with `linkgit:git-reset[1] --hard`, and then
+Say you modify a branch with +linkgit:git-reset[1] \--hard+, and then
 realize that the branch was the only reference you had to that point in
 history.
 
@@ -4207,7 +4207,7 @@ commits one by one with the function `get_revision()`.
 
 If you are interested in more details of the revision walking process,
 just have a look at the first implementation of `cmd_log()`; call
-`git show v1.3.0{tilde}155^2{tilde}4` and scroll down to that function (note that you
+`git show v1.3.0~155^2~4` and scroll down to that function (note that you
 no longer need to call `setup_pager()` directly).
 
 Nowadays, `git log` is a builtin, which means that it is _contained_ in the
@@ -4270,9 +4270,9 @@ Two things are interesting here:
   negative numbers in case of different errors--and 0 on success.
 
 - the variable `sha1` in the function signature of `get_sha1()` is `unsigned
-  char {asterisk}`, but is actually expected to be a pointer to `unsigned
+  char *`, but is actually expected to be a pointer to `unsigned
   char[20]`.  This variable will contain the 160-bit SHA-1 of the given
-  commit.  Note that whenever a SHA-1 is passed as `unsigned char {asterisk}`, it
+  commit.  Note that whenever a SHA-1 is passed as `unsigned char *`, it
   is the binary representation, as opposed to the ASCII representation in
   hex characters, which is passed as `char *`.
 
index 692222afb3f634879408c8d470cdd854b5a3752c..c92dbed2ecac1061770400af9c338c92d68bd1e5 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.10.1
+DEF_VER=v1.7.10.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index 58b2b86ccf93d045d4c406fe422e94db75533607..87e03bbfd48c5bceea6d3f3bd61dc166a5e0a79f 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -131,6 +131,9 @@ Issues of note:
          use English. Under autoconf the configure script will do this
          automatically if it can't find libintl on the system.
 
+       - Python version 2.6 or later is needed to use the git-p4
+         interface to Perforce.
+
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
index be1957a5e986d2e0581123dfe4e0eeb6702c13dc..2fa7211055fae82b3a77d66ce728aadf3bb8c1fa 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -288,6 +288,11 @@ all::
 # dependency rules.
 #
 # Define NATIVE_CRLF if your platform uses CRLF for line endings.
+#
+# Define XDL_FAST_HASH to use an alternative line-hashing method in
+# the diff algorithm.  It gives a nice speedup if your processor has
+# fast unaligned word loads.  Does NOT work on big-endian systems!
+# Enabled by default on x86_64.
 
 GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -386,6 +391,7 @@ XDIFF_OBJS =
 VCSSVN_H =
 VCSSVN_OBJS =
 VCSSVN_TEST_OBJS =
+MISC_H =
 EXTRA_CPPFLAGS =
 LIB_H =
 LIB_OBJS =
@@ -440,6 +446,7 @@ SCRIPT_PERL += git-send-email.perl
 SCRIPT_PERL += git-svn.perl
 
 SCRIPT_PYTHON += git-remote-testgit.py
+SCRIPT_PYTHON += git-p4.py
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
@@ -454,15 +461,15 @@ EXTRA_PROGRAMS =
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS += $(EXTRA_PROGRAMS)
 
+PROGRAM_OBJS += credential-store.o
 PROGRAM_OBJS += daemon.o
 PROGRAM_OBJS += fast-import.o
+PROGRAM_OBJS += http-backend.o
 PROGRAM_OBJS += imap-send.o
+PROGRAM_OBJS += sh-i18n--envsubst.o
 PROGRAM_OBJS += shell.o
 PROGRAM_OBJS += show-index.o
 PROGRAM_OBJS += upload-pack.o
-PROGRAM_OBJS += http-backend.o
-PROGRAM_OBJS += sh-i18n--envsubst.o
-PROGRAM_OBJS += credential-store.o
 
 # Binary suffix, set to .exe for Windows builds
 X =
@@ -475,15 +482,17 @@ TEST_PROGRAMS_NEED_X += test-ctype
 TEST_PROGRAMS_NEED_X += test-date
 TEST_PROGRAMS_NEED_X += test-delta
 TEST_PROGRAMS_NEED_X += test-dump-cache-tree
-TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
 TEST_PROGRAMS_NEED_X += test-genrandom
 TEST_PROGRAMS_NEED_X += test-index-version
 TEST_PROGRAMS_NEED_X += test-line-buffer
 TEST_PROGRAMS_NEED_X += test-match-trees
+TEST_PROGRAMS_NEED_X += test-mergesort
 TEST_PROGRAMS_NEED_X += test-mktemp
 TEST_PROGRAMS_NEED_X += test-parse-options
 TEST_PROGRAMS_NEED_X += test-path-utils
+TEST_PROGRAMS_NEED_X += test-revision-walking
 TEST_PROGRAMS_NEED_X += test-run-command
+TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
 TEST_PROGRAMS_NEED_X += test-sha1
 TEST_PROGRAMS_NEED_X += test-sigchain
 TEST_PROGRAMS_NEED_X += test-subprocess
@@ -543,6 +552,36 @@ LIB_FILE=libgit.a
 XDIFF_LIB=xdiff/lib.a
 VCSSVN_LIB=vcs-svn/lib.a
 
+XDIFF_H += xdiff/xinclude.h
+XDIFF_H += xdiff/xmacros.h
+XDIFF_H += xdiff/xdiff.h
+XDIFF_H += xdiff/xtypes.h
+XDIFF_H += xdiff/xutils.h
+XDIFF_H += xdiff/xprepare.h
+XDIFF_H += xdiff/xdiffi.h
+XDIFF_H += xdiff/xemit.h
+
+VCSSVN_H += vcs-svn/line_buffer.h
+VCSSVN_H += vcs-svn/sliding_window.h
+VCSSVN_H += vcs-svn/repo_tree.h
+VCSSVN_H += vcs-svn/fast_export.h
+VCSSVN_H += vcs-svn/svndiff.h
+VCSSVN_H += vcs-svn/svndump.h
+
+MISC_H += bisect.h
+MISC_H += branch.h
+MISC_H += bundle.h
+MISC_H += common-cmds.h
+MISC_H += fetch-pack.h
+MISC_H += reachable.h
+MISC_H += send-pack.h
+MISC_H += shortlog.h
+MISC_H += tar.h
+MISC_H += thread-utils.h
+MISC_H += url.h
+MISC_H += walker.h
+MISC_H += wt-status.h
+
 LIB_H += advice.h
 LIB_H += archive.h
 LIB_H += argv-array.h
@@ -559,18 +598,18 @@ LIB_H += compat/cygwin.h
 LIB_H += compat/mingw.h
 LIB_H += compat/obstack.h
 LIB_H += compat/terminal.h
+LIB_H += compat/win32/dirent.h
+LIB_H += compat/win32/poll.h
 LIB_H += compat/win32/pthread.h
 LIB_H += compat/win32/syslog.h
-LIB_H += compat/win32/poll.h
-LIB_H += compat/win32/dirent.h
 LIB_H += connected.h
 LIB_H += convert.h
 LIB_H += credential.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
-LIB_H += diffcore.h
 LIB_H += diff.h
+LIB_H += diffcore.h
 LIB_H += dir.h
 LIB_H += exec_cmd.h
 LIB_H += fmt-merge-msg.h
@@ -590,6 +629,7 @@ LIB_H += log-tree.h
 LIB_H += mailmap.h
 LIB_H += merge-file.h
 LIB_H += merge-recursive.h
+LIB_H += mergesort.h
 LIB_H += notes.h
 LIB_H += notes-cache.h
 LIB_H += notes-merge.h
@@ -627,6 +667,7 @@ LIB_H += tree-walk.h
 LIB_H += unpack-trees.h
 LIB_H += userdiff.h
 LIB_H += utf8.h
+LIB_H += varint.h
 LIB_H += xdiff-interface.h
 LIB_H += xdiff/xdiff.h
 
@@ -647,6 +688,7 @@ LIB_OBJS += bulk-checkin.o
 LIB_OBJS += bundle.o
 LIB_OBJS += cache-tree.o
 LIB_OBJS += color.o
+LIB_OBJS += column.o
 LIB_OBJS += combine-diff.o
 LIB_OBJS += commit.o
 LIB_OBJS += compat/obstack.o
@@ -676,8 +718,8 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
-LIB_OBJS += gpg-interface.o
 LIB_OBJS += gettext.o
+LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -694,6 +736,7 @@ LIB_OBJS += mailmap.o
 LIB_OBJS += match-trees.o
 LIB_OBJS += merge-file.o
 LIB_OBJS += merge-recursive.o
+LIB_OBJS += mergesort.o
 LIB_OBJS += name-hash.o
 LIB_OBJS += notes.o
 LIB_OBJS += notes-cache.o
@@ -725,9 +768,9 @@ LIB_OBJS += rerere.o
 LIB_OBJS += resolve-undo.o
 LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
+LIB_OBJS += sequencer.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
-LIB_OBJS += sequencer.o
 LIB_OBJS += sha1-array.o
 LIB_OBJS += sha1-lookup.o
 LIB_OBJS += sha1_file.o
@@ -752,6 +795,7 @@ LIB_OBJS += url.o
 LIB_OBJS += usage.o
 LIB_OBJS += userdiff.o
 LIB_OBJS += utf8.o
+LIB_OBJS += varint.o
 LIB_OBJS += walker.o
 LIB_OBJS += wrapper.o
 LIB_OBJS += write_or_die.o
@@ -775,6 +819,7 @@ BUILTIN_OBJS += builtin/checkout-index.o
 BUILTIN_OBJS += builtin/checkout.o
 BUILTIN_OBJS += builtin/clean.o
 BUILTIN_OBJS += builtin/clone.o
+BUILTIN_OBJS += builtin/column.o
 BUILTIN_OBJS += builtin/commit-tree.o
 BUILTIN_OBJS += builtin/commit.o
 BUILTIN_OBJS += builtin/config.o
@@ -864,6 +909,9 @@ EXTLIBS =
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
 
+ifeq ($(uname_M),x86_64)
+       XDL_FAST_HASH = YesPlease
+endif
 ifeq ($(uname_S),OSF1)
        # Need this for u_short definitions et al
        BASIC_CFLAGS += -D_OSF_SOURCE
@@ -1737,6 +1785,10 @@ ifndef NO_MSGFMT_EXTENDED_OPTIONS
        MSGFMT += --check --statistics
 endif
 
+ifneq (,$(XDL_FAST_HASH))
+       BASIC_CFLAGS += -DXDL_FAST_HASH
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1849,6 +1901,13 @@ DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(DEFAULT_PAGER_CQ))
 BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
 endif
 
+ifdef SHELL_PATH
+SHELL_PATH_CQ = "$(subst ",\",$(subst \,\\,$(SHELL_PATH)))"
+SHELL_PATH_CQ_SQ = $(subst ','\'',$(SHELL_PATH_CQ))
+
+BASIC_CFLAGS += -DSHELL_PATH='$(SHELL_PATH_CQ_SQ)'
+endif
+
 ALL_CFLAGS += $(BASIC_CFLAGS)
 ALL_LDFLAGS += $(BASIC_LDFLAGS)
 
@@ -2167,27 +2226,12 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h
 builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
 builtin/tar-tree.o archive-tar.o: tar.h
 connect.o transport.o url.o http-backend.o: url.h
+builtin/branch.o builtin/commit.o builtin/tag.o column.o help.o pager.o: column.h
 http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
 http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
 
-XDIFF_H += xdiff/xinclude.h
-XDIFF_H += xdiff/xmacros.h
-XDIFF_H += xdiff/xdiff.h
-XDIFF_H += xdiff/xtypes.h
-XDIFF_H += xdiff/xutils.h
-XDIFF_H += xdiff/xprepare.h
-XDIFF_H += xdiff/xdiffi.h
-XDIFF_H += xdiff/xemit.h
-
 xdiff-interface.o $(XDIFF_OBJS): $(XDIFF_H)
 
-VCSSVN_H += vcs-svn/line_buffer.h
-VCSSVN_H += vcs-svn/sliding_window.h
-VCSSVN_H += vcs-svn/repo_tree.h
-VCSSVN_H += vcs-svn/fast_export.h
-VCSSVN_H += vcs-svn/svndiff.h
-VCSSVN_H += vcs-svn/svndump.h
-
 $(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) $(VCSSVN_H)
 endif
 
@@ -2258,6 +2302,8 @@ $(XDIFF_LIB): $(XDIFF_OBJS)
 $(VCSSVN_LIB): $(VCSSVN_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
 
+export DEFAULT_EDITOR DEFAULT_PAGER
+
 doc:
        $(MAKE) -C Documentation all
 
@@ -2282,7 +2328,7 @@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
        --keyword=_ --keyword=N_ --keyword="Q_:1,2"
 XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
 XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
-LOCALIZED_C := $(C_OBJ:o=c)
+LOCALIZED_C := $(C_OBJ:o=c) $(LIB_H) $(XDIFF_H) $(VCSSVN_H) $(MISC_H)
 LOCALIZED_SH := $(SCRIPT_SH)
 LOCALIZED_PERL := $(SCRIPT_PERL)
 
index 46474cc7082ee3846e8c420762093d4d5c44209e..bcb4fb98ff925389797125283e40c6e397efc1fd 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.10.1.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.11.txt
\ No newline at end of file
index 01130e54e7b270df7f535fb815dba25ddb72ec1a..a492eea24f71ad2d2082efce9d1d925a5766b111 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -1,6 +1,9 @@
 #include "cache.h"
 
 int advice_push_nonfastforward = 1;
+int advice_push_non_ff_current = 1;
+int advice_push_non_ff_default = 1;
+int advice_push_non_ff_matching = 1;
 int advice_status_hints = 1;
 int advice_commit_before_merge = 1;
 int advice_resolve_conflict = 1;
@@ -12,6 +15,9 @@ static struct {
        int *preference;
 } advice_config[] = {
        { "pushnonfastforward", &advice_push_nonfastforward },
+       { "pushnonffcurrent", &advice_push_non_ff_current },
+       { "pushnonffdefault", &advice_push_non_ff_default },
+       { "pushnonffmatching", &advice_push_non_ff_matching },
        { "statushints", &advice_status_hints },
        { "commitbeforemerge", &advice_commit_before_merge },
        { "resolveconflict", &advice_resolve_conflict },
index 7bda45b83e34b8417e5c20219c7424bb35b3d681..f3cdbbf29e570e151b2b6b329ee9a9940ae59a98 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -4,6 +4,9 @@
 #include "git-compat-util.h"
 
 extern int advice_push_nonfastforward;
+extern int advice_push_non_ff_current;
+extern int advice_push_non_ff_default;
+extern int advice_push_non_ff_matching;
 extern int advice_status_hints;
 extern int advice_commit_before_merge;
 extern int advice_resolve_conflict;
index a4e04201e61d0243dc225d85117341d993ca0c3b..0b5f8898a10f16df8a6273f8960f05b670ba94bc 100644 (file)
@@ -2,8 +2,7 @@
 #include "argv-array.h"
 #include "strbuf.h"
 
-static const char *empty_argv_storage = NULL;
-const char **empty_argv = &empty_argv_storage;
+const char *empty_argv[] = { NULL };
 
 void argv_array_init(struct argv_array *array)
 {
@@ -39,6 +38,17 @@ void argv_array_pushf(struct argv_array *array, const char *fmt, ...)
        argv_array_push_nodup(array, strbuf_detach(&v, NULL));
 }
 
+void argv_array_pushl(struct argv_array *array, ...)
+{
+       va_list ap;
+       const char *arg;
+
+       va_start(ap, array);
+       while((arg = va_arg(ap, const char *)))
+               argv_array_push(array, arg);
+       va_end(ap);
+}
+
 void argv_array_clear(struct argv_array *array)
 {
        if (array->argv != empty_argv) {
index 74dd2b1bc0487fb3493c1c2747ebe2bd8958616b..b93a69c36cb8d391c1d7de93f75b3d54c00e60ca 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef ARGV_ARRAY_H
 #define ARGV_ARRAY_H
 
-extern const char **empty_argv;
+extern const char *empty_argv[];
 
 struct argv_array {
        const char **argv;
@@ -15,6 +15,7 @@ void argv_array_init(struct argv_array *);
 void argv_array_push(struct argv_array *, const char *);
 __attribute__((format (printf,2,3)))
 void argv_array_pushf(struct argv_array *, const char *fmt, ...);
+void argv_array_pushl(struct argv_array *, ...);
 void argv_array_clear(struct argv_array *);
 
 #endif /* ARGV_ARRAY_H */
index 9971820a184d9713126c3c9f763dd8f6ec1b1a50..eccdaf93924334137833e5acf7541aa514588c2d 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -101,9 +101,10 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
  * config.
  */
 static int setup_tracking(const char *new_ref, const char *orig_ref,
-                          enum branch_track track)
+                         enum branch_track track, int quiet)
 {
        struct tracking tracking;
+       int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
 
        if (strlen(new_ref) > 1024 - 7 - 7 - 1)
                return error("Tracking not set up: name too long: %s",
@@ -128,7 +129,7 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
                return error("Not tracking: ambiguous information for ref %s",
                                orig_ref);
 
-       install_branch_config(BRANCH_CONFIG_VERBOSE, new_ref, tracking.remote,
+       install_branch_config(config_flags, new_ref, tracking.remote,
                              tracking.src ? tracking.src : orig_ref);
 
        free(tracking.src);
@@ -191,7 +192,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref,
 void create_branch(const char *head,
                   const char *name, const char *start_name,
                   int force, int reflog, int clobber_head,
-                  enum branch_track track)
+                  int quiet, enum branch_track track)
 {
        struct ref_lock *lock = NULL;
        struct commit *commit;
@@ -260,7 +261,7 @@ void create_branch(const char *head,
                         start_name);
 
        if (real_ref && track)
-               setup_tracking(ref.buf+11, real_ref, track);
+               setup_tracking(ref.buf+11, real_ref, track, quiet);
 
        if (!dont_change_ref)
                if (write_ref_sha1(lock, sha1, msg) < 0)
index b99c5a369e31a85d1fff822460e69a79d8c6102b..64173abf4db65b0a8e71c1c8880f97a3350306f7 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -14,7 +14,7 @@
  */
 void create_branch(const char *head, const char *name, const char *start_name,
                   int force, int reflog,
-                  int clobber_head, enum branch_track track);
+                  int clobber_head, int quiet, enum branch_track track);
 
 /*
  * Validates that the requested branch may be created, returning the
index 857b9c8aa85fff5764b528485a880cd9dfa95b17..338f540e39af7093b39668a3bed16158a4483566 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
 extern int cmd_clone(int argc, const char **argv, const char *prefix);
 extern int cmd_clean(int argc, const char **argv, const char *prefix);
+extern int cmd_column(int argc, const char **argv, const char *prefix);
 extern int cmd_commit(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_config(int argc, const char **argv, const char *prefix);
index 389898f13364eb640077c1d82fefea98d9d3755f..725712d7888c1a5503c2249c6dbd50ee796c2851 100644 (file)
@@ -103,7 +103,7 @@ static void parse_whitespace_option(const char *option)
                ws_error_action = correct_ws_error;
                return;
        }
-       die("unrecognized whitespace option '%s'", option);
+       die(_("unrecognized whitespace option '%s'"), option);
 }
 
 static void parse_ignorewhitespace_option(const char *option)
@@ -118,7 +118,7 @@ static void parse_ignorewhitespace_option(const char *option)
                ws_ignore_action = ignore_ws_change;
                return;
        }
-       die("unrecognized whitespace ignore option '%s'", option);
+       die(_("unrecognized whitespace ignore option '%s'"), option);
 }
 
 static void set_default_whitespace_mode(const char *whitespace_option)
@@ -152,9 +152,14 @@ struct fragment {
        unsigned long leading, trailing;
        unsigned long oldpos, oldlines;
        unsigned long newpos, newlines;
+       /*
+        * 'patch' is usually borrowed from buf in apply_patch(),
+        * but some codepaths store an allocated buffer.
+        */
        const char *patch;
+       unsigned free_patch:1,
+               rejected:1;
        int size;
-       int rejected;
        int linenr;
        struct fragment *next;
 };
@@ -196,6 +201,36 @@ struct patch {
        struct patch *next;
 };
 
+static void free_fragment_list(struct fragment *list)
+{
+       while (list) {
+               struct fragment *next = list->next;
+               if (list->free_patch)
+                       free((char *)list->patch);
+               free(list);
+               list = next;
+       }
+}
+
+static void free_patch(struct patch *patch)
+{
+       free_fragment_list(patch->fragments);
+       free(patch->def_name);
+       free(patch->old_name);
+       free(patch->new_name);
+       free(patch->result);
+       free(patch);
+}
+
+static void free_patch_list(struct patch *list)
+{
+       while (list) {
+               struct patch *next = list->next;
+               free_patch(list);
+               list = next;
+       }
+}
+
 /*
  * A line in a file, len-bytes long (includes the terminating LF,
  * except for an incomplete line at the end if the file ends with
@@ -302,6 +337,11 @@ static void add_line_info(struct image *img, const char *bol, size_t len, unsign
        img->nr++;
 }
 
+/*
+ * "buf" has the file contents to be patched (read from various sources).
+ * attach it to "image" and add line-based index to it.
+ * "image" now owns the "buf".
+ */
 static void prepare_image(struct image *image, char *buf, size_t len,
                          int prepare_linetable)
 {
@@ -335,25 +375,27 @@ static void clear_image(struct image *image)
        image->len = 0;
 }
 
-static void say_patch_name(FILE *output, const char *pre,
-                          struct patch *patch, const char *post)
+/* fmt must contain _one_ %s and no other substitution */
+static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
 {
-       fputs(pre, output);
+       struct strbuf sb = STRBUF_INIT;
+
        if (patch->old_name && patch->new_name &&
            strcmp(patch->old_name, patch->new_name)) {
-               quote_c_style(patch->old_name, NULL, output, 0);
-               fputs(" => ", output);
-               quote_c_style(patch->new_name, NULL, output, 0);
+               quote_c_style(patch->old_name, &sb, NULL, 0);
+               strbuf_addstr(&sb, " => ");
+               quote_c_style(patch->new_name, &sb, NULL, 0);
        } else {
                const char *n = patch->new_name;
                if (!n)
                        n = patch->old_name;
-               quote_c_style(n, NULL, output, 0);
+               quote_c_style(n, &sb, NULL, 0);
        }
-       fputs(post, output);
+       fprintf(output, fmt, sb.buf);
+       fputc('\n', output);
+       strbuf_release(&sb);
 }
 
-#define CHUNKSIZE (8192)
 #define SLOP (16)
 
 static void read_patch_file(struct strbuf *sb, int fd)
@@ -416,7 +458,7 @@ static char *squash_slash(char *name)
        return name;
 }
 
-static char *find_name_gnu(const char *line, char *def, int p_value)
+static char *find_name_gnu(const char *line, const char *def, int p_value)
 {
        struct strbuf name = STRBUF_INIT;
        char *cp;
@@ -439,11 +481,7 @@ static char *find_name_gnu(const char *line, char *def, int p_value)
                cp++;
        }
 
-       /* name can later be freed, so we need
-        * to memmove, not just return cp
-        */
        strbuf_remove(&name, 0, cp - name.buf);
-       free(def);
        if (root)
                strbuf_insert(&name, 0, root, root_len);
        return squash_slash(strbuf_detach(&name, NULL));
@@ -608,8 +646,13 @@ static size_t diff_timestamp_len(const char *line, size_t len)
        return line + len - end;
 }
 
-static char *find_name_common(const char *line, char *def, int p_value,
-                               const char *end, int terminate)
+static char *null_strdup(const char *s)
+{
+       return s ? xstrdup(s) : NULL;
+}
+
+static char *find_name_common(const char *line, const char *def,
+                             int p_value, const char *end, int terminate)
 {
        int len;
        const char *start = NULL;
@@ -630,10 +673,10 @@ static char *find_name_common(const char *line, char *def, int p_value,
                        start = line;
        }
        if (!start)
-               return squash_slash(def);
+               return squash_slash(null_strdup(def));
        len = line - start;
        if (!len)
-               return squash_slash(def);
+               return squash_slash(null_strdup(def));
 
        /*
         * Generally we prefer the shorter name, especially
@@ -644,8 +687,7 @@ static char *find_name_common(const char *line, char *def, int p_value,
        if (def) {
                int deflen = strlen(def);
                if (deflen < len && !strncmp(start, def, deflen))
-                       return squash_slash(def);
-               free(def);
+                       return squash_slash(xstrdup(def));
        }
 
        if (root) {
@@ -770,7 +812,7 @@ static int has_epoch_timestamp(const char *nameline)
        if (!stamp) {
                stamp = xmalloc(sizeof(*stamp));
                if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
-                       warning("Cannot prepare timestamp regexp %s",
+                       warning(_("Cannot prepare timestamp regexp %s"),
                                stamp_regexp);
                        return 0;
                }
@@ -779,7 +821,7 @@ static int has_epoch_timestamp(const char *nameline)
        status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
        if (status) {
                if (status != REG_NOMATCH)
-                       warning("regexec returned %d for input: %s",
+                       warning(_("regexec returned %d for input: %s"),
                                status, timestamp);
                return 0;
        }
@@ -842,8 +884,10 @@ static void parse_traditional_patch(const char *first, const char *second, struc
                name = find_name_traditional(first, NULL, p_value);
                patch->old_name = name;
        } else {
-               name = find_name_traditional(first, NULL, p_value);
-               name = find_name_traditional(second, name, p_value);
+               char *first_name;
+               first_name = find_name_traditional(first, NULL, p_value);
+               name = find_name_traditional(second, first_name, p_value);
+               free(first_name);
                if (has_epoch_timestamp(first)) {
                        patch->is_new = 1;
                        patch->is_delete = 0;
@@ -853,11 +897,12 @@ static void parse_traditional_patch(const char *first, const char *second, struc
                        patch->is_delete = 1;
                        patch->old_name = name;
                } else {
-                       patch->old_name = patch->new_name = name;
+                       patch->old_name = name;
+                       patch->new_name = xstrdup(name);
                }
        }
        if (!name)
-               die("unable to find filename in patch at line %d", linenr);
+               die(_("unable to find filename in patch at line %d"), linenr);
 }
 
 static int gitdiff_hdrend(const char *line, struct patch *patch)
@@ -886,30 +931,36 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
                name = orig_name;
                len = strlen(name);
                if (isnull)
-                       die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
+                       die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr);
                another = find_name(line, NULL, p_value, TERM_TAB);
                if (!another || memcmp(another, name, len + 1))
-                       die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+                       die(_("git apply: bad git-diff - inconsistent %s filename on line %d"), oldnew, linenr);
                free(another);
                return orig_name;
        }
        else {
                /* expect "/dev/null" */
                if (memcmp("/dev/null", line, 9) || line[9] != '\n')
-                       die("git apply: bad git-diff - expected /dev/null on line %d", linenr);
+                       die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr);
                return NULL;
        }
 }
 
 static int gitdiff_oldname(const char *line, struct patch *patch)
 {
+       char *orig = patch->old_name;
        patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
+       if (orig != patch->old_name)
+               free(orig);
        return 0;
 }
 
 static int gitdiff_newname(const char *line, struct patch *patch)
 {
+       char *orig = patch->new_name;
        patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
+       if (orig != patch->new_name)
+               free(orig);
        return 0;
 }
 
@@ -928,20 +979,23 @@ static int gitdiff_newmode(const char *line, struct patch *patch)
 static int gitdiff_delete(const char *line, struct patch *patch)
 {
        patch->is_delete = 1;
-       patch->old_name = patch->def_name;
+       free(patch->old_name);
+       patch->old_name = null_strdup(patch->def_name);
        return gitdiff_oldmode(line, patch);
 }
 
 static int gitdiff_newfile(const char *line, struct patch *patch)
 {
        patch->is_new = 1;
-       patch->new_name = patch->def_name;
+       free(patch->new_name);
+       patch->new_name = null_strdup(patch->def_name);
        return gitdiff_newmode(line, patch);
 }
 
 static int gitdiff_copysrc(const char *line, struct patch *patch)
 {
        patch->is_copy = 1;
+       free(patch->old_name);
        patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
 }
@@ -949,6 +1003,7 @@ static int gitdiff_copysrc(const char *line, struct patch *patch)
 static int gitdiff_copydst(const char *line, struct patch *patch)
 {
        patch->is_copy = 1;
+       free(patch->new_name);
        patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
 }
@@ -956,6 +1011,7 @@ static int gitdiff_copydst(const char *line, struct patch *patch)
 static int gitdiff_renamesrc(const char *line, struct patch *patch)
 {
        patch->is_rename = 1;
+       free(patch->old_name);
        patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
 }
@@ -963,6 +1019,7 @@ static int gitdiff_renamesrc(const char *line, struct patch *patch)
 static int gitdiff_renamedst(const char *line, struct patch *patch)
 {
        patch->is_rename = 1;
+       free(patch->new_name);
        patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
 }
@@ -1044,7 +1101,7 @@ static const char *stop_at_slash(const char *line, int llen)
  * creation or deletion of an empty file.  In any of these cases,
  * both sides are the same name under a/ and b/ respectively.
  */
-static char *git_header_name(char *line, int llen)
+static char *git_header_name(const char *line, int llen)
 {
        const char *name;
        const char *second = NULL;
@@ -1171,7 +1228,7 @@ static char *git_header_name(char *line, int llen)
 }
 
 /* Verify that we recognize the lines following a git header */
-static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
+static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch)
 {
        unsigned long offset;
 
@@ -1287,7 +1344,7 @@ static int parse_range(const char *line, int len, int offset, const char *expect
        return offset + ex;
 }
 
-static void recount_diff(char *line, int size, struct fragment *fragment)
+static void recount_diff(const char *line, int size, struct fragment *fragment)
 {
        int oldlines = 0, newlines = 0, ret = 0;
 
@@ -1327,7 +1384,7 @@ static void recount_diff(char *line, int size, struct fragment *fragment)
                        break;
                }
                if (ret) {
-                       warning("recount: unexpected line: %.*s",
+                       warning(_("recount: unexpected line: %.*s"),
                                (int)linelen(line, size), line);
                        return;
                }
@@ -1341,7 +1398,7 @@ static void recount_diff(char *line, int size, struct fragment *fragment)
  * Parse a unified diff fragment header of the
  * form "@@ -a,b +c,d @@"
  */
-static int parse_fragment_header(char *line, int len, struct fragment *fragment)
+static int parse_fragment_header(const char *line, int len, struct fragment *fragment)
 {
        int offset;
 
@@ -1355,7 +1412,7 @@ static int parse_fragment_header(char *line, int len, struct fragment *fragment)
        return offset;
 }
 
-static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
+static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch)
 {
        unsigned long offset, len;
 
@@ -1384,7 +1441,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                        struct fragment dummy;
                        if (parse_fragment_header(line, len, &dummy) < 0)
                                continue;
-                       die("patch fragment without header at line %d: %.*s",
+                       die(_("patch fragment without header at line %d: %.*s"),
                            linenr, (int)len-1, line);
                }
 
@@ -1401,9 +1458,14 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                                continue;
                        if (!patch->old_name && !patch->new_name) {
                                if (!patch->def_name)
-                                       die("git diff header lacks filename information when removing "
-                                           "%d leading pathname components (line %d)" , p_value, linenr);
-                               patch->old_name = patch->new_name = patch->def_name;
+                                       die(Q_("git diff header lacks filename information when removing "
+                                              "%d leading pathname component (line %d)",
+                                              "git diff header lacks filename information when removing "
+                                              "%d leading pathname components (line %d)",
+                                              p_value),
+                                           p_value, linenr);
+                               patch->old_name = xstrdup(patch->def_name);
+                               patch->new_name = xstrdup(patch->def_name);
                        }
                        if (!patch->is_delete && !patch->new_name)
                                die("git diff header lacks filename information "
@@ -1466,7 +1528,7 @@ static void check_whitespace(const char *line, int len, unsigned ws_rule)
  * between a "---" that is part of a patch, and a "---" that starts
  * the next patch is to look at the line counts..
  */
-static int parse_fragment(char *line, unsigned long size,
+static int parse_fragment(const char *line, unsigned long size,
                          struct patch *patch, struct fragment *fragment)
 {
        int added, deleted;
@@ -1556,13 +1618,21 @@ static int parse_fragment(char *line, unsigned long size,
        patch->lines_deleted += deleted;
 
        if (0 < patch->is_new && oldlines)
-               return error("new file depends on old contents");
+               return error(_("new file depends on old contents"));
        if (0 < patch->is_delete && newlines)
-               return error("deleted file still has contents");
+               return error(_("deleted file still has contents"));
        return offset;
 }
 
-static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+/*
+ * We have seen "diff --git a/... b/..." header (or a traditional patch
+ * header).  Read hunks that belong to this patch into fragments and hang
+ * them to the given patch structure.
+ *
+ * The (fragment->patch, fragment->size) pair points into the memory given
+ * by the caller, not a copy, when we return.
+ */
+static int parse_single_patch(const char *line, unsigned long size, struct patch *patch)
 {
        unsigned long offset = 0;
        unsigned long oldlines = 0, newlines = 0, context = 0;
@@ -1576,7 +1646,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
                fragment->linenr = linenr;
                len = parse_fragment(line, size, patch, fragment);
                if (len <= 0)
-                       die("corrupt patch at line %d", linenr);
+                       die(_("corrupt patch at line %d"), linenr);
                fragment->patch = line;
                fragment->size = len;
                oldlines += fragment->oldlines;
@@ -1612,12 +1682,14 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
                patch->is_delete = 0;
 
        if (0 < patch->is_new && oldlines)
-               die("new file %s depends on old contents", patch->new_name);
+               die(_("new file %s depends on old contents"), patch->new_name);
        if (0 < patch->is_delete && newlines)
-               die("deleted file %s still has contents", patch->old_name);
+               die(_("deleted file %s still has contents"), patch->old_name);
        if (!patch->is_delete && !newlines && context)
-               fprintf(stderr, "** warning: file %s becomes empty but "
-                       "is not deleted\n", patch->new_name);
+               fprintf_ln(stderr,
+                          _("** warning: "
+                            "file %s becomes empty but is not deleted"),
+                          patch->new_name);
 
        return offset;
 }
@@ -1655,6 +1727,11 @@ static char *inflate_it(const void *data, unsigned long size,
        return out;
 }
 
+/*
+ * Read a binary hunk and return a new fragment; fragment->patch
+ * points at an allocated memory that the caller must free, so
+ * it is marked as "->free_patch = 1".
+ */
 static struct fragment *parse_binary_hunk(char **buf_p,
                                          unsigned long *sz_p,
                                          int *status_p,
@@ -1742,6 +1819,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
 
        frag = xcalloc(1, sizeof(*frag));
        frag->patch = inflate_it(data, hunk_size, origlen);
+       frag->free_patch = 1;
        if (!frag->patch)
                goto corrupt;
        free(data);
@@ -1755,7 +1833,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
  corrupt:
        free(data);
        *status_p = -1;
-       error("corrupt binary patch at line %d: %.*s",
+       error(_("corrupt binary patch at line %d: %.*s"),
              linenr-1, llen-1, buffer);
        return NULL;
 }
@@ -1784,7 +1862,7 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
        forward = parse_binary_hunk(&buffer, &size, &status, &used);
        if (!forward && !status)
                /* there has to be one hunk (forward hunk) */
-               return error("unrecognized binary patch at line %d", linenr-1);
+               return error(_("unrecognized binary patch at line %d"), linenr-1);
        if (status)
                /* otherwise we already gave an error message */
                return status;
@@ -1807,6 +1885,13 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
        return used;
 }
 
+/*
+ * Read the patch text in "buffer" taht extends for "size" bytes; stop
+ * reading after seeing a single patch (i.e. changes to a single file).
+ * Create fragments (i.e. patch hunks) and hang them to the given patch.
+ * Return the number of bytes consumed, so that the caller can call us
+ * again for the next patch.
+ */
 static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 {
        int hdrsize, patchsize;
@@ -1863,7 +1948,7 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
                 */
                if ((apply || check) &&
                    (!patch->is_binary && !metadata_changes(patch)))
-                       die("patch with only garbage at line %d", linenr);
+                       die(_("patch with only garbage at line %d"), linenr);
        }
 
        return offset + hdrsize + patchsize;
@@ -1953,11 +2038,11 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
        switch (st->st_mode & S_IFMT) {
        case S_IFLNK:
                if (strbuf_readlink(buf, path, st->st_size) < 0)
-                       return error("unable to read symlink %s", path);
+                       return error(_("unable to read symlink %s"), path);
                return 0;
        case S_IFREG:
                if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
-                       return error("unable to open or read %s", path);
+                       return error(_("unable to open or read %s"), path);
                convert_to_git(path, buf->buf, buf->len, buf, 0);
                return 0;
        default:
@@ -2028,7 +2113,7 @@ static void update_pre_post_images(struct image *preimage,
                        ctx++;
                }
                if (preimage->nr <= ctx)
-                       die("oops");
+                       die(_("oops"));
 
                /* and copy it in, while fixing the line length */
                len = preimage->line[ctx].len;
@@ -2367,6 +2452,11 @@ static void remove_last_line(struct image *img)
        img->len -= img->line[--img->nr].len;
 }
 
+/*
+ * The change from "preimage" and "postimage" has been found to
+ * apply at applied_pos (counts in line numbers) in "img".
+ * Update "img" to remove "preimage" and replace it with "postimage".
+ */
 static void update_image(struct image *img,
                         int applied_pos,
                         struct image *preimage,
@@ -2438,6 +2528,11 @@ static void update_image(struct image *img,
        img->nr = nr;
 }
 
+/*
+ * Use the patch-hunk text in "frag" to prepare two images (preimage and
+ * postimage) for the hunk.  Find lines that match "preimage" in "img" and
+ * replace the part of "img" with "postimage" text.
+ */
 static int apply_one_fragment(struct image *img, struct fragment *frag,
                              int inaccurate_eof, unsigned ws_rule,
                              int nth_fragment)
@@ -2540,7 +2635,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                        break;
                default:
                        if (apply_verbosely)
-                               error("invalid start of line: '%c'", first);
+                               error(_("invalid start of line: '%c'"), first);
                        return -1;
                }
                if (added_blank_line) {
@@ -2657,9 +2752,11 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                        int offset = applied_pos - pos;
                        if (apply_in_reverse)
                                offset = 0 - offset;
-                       fprintf(stderr,
-                               "Hunk #%d succeeded at %d (offset %d lines).\n",
-                               nth_fragment, applied_pos + 1, offset);
+                       fprintf_ln(stderr,
+                                  Q_("Hunk #%d succeeded at %d (offset %d line).",
+                                     "Hunk #%d succeeded at %d (offset %d lines).",
+                                     offset),
+                                  nth_fragment, applied_pos + 1, offset);
                }
 
                /*
@@ -2668,13 +2765,13 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                 */
                if ((leading != frag->leading) ||
                    (trailing != frag->trailing))
-                       fprintf(stderr, "Context reduced to (%ld/%ld)"
-                               " to apply fragment at %d\n",
-                               leading, trailing, applied_pos+1);
+                       fprintf_ln(stderr, _("Context reduced to (%ld/%ld)"
+                                            " to apply fragment at %d"),
+                                  leading, trailing, applied_pos+1);
                update_image(img, applied_pos, &preimage, &postimage);
        } else {
                if (apply_verbosely)
-                       error("while searching for:\n%.*s",
+                       error(_("while searching for:\n%.*s"),
                              (int)(old - oldlines), oldlines);
        }
 
@@ -2693,7 +2790,7 @@ static int apply_binary_fragment(struct image *img, struct patch *patch)
        void *dst;
 
        if (!fragment)
-               return error("missing binary patch data for '%s'",
+               return error(_("missing binary patch data for '%s'"),
                             patch->new_name ?
                             patch->new_name :
                             patch->old_name);
@@ -2728,6 +2825,12 @@ static int apply_binary_fragment(struct image *img, struct patch *patch)
        return -1;
 }
 
+/*
+ * Replace "img" with the result of applying the binary patch.
+ * The binary patch data itself in patch->fragment is still kept
+ * but the preimage prepared by the caller in "img" is freed here
+ * or in the helper function apply_binary_fragment() this calls.
+ */
 static int apply_binary(struct image *img, struct patch *patch)
 {
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
@@ -2790,13 +2893,13 @@ static int apply_binary(struct image *img, struct patch *patch)
                 * in the patch->fragments->{patch,size}.
                 */
                if (apply_binary_fragment(img, patch))
-                       return error("binary patch does not apply to '%s'",
+                       return error(_("binary patch does not apply to '%s'"),
                                     name);
 
                /* verify that the result matches */
                hash_sha1_file(img->buf, img->len, blob_type, sha1);
                if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
-                       return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
+                       return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
                                name, patch->new_sha1_prefix, sha1_to_hex(sha1));
        }
 
@@ -2817,7 +2920,7 @@ static int apply_fragments(struct image *img, struct patch *patch)
        while (frag) {
                nth++;
                if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) {
-                       error("patch failed: %s:%ld", name, frag->oldpos);
+                       error(_("patch failed: %s:%ld"), name, frag->oldpos);
                        if (!apply_with_reject)
                                return -1;
                        frag->rejected = 1;
@@ -2932,14 +3035,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
        if (!(patch->is_copy || patch->is_rename) &&
            (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
                if (was_deleted(tpatch)) {
-                       return error("patch %s has been renamed/deleted",
+                       return error(_("patch %s has been renamed/deleted"),
                                patch->old_name);
                }
-               /* We have a patched copy in memory use that */
+               /* We have a patched copy in memory; use that. */
                strbuf_add(&buf, tpatch->result, tpatch->resultsize);
        } else if (cached) {
                if (read_file_or_gitlink(ce, &buf))
-                       return error("read of %s failed", patch->old_name);
+                       return error(_("read of %s failed"), patch->old_name);
        } else if (patch->old_name) {
                if (S_ISGITLINK(patch->old_mode)) {
                        if (ce) {
@@ -2948,12 +3051,15 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
                                /*
                                 * There is no way to apply subproject
                                 * patch without looking at the index.
+                                * NEEDSWORK: shouldn't this be flagged
+                                * as an error???
                                 */
+                               free_fragment_list(patch->fragments);
                                patch->fragments = NULL;
                        }
                } else {
                        if (read_old_data(st, patch->old_name, &buf))
-                               return error("read of %s failed", patch->old_name);
+                               return error(_("read of %s failed"), patch->old_name);
                }
        }
 
@@ -2968,7 +3074,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
        free(image.line_allocated);
 
        if (0 < patch->is_delete && patch->resultsize)
-               return error("removal patch leaves file contents");
+               return error(_("removal patch leaves file contents"));
 
        return 0;
 }
@@ -2989,7 +3095,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists)
                if (has_symlink_leading_path(new_name, strlen(new_name)))
                        return 0;
 
-               return error("%s: already exists in working directory", new_name);
+               return error(_("%s: already exists in working directory"), new_name);
        }
        else if ((errno != ENOENT) && (errno != ENOTDIR))
                return error("%s: %s", new_name, strerror(errno));
@@ -3027,12 +3133,12 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
        if (!(patch->is_copy || patch->is_rename) &&
            (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
                if (was_deleted(tpatch))
-                       return error("%s: has been deleted/renamed", old_name);
+                       return error(_("%s: has been deleted/renamed"), old_name);
                st_mode = tpatch->new_mode;
        } else if (!cached) {
                stat_ret = lstat(old_name, st);
                if (stat_ret && errno != ENOENT)
-                       return error("%s: %s", old_name, strerror(errno));
+                       return error(_("%s: %s"), old_name, strerror(errno));
        }
 
        if (to_be_deleted(tpatch))
@@ -3043,7 +3149,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
                if (pos < 0) {
                        if (patch->is_new < 0)
                                goto is_new;
-                       return error("%s: does not exist in index", old_name);
+                       return error(_("%s: does not exist in index"), old_name);
                }
                *ce = active_cache[pos];
                if (stat_ret < 0) {
@@ -3057,13 +3163,13 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
                                return -1;
                }
                if (!cached && verify_index_match(*ce, st))
-                       return error("%s: does not match index", old_name);
+                       return error(_("%s: does not match index"), old_name);
                if (cached)
                        st_mode = (*ce)->ce_mode;
        } else if (stat_ret < 0) {
                if (patch->is_new < 0)
                        goto is_new;
-               return error("%s: %s", old_name, strerror(errno));
+               return error(_("%s: %s"), old_name, strerror(errno));
        }
 
        if (!cached && !tpatch)
@@ -3074,9 +3180,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
        if (!patch->old_mode)
                patch->old_mode = st_mode;
        if ((st_mode ^ patch->old_mode) & S_IFMT)
-               return error("%s: wrong type", old_name);
+               return error(_("%s: wrong type"), old_name);
        if (st_mode != patch->old_mode)
-               warning("%s has type %o, expected %o",
+               warning(_("%s has type %o, expected %o"),
                        old_name, st_mode, patch->old_mode);
        if (!patch->new_mode && !patch->is_delete)
                patch->new_mode = st_mode;
@@ -3085,10 +3191,15 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
  is_new:
        patch->is_new = 1;
        patch->is_delete = 0;
+       free(patch->old_name);
        patch->old_name = NULL;
        return 0;
 }
 
+/*
+ * Check and apply the patch in-core; leave the result in patch->result
+ * for the caller to write it out to the final destination.
+ */
 static int check_patch(struct patch *patch)
 {
        struct stat st;
@@ -3126,7 +3237,7 @@ static int check_patch(struct patch *patch)
                if (check_index &&
                    cache_name_pos(new_name, strlen(new_name)) >= 0 &&
                    !ok_if_exists)
-                       return error("%s: already exists in index", new_name);
+                       return error(_("%s: already exists in index"), new_name);
                if (!cached) {
                        int err = check_to_create_blob(new_name, ok_if_exists);
                        if (err)
@@ -3145,13 +3256,13 @@ static int check_patch(struct patch *patch)
                if (!patch->new_mode)
                        patch->new_mode = patch->old_mode;
                if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
-                       return error("new mode (%o) of %s does not match old mode (%o)%s%s",
+                       return error(_("new mode (%o) of %s does not match old mode (%o)%s%s"),
                                patch->new_mode, new_name, patch->old_mode,
                                same ? "" : " of ", same ? "" : old_name);
        }
 
        if (apply_data(patch, &st, ce) < 0)
-               return error("%s: patch does not apply", name);
+               return error(_("%s: patch does not apply"), name);
        patch->rejected = 0;
        return 0;
 }
@@ -3164,7 +3275,7 @@ static int check_patch_list(struct patch *patch)
        while (patch) {
                if (apply_verbosely)
                        say_patch_name(stderr,
-                                      "Checking patch ", patch, "...\n");
+                                      _("Checking patch %s..."), patch);
                err |= check_patch(patch);
                patch = patch->next;
        }
@@ -3219,7 +3330,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename)
 
                ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
                if (!ce)
-                       die("make_cache_entry failed for path '%s'", name);
+                       die(_("make_cache_entry failed for path '%s'"), name);
                if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
                        die ("Could not add %s to temporary index", name);
        }
@@ -3362,7 +3473,7 @@ static void remove_file(struct patch *patch, int rmdir_empty)
 {
        if (update_index) {
                if (remove_file_from_cache(patch->old_name) < 0)
-                       die("unable to remove %s from index", patch->old_name);
+                       die(_("unable to remove %s from index"), patch->old_name);
        }
        if (!cached) {
                if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
@@ -3389,19 +3500,19 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
                const char *s = buf;
 
                if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
-                       die("corrupt patch for subproject %s", path);
+                       die(_("corrupt patch for subproject %s"), path);
        } else {
                if (!cached) {
                        if (lstat(path, &st) < 0)
-                               die_errno("unable to stat newly created file '%s'",
+                               die_errno(_("unable to stat newly created file '%s'"),
                                          path);
                        fill_stat_cache_info(ce, &st);
                }
                if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
-                       die("unable to create backing store for newly created file %s", path);
+                       die(_("unable to create backing store for newly created file %s"), path);
        }
        if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
-               die("unable to add cache entry for %s", path);
+               die(_("unable to add cache entry for %s"), path);
 }
 
 static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
@@ -3434,7 +3545,7 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
        strbuf_release(&nbuf);
 
        if (close(fd) < 0)
-               die_errno("closing file '%s'", path);
+               die_errno(_("closing file '%s'"), path);
        return 0;
 }
 
@@ -3483,7 +3594,7 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
                        ++nr;
                }
        }
-       die_errno("unable to write file '%s' mode %o", path, mode);
+       die_errno(_("unable to write file '%s' mode %o"), path, mode);
 }
 
 static void create_file(struct patch *patch)
@@ -3528,6 +3639,7 @@ static int write_out_one_reject(struct patch *patch)
        char namebuf[PATH_MAX];
        struct fragment *frag;
        int cnt = 0;
+       struct strbuf sb = STRBUF_INIT;
 
        for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
                if (!frag->rejected)
@@ -3538,7 +3650,7 @@ static int write_out_one_reject(struct patch *patch)
        if (!cnt) {
                if (apply_verbosely)
                        say_patch_name(stderr,
-                                      "Applied patch ", patch, " cleanly.\n");
+                                      _("Applied patch %s cleanly."), patch);
                return 0;
        }
 
@@ -3546,16 +3658,20 @@ static int write_out_one_reject(struct patch *patch)
         * contents are marked "rejected" at the patch level.
         */
        if (!patch->new_name)
-               die("internal error");
+               die(_("internal error"));
 
        /* Say this even without --verbose */
-       say_patch_name(stderr, "Applying patch ", patch, " with");
-       fprintf(stderr, " %d rejects...\n", cnt);
+       strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...",
+                           "Applying patch %%s with %d rejects...",
+                           cnt),
+                   cnt);
+       say_patch_name(stderr, sb.buf, patch);
+       strbuf_release(&sb);
 
        cnt = strlen(patch->new_name);
        if (ARRAY_SIZE(namebuf) <= cnt + 5) {
                cnt = ARRAY_SIZE(namebuf) - 5;
-               warning("truncating .rej filename to %.*s.rej",
+               warning(_("truncating .rej filename to %.*s.rej"),
                        cnt - 1, patch->new_name);
        }
        memcpy(namebuf, patch->new_name, cnt);
@@ -3563,7 +3679,7 @@ static int write_out_one_reject(struct patch *patch)
 
        rej = fopen(namebuf, "w");
        if (!rej)
-               return error("cannot open %s: %s", namebuf, strerror(errno));
+               return error(_("cannot open %s: %s"), namebuf, strerror(errno));
 
        /* Normal git tools never deal with .rej, so do not pretend
         * this is a git patch by saying --git nor give extended
@@ -3576,10 +3692,10 @@ static int write_out_one_reject(struct patch *patch)
             frag;
             cnt++, frag = frag->next) {
                if (!frag->rejected) {
-                       fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+                       fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt);
                        continue;
                }
-               fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+               fprintf_ln(stderr, _("Rejected hunk #%d."), cnt);
                fprintf(rej, "%.*s", frag->size, frag->patch);
                if (frag->patch[frag->size-1] != '\n')
                        fputc('\n', rej);
@@ -3665,15 +3781,8 @@ static void prefix_patches(struct patch *p)
        if (!prefix || p->is_toplevel_relative)
                return;
        for ( ; p; p = p->next) {
-               if (p->new_name == p->old_name) {
-                       char *prefixed = p->new_name;
-                       prefix_one(&prefixed);
-                       p->new_name = p->old_name = prefixed;
-               }
-               else {
-                       prefix_one(&p->new_name);
-                       prefix_one(&p->old_name);
-               }
+               prefix_one(&p->new_name);
+               prefix_one(&p->old_name);
        }
 }
 
@@ -3683,12 +3792,10 @@ static void prefix_patches(struct patch *p)
 static int apply_patch(int fd, const char *filename, int options)
 {
        size_t offset;
-       struct strbuf buf = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT; /* owns the patch text */
        struct patch *list = NULL, **listp = &list;
        int skipped_patch = 0;
 
-       /* FIXME - memory leak when using multiple patch files as inputs */
-       memset(&fn_table, 0, sizeof(struct string_list));
        patch_input_file = filename;
        read_patch_file(&buf, fd);
        offset = 0;
@@ -3712,15 +3819,14 @@ static int apply_patch(int fd, const char *filename, int options)
                        listp = &patch->next;
                }
                else {
-                       /* perhaps free it a bit better? */
-                       free(patch);
+                       free_patch(patch);
                        skipped_patch++;
                }
                offset += nr;
        }
 
        if (!list && !skipped_patch)
-               die("unrecognized input");
+               die(_("unrecognized input"));
 
        if (whitespace_error && (ws_error_action == die_on_ws_error))
                apply = 0;
@@ -3731,7 +3837,7 @@ static int apply_patch(int fd, const char *filename, int options)
 
        if (check_index) {
                if (read_cache() < 0)
-                       die("unable to read index file");
+                       die(_("unable to read index file"));
        }
 
        if ((check || apply) &&
@@ -3754,7 +3860,9 @@ static int apply_patch(int fd, const char *filename, int options)
        if (summary)
                summary_patch_list(list);
 
+       free_patch_list(list);
        strbuf_release(&buf);
+       string_list_clear(&fn_table, 0);
        return 0;
 }
 
@@ -3924,10 +4032,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
        if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
                apply = 0;
        if (check_index && is_not_gitdir)
-               die("--index outside a repository");
+               die(_("--index outside a repository"));
        if (cached) {
                if (is_not_gitdir)
-                       die("--cached outside a repository");
+                       die(_("--cached outside a repository"));
                check_index = 1;
        }
        for (i = 0; i < argc; i++) {
@@ -3943,7 +4051,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
 
                fd = open(arg, O_RDONLY);
                if (fd < 0)
-                       die_errno("can't open patch '%s'", arg);
+                       die_errno(_("can't open patch '%s'"), arg);
                read_stdin = 0;
                set_default_whitespace_mode(whitespace_option);
                errs |= apply_patch(fd, arg, options);
@@ -3957,32 +4065,32 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
                    squelch_whitespace_errors < whitespace_error) {
                        int squelched =
                                whitespace_error - squelch_whitespace_errors;
-                       warning("squelched %d "
-                               "whitespace error%s",
-                               squelched,
-                               squelched == 1 ? "" : "s");
+                       warning(Q_("squelched %d whitespace error",
+                                  "squelched %d whitespace errors",
+                                  squelched),
+                               squelched);
                }
                if (ws_error_action == die_on_ws_error)
-                       die("%d line%s add%s whitespace errors.",
-                           whitespace_error,
-                           whitespace_error == 1 ? "" : "s",
-                           whitespace_error == 1 ? "s" : "");
+                       die(Q_("%d line adds whitespace errors.",
+                              "%d lines add whitespace errors.",
+                              whitespace_error),
+                           whitespace_error);
                if (applied_after_fixing_ws && apply)
                        warning("%d line%s applied after"
                                " fixing whitespace errors.",
                                applied_after_fixing_ws,
                                applied_after_fixing_ws == 1 ? "" : "s");
                else if (whitespace_error)
-                       warning("%d line%s add%s whitespace errors.",
-                               whitespace_error,
-                               whitespace_error == 1 ? "" : "s",
-                               whitespace_error == 1 ? "s" : "");
+                       warning(Q_("%d line adds whitespace errors.",
+                                  "%d lines add whitespace errors.",
+                                  whitespace_error),
+                               whitespace_error);
        }
 
        if (update_index) {
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_locked_index(&lock_file))
-                       die("Unable to write new index file");
+                       die(_("Unable to write new index file"));
        }
 
        return !!errs;
index d8cccf725d3fab24ad585a26629373fc987bb3f8..d51648fee4e73ef7fb72f32c722b79a913d6b275 100644 (file)
@@ -15,6 +15,8 @@
 #include "branch.h"
 #include "diff.h"
 #include "revision.h"
+#include "string-list.h"
+#include "column.h"
 
 static const char * const builtin_branch_usage[] = {
        "git branch [options] [-r | -a] [--merged | --no-merged]",
@@ -53,6 +55,9 @@ static enum merge_filter {
 } merge_filter;
 static unsigned char merge_filter_ref[20];
 
+static struct string_list output = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
+
 static int parse_branch_color_slot(const char *var, int ofs)
 {
        if (!strcasecmp(var+ofs, "plain"))
@@ -70,6 +75,8 @@ static int parse_branch_color_slot(const char *var, int ofs)
 
 static int git_branch_config(const char *var, const char *value, void *cb)
 {
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "branch", &colopts);
        if (!strcmp(var, "color.branch")) {
                branch_use_color = git_config_colorbool(var, value);
                return 0;
@@ -146,26 +153,28 @@ static int branch_merged(int kind, const char *name,
        return merged;
 }
 
-static int delete_branches(int argc, const char **argv, int force, int kinds)
+static int delete_branches(int argc, const char **argv, int force, int kinds,
+                          int quiet)
 {
        struct commit *rev, *head_rev = NULL;
        unsigned char sha1[20];
        char *name = NULL;
-       const char *fmt, *remote;
+       const char *fmt;
        int i;
        int ret = 0;
+       int remote_branch = 0;
        struct strbuf bname = STRBUF_INIT;
 
        switch (kinds) {
        case REF_REMOTE_BRANCH:
                fmt = "refs/remotes/%s";
-               /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */
-               remote = _("remote ");
+               /* For subsequent UI messages */
+               remote_branch = 1;
+
                force = 1;
                break;
        case REF_LOCAL_BRANCH:
                fmt = "refs/heads/%s";
-               remote = "";
                break;
        default:
                die(_("cannot use -a with -d"));
@@ -189,8 +198,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
 
                name = xstrdup(mkpath(fmt, bname.buf));
                if (read_ref(name, sha1)) {
-                       error(_("%sbranch '%s' not found."),
-                                       remote, bname.buf);
+                       error(remote_branch
+                             ? _("remote branch '%s' not found.")
+                             : _("branch '%s' not found."), bname.buf);
                        ret = 1;
                        continue;
                }
@@ -211,14 +221,19 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                }
 
                if (delete_ref(name, sha1, 0)) {
-                       error(_("Error deleting %sbranch '%s'"), remote,
+                       error(remote_branch
+                             ? _("Error deleting remote branch '%s'")
+                             : _("Error deleting branch '%s'"),
                              bname.buf);
                        ret = 1;
                } else {
                        struct strbuf buf = STRBUF_INIT;
-                       printf(_("Deleted %sbranch %s (was %s).\n"), remote,
-                              bname.buf,
-                              find_unique_abbrev(sha1, DEFAULT_ABBREV));
+                       if (!quiet)
+                               printf(remote_branch
+                                      ? _("Deleted remote branch %s (was %s).\n")
+                                      : _("Deleted branch %s (was %s).\n"),
+                                      bname.buf,
+                                      find_unique_abbrev(sha1, DEFAULT_ABBREV));
                        strbuf_addf(&buf, "branch.%s", bname.buf);
                        if (git_config_rename_section(buf.buf, NULL) < 0)
                                warning(_("Update of config-file failed"));
@@ -474,7 +489,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
        else if (verbose)
                /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
                add_verbose_info(&out, item, verbose, abbrev);
-       printf("%s\n", out.buf);
+       if (column_active(colopts)) {
+               assert(!verbose && "--column and --verbose are incompatible");
+               string_list_append(&output, out.buf);
+       } else {
+               printf("%s\n", out.buf);
+       }
        strbuf_release(&name);
        strbuf_release(&out);
 }
@@ -655,7 +675,7 @@ static int edit_branch_description(const char *branch_name)
        fp = fopen(git_path(edit_description), "w");
        if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
                strbuf_release(&buf);
-               return error(_("could not write branch description template: %s\n"),
+               return error(_("could not write branch description template: %s"),
                             strerror(errno));
        }
        strbuf_reset(&buf);
@@ -678,6 +698,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        int delete = 0, rename = 0, force_create = 0, list = 0;
        int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0, edit_description = 0;
+       int quiet = 0;
        enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
        struct commit_list *with_commit = NULL;
@@ -686,6 +707,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_GROUP("Generic options"),
                OPT__VERBOSE(&verbose,
                        "show hash and subject, give twice for upstream branch"),
+               OPT__QUIET(&quiet, "suppress informational messages"),
                OPT_SET_INT('t', "track",  &track, "set up tracking mode (see git-pull(1))",
                        BRANCH_TRACK_EXPLICIT),
                OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
@@ -731,6 +753,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
                        opt_parse_merge_filter, (intptr_t) "HEAD",
                },
+               OPT_COLUMN(0, "column", &colopts, "list branches in columns"),
                OPT_END(),
        };
 
@@ -753,6 +776,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        }
        hashcpy(merge_filter_ref, head_sha1);
 
+
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
 
@@ -764,12 +788,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        if (abbrev == -1)
                abbrev = DEFAULT_ABBREV;
+       finalize_colopts(&colopts, -1);
+       if (verbose) {
+               if (explicitly_enable_column(colopts))
+                       die(_("--column and --verbose are incompatible"));
+               colopts = 0;
+       }
 
        if (delete)
-               return delete_branches(argc, argv, delete > 1, kinds);
-       else if (list)
-               return print_ref_list(kinds, detached, verbose, abbrev,
-                                     with_commit, argv);
+               return delete_branches(argc, argv, delete > 1, kinds, quiet);
+       else if (list) {
+               int ret = print_ref_list(kinds, detached, verbose, abbrev,
+                                        with_commit, argv);
+               print_columns(&output, colopts, NULL);
+               string_list_clear(&output, 0);
+               return ret;
+       }
        else if (edit_description) {
                const char *branch_name;
                struct strbuf branch_ref = STRBUF_INIT;
@@ -808,7 +842,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                if (kinds != REF_LOCAL_BRANCH)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
-                             force_create, reflog, 0, track);
+                             force_create, reflog, 0, quiet, track);
        } else
                usage_with_options(builtin_branch_usage, options);
 
index 8ed501f220424976cc30f4a4dbf3d59f979902be..36a9104433e23422aab39b1912e998a7f54cd3f4 100644 (file)
@@ -11,6 +11,7 @@
 #include "parse-options.h"
 #include "diff.h"
 #include "userdiff.h"
+#include "streaming.h"
 
 #define BATCH 1
 #define BATCH_CHECK 2
@@ -127,6 +128,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
                        return cmd_ls_tree(2, ls_args, NULL);
                }
 
+               if (type == OBJ_BLOB)
+                       return stream_blob_to_fd(1, sha1, NULL, 0);
                buf = read_sha1_file(sha1, &type, &size);
                if (!buf)
                        die("Cannot read object %s", obj_name);
@@ -149,6 +152,28 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
                break;
 
        case 0:
+               if (type_from_string(exp_type) == OBJ_BLOB) {
+                       unsigned char blob_sha1[20];
+                       if (sha1_object_info(sha1, NULL) == OBJ_TAG) {
+                               enum object_type type;
+                               unsigned long size;
+                               char *buffer = read_sha1_file(sha1, &type, &size);
+                               if (memcmp(buffer, "object ", 7) ||
+                                   get_sha1_hex(buffer + 7, blob_sha1))
+                                       die("%s not a valid tag", sha1_to_hex(sha1));
+                               free(buffer);
+                       } else
+                               hashcpy(blob_sha1, sha1);
+
+                       if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+                               return stream_blob_to_fd(1, blob_sha1, NULL, 0);
+                       /*
+                        * we attempted to dereference a tag to a blob
+                        * and failed; there may be new dereference
+                        * mechanisms this code is not aware of.
+                        * fall-back to the usual case.
+                        */
+               }
                buf = read_object_with_reference(sha1, exp_type, &size, NULL);
                break;
 
index 6b9061f26f5f33ae1ded811891e933441c210fb0..23fc56d88d478593727fe1cd6d9694226b0ad72a 100644 (file)
@@ -543,6 +543,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                                      opts->new_branch_force ? 1 : 0,
                                      opts->new_branch_log,
                                      opts->new_branch_force ? 1 : 0,
+                                     opts->quiet,
                                      opts->track);
                new->name = opts->new_branch;
                setup_branch_path(new);
diff --git a/builtin/column.c b/builtin/column.c
new file mode 100644 (file)
index 0000000..5ea798a
--- /dev/null
@@ -0,0 +1,59 @@
+#include "builtin.h"
+#include "cache.h"
+#include "strbuf.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "column.h"
+
+static const char * const builtin_column_usage[] = {
+       "git column [options]",
+       NULL
+};
+static unsigned int colopts;
+
+static int column_config(const char *var, const char *value, void *cb)
+{
+       return git_column_config(var, value, cb, &colopts);
+}
+
+int cmd_column(int argc, const char **argv, const char *prefix)
+{
+       struct string_list list = STRING_LIST_INIT_DUP;
+       struct strbuf sb = STRBUF_INIT;
+       struct column_options copts;
+       const char *command = NULL, *real_command = NULL;
+       struct option options[] = {
+               OPT_STRING(0, "command", &real_command, "name", "lookup config vars"),
+               OPT_COLUMN(0, "mode", &colopts, "layout to use"),
+               OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"),
+               OPT_INTEGER(0, "width", &copts.width, "Maximum width"),
+               OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"),
+               OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"),
+               OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"),
+               OPT_END()
+       };
+
+       /* This one is special and must be the first one */
+       if (argc > 1 && !prefixcmp(argv[1], "--command=")) {
+               command = argv[1] + 10;
+               git_config(column_config, (void *)command);
+       } else
+               git_config(column_config, NULL);
+
+       memset(&copts, 0, sizeof(copts));
+       copts.width = term_columns();
+       copts.padding = 1;
+       argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
+       if (argc)
+               usage_with_options(builtin_column_usage, options);
+       if (real_command || command) {
+               if (!real_command || !command || strcmp(real_command, command))
+                       die(_("--command must be the first argument"));
+       }
+       finalize_colopts(&colopts, -1);
+       while (!strbuf_getline(&sb, stdin, '\n'))
+               string_list_append(&list, sb.buf);
+
+       print_columns(&list, colopts, &copts);
+       return 0;
+}
index e2d9cbe3e387340e14103473e984f5b382df3134..b15e3119d672e753a5196f7ee7767f26c48af182 100644 (file)
@@ -27,6 +27,7 @@
 #include "quote.h"
 #include "submodule.h"
 #include "gpg-interface.h"
+#include "column.h"
 
 static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@ -88,6 +89,7 @@ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
 static char *sign_commit;
+static unsigned int colopts;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -139,24 +141,6 @@ static void determine_whence(struct wt_status *s)
                s->whence = whence;
 }
 
-static const char *whence_s(void)
-{
-       const char *s = "";
-
-       switch (whence) {
-       case FROM_COMMIT:
-               break;
-       case FROM_MERGE:
-               s = _("merge");
-               break;
-       case FROM_CHERRY_PICK:
-               s = _("cherry-pick");
-               break;
-       }
-
-       return s;
-}
-
 static void rollback_index_files(void)
 {
        switch (commit_style) {
@@ -398,8 +382,12 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
         */
        commit_style = COMMIT_PARTIAL;
 
-       if (whence != FROM_COMMIT)
-               die(_("cannot do a partial commit during a %s."), whence_s());
+       if (whence != FROM_COMMIT) {
+               if (whence == FROM_MERGE)
+                       die(_("cannot do a partial commit during a merge."));
+               else if (whence == FROM_CHERRY_PICK)
+                       die(_("cannot do a partial commit during a cherry-pick."));
+       }
 
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
@@ -741,28 +729,31 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                char *ai_tmp, *ci_tmp;
                if (whence != FROM_COMMIT)
                        status_printf_ln(s, GIT_COLOR_NORMAL,
-                               _("\n"
-                               "It looks like you may be committing a %s.\n"
-                               "If this is not correct, please remove the file\n"
-                               "       %s\n"
-                               "and try again.\n"
-                               ""),
-                               whence_s(),
+                           whence == FROM_MERGE
+                               ? _("\n"
+                                       "It looks like you may be committing a merge.\n"
+                                       "If this is not correct, please remove the file\n"
+                                       "       %s\n"
+                                       "and try again.\n")
+                               : _("\n"
+                                       "It looks like you may be committing a cherry-pick.\n"
+                                       "If this is not correct, please remove the file\n"
+                                       "       %s\n"
+                                       "and try again.\n"),
                                git_path(whence == FROM_MERGE
                                         ? "MERGE_HEAD"
                                         : "CHERRY_PICK_HEAD"));
 
                fprintf(s->fp, "\n");
-               status_printf(s, GIT_COLOR_NORMAL,
-                       _("Please enter the commit message for your changes."));
                if (cleanup_mode == CLEANUP_ALL)
-                       status_printf_more(s, GIT_COLOR_NORMAL,
-                               _(" Lines starting\n"
-                               "with '#' will be ignored, and an empty"
+                       status_printf(s, GIT_COLOR_NORMAL,
+                               _("Please enter the commit message for your changes."
+                               " Lines starting\nwith '#' will be ignored, and an empty"
                                " message aborts the commit.\n"));
                else /* CLEANUP_SPACE, that is. */
-                       status_printf_more(s, GIT_COLOR_NORMAL,
-                               _(" Lines starting\n"
+                       status_printf(s, GIT_COLOR_NORMAL,
+                               _("Please enter the commit message for your changes."
+                               " Lines starting\n"
                                "with '#' will be kept; you may remove them"
                                " yourself if you want to.\n"
                                "An empty message aborts the commit.\n"));
@@ -1017,8 +1008,12 @@ static int parse_and_validate_options(int argc, const char *argv[],
        /* Sanity check options */
        if (amend && !current_head)
                die(_("You have nothing to amend."));
-       if (amend && whence != FROM_COMMIT)
-               die(_("You are in the middle of a %s -- cannot amend."), whence_s());
+       if (amend && whence != FROM_COMMIT) {
+               if (whence == FROM_MERGE)
+                       die(_("You are in the middle of a merge -- cannot amend."));
+               else if (whence == FROM_CHERRY_PICK)
+                       die(_("You are in the middle of a cherry-pick -- cannot amend."));
+       }
        if (fixup_message && squash_message)
                die(_("Options --squash and --fixup cannot be used together"));
        if (use_message)
@@ -1125,6 +1120,8 @@ static int git_status_config(const char *k, const char *v, void *cb)
 {
        struct wt_status *s = cb;
 
+       if (!prefixcmp(k, "column."))
+               return git_column_config(k, v, "status", &colopts);
        if (!strcmp(k, "status.submodulesummary")) {
                int is_bool;
                s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
@@ -1190,6 +1187,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when",
                  "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)",
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+               OPT_COLUMN(0, "column", &colopts, "list untracked files in columns"),
                OPT_END(),
        };
 
@@ -1203,6 +1201,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
+       finalize_colopts(&colopts, -1);
+       s.colopts = colopts;
 
        if (s.null_termination && status_format == STATUS_FORMAT_LONG)
                status_format = STATUS_FORMAT_PORCELAIN;
index 1c8cb62445a3dc7a0ff1ff6d2663faec1bced167..bb9a0743ff565f3002eb0fede8e35b7f01a73b75 100644 (file)
@@ -546,8 +546,8 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
        int result = 0;
        struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
        const char *dangling_msg = dry_run
-               ? _("   (%s will become dangling)\n")
-               : _("   (%s has become dangling)\n");
+               ? _("   (%s will become dangling)")
+               : _("   (%s has become dangling)");
 
        for (ref = stale_refs; ref; ref = ref->next) {
                if (!dry_run)
index fc083e3c5cdddc1696c42846e3a624361fc26e5a..a517f1794a1c1bcc0939ad8b81d482356c20d2ba 100644 (file)
@@ -27,6 +27,8 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
                        merge_log_config = DEFAULT_MERGE_LOG_LEN;
        } else if (!strcmp(key, "merge.branchdesc")) {
                use_branch_desc = git_config_bool(key, value);
+       } else {
+               return git_default_config(key, value, cb);
        }
        return 0;
 }
@@ -223,6 +225,101 @@ static void add_branch_desc(struct strbuf *out, const char *name)
        strbuf_release(&desc);
 }
 
+#define util_as_integral(elem) ((intptr_t)((elem)->util))
+
+static void record_person(int which, struct string_list *people,
+                         struct commit *commit)
+{
+       char name_buf[MAX_GITNAME], *name, *name_end;
+       struct string_list_item *elem;
+       const char *field = (which == 'a') ? "\nauthor " : "\ncommitter ";
+
+       name = strstr(commit->buffer, field);
+       if (!name)
+               return;
+       name += strlen(field);
+       name_end = strchrnul(name, '<');
+       if (*name_end)
+               name_end--;
+       while (isspace(*name_end) && name <= name_end)
+               name_end--;
+       if (name_end < name || name + MAX_GITNAME <= name_end)
+               return;
+       memcpy(name_buf, name, name_end - name + 1);
+       name_buf[name_end - name + 1] = '\0';
+
+       elem = string_list_lookup(people, name_buf);
+       if (!elem) {
+               elem = string_list_insert(people, name_buf);
+               elem->util = (void *)0;
+       }
+       elem->util = (void*)(util_as_integral(elem) + 1);
+}
+
+static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
+{
+       const struct string_list_item *a = a_, *b = b_;
+       return util_as_integral(b) - util_as_integral(a);
+}
+
+static void add_people_count(struct strbuf *out, struct string_list *people)
+{
+       if (people->nr == 1)
+               strbuf_addf(out, "%s", people->items[0].string);
+       else if (people->nr == 2)
+               strbuf_addf(out, "%s (%d) and %s (%d)",
+                           people->items[0].string,
+                           (int)util_as_integral(&people->items[0]),
+                           people->items[1].string,
+                           (int)util_as_integral(&people->items[1]));
+       else if (people->nr)
+               strbuf_addf(out, "%s (%d) and others",
+                           people->items[0].string,
+                           (int)util_as_integral(&people->items[0]));
+}
+
+static void credit_people(struct strbuf *out,
+                         struct string_list *them,
+                         int kind)
+{
+       const char *label;
+       const char *me;
+
+       if (kind == 'a') {
+               label = "\nBy ";
+               me = git_author_info(IDENT_NO_DATE);
+       } else {
+               label = "\nvia ";
+               me = git_committer_info(IDENT_NO_DATE);
+       }
+
+       if (!them->nr ||
+           (them->nr == 1 &&
+            me &&
+            (me = skip_prefix(me, them->items->string)) != NULL &&
+            skip_prefix(me, " <")))
+               return;
+       strbuf_addstr(out, label);
+       add_people_count(out, them);
+}
+
+static void add_people_info(struct strbuf *out,
+                           struct string_list *authors,
+                           struct string_list *committers)
+{
+       if (authors->nr)
+               qsort(authors->items,
+                     authors->nr, sizeof(authors->items[0]),
+                     cmp_string_list_util_as_integral);
+       if (committers->nr)
+               qsort(committers->items,
+                     committers->nr, sizeof(committers->items[0]),
+                     cmp_string_list_util_as_integral);
+
+       credit_people(out, authors, 'a');
+       credit_people(out, committers, 'c');
+}
+
 static void shortlog(const char *name,
                     struct origin_data *origin_data,
                     struct commit *head,
@@ -233,6 +330,8 @@ static void shortlog(const char *name,
        struct commit *commit;
        struct object *branch;
        struct string_list subjects = STRING_LIST_INIT_DUP;
+       struct string_list authors = STRING_LIST_INIT_DUP;
+       struct string_list committers = STRING_LIST_INIT_DUP;
        int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
        struct strbuf sb = STRBUF_INIT;
        const unsigned char *sha1 = origin_data->sha1;
@@ -242,7 +341,6 @@ static void shortlog(const char *name,
                return;
 
        setup_revisions(0, NULL, rev, NULL);
-       rev->ignore_merges = 1;
        add_pending_object(rev, branch, name);
        add_pending_object(rev, &head->object, "^HEAD");
        head->object.flags |= UNINTERESTING;
@@ -251,10 +349,15 @@ static void shortlog(const char *name,
        while ((commit = get_revision(rev)) != NULL) {
                struct pretty_print_context ctx = {0};
 
-               /* ignore merges */
-               if (commit->parents && commit->parents->next)
+               if (commit->parents && commit->parents->next) {
+                       /* do not list a merge but count committer */
+                       record_person('c', &committers, commit);
                        continue;
-
+               }
+               if (!count)
+                       /* the 'tip' committer */
+                       record_person('c', &committers, commit);
+               record_person('a', &authors, commit);
                count++;
                if (subjects.nr > limit)
                        continue;
@@ -269,6 +372,7 @@ static void shortlog(const char *name,
                        string_list_append(&subjects, strbuf_detach(&sb, NULL));
        }
 
+       add_people_info(out, &authors, &committers);
        if (count > limit)
                strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
        else
@@ -289,6 +393,8 @@ static void shortlog(const char *name,
        rev->commits = NULL;
        rev->pending.nr = 0;
 
+       string_list_clear(&authors, 0);
+       string_list_clear(&committers, 0);
        string_list_clear(&subjects, 0);
 }
 
index 67eb553c7dc3d8ce62fbbefbe64a90c6431963c7..a710227a64a9862c0a70f3022f901fc65b0c7f90 100644 (file)
@@ -12,6 +12,7 @@
 #include "parse-options.h"
 #include "dir.h"
 #include "progress.h"
+#include "streaming.h"
 
 #define REACHABLE 0x0001
 #define SEEN      0x0002
@@ -238,13 +239,8 @@ static void check_unreachable_object(struct object *obj)
                        if (!(f = fopen(filename, "w")))
                                die_errno("Could not open '%s'", filename);
                        if (obj->type == OBJ_BLOB) {
-                               enum object_type type;
-                               unsigned long size;
-                               char *buf = read_sha1_file(obj->sha1,
-                                               &type, &size);
-                               if (buf && fwrite(buf, 1, size, f) != size)
+                               if (stream_blob_to_fd(fileno(f), obj->sha1, NULL, 1))
                                        die_errno("Could not write '%s'", filename);
-                               free(buf);
                        } else
                                fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
                        if (fclose(f))
index 271376d82b4391318fda9d5cfd5a0764d3768117..9b4232c8f30715327342c3d86c027d04c2a44c94 100644 (file)
@@ -14,6 +14,7 @@
 #include "cache.h"
 #include "parse-options.h"
 #include "run-command.h"
+#include "argv-array.h"
 
 #define FAILED_RUN "failed to run %s"
 
@@ -28,12 +29,11 @@ static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
 static const char *prune_expire = "2.weeks.ago";
 
-#define MAX_ADD 10
-static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
-static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
-static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", "--expire", NULL, NULL, NULL};
-static const char *argv_rerere[] = {"rerere", "gc", NULL};
+static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
+static struct argv_array reflog = ARGV_ARRAY_INIT;
+static struct argv_array repack = ARGV_ARRAY_INIT;
+static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array rerere = ARGV_ARRAY_INIT;
 
 static int gc_config(const char *var, const char *value, void *cb)
 {
@@ -67,19 +67,6 @@ static int gc_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
-static void append_option(const char **cmd, const char *opt, int max_length)
-{
-       int i;
-
-       for (i = 0; cmd[i]; i++)
-               ;
-
-       if (i + 2 >= max_length)
-               die(_("Too many options specified"));
-       cmd[i++] = opt;
-       cmd[i] = NULL;
-}
-
 static int too_many_loose_objects(void)
 {
        /*
@@ -144,6 +131,17 @@ static int too_many_packs(void)
        return gc_auto_pack_limit <= cnt;
 }
 
+static void add_repack_all_option(void)
+{
+       if (prune_expire && !strcmp(prune_expire, "now"))
+               argv_array_push(&repack, "-a");
+       else {
+               argv_array_push(&repack, "-A");
+               if (prune_expire)
+                       argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
+       }
+}
+
 static int need_to_gc(void)
 {
        /*
@@ -160,10 +158,7 @@ static int need_to_gc(void)
         * there is no need.
         */
        if (too_many_packs())
-               append_option(argv_repack,
-                             prune_expire && !strcmp(prune_expire, "now") ?
-                             "-a" : "-A",
-                             MAX_ADD);
+               add_repack_all_option();
        else if (!too_many_loose_objects())
                return 0;
 
@@ -177,7 +172,6 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        int aggressive = 0;
        int auto_gc = 0;
        int quiet = 0;
-       char buf[80];
 
        struct option builtin_gc_options[] = {
                OPT__QUIET(&quiet, "suppress progress reporting"),
@@ -192,6 +186,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_gc_usage, builtin_gc_options);
 
+       argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
+       argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
+       argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
+       argv_array_pushl(&prune, "prune", "--expire", NULL );
+       argv_array_pushl(&rerere, "rerere", "gc", NULL);
+
        git_config(gc_config, NULL);
 
        if (pack_refs < 0)
@@ -203,15 +203,13 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                usage_with_options(builtin_gc_usage, builtin_gc_options);
 
        if (aggressive) {
-               append_option(argv_repack, "-f", MAX_ADD);
-               append_option(argv_repack, "--depth=250", MAX_ADD);
-               if (aggressive_window > 0) {
-                       sprintf(buf, "--window=%d", aggressive_window);
-                       append_option(argv_repack, buf, MAX_ADD);
-               }
+               argv_array_push(&repack, "-f");
+               argv_array_push(&repack, "--depth=250");
+               if (aggressive_window > 0)
+                       argv_array_pushf(&repack, "--window=%d", aggressive_window);
        }
        if (quiet)
-               append_option(argv_repack, "-q", MAX_ADD);
+               argv_array_push(&repack, "-q");
 
        if (auto_gc) {
                /*
@@ -227,30 +225,27 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                                        "run \"git gc\" manually. See "
                                        "\"git help gc\" for more information.\n"));
        } else
-               append_option(argv_repack,
-                             prune_expire && !strcmp(prune_expire, "now")
-                             ? "-a" : "-A",
-                             MAX_ADD);
+               add_repack_all_option();
 
-       if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_pack_refs[0]);
+       if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
+               return error(FAILED_RUN, pack_refs_cmd.argv[0]);
 
-       if (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_reflog[0]);
+       if (run_command_v_opt(reflog.argv, RUN_GIT_CMD))
+               return error(FAILED_RUN, reflog.argv[0]);
 
-       if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_repack[0]);
+       if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
+               return error(FAILED_RUN, repack.argv[0]);
 
        if (prune_expire) {
-               argv_prune[2] = prune_expire;
+               argv_array_push(&prune, prune_expire);
                if (quiet)
-                       argv_prune[3] = "--no-progress";
-               if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
-                       return error(FAILED_RUN, argv_prune[0]);
+                       argv_array_push(&prune, "--no-progress");
+               if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
+                       return error(FAILED_RUN, prune.argv[0]);
        }
 
-       if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
-               return error(FAILED_RUN, argv_rerere[0]);
+       if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
+               return error(FAILED_RUN, rerere.argv[0]);
 
        if (auto_gc && too_many_loose_objects())
                warning(_("There are too many unreachable loose objects; "
index 61ff79839bc4778451425d0ae1cfd199247954a1..43d3c84449a57ec7028acb663d4ab4bea134c97e 100644 (file)
@@ -9,6 +9,7 @@
 #include "common-cmds.h"
 #include "parse-options.h"
 #include "run-command.h"
+#include "column.h"
 #include "help.h"
 
 static struct man_viewer_list {
@@ -30,6 +31,7 @@ enum help_format {
 };
 
 static int show_all = 0;
+static unsigned int colopts;
 static enum help_format help_format = HELP_FORMAT_NONE;
 static struct option builtin_help_options[] = {
        OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
@@ -54,7 +56,7 @@ static enum help_format parse_help_format(const char *format)
                return HELP_FORMAT_INFO;
        if (!strcmp(format, "web") || !strcmp(format, "html"))
                return HELP_FORMAT_WEB;
-       die("unrecognized help format '%s'", format);
+       die(_("unrecognized help format '%s'"), format);
 }
 
 static const char *get_man_viewer_info(const char *name)
@@ -82,7 +84,7 @@ static int check_emacsclient_version(void)
        ec_process.err = -1;
        ec_process.stdout_to_stderr = 1;
        if (start_command(&ec_process))
-               return error("Failed to start emacsclient.");
+               return error(_("Failed to start emacsclient."));
 
        strbuf_read(&buffer, ec_process.err, 20);
        close(ec_process.err);
@@ -95,7 +97,7 @@ static int check_emacsclient_version(void)
 
        if (prefixcmp(buffer.buf, "emacsclient")) {
                strbuf_release(&buffer);
-               return error("Failed to parse emacsclient version.");
+               return error(_("Failed to parse emacsclient version."));
        }
 
        strbuf_remove(&buffer, 0, strlen("emacsclient"));
@@ -103,7 +105,7 @@ static int check_emacsclient_version(void)
 
        if (version < 22) {
                strbuf_release(&buffer);
-               return error("emacsclient version '%d' too old (< 22).",
+               return error(_("emacsclient version '%d' too old (< 22)."),
                        version);
        }
 
@@ -121,7 +123,7 @@ static void exec_woman_emacs(const char *path, const char *page)
                        path = "emacsclient";
                strbuf_addf(&man_page, "(woman \"%s\")", page);
                execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
-               warning("failed to exec '%s': %s", path, strerror(errno));
+               warning(_("failed to exec '%s': %s"), path, strerror(errno));
        }
 }
 
@@ -149,7 +151,7 @@ static void exec_man_konqueror(const char *path, const char *page)
                        path = "kfmclient";
                strbuf_addf(&man_page, "man:%s(1)", page);
                execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
-               warning("failed to exec '%s': %s", path, strerror(errno));
+               warning(_("failed to exec '%s': %s"), path, strerror(errno));
        }
 }
 
@@ -158,7 +160,7 @@ static void exec_man_man(const char *path, const char *page)
        if (!path)
                path = "man";
        execlp(path, "man", page, (char *)NULL);
-       warning("failed to exec '%s': %s", path, strerror(errno));
+       warning(_("failed to exec '%s': %s"), path, strerror(errno));
 }
 
 static void exec_man_cmd(const char *cmd, const char *page)
@@ -166,7 +168,7 @@ static void exec_man_cmd(const char *cmd, const char *page)
        struct strbuf shell_cmd = STRBUF_INIT;
        strbuf_addf(&shell_cmd, "%s %s", cmd, page);
        execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
-       warning("failed to exec '%s': %s", cmd, strerror(errno));
+       warning(_("failed to exec '%s': %s"), cmd, strerror(errno));
 }
 
 static void add_man_viewer(const char *name)
@@ -206,8 +208,8 @@ static int add_man_viewer_path(const char *name,
        if (supported_man_viewer(name, len))
                do_add_man_viewer_info(name, len, value);
        else
-               warning("'%s': path for unsupported man viewer.\n"
-                       "Please consider using 'man.<tool>.cmd' instead.",
+               warning(_("'%s': path for unsupported man viewer.\n"
+                         "Please consider using 'man.<tool>.cmd' instead."),
                        name);
 
        return 0;
@@ -218,8 +220,8 @@ static int add_man_viewer_cmd(const char *name,
                              const char *value)
 {
        if (supported_man_viewer(name, len))
-               warning("'%s': cmd for supported man viewer.\n"
-                       "Please consider using 'man.<tool>.path' instead.",
+               warning(_("'%s': cmd for supported man viewer.\n"
+                         "Please consider using 'man.<tool>.path' instead."),
                        name);
        else
                do_add_man_viewer_info(name, len, value);
@@ -251,6 +253,8 @@ static int add_man_viewer_info(const char *var, const char *value)
 
 static int git_help_config(const char *var, const char *value, void *cb)
 {
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "help", &colopts);
        if (!strcmp(var, "help.format")) {
                if (!value)
                        return config_error_nonbool(var);
@@ -280,11 +284,11 @@ void list_common_cmds_help(void)
                        longest = strlen(common_cmds[i].name);
        }
 
-       puts("The most commonly used git commands are:");
+       puts(_("The most commonly used git commands are:"));
        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
                printf("   %s   ", common_cmds[i].name);
                mput_char(' ', longest - strlen(common_cmds[i].name));
-               puts(common_cmds[i].help);
+               puts(_(common_cmds[i].help));
        }
 }
 
@@ -348,7 +352,7 @@ static void exec_viewer(const char *name, const char *page)
        else if (info)
                exec_man_cmd(info, page);
        else
-               warning("'%s': unknown man viewer.", name);
+               warning(_("'%s': unknown man viewer."), name);
 }
 
 static void show_man_page(const char *git_cmd)
@@ -365,7 +369,7 @@ static void show_man_page(const char *git_cmd)
        if (fallback)
                exec_viewer(fallback, page);
        exec_viewer("man", page);
-       die("no man viewer handled the request");
+       die(_("no man viewer handled the request"));
 }
 
 static void show_info_page(const char *git_cmd)
@@ -373,7 +377,7 @@ static void show_info_page(const char *git_cmd)
        const char *page = cmd_to_page(git_cmd);
        setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
        execlp("info", "info", "gitman", page, (char *)NULL);
-       die("no info viewer handled the request");
+       die(_("no info viewer handled the request"));
 }
 
 static void get_html_page_path(struct strbuf *page_path, const char *page)
@@ -384,7 +388,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page)
        /* Check that we have a git documentation directory. */
        if (stat(mkpath("%s/git.html", html_path), &st)
            || !S_ISREG(st.st_mode))
-               die("'%s': not a documentation directory.", html_path);
+               die(_("'%s': not a documentation directory."), html_path);
 
        strbuf_init(page_path, 0);
        strbuf_addf(page_path, "%s/%s.html", html_path, page);
@@ -424,16 +428,17 @@ int cmd_help(int argc, const char **argv, const char *prefix)
        parsed_help_format = help_format;
 
        if (show_all) {
-               printf("usage: %s\n\n", git_usage_string);
-               list_commands("git commands", &main_cmds, &other_cmds);
-               printf("%s\n", git_more_info_string);
+               git_config(git_help_config, NULL);
+               printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
+               list_commands(colopts, &main_cmds, &other_cmds);
+               printf("%s\n", _(git_more_info_string));
                return 0;
        }
 
        if (!argv[0]) {
-               printf("usage: %s\n\n", git_usage_string);
+               printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
                list_common_cmds_help();
-               printf("\n%s\n", git_more_info_string);
+               printf("\n%s\n", _(git_more_info_string));
                return 0;
        }
 
@@ -445,7 +450,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
 
        alias = alias_lookup(argv[0]);
        if (alias && !is_git_command(argv[0])) {
-               printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+               printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
                return 0;
        }
 
index dd1c5c961db087d1a9f649463d41c0ccbd7a5199..83555e56353a8787a8582d69e50ec2147d9c4929 100644 (file)
@@ -81,7 +81,7 @@ static int mark_link(struct object *obj, int type, void *data)
                return -1;
 
        if (type != OBJ_ANY && obj->type != type)
-               die("object type mismatch at %s", sha1_to_hex(obj->sha1));
+               die(_("object type mismatch at %s"), sha1_to_hex(obj->sha1));
 
        obj->flags |= FLAG_LINK;
        return 0;
@@ -101,7 +101,7 @@ static void check_object(struct object *obj)
                unsigned long size;
                int type = sha1_object_info(obj->sha1, &size);
                if (type != obj->type || type <= 0)
-                       die("object of unexpected type");
+                       die(_("object of unexpected type"));
                obj->flags |= FLAG_CHECKED;
                return;
        }
@@ -138,15 +138,18 @@ static void *fill(int min)
        if (min <= input_len)
                return input_buffer + input_offset;
        if (min > sizeof(input_buffer))
-               die("cannot fill %d bytes", min);
+               die(Q_("cannot fill %d byte",
+                      "cannot fill %d bytes",
+                      min),
+                   min);
        flush();
        do {
                ssize_t ret = xread(input_fd, input_buffer + input_len,
                                sizeof(input_buffer) - input_len);
                if (ret <= 0) {
                        if (!ret)
-                               die("early EOF");
-                       die_errno("read error on input");
+                               die(_("early EOF"));
+                       die_errno(_("read error on input"));
                }
                input_len += ret;
                if (from_stdin)
@@ -158,14 +161,14 @@ static void *fill(int min)
 static void use(int bytes)
 {
        if (bytes > input_len)
-               die("used more bytes than were available");
+               die(_("used more bytes than were available"));
        input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
        input_len -= bytes;
        input_offset += bytes;
 
        /* make sure off_t is sufficiently large not to wrap */
        if (signed_add_overflows(consumed_bytes, bytes))
-               die("pack too large for current definition of off_t");
+               die(_("pack too large for current definition of off_t"));
        consumed_bytes += bytes;
 }
 
@@ -181,12 +184,12 @@ static const char *open_pack_file(const char *pack_name)
                } else
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
                if (output_fd < 0)
-                       die_errno("unable to create '%s'", pack_name);
+                       die_errno(_("unable to create '%s'"), pack_name);
                pack_fd = output_fd;
        } else {
                input_fd = open(pack_name, O_RDONLY);
                if (input_fd < 0)
-                       die_errno("cannot open packfile '%s'", pack_name);
+                       die_errno(_("cannot open packfile '%s'"), pack_name);
                output_fd = -1;
                pack_fd = input_fd;
        }
@@ -200,7 +203,7 @@ static void parse_pack_header(void)
 
        /* Header consistency check */
        if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
-               die("pack signature mismatch");
+               die(_("pack signature mismatch"));
        if (!pack_version_ok(hdr->hdr_version))
                die("pack version %"PRIu32" unsupported",
                        ntohl(hdr->hdr_version));
@@ -220,7 +223,7 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...)
        va_start(params, format);
        vsnprintf(buf, sizeof(buf), format, params);
        va_end(params);
-       die("pack has bad object at offset %lu: %s", offset, buf);
+       die(_("pack has bad object at offset %lu: %s"), offset, buf);
 }
 
 static struct base_data *alloc_base_data(void)
@@ -294,7 +297,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size)
                use(input_len - stream.avail_in);
        } while (status == Z_OK);
        if (stream.total_out != size || status != Z_STREAM_END)
-               bad_object(offset, "inflate returned %d", status);
+               bad_object(offset, _("inflate returned %d"), status);
        git_inflate_end(&stream);
        return buf;
 }
@@ -339,7 +342,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
                while (c & 128) {
                        base_offset += 1;
                        if (!base_offset || MSB(base_offset, 7))
-                               bad_object(obj->idx.offset, "offset value overflow for delta base object");
+                               bad_object(obj->idx.offset, _("offset value overflow for delta base object"));
                        p = fill(1);
                        c = *p;
                        use(1);
@@ -347,7 +350,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
                }
                delta_base->offset = obj->idx.offset - base_offset;
                if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
-                       bad_object(obj->idx.offset, "delta base offset is out of bound");
+                       bad_object(obj->idx.offset, _("delta base offset is out of bound"));
                break;
        case OBJ_COMMIT:
        case OBJ_TREE:
@@ -355,7 +358,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        case OBJ_TAG:
                break;
        default:
-               bad_object(obj->idx.offset, "unknown object type %d", obj->type);
+               bad_object(obj->idx.offset, _("unknown object type %d"), obj->type);
        }
        obj->hdr_size = consumed_bytes - obj->idx.offset;
 
@@ -384,9 +387,12 @@ static void *get_data_from_pack(struct object_entry *obj)
                ssize_t n = (len < 64*1024) ? len : 64*1024;
                n = pread(pack_fd, inbuf, n, from);
                if (n < 0)
-                       die_errno("cannot pread pack file");
+                       die_errno(_("cannot pread pack file"));
                if (!n)
-                       die("premature end of pack file, %lu bytes missing", len);
+                       die(Q_("premature end of pack file, %lu byte missing",
+                              "premature end of pack file, %lu bytes missing",
+                              len),
+                           len);
                from += n;
                len -= n;
                stream.next_in = inbuf;
@@ -396,7 +402,7 @@ static void *get_data_from_pack(struct object_entry *obj)
 
        /* This has been inflated OK when first encountered, so... */
        if (status != Z_STREAM_END || stream.total_out != obj->size)
-               die("serious inflate inconsistency");
+               die(_("serious inflate inconsistency"));
 
        git_inflate_end(&stream);
        free(inbuf);
@@ -467,10 +473,10 @@ static void sha1_object(const void *data, unsigned long size,
                unsigned long has_size;
                has_data = read_sha1_file(sha1, &has_type, &has_size);
                if (!has_data)
-                       die("cannot read existing object %s", sha1_to_hex(sha1));
+                       die(_("cannot read existing object %s"), sha1_to_hex(sha1));
                if (size != has_size || type != has_type ||
                    memcmp(data, has_data, size) != 0)
-                       die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+                       die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1));
                free(has_data);
        }
        if (strict) {
@@ -479,7 +485,7 @@ static void sha1_object(const void *data, unsigned long size,
                        if (blob)
                                blob->object.flags |= FLAG_CHECKED;
                        else
-                               die("invalid blob object %s", sha1_to_hex(sha1));
+                               die(_("invalid blob object %s"), sha1_to_hex(sha1));
                } else {
                        struct object *obj;
                        int eaten;
@@ -491,11 +497,11 @@ static void sha1_object(const void *data, unsigned long size,
                         */
                        obj = parse_object_buffer(sha1, type, size, buf, &eaten);
                        if (!obj)
-                               die("invalid %s", typename(type));
+                               die(_("invalid %s"), typename(type));
                        if (fsck_object(obj, 1, fsck_error_function))
-                               die("Error in object");
+                               die(_("Error in object"));
                        if (fsck_walk(obj, mark_link, NULL))
-                               die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1));
+                               die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
 
                        if (obj->type == OBJ_TREE) {
                                struct tree *item = (struct tree *) obj;
@@ -567,7 +573,7 @@ static void *get_base_data(struct base_data *c)
                                &c->size);
                        free(raw);
                        if (!c->data)
-                               bad_object(obj->idx.offset, "failed to apply delta");
+                               bad_object(obj->idx.offset, _("failed to apply delta"));
                        base_cache_used += c->size;
                        prune_base_data(c);
                }
@@ -593,7 +599,7 @@ static void resolve_delta(struct object_entry *delta_obj,
                                   delta_data, delta_obj->size, &result->size);
        free(delta_data);
        if (!result->data)
-               bad_object(delta_obj->idx.offset, "failed to apply delta");
+               bad_object(delta_obj->idx.offset, _("failed to apply delta"));
        sha1_object(result->data, result->size, delta_obj->real_type,
                    delta_obj->idx.sha1);
        nr_resolved_deltas++;
@@ -697,7 +703,7 @@ static void parse_pack_objects(unsigned char *sha1)
         */
        if (verbose)
                progress = start_progress(
-                               from_stdin ? "Receiving objects" : "Indexing objects",
+                               from_stdin ? _("Receiving objects") : _("Indexing objects"),
                                nr_objects);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
@@ -719,15 +725,15 @@ static void parse_pack_objects(unsigned char *sha1)
        flush();
        git_SHA1_Final(sha1, &input_ctx);
        if (hashcmp(fill(20), sha1))
-               die("pack is corrupted (SHA1 mismatch)");
+               die(_("pack is corrupted (SHA1 mismatch)"));
        use(20);
 
        /* If input_fd is a file, we should have reached its end now. */
        if (fstat(input_fd, &st))
-               die_errno("cannot fstat packfile");
+               die_errno(_("cannot fstat packfile"));
        if (S_ISREG(st.st_mode) &&
                        lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
-               die("pack has junk at the end");
+               die(_("pack has junk at the end"));
 
        if (!nr_deltas)
                return;
@@ -745,7 +751,7 @@ static void parse_pack_objects(unsigned char *sha1)
         *   for some more deltas.
         */
        if (verbose)
-               progress = start_progress("Resolving deltas", nr_deltas);
+               progress = start_progress(_("Resolving deltas"), nr_deltas);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
                struct base_data *base_obj = alloc_base_data();
@@ -778,7 +784,7 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size)
        } while (status == Z_OK);
 
        if (status != Z_STREAM_END)
-               die("unable to deflate appended object (%d)", status);
+               die(_("unable to deflate appended object (%d)"), status);
        size = stream.total_out;
        git_deflate_end(&stream);
        return size;
@@ -857,7 +863,7 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
 
                if (check_sha1_signature(d->base.sha1, base_obj->data,
                                base_obj->size, typename(type)))
-                       die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+                       die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1));
                base_obj->obj = append_obj_to_pack(f, d->base.sha1,
                                        base_obj->data, base_obj->size, type);
                find_unresolved_deltas(base_obj);
@@ -881,7 +887,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                fsync_or_die(output_fd, curr_pack_name);
                err = close(output_fd);
                if (err)
-                       die_errno("error while closing pack file");
+                       die_errno(_("error while closing pack file"));
        }
 
        if (keep_msg) {
@@ -894,7 +900,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 
                if (keep_fd < 0) {
                        if (errno != EEXIST)
-                               die_errno("cannot write keep file '%s'",
+                               die_errno(_("cannot write keep file '%s'"),
                                          keep_name);
                } else {
                        if (keep_msg_len > 0) {
@@ -902,7 +908,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                                write_or_die(keep_fd, "\n", 1);
                        }
                        if (close(keep_fd) != 0)
-                               die_errno("cannot close written keep file '%s'",
+                               die_errno(_("cannot close written keep file '%s'"),
                                    keep_name);
                        report = "keep";
                }
@@ -915,7 +921,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                        final_pack_name = name;
                }
                if (move_temp_to_file(curr_pack_name, final_pack_name))
-                       die("cannot store pack file");
+                       die(_("cannot store pack file"));
        } else if (from_stdin)
                chmod(final_pack_name, 0444);
 
@@ -926,7 +932,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                        final_index_name = name;
                }
                if (move_temp_to_file(curr_index_name, final_index_name))
-                       die("cannot store index file");
+                       die(_("cannot store index file"));
        } else
                chmod(final_index_name, 0444);
 
@@ -1015,9 +1021,9 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
        struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1);
 
        if (!p)
-               die("Cannot open existing pack file '%s'", pack_name);
+               die(_("Cannot open existing pack file '%s'"), pack_name);
        if (open_pack_index(p))
-               die("Cannot open existing pack idx file for '%s'", pack_name);
+               die(_("Cannot open existing pack idx file for '%s'"), pack_name);
 
        /* Read the attributes from the existing idx file */
        opts->version = p->index_version;
@@ -1064,15 +1070,18 @@ static void show_pack_info(int stat_only)
        }
 
        if (baseobjects)
-               printf("non delta: %d object%s\n",
-                      baseobjects, baseobjects > 1 ? "s" : "");
+               printf_ln(Q_("non delta: %d object",
+                            "non delta: %d objects",
+                            baseobjects),
+                         baseobjects);
        for (i = 0; i < deepest_delta; i++) {
                if (!chain_histogram[i])
                        continue;
-               printf("chain length = %d: %lu object%s\n",
-                      i + 1,
-                      chain_histogram[i],
-                      chain_histogram[i] > 1 ? "s" : "");
+               printf_ln(Q_("chain length = %d: %lu object",
+                            "chain length = %d: %lu objects",
+                            chain_histogram[i]),
+                         i + 1,
+                         chain_histogram[i]);
        }
 }
 
@@ -1095,7 +1104,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
        reset_pack_idx_option(&opts);
        git_config(git_index_pack_config, &opts);
        if (prefix && chdir(prefix))
-               die("Cannot come back to cwd");
+               die(_("Cannot come back to cwd"));
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -1128,10 +1137,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                hdr->hdr_signature = htonl(PACK_SIGNATURE);
                                hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
                                if (*c != ',')
-                                       die("bad %s", arg);
+                                       die(_("bad %s"), arg);
                                hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
                                if (*c)
-                                       die("bad %s", arg);
+                                       die(_("bad %s"), arg);
                                input_len = sizeof(*hdr);
                        } else if (!strcmp(arg, "-v")) {
                                verbose = 1;
@@ -1143,11 +1152,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                char *c;
                                opts.version = strtoul(arg + 16, &c, 10);
                                if (opts.version > 2)
-                                       die("bad %s", arg);
+                                       die(_("bad %s"), arg);
                                if (*c == ',')
                                        opts.off32_limit = strtoul(c+1, &c, 0);
                                if (*c || opts.off32_limit & 0x80000000)
-                                       die("bad %s", arg);
+                                       die(_("bad %s"), arg);
                        } else
                                usage(index_pack_usage);
                        continue;
@@ -1161,11 +1170,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
        if (!pack_name && !from_stdin)
                usage(index_pack_usage);
        if (fix_thin_pack && !from_stdin)
-               die("--fix-thin cannot be used without --stdin");
+               die(_("--fix-thin cannot be used without --stdin"));
        if (!index_name && pack_name) {
                int len = strlen(pack_name);
                if (!has_extension(pack_name, ".pack"))
-                       die("packfile name '%s' does not end with '.pack'",
+                       die(_("packfile name '%s' does not end with '.pack'"),
                            pack_name);
                index_name_buf = xmalloc(len);
                memcpy(index_name_buf, pack_name, len - 5);
@@ -1175,7 +1184,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
        if (keep_msg && !keep_name && pack_name) {
                int len = strlen(pack_name);
                if (!has_extension(pack_name, ".pack"))
-                       die("packfile name '%s' does not end with '.pack'",
+                       die(_("packfile name '%s' does not end with '.pack'"),
                            pack_name);
                keep_name_buf = xmalloc(len);
                memcpy(keep_name_buf, pack_name, len - 5);
@@ -1184,7 +1193,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
        }
        if (verify) {
                if (!index_name)
-                       die("--verify with no packfile name given");
+                       die(_("--verify with no packfile name given"));
                read_idx_option(&opts, index_name);
                opts.flags |= WRITE_IDX_VERIFY | WRITE_IDX_STRICT;
        }
@@ -1208,7 +1217,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                        int nr_unresolved = nr_deltas - nr_resolved_deltas;
                        int nr_objects_initial = nr_objects;
                        if (nr_unresolved <= 0)
-                               die("confusion beyond insanity");
+                               die(_("confusion beyond insanity"));
                        objects = xrealloc(objects,
                                           (nr_objects + nr_unresolved + 1)
                                           * sizeof(*objects));
@@ -1227,7 +1236,9 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                    "(disk corruption?)", curr_pack);
                }
                if (nr_deltas != nr_resolved_deltas)
-                       die("pack has %d unresolved deltas",
+                       die(Q_("pack has %d unresolved delta",
+                              "pack has %d unresolved deltas",
+                              nr_deltas - nr_resolved_deltas),
                            nr_deltas - nr_resolved_deltas);
        }
        free(deltas);
index 8a47012b0bd2fefe616c44b918d16a18463b5d2a..690caa7830b2a4549012db5e46794118bc36e989 100644 (file)
@@ -20,6 +20,7 @@
 #include "string-list.h"
 #include "parse-options.h"
 #include "branch.h"
+#include "streaming.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -383,8 +384,13 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
        strbuf_release(&out);
 }
 
-static int show_object(const unsigned char *sha1, int show_tag_object,
-       struct rev_info *rev)
+static int show_blob_object(const unsigned char *sha1, struct rev_info *rev)
+{
+       fflush(stdout);
+       return stream_blob_to_fd(1, sha1, NULL, 0);
+}
+
+static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
 {
        unsigned long size;
        enum object_type type;
@@ -394,16 +400,16 @@ static int show_object(const unsigned char *sha1, int show_tag_object,
        if (!buf)
                return error(_("Could not read object %s"), sha1_to_hex(sha1));
 
-       if (show_tag_object)
-               while (offset < size && buf[offset] != '\n') {
-                       int new_offset = offset + 1;
-                       while (new_offset < size && buf[new_offset++] != '\n')
-                               ; /* do nothing */
-                       if (!prefixcmp(buf + offset, "tagger "))
-                               show_tagger(buf + offset + 7,
-                                           new_offset - offset - 7, rev);
-                       offset = new_offset;
-               }
+       assert(type == OBJ_TAG);
+       while (offset < size && buf[offset] != '\n') {
+               int new_offset = offset + 1;
+               while (new_offset < size && buf[new_offset++] != '\n')
+                       ; /* do nothing */
+               if (!prefixcmp(buf + offset, "tagger "))
+                       show_tagger(buf + offset + 7,
+                                   new_offset - offset - 7, rev);
+               offset = new_offset;
+       }
 
        if (offset < size)
                fwrite(buf + offset, size - offset, 1, stdout);
@@ -463,7 +469,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
                const char *name = objects[i].name;
                switch (o->type) {
                case OBJ_BLOB:
-                       ret = show_object(o->sha1, 0, NULL);
+                       ret = show_blob_object(o->sha1, NULL);
                        break;
                case OBJ_TAG: {
                        struct tag *t = (struct tag *)o;
@@ -474,7 +480,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
                                        diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
                                        t->tag,
                                        diff_get_color_opt(&rev.diffopt, DIFF_RESET));
-                       ret = show_object(o->sha1, 1, &rev);
+                       ret = show_tag_object(o->sha1, &rev);
                        rev.shown_one = 1;
                        if (ret)
                                break;
index 237abd3c0b27b601aca140bfb67a302666ff9cf2..6f0efef43c5b93c87fa8f586319cd03c8018bd55 100644 (file)
@@ -63,7 +63,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
        if (quiet) {
                if (!freopen("/dev/null", "w", stderr))
                        return error("failed to redirect stderr to /dev/null: "
-                                    "%s\n", strerror(errno));
+                                    "%s", strerror(errno));
        }
 
        if (prefix)
@@ -76,7 +76,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
                if (read_mmfile(mmfs + i, fname))
                        return -1;
                if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
-                       return error("Cannot merge binary files: %s\n",
+                       return error("Cannot merge binary files: %s",
                                        argv[i]);
        }
 
index 7b07c092cc5550d6784531c3137f05f54cfec258..1861093e9db4b951e4cb4c790d4792fe7bc70da7 100644 (file)
@@ -63,6 +63,7 @@ static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
 static int non_empty;
 static int reuse_delta = 1, reuse_object = 1;
 static int keep_unreachable, unpack_unreachable, include_tag;
+static unsigned long unpack_unreachable_expiration;
 static int local;
 static int incremental;
 static int ignore_packed_keep;
@@ -2249,6 +2250,10 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
                if (!p->pack_local || p->pack_keep)
                        continue;
 
+               if (unpack_unreachable_expiration &&
+                   p->mtime < unpack_unreachable_expiration)
+                       continue;
+
                if (open_pack_index(p))
                        die("cannot open pack index");
 
@@ -2315,6 +2320,21 @@ static int option_parse_index_version(const struct option *opt,
        return 0;
 }
 
+static int option_parse_unpack_unreachable(const struct option *opt,
+                                          const char *arg, int unset)
+{
+       if (unset) {
+               unpack_unreachable = 0;
+               unpack_unreachable_expiration = 0;
+       }
+       else {
+               unpack_unreachable = 1;
+               if (arg)
+                       unpack_unreachable_expiration = approxidate(arg);
+       }
+       return 0;
+}
+
 static int option_parse_ulong(const struct option *opt,
                              const char *arg, int unset)
 {
@@ -2392,8 +2412,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                         "include tag objects that refer to objects to be packed"),
                OPT_BOOL(0, "keep-unreachable", &keep_unreachable,
                         "keep unreachable objects"),
-               OPT_BOOL(0, "unpack-unreachable", &unpack_unreachable,
-                        "unpack unreachable objects"),
+               { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, "time",
+                 "unpack unreachable objects newer than <time>",
+                 PARSE_OPT_OPTARG, option_parse_unpack_unreachable },
                OPT_BOOL(0, "thin", &thin,
                         "create thin packs"),
                OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep,
index b6c0fee4c6058a3aadef659a3e98bc9e83fb6398..fdfcc6c71607a7412666b7cc647db98e1b85c8c1 100644 (file)
@@ -24,6 +24,7 @@ static int progress = -1;
 static const char **refspec;
 static int refspec_nr;
 static int refspec_alloc;
+static int default_matching_used;
 
 static void add_refspec(const char *ref)
 {
@@ -75,7 +76,44 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p)
        return remote->url_nr;
 }
 
-static void setup_push_upstream(struct remote *remote)
+static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) {
+       /*
+        * There's no point in using shorten_unambiguous_ref here,
+        * as the ambiguity would be on the remote side, not what
+        * we have locally. Plus, this is supposed to be the simple
+        * mode. If the user is doing something crazy like setting
+        * upstream to a non-branch, we should probably be showing
+        * them the big ugly fully qualified ref.
+        */
+       const char *advice_maybe = "";
+       const char *short_upstream =
+               skip_prefix(branch->merge[0]->src, "refs/heads/");
+
+       if (!short_upstream)
+               short_upstream = branch->merge[0]->src;
+       /*
+        * Don't show advice for people who explicitely set
+        * push.default.
+        */
+       if (push_default == PUSH_DEFAULT_UNSPECIFIED)
+               advice_maybe = _("\n"
+                                "To choose either option permanently, "
+                                "see push.default in 'git help config'.");
+       die(_("The upstream branch of your current branch does not match\n"
+             "the name of your current branch.  To push to the upstream branch\n"
+             "on the remote, use\n"
+             "\n"
+             "    git push %s HEAD:%s\n"
+             "\n"
+             "To push to the branch of the same name on the remote, use\n"
+             "\n"
+             "    git push %s %s\n"
+             "%s"),
+           remote->name, short_upstream,
+           remote->name, branch->name, advice_maybe);
+}
+
+static void setup_push_upstream(struct remote *remote, int simple)
 {
        struct strbuf refspec = STRBUF_INIT;
        struct branch *branch = branch_get(NULL);
@@ -102,6 +140,8 @@ static void setup_push_upstream(struct remote *remote)
                      "your current branch '%s', without telling me what to push\n"
                      "to update which remote branch."),
                    remote->name, branch->name);
+       if (simple && strcmp(branch->refname, branch->merge[0]->src))
+               die_push_simple(branch, remote);
 
        strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
        add_refspec(refspec.buf);
@@ -111,12 +151,19 @@ static void setup_default_push_refspecs(struct remote *remote)
 {
        switch (push_default) {
        default:
+       case PUSH_DEFAULT_UNSPECIFIED:
+               default_matching_used = 1;
+               /* fallthru */
        case PUSH_DEFAULT_MATCHING:
                add_refspec(":");
                break;
 
+       case PUSH_DEFAULT_SIMPLE:
+               setup_push_upstream(remote, 1);
+               break;
+
        case PUSH_DEFAULT_UPSTREAM:
-               setup_push_upstream(remote);
+               setup_push_upstream(remote, 0);
                break;
 
        case PUSH_DEFAULT_CURRENT:
@@ -130,6 +177,45 @@ static void setup_default_push_refspecs(struct remote *remote)
        }
 }
 
+static const char message_advice_pull_before_push[] =
+       N_("Updates were rejected because the tip of your current branch is behind\n"
+          "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+          "before pushing again.\n"
+          "See the 'Note about fast-forwards' in 'git push --help' for details.");
+
+static const char message_advice_use_upstream[] =
+       N_("Updates were rejected because a pushed branch tip is behind its remote\n"
+          "counterpart. If you did not intend to push that branch, you may want to\n"
+          "specify branches to push or set the 'push.default' configuration\n"
+          "variable to 'current' or 'upstream' to push only the current branch.");
+
+static const char message_advice_checkout_pull_push[] =
+       N_("Updates were rejected because a pushed branch tip is behind its remote\n"
+          "counterpart. Check out this branch and merge the remote changes\n"
+          "(e.g. 'git pull') before pushing again.\n"
+          "See the 'Note about fast-forwards' in 'git push --help' for details.");
+
+static void advise_pull_before_push(void)
+{
+       if (!advice_push_non_ff_current || !advice_push_nonfastforward)
+               return;
+       advise(_(message_advice_pull_before_push));
+}
+
+static void advise_use_upstream(void)
+{
+       if (!advice_push_non_ff_default || !advice_push_nonfastforward)
+               return;
+       advise(_(message_advice_use_upstream));
+}
+
+static void advise_checkout_pull_push(void)
+{
+       if (!advice_push_non_ff_matching || !advice_push_nonfastforward)
+               return;
+       advise(_(message_advice_checkout_pull_push));
+}
+
 static int push_with_options(struct transport *transport, int flags)
 {
        int err;
@@ -151,14 +237,21 @@ static int push_with_options(struct transport *transport, int flags)
                error(_("failed to push some refs to '%s'"), transport->url);
 
        err |= transport_disconnect(transport);
-
        if (!err)
                return 0;
 
-       if (nonfastforward && advice_push_nonfastforward) {
-               fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n"
-                               "Merge the remote changes (e.g. 'git pull') before pushing again.  See the\n"
-                               "'Note about fast-forwards' section of 'git push --help' for details.\n"));
+       switch (nonfastforward) {
+       default:
+               break;
+       case NON_FF_HEAD:
+               advise_pull_before_push();
+               break;
+       case NON_FF_OTHER:
+               if (default_matching_used)
+                       advise_use_upstream();
+               else
+                       advise_checkout_pull_push();
+               break;
        }
 
        return 1;
@@ -234,13 +327,21 @@ static int option_parse_recurse_submodules(const struct option *opt,
                                   const char *arg, int unset)
 {
        int *flags = opt->value;
+
+       if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |
+                     TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))
+               die("%s can only be used once.", opt->long_name);
+
        if (arg) {
                if (!strcmp(arg, "check"))
                        *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
+               else if (!strcmp(arg, "on-demand"))
+                       *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
                else
                        die("bad %s argument: %s", opt->long_name, arg);
        } else
-               die("option %s needs an argument (check)", opt->long_name);
+               die("option %s needs an argument (check|on-demand)",
+                               opt->long_name);
 
        return 0;
 }
index fec92bc66e41b82f929e37b1935d00d6558c34a0..0f0c594b2fc2194d841cabceaf180ee664376af1 100644 (file)
@@ -9,7 +9,7 @@
 
 static const char * const builtin_remote_usage[] = {
        "git remote [-v | --verbose]",
-       "git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>",
+       "git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>",
        "git remote rename <old> <new>",
        "git remote rm <name>",
        "git remote set-head <name> (-a | -d | <branch>)",
@@ -17,7 +17,7 @@ static const char * const builtin_remote_usage[] = {
        "git remote prune [-n | --dry-run] <name>",
        "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]",
        "git remote set-branches [--add] <name> <branch>...",
-       "git remote set-url <name> <newurl> [<oldurl>]",
+       "git remote set-url [--push] <name> <newurl> [<oldurl>]",
        "git remote set-url --add <name> <newurl>",
        "git remote set-url --delete <name> <url>",
        NULL
@@ -95,9 +95,9 @@ static int fetch_remote(const char *name)
                argv[1] = "-v";
                argv[2] = name;
        }
-       printf("Updating %s\n", name);
+       printf_ln(_("Updating %s"), name);
        if (run_command_v_opt(argv, RUN_GIT_CMD))
-               return error("Could not fetch %s", name);
+               return error(_("Could not fetch %s"), name);
        return 0;
 }
 
@@ -127,8 +127,8 @@ static int add_branch(const char *key, const char *branchname,
 }
 
 static const char mirror_advice[] =
-"--mirror is dangerous and deprecated; please\n"
-"\t use --mirror=fetch or --mirror=push instead";
+N_("--mirror is dangerous and deprecated; please\n"
+   "\t use --mirror=fetch or --mirror=push instead");
 
 static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
 {
@@ -136,7 +136,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
        if (not)
                *mirror = MIRROR_NONE;
        else if (!arg) {
-               warning("%s", mirror_advice);
+               warning("%s", _(mirror_advice));
                *mirror = MIRROR_BOTH;
        }
        else if (!strcmp(arg, "fetch"))
@@ -144,7 +144,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
        else if (!strcmp(arg, "push"))
                *mirror = MIRROR_PUSH;
        else
-               return error("unknown mirror argument: %s", arg);
+               return error(_("unknown mirror argument: %s"), arg);
        return 0;
 }
 
@@ -182,9 +182,9 @@ static int add(int argc, const char **argv)
                usage_with_options(builtin_remote_add_usage, options);
 
        if (mirror && master)
-               die("specifying a master branch makes no sense with --mirror");
+               die(_("specifying a master branch makes no sense with --mirror"));
        if (mirror && !(mirror & MIRROR_FETCH) && track.nr)
-               die("specifying branches to track makes sense only with fetch mirrors");
+               die(_("specifying branches to track makes sense only with fetch mirrors"));
 
        name = argv[0];
        url = argv[1];
@@ -192,11 +192,11 @@ static int add(int argc, const char **argv)
        remote = remote_get(name);
        if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
                        remote->fetch_refspec_nr))
-               die("remote %s already exists.", name);
+               die(_("remote %s already exists."), name);
 
        strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
        if (!valid_fetch_refspec(buf2.buf))
-               die("'%s' is not a valid remote name", name);
+               die(_("'%s' is not a valid remote name"), name);
 
        strbuf_addf(&buf, "remote.%s.url", name);
        if (git_config_set(buf.buf, url))
@@ -240,7 +240,7 @@ static int add(int argc, const char **argv)
                strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
 
                if (create_symref(buf.buf, buf2.buf, "remote add"))
-                       return error("Could not setup master '%s'", master);
+                       return error(_("Could not setup master '%s'"), master);
        }
 
        strbuf_release(&buf);
@@ -296,7 +296,7 @@ static int config_read_branches(const char *key, const char *value, void *cb)
                info = item->util;
                if (type == REMOTE) {
                        if (info->remote_name)
-                               warning("more than one %s", orig_key);
+                               warning(_("more than one %s"), orig_key);
                        info->remote_name = xstrdup(value);
                } else if (type == MERGE) {
                        char *space = strchr(value, ' ');
@@ -336,7 +336,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
 
        for (i = 0; i < states->remote->fetch_refspec_nr; i++)
                if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
-                       die("Could not get fetch map for refspec %s",
+                       die(_("Could not get fetch map for refspec %s"),
                                states->remote->fetch_refspec[i]);
 
        states->new.strdup_strings = 1;
@@ -437,7 +437,7 @@ static int get_push_ref_states_noquery(struct ref_states *states)
 
        states->push.strdup_strings = 1;
        if (!remote->push_refspec_nr) {
-               item = string_list_append(&states->push, "(matching)");
+               item = string_list_append(&states->push, _("(matching)"));
                info = item->util = xcalloc(sizeof(struct push_info), 1);
                info->status = PUSH_STATUS_NOTQUERIED;
                info->dest = xstrdup(item->string);
@@ -445,11 +445,11 @@ static int get_push_ref_states_noquery(struct ref_states *states)
        for (i = 0; i < remote->push_refspec_nr; i++) {
                struct refspec *spec = remote->push + i;
                if (spec->matching)
-                       item = string_list_append(&states->push, "(matching)");
+                       item = string_list_append(&states->push, _("(matching)"));
                else if (strlen(spec->src))
                        item = string_list_append(&states->push, spec->src);
                else
-                       item = string_list_append(&states->push, "(delete)");
+                       item = string_list_append(&states->push, _("(delete)"));
 
                info = item->util = xcalloc(sizeof(struct push_info), 1);
                info->forced = spec->force;
@@ -592,19 +592,19 @@ static int migrate_file(struct remote *remote)
        strbuf_addf(&buf, "remote.%s.url", remote->name);
        for (i = 0; i < remote->url_nr; i++)
                if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0))
-                       return error("Could not append '%s' to '%s'",
+                       return error(_("Could not append '%s' to '%s'"),
                                        remote->url[i], buf.buf);
        strbuf_reset(&buf);
        strbuf_addf(&buf, "remote.%s.push", remote->name);
        for (i = 0; i < remote->push_refspec_nr; i++)
                if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0))
-                       return error("Could not append '%s' to '%s'",
+                       return error(_("Could not append '%s' to '%s'"),
                                        remote->push_refspec[i], buf.buf);
        strbuf_reset(&buf);
        strbuf_addf(&buf, "remote.%s.fetch", remote->name);
        for (i = 0; i < remote->fetch_refspec_nr; i++)
                if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0))
-                       return error("Could not append '%s' to '%s'",
+                       return error(_("Could not append '%s' to '%s'"),
                                        remote->fetch_refspec[i], buf.buf);
        if (remote->origin == REMOTE_REMOTES)
                path = git_path("remotes/%s", remote->name);
@@ -636,30 +636,30 @@ static int mv(int argc, const char **argv)
 
        oldremote = remote_get(rename.old);
        if (!oldremote)
-               die("No such remote: %s", rename.old);
+               die(_("No such remote: %s"), rename.old);
 
        if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
                return migrate_file(oldremote);
 
        newremote = remote_get(rename.new);
        if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
-               die("remote %s already exists.", rename.new);
+               die(_("remote %s already exists."), rename.new);
 
        strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
        if (!valid_fetch_refspec(buf.buf))
-               die("'%s' is not a valid remote name", rename.new);
+               die(_("'%s' is not a valid remote name"), rename.new);
 
        strbuf_reset(&buf);
        strbuf_addf(&buf, "remote.%s", rename.old);
        strbuf_addf(&buf2, "remote.%s", rename.new);
        if (git_config_rename_section(buf.buf, buf2.buf) < 1)
-               return error("Could not rename config section '%s' to '%s'",
+               return error(_("Could not rename config section '%s' to '%s'"),
                                buf.buf, buf2.buf);
 
        strbuf_reset(&buf);
        strbuf_addf(&buf, "remote.%s.fetch", rename.new);
        if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
-               return error("Could not remove config section '%s'", buf.buf);
+               return error(_("Could not remove config section '%s'"), buf.buf);
        strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
        for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
                char *ptr;
@@ -674,13 +674,13 @@ static int mv(int argc, const char **argv)
                                      strlen(rename.old), rename.new,
                                      strlen(rename.new));
                } else
-                       warning("Not updating non-default fetch respec\n"
-                               "\t%s\n"
-                               "\tPlease update the configuration manually if necessary.",
+                       warning(_("Not updating non-default fetch respec\n"
+                                 "\t%s\n"
+                                 "\tPlease update the configuration manually if necessary."),
                                buf2.buf);
 
                if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
-                       return error("Could not append '%s'", buf.buf);
+                       return error(_("Could not append '%s'"), buf.buf);
        }
 
        read_branches();
@@ -691,7 +691,7 @@ static int mv(int argc, const char **argv)
                        strbuf_reset(&buf);
                        strbuf_addf(&buf, "branch.%s.remote", item->string);
                        if (git_config_set(buf.buf, rename.new)) {
-                               return error("Could not set '%s'", buf.buf);
+                               return error(_("Could not set '%s'"), buf.buf);
                        }
                }
        }
@@ -713,7 +713,7 @@ static int mv(int argc, const char **argv)
                if (!(flag & REF_ISSYMREF))
                        continue;
                if (delete_ref(item->string, NULL, REF_NODEREF))
-                       die("deleting '%s' failed", item->string);
+                       die(_("deleting '%s' failed"), item->string);
        }
        for (i = 0; i < remote_branches.nr; i++) {
                struct string_list_item *item = remote_branches.items + i;
@@ -728,7 +728,7 @@ static int mv(int argc, const char **argv)
                strbuf_addf(&buf2, "remote: renamed %s to %s",
                                item->string, buf.buf);
                if (rename_ref(item->string, buf.buf, buf2.buf))
-                       die("renaming '%s' failed", item->string);
+                       die(_("renaming '%s' failed"), item->string);
        }
        for (i = 0; i < remote_branches.nr; i++) {
                struct string_list_item *item = remote_branches.items + i;
@@ -747,7 +747,7 @@ static int mv(int argc, const char **argv)
                strbuf_addf(&buf3, "remote: renamed %s to %s",
                                item->string, buf.buf);
                if (create_symref(buf.buf, buf2.buf, buf3.buf))
-                       die("creating '%s' failed", buf.buf);
+                       die(_("creating '%s' failed"), buf.buf);
        }
        return 0;
 }
@@ -761,7 +761,7 @@ static int remove_branches(struct string_list *branches)
                unsigned char *sha1 = item->util;
 
                if (delete_ref(refname, sha1, 0))
-                       result |= error("Could not remove branch %s", refname);
+                       result |= error(_("Could not remove branch %s"), refname);
        }
        return result;
 }
@@ -789,14 +789,14 @@ static int rm(int argc, const char **argv)
 
        remote = remote_get(argv[1]);
        if (!remote)
-               die("No such remote: %s", argv[1]);
+               die(_("No such remote: %s"), argv[1]);
 
        known_remotes.to_delete = remote;
        for_each_remote(add_known_remote, &known_remotes);
 
        strbuf_addf(&buf, "remote.%s", remote->name);
        if (git_config_rename_section(buf.buf, NULL) < 1)
-               return error("Could not remove config section '%s'", buf.buf);
+               return error(_("Could not remove config section '%s'"), buf.buf);
 
        read_branches();
        for (i = 0; i < branch_list.nr; i++) {
@@ -830,11 +830,12 @@ static int rm(int argc, const char **argv)
        string_list_clear(&branches, 1);
 
        if (skipped.nr) {
-               fprintf(stderr, skipped.nr == 1 ?
-                       "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
-                       "to delete it, use:\n" :
-                       "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
-                       "to delete them, use:\n");
+               fprintf_ln(stderr,
+                          Q_("Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+                             "to delete it, use:",
+                             "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+                             "to delete them, use:",
+                             skipped.nr));
                for (i = 0; i < skipped.nr; i++)
                        fprintf(stderr, "  git branch -d %s\n",
                                skipped.items[i].string);
@@ -886,7 +887,7 @@ static int get_remote_ref_states(const char *name,
 
        states->remote = remote_get(name);
        if (!states->remote)
-               return error("No such remote: %s", name);
+               return error(_("No such remote: %s"), name);
 
        read_branches();
 
@@ -939,14 +940,14 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data)
                const char *fmt = "%s";
                const char *arg = "";
                if (string_list_has_string(&states->new, name)) {
-                       fmt = " new (next fetch will store in remotes/%s)";
+                       fmt = _(" new (next fetch will store in remotes/%s)");
                        arg = states->remote->name;
                } else if (string_list_has_string(&states->tracked, name))
-                       arg = " tracked";
+                       arg = _(" tracked");
                else if (string_list_has_string(&states->stale, name))
-                       arg = " stale (use 'git remote prune' to remove)";
+                       arg = _(" stale (use 'git remote prune' to remove)");
                else
-                       arg = " ???";
+                       arg = _(" ???");
                printf("    %-*s", info->width, name);
                printf(fmt, arg);
                printf("\n");
@@ -987,21 +988,21 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
        int i;
 
        if (branch_info->rebase && branch_info->merge.nr > 1) {
-               error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+               error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"),
                        item->string);
                return 0;
        }
 
        printf("    %-*s ", show_info->width, item->string);
        if (branch_info->rebase) {
-               printf("rebases onto remote %s\n", merge->items[0].string);
+               printf_ln(_("rebases onto remote %s"), merge->items[0].string);
                return 0;
        } else if (show_info->any_rebase) {
-               printf(" merges with remote %s\n", merge->items[0].string);
-               also = "    and with remote";
+               printf_ln(_(" merges with remote %s"), merge->items[0].string);
+               also = _("    and with remote");
        } else {
-               printf("merges with remote %s\n", merge->items[0].string);
-               also = "   and with remote";
+               printf_ln(_("merges with remote %s"), merge->items[0].string);
+               also = _("   and with remote");
        }
        for (i = 1; i < merge->nr; i++)
                printf("    %-*s %s %s\n", show_info->width, "", also,
@@ -1043,36 +1044,43 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data)
 {
        struct show_info *show_info = cb_data;
        struct push_info *push_info = item->util;
-       char *src = item->string, *status = NULL;
+       const char *src = item->string, *status = NULL;
 
        switch (push_info->status) {
        case PUSH_STATUS_CREATE:
-               status = "create";
+               status = _("create");
                break;
        case PUSH_STATUS_DELETE:
-               status = "delete";
-               src = "(none)";
+               status = _("delete");
+               src = _("(none)");
                break;
        case PUSH_STATUS_UPTODATE:
-               status = "up to date";
+               status = _("up to date");
                break;
        case PUSH_STATUS_FASTFORWARD:
-               status = "fast-forwardable";
+               status = _("fast-forwardable");
                break;
        case PUSH_STATUS_OUTOFDATE:
-               status = "local out of date";
+               status = _("local out of date");
                break;
        case PUSH_STATUS_NOTQUERIED:
                break;
        }
-       if (status)
-               printf("    %-*s %s to %-*s (%s)\n", show_info->width, src,
-                       push_info->forced ? "forces" : "pushes",
-                       show_info->width2, push_info->dest, status);
-       else
-               printf("    %-*s %s to %s\n", show_info->width, src,
-                       push_info->forced ? "forces" : "pushes",
-                       push_info->dest);
+       if (status) {
+               if (push_info->forced)
+                       printf_ln(_("    %-*s forces to %-*s (%s)"), show_info->width, src,
+                              show_info->width2, push_info->dest, status);
+               else
+                       printf_ln(_("    %-*s pushes to %-*s (%s)"), show_info->width, src,
+                              show_info->width2, push_info->dest, status);
+       } else {
+               if (push_info->forced)
+                       printf_ln(_("    %-*s forces to %s"), show_info->width, src,
+                              push_info->dest);
+               else
+                       printf_ln(_("    %-*s pushes to %s"), show_info->width, src,
+                              push_info->dest);
+       }
        return 0;
 }
 
@@ -1107,9 +1115,9 @@ static int show(int argc, const char **argv)
 
                get_remote_ref_states(*argv, &states, query_flag);
 
-               printf("* remote %s\n", *argv);
-               printf("  Fetch URL: %s\n", states.remote->url_nr > 0 ?
-                       states.remote->url[0] : "(no URL)");
+               printf_ln(_("* remote %s"), *argv);
+               printf_ln(_("  Fetch URL: %s"), states.remote->url_nr > 0 ?
+                      states.remote->url[0] : _("(no URL)"));
                if (states.remote->pushurl_nr) {
                        url = states.remote->pushurl;
                        url_nr = states.remote->pushurl_nr;
@@ -1118,18 +1126,18 @@ static int show(int argc, const char **argv)
                        url_nr = states.remote->url_nr;
                }
                for (i = 0; i < url_nr; i++)
-                       printf("  Push  URL: %s\n", url[i]);
+                       printf_ln(_("  Push  URL: %s"), url[i]);
                if (!i)
-                       printf("  Push  URL: %s\n", "(no URL)");
+                       printf_ln(_("  Push  URL: %s"), "(no URL)");
                if (no_query)
-                       printf("  HEAD branch: (not queried)\n");
+                       printf_ln(_("  HEAD branch: %s"), "(not queried)");
                else if (!states.heads.nr)
-                       printf("  HEAD branch: (unknown)\n");
+                       printf_ln(_("  HEAD branch: %s"), "(unknown)");
                else if (states.heads.nr == 1)
-                       printf("  HEAD branch: %s\n", states.heads.items[0].string);
+                       printf_ln(_("  HEAD branch: %s"), states.heads.items[0].string);
                else {
-                       printf("  HEAD branch (remote HEAD is ambiguous,"
-                              " may be one of the following):\n");
+                       printf(_("  HEAD branch (remote HEAD is ambiguous,"
+                                " may be one of the following):\n"));
                        for (i = 0; i < states.heads.nr; i++)
                                printf("    %s\n", states.heads.items[i].string);
                }
@@ -1140,9 +1148,10 @@ static int show(int argc, const char **argv)
                for_each_string_list(&states.tracked, add_remote_to_show_info, &info);
                for_each_string_list(&states.stale, add_remote_to_show_info, &info);
                if (info.list->nr)
-                       printf("  Remote branch%s:%s\n",
-                              info.list->nr > 1 ? "es" : "",
-                               no_query ? " (status not queried)" : "");
+                       printf_ln(Q_("  Remote branch:%s",
+                                    "  Remote branches:%s",
+                                    info.list->nr),
+                                 no_query ? _(" (status not queried)") : "");
                for_each_string_list(info.list, show_remote_info_item, &info);
                string_list_clear(info.list, 0);
 
@@ -1151,23 +1160,25 @@ static int show(int argc, const char **argv)
                info.any_rebase = 0;
                for_each_string_list(&branch_list, add_local_to_show_info, &info);
                if (info.list->nr)
-                       printf("  Local branch%s configured for 'git pull':\n",
-                              info.list->nr > 1 ? "es" : "");
+                       printf_ln(Q_("  Local branch configured for 'git pull':",
+                                    "  Local branches configured for 'git pull':",
+                                    info.list->nr));
                for_each_string_list(info.list, show_local_info_item, &info);
                string_list_clear(info.list, 0);
 
                /* git push info */
                if (states.remote->mirror)
-                       printf("  Local refs will be mirrored by 'git push'\n");
+                       printf_ln(_("  Local refs will be mirrored by 'git push'"));
 
                info.width = info.width2 = 0;
                for_each_string_list(&states.push, add_push_to_show_info, &info);
                qsort(info.list->items, info.list->nr,
                        sizeof(*info.list->items), cmp_string_with_push);
                if (info.list->nr)
-                       printf("  Local ref%s configured for 'git push'%s:\n",
-                               info.list->nr > 1 ? "s" : "",
-                               no_query ? " (status not queried)" : "");
+                       printf_ln(Q_("  Local ref configured for 'git push'%s:",
+                                    "  Local refs configured for 'git push'%s:",
+                                    info.list->nr),
+                                 no_query ? _(" (status not queried)") : "");
                for_each_string_list(info.list, show_push_info_item, &info);
                string_list_clear(info.list, 0);
 
@@ -1202,10 +1213,10 @@ static int set_head(int argc, const char **argv)
                memset(&states, 0, sizeof(states));
                get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
                if (!states.heads.nr)
-                       result |= error("Cannot determine remote HEAD");
+                       result |= error(_("Cannot determine remote HEAD"));
                else if (states.heads.nr > 1) {
-                       result |= error("Multiple remote HEAD branches. "
-                                       "Please choose one explicitly with:");
+                       result |= error(_("Multiple remote HEAD branches. "
+                                         "Please choose one explicitly with:"));
                        for (i = 0; i < states.heads.nr; i++)
                                fprintf(stderr, "  git remote set-head %s %s\n",
                                        argv[0], states.heads.items[i].string);
@@ -1214,7 +1225,7 @@ static int set_head(int argc, const char **argv)
                free_remote_ref_states(&states);
        } else if (opt_d && !opt_a && argc == 1) {
                if (delete_ref(buf.buf, NULL, REF_NODEREF))
-                       result |= error("Could not delete %s", buf.buf);
+                       result |= error(_("Could not delete %s"), buf.buf);
        } else
                usage_with_options(builtin_remote_sethead_usage, options);
 
@@ -1222,9 +1233,9 @@ static int set_head(int argc, const char **argv)
                strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
                /* make sure it's valid */
                if (!ref_exists(buf2.buf))
-                       result |= error("Not a valid ref: %s", buf2.buf);
+                       result |= error(_("Not a valid ref: %s"), buf2.buf);
                else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
-                       result |= error("Could not setup %s", buf.buf);
+                       result |= error(_("Could not setup %s"), buf.buf);
                if (opt_a)
                        printf("%s/HEAD set to %s\n", argv[0], head_name);
                free(head_name);
@@ -1260,18 +1271,18 @@ static int prune_remote(const char *remote, int dry_run)
        int result = 0, i;
        struct ref_states states;
        const char *dangling_msg = dry_run
-               ? " %s will become dangling!\n"
-               : " %s has become dangling!\n";
+               ? _(" %s will become dangling!")
+               : _(" %s has become dangling!");
 
        memset(&states, 0, sizeof(states));
        get_remote_ref_states(remote, &states, GET_REF_STATES);
 
        if (states.stale.nr) {
-               printf("Pruning %s\n", remote);
-               printf("URL: %s\n",
+               printf_ln(_("Pruning %s"), remote);
+               printf_ln(_("URL: %s"),
                       states.remote->url_nr
                       ? states.remote->url[0]
-                      : "(no URL)");
+                      : _("(no URL)"));
        }
 
        for (i = 0; i < states.stale.nr; i++) {
@@ -1280,8 +1291,12 @@ static int prune_remote(const char *remote, int dry_run)
                if (!dry_run)
                        result |= delete_ref(refname, NULL, 0);
 
-               printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
-                      abbrev_ref(refname, "refs/remotes/"));
+               if (dry_run)
+                       printf_ln(_(" * [would prune] %s"),
+                              abbrev_ref(refname, "refs/remotes/"));
+               else
+                       printf_ln(_(" * [pruned] %s"),
+                              abbrev_ref(refname, "refs/remotes/"));
                warn_dangling_symref(stdout, dangling_msg, refname);
        }
 
@@ -1369,7 +1384,7 @@ static int set_remote_branches(const char *remotename, const char **branches,
        strbuf_addf(&key, "remote.%s.fetch", remotename);
 
        if (!remote_is_configured(remotename))
-               die("No such remote '%s'", remotename);
+               die(_("No such remote '%s'"), remotename);
        remote = remote_get(remotename);
 
        if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
@@ -1396,7 +1411,7 @@ static int set_branches(int argc, const char **argv)
        argc = parse_options(argc, argv, NULL, options,
                             builtin_remote_setbranches_usage, 0);
        if (argc == 0) {
-               error("no remote specified");
+               error(_("no remote specified"));
                usage_with_options(builtin_remote_setbranches_usage, options);
        }
        argv[argc] = NULL;
@@ -1429,7 +1444,7 @@ static int set_url(int argc, const char **argv)
                             PARSE_OPT_KEEP_ARGV0);
 
        if (add_mode && delete_mode)
-               die("--add --delete doesn't make sense");
+               die(_("--add --delete doesn't make sense"));
 
        if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
                usage_with_options(builtin_remote_seturl_usage, options);
@@ -1443,7 +1458,7 @@ static int set_url(int argc, const char **argv)
                oldurl = newurl;
 
        if (!remote_is_configured(remotename))
-               die("No such remote '%s'", remotename);
+               die(_("No such remote '%s'"), remotename);
        remote = remote_get(remotename);
 
        if (push_mode) {
@@ -1469,7 +1484,7 @@ static int set_url(int argc, const char **argv)
 
        /* Old URL specified. Demand that one matches. */
        if (regcomp(&old_regex, oldurl, REG_EXTENDED))
-               die("Invalid old URL pattern: %s", oldurl);
+               die(_("Invalid old URL pattern: %s"), oldurl);
 
        for (i = 0; i < urlset_nr; i++)
                if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
@@ -1477,9 +1492,9 @@ static int set_url(int argc, const char **argv)
                else
                        negative_matches++;
        if (!delete_mode && !matches)
-               die("No such URL found: %s", oldurl);
+               die(_("No such URL found: %s"), oldurl);
        if (delete_mode && !negative_matches && !push_mode)
-               die("Will not delete all non-push URLs");
+               die(_("Will not delete all non-push URLs"));
 
        regfree(&old_regex);
 
@@ -1580,7 +1595,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
        else if (!strcmp(argv[0], "update"))
                result = update(argc, argv);
        else {
-               error("Unknown subcommand: %s", argv[0]);
+               error(_("Unknown subcommand: %s"), argv[0]);
                usage_with_options(builtin_remote_usage, options);
        }
 
index 98d1cbeccacc4b22dee88dab3d0154a3f70afe81..733f626f6c3e4ef54d54df923230f7ae4fbb2d7d 100644 (file)
@@ -634,6 +634,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                        if (!strcmp(arg, "--show-prefix")) {
                                if (prefix)
                                        puts(prefix);
+                               else
+                                       putchar('\n');
                                continue;
                        }
                        if (!strcmp(arg, "--show-cdup")) {
index 92f3fa5f57f289dbc966f5a49c7aedf2ee87e547..82d1bf844b996d5d121169f244358964fd36fd19 100644 (file)
@@ -86,6 +86,7 @@ static void verify_opt_mutually_compatible(const char *me, ...)
                                break;
                }
        }
+       va_end(ap);
 
        if (opt1 && opt2)
                die(_("%s: %s cannot be used with %s"), me, opt1, opt2);
@@ -114,12 +115,16 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                OPT_END(),
                OPT_END(),
                OPT_END(),
+               OPT_END(),
+               OPT_END(),
        };
 
        if (opts->action == REPLAY_PICK) {
                struct option cp_extra[] = {
                        OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
                        OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
+                       OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, "preserve initially empty commits"),
+                       OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, "keep redundant, empty commits"),
                        OPT_END(),
                };
                if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
@@ -137,6 +142,10 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
                                "--abort", rollback,
                                NULL);
 
+       /* implies allow_empty */
+       if (opts->keep_redundant_commits)
+               opts->allow_empty = 1;
+
        /* Set the subcommand */
        if (remove_state)
                opts->subcommand = REPLAY_REMOVE_STATE;
index 9df341c793d58eff215805bf5ca5da383f4a99d9..d5d7105ba2debde9fa6f2f33171d84cc7c25339a 100644 (file)
@@ -410,6 +410,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        const char *receivepack = "git-receive-pack";
        int flags;
        int nonfastforward = 0;
+       int progress = -1;
 
        argv++;
        for (i = 1; i < argc; i++, argv++) {
@@ -452,6 +453,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.verbose = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--progress")) {
+                               progress = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-progress")) {
+                               progress = 0;
+                               continue;
+                       }
                        if (!strcmp(arg, "--thin")) {
                                args.use_thin_pack = 1;
                                continue;
@@ -492,6 +501,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                }
        }
 
+       if (progress == -1)
+               progress = !args.quiet && isatty(2);
+       args.progress = progress;
+
        if (args.stateless_rpc) {
                conn = NULL;
                fd[0] = 0;
index fe7e5e5b3d64dc8168ebe687ac5c6c2d1cbbdba1..4fb6bd7b3dd39dadbbb2eb3bfb8a8c3dc96e8484 100644 (file)
@@ -16,6 +16,7 @@
 #include "revision.h"
 #include "gpg-interface.h"
 #include "sha1-array.h"
+#include "column.h"
 
 static const char * const git_tag_usage[] = {
        "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
@@ -33,6 +34,7 @@ struct tag_filter {
 };
 
 static struct sha1_array points_at;
+static unsigned int colopts;
 
 static int match_pattern(const char **patterns, const char *ref)
 {
@@ -263,6 +265,8 @@ static int git_tag_config(const char *var, const char *value, void *cb)
        int status = git_gpg_config(var, value, cb);
        if (status)
                return status;
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "tag", &colopts);
        return git_default_config(var, value, cb);
 }
 
@@ -459,6 +463,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_STRING('u', "local-user", &keyid, "key-id",
                                        "use another key to sign the tag"),
                OPT__FORCE(&force, "replace the tag if exists"),
+               OPT_COLUMN(0, "column", &colopts, "show tag list in columns"),
 
                OPT_GROUP("Tag listing options"),
                {
@@ -495,9 +500,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
        if (list + delete + verify > 1)
                usage_with_options(git_tag_usage, options);
-       if (list)
-               return list_tags(argv, lines == -1 ? 0 : lines,
-                                with_commit);
+       finalize_colopts(&colopts, -1);
+       if (list && lines != -1) {
+               if (explicitly_enable_column(colopts))
+                       die(_("--column and -n are incompatible"));
+               colopts = 0;
+       }
+       if (list) {
+               int ret;
+               if (column_active(colopts)) {
+                       struct column_options copts;
+                       memset(&copts, 0, sizeof(copts));
+                       copts.padding = 2;
+                       run_column_filter(colopts, &copts);
+               }
+               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
+               if (column_active(colopts))
+                       stop_column_filter();
+               return ret;
+       }
        if (lines != -1)
                die(_("-n option is only allowed with -l."));
        if (with_commit)
index 14e04e6795bd514fb1fec506073d8a9a71668cfa..2217d7b3ae57235fc25ac3f5bf8429588aa034c4 100644 (file)
@@ -107,7 +107,7 @@ static void *get_data(unsigned long size)
                if (stream.total_out == size && ret == Z_STREAM_END)
                        break;
                if (ret != Z_OK) {
-                       error("inflate returned %d\n", ret);
+                       error("inflate returned %d", ret);
                        free(buf);
                        buf = NULL;
                        if (!recover)
index a6a23fa1f3c7782566d7fcfe470dd424b876e4a5..5f038d64da38820ebaa73ff73d1082e17c3c80d2 100644 (file)
@@ -708,6 +708,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
        int newfd, entries, has_errors = 0, line_termination = '\n';
        int read_from_stdin = 0;
        int prefix_length = prefix ? strlen(prefix) : 0;
+       int preferred_index_format = 0;
        char set_executable_bit = 0;
        struct refresh_params refresh_args = {0, &has_errors};
        int lock_error = 0;
@@ -791,6 +792,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                        "(for porcelains) forget saved unresolved conflicts",
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
                        resolve_undo_clear_callback},
+               OPT_INTEGER(0, "index-version", &preferred_index_format,
+                           "write index in this format"),
                OPT_END()
        };
 
@@ -851,6 +854,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                }
        }
        argc = parse_options_end(&ctx);
+       if (preferred_index_format) {
+               if (preferred_index_format < INDEX_FORMAT_LB ||
+                   INDEX_FORMAT_UB < preferred_index_format)
+                       die("index-version %d not in range: %d..%d",
+                           preferred_index_format,
+                           INDEX_FORMAT_LB, INDEX_FORMAT_UB);
+
+               if (the_index.version != preferred_index_format)
+                       active_cache_changed = 1;
+               the_index.version = preferred_index_format;
+       }
 
        if (read_from_stdin) {
                struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
index b90dce6358153b274a1e26afde9cc89aad473d14..0d63c4498c0c10193846c020a2d76958bd12e1bd 100644 (file)
@@ -15,6 +15,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
+       git_config(git_default_config, NULL);
        argc = parse_options(argc, argv, prefix, options,
                             update_server_info_usage, 0);
        if (argc > 0)
index 27ab32e431660a217d5dce50fe5b5b91888b5908..8d31b98f58b9e9bf156615130ec80684f788fcaa 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -33,7 +33,7 @@ static int parse_bundle_header(int fd, struct bundle_header *header,
        if (strbuf_getwholeline_fd(&buf, fd, '\n') ||
            strcmp(buf.buf, bundle_signature)) {
                if (report_path)
-                       error("'%s' does not look like a v2 bundle file",
+                       error(_("'%s' does not look like a v2 bundle file"),
                              report_path);
                status = -1;
                goto abort;
@@ -60,7 +60,7 @@ static int parse_bundle_header(int fd, struct bundle_header *header,
                    (40 <= buf.len && !isspace(buf.buf[40])) ||
                    (!is_prereq && buf.len <= 40)) {
                        if (report_path)
-                               error("unrecognized header: %s%s (%d)",
+                               error(_("unrecognized header: %s%s (%d)"),
                                      (is_prereq ? "-" : ""), buf.buf, (int)buf.len);
                        status = -1;
                        break;
@@ -86,7 +86,7 @@ int read_bundle_header(const char *path, struct bundle_header *header)
        int fd = open(path, O_RDONLY);
 
        if (fd < 0)
-               return error("could not open '%s'", path);
+               return error(_("could not open '%s'"), path);
        return parse_bundle_header(fd, header, path);
 }
 
@@ -137,7 +137,7 @@ int verify_bundle(struct bundle_header *header, int verbose)
        struct object_array refs;
        struct commit *commit;
        int i, ret = 0, req_nr;
-       const char *message = "Repository lacks these prerequisite commits:";
+       const char *message = _("Repository lacks these prerequisite commits:");
 
        init_revisions(&revs, NULL);
        for (i = 0; i < p->nr; i++) {
@@ -161,7 +161,7 @@ int verify_bundle(struct bundle_header *header, int verbose)
        revs.leak_pending = 1;
 
        if (prepare_revision_walk(&revs))
-               die("revision walk setup failed");
+               die(_("revision walk setup failed"));
 
        i = req_nr;
        while (i && (commit = get_revision(&revs)))
@@ -183,12 +183,16 @@ int verify_bundle(struct bundle_header *header, int verbose)
                struct ref_list *r;
 
                r = &header->references;
-               printf("The bundle contains %d ref%s\n",
-                      r->nr, (1 < r->nr) ? "s" : "");
+               printf_ln(Q_("The bundle contains %d ref",
+                            "The bundle contains %d refs",
+                            r->nr),
+                         r->nr);
                list_refs(r, 0, NULL);
                r = &header->prerequisites;
-               printf("The bundle requires these %d ref%s\n",
-                      r->nr, (1 < r->nr) ? "s" : "");
+               printf_ln(Q_("The bundle requires this ref",
+                            "The bundle requires these %d refs",
+                            r->nr),
+                         r->nr);
                list_refs(r, 0, NULL);
        }
        return ret;
@@ -283,13 +287,13 @@ int create_bundle(struct bundle_header *header, const char *path,
        strbuf_release(&buf);
        fclose(rls_fout);
        if (finish_command(&rls))
-               return error("rev-list died");
+               return error(_("rev-list died"));
 
        /* write references */
        argc = setup_revisions(argc, argv, &revs, NULL);
 
        if (argc > 1)
-               return error("unrecognized argument: %s", argv[1]);
+               return error(_("unrecognized argument: %s"), argv[1]);
 
        object_array_remove_duplicates(&revs.pending);
 
@@ -324,7 +328,7 @@ int create_bundle(struct bundle_header *header, const char *path,
                 * constraints.
                 */
                if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) {
-                       warning("ref '%s' is excluded by the rev-list options",
+                       warning(_("ref '%s' is excluded by the rev-list options"),
                                e->name);
                        free(ref);
                        continue;
@@ -369,7 +373,7 @@ int create_bundle(struct bundle_header *header, const char *path,
                free(ref);
        }
        if (!ref_count)
-               die ("Refusing to create empty bundle.");
+               die(_("Refusing to create empty bundle."));
 
        /* end header */
        write_or_die(bundle_fd, "\n", 1);
@@ -387,7 +391,7 @@ int create_bundle(struct bundle_header *header, const char *path,
        rls.out = bundle_fd;
        rls.git_cmd = 1;
        if (start_command(&rls))
-               return error("Could not spawn pack-objects");
+               return error(_("Could not spawn pack-objects"));
 
        /*
         * start_command closed bundle_fd if it was > 1
@@ -405,10 +409,10 @@ int create_bundle(struct bundle_header *header, const char *path,
        }
        close(rls.in);
        if (finish_command(&rls))
-               return error ("pack-objects died");
+               return error(_("pack-objects died"));
        if (!bundle_to_stdout) {
                if (commit_lock_file(&lock))
-                       die_errno("cannot create '%s'", path);
+                       die_errno(_("cannot create '%s'"), path);
        }
        return 0;
 }
@@ -430,6 +434,6 @@ int unbundle(struct bundle_header *header, int bundle_fd, int flags)
        ip.no_stdout = 1;
        ip.git_cmd = 1;
        if (run_command(&ip))
-               return error("index-pack died");
+               return error(_("index-pack died"));
        return 0;
 }
diff --git a/cache.h b/cache.h
index 5e6eef9b4d0fca96fb0f90265f5f68d640f1583a..e14ffcd914be8759a5f5a71fa7fc71e4f9774f0e 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -105,6 +105,9 @@ struct cache_header {
        unsigned int hdr_entries;
 };
 
+#define INDEX_FORMAT_LB 2
+#define INDEX_FORMAT_UB 4
+
 /*
  * The "cache_time" is just the low 32 bits of the
  * time. It doesn't matter if it overflows - we only
@@ -115,48 +118,6 @@ struct cache_time {
        unsigned int nsec;
 };
 
-/*
- * dev/ino/uid/gid/size are also just tracked to the low 32 bits
- * Again - this is just a (very strong in practice) heuristic that
- * the inode hasn't changed.
- *
- * We save the fields in big-endian order to allow using the
- * index file over NFS transparently.
- */
-struct ondisk_cache_entry {
-       struct cache_time ctime;
-       struct cache_time mtime;
-       unsigned int dev;
-       unsigned int ino;
-       unsigned int mode;
-       unsigned int uid;
-       unsigned int gid;
-       unsigned int size;
-       unsigned char sha1[20];
-       unsigned short flags;
-       char name[FLEX_ARRAY]; /* more */
-};
-
-/*
- * This struct is used when CE_EXTENDED bit is 1
- * The struct must match ondisk_cache_entry exactly from
- * ctime till flags
- */
-struct ondisk_cache_entry_extended {
-       struct cache_time ctime;
-       struct cache_time mtime;
-       unsigned int dev;
-       unsigned int ino;
-       unsigned int mode;
-       unsigned int uid;
-       unsigned int gid;
-       unsigned int size;
-       unsigned char sha1[20];
-       unsigned short flags;
-       unsigned short flags2;
-       char name[FLEX_ARRAY]; /* more */
-};
-
 struct cache_entry {
        struct cache_time ce_ctime;
        struct cache_time ce_mtime;
@@ -253,9 +214,6 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
 }
 
 #define ce_size(ce) cache_entry_size(ce_namelen(ce))
-#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
-                           ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
-                           ondisk_cache_entry_size(ce_namelen(ce)))
 #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
 #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
 #define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
@@ -306,13 +264,11 @@ static inline unsigned int canon_mode(unsigned int mode)
        return S_IFGITLINK;
 }
 
-#define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
 #define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1)
-#define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
-#define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
 
 struct index_state {
        struct cache_entry **cache;
+       unsigned int version;
        unsigned int cache_nr, cache_alloc, cache_changed;
        struct string_list *resolve_undo;
        struct cache_tree *cache_tree;
@@ -624,8 +580,10 @@ enum rebase_setup_type {
 enum push_default_type {
        PUSH_DEFAULT_NOTHING = 0,
        PUSH_DEFAULT_MATCHING,
+       PUSH_DEFAULT_SIMPLE,
        PUSH_DEFAULT_UPSTREAM,
-       PUSH_DEFAULT_CURRENT
+       PUSH_DEFAULT_CURRENT,
+       PUSH_DEFAULT_UNSPECIFIED
 };
 
 extern enum branch_track git_branch_track;
@@ -919,10 +877,8 @@ enum date_mode {
 };
 
 const char *show_date(unsigned long time, int timezone, enum date_mode mode);
-const char *show_date_relative(unsigned long time, int tz,
-                              const struct timeval *now,
-                              char *timebuf,
-                              size_t timebuf_size);
+void show_date_relative(unsigned long time, int tz, const struct timeval *now,
+                       struct strbuf *timebuf);
 int parse_date(const char *date, char *buf, int bufsize);
 int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
 void datestamp(char *buf, int bufsize);
diff --git a/column.c b/column.c
new file mode 100644 (file)
index 0000000..9367ba5
--- /dev/null
+++ b/column.c
@@ -0,0 +1,434 @@
+#include "cache.h"
+#include "column.h"
+#include "string-list.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "utf8.h"
+
+#define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \
+                           (x) * (d)->rows + (y) : \
+                           (y) * (d)->cols + (x))
+
+struct column_data {
+       const struct string_list *list;
+       unsigned int colopts;
+       struct column_options opts;
+
+       int rows, cols;
+       int *len;               /* cell length */
+       int *width;           /* index to the longest row in column */
+};
+
+/* return length of 's' in letters, ANSI escapes stripped */
+static int item_length(unsigned int colopts, const char *s)
+{
+       int len, i = 0;
+       struct strbuf str = STRBUF_INIT;
+
+       strbuf_addstr(&str, s);
+       while ((s = strstr(str.buf + i, "\033[")) != NULL) {
+               int len = strspn(s + 2, "0123456789;");
+               i = s - str.buf;
+               strbuf_remove(&str, i, len + 3); /* \033[<len><func char> */
+       }
+       len = utf8_strwidth(str.buf);
+       strbuf_release(&str);
+       return len;
+}
+
+/*
+ * Calculate cell width, rows and cols for a table of equal cells, given
+ * table width and how many spaces between cells.
+ */
+static void layout(struct column_data *data, int *width)
+{
+       int i;
+
+       *width = 0;
+       for (i = 0; i < data->list->nr; i++)
+               if (*width < data->len[i])
+                       *width = data->len[i];
+
+       *width += data->opts.padding;
+
+       data->cols = (data->opts.width - strlen(data->opts.indent)) / *width;
+       if (data->cols == 0)
+               data->cols = 1;
+
+       data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
+}
+
+static void compute_column_width(struct column_data *data)
+{
+       int i, x, y;
+       for (x = 0; x < data->cols; x++) {
+               data->width[x] = XY2LINEAR(data, x, 0);
+               for (y = 0; y < data->rows; y++) {
+                       i = XY2LINEAR(data, x, y);
+                       if (i < data->list->nr &&
+                           data->len[data->width[x]] < data->len[i])
+                               data->width[x] = i;
+               }
+       }
+}
+
+/*
+ * Shrink all columns by shortening them one row each time (and adding
+ * more columns along the way). Hopefully the longest cell will be
+ * moved to the next column, column is shrunk so we have more space
+ * for new columns. The process ends when the whole thing no longer
+ * fits in data->total_width.
+ */
+static void shrink_columns(struct column_data *data)
+{
+       data->width = xrealloc(data->width,
+                              sizeof(*data->width) * data->cols);
+       while (data->rows > 1) {
+               int x, total_width, cols, rows;
+               rows = data->rows;
+               cols = data->cols;
+
+               data->rows--;
+               data->cols = DIV_ROUND_UP(data->list->nr, data->rows);
+               if (data->cols != cols)
+                       data->width = xrealloc(data->width,
+                                              sizeof(*data->width) * data->cols);
+               compute_column_width(data);
+
+               total_width = strlen(data->opts.indent);
+               for (x = 0; x < data->cols; x++) {
+                       total_width += data->len[data->width[x]];
+                       total_width += data->opts.padding;
+               }
+               if (total_width > data->opts.width) {
+                       data->rows = rows;
+                       data->cols = cols;
+                       break;
+               }
+       }
+       compute_column_width(data);
+}
+
+/* Display without layout when not enabled */
+static void display_plain(const struct string_list *list,
+                         const char *indent, const char *nl)
+{
+       int i;
+
+       for (i = 0; i < list->nr; i++)
+               printf("%s%s%s", indent, list->items[i].string, nl);
+}
+
+/* Print a cell to stdout with all necessary leading/traling space */
+static int display_cell(struct column_data *data, int initial_width,
+                       const char *empty_cell, int x, int y)
+{
+       int i, len, newline;
+
+       i = XY2LINEAR(data, x, y);
+       if (i >= data->list->nr)
+               return -1;
+
+       len = data->len[i];
+       if (data->width && data->len[data->width[x]] < initial_width) {
+               /*
+                * empty_cell has initial_width chars, if real column
+                * is narrower, increase len a bit so we fill less
+                * space.
+                */
+               len += initial_width - data->len[data->width[x]];
+               len -= data->opts.padding;
+       }
+
+       if (COL_LAYOUT(data->colopts) == COL_COLUMN)
+               newline = i + data->rows >= data->list->nr;
+       else
+               newline = x == data->cols - 1 || i == data->list->nr - 1;
+
+       printf("%s%s%s",
+              x == 0 ? data->opts.indent : "",
+              data->list->items[i].string,
+              newline ? data->opts.nl : empty_cell + len);
+       return 0;
+}
+
+/* Display COL_COLUMN or COL_ROW */
+static void display_table(const struct string_list *list,
+                         unsigned int colopts,
+                         const struct column_options *opts)
+{
+       struct column_data data;
+       int x, y, i, initial_width;
+       char *empty_cell;
+
+       memset(&data, 0, sizeof(data));
+       data.list = list;
+       data.colopts = colopts;
+       data.opts = *opts;
+
+       data.len = xmalloc(sizeof(*data.len) * list->nr);
+       for (i = 0; i < list->nr; i++)
+               data.len[i] = item_length(colopts, list->items[i].string);
+
+       layout(&data, &initial_width);
+
+       if (colopts & COL_DENSE)
+               shrink_columns(&data);
+
+       empty_cell = xmalloc(initial_width + 1);
+       memset(empty_cell, ' ', initial_width);
+       empty_cell[initial_width] = '\0';
+       for (y = 0; y < data.rows; y++) {
+               for (x = 0; x < data.cols; x++)
+                       if (display_cell(&data, initial_width, empty_cell, x, y))
+                               break;
+       }
+
+       free(data.len);
+       free(data.width);
+       free(empty_cell);
+}
+
+void print_columns(const struct string_list *list, unsigned int colopts,
+                  const struct column_options *opts)
+{
+       struct column_options nopts;
+
+       if (!list->nr)
+               return;
+       assert((colopts & COL_ENABLE_MASK) != COL_AUTO);
+
+       memset(&nopts, 0, sizeof(nopts));
+       nopts.indent = opts && opts->indent ? opts->indent : "";
+       nopts.nl = opts && opts->nl ? opts->nl : "\n";
+       nopts.padding = opts ? opts->padding : 1;
+       nopts.width = opts && opts->width ? opts->width : term_columns() - 1;
+       if (!column_active(colopts)) {
+               display_plain(list, "", "\n");
+               return;
+       }
+       switch (COL_LAYOUT(colopts)) {
+       case COL_PLAIN:
+               display_plain(list, nopts.indent, nopts.nl);
+               break;
+       case COL_ROW:
+       case COL_COLUMN:
+               display_table(list, colopts, &nopts);
+               break;
+       default:
+               die("BUG: invalid layout mode %d", COL_LAYOUT(colopts));
+       }
+}
+
+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)
+{
+       if ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {
+               if (stdout_is_tty < 0)
+                       stdout_is_tty = isatty(1);
+               *colopts &= ~COL_ENABLE_MASK;
+               if (stdout_is_tty)
+                       *colopts |= COL_ENABLED;
+       }
+       return 0;
+}
+
+struct colopt {
+       const char *name;
+       unsigned int value;
+       unsigned int mask;
+};
+
+#define LAYOUT_SET 1
+#define ENABLE_SET 2
+
+static int parse_option(const char *arg, int len, unsigned int *colopts,
+                       int *group_set)
+{
+       struct colopt opts[] = {
+               { "always", COL_ENABLED,  COL_ENABLE_MASK },
+               { "never",  COL_DISABLED, COL_ENABLE_MASK },
+               { "auto",   COL_AUTO,     COL_ENABLE_MASK },
+               { "plain",  COL_PLAIN,    COL_LAYOUT_MASK },
+               { "column", COL_COLUMN,   COL_LAYOUT_MASK },
+               { "row",    COL_ROW,      COL_LAYOUT_MASK },
+               { "dense",  COL_DENSE,    0 },
+       };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(opts); i++) {
+               int set = 1, arg_len = len, name_len;
+               const char *arg_str = arg;
+
+               if (!opts[i].mask) {
+                       if (arg_len > 2 && !strncmp(arg_str, "no", 2)) {
+                               arg_str += 2;
+                               arg_len -= 2;
+                               set = 0;
+                       }
+               }
+
+               name_len = strlen(opts[i].name);
+               if (arg_len != name_len ||
+                   strncmp(arg_str, opts[i].name, name_len))
+                       continue;
+
+               switch (opts[i].mask) {
+               case COL_ENABLE_MASK:
+                       *group_set |= ENABLE_SET;
+                       break;
+               case COL_LAYOUT_MASK:
+                       *group_set |= LAYOUT_SET;
+                       break;
+               }
+
+               if (opts[i].mask)
+                       *colopts = (*colopts & ~opts[i].mask) | opts[i].value;
+               else {
+                       if (set)
+                               *colopts |= opts[i].value;
+                       else
+                               *colopts &= ~opts[i].value;
+               }
+               return 0;
+       }
+
+       return error("unsupported option '%s'", arg);
+}
+
+static int parse_config(unsigned int *colopts, const char *value)
+{
+       const char *sep = " ,";
+       int group_set = 0;
+
+       while (*value) {
+               int len = strcspn(value, sep);
+               if (len) {
+                       if (parse_option(value, len, colopts, &group_set))
+                               return -1;
+
+                       value += len;
+               }
+               value += strspn(value, sep);
+       }
+       /*
+        * Setting layout implies "always" if neither always, never
+        * nor auto is specified.
+        *
+        * Current value in COL_ENABLE_MASK is disregarded. This means if
+        * you set column.ui = auto and pass --column=row, then "auto"
+        * will become "always".
+        */
+       if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))
+               *colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+       return 0;
+}
+
+static int column_config(const char *var, const char *value,
+                        const char *key, unsigned int *colopts)
+{
+       if (!value)
+               return config_error_nonbool(var);
+       if (parse_config(colopts, value))
+               return error("invalid column.%s mode %s", key, value);
+       return 0;
+}
+
+int git_column_config(const char *var, const char *value,
+                     const char *command, unsigned int *colopts)
+{
+       const char *it = skip_prefix(var, "column.");
+       if (!it)
+               return 0;
+
+       if (!strcmp(it, "ui"))
+               return column_config(var, value, "ui", colopts);
+
+       if (command && !strcmp(it, command))
+               return column_config(var, value, it, colopts);
+
+       return 0;
+}
+
+int parseopt_column_callback(const struct option *opt,
+                            const char *arg, int unset)
+{
+       unsigned int *colopts = opt->value;
+       *colopts |= COL_PARSEOPT;
+       *colopts &= ~COL_ENABLE_MASK;
+       if (unset)              /* --no-column == never */
+               return 0;
+       /* --column == always unless "arg" states otherwise */
+       *colopts |= COL_ENABLED;
+       if (arg)
+               return parse_config(colopts, arg);
+
+       return 0;
+}
+
+static int fd_out = -1;
+static struct child_process column_process;
+
+int run_column_filter(int colopts, const struct column_options *opts)
+{
+       const char *av[10];
+       int ret, ac = 0;
+       struct strbuf sb_colopt  = STRBUF_INIT;
+       struct strbuf sb_width   = STRBUF_INIT;
+       struct strbuf sb_padding = STRBUF_INIT;
+
+       if (fd_out != -1)
+               return -1;
+
+       av[ac++] = "column";
+       strbuf_addf(&sb_colopt, "--raw-mode=%d", colopts);
+       av[ac++] = sb_colopt.buf;
+       if (opts && opts->width) {
+               strbuf_addf(&sb_width, "--width=%d", opts->width);
+               av[ac++] = sb_width.buf;
+       }
+       if (opts && opts->indent) {
+               av[ac++] = "--indent";
+               av[ac++] = opts->indent;
+       }
+       if (opts && opts->padding) {
+               strbuf_addf(&sb_padding, "--padding=%d", opts->padding);
+               av[ac++] = sb_padding.buf;
+       }
+       av[ac] = NULL;
+
+       fflush(stdout);
+       memset(&column_process, 0, sizeof(column_process));
+       column_process.in = -1;
+       column_process.out = dup(1);
+       column_process.git_cmd = 1;
+       column_process.argv = av;
+
+       ret = start_command(&column_process);
+
+       strbuf_release(&sb_colopt);
+       strbuf_release(&sb_width);
+       strbuf_release(&sb_padding);
+
+       if (ret)
+               return -2;
+
+       fd_out = dup(1);
+       close(1);
+       dup2(column_process.in, 1);
+       close(column_process.in);
+       return 0;
+}
+
+int stop_column_filter(void)
+{
+       if (fd_out == -1)
+               return -1;
+
+       fflush(stdout);
+       close(1);
+       finish_command(&column_process);
+       dup2(fd_out, 1);
+       close(fd_out);
+       fd_out = -1;
+       return 0;
+}
diff --git a/column.h b/column.h
new file mode 100644 (file)
index 0000000..0a61917
--- /dev/null
+++ b/column.h
@@ -0,0 +1,45 @@
+#ifndef COLUMN_H
+#define COLUMN_H
+
+#define COL_LAYOUT_MASK   0x000F
+#define COL_ENABLE_MASK   0x0030   /* always, never or auto */
+#define COL_PARSEOPT      0x0040   /* --column is given from cmdline */
+#define COL_DENSE         0x0080   /* Shrink columns when possible,
+                                     making space for more columns */
+
+#define COL_DISABLED      0x0000   /* must be zero */
+#define COL_ENABLED       0x0010
+#define COL_AUTO          0x0020
+
+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)
+#define COL_COLUMN             0   /* Fill columns before rows */
+#define COL_ROW                1   /* Fill rows before columns */
+#define COL_PLAIN             15   /* one column */
+
+#define explicitly_enable_column(c) \
+       (((c) & COL_PARSEOPT) && column_active(c))
+
+struct column_options {
+       int width;
+       int padding;
+       const char *indent;
+       const char *nl;
+};
+
+struct option;
+extern int parseopt_column_callback(const struct option *, const char *, int);
+extern int git_column_config(const char *var, const char *value,
+                            const char *command, unsigned int *colopts);
+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);
+static inline int column_active(unsigned int colopts)
+{
+       return (colopts & COL_ENABLE_MASK) == COL_ENABLED;
+}
+
+extern void print_columns(const struct string_list *list, unsigned int colopts,
+                         const struct column_options *opts);
+
+extern int run_column_filter(int colopts, const struct column_options *);
+extern int stop_column_filter(void);
+
+#endif
index a36ee9b0150278e8fc4b61e7226df18409b334be..14ea67af038fbfce388dd20b8adcda503106911d 100644 (file)
@@ -20,6 +20,7 @@ git-cherry-pick                         mainporcelain
 git-citool                              mainporcelain
 git-clean                               mainporcelain
 git-clone                               mainporcelain common
+git-column                              purehelpers
 git-commit                              mainporcelain common
 git-commit-tree                         plumbingmanipulators
 git-config                              ancillarymanipulators
@@ -76,6 +77,7 @@ git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain common
 git-name-rev                            plumbinginterrogators
 git-notes                               mainporcelain
+git-p4                                  foreignscminterface
 git-pack-objects                        plumbingmanipulators
 git-pack-redundant                      plumbinginterrogators
 git-pack-refs                           ancillarymanipulators
index 4b39c19123c7fa8584a67d5fd91e11f89e5816e4..9ed36c7db5ee0d2e820a01b27507c4ab0afe9663 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -7,6 +7,7 @@
 #include "revision.h"
 #include "notes.h"
 #include "gpg-interface.h"
+#include "mergesort.h"
 
 int save_commit_buffer = 1;
 
@@ -390,15 +391,31 @@ struct commit_list * commit_list_insert_by_date(struct commit *item, struct comm
        return commit_list_insert(item, pp);
 }
 
+static int commit_list_compare_by_date(const void *a, const void *b)
+{
+       unsigned long a_date = ((const struct commit_list *)a)->item->date;
+       unsigned long b_date = ((const struct commit_list *)b)->item->date;
+       if (a_date < b_date)
+               return 1;
+       if (a_date > b_date)
+               return -1;
+       return 0;
+}
+
+static void *commit_list_get_next(const void *a)
+{
+       return ((const struct commit_list *)a)->next;
+}
+
+static void commit_list_set_next(void *a, void *next)
+{
+       ((struct commit_list *)a)->next = next;
+}
 
 void commit_list_sort_by_date(struct commit_list **list)
 {
-       struct commit_list *ret = NULL;
-       while (*list) {
-               commit_list_insert_by_date((*list)->item, &ret);
-               *list = (*list)->next;
-       }
-       *list = ret;
+       *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next,
+                               commit_list_compare_by_date);
 }
 
 struct commit *pop_most_recent_commit(struct commit_list **list,
@@ -1182,3 +1199,30 @@ struct commit *get_merge_parent(const char *name)
        }
        return commit;
 }
+
+/*
+ * Append a commit to the end of the commit_list.
+ *
+ * next starts by pointing to the variable that holds the head of an
+ * empty commit_list, and is updated to point to the "next" field of
+ * the last item on the list as new commits are appended.
+ *
+ * Usage example:
+ *
+ *     struct commit_list *list;
+ *     struct commit_list **next = &list;
+ *
+ *     next = commit_list_append(c1, next);
+ *     next = commit_list_append(c2, next);
+ *     assert(commit_list_count(list) == 2);
+ *     return list;
+ */
+struct commit_list **commit_list_append(struct commit *commit,
+                                       struct commit_list **next)
+{
+       struct commit_list *new = xmalloc(sizeof(struct commit_list));
+       new->item = commit;
+       *next = new;
+       new->next = NULL;
+       return &new->next;
+}
index 154c0e34ff7d2dbaddcfb66b74d26697ffba6381..ccaa20b2621bd3856f7eafbe1193a20326278ae3 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -53,6 +53,8 @@ int find_commit_subject(const char *commit_buffer, const char **subject);
 
 struct commit_list *commit_list_insert(struct commit *item,
                                        struct commit_list **list);
+struct commit_list **commit_list_append(struct commit *commit,
+                                       struct commit_list **next);
 unsigned commit_list_count(const struct commit_list *l);
 struct commit_list *commit_list_insert_by_date(struct commit *item,
                                    struct commit_list **list);
index ef5b15014e98b2e4d20a8087dd497216db95d2fc..61a652138ac7b7ef412bbc15e6ce479db158b181 100644 (file)
@@ -22,9 +22,10 @@ typedef int socklen_t;
 #define S_IWOTH 0
 #define S_IXOTH 0
 #define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
-#define S_ISUID 0
-#define S_ISGID 0
-#define S_ISVTX 0
+
+#define S_ISUID 0004000
+#define S_ISGID 0002000
+#define S_ISVTX 0001000
 
 #define WIFEXITED(x) 1
 #define WIFSIGNALED(x) 0
index b58aa69fa0609dad7f591024f9da31dfa58496fb..61d2ef8e46084ef14ea22690a68c6c99e2644455 100644 (file)
@@ -30,7 +30,7 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of
        temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start);
 
        if (!CloseHandle(hmap))
-               warning("unable to close file mapping handle\n");
+               warning("unable to close file mapping handle");
 
        return temp ? temp : MAP_FAILED;
 }
index 68d32940f3f8f1512fa36702beb98c2457ca5d46..eeee986022fc8f235c43a2d45eb942fc0095f8b1 100644 (file)
--- a/config.c
+++ b/config.c
@@ -37,6 +37,11 @@ static int handle_path_include(const char *path, struct config_include_data *inc
 {
        int ret = 0;
        struct strbuf buf = STRBUF_INIT;
+       char *expanded = expand_user_path(path);
+
+       if (!expanded)
+               return error("Could not expand include path '%s'", path);
+       path = expanded;
 
        /*
         * Use an absolute path as-is, but interpret relative paths
@@ -63,6 +68,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc
                inc->depth--;
        }
        strbuf_release(&buf);
+       free(expanded);
        return ret;
 }
 
@@ -829,6 +835,8 @@ static int git_default_push_config(const char *var, const char *value)
                        push_default = PUSH_DEFAULT_NOTHING;
                else if (!strcmp(value, "matching"))
                        push_default = PUSH_DEFAULT_MATCHING;
+               else if (!strcmp(value, "simple"))
+                       push_default = PUSH_DEFAULT_SIMPLE;
                else if (!strcmp(value, "upstream"))
                        push_default = PUSH_DEFAULT_UPSTREAM;
                else if (!strcmp(value, "tracking")) /* deprecated */
@@ -837,8 +845,8 @@ static int git_default_push_config(const char *var, const char *value)
                        push_default = PUSH_DEFAULT_CURRENT;
                else {
                        error("Malformed value for %s: %s", var, value);
-                       return error("Must be one of nothing, matching, "
-                                    "tracking or current.");
+                       return error("Must be one of nothing, matching, simple, "
+                                    "upstream or current.");
                }
                return 0;
        }
@@ -1552,20 +1560,42 @@ static int section_name_match (const char *buf, const char *name)
        return 0;
 }
 
+static int section_name_is_ok(const char *name)
+{
+       /* Empty section names are bogus. */
+       if (!*name)
+               return 0;
+
+       /*
+        * Before a dot, we must be alphanumeric or dash. After the first dot,
+        * anything goes, so we can stop checking.
+        */
+       for (; *name && *name != '.'; name++)
+               if (*name != '-' && !isalnum(*name))
+                       return 0;
+       return 1;
+}
+
 /* if new_name == NULL, the section is removed instead */
 int git_config_rename_section_in_file(const char *config_filename,
                                      const char *old_name, const char *new_name)
 {
        int ret = 0, remove = 0;
        char *filename_buf = NULL;
-       struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
+       struct lock_file *lock;
        int out_fd;
        char buf[1024];
        FILE *config_file;
 
+       if (new_name && !section_name_is_ok(new_name)) {
+               ret = error("invalid section name: %s", new_name);
+               goto out;
+       }
+
        if (!config_filename)
                config_filename = filename_buf = git_pathdup("config");
 
+       lock = xcalloc(sizeof(struct lock_file), 1);
        out_fd = hold_lock_file_for_update(lock, config_filename, 0);
        if (out_fd < 0) {
                ret = error("could not lock config file %s", config_filename);
index 72f7958824dad94eb50abf7d99b264ff92b2ae88..e1255506a636722031c58398cb33056c43cffed3 100644 (file)
@@ -1,65 +1,55 @@
 #                                               -*- Autoconf -*-
 # Process this file with autoconf to produce a configure script.
 
-AC_PREREQ(2.59)
-AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org])
-
-AC_CONFIG_SRCDIR([git.c])
-
-config_file=config.mak.autogen
-config_append=config.mak.append
-config_in=config.mak.in
-
-echo "# ${config_append}.  Generated by configure." > "${config_append}"
-
+## Definitions of private macros.
 
-## Definitions of macros
 # GIT_CONF_APPEND_LINE(LINE)
 # --------------------------
 # Append LINE to file ${config_append}
 AC_DEFUN([GIT_CONF_APPEND_LINE],
-[echo "$1" >> "${config_append}"])# GIT_CONF_APPEND_LINE
-#
+         [echo "$1" >> "${config_append}"])
+
 # GIT_ARG_SET_PATH(PROGRAM)
 # -------------------------
 # Provide --with-PROGRAM=PATH option to set PATH to PROGRAM
 # Optional second argument allows setting NO_PROGRAM=YesPlease if
 # --without-PROGRAM version used.
 AC_DEFUN([GIT_ARG_SET_PATH],
-[AC_ARG_WITH([$1],
- [AS_HELP_STRING([--with-$1=PATH],
-                 [provide PATH to $1])],
- [GIT_CONF_APPEND_PATH($1,$2)],[])
-])# GIT_ARG_SET_PATH
-#
+    [AC_ARG_WITH([$1],
       [AS_HELP_STRING([--with-$1=PATH],
+                        [provide PATH to $1])],
+        [GIT_CONF_APPEND_PATH([$1], [$2])],
+        [])])
+
 # GIT_CONF_APPEND_PATH(PROGRAM)
-# ------------------------------
+# -----------------------------
 # Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH
 # Used by GIT_ARG_SET_PATH(PROGRAM)
 # Optional second argument allows setting NO_PROGRAM=YesPlease if
 # --without-PROGRAM is used.
 AC_DEFUN([GIT_CONF_APPEND_PATH],
-[PROGRAM=m4_toupper($1); \
-if test "$withval" = "no"; then \
-       if test -n "$2"; then \
-               m4_toupper($1)_PATH=$withval; \
-               AC_MSG_NOTICE([Disabling use of ${PROGRAM}]); \
-               GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease); \
-               GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=); \
-       else \
-               AC_MSG_ERROR([You cannot use git without $1]); \
-       fi; \
-else \
-       if test "$withval" = "yes"; then \
-               AC_MSG_WARN([You should provide path for --with-$1=PATH]); \
-       else \
-               m4_toupper($1)_PATH=$withval; \
-               AC_MSG_NOTICE([Setting m4_toupper($1)_PATH to $withval]); \
-               GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \
-       fi; \
-fi; \
-]) # GIT_CONF_APPEND_PATH
-#
+    [m4_pushdef([GIT_UC_PROGRAM], m4_toupper([$1]))dnl
+    PROGRAM=GIT_UC_PROGRAM
+    if test "$withval" = "no"; then
+       if test -n "$2"; then
+               GIT_UC_PROGRAM[]_PATH=$withval
+               AC_MSG_NOTICE([Disabling use of ${PROGRAM}])
+               GIT_CONF_APPEND_LINE(NO_${PROGRAM}=YesPlease)
+               GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=)
+       else
+               AC_MSG_ERROR([You cannot use git without $1])
+       fi
+    else
+       if test "$withval" = "yes"; then
+               AC_MSG_WARN([You should provide path for --with-$1=PATH])
+       else
+               GIT_UC_PROGRAM[]_PATH=$withval
+               AC_MSG_NOTICE([Setting GIT_UC_PROGRAM[]_PATH to $withval])
+               GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval)
+       fi
+    fi
+    m4_popdef([GIT_UC_PROGRAM])])
+
 # GIT_PARSE_WITH(PACKAGE)
 # -----------------------
 # For use in AC_ARG_WITH action-if-found, for packages default ON.
@@ -67,21 +57,22 @@ fi; \
 # * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH
 # * Unset NO_PACKAGE for --with-PACKAGE without ARG
 AC_DEFUN([GIT_PARSE_WITH],
-[PACKAGE=m4_toupper($1); \
-if test "$withval" = "no"; then \
-       m4_toupper(NO_$1)=YesPlease; \
-elif test "$withval" = "yes"; then \
-       m4_toupper(NO_$1)=; \
-else \
-       m4_toupper(NO_$1)=; \
-       m4_toupper($1)DIR=$withval; \
-       AC_MSG_NOTICE([Setting m4_toupper($1)DIR to $withval]); \
-       GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \
-fi \
-])# GIT_PARSE_WITH
-#
+    [m4_pushdef([GIT_UC_PACKAGE], m4_toupper([$1]))dnl
+    PACKAGE=GIT_UC_PACKAGE
+    if test "$withval" = "no"; then
+       NO_[]GIT_UC_PACKAGE=YesPlease
+    elif test "$withval" = "yes"; then
+       NO_[]GIT_UC_PACKAGE=
+    else
+       NO_[]GIT_UC_PACKAGE=
+       GIT_UC_PACKAGE[]DIR=$withval
+       AC_MSG_NOTICE([Setting GIT_UC_PACKAGE[]DIR to $withval])
+       GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval)
+    fi
+    m4_popdef([GIT_UC_PACKAGE])])
+
 # GIT_PARSE_WITH_SET_MAKE_VAR(WITHNAME, VAR, HELP_TEXT)
-# ---------------------
+# -----------------------------------------------------
 # Set VAR to the value specied by --with-WITHNAME.
 # No verification of arguments is performed, but warnings are issued
 # if either 'yes' or 'no' is specified.
@@ -90,33 +81,32 @@ fi \
 AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR],
 [AC_ARG_WITH([$1],
  [AS_HELP_STRING([--with-$1=VALUE], $3)],
- if test -n "$withval"; then \
-  if test "$withval" = "yes" -o "$withval" = "no"; then \
+ if test -n "$withval"; then
+  if test "$withval" = "yes" -o "$withval" = "no"; then
     AC_MSG_WARN([You likely do not want either 'yes' or 'no' as]
-                    [a value for $1 ($2).  Maybe you do...?]); \
-  fi; \
-  \
-  AC_MSG_NOTICE([Setting $2 to $withval]); \
-  GIT_CONF_APPEND_LINE($2=$withval); \
+                    [a value for $1 ($2).  Maybe you do...?])
+  fi
+  AC_MSG_NOTICE([Setting $2 to $withval])
+  GIT_CONF_APPEND_LINE($2=$withval)
  fi)])# GIT_PARSE_WITH_SET_MAKE_VAR
 
-dnl
-dnl GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
-dnl -----------------------------------------
-dnl Similar to AC_CHECK_FUNC, but on systems that do not generate
-dnl warnings for missing prototypes (e.g. FreeBSD when compiling without
-dnl -Wall), it does not work.  By looking for function definition in
-dnl libraries, this problem can be worked around.
+#
+# GIT_CHECK_FUNC(FUNCTION, IFTRUE, IFFALSE)
+# -----------------------------------------
+# Similar to AC_CHECK_FUNC, but on systems that do not generate
+# warnings for missing prototypes (e.g. FreeBSD when compiling without
+# -Wall), it does not work.  By looking for function definition in
+# libraries, this problem can be worked around.
 AC_DEFUN([GIT_CHECK_FUNC],[AC_CHECK_FUNC([$1],[
   AC_SEARCH_LIBS([$1],,
   [$2],[$3])
 ],[$3])])
 
-dnl
-dnl GIT_STASH_FLAGS(BASEPATH_VAR)
-dnl -----------------------------
-dnl Allow for easy stashing of LDFLAGS and CPPFLAGS before running
-dnl tests that may want to take user settings into account.
+#
+# GIT_STASH_FLAGS(BASEPATH_VAR)
+# -----------------------------
+# Allow for easy stashing of LDFLAGS and CPPFLAGS before running
+# tests that may want to take user settings into account.
 AC_DEFUN([GIT_STASH_FLAGS],[
 if test -n "$1"; then
    old_CPPFLAGS="$CPPFLAGS"
@@ -137,6 +127,19 @@ if test -n "$1"; then
 fi
 ])
 
+## Configure body starts here.
+
+AC_PREREQ(2.59)
+AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org])
+
+AC_CONFIG_SRCDIR([git.c])
+
+config_file=config.mak.autogen
+config_append=config.mak.append
+config_in=config.mak.in
+
+echo "# ${config_append}.  Generated by configure." > "${config_append}"
+
 # Directories holding "saner" versions of common or POSIX binaries.
 AC_ARG_WITH([sane-tool-path],
   [AS_HELP_STRING(
@@ -161,14 +164,13 @@ AC_ARG_WITH([sane-tool-path],
 AC_ARG_WITH([lib],
  [AS_HELP_STRING([--with-lib=ARG],
                  [ARG specifies alternative name for lib directory])],
- [if test "$withval" = "no" || test "$withval" = "yes"; then \
-       AC_MSG_WARN([You should provide name for --with-lib=ARG]); \
-else \
-       lib=$withval; \
-       AC_MSG_NOTICE([Setting lib to '$lib']); \
-       GIT_CONF_APPEND_LINE(lib=$withval); \
-fi; \
-],[])
+ [if test "$withval" = "no" || test "$withval" = "yes"; then
+       AC_MSG_WARN([You should provide name for --with-lib=ARG])
+  else
+       lib=$withval
+       AC_MSG_NOTICE([Setting lib to '$lib'])
+       GIT_CONF_APPEND_LINE(lib=$withval)
+  fi])
 
 if test -z "$lib"; then
    AC_MSG_NOTICE([Setting lib to 'lib' (the default)])
@@ -234,9 +236,9 @@ AC_MSG_NOTICE([CHECKS for site configuration])
 # /foo/bar/include and /foo/bar/lib directories.
 AC_ARG_WITH(openssl,
 AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
-AS_HELP_STRING([],              [ARG can be prefix for openssl library and headers]),\
-GIT_PARSE_WITH(openssl))
-#
+AS_HELP_STRING([],              [ARG can be prefix for openssl library and headers]),
+GIT_PARSE_WITH([openssl]))
+
 # Define USE_LIBPCRE if you have and want to use libpcre. git-grep will be
 # able to use Perl-compatible regular expressions.
 #
@@ -246,17 +248,16 @@ GIT_PARSE_WITH(openssl))
 AC_ARG_WITH(libpcre,
 AS_HELP_STRING([--with-libpcre],[support Perl-compatible regexes (default is NO)])
 AS_HELP_STRING([],           [ARG can be also prefix for libpcre library and headers]),
-if test "$withval" = "no"; then \
-       USE_LIBPCRE=; \
-elif test "$withval" = "yes"; then \
-       USE_LIBPCRE=YesPlease; \
-else
-       USE_LIBPCRE=YesPlease; \
-       LIBPCREDIR=$withval; \
-       AC_MSG_NOTICE([Setting LIBPCREDIR to $withval]); \
-       GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval); \
-fi \
-)
+    if test "$withval" = "no"; then
+       USE_LIBPCRE=
+    elif test "$withval" = "yes"; then
+       USE_LIBPCRE=YesPlease
+    else
+       USE_LIBPCRE=YesPlease
+       LIBPCREDIR=$withval
+       AC_MSG_NOTICE([Setting LIBPCREDIR to $withval])
+       GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval)
+    fi)
 #
 # Define NO_CURL if you do not have curl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
@@ -364,7 +365,7 @@ AC_ARG_WITH(tcltk,
 AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)])
 AS_HELP_STRING([],[ARG is the full path to the Tcl/Tk interpreter.])
 AS_HELP_STRING([],[Bare --with-tcltk will make the GUI part only if])
-AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),\
+AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),
 GIT_PARSE_WITH(tcltk))
 #
 
index 31f714da927511fb5b2b7ac11dd7af1e3f98ae76..9f56ec7a6bde4b195b6e960e38e5403ce0b001b0 100755 (executable)
@@ -304,16 +304,16 @@ __git_ps1 ()
        fi
 }
 
-# __gitcomp_1 requires 2 arguments
 __gitcomp_1 ()
 {
-       local c IFS=' '$'\t'$'\n'
+       local c IFS=$' \t\n'
        for c in $1; do
-               case "$c$2" in
-               --*=*) printf %s$'\n' "$c$2" ;;
-               *.)    printf %s$'\n' "$c$2" ;;
-               *)     printf %s$'\n' "$c$2 " ;;
+               c="$c$2"
+               case $c in
+               --*=*|*.) ;;
+               *) c="$c " ;;
                esac
+               printf '%s\n' "$c"
        done
 }
 
@@ -1658,7 +1658,7 @@ _git_notes ()
                __gitcomp '--ref'
                ;;
        ,*)
-               case "${words[cword-1]}" in
+               case "$prev" in
                --ref)
                        __gitcomp_nl "$(__git_refs)"
                        ;;
@@ -1684,7 +1684,7 @@ _git_notes ()
        prune,*)
                ;;
        *)
-               case "${words[cword-1]}" in
+               case "$prev" in
                -m|-F)
                        ;;
                *)
@@ -2623,8 +2623,9 @@ _git ()
                case "$i" in
                --git-dir=*) __git_dir="${i#--git-dir=}" ;;
                --bare)      __git_dir="." ;;
-               --version|-p|--paginate) ;;
                --help) command="help"; break ;;
+               -c) c=$((++c)) ;;
+               -*) ;;
                *) command="$i"; break ;;
                esac
                ((c++))
@@ -2639,9 +2640,12 @@ _git ()
                        --bare
                        --version
                        --exec-path
+                       --exec-path=
                        --html-path
+                       --info-path
                        --work-tree=
                        --namespace=
+                       --no-replace-objects
                        --help
                        "
                        ;;
index 3140e405fa130a0fa20b2f6ee4171da70935de66..0d54aa7061e780dd0000b8c2a48e266ad5b8ce53 100644 (file)
@@ -518,7 +518,7 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
                filename = git_path("FETCH_HEAD");
                fp = fopen(filename, "a");
                if (!fp)
-                       return error("cannot open %s: %s\n", filename, strerror(errno));
+                       return error("cannot open %s: %s", filename, strerror(errno));
                result = append_fetch_head(fp, argv[2], argv[3],
                                           argv[4], argv[5],
                                           argv[6], !!argv[7][0],
@@ -536,7 +536,7 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
                filename = git_path("FETCH_HEAD");
                fp = fopen(filename, "a");
                if (!fp)
-                       return error("cannot open %s: %s\n", filename, strerror(errno));
+                       return error("cannot open %s: %s", filename, strerror(errno));
                result = fetch_native_store(fp, argv[2], argv[3], argv[4],
                                            verbose, force);
                fclose(fp);
diff --git a/contrib/fast-import/git-p4.README b/contrib/fast-import/git-p4.README
new file mode 100644 (file)
index 0000000..cec5ecf
--- /dev/null
@@ -0,0 +1,12 @@
+The git-p4 script moved to the top-level of the git source directory.
+
+Invoke it as any other git command, like "git p4 clone", for instance.
+
+Note that the top-level git-p4.py script is now the source.  It is
+built using make to git-p4, which will be installed.
+
+Windows users can copy the git-p4.py source script directly, possibly
+invoking it through a batch file called "git-p4.bat" in the same folder.
+It should contain just one line:
+
+    @python "%~d0%~p0git-p4.py" %*
diff --git a/contrib/fast-import/git-p4.bat b/contrib/fast-import/git-p4.bat
deleted file mode 100644 (file)
index 9f97e88..0000000
+++ /dev/null
@@ -1 +0,0 @@
-@python "%~d0%~p0git-p4" %*
index 2cfe1b936b0feef1bd40947ce6ab249f62a6ad55..36b6feebe00060e4d522c506f12d2848673dbee6 100755 (executable)
@@ -7,7 +7,7 @@ USAGE="$me rev-list-args"
 
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
-. git-sh-setup
+. $(git --exec-path)/git-sh-setup
 require_work_tree
 cd_to_toplevel
 
diff --git a/contrib/subtree/.gitignore b/contrib/subtree/.gitignore
new file mode 100644 (file)
index 0000000..7e77c9d
--- /dev/null
@@ -0,0 +1,5 @@
+*~
+git-subtree.xml
+git-subtree.1
+mainline
+subproj
diff --git a/contrib/subtree/COPYING b/contrib/subtree/COPYING
new file mode 100644 (file)
index 0000000..d511905
--- /dev/null
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/contrib/subtree/INSTALL b/contrib/subtree/INSTALL
new file mode 100644 (file)
index 0000000..7ab0cf4
--- /dev/null
@@ -0,0 +1,28 @@
+HOW TO INSTALL git-subtree
+==========================
+
+First, build from the top source directory.
+
+Then, in contrib/subtree, run:
+
+  make
+  make install
+  make install-doc
+
+If you used configure to do the main build the git-subtree build will
+pick up those settings.  If not, you will likely have to provide a
+value for prefix:
+
+  make prefix=<some dir>
+  make prefix=<some dir> install
+  make prefix=<some dir> install-doc
+
+To run tests first copy git-subtree to the main build area so the
+newly-built git can find it:
+
+  cp git-subtree ../..
+
+Then:
+
+  make test
+
diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile
new file mode 100644 (file)
index 0000000..05cdd5c
--- /dev/null
@@ -0,0 +1,52 @@
+-include ../../config.mak.autogen
+-include ../../config.mak
+
+prefix ?= /usr/local
+mandir ?= $(prefix)/share/man
+libexecdir ?= $(prefix)/libexec/git-core
+gitdir ?= $(shell git --exec-path)
+man1dir ?= $(mandir)/man1
+
+gitver ?= $(word 3,$(shell git --version))
+
+# this should be set to a 'standard' bsd-type install program
+INSTALL ?= install
+
+ASCIIDOC_CONF      = ../../Documentation/asciidoc.conf
+MANPAGE_NORMAL_XSL =  ../../Documentation/manpage-normal.xsl
+
+GIT_SUBTREE_SH := git-subtree.sh
+GIT_SUBTREE    := git-subtree
+
+GIT_SUBTREE_DOC := git-subtree.1
+GIT_SUBTREE_XML := git-subtree.xml
+GIT_SUBTREE_TXT := git-subtree.txt
+
+all: $(GIT_SUBTREE)
+
+$(GIT_SUBTREE): $(GIT_SUBTREE_SH)
+       cp $< $@ && chmod +x $@
+
+doc: $(GIT_SUBTREE_DOC)
+
+install: $(GIT_SUBTREE)
+       $(INSTALL) -m 755 $(GIT_SUBTREE) $(libexecdir)
+
+install-doc: install-man
+
+install-man: $(GIT_SUBTREE_DOC)
+       $(INSTALL) -m 644 $^ $(man1dir)
+
+$(GIT_SUBTREE_DOC): $(GIT_SUBTREE_XML)
+       xmlto -m $(MANPAGE_NORMAL_XSL)  man $^
+
+$(GIT_SUBTREE_XML): $(GIT_SUBTREE_TXT)
+       asciidoc -b docbook -d manpage -f $(ASCIIDOC_CONF) \
+               -agit_version=$(gitver) $^
+
+test:
+       $(MAKE) -C t/ test
+
+clean:
+       rm -f *~ *.xml *.html *.1
+       rm -rf subproj mainline
diff --git a/contrib/subtree/README b/contrib/subtree/README
new file mode 100644 (file)
index 0000000..c686b4a
--- /dev/null
@@ -0,0 +1,8 @@
+
+Please read git-subtree.txt for documentation.
+
+Please don't contact me using github mail; it's slow, ugly, and worst of
+all, redundant. Email me instead at apenwarr@gmail.com and I'll be happy to
+help.
+
+Avery
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
new file mode 100755 (executable)
index 0000000..920c664
--- /dev/null
@@ -0,0 +1,712 @@
+#!/bin/bash
+#
+# git-subtree.sh: split/join git repositories in subdirectories of this one
+#
+# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
+#
+if [ $# -eq 0 ]; then
+    set -- -h
+fi
+OPTS_SPEC="\
+git subtree add   --prefix=<prefix> <commit>
+git subtree merge --prefix=<prefix> <commit>
+git subtree pull  --prefix=<prefix> <repository> <refspec...>
+git subtree push  --prefix=<prefix> <repository> <refspec...>
+git subtree split --prefix=<prefix> <commit...>
+--
+h,help        show the help
+q             quiet
+d             show debug messages
+P,prefix=     the name of the subdir to split out
+m,message=    use the given message as the commit message for the merge commit
+ options for 'split'
+annotate=     add a prefix to commit message of new commits
+b,branch=     create a new branch from the split subtree
+ignore-joins  ignore prior --rejoin commits
+onto=         try connecting new tree to an existing one
+rejoin        merge the new branch back into HEAD
+ options for 'add', 'merge', 'pull' and 'push'
+squash        merge subtree changes as a single commit
+"
+eval "$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+
+PATH=$PATH:$(git --exec-path)
+. git-sh-setup
+
+require_work_tree
+
+quiet=
+branch=
+debug=
+command=
+onto=
+rejoin=
+ignore_joins=
+annotate=
+squash=
+message=
+
+debug()
+{
+       if [ -n "$debug" ]; then
+               echo "$@" >&2
+       fi
+}
+
+say()
+{
+       if [ -z "$quiet" ]; then
+               echo "$@" >&2
+       fi
+}
+
+assert()
+{
+       if "$@"; then
+               :
+       else
+               die "assertion failed: " "$@"
+       fi
+}
+
+
+#echo "Options: $*"
+
+while [ $# -gt 0 ]; do
+       opt="$1"
+       shift
+       case "$opt" in
+               -q) quiet=1 ;;
+               -d) debug=1 ;;
+               --annotate) annotate="$1"; shift ;;
+               --no-annotate) annotate= ;;
+               -b) branch="$1"; shift ;;
+               -P) prefix="$1"; shift ;;
+               -m) message="$1"; shift ;;
+               --no-prefix) prefix= ;;
+               --onto) onto="$1"; shift ;;
+               --no-onto) onto= ;;
+               --rejoin) rejoin=1 ;;
+               --no-rejoin) rejoin= ;;
+               --ignore-joins) ignore_joins=1 ;;
+               --no-ignore-joins) ignore_joins= ;;
+               --squash) squash=1 ;;
+               --no-squash) squash= ;;
+               --) break ;;
+               *) die "Unexpected option: $opt" ;;
+       esac
+done
+
+command="$1"
+shift
+case "$command" in
+       add|merge|pull) default= ;;
+       split|push) default="--default HEAD" ;;
+       *) die "Unknown command '$command'" ;;
+esac
+
+if [ -z "$prefix" ]; then
+       die "You must provide the --prefix option."
+fi
+
+case "$command" in
+       add) [ -e "$prefix" ] && 
+               die "prefix '$prefix' already exists." ;;
+       *)   [ -e "$prefix" ] || 
+               die "'$prefix' does not exist; use 'git subtree add'" ;;
+esac
+
+dir="$(dirname "$prefix/.")"
+
+if [ "$command" != "pull" -a "$command" != "add" -a "$command" != "push" ]; then
+       revs=$(git rev-parse $default --revs-only "$@") || exit $?
+       dirs="$(git rev-parse --no-revs --no-flags "$@")" || exit $?
+       if [ -n "$dirs" ]; then
+               die "Error: Use --prefix instead of bare filenames."
+       fi
+fi
+
+debug "command: {$command}"
+debug "quiet: {$quiet}"
+debug "revs: {$revs}"
+debug "dir: {$dir}"
+debug "opts: {$*}"
+debug
+
+cache_setup()
+{
+       cachedir="$GIT_DIR/subtree-cache/$$"
+       rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
+       mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
+       mkdir -p "$cachedir/notree" || die "Can't create new cachedir: $cachedir/notree"
+       debug "Using cachedir: $cachedir" >&2
+}
+
+cache_get()
+{
+       for oldrev in $*; do
+               if [ -r "$cachedir/$oldrev" ]; then
+                       read newrev <"$cachedir/$oldrev"
+                       echo $newrev
+               fi
+       done
+}
+
+cache_miss()
+{
+       for oldrev in $*; do
+               if [ ! -r "$cachedir/$oldrev" ]; then
+                       echo $oldrev
+               fi
+       done
+}
+
+check_parents()
+{
+       missed=$(cache_miss $*)
+       for miss in $missed; do
+               if [ ! -r "$cachedir/notree/$miss" ]; then
+                       debug "  incorrect order: $miss"
+               fi
+       done
+}
+
+set_notree()
+{
+       echo "1" > "$cachedir/notree/$1"
+}
+
+cache_set()
+{
+       oldrev="$1"
+       newrev="$2"
+       if [ "$oldrev" != "latest_old" \
+            -a "$oldrev" != "latest_new" \
+            -a -e "$cachedir/$oldrev" ]; then
+               die "cache for $oldrev already exists!"
+       fi
+       echo "$newrev" >"$cachedir/$oldrev"
+}
+
+rev_exists()
+{
+       if git rev-parse "$1" >/dev/null 2>&1; then
+               return 0
+       else
+               return 1
+       fi
+}
+
+rev_is_descendant_of_branch()
+{
+       newrev="$1"
+       branch="$2"
+       branch_hash=$(git rev-parse $branch)
+       match=$(git rev-list -1 $branch_hash ^$newrev)
+
+       if [ -z "$match" ]; then
+               return 0
+       else
+               return 1
+       fi
+}
+
+# if a commit doesn't have a parent, this might not work.  But we only want
+# to remove the parent from the rev-list, and since it doesn't exist, it won't
+# be there anyway, so do nothing in that case.
+try_remove_previous()
+{
+       if rev_exists "$1^"; then
+               echo "^$1^"
+       fi
+}
+
+find_latest_squash()
+{
+       debug "Looking for latest squash ($dir)..."
+       dir="$1"
+       sq=
+       main=
+       sub=
+       git log --grep="^git-subtree-dir: $dir/*\$" \
+               --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
+       while read a b junk; do
+               debug "$a $b $junk"
+               debug "{{$sq/$main/$sub}}"
+               case "$a" in
+                       START) sq="$b" ;;
+                       git-subtree-mainline:) main="$b" ;;
+                       git-subtree-split:) sub="$b" ;;
+                       END)
+                               if [ -n "$sub" ]; then
+                                       if [ -n "$main" ]; then
+                                               # a rejoin commit?
+                                               # Pretend its sub was a squash.
+                                               sq="$sub"
+                                       fi
+                                       debug "Squash found: $sq $sub"
+                                       echo "$sq" "$sub"
+                                       break
+                               fi
+                               sq=
+                               main=
+                               sub=
+                               ;;
+               esac
+       done
+}
+
+find_existing_splits()
+{
+       debug "Looking for prior splits..."
+       dir="$1"
+       revs="$2"
+       main=
+       sub=
+       git log --grep="^git-subtree-dir: $dir/*\$" \
+               --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
+       while read a b junk; do
+               case "$a" in
+                       START) sq="$b" ;;
+                       git-subtree-mainline:) main="$b" ;;
+                       git-subtree-split:) sub="$b" ;;
+                       END)
+                               debug "  Main is: '$main'"
+                               if [ -z "$main" -a -n "$sub" ]; then
+                                       # squash commits refer to a subtree
+                                       debug "  Squash: $sq from $sub"
+                                       cache_set "$sq" "$sub"
+                               fi
+                               if [ -n "$main" -a -n "$sub" ]; then
+                                       debug "  Prior: $main -> $sub"
+                                       cache_set $main $sub
+                                       cache_set $sub $sub
+                                       try_remove_previous "$main"
+                                       try_remove_previous "$sub"
+                               fi
+                               main=
+                               sub=
+                               ;;
+               esac
+       done
+}
+
+copy_commit()
+{
+       # We're going to set some environment vars here, so
+       # do it in a subshell to get rid of them safely later
+       debug copy_commit "{$1}" "{$2}" "{$3}"
+       git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
+       (
+               read GIT_AUTHOR_NAME
+               read GIT_AUTHOR_EMAIL
+               read GIT_AUTHOR_DATE
+               read GIT_COMMITTER_NAME
+               read GIT_COMMITTER_EMAIL
+               read GIT_COMMITTER_DATE
+               export  GIT_AUTHOR_NAME \
+                       GIT_AUTHOR_EMAIL \
+                       GIT_AUTHOR_DATE \
+                       GIT_COMMITTER_NAME \
+                       GIT_COMMITTER_EMAIL \
+                       GIT_COMMITTER_DATE
+               (echo -n "$annotate"; cat ) |
+               git commit-tree "$2" $3  # reads the rest of stdin
+       ) || die "Can't copy commit $1"
+}
+
+add_msg()
+{
+       dir="$1"
+       latest_old="$2"
+       latest_new="$3"
+       if [ -n "$message" ]; then
+               commit_message="$message"
+       else
+               commit_message="Add '$dir/' from commit '$latest_new'"
+       fi
+       cat <<-EOF
+               $commit_message
+               
+               git-subtree-dir: $dir
+               git-subtree-mainline: $latest_old
+               git-subtree-split: $latest_new
+       EOF
+}
+
+add_squashed_msg()
+{
+       if [ -n "$message" ]; then
+               echo "$message"
+       else
+               echo "Merge commit '$1' as '$2'"
+       fi
+}
+
+rejoin_msg()
+{
+       dir="$1"
+       latest_old="$2"
+       latest_new="$3"
+       if [ -n "$message" ]; then
+               commit_message="$message"
+       else
+               commit_message="Split '$dir/' into commit '$latest_new'"
+       fi
+       cat <<-EOF
+               $commit_message
+               
+               git-subtree-dir: $dir
+               git-subtree-mainline: $latest_old
+               git-subtree-split: $latest_new
+       EOF
+}
+
+squash_msg()
+{
+       dir="$1"
+       oldsub="$2"
+       newsub="$3"
+       newsub_short=$(git rev-parse --short "$newsub")
+       
+       if [ -n "$oldsub" ]; then
+               oldsub_short=$(git rev-parse --short "$oldsub")
+               echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
+               echo
+               git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
+               git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
+       else
+               echo "Squashed '$dir/' content from commit $newsub_short"
+       fi
+       
+       echo
+       echo "git-subtree-dir: $dir"
+       echo "git-subtree-split: $newsub"
+}
+
+toptree_for_commit()
+{
+       commit="$1"
+       git log -1 --pretty=format:'%T' "$commit" -- || exit $?
+}
+
+subtree_for_commit()
+{
+       commit="$1"
+       dir="$2"
+       git ls-tree "$commit" -- "$dir" |
+       while read mode type tree name; do
+               assert [ "$name" = "$dir" ]
+               assert [ "$type" = "tree" -o "$type" = "commit" ]
+               [ "$type" = "commit" ] && continue  # ignore submodules
+               echo $tree
+               break
+       done
+}
+
+tree_changed()
+{
+       tree=$1
+       shift
+       if [ $# -ne 1 ]; then
+               return 0   # weird parents, consider it changed
+       else
+               ptree=$(toptree_for_commit $1)
+               if [ "$ptree" != "$tree" ]; then
+                       return 0   # changed
+               else
+                       return 1   # not changed
+               fi
+       fi
+}
+
+new_squash_commit()
+{
+       old="$1"
+       oldsub="$2"
+       newsub="$3"
+       tree=$(toptree_for_commit $newsub) || exit $?
+       if [ -n "$old" ]; then
+               squash_msg "$dir" "$oldsub" "$newsub" | 
+                       git commit-tree "$tree" -p "$old" || exit $?
+       else
+               squash_msg "$dir" "" "$newsub" |
+                       git commit-tree "$tree" || exit $?
+       fi
+}
+
+copy_or_skip()
+{
+       rev="$1"
+       tree="$2"
+       newparents="$3"
+       assert [ -n "$tree" ]
+
+       identical=
+       nonidentical=
+       p=
+       gotparents=
+       for parent in $newparents; do
+               ptree=$(toptree_for_commit $parent) || exit $?
+               [ -z "$ptree" ] && continue
+               if [ "$ptree" = "$tree" ]; then
+                       # an identical parent could be used in place of this rev.
+                       identical="$parent"
+               else
+                       nonidentical="$parent"
+               fi
+               
+               # sometimes both old parents map to the same newparent;
+               # eliminate duplicates
+               is_new=1
+               for gp in $gotparents; do
+                       if [ "$gp" = "$parent" ]; then
+                               is_new=
+                               break
+                       fi
+               done
+               if [ -n "$is_new" ]; then
+                       gotparents="$gotparents $parent"
+                       p="$p -p $parent"
+               fi
+       done
+       
+       if [ -n "$identical" ]; then
+               echo $identical
+       else
+               copy_commit $rev $tree "$p" || exit $?
+       fi
+}
+
+ensure_clean()
+{
+       if ! git diff-index HEAD --exit-code --quiet 2>&1; then
+               die "Working tree has modifications.  Cannot add."
+       fi
+       if ! git diff-index --cached HEAD --exit-code --quiet 2>&1; then
+               die "Index has modifications.  Cannot add."
+       fi
+}
+
+cmd_add()
+{
+       if [ -e "$dir" ]; then
+               die "'$dir' already exists.  Cannot add."
+       fi
+
+       ensure_clean
+       
+       if [ $# -eq 1 ]; then
+               "cmd_add_commit" "$@"
+       elif [ $# -eq 2 ]; then
+               "cmd_add_repository" "$@"
+       else
+           say "error: parameters were '$@'"
+           die "Provide either a refspec or a repository and refspec."
+       fi
+}
+
+cmd_add_repository()
+{
+       echo "git fetch" "$@"
+       repository=$1
+       refspec=$2
+       git fetch "$@" || exit $?
+       revs=FETCH_HEAD
+       set -- $revs
+       cmd_add_commit "$@"
+}
+
+cmd_add_commit()
+{
+       revs=$(git rev-parse $default --revs-only "$@") || exit $?
+       set -- $revs
+       rev="$1"
+       
+       debug "Adding $dir as '$rev'..."
+       git read-tree --prefix="$dir" $rev || exit $?
+       git checkout -- "$dir" || exit $?
+       tree=$(git write-tree) || exit $?
+       
+       headrev=$(git rev-parse HEAD) || exit $?
+       if [ -n "$headrev" -a "$headrev" != "$rev" ]; then
+               headp="-p $headrev"
+       else
+               headp=
+       fi
+       
+       if [ -n "$squash" ]; then
+               rev=$(new_squash_commit "" "" "$rev") || exit $?
+               commit=$(add_squashed_msg "$rev" "$dir" |
+                        git commit-tree $tree $headp -p "$rev") || exit $?
+       else
+               commit=$(add_msg "$dir" "$headrev" "$rev" |
+                        git commit-tree $tree $headp -p "$rev") || exit $?
+       fi
+       git reset "$commit" || exit $?
+       
+       say "Added dir '$dir'"
+}
+
+cmd_split()
+{
+       debug "Splitting $dir..."
+       cache_setup || exit $?
+       
+       if [ -n "$onto" ]; then
+               debug "Reading history for --onto=$onto..."
+               git rev-list $onto |
+               while read rev; do
+                       # the 'onto' history is already just the subdir, so
+                       # any parent we find there can be used verbatim
+                       debug "  cache: $rev"
+                       cache_set $rev $rev
+               done
+       fi
+       
+       if [ -n "$ignore_joins" ]; then
+               unrevs=
+       else
+               unrevs="$(find_existing_splits "$dir" "$revs")"
+       fi
+       
+       # We can't restrict rev-list to only $dir here, because some of our
+       # parents have the $dir contents the root, and those won't match.
+       # (and rev-list --follow doesn't seem to solve this)
+       grl='git rev-list --topo-order --reverse --parents $revs $unrevs'
+       revmax=$(eval "$grl" | wc -l)
+       revcount=0
+       createcount=0
+       eval "$grl" |
+       while read rev parents; do
+               revcount=$(($revcount + 1))
+               say -n "$revcount/$revmax ($createcount)\r"
+               debug "Processing commit: $rev"
+               exists=$(cache_get $rev)
+               if [ -n "$exists" ]; then
+                       debug "  prior: $exists"
+                       continue
+               fi
+               createcount=$(($createcount + 1))
+               debug "  parents: $parents"
+               newparents=$(cache_get $parents)
+               debug "  newparents: $newparents"
+               
+               tree=$(subtree_for_commit $rev "$dir")
+               debug "  tree is: $tree"
+
+               check_parents $parents
+               
+               # ugly.  is there no better way to tell if this is a subtree
+               # vs. a mainline commit?  Does it matter?
+               if [ -z $tree ]; then
+                       set_notree $rev
+                       if [ -n "$newparents" ]; then
+                               cache_set $rev $rev
+                       fi
+                       continue
+               fi
+
+               newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
+               debug "  newrev is: $newrev"
+               cache_set $rev $newrev
+               cache_set latest_new $newrev
+               cache_set latest_old $rev
+       done || exit $?
+       latest_new=$(cache_get latest_new)
+       if [ -z "$latest_new" ]; then
+               die "No new revisions were found"
+       fi
+       
+       if [ -n "$rejoin" ]; then
+               debug "Merging split branch into HEAD..."
+               latest_old=$(cache_get latest_old)
+               git merge -s ours \
+                       -m "$(rejoin_msg $dir $latest_old $latest_new)" \
+                       $latest_new >&2 || exit $?
+       fi
+       if [ -n "$branch" ]; then
+               if rev_exists "refs/heads/$branch"; then
+                       if ! rev_is_descendant_of_branch $latest_new $branch; then
+                               die "Branch '$branch' is not an ancestor of commit '$latest_new'."
+                       fi
+                       action='Updated'
+               else
+                       action='Created'
+               fi
+               git update-ref -m 'subtree split' "refs/heads/$branch" $latest_new || exit $?
+               say "$action branch '$branch'"
+       fi
+       echo $latest_new
+       exit 0
+}
+
+cmd_merge()
+{
+       revs=$(git rev-parse $default --revs-only "$@") || exit $?
+       ensure_clean
+       
+       set -- $revs
+       if [ $# -ne 1 ]; then
+               die "You must provide exactly one revision.  Got: '$revs'"
+       fi
+       rev="$1"
+       
+       if [ -n "$squash" ]; then
+               first_split="$(find_latest_squash "$dir")"
+               if [ -z "$first_split" ]; then
+                       die "Can't squash-merge: '$dir' was never added."
+               fi
+               set $first_split
+               old=$1
+               sub=$2
+               if [ "$sub" = "$rev" ]; then
+                       say "Subtree is already at commit $rev."
+                       exit 0
+               fi
+               new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
+               debug "New squash commit: $new"
+               rev="$new"
+       fi
+
+       version=$(git version)
+       if [ "$version" \< "git version 1.7" ]; then
+               if [ -n "$message" ]; then
+                       git merge -s subtree --message="$message" $rev
+               else
+                       git merge -s subtree $rev
+               fi
+       else
+               if [ -n "$message" ]; then
+                       git merge -Xsubtree="$prefix" --message="$message" $rev
+               else
+                       git merge -Xsubtree="$prefix" $rev
+               fi
+       fi
+}
+
+cmd_pull()
+{
+       ensure_clean
+       git fetch "$@" || exit $?
+       revs=FETCH_HEAD
+       set -- $revs
+       cmd_merge "$@"
+}
+
+cmd_push()
+{
+       if [ $# -ne 2 ]; then
+           die "You must provide <repository> <refspec>"
+       fi
+       if [ -e "$dir" ]; then
+           repository=$1
+           refspec=$2
+           echo "git push using: " $repository $refspec
+           git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec
+       else
+           die "'$dir' must already exist. Try 'git subtree add'."
+       fi
+}
+
+"cmd_$command" "$@"
diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt
new file mode 100644 (file)
index 0000000..0c44fda
--- /dev/null
@@ -0,0 +1,366 @@
+git-subtree(1)
+==============
+
+NAME
+----
+git-subtree - Merge subtrees together and split repository into subtrees
+
+
+SYNOPSIS
+--------
+[verse]
+'git subtree' add   -P <prefix> <commit>
+'git subtree' pull  -P <prefix> <repository> <refspec...>
+'git subtree' push  -P <prefix> <repository> <refspec...>
+'git subtree' merge -P <prefix> <commit>
+'git subtree' split -P <prefix> [OPTIONS] [<commit>]
+
+
+DESCRIPTION
+-----------
+Subtrees allow subprojects to be included within a subdirectory
+of the main project, optionally including the subproject's
+entire history.
+
+For example, you could include the source code for a library
+as a subdirectory of your application.
+
+Subtrees are not to be confused with submodules, which are meant for
+the same task. Unlike submodules, subtrees do not need any special
+constructions (like .gitmodule files or gitlinks) be present in
+your repository, and do not force end-users of your
+repository to do anything special or to understand how subtrees
+work. A subtree is just a subdirectory that can be
+committed to, branched, and merged along with your project in
+any way you want.
+
+They are also not to be confused with using the subtree merge
+strategy. The main difference is that, besides merging
+the other project as a subdirectory, you can also extract the
+entire history of a subdirectory from your project and make it
+into a standalone project. Unlike the subtree merge strategy
+you can alternate back and forth between these
+two operations. If the standalone library gets updated, you can
+automatically merge the changes into your project; if you
+update the library inside your project, you can "split" the
+changes back out again and merge them back into the library
+project.
+
+For example, if a library you made for one application ends up being
+useful elsewhere, you can extract its entire history and publish
+that as its own git repository, without accidentally
+intermingling the history of your application project.
+
+[TIP]
+In order to keep your commit messages clean, we recommend that
+people split their commits between the subtrees and the main
+project as much as possible.  That is, if you make a change that
+affects both the library and the main application, commit it in
+two pieces.  That way, when you split the library commits out
+later, their descriptions will still make sense.  But if this
+isn't important to you, it's not *necessary*.  git subtree will
+simply leave out the non-library-related parts of the commit
+when it splits it out into the subproject later.
+
+
+COMMANDS
+--------
+add::
+       Create the <prefix> subtree by importing its contents
+       from the given <refspec> or <repository> and remote <refspec>.
+       A new commit is created automatically, joining the imported
+       project's history with your own.  With '--squash', imports
+       only a single commit from the subproject, rather than its
+       entire history.
+
+merge::
+       Merge recent changes up to <commit> into the <prefix>
+       subtree.  As with normal 'git merge', this doesn't
+       remove your own local changes; it just merges those
+       changes into the latest <commit>.  With '--squash',
+       creates only one commit that contains all the changes,
+       rather than merging in the entire history.
+
+       If you use '--squash', the merge direction doesn't
+       always have to be forward; you can use this command to
+       go back in time from v2.5 to v2.4, for example.  If your
+       merge introduces a conflict, you can resolve it in the
+       usual ways.
+       
+pull::
+       Exactly like 'merge', but parallels 'git pull' in that
+       it fetches the given commit from the specified remote
+       repository.
+       
+push::
+       Does a 'split' (see above) using the <prefix> supplied
+       and then does a 'git push' to push the result to the 
+       repository and refspec. This can be used to push your
+       subtree to different branches of the remote repository.
+
+split::
+       Extract a new, synthetic project history from the
+       history of the <prefix> subtree.  The new history
+       includes only the commits (including merges) that
+       affected <prefix>, and each of those commits now has the
+       contents of <prefix> at the root of the project instead
+       of in a subdirectory.  Thus, the newly created history
+       is suitable for export as a separate git repository.
+       
+       After splitting successfully, a single commit id is
+       printed to stdout.  This corresponds to the HEAD of the
+       newly created tree, which you can manipulate however you
+       want.
+       
+       Repeated splits of exactly the same history are
+       guaranteed to be identical (ie. to produce the same
+       commit ids).  Because of this, if you add new commits
+       and then re-split, the new commits will be attached as
+       commits on top of the history you generated last time,
+       so 'git merge' and friends will work as expected.
+       
+       Note that if you use '--squash' when you merge, you
+       should usually not just '--rejoin' when you split.
+
+
+OPTIONS
+-------
+-q::
+--quiet::
+       Suppress unnecessary output messages on stderr.
+
+-d::
+--debug::
+       Produce even more unnecessary output messages on stderr.
+
+-P <prefix>::
+--prefix=<prefix>::
+       Specify the path in the repository to the subtree you
+       want to manipulate.  This option is mandatory
+       for all commands.
+
+-m <message>::
+--message=<message>::
+       This option is only valid for add, merge and pull (unsure).
+       Specify <message> as the commit message for the merge commit.
+
+
+OPTIONS FOR add, merge, push, pull
+----------------------------------
+--squash::
+       This option is only valid for add, merge, push and pull
+       commands.
+
+       Instead of merging the entire history from the subtree
+       project, produce only a single commit that contains all
+       the differences you want to merge, and then merge that
+       new commit into your project.
+       
+       Using this option helps to reduce log clutter. People
+       rarely want to see every change that happened between
+       v1.0 and v1.1 of the library they're using, since none of the
+       interim versions were ever included in their application.
+       
+       Using '--squash' also helps avoid problems when the same
+       subproject is included multiple times in the same
+       project, or is removed and then re-added.  In such a
+       case, it doesn't make sense to combine the histories
+       anyway, since it's unclear which part of the history
+       belongs to which subtree.
+       
+       Furthermore, with '--squash', you can switch back and
+       forth between different versions of a subtree, rather
+       than strictly forward.  'git subtree merge --squash'
+       always adjusts the subtree to match the exactly
+       specified commit, even if getting to that commit would
+       require undoing some changes that were added earlier.
+       
+       Whether or not you use '--squash', changes made in your
+       local repository remain intact and can be later split
+       and send upstream to the subproject.
+
+
+OPTIONS FOR split
+-----------------
+--annotate=<annotation>::
+       This option is only valid for the split command.
+
+       When generating synthetic history, add <annotation> as a
+       prefix to each commit message.  Since we're creating new
+       commits with the same commit message, but possibly
+       different content, from the original commits, this can help
+       to differentiate them and avoid confusion.
+       
+       Whenever you split, you need to use the same
+       <annotation>, or else you don't have a guarantee that
+       the new re-created history will be identical to the old
+       one.  That will prevent merging from working correctly. 
+       git subtree tries to make it work anyway, particularly
+       if you use --rejoin, but it may not always be effective.
+
+-b <branch>::
+--branch=<branch>::
+       This option is only valid for the split command.
+
+       After generating the synthetic history, create a new
+       branch called <branch> that contains the new history. 
+       This is suitable for immediate pushing upstream. 
+       <branch> must not already exist.
+
+--ignore-joins::
+       This option is only valid for the split command.
+
+       If you use '--rejoin', git subtree attempts to optimize
+       its history reconstruction to generate only the new
+       commits since the last '--rejoin'.  '--ignore-join'
+       disables this behaviour, forcing it to regenerate the
+       entire history.  In a large project, this can take a
+       long time.
+
+--onto=<onto>::
+       This option is only valid for the split command.
+
+       If your subtree was originally imported using something
+       other than git subtree, its history may not match what
+       git subtree is expecting.  In that case, you can specify
+       the commit id <onto> that corresponds to the first
+       revision of the subproject's history that was imported
+       into your project, and git subtree will attempt to build
+       its history from there.
+       
+       If you used 'git subtree add', you should never need
+       this option.
+
+--rejoin::
+       This option is only valid for the split command.
+
+       After splitting, merge the newly created synthetic
+       history back into your main project.  That way, future
+       splits can search only the part of history that has
+       been added since the most recent --rejoin.
+       
+       If your split commits end up merged into the upstream
+       subproject, and then you want to get the latest upstream
+       version, this will allow git's merge algorithm to more
+       intelligently avoid conflicts (since it knows these
+       synthetic commits are already part of the upstream
+       repository).
+       
+       Unfortunately, using this option results in 'git log'
+       showing an extra copy of every new commit that was
+       created (the original, and the synthetic one).
+       
+       If you do all your merges with '--squash', don't use
+       '--rejoin' when you split, because you don't want the
+       subproject's history to be part of your project anyway.
+
+
+EXAMPLE 1. Add command
+----------------------
+Let's assume that you have a local repository that you would like
+to add an external vendor library to. In this case we will add the
+git-subtree repository as a subdirectory of your already existing
+git-extensions repository in ~/git-extensions/:
+
+       $ git subtree add --prefix=git-subtree --squash \
+               git://github.com/apenwarr/git-subtree.git master
+
+'master' needs to be a valid remote ref and can be a different branch
+name
+
+You can omit the --squash flag, but doing so will increase the number
+of commits that are incldued in your local repository.
+
+We now have a ~/git-extensions/git-subtree directory containing code
+from the master branch of git://github.com/apenwarr/git-subtree.git
+in our git-extensions repository.
+
+EXAMPLE 2. Extract a subtree using commit, merge and pull
+---------------------------------------------------------
+Let's use the repository for the git source code as an example.
+First, get your own copy of the git.git repository:
+
+       $ git clone git://git.kernel.org/pub/scm/git/git.git test-git
+       $ cd test-git
+
+gitweb (commit 1130ef3) was merged into git as of commit
+0a8f4f0, after which it was no longer maintained separately. 
+But imagine it had been maintained separately, and we wanted to
+extract git's changes to gitweb since that time, to share with
+the upstream.  You could do this:
+
+       $ git subtree split --prefix=gitweb --annotate='(split) ' \
+               0a8f4f0^.. --onto=1130ef3 --rejoin \
+               --branch gitweb-latest
+        $ gitk gitweb-latest
+        $ git push git@github.com:whatever/gitweb.git gitweb-latest:master
+        
+(We use '0a8f4f0^..' because that means "all the changes from
+0a8f4f0 to the current version, including 0a8f4f0 itself.")
+
+If gitweb had originally been merged using 'git subtree add' (or
+a previous split had already been done with --rejoin specified)
+then you can do all your splits without having to remember any
+weird commit ids:
+
+       $ git subtree split --prefix=gitweb --annotate='(split) ' --rejoin \
+               --branch gitweb-latest2
+
+And you can merge changes back in from the upstream project just
+as easily:
+
+       $ git subtree pull --prefix=gitweb \
+               git@github.com:whatever/gitweb.git master
+
+Or, using '--squash', you can actually rewind to an earlier
+version of gitweb:
+
+       $ git subtree merge --prefix=gitweb --squash gitweb-latest~10
+
+Then make some changes:
+
+       $ date >gitweb/myfile
+       $ git add gitweb/myfile
+       $ git commit -m 'created myfile'
+
+And fast forward again:
+
+       $ git subtree merge --prefix=gitweb --squash gitweb-latest
+
+And notice that your change is still intact:
+       
+       $ ls -l gitweb/myfile
+
+And you can split it out and look at your changes versus
+the standard gitweb:
+
+       git log gitweb-latest..$(git subtree split --prefix=gitweb)
+
+EXAMPLE 3. Extract a subtree using branch
+-----------------------------------------
+Suppose you have a source directory with many files and
+subdirectories, and you want to extract the lib directory to its own
+git project. Here's a short way to do it:
+
+First, make the new repository wherever you want:
+
+       $ <go to the new location>
+       $ git init --bare
+
+Back in your original directory:
+
+       $ git subtree split --prefix=lib --annotate="(split)" -b split
+
+Then push the new branch onto the new empty repository:
+
+       $ git push <new-repo> split:master
+
+
+AUTHOR
+------
+Written by Avery Pennarun <apenwarr@gmail.com>
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile
new file mode 100644 (file)
index 0000000..c864810
--- /dev/null
@@ -0,0 +1,69 @@
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+#GIT_TEST_OPTS=--verbose --debug
+SHELL_PATH ?= $(SHELL)
+PERL_PATH ?= /usr/bin/perl
+TAR ?= $(TAR)
+RM ?= rm -f
+PROVE ?= prove
+DEFAULT_TEST_TARGET ?= test
+
+# Shell quote;
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+all: $(DEFAULT_TEST_TARGET)
+
+test: pre-clean $(TEST_LINT)
+       $(MAKE) aggregate-results-and-cleanup
+
+prove: pre-clean $(TEST_LINT)
+       @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
+       $(MAKE) clean
+
+$(T):
+       @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
+
+pre-clean:
+       $(RM) -r test-results
+
+clean:
+       $(RM) -r 'trash directory'.* test-results
+       $(RM) -r valgrind/bin
+       $(RM) .prove
+
+test-lint: test-lint-duplicates test-lint-executable
+
+test-lint-duplicates:
+       @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
+               test -z "$$dups" || { \
+               echo >&2 "duplicate test numbers:" $$dups; exit 1; }
+
+test-lint-executable:
+       @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \
+               test -z "$$bad" || { \
+               echo >&2 "non-executable tests:" $$bad; exit 1; }
+
+aggregate-results-and-cleanup: $(T)
+       $(MAKE) aggregate-results
+       $(MAKE) clean
+
+aggregate-results:
+       for f in ../../../t/test-results/t*-*.counts; do \
+               echo "$$f"; \
+       done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh
+
+valgrind:
+       $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
+
+test-results:
+       mkdir -p test-results
+
+.PHONY: pre-clean $(T) aggregate-results clean valgrind
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
new file mode 100755 (executable)
index 0000000..bc2eeb0
--- /dev/null
@@ -0,0 +1,508 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Avery Pennaraum
+#
+test_description='Basic porcelain support for subtrees
+
+This test verifies the basic operation of the merge, pull, add
+and split subcommands of git subtree.
+'
+
+export TEST_DIRECTORY=$(pwd)/../../../t
+
+. ../../../t/test-lib.sh
+
+create()
+{
+       echo "$1" >"$1"
+       git add "$1"
+}
+
+
+check_equal()
+{
+       test_debug 'echo'
+       test_debug "echo \"check a:\" \"{$1}\""
+       test_debug "echo \"      b:\" \"{$2}\""
+       if [ "$1" = "$2" ]; then
+               return 0
+       else
+               return 1
+       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~
+}
+
+last_commit_message()
+{
+       git log --pretty=format:%s -1
+}
+
+# 1
+test_expect_success 'init subproj' '
+        test_create_repo subproj
+'
+
+# To the subproject!
+cd subproj
+
+# 2
+test_expect_success 'add sub1' '
+        create sub1 &&
+        git commit -m "sub1" &&
+        git branch sub1 &&
+        git branch -m master subproj
+'
+
+# 3
+test_expect_success 'add sub2' '
+        create sub2 &&
+        git commit -m "sub2" &&
+        git branch sub2
+'
+
+# 4
+test_expect_success 'add sub3' '
+        create sub3 &&
+        git commit -m "sub3" &&
+        git branch sub3
+'
+
+# Back to mainline
+cd ..
+
+# 5
+test_expect_success 'add main4' '
+        create main4 &&
+        git commit -m "main4" &&
+        git branch -m master mainline &&
+        git branch subdir
+'
+
+# 6
+test_expect_success 'fetch subproj history' '
+        git fetch ./subproj sub1 &&
+        git branch sub1 FETCH_HEAD
+'
+
+# 7
+test_expect_success 'no subtree exists in main tree' '
+        test_must_fail git subtree merge --prefix=subdir sub1
+'
+
+# 8
+test_expect_success 'no pull from non-existant subtree' '
+        test_must_fail git subtree pull --prefix=subdir ./subproj sub1
+'
+
+# 9
+test_expect_success 'check if --message works for add' '
+        git subtree add --prefix=subdir --message="Added subproject" sub1 &&
+        check_equal ''"$(last_commit_message)"'' "Added subproject" &&
+        undo
+'
+
+# 10
+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 &&
+        check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
+        undo
+'
+
+# 11
+test_expect_success 'check if --message works with squash too' '
+        git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
+        check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
+        undo
+'
+
+# 12
+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)"'''"'"'"
+'
+
+# 13
+# this shouldn't actually do anything, since FETCH_HEAD is already a parent
+test_expect_success 'merge fetched subproj' '
+        git merge -m "merge -s -ours" -s ours FETCH_HEAD
+'
+
+# 14
+test_expect_success 'add main-sub5' '
+        create subdir/main-sub5 &&
+        git commit -m "main-sub5"
+'
+
+# 15
+test_expect_success 'add main6' '
+        create main6 &&
+        git commit -m "main6 boring"
+'
+
+# 16
+test_expect_success 'add main-sub7' '
+        create subdir/main-sub7 &&
+        git commit -m "main-sub7"
+'
+
+# 17
+test_expect_success 'fetch new subproj history' '
+        git fetch ./subproj sub2 &&
+        git branch sub2 FETCH_HEAD
+'
+
+# 18
+test_expect_success 'check if --message works for merge' '
+        git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
+        check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
+        undo
+'
+
+# 19
+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 &&
+        check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
+        undo
+'
+
+# 20
+test_expect_success 'merge new subproj history into subdir' '
+        git subtree merge --prefix=subdir FETCH_HEAD &&
+        git branch pre-split &&
+        check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline"
+'
+
+# 21
+test_expect_success 'Check that prefix argument is required for split' '
+        echo "You must provide the --prefix option." > expected &&
+        test_must_fail git subtree split > actual 2>&1 &&
+        test_debug "echo -n expected: " &&
+        test_debug "cat expected" &&
+        test_debug "echo -n actual: " &&
+        test_debug "cat actual" &&
+        test_cmp expected actual &&
+        rm -f expected actual
+'
+
+# 22
+test_expect_success 'Check that the <prefix> exists for a split' '
+        echo "'"'"'non-existent-directory'"'"'" does not exist\; use "'"'"'git subtree add'"'"'" > expected &&
+        test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 &&
+        test_debug "echo -n expected: " &&
+        test_debug "cat expected" &&
+        test_debug "echo -n actual: " &&
+        test_debug "cat actual" &&
+        test_cmp expected actual
+#        rm -f expected actual
+'
+
+# 23
+test_expect_success 'check if --message works for split+rejoin' '
+        spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+        git branch spl1 "$spl1" &&
+        check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
+        undo
+'
+
+# 24
+test_expect_success 'check split with --branch' '
+        spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
+        undo &&
+        git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
+        check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
+'
+
+# 25
+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)"'' &&
+        undo &&
+        git branch splitbr2 sub1 &&
+        git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
+        check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
+'
+
+# 26
+test_expect_success 'check split with --branch for an incompatible branch' '
+        test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
+'
+
+
+# 27
+test_expect_success 'check split+rejoin' '
+        spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --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"'"'"'"
+'
+
+# 28
+test_expect_success 'add main-sub8' '
+        create subdir/main-sub8 &&
+        git commit -m "main-sub8"
+'
+
+# To the subproject!
+cd ./subproj
+
+# 29
+test_expect_success 'merge split into subproj' '
+        git fetch .. spl1 &&
+        git branch spl1 FETCH_HEAD &&
+        git merge FETCH_HEAD
+'
+
+# 30
+test_expect_success 'add sub9' '
+        create sub9 &&
+        git commit -m "sub9"
+'
+
+# Back to mainline
+cd ..
+
+# 31
+test_expect_success 'split for sub8' '
+        split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"''
+        git branch split2 "$split2"
+'
+
+# 32
+test_expect_success 'add main-sub10' '
+        create subdir/main-sub10 &&
+        git commit -m "main-sub10"
+'
+
+# 33
+test_expect_success 'split for sub10' '
+        spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
+        git branch spl3 "$spl3"
+'
+
+# To the subproject!
+cd ./subproj
+
+# 34
+test_expect_success 'merge split into subproj' '
+        git fetch .. spl3 &&
+        git branch spl3 FETCH_HEAD &&
+        git merge FETCH_HEAD &&
+        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)
+
+# 35
+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"
+'
+
+# 36
+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"
+'
+
+# Back to mainline
+cd ..
+
+# 37
+test_expect_success 'pull from subproj' '
+        git fetch ./subproj subproj-merge-spl3 &&
+        git branch subproj-merge-spl3 FETCH_HEAD &&
+        git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
+'
+
+# 38
+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"
+'
+
+# 39
+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)"''
+'
+
+# 40
+test_expect_success 'make sure the --rejoin commits never make it into subproj' '
+        check_equal ''"$(git log --pretty=format:'"'%s'"' HEAD^2 | grep -i split)"'' ""
+'
+
+# 41
+test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' '
+        # They are meaningless to subproj since one side of the merge refers to the mainline
+        check_equal ''"$(git log --pretty=format:'"'%s%n%b'"' HEAD^2 | grep "git-subtree.*:")"'' ""
+'
+
+# prepare second pair of repositories
+mkdir test2
+cd test2
+
+# 42
+test_expect_success 'init main' '
+        test_create_repo main
+'
+
+cd main
+
+# 43
+test_expect_success 'add main1' '
+        create main1 &&
+        git commit -m "main1"
+'
+
+cd ..
+
+# 44
+test_expect_success 'init sub' '
+        test_create_repo sub
+'
+
+cd sub
+
+# 45
+test_expect_success 'add sub2' '
+        create sub2 &&
+        git commit -m "sub2"
+'
+
+cd ../main
+
+# check if split can find proper base without --onto
+
+# 46
+test_expect_success 'add sub as subdir in main' '
+        git fetch ../sub master &&
+        git branch sub2 FETCH_HEAD &&
+        git subtree add --prefix subdir sub2
+'
+
+cd ../sub
+
+# 47
+test_expect_success 'add sub3' '
+        create sub3 &&
+        git commit -m "sub3"
+'
+
+cd ../main
+
+# 48
+test_expect_success 'merge from sub' '
+        git fetch ../sub master &&
+        git branch sub3 FETCH_HEAD &&
+        git subtree merge --prefix subdir sub3
+'
+
+# 49
+test_expect_success 'add main-sub4' '
+        create subdir/main-sub4 &&
+        git commit -m "main-sub4"
+'
+
+# 50
+test_expect_success 'split for main-sub4 without --onto' '
+        git subtree split --prefix subdir --branch mainsub4
+'
+
+# at this point, the new commit parent should be sub3 if it is not,
+# something went wrong (the "newparent" of "master~" commit should
+# have been sub3, but it was not, because its cache was not set to
+# itself)
+
+# 51
+test_expect_success 'check that the commit parent is sub3' '
+        check_equal ''"$(git log --pretty=format:%P -1 mainsub4)"'' ''"$(git rev-parse sub3)"''
+'
+
+# 52
+test_expect_success 'add main-sub5' '
+        mkdir subdir2 &&
+        create subdir2/main-sub5 &&
+        git commit -m "main-sub5"
+'
+
+# 53
+test_expect_success 'split for main-sub5 without --onto' '
+        # also test that we still can split out an entirely new subtree
+        # if the parent of the first commit in the tree is not empty,
+        # then the new subtree has accidently been attached to something
+        git subtree split --prefix subdir2 --branch mainsub5 &&
+        check_equal ''"$(git log --pretty=format:%P -1 mainsub5)"'' ""
+'
+
+# make sure no patch changes more than one file.  The original set of commits
+# changed only one file each.  A multi-file change would imply that we pruned
+# commits too aggressively.
+joincommits()
+{
+       commit=
+       all=
+       while read x y; do
+               #echo "{$x}" >&2
+               if [ -z "$x" ]; then
+                       continue
+               elif [ "$x" = "commit:" ]; then
+                       if [ -n "$commit" ]; then
+                               echo "$commit $all"
+                               all=
+                       fi
+                       commit="$y"
+               else
+                       all="$all $y"
+               fi
+       done
+       echo "$commit $all"
+}
+
+# 54
+test_expect_success 'verify one file change per commit' '
+        x= &&
+        list=''"$(git log --pretty=format:'"'commit: %H'"' | joincommits)"'' &&
+#        test_debug "echo HERE" &&
+#        test_debug "echo ''"$list"''" &&
+        (git log --pretty=format:'"'commit: %H'"' | joincommits |
+        (       while read commit a b; do
+                       test_debug "echo Verifying commit "''"$commit"''
+                       test_debug "echo a: "''"$a"''
+                       test_debug "echo b: "''"$b"''
+                       check_equal "$b" ""
+                       x=1
+               done
+               check_equal "$x" 1
+        ))
+'
+
+test_done
diff --git a/contrib/subtree/todo b/contrib/subtree/todo
new file mode 100644 (file)
index 0000000..7e44b00
--- /dev/null
@@ -0,0 +1,50 @@
+
+       delete tempdir
+
+       'git subtree rejoin' option to do the same as --rejoin, eg. after a
+         rebase
+
+       --prefix doesn't force the subtree correctly in merge/pull:
+       "-s subtree" should be given an explicit subtree option?
+               There doesn't seem to be a way to do this.  We'd have to
+               patch git-merge-subtree.  Ugh.
+               (but we could avoid this problem by generating squashes with
+               exactly the right subtree structure, rather than using
+               subtree merge...)
+
+       add a 'push' subcommand to parallel 'pull'
+       
+       add a 'log' subcommand to see what's new in a subtree?
+
+       add to-submodule and from-submodule commands
+
+       automated tests for --squash stuff
+
+       "add" command non-obviously requires a commitid; would be easier if
+               it had a "pull" sort of mode instead
+
+       "pull" and "merge" commands should fail if you've never merged
+               that --prefix before
+               
+       docs should provide an example of "add"
+       
+       note that the initial split doesn't *have* to have a commitid
+               specified... that's just an optimization
+
+       if you try to add (or maybe merge?) with an invalid commitid, you
+               get a misleading "prefix must end with /" message from
+               one of the other git tools that git-subtree calls.  Should
+               detect this situation and print the *real* problem.
+       
+       "pull --squash" should do fetch-synthesize-merge, but instead just
+               does "pull" directly, which doesn't work at all.
+
+       make a 'force-update' that does what 'add' does even if the subtree
+               already exists.  That way we can help people who imported
+               subtrees "incorrectly" (eg. by just copying in the files) in
+               the past.
+
+       guess --prefix automatically if possible based on pwd
+
+       make a 'git subtree grafts' that automatically expands --squash'd
+               commits so you can see the full history if you want it.
diff --git a/date.c b/date.c
index a5055ca09dc1fafce2b9434c4fda02ad4f8e117f..1fdcf7c6eae0386552c5d7e069f5242fca3c2e0e 100644 (file)
--- a/date.c
+++ b/date.c
@@ -86,83 +86,98 @@ static int local_tzoffset(unsigned long time)
        return offset * eastwest;
 }
 
-const char *show_date_relative(unsigned long time, int tz,
+void show_date_relative(unsigned long time, int tz,
                               const struct timeval *now,
-                              char *timebuf,
-                              size_t timebuf_size)
+                              struct strbuf *timebuf)
 {
        unsigned long diff;
-       if (now->tv_sec < time)
-               return "in the future";
+       if (now->tv_sec < time) {
+               strbuf_addstr(timebuf, _("in the future"));
+               return;
+       }
        diff = now->tv_sec - time;
        if (diff < 90) {
-               snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);
-               return timebuf;
+               strbuf_addf(timebuf,
+                        Q_("%lu second ago", "%lu seconds ago", diff), diff);
+               return;
        }
        /* Turn it into minutes */
        diff = (diff + 30) / 60;
        if (diff < 90) {
-               snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);
-               return timebuf;
+               strbuf_addf(timebuf,
+                        Q_("%lu minute ago", "%lu minutes ago", diff), diff);
+               return;
        }
        /* Turn it into hours */
        diff = (diff + 30) / 60;
        if (diff < 36) {
-               snprintf(timebuf, timebuf_size, "%lu hours ago", diff);
-               return timebuf;
+               strbuf_addf(timebuf,
+                        Q_("%lu hour ago", "%lu hours ago", diff), diff);
+               return;
        }
        /* We deal with number of days from here on */
        diff = (diff + 12) / 24;
        if (diff < 14) {
-               snprintf(timebuf, timebuf_size, "%lu days ago", diff);
-               return timebuf;
+               strbuf_addf(timebuf,
+                        Q_("%lu day ago", "%lu days ago", diff), diff);
+               return;
        }
        /* Say weeks for the past 10 weeks or so */
        if (diff < 70) {
-               snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);
-               return timebuf;
+               strbuf_addf(timebuf,
+                        Q_("%lu week ago", "%lu weeks ago", (diff + 3) / 7),
+                        (diff + 3) / 7);
+               return;
        }
        /* Say months for the past 12 months or so */
        if (diff < 365) {
-               snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);
-               return timebuf;
+               strbuf_addf(timebuf,
+                        Q_("%lu month ago", "%lu months ago", (diff + 15) / 30),
+                        (diff + 15) / 30);
+               return;
        }
        /* Give years and months for 5 years or so */
        if (diff < 1825) {
                unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);
                unsigned long years = totalmonths / 12;
                unsigned long months = totalmonths % 12;
-               int n;
-               n = snprintf(timebuf, timebuf_size, "%lu year%s",
-                               years, (years > 1 ? "s" : ""));
-               if (months)
-                       snprintf(timebuf + n, timebuf_size - n,
-                                       ", %lu month%s ago",
-                                       months, (months > 1 ? "s" : ""));
-               else
-                       snprintf(timebuf + n, timebuf_size - n, " ago");
-               return timebuf;
+               if (months) {
+                       struct strbuf sb = STRBUF_INIT;
+                       strbuf_addf(&sb, Q_("%lu year", "%lu years", years), years);
+                       /* TRANSLATORS: "%s" is "<n> years" */
+                       strbuf_addf(timebuf,
+                                Q_("%s, %lu month ago", "%s, %lu months ago", months),
+                                sb.buf, months);
+                       strbuf_release(&sb);
+               } else
+                       strbuf_addf(timebuf,
+                                Q_("%lu year ago", "%lu years ago", years), years);
+               return;
        }
        /* Otherwise, just years. Centuries is probably overkill. */
-       snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);
-       return timebuf;
+       strbuf_addf(timebuf,
+                Q_("%lu year ago", "%lu years ago", (diff + 183) / 365),
+                (diff + 183) / 365);
 }
 
 const char *show_date(unsigned long time, int tz, enum date_mode mode)
 {
        struct tm *tm;
-       static char timebuf[200];
+       static struct strbuf timebuf = STRBUF_INIT;
 
        if (mode == DATE_RAW) {
-               snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
-               return timebuf;
+               strbuf_reset(&timebuf);
+               strbuf_addf(&timebuf, "%lu %+05d", time, tz);
+               return timebuf.buf;
        }
 
        if (mode == DATE_RELATIVE) {
                struct timeval now;
+
+               strbuf_reset(&timebuf);
                gettimeofday(&now, NULL);
-               return show_date_relative(time, tz, &now,
-                                         timebuf, sizeof(timebuf));
+               show_date_relative(time, tz, &now, &timebuf);
+               return timebuf.buf;
        }
 
        if (mode == DATE_LOCAL)
@@ -171,23 +186,25 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        tm = time_to_tm(time, tz);
        if (!tm)
                return NULL;
+
+       strbuf_reset(&timebuf);
        if (mode == DATE_SHORT)
-               sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
+               strbuf_addf(&timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
                                tm->tm_mon + 1, tm->tm_mday);
        else if (mode == DATE_ISO8601)
-               sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
+               strbuf_addf(&timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
                                tm->tm_year + 1900,
                                tm->tm_mon + 1,
                                tm->tm_mday,
                                tm->tm_hour, tm->tm_min, tm->tm_sec,
                                tz);
        else if (mode == DATE_RFC2822)
-               sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+               strbuf_addf(&timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
                        weekday_names[tm->tm_wday], tm->tm_mday,
                        month_names[tm->tm_mon], tm->tm_year + 1900,
                        tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
        else
-               sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
+               strbuf_addf(&timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
                                weekday_names[tm->tm_wday],
                                month_names[tm->tm_mon],
                                tm->tm_mday,
@@ -195,7 +212,7 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
                                tm->tm_year + 1900,
                                (mode == DATE_LOCAL) ? 0 : ' ',
                                tz);
-       return timebuf;
+       return timebuf.buf;
 }
 
 /*
diff --git a/diff.c b/diff.c
index cd029b33d536a36bc5797821848d25fa98fe7d8f..77edd5086fff30af36e92a151377c285de21aca3 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1443,8 +1443,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 {
        int i, len, add, del, adds = 0, dels = 0;
        uintmax_t max_change = 0, max_len = 0;
-       int total_files = data->nr;
-       int width, name_width, graph_width, number_width = 4, count;
+       int total_files = data->nr, count;
+       int width, name_width, graph_width, number_width = 0, bin_width = 0;
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
        int extra_shown = 0;
@@ -1480,8 +1480,21 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                if (max_len < len)
                        max_len = len;
 
-               if (file->is_binary || file->is_unmerged)
+               if (file->is_unmerged) {
+                       /* "Unmerged" is 8 characters */
+                       bin_width = bin_width < 8 ? 8 : bin_width;
                        continue;
+               }
+               if (file->is_binary) {
+                       /* "Bin XXX -> YYY bytes" */
+                       int w = 14 + decimal_width(file->added)
+                               + decimal_width(file->deleted);
+                       bin_width = bin_width < w ? w : bin_width;
+                       /* Display change counts aligned with "Bin" */
+                       number_width = 3;
+                       continue;
+               }
+
                if (max_change < change)
                        max_change = change;
        }
@@ -1506,12 +1519,22 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
         * stat_name_width fixes the maximum width of the filename,
         * and is also used to divide available columns if there
         * aren't enough.
+        *
+        * Binary files are displayed with "Bin XXX -> YYY bytes"
+        * instead of the change count and graph. This part is treated
+        * similarly to the graph part, except that it is not
+        * "scaled". If total width is too small to accomodate the
+        * guaranteed minimum width of the filename part and the
+        * separators and this message, this message will "overflow"
+        * making the line longer than the maximum width.
         */
 
        if (options->stat_width == -1)
-               width = term_columns();
+               width = term_columns() - options->output_prefix_length;
        else
                width = options->stat_width ? options->stat_width : 80;
+       number_width = decimal_width(max_change) > number_width ?
+               decimal_width(max_change) : number_width;
 
        if (options->stat_graph_width == -1)
                options->stat_graph_width = diff_stat_graph_width;
@@ -1525,10 +1548,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 
        /*
         * First assign sizes that are wanted, ignoring available width.
+        * strlen("Bin XXX -> YYY bytes") == bin_width, and the part
+        * starting from "XXX" should fit in graph_width.
         */
-       graph_width = (options->stat_graph_width &&
-                      options->stat_graph_width < max_change) ?
-               options->stat_graph_width : max_change;
+       graph_width = max_change + 4 > bin_width ? max_change : bin_width - 4;
+       if (options->stat_graph_width &&
+           options->stat_graph_width < graph_width)
+               graph_width = options->stat_graph_width;
+
        name_width = (options->stat_name_width > 0 &&
                      options->stat_name_width < max_len) ?
                options->stat_name_width : max_len;
@@ -1537,8 +1564,12 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
         * Adjust adjustable widths not to exceed maximum width
         */
        if (name_width + number_width + 6 + graph_width > width) {
-               if (graph_width > width * 3/8 - number_width - 6)
+               if (graph_width > width * 3/8 - number_width - 6) {
                        graph_width = width * 3/8 - number_width - 6;
+                       if (graph_width < 6)
+                               graph_width = 6;
+               }
+
                if (options->stat_graph_width &&
                    graph_width > options->stat_graph_width)
                        graph_width = options->stat_graph_width;
@@ -1583,8 +1614,12 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                if (data->files[i]->is_binary) {
                        fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
-                       fprintf(options->file, "  Bin ");
-                       fprintf(options->file, "%s%"PRIuMAX"%s",
+                       fprintf(options->file, " %*s", number_width, "Bin");
+                       if (!added && !deleted) {
+                               putc('\n', options->file);
+                               continue;
+                       }
+                       fprintf(options->file, " %s%"PRIuMAX"%s",
                                del_c, deleted, reset);
                        fprintf(options->file, " -> ");
                        fprintf(options->file, "%s%"PRIuMAX"%s",
@@ -1596,7 +1631,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                else if (data->files[i]->is_unmerged) {
                        fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
-                       fprintf(options->file, "  Unmerged\n");
+                       fprintf(options->file, " Unmerged\n");
                        continue;
                }
 
@@ -1625,8 +1660,9 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                }
                fprintf(options->file, "%s", line_prefix);
                show_name(options->file, prefix, name, len);
-               fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
-                               added + deleted ? " " : "");
+               fprintf(options->file, " %*"PRIuMAX"%s",
+                       number_width, added + deleted,
+                       added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
@@ -1657,17 +1693,16 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
                return;
 
        for (i = 0; i < data->nr; i++) {
-               if (!data->files[i]->is_binary &&
-                   !data->files[i]->is_unmerged) {
-                       int added = data->files[i]->added;
-                       int deleted= data->files[i]->deleted;
-                       if (!data->files[i]->is_renamed &&
-                           (added + deleted == 0)) {
-                               total_files--;
-                       } else {
-                               adds += added;
-                               dels += deleted;
-                       }
+               int added = data->files[i]->added;
+               int deleted= data->files[i]->deleted;
+
+               if (data->files[i]->is_unmerged)
+                       continue;
+               if (!data->files[i]->is_renamed && (added + deleted == 0)) {
+                       total_files--;
+               } else {
+                       adds += added;
+                       dels += deleted;
                }
        }
        if (options->output_prefix) {
@@ -2367,6 +2402,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
 {
        mmfile_t mf1, mf2;
        struct diffstat_file *data;
+       int same_contents;
 
        data = diffstat_add(diffstat, name_a, name_b);
 
@@ -2375,10 +2411,17 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                return;
        }
 
+       same_contents = !hashcmp(one->sha1, two->sha1);
+
        if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
                data->is_binary = 1;
-               data->added = diff_filespec_size(two);
-               data->deleted = diff_filespec_size(one);
+               if (same_contents) {
+                       data->added = 0;
+                       data->deleted = 0;
+               } else {
+                       data->added = diff_filespec_size(two);
+                       data->deleted = diff_filespec_size(one);
+               }
        }
 
        else if (complete_rewrite) {
@@ -2388,7 +2431,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                data->added = count_lines(two->data, two->size);
        }
 
-       else {
+       else if (!same_contents) {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
@@ -3540,9 +3583,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--patience"))
-               DIFF_XDL_SET(options, PATIENCE_DIFF);
+               options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
        else if (!strcmp(arg, "--histogram"))
-               DIFF_XDL_SET(options, HISTOGRAM_DIFF);
+               options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
diff --git a/diff.h b/diff.h
index dd48eca71ddf1a8709135f158520c8bb7cd5f8d1..e027650cb0ff2651e2e890e7f00753c15f5b3cff 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -91,6 +91,8 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_XDL_SET(opts, flag)    ((opts)->xdl_opts |= XDF_##flag)
 #define DIFF_XDL_CLR(opts, flag)    ((opts)->xdl_opts &= ~XDF_##flag)
 
+#define DIFF_WITH_ALG(opts, flag)   (((opts)->xdl_opts & ~XDF_DIFF_ALGORITHM_MASK) | XDF_##flag)
+
 enum diff_words_type {
        DIFF_WORDS_NONE = 0,
        DIFF_WORDS_PORCELAIN,
@@ -150,6 +152,7 @@ struct diff_options {
        diff_format_fn_t format_callback;
        void *format_callback_data;
        diff_prefix_fn_t output_prefix;
+       int output_prefix_length;
        void *output_prefix_data;
 };
 
diff --git a/entry.c b/entry.c
index 852fea13955475c1e2fda9cfc25a63a54a1f61c7..17a6bccec64e0e523aacc124611c43bd818372e3 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -120,58 +120,15 @@ static int streaming_write_entry(struct cache_entry *ce, char *path,
                                 const struct checkout *state, int to_tempfile,
                                 int *fstat_done, struct stat *statbuf)
 {
-       struct git_istream *st;
-       enum object_type type;
-       unsigned long sz;
        int result = -1;
-       ssize_t kept = 0;
-       int fd = -1;
-
-       st = open_istream(ce->sha1, &type, &sz, filter);
-       if (!st)
-               return -1;
-       if (type != OBJ_BLOB)
-               goto close_and_exit;
+       int fd;
 
        fd = open_output_fd(path, ce, to_tempfile);
-       if (fd < 0)
-               goto close_and_exit;
-
-       for (;;) {
-               char buf[1024 * 16];
-               ssize_t wrote, holeto;
-               ssize_t readlen = read_istream(st, buf, sizeof(buf));
-
-               if (!readlen)
-                       break;
-               if (sizeof(buf) == readlen) {
-                       for (holeto = 0; holeto < readlen; holeto++)
-                               if (buf[holeto])
-                                       break;
-                       if (readlen == holeto) {
-                               kept += holeto;
-                               continue;
-                       }
-               }
-
-               if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
-                       goto close_and_exit;
-               else
-                       kept = 0;
-               wrote = write_in_full(fd, buf, readlen);
-
-               if (wrote != readlen)
-                       goto close_and_exit;
-       }
-       if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
-                    write(fd, "", 1) != 1))
-               goto close_and_exit;
-       *fstat_done = fstat_output(fd, state, statbuf);
-
-close_and_exit:
-       close_istream(st);
-       if (0 <= fd)
+       if (0 <= fd) {
+               result = stream_blob_to_fd(fd, ce->sha1, filter, 1);
+               *fstat_done = fstat_output(fd, state, statbuf);
                result = close(fd);
+       }
        if (result && 0 <= fd)
                unlink(path);
        return result;
index c93b8f44df0171a0f923546813d00e8b8e837af1..d7e6c657631f05553250a1705ea6c77c375c4bf4 100644 (file)
@@ -52,7 +52,7 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
 enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
 enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
-enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
+enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
 #ifndef OBJECT_CREATION_MODE
 #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
 #endif
diff --git a/fsck.c b/fsck.c
index 6c855f84f01c19678399d85181da1094bd61b371..4c63b2cc41eec4f568ee6f0d18a51c97304d5d96 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -27,7 +27,7 @@ static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data)
                else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode))
                        result = walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data);
                else {
-                       result = error("in tree %s: entry %s has bad mode %.6o\n",
+                       result = error("in tree %s: entry %s has bad mode %.6o",
                                        sha1_to_hex(tree->object.sha1), entry.path, entry.mode);
                }
                if (result < 0)
index 1093ef4ad6b9793fb829403c41391a09a07e57a2..9a4c9b94e61c6a310b69e546c7890a676a3fb96f 100755 (executable)
@@ -16,7 +16,7 @@ do
      /^NAME/,/git-'"$cmd"'/H
      ${
            x
-           s/.*git-'"$cmd"' - \(.*\)/  {"'"$cmd"'", "\1"},/
+           s/.*git-'"$cmd"' - \(.*\)/  {"'"$cmd"'", N_("\1")},/
            p
      }' "Documentation/git-$cmd.txt"
 done
index 4da0ddafc4bf2029823148e7285085661c134497..f8b7a0cb602d2d2425f68f8c27338cc003b70f6b 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -24,6 +24,7 @@ ignore-space-change pass it through git-apply
 ignore-whitespace pass it through git-apply
 directory=      pass it through git-apply
 exclude=        pass it through git-apply
+include=        pass it through git-apply
 C=              pass it through git-apply
 p=              pass it through git-apply
 patch-format=   format the patch(es) are in
@@ -138,6 +139,12 @@ fall_back_3way () {
     say Using index info to reconstruct a base tree...
 
     cmd='GIT_INDEX_FILE="$dotest/patch-merge-tmp-index"'
+
+    if test -z "$GIT_QUIET"
+    then
+       eval "$cmd git diff-index --cached --diff-filter=AM --name-status HEAD"
+    fi
+
     cmd="$cmd git apply --cached $git_apply_opt"' <"$dotest/patch"'
     if eval "$cmd"
     then
@@ -412,7 +419,7 @@ do
                ;;
        --resolvemsg)
                shift; resolvemsg=$1 ;;
-       --whitespace|--directory|--exclude)
+       --whitespace|--directory|--exclude|--include)
                git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;;
        -C|-p)
                git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
index e6558d101062929241cc30cd0272556c3164310b..3d0fe0cd93b393a5f376aa10505e927fbed37f8f 100755 (executable)
@@ -73,9 +73,16 @@ then
        fi
 fi
 
-# Launch the merge tool on each path provided by 'git diff'
-while test $# -gt 6
-do
-       launch_merge_tool "$1" "$2" "$5"
-       shift 7
-done
+if test -n "$GIT_DIFFTOOL_DIRDIFF"
+then
+       LOCAL="$1"
+       REMOTE="$2"
+       run_merge_tool "$merge_tool" false
+else
+       # Launch the merge tool on each path provided by 'git diff'
+       while test $# -gt 6
+       do
+               launch_merge_tool "$1" "$2" "$5"
+               shift 7
+       done
+fi
index 09b65f1770c09824df5543d208d1df0e9a60d831..ae1e0525d89181f8adbe288b62e107b8dadb8ca1 100755 (executable)
-#!/usr/bin/env perl
+#!/usr/bin/perl
 # Copyright (c) 2009, 2010 David Aguilar
+# Copyright (c) 2012 Tim Henigan
 #
 # This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
 # git-difftool--helper script.
 #
 # This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
-# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL
-# are exported for use by git-difftool--helper.
+# The GIT_DIFF* variables are exported for use by git-difftool--helper.
 #
 # Any arguments that are unknown to this script are forwarded to 'git diff'.
 
 use 5.008;
 use strict;
 use warnings;
-use Cwd qw(abs_path);
 use File::Basename qw(dirname);
+use File::Copy;
+use File::Find;
+use File::stat;
+use File::Path qw(mkpath);
+use File::Temp qw(tempdir);
+use Getopt::Long qw(:config pass_through);
+use Git;
 
-require Git;
-
-my $DIR = abs_path(dirname($0));
-
+my @tools;
+my @working_tree;
+my $rc;
+my $repo = Git->repository();
+my $repo_path = $repo->repo_path();
 
 sub usage
 {
+       my $exitcode = shift;
        print << 'USAGE';
-usage: git difftool [-t|--tool=<tool>] [-x|--extcmd=<cmd>]
-                    [-y|--no-prompt]   [-g|--gui]
+usage: git difftool [-t|--tool=<tool>] [--tool-help]
+                    [-x|--extcmd=<cmd>]
+                    [-g|--gui] [--no-gui]
+                    [--prompt] [-y|--no-prompt]
+                    [-d|--dir-diff]
                     ['git diff' options]
 USAGE
-       exit 1;
+       exit($exitcode);
 }
 
-sub setup_environment
+sub find_worktree
 {
-       $ENV{PATH} = "$DIR:$ENV{PATH}";
-       $ENV{GIT_PAGER} = '';
-       $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
+       # Git->repository->wc_path() does not honor changes to the working
+       # tree location made by $ENV{GIT_WORK_TREE} or the 'core.worktree'
+       # config variable.
+       my $worktree;
+       my $env_worktree = $ENV{GIT_WORK_TREE};
+       my $core_worktree = Git::config('core.worktree');
+
+       if (defined($env_worktree) and (length($env_worktree) > 0)) {
+               $worktree = $env_worktree;
+       } elsif (defined($core_worktree) and (length($core_worktree) > 0)) {
+               $worktree = $core_worktree;
+       } else {
+               $worktree = $repo->wc_path();
+       }
+
+       return $worktree;
 }
 
-sub exe
+my $workdir = find_worktree();
+
+sub filter_tool_scripts
 {
-       my $exe = shift;
-       if ($^O eq 'MSWin32' || $^O eq 'msys') {
-               return "$exe.exe";
+       if (-d $_) {
+               if ($_ ne ".") {
+                       # Ignore files in subdirectories
+                       $File::Find::prune = 1;
+               }
+       } else {
+               if ((-f $_) && ($_ ne "defaults")) {
+                       push(@tools, $_);
+               }
        }
-       return $exe;
 }
 
-sub generate_command
+sub print_tool_help
 {
-       my @command = (exe('git'), 'diff');
-       my $skip_next = 0;
-       my $idx = -1;
-       my $prompt = '';
-       for my $arg (@ARGV) {
-               $idx++;
-               if ($skip_next) {
-                       $skip_next = 0;
-                       next;
+       my ($cmd, @found, @notfound);
+       my $gitpath = Git::exec_path();
+
+       find(\&filter_tool_scripts, "$gitpath/mergetools");
+
+       foreach my $tool (@tools) {
+               $cmd  = "TOOL_MODE=diff";
+               $cmd .= ' && . "$(git --exec-path)/git-mergetool--lib"';
+               $cmd .= " && get_merge_tool_path $tool >/dev/null 2>&1";
+               $cmd .= " && can_diff >/dev/null 2>&1";
+               if (system('sh', '-c', $cmd) == 0) {
+                       push(@found, $tool);
+               } else {
+                       push(@notfound, $tool);
                }
-               if ($arg eq '-t' || $arg eq '--tool') {
-                       usage() if $#ARGV <= $idx;
-                       $ENV{GIT_DIFF_TOOL} = $ARGV[$idx + 1];
-                       $skip_next = 1;
-                       next;
+       }
+
+       print "'git difftool --tool=<tool>' may be set to one of the following:\n";
+       print "\t$_\n" for (sort(@found));
+
+       print "\nThe following tools are valid, but not currently available:\n";
+       print "\t$_\n" for (sort(@notfound));
+
+       print "\nNOTE: Some of the tools listed above only work in a windowed\n";
+       print "environment. If run in a terminal-only session, they will fail.\n";
+
+       exit(0);
+}
+
+sub setup_dir_diff
+{
+       # Run the diff; exit immediately if no diff found
+       # 'Repository' and 'WorkingCopy' must be explicitly set to insure that
+       # if $GIT_DIR and $GIT_WORK_TREE are set in ENV, they are actually used
+       # by Git->repository->command*.
+       my $diffrepo = Git->repository(Repository => $repo_path, WorkingCopy => $workdir);
+       my $diffrtn = $diffrepo->command_oneline('diff', '--raw', '--no-abbrev', '-z', @ARGV);
+       exit(0) if (length($diffrtn) == 0);
+
+       # Setup temp directories
+       my $tmpdir = tempdir('git-diffall.XXXXX', CLEANUP => 1, TMPDIR => 1);
+       my $ldir = "$tmpdir/left";
+       my $rdir = "$tmpdir/right";
+       mkpath($ldir) or die $!;
+       mkpath($rdir) or die $!;
+
+       # Build index info for left and right sides of the diff
+       my $submodule_mode = '160000';
+       my $symlink_mode = '120000';
+       my $null_mode = '0' x 6;
+       my $null_sha1 = '0' x 40;
+       my $lindex = '';
+       my $rindex = '';
+       my %submodule;
+       my %symlink;
+       my @rawdiff = split('\0', $diffrtn);
+
+       my $i = 0;
+       while ($i < $#rawdiff) {
+               if ($rawdiff[$i] =~ /^::/) {
+                       print "Combined diff formats ('-c' and '--cc') are not supported in directory diff mode.\n";
+                       exit(1);
                }
-               if ($arg =~ /^--tool=/) {
-                       $ENV{GIT_DIFF_TOOL} = substr($arg, 7);
-                       next;
+
+               my ($lmode, $rmode, $lsha1, $rsha1, $status) = split(' ', substr($rawdiff[$i], 1));
+               my $src_path = $rawdiff[$i + 1];
+               my $dst_path;
+
+               if ($status =~ /^[CR]/) {
+                       $dst_path = $rawdiff[$i + 2];
+                       $i += 3;
+               } else {
+                       $dst_path = $src_path;
+                       $i += 2;
                }
-               if ($arg eq '-x' || $arg eq '--extcmd') {
-                       usage() if $#ARGV <= $idx;
-                       $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1];
-                       $skip_next = 1;
+
+               if (($lmode eq $submodule_mode) or ($rmode eq $submodule_mode)) {
+                       $submodule{$src_path}{left} = $lsha1;
+                       if ($lsha1 ne $rsha1) {
+                               $submodule{$dst_path}{right} = $rsha1;
+                       } else {
+                               $submodule{$dst_path}{right} = "$rsha1-dirty";
+                       }
                        next;
                }
-               if ($arg =~ /^--extcmd=/) {
-                       $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9);
-                       next;
+
+               if ($lmode eq $symlink_mode) {
+                       $symlink{$src_path}{left} = $diffrepo->command_oneline('show', "$lsha1");
                }
-               if ($arg eq '-g' || $arg eq '--gui') {
-                       eval {
-                               my $tool = Git::command_oneline('config',
-                                                               'diff.guitool');
-                               if (length($tool)) {
-                                       $ENV{GIT_DIFF_TOOL} = $tool;
-                               }
-                       };
-                       next;
+
+               if ($rmode eq $symlink_mode) {
+                       $symlink{$dst_path}{right} = $diffrepo->command_oneline('show', "$rsha1");
                }
-               if ($arg eq '-y' || $arg eq '--no-prompt') {
-                       $prompt = 'no';
-                       next;
+
+               if (($lmode ne $null_mode) and ($status !~ /^C/)) {
+                       $lindex .= "$lmode $lsha1\t$src_path\0";
                }
-               if ($arg eq '--prompt') {
-                       $prompt = 'yes';
-                       next;
+
+               if ($rmode ne $null_mode) {
+                       if ($rsha1 ne $null_sha1) {
+                               $rindex .= "$rmode $rsha1\t$dst_path\0";
+                       } else {
+                               push(@working_tree, $dst_path);
+                       }
                }
-               if ($arg eq '-h') {
-                       usage();
+       }
+
+       # If $GIT_DIR is not set prior to calling 'git update-index' and
+       # 'git checkout-index', then those commands will fail if difftool
+       # is called from a directory other than the repo root.
+       my $must_unset_git_dir = 0;
+       if (not defined($ENV{GIT_DIR})) {
+               $must_unset_git_dir = 1;
+               $ENV{GIT_DIR} = $repo_path;
+       }
+
+       # Populate the left and right directories based on each index file
+       my ($inpipe, $ctx);
+       $ENV{GIT_INDEX_FILE} = "$tmpdir/lindex";
+       ($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);
+       print($inpipe $lindex);
+       $repo->command_close_pipe($inpipe, $ctx);
+       $rc = system('git', 'checkout-index', '--all', "--prefix=$ldir/");
+       exit($rc | ($rc >> 8)) if ($rc != 0);
+
+       $ENV{GIT_INDEX_FILE} = "$tmpdir/rindex";
+       ($inpipe, $ctx) = $repo->command_input_pipe(qw/update-index -z --index-info/);
+       print($inpipe $rindex);
+       $repo->command_close_pipe($inpipe, $ctx);
+       $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/");
+       exit($rc | ($rc >> 8)) if ($rc != 0);
+
+       # If $GIT_DIR was explicitly set just for the update/checkout
+       # commands, then it should be unset before continuing.
+       delete($ENV{GIT_DIR}) if ($must_unset_git_dir);
+       delete($ENV{GIT_INDEX_FILE});
+
+       # Changes in the working tree need special treatment since they are
+       # not part of the index
+       for my $file (@working_tree) {
+               my $dir = dirname($file);
+               unless (-d "$rdir/$dir") {
+                       mkpath("$rdir/$dir") or die $!;
+               }
+               copy("$workdir/$file", "$rdir/$file") or die $!;
+               chmod(stat("$workdir/$file")->mode, "$rdir/$file") or die $!;
+       }
+
+       # Changes to submodules require special treatment. This loop writes a
+       # temporary file to both the left and right directories to show the
+       # change in the recorded SHA1 for the submodule.
+       for my $path (keys %submodule) {
+               if (defined($submodule{$path}{left})) {
+                       write_to_file("$ldir/$path", "Subproject commit $submodule{$path}{left}");
+               }
+               if (defined($submodule{$path}{right})) {
+                       write_to_file("$rdir/$path", "Subproject commit $submodule{$path}{right}");
+               }
+       }
+
+       # Symbolic links require special treatment. The standard "git diff"
+       # shows only the link itself, not the contents of the link target.
+       # This loop replicates that behavior.
+       for my $path (keys %symlink) {
+               if (defined($symlink{$path}{left})) {
+                       write_to_file("$ldir/$path", $symlink{$path}{left});
+               }
+               if (defined($symlink{$path}{right})) {
+                       write_to_file("$rdir/$path", $symlink{$path}{right});
                }
-               push @command, $arg;
        }
-       if ($prompt eq 'yes') {
-               $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
-       } elsif ($prompt eq 'no') {
-               $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+
+       return ($ldir, $rdir);
+}
+
+sub write_to_file
+{
+       my $path = shift;
+       my $value = shift;
+
+       # Make sure the path to the file exists
+       my $dir = dirname($path);
+       unless (-d "$dir") {
+               mkpath("$dir") or die $!;
        }
-       return @command
+
+       # If the file already exists in that location, delete it.  This
+       # is required in the case of symbolic links.
+       unlink("$path");
+
+       open(my $fh, '>', "$path") or die $!;
+       print($fh $value);
+       close($fh);
 }
 
-setup_environment();
+# parse command-line options. all unrecognized options and arguments
+# are passed through to the 'git diff' command.
+my ($difftool_cmd, $dirdiff, $extcmd, $gui, $help, $prompt, $tool_help);
+GetOptions('g|gui!' => \$gui,
+       'd|dir-diff' => \$dirdiff,
+       'h' => \$help,
+       'prompt!' => \$prompt,
+       'y' => sub { $prompt = 0; },
+       't|tool:s' => \$difftool_cmd,
+       'tool-help' => \$tool_help,
+       'x|extcmd:s' => \$extcmd);
 
-# ActiveState Perl for Win32 does not implement POSIX semantics of
-# exec* system call. It just spawns the given executable and finishes
-# the starting program, exiting with code 0.
-# system will at least catch the errors returned by git diff,
-# allowing the caller of git difftool better handling of failures.
-my $rc = system(generate_command());
-exit($rc | ($rc >> 8));
+if (defined($help)) {
+       usage(0);
+}
+if (defined($tool_help)) {
+       print_tool_help();
+}
+if (defined($difftool_cmd)) {
+       if (length($difftool_cmd) > 0) {
+               $ENV{GIT_DIFF_TOOL} = $difftool_cmd;
+       } else {
+               print "No <tool> given for --tool=<tool>\n";
+               usage(1);
+       }
+}
+if (defined($extcmd)) {
+       if (length($extcmd) > 0) {
+               $ENV{GIT_DIFFTOOL_EXTCMD} = $extcmd;
+       } else {
+               print "No <cmd> given for --extcmd=<cmd>\n";
+               usage(1);
+       }
+}
+if ($gui) {
+       my $guitool = '';
+       $guitool = Git::config('diff.guitool');
+       if (length($guitool) > 0) {
+               $ENV{GIT_DIFF_TOOL} = $guitool;
+       }
+}
+
+# In directory diff mode, 'git-difftool--helper' is called once
+# to compare the a/b directories.  In file diff mode, 'git diff'
+# will invoke a separate instance of 'git-difftool--helper' for
+# each file that changed.
+if (defined($dirdiff)) {
+       my ($a, $b) = setup_dir_diff();
+       if (defined($extcmd)) {
+               $rc = system($extcmd, $a, $b);
+       } else {
+               $ENV{GIT_DIFFTOOL_DIRDIFF} = 'true';
+               $rc = system('git', 'difftool--helper', $a, $b);
+       }
+
+       exit($rc | ($rc >> 8)) if ($rc != 0);
+
+       # If the diff including working copy files and those
+       # files were modified during the diff, then the changes
+       # should be copied back to the working tree
+       for my $file (@working_tree) {
+               copy("$b/$file", "$workdir/$file") or die $!;
+               chmod(stat("$b/$file")->mode, "$workdir/$file") or die $!;
+       }
+} else {
+       if (defined($prompt)) {
+               if ($prompt) {
+                       $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
+               } else {
+                       $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+               }
+       }
+
+       $ENV{GIT_PAGER} = '';
+       $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool--helper';
+
+       # ActiveState Perl for Win32 does not implement POSIX semantics of
+       # exec* system call. It just spawns the given executable and finishes
+       # the starting program, exiting with code 0.
+       # system will at least catch the errors returned by git diff,
+       # allowing the caller of git difftool better handling of failures.
+       my $rc = system('git', 'diff', @ARGV);
+       exit($rc | ($rc >> 8));
+}
similarity index 88%
rename from contrib/fast-import/git-p4
rename to git-p4.py
index c5362c4c11d00e169781ff7da89c88a3051f6227..565cfbcc78aac31f3e799967c0746a41063bf501 100755 (executable)
+++ b/git-p4.py
@@ -14,6 +14,8 @@ import re, shutil
 
 verbose = False
 
+# Only labels/tags matching this will be imported/exported
+defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
 
 def p4_build_cmd(cmd):
     """Build a suitable p4 command line.
@@ -131,25 +133,29 @@ def p4_system(cmd):
     subprocess.check_call(real_cmd, shell=expand)
 
 def p4_integrate(src, dest):
-    p4_system(["integrate", "-Dt", src, dest])
+    p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
 
-def p4_sync(path):
-    p4_system(["sync", path])
+def p4_sync(f, *options):
+    p4_system(["sync"] + list(options) + [wildcard_encode(f)])
 
 def p4_add(f):
-    p4_system(["add", f])
+    # forcibly add file names with wildcards
+    if wildcard_present(f):
+        p4_system(["add", "-f", f])
+    else:
+        p4_system(["add", f])
 
 def p4_delete(f):
-    p4_system(["delete", f])
+    p4_system(["delete", wildcard_encode(f)])
 
 def p4_edit(f):
-    p4_system(["edit", f])
+    p4_system(["edit", wildcard_encode(f)])
 
 def p4_revert(f):
-    p4_system(["revert", f])
+    p4_system(["revert", wildcard_encode(f)])
 
-def p4_reopen(type, file):
-    p4_system(["reopen", "-t", type, file])
+def p4_reopen(type, f):
+    p4_system(["reopen", "-t", type, wildcard_encode(f)])
 
 #
 # Canonicalize the p4 type and return a tuple of the
@@ -246,13 +252,33 @@ def setP4ExecBit(file, mode):
 def getP4OpenedType(file):
     # Returns the perforce file type for the given file.
 
-    result = p4_read_pipe(["opened", file])
+    result = p4_read_pipe(["opened", wildcard_encode(file)])
     match = re.match(".*\((.+)\)\r?$", result)
     if match:
         return match.group(1)
     else:
         die("Could not determine file type for %s (result: '%s')" % (file, result))
 
+# Return the set of all p4 labels
+def getP4Labels(depotPaths):
+    labels = set()
+    if isinstance(depotPaths,basestring):
+        depotPaths = [depotPaths]
+
+    for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
+        label = l['label']
+        labels.add(label)
+
+    return labels
+
+# Return the set of all git tags
+def getGitTags():
+    gitTags = set()
+    for line in read_pipe_lines(["git", "tag"]):
+        tag = line.strip()
+        gitTags.add(tag)
+    return gitTags
+
 def diffTreePattern():
     # This is a simple generator for the diff tree regex pattern. This could be
     # a class variable if this and parseDiffTreeEntry were a part of a class.
@@ -636,10 +662,39 @@ def getClientRoot():
 
     return entry["Root"]
 
+#
+# P4 wildcards are not allowed in filenames.  P4 complains
+# if you simply add them, but you can force it with "-f", in
+# which case it translates them into %xx encoding internally.
+#
+def wildcard_decode(path):
+    # Search for and fix just these four characters.  Do % last so
+    # that fixing it does not inadvertently create new %-escapes.
+    # Cannot have * in a filename in windows; untested as to
+    # what p4 would do in such a case.
+    if not platform.system() == "Windows":
+        path = path.replace("%2A", "*")
+    path = path.replace("%23", "#") \
+               .replace("%40", "@") \
+               .replace("%25", "%")
+    return path
+
+def wildcard_encode(path):
+    # do % first to avoid double-encoding the %s introduced here
+    path = path.replace("%", "%25") \
+               .replace("*", "%2A") \
+               .replace("#", "%23") \
+               .replace("@", "%40")
+    return path
+
+def wildcard_present(path):
+    return path.translate(None, "*#@%") != path
+
 class Command:
     def __init__(self):
         self.usage = "usage: %prog [options]"
         self.needsGit = True
+        self.verbose = False
 
 class P4UserMap:
     def __init__(self):
@@ -705,13 +760,9 @@ class P4UserMap:
 class P4Debug(Command):
     def __init__(self):
         Command.__init__(self)
-        self.options = [
-            optparse.make_option("--verbose", dest="verbose", action="store_true",
-                                 default=False),
-            ]
+        self.options = []
         self.description = "A tool to debug the output of p4 -G."
         self.needsGit = False
-        self.verbose = False
 
     def run(self, args):
         j = 0
@@ -725,11 +776,9 @@ class P4RollBack(Command):
     def __init__(self):
         Command.__init__(self)
         self.options = [
-            optparse.make_option("--verbose", dest="verbose", action="store_true"),
             optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
         ]
         self.description = "A tool to debug the multi-branch import. Don't use :)"
-        self.verbose = False
         self.rollbackLocalBranches = False
 
     def run(self, args):
@@ -787,20 +836,20 @@ class P4Submit(Command, P4UserMap):
         Command.__init__(self)
         P4UserMap.__init__(self)
         self.options = [
-                optparse.make_option("--verbose", dest="verbose", action="store_true"),
                 optparse.make_option("--origin", dest="origin"),
                 optparse.make_option("-M", dest="detectRenames", action="store_true"),
                 # preserve the user, requires relevant p4 permissions
                 optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
+                optparse.make_option("--export-labels", dest="exportLabels", action="store_true"),
         ]
         self.description = "Submit changes from git to the perforce depot."
         self.usage += " [name of git branch to submit into perforce depot]"
         self.interactive = True
         self.origin = ""
         self.detectRenames = False
-        self.verbose = False
         self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
         self.isWindows = (platform.system() == "Windows")
+        self.exportLabels = False
 
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
@@ -970,7 +1019,7 @@ class P4Submit(Command, P4UserMap):
         mtime = os.stat(template_file).st_mtime
 
         # invoke the editor
-        if os.environ.has_key("P4EDITOR"):
+        if os.environ.has_key("P4EDITOR") and (os.environ.get("P4EDITOR") != ""):
             editor = os.environ.get("P4EDITOR")
         else:
             editor = read_pipe("git var GIT_EDITOR").strip()
@@ -1021,6 +1070,7 @@ class P4Submit(Command, P4UserMap):
         filesToAdd = set()
         filesToDelete = set()
         editedFiles = set()
+        pureRenameCopy = set()
         filesToChangeExecBit = {}
 
         for line in diff:
@@ -1044,10 +1094,13 @@ class P4Submit(Command, P4UserMap):
             elif modifier == "C":
                 src, dest = diff['src'], diff['dst']
                 p4_integrate(src, dest)
+                pureRenameCopy.add(dest)
                 if diff['src_sha1'] != diff['dst_sha1']:
                     p4_edit(dest)
+                    pureRenameCopy.discard(dest)
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
                     p4_edit(dest)
+                    pureRenameCopy.discard(dest)
                     filesToChangeExecBit[dest] = diff['dst_mode']
                 os.unlink(dest)
                 editedFiles.add(dest)
@@ -1056,6 +1109,8 @@ class P4Submit(Command, P4UserMap):
                 p4_integrate(src, dest)
                 if diff['src_sha1'] != diff['dst_sha1']:
                     p4_edit(dest)
+                else:
+                    pureRenameCopy.add(dest)
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
                     p4_edit(dest)
                     filesToChangeExecBit[dest] = diff['dst_mode']
@@ -1129,12 +1184,12 @@ class P4Submit(Command, P4UserMap):
                     print "The following files should be scheduled for deletion with p4 delete:"
                     print " ".join(filesToDelete)
                 die("Please resolve and submit the conflict manually and "
-                    + "continue afterwards with git-p4 submit --continue")
+                    + "continue afterwards with git p4 submit --continue")
             elif response == "w":
                 system(diffcmd + " > patch.txt")
                 print "Patch saved to patch.txt in %s !" % self.clientPath
                 die("Please resolve and submit the conflict manually and "
-                    "continue afterwards with git-p4 submit --continue")
+                    "continue afterwards with git p4 submit --continue")
 
         system(applyPatchCmd)
 
@@ -1164,7 +1219,8 @@ class P4Submit(Command, P4UserMap):
                 del(os.environ["P4DIFF"])
             diff = ""
             for editedFile in editedFiles:
-                diff += p4_read_pipe(['diff', '-du', editedFile])
+                diff += p4_read_pipe(['diff', '-du',
+                                      wildcard_encode(editedFile)])
 
             newdiff = ""
             for newFile in filesToAdd:
@@ -1178,8 +1234,8 @@ class P4Submit(Command, P4UserMap):
 
             if self.checkAuthorship and not self.p4UserIsMe(p4User):
                 submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
-                submitTemplate += "######## Use git-p4 option --preserve-user to modify authorship\n"
-                submitTemplate += "######## Use git-p4 config git-p4.skipUserNameCheck hides this message.\n"
+                submitTemplate += "######## Use option --preserve-user to modify authorship.\n"
+                submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
 
             separatorLine = "######## everything below this line is just the diff #######\n"
 
@@ -1209,6 +1265,12 @@ class P4Submit(Command, P4UserMap):
                         # unmarshalled.
                         changelist = self.lastP4Changelist()
                         self.modifyChangelistUser(changelist, p4User)
+
+                # The rename/copy happened by applying a patch that created a
+                # new file.  This leaves it writable, which confuses p4.
+                for f in pureRenameCopy:
+                    p4_sync(f, "-f")
+
             else:
                 # skip this patch
                 print "Submission cancelled, undoing p4 changes."
@@ -1228,6 +1290,71 @@ class P4Submit(Command, P4UserMap):
                    + "Please review/edit and then use p4 submit -i < %s to submit directly!"
                    % (fileName, fileName))
 
+    # Export git tags as p4 labels. Create a p4 label and then tag
+    # with that.
+    def exportGitTags(self, gitTags):
+        validLabelRegexp = gitConfig("git-p4.labelExportRegexp")
+        if len(validLabelRegexp) == 0:
+            validLabelRegexp = defaultLabelRegexp
+        m = re.compile(validLabelRegexp)
+
+        for name in gitTags:
+
+            if not m.match(name):
+                if verbose:
+                    print "tag %s does not match regexp %s" % (name, validTagRegexp)
+                continue
+
+            # Get the p4 commit this corresponds to
+            logMessage = extractLogMessageFromGitCommit(name)
+            values = extractSettingsGitLog(logMessage)
+
+            if not values.has_key('change'):
+                # a tag pointing to something not sent to p4; ignore
+                if verbose:
+                    print "git tag %s does not give a p4 commit" % name
+                continue
+            else:
+                changelist = values['change']
+
+            # Get the tag details.
+            inHeader = True
+            isAnnotated = False
+            body = []
+            for l in read_pipe_lines(["git", "cat-file", "-p", name]):
+                l = l.strip()
+                if inHeader:
+                    if re.match(r'tag\s+', l):
+                        isAnnotated = True
+                    elif re.match(r'\s*$', l):
+                        inHeader = False
+                        continue
+                else:
+                    body.append(l)
+
+            if not isAnnotated:
+                body = ["lightweight tag imported by git p4\n"]
+
+            # Create the label - use the same view as the client spec we are using
+            clientSpec = getClientSpec()
+
+            labelTemplate  = "Label: %s\n" % name
+            labelTemplate += "Description:\n"
+            for b in body:
+                labelTemplate += "\t" + b + "\n"
+            labelTemplate += "View:\n"
+            for mapping in clientSpec.mappings:
+                labelTemplate += "\t%s\n" % mapping.depot_side.path
+
+            p4_write_pipe(["label", "-i"], labelTemplate)
+
+            # Use the label
+            p4_system(["tag", "-l", name] +
+                      ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])
+
+            if verbose:
+                print "created p4 label for tag %s" % name
+
     def run(self, args):
         if len(args) == 0:
             self.master = currentGitBranch()
@@ -1279,12 +1406,18 @@ class P4Submit(Command, P4UserMap):
         self.oldWorkingDirectory = os.getcwd()
 
         # ensure the clientPath exists
+        new_client_dir = False
         if not os.path.exists(self.clientPath):
+            new_client_dir = True
             os.makedirs(self.clientPath)
 
         chdir(self.clientPath)
         print "Synchronizing p4 checkout..."
-        p4_sync("...")
+        if new_client_dir:
+            # old one was destroyed, and maybe nobody told p4
+            p4_sync("...", "-f")
+        else:
+            p4_sync("...")
         self.check()
 
         commits = []
@@ -1317,6 +1450,16 @@ class P4Submit(Command, P4UserMap):
             rebase = P4Rebase()
             rebase.rebase()
 
+        if gitConfig("git-p4.exportLabels", "--bool") == "true":
+            self.exportLabels = true
+
+        if self.exportLabels:
+            p4Labels = getP4Labels(self.depotPath)
+            gitTags = getGitTags()
+
+            missingGitTags = gitTags - p4Labels
+            self.exportGitTags(missingGitTags)
+
         return True
 
 class View(object):
@@ -1544,7 +1687,7 @@ class P4Sync(Command, P4UserMap):
                 optparse.make_option("--changesfile", dest="changesFile"),
                 optparse.make_option("--silent", dest="silent", action="store_true"),
                 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
-                optparse.make_option("--verbose", dest="verbose", action="store_true"),
+                optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
                 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
                                      help="Import into refs/heads/ , not refs/remotes"),
                 optparse.make_option("--max-changes", dest="maxChanges"),
@@ -1568,9 +1711,9 @@ class P4Sync(Command, P4UserMap):
         self.branch = ""
         self.detectBranches = False
         self.detectLabels = False
+        self.importLabels = False
         self.changesFile = ""
         self.syncWithOrigin = True
-        self.verbose = False
         self.importIntoRemotes = True
         self.maxChanges = ""
         self.isWindows = (platform.system() == "Windows")
@@ -1587,23 +1730,6 @@ class P4Sync(Command, P4UserMap):
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
 
-    #
-    # P4 wildcards are not allowed in filenames.  P4 complains
-    # if you simply add them, but you can force it with "-f", in
-    # which case it translates them into %xx encoding internally.
-    # Search for and fix just these four characters.  Do % last so
-    # that fixing it does not inadvertently create new %-escapes.
-    #
-    def wildcard_decode(self, path):
-        # Cannot have * in a filename in windows; untested as to
-        # what p4 would do in such a case.
-        if not self.isWindows:
-            path = path.replace("%2A", "*")
-        path = path.replace("%23", "#") \
-                   .replace("%40", "@") \
-                   .replace("%25", "%")
-        return path
-
     # Force a checkpoint in fast-import and wait for it to finish
     def checkpoint(self):
         self.gitStream.write("checkpoint\n\n")
@@ -1671,6 +1797,7 @@ class P4Sync(Command, P4UserMap):
             fnum = fnum + 1
 
             relPath = self.stripRepoPath(path, self.depotPaths)
+            relPath = wildcard_decode(relPath)
 
             for branch in self.knownBranches.keys():
 
@@ -1688,7 +1815,7 @@ class P4Sync(Command, P4UserMap):
 
     def streamOneP4File(self, file, contents):
         relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
-        relPath = self.wildcard_decode(relPath)
+        relPath = wildcard_decode(relPath)
         if verbose:
             sys.stderr.write("%s\n" % relPath)
 
@@ -1757,6 +1884,7 @@ class P4Sync(Command, P4UserMap):
 
     def streamOneP4Deletion(self, file):
         relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
+        relPath = wildcard_decode(relPath)
         if verbose:
             sys.stderr.write("delete %s\n" % relPath)
         self.gitStream.write("D %s\n" % relPath)
@@ -1829,6 +1957,38 @@ class P4Sync(Command, P4UserMap):
         else:
             return "%s <a@b>" % userid
 
+    # Stream a p4 tag
+    def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
+        if verbose:
+            print "writing tag %s for commit %s" % (labelName, commit)
+        gitStream.write("tag %s\n" % labelName)
+        gitStream.write("from %s\n" % commit)
+
+        if labelDetails.has_key('Owner'):
+            owner = labelDetails["Owner"]
+        else:
+            owner = None
+
+        # Try to use the owner of the p4 label, or failing that,
+        # the current p4 user id.
+        if owner:
+            email = self.make_email(owner)
+        else:
+            email = self.make_email(self.p4UserId())
+        tagger = "%s %s %s" % (email, epoch, self.tz)
+
+        gitStream.write("tagger %s\n" % tagger)
+
+        print "labelDetails=",labelDetails
+        if labelDetails.has_key('Description'):
+            description = labelDetails['Description']
+        else:
+            description = 'Label from git p4'
+
+        gitStream.write("data %d\n" % len(description))
+        gitStream.write(description)
+        gitStream.write("\n")
+
     def commit(self, details, files, branch, branchPrefixes, parent = ""):
         epoch = details["time"]
         author = details["user"]
@@ -1893,25 +2053,7 @@ class P4Sync(Command, P4UserMap):
                     cleanedFiles[info["depotFile"]] = info["rev"]
 
                 if cleanedFiles == labelRevisions:
-                    self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
-                    self.gitStream.write("from %s\n" % branch)
-
-                    owner = labelDetails["Owner"]
-
-                    # Try to use the owner of the p4 label, or failing that,
-                    # the current p4 user id.
-                    if owner:
-                        email = self.make_email(owner)
-                    else:
-                        email = self.make_email(self.p4UserId())
-                    tagger = "%s %s %s" % (email, epoch, self.tz)
-
-                    self.gitStream.write("tagger %s\n" % tagger)
-
-                    description = labelDetails["Description"]
-                    self.gitStream.write("data %d\n" % len(description))
-                    self.gitStream.write(description)
-                    self.gitStream.write("\n")
+                    self.streamTag(self.gitStream, 'tag_%s' % labelDetails['label'], labelDetails, branch, epoch)
 
                 else:
                     if not self.silent:
@@ -1923,6 +2065,7 @@ class P4Sync(Command, P4UserMap):
                     print ("Tag %s does not match with change %s: file count is different."
                            % (labelDetails["label"], change))
 
+    # Build a dictionary of changelists and labels, for "detect-labels" option.
     def getLabels(self):
         self.labels = {}
 
@@ -1949,6 +2092,69 @@ class P4Sync(Command, P4UserMap):
         if self.verbose:
             print "Label changes: %s" % self.labels.keys()
 
+    # Import p4 labels as git tags. A direct mapping does not
+    # exist, so assume that if all the files are at the same revision
+    # then we can use that, or it's something more complicated we should
+    # just ignore.
+    def importP4Labels(self, stream, p4Labels):
+        if verbose:
+            print "import p4 labels: " + ' '.join(p4Labels)
+
+        ignoredP4Labels = gitConfigList("git-p4.ignoredP4Labels")
+        validLabelRegexp = gitConfig("git-p4.labelImportRegexp")
+        if len(validLabelRegexp) == 0:
+            validLabelRegexp = defaultLabelRegexp
+        m = re.compile(validLabelRegexp)
+
+        for name in p4Labels:
+            commitFound = False
+
+            if not m.match(name):
+                if verbose:
+                    print "label %s does not match regexp %s" % (name,validLabelRegexp)
+                continue
+
+            if name in ignoredP4Labels:
+                continue
+
+            labelDetails = p4CmdList(['label', "-o", name])[0]
+
+            # get the most recent changelist for each file in this label
+            change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name)
+                                for p in self.depotPaths])
+
+            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()
+                    commitFound = True
+                    # Convert from p4 time format
+                    try:
+                        tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
+                    except ValueError:
+                        print "Could not convert label time %s" % labelDetail['Update']
+                        tmwhen = 1
+
+                    when = int(time.mktime(tmwhen))
+                    self.streamTag(stream, name, labelDetails, gitCommit, when)
+                    if verbose:
+                        print "p4 label %s mapped to git commit %s" % (name, gitCommit)
+            else:
+                if verbose:
+                    print "Label %s has no changelists - possibly deleted?" % name
+
+            if not commitFound:
+                # We can't import this label; don't try again as it will get very
+                # expensive repeatedly fetching all the files for labels that will
+                # never be imported. If the label is moved in the future, the
+                # ignore will need to be removed manually.
+                system(["git", "config", "--add", "git-p4.ignoredP4Labels", name])
+
     def guessProjectName(self):
         for p in self.depotPaths:
             if p.endswith("/"):
@@ -2254,7 +2460,7 @@ class P4Sync(Command, P4UserMap):
 
         details["change"] = newestRevision
 
-        # Use time from top-most change so that all git-p4 clones of
+        # Use time from top-most change so that all git p4 clones of
         # the same p4 repo have the same commit SHA1s.
         res = p4CmdList("describe -s %d" % newestRevision)
         newestTime = None
@@ -2425,7 +2631,6 @@ class P4Sync(Command, P4UserMap):
 
         self.depotPaths = newPaths
 
-
         self.loadUserMapFromCache()
         self.labels = {}
         if self.detectLabels:
@@ -2474,8 +2679,8 @@ class P4Sync(Command, P4UserMap):
 
                 changes.sort()
             else:
-                # catch "git-p4 sync" with no new branches, in a repo that
-                # does not have any existing git-p4 branches
+                # catch "git p4 sync" with no new branches, in a repo that
+                # does not have any existing p4 branches
                 if len(args) == 0 and not self.p4BranchesInGit:
                     die("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.");
                 if self.verbose:
@@ -2489,22 +2694,31 @@ class P4Sync(Command, P4UserMap):
             if len(changes) == 0:
                 if not self.silent:
                     print "No changes to import!"
-                return True
+            else:
+                if not self.silent and not self.detectBranches:
+                    print "Import destination: %s" % self.branch
+
+                self.updatedBranches = set()
 
-            if not self.silent and not self.detectBranches:
-                print "Import destination: %s" % self.branch
+                self.importChanges(changes)
 
-            self.updatedBranches = set()
+                if not self.silent:
+                    print ""
+                    if len(self.updatedBranches) > 0:
+                        sys.stdout.write("Updated branches: ")
+                        for b in self.updatedBranches:
+                            sys.stdout.write("%s " % b)
+                        sys.stdout.write("\n")
 
-            self.importChanges(changes)
+        if gitConfig("git-p4.importLabels", "--bool") == "true":
+            self.importLabels = true
 
-            if not self.silent:
-                print ""
-                if len(self.updatedBranches) > 0:
-                    sys.stdout.write("Updated branches: ")
-                    for b in self.updatedBranches:
-                        sys.stdout.write("%s " % b)
-                    sys.stdout.write("\n")
+        if self.importLabels:
+            p4Labels = getP4Labels(self.depotPaths)
+            gitTags = getGitTags()
+
+            missingP4Labels = p4Labels - gitTags
+            self.importP4Labels(self.gitStream, missingP4Labels)
 
         self.gitStream.close()
         if importProcess.wait() != 0:
@@ -2523,13 +2737,16 @@ class P4Sync(Command, P4UserMap):
 class P4Rebase(Command):
     def __init__(self):
         Command.__init__(self)
-        self.options = [ ]
+        self.options = [
+                optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
+        ]
+        self.importLabels = False
         self.description = ("Fetches the latest revision from perforce and "
                             + "rebases the current work (branch) against it")
-        self.verbose = False
 
     def run(self, args):
         sync = P4Sync()
+        sync.importLabels = self.importLabels
         sync.run([])
 
         return self.rebase()
@@ -2719,16 +2936,16 @@ def main():
 
     args = sys.argv[2:]
 
-    if len(options) > 0:
-        if cmd.needsGit:
-            options.append(optparse.make_option("--git-dir", dest="gitdir"))
+    options.append(optparse.make_option("--verbose", dest="verbose", action="store_true"))
+    if cmd.needsGit:
+        options.append(optparse.make_option("--git-dir", dest="gitdir"))
 
-        parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
-                                       options,
-                                       description = cmd.description,
-                                       formatter = HelpFormatter())
+    parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
+                                   options,
+                                   description = cmd.description,
+                                   formatter = HelpFormatter())
 
-        (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
+    (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
     global verbose
     verbose = cmd.verbose
     if cmd.needsGit:
index c815a2412c2f33556ad712d328eaa80241c07bda..04d89415fe8ca1634fa362c66ea9569794dcadd5 100644 (file)
@@ -20,11 +20,20 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
-git format-patch -k --stdout --full-index --ignore-if-in-upstream \
-       --src-prefix=a/ --dst-prefix=b/ \
-       --no-renames $root_flag "$revisions" |
-git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" &&
-move_to_original_branch
+if test -n "$keep_empty"
+then
+       # we have to do this the hard way.  git format-patch completely squashes
+       # empty commits and even if it didn't the format doesn't really lend
+       # itself well to recording empty patches.  fortunately, cherry-pick
+       # makes this easy
+       git cherry-pick --allow-empty "$revisions"
+else
+       git format-patch -k --stdout --full-index --ignore-if-in-upstream \
+               --src-prefix=a/ --dst-prefix=b/ \
+               --no-renames $root_flag "$revisions" |
+       git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
+fi && move_to_original_branch
+
 ret=$?
 test 0 != $ret -a -d "$state_dir" && write_basic_state
 exit $ret
index 454674976ea7661b90d46ae5b5698be37b391166..0c19b7c7539192851d5bd43df686baa97dc0df73 100644 (file)
@@ -167,6 +167,14 @@ has_action () {
        sane_grep '^[^#]' "$1" >/dev/null
 }
 
+is_empty_commit() {
+       tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null ||
+               die "$1: not a commit that can be picked")
+       ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null ||
+               ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+       test "$tree" = "$ptree"
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -191,12 +199,19 @@ git_sequence_editor () {
 
 pick_one () {
        ff=--ff
+
        case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
        case "$force_rebase" in '') ;; ?*) ff= ;; esac
        output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+
+       if is_empty_commit "$sha1"
+       then
+               empty_args="--allow-empty"
+       fi
+
        test -d "$rewritten" &&
                pick_one_preserving_merges "$@" && return
-       output git cherry-pick $ff "$@"
+       output git cherry-pick $empty_args $ff "$@"
 }
 
 pick_one_preserving_merges () {
@@ -780,9 +795,17 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
        sed -n "s/^>//p" |
 while read -r shortsha1 rest
 do
+
+       if test -z "$keep_empty" && is_empty_commit $shortsha1
+       then
+               comment_out="# "
+       else
+               comment_out=
+       fi
+
        if test t != "$preserve_merges"
        then
-               printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+               printf '%s\n' "${comment_out}pick $shortsha1 $rest" >>"$todo"
        else
                sha1=$(git rev-parse $shortsha1)
                if test -z "$rebase_root"
@@ -801,7 +824,7 @@ do
                if test f = "$preserve"
                then
                        touch "$rewritten"/$sha1
-                       printf '%s\n' "pick $shortsha1 $rest" >> "$todo"
+                       printf '%s\n' "${comment_out}pick $shortsha1 $rest" >>"$todo"
                fi
        fi
 done
@@ -846,11 +869,19 @@ cat >> "$todo" << EOF
 #  f, fixup = like "squash", but discard this commit's log message
 #  x, exec = run command (the rest of the line) using shell
 #
+# These lines can be re-ordered; they are executed from top to bottom.
+#
 # If you remove a line here THAT COMMIT WILL BE LOST.
 # However, if you remove everything, the rebase will be aborted.
 #
 EOF
 
+if test -z "$keep_empty"
+then
+       echo "# Note that empty commits are commented out" >>"$todo"
+fi
+
+
 has_action "$todo" ||
        die_abort "Nothing to do"
 
index 69c1374823084804662dbd6b6d510ec2ec3428e9..24a2840033c175ff5399de82a1a777c75c0e85dd 100755 (executable)
@@ -43,6 +43,7 @@ s,strategy=!       use the given merge strategy
 no-ff!             cherry-pick all commits, even if unchanged
 m,merge!           use merging strategies to rebase
 i,interactive!     let the user edit the list of commits to rebase
+k,keep-empty      preserve empty commits during rebase
 f,force-rebase!    force rebase even if branch is up to date
 X,strategy-option=! pass the argument through to the merge strategy
 stat!              display a diffstat of what changed upstream
@@ -97,6 +98,7 @@ state_dir=
 action=
 preserve_merges=
 autosquash=
+keep_empty=
 test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
 
 read_basic_state () {
@@ -220,6 +222,9 @@ do
        -i)
                interactive_rebase=explicit
                ;;
+       -k)
+               keep_empty=yes
+               ;;
        -p)
                preserve_merges=t
                test -z "$interactive_rebase" && interactive_rebase=implied
index e136732cea80c1594ac02c93cf246e8c77ff89b8..f29285c411a6dc9b58cffb47b91577bb2d023bc6 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env perl
+#!/usr/bin/perl
 # Copyright 2005, Ryan Anderson <ryan@michonline.com>
 # Distribution permitted under the GPL v2, as distributed
 # by the Free Software Foundation.
index 3dc4851cfc70a2804b23a8eca4dac7702ae93c87..5f3ebd244dac3ca28298ece9df914102b8155660 100644 (file)
@@ -22,6 +22,7 @@ except ImportError:
     _digest = sha.new
 import sys
 import os
+import time
 sys.path.insert(0, os.getenv("GITPYTHONLIB","."))
 
 from git_remote_helpers.util import die, debug, warn
@@ -204,6 +205,11 @@ def read_one_line(repo):
     """Reads and processes one command.
     """
 
+    sleepy = os.environ.get("GIT_REMOTE_TESTGIT_SLEEPY")
+    if sleepy:
+        debug("Sleeping %d sec before readline" % int(sleepy))
+        time.sleep(int(sleepy))
+
     line = sys.stdin.readline()
 
     cmdline = line
@@ -258,6 +264,7 @@ def main(args):
 
     more = True
 
+    sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0)
     while (more):
         more = read_one_line(repo)
 
index 624feec26f66b15926ffa11767c146222bc9a185..757933174e4c17136f14a1fdd7c8c67c0a8bca50 100755 (executable)
@@ -15,6 +15,7 @@ F               pass --no-reuse-object to git-pack-objects
 n               do not run git-update-server-info
 q,quiet         be quiet
 l               pass --local to git-pack-objects
+unpack-unreachable=  with -A, do not loosen objects older than this
  Packing constraints
 window=         size of the window used for delta compression
 window-memory=  same as the above, but limit memory size instead of entries count
@@ -33,6 +34,8 @@ do
        -a)     all_into_one=t ;;
        -A)     all_into_one=t
                unpack_unreachable=--unpack-unreachable ;;
+       --unpack-unreachable)
+               unpack_unreachable="--unpack-unreachable=$2"; shift ;;
        -d)     remove_redundant=t ;;
        -q)     GIT_QUIET=t ;;
        -f)     no_reuse=--no-reuse-delta ;;
@@ -76,7 +79,12 @@ case ",$all_into_one," in
                if test -n "$existing" -a -n "$unpack_unreachable" -a \
                        -n "$remove_redundant"
                then
-                       args="$args $unpack_unreachable"
+                       # This may have arbitrary user arguments, so we
+                       # have to protect it against whitespace splitting
+                       # when it gets run as "pack-objects $args" later.
+                       # Fortunately, we know it's an approxidate, so we
+                       # can just use dots instead.
+                       args="$args $(echo "$unpack_unreachable" | tr ' ' .)"
                fi
        fi
        ;;
index 5d8e4e6c89f0471567a7aa1f07b054d8ac502e15..7b3ae75d7a631fb07404ab85fa6d43ada74d8512 100644 (file)
@@ -248,6 +248,10 @@ case $(uname -s) in
        find () {
                /usr/bin/find "$@"
        }
+       # git sees Windows-style pwd
+       pwd () {
+               builtin pwd -W
+       }
        is_absolute_path () {
                case "$1" in
                [/\\]* | [A-Za-z]:*)
index efc86ad4e0b90edbbf5a927aa87e7ad4b1a1949e..64a70d621aa5b81547ff63740b6876009677aae5 100755 (executable)
@@ -101,11 +101,12 @@ module_list()
 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 '\$path'")"
+       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$sm_path'")"
        echo "$name"
 }
 
@@ -119,7 +120,7 @@ module_name()
 #
 module_clone()
 {
-       path=$1
+       sm_path=$1
        url=$2
        reference="$3"
        quiet=
@@ -130,8 +131,8 @@ module_clone()
 
        gitdir=
        gitdir_base=
-       name=$(module_name "$path" 2>/dev/null)
-       test -n "$name" || name="$path"
+       name=$(module_name "$sm_path" 2>/dev/null)
+       test -n "$name" || name="$sm_path"
        base_name=$(dirname "$name")
 
        gitdir=$(git rev-parse --git-dir)
@@ -140,17 +141,17 @@ module_clone()
 
        if test -d "$gitdir"
        then
-               mkdir -p "$path"
+               mkdir -p "$sm_path"
                rm -f "$gitdir/index"
        else
                mkdir -p "$gitdir_base"
                git clone $quiet -n ${reference:+"$reference"} \
-                       --separate-git-dir "$gitdir" "$url" "$path" ||
-               die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
+                       --separate-git-dir "$gitdir" "$url" "$sm_path" ||
+               die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
        fi
 
        a=$(cd "$gitdir" && pwd)/
-       b=$(cd "$path" && pwd)/
+       b=$(cd "$sm_path" && pwd)/
        # normalize Windows-style absolute paths to POSIX-style absolute paths
        case $a in [a-zA-Z]:/*) a=/${a%%:*}${a#*:} ;; esac
        case $b in [a-zA-Z]:/*) b=/${b%%:*}${b#*:} ;; esac
@@ -167,11 +168,12 @@ module_clone()
        a=${a%/}
        b=${b%/}
 
-       rel=$(echo $b | sed -e 's|[^/]*|..|g')
-       echo "gitdir: $rel/$a" >"$path/.git"
+       # Turn each leading "*/" component into "../"
+       rel=$(echo $b | sed -e 's|[^/][^/]*|..|g')
+       echo "gitdir: $rel/$a" >"$sm_path/.git"
 
-       rel=$(echo $a | sed -e 's|[^/]*|..|g')
-       (clear_local_git_env; cd "$path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
+       rel=$(echo $a | sed -e 's|[^/][^/]*|..|g')
+       (clear_local_git_env; cd "$sm_path" && GIT_WORK_TREE=. git config core.worktree "$rel/$b")
 }
 
 #
@@ -222,14 +224,14 @@ cmd_add()
        done
 
        repo=$1
-       path=$2
+       sm_path=$2
 
-       if test -z "$path"; then
-               path=$(echo "$repo" |
+       if test -z "$sm_path"; then
+               sm_path=$(echo "$repo" |
                        sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
        fi
 
-       if test -z "$repo" -o -z "$path"; then
+       if test -z "$repo" -o -z "$sm_path"; then
                usage
        fi
 
@@ -250,7 +252,7 @@ cmd_add()
 
        # normalize path:
        # multiple //; leading ./; /./; /../; trailing /
-       path=$(printf '%s/\n' "$path" |
+       sm_path=$(printf '%s/\n' "$sm_path" |
                sed -e '
                        s|//*|/|g
                        s|^\(\./\)*||
@@ -260,49 +262,49 @@ cmd_add()
                        tstart
                        s|/*$||
                ')
-       git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
-       die "$(eval_gettext "'\$path' already exists in the index")"
+       git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
+       die "$(eval_gettext "'\$sm_path' already exists in the index")"
 
-       if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1
+       if test -z "$force" && ! git add --dry-run --ignore-missing "$sm_path" > /dev/null 2>&1
        then
                eval_gettextln "The following path is ignored by one of your .gitignore files:
-\$path
+\$sm_path
 Use -f if you really want to add it." >&2
                exit 1
        fi
 
        # perhaps the path exists and is already a git repo, else clone it
-       if test -e "$path"
+       if test -e "$sm_path"
        then
-               if test -d "$path"/.git -o -f "$path"/.git
+               if test -d "$sm_path"/.git -o -f "$sm_path"/.git
                then
-                       eval_gettextln "Adding existing repo at '\$path' to the index"
+                       eval_gettextln "Adding existing repo at '\$sm_path' to the index"
                else
-                       die "$(eval_gettext "'\$path' already exists and is not a valid git repo")"
+                       die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")"
                fi
 
        else
 
-               module_clone "$path" "$realrepo" "$reference" || exit
+               module_clone "$sm_path" "$realrepo" "$reference" || exit
                (
                        clear_local_git_env
-                       cd "$path" &&
+                       cd "$sm_path" &&
                        # ash fails to wordsplit ${branch:+-b "$branch"...}
                        case "$branch" in
                        '') git checkout -f -q ;;
                        ?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
                        esac
-               ) || die "$(eval_gettext "Unable to checkout submodule '\$path'")"
+               ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")"
        fi
-       git config submodule."$path".url "$realrepo"
+       git config submodule."$sm_path".url "$realrepo"
 
-       git add $force "$path" ||
-       die "$(eval_gettext "Failed to add submodule '\$path'")"
+       git add $force "$sm_path" ||
+       die "$(eval_gettext "Failed to add submodule '\$sm_path'")"
 
-       git config -f .gitmodules submodule."$path".path "$path" &&
-       git config -f .gitmodules submodule."$path".url "$repo" &&
+       git config -f .gitmodules submodule."$sm_path".path "$sm_path" &&
+       git config -f .gitmodules submodule."$sm_path".url "$repo" &&
        git add --force .gitmodules ||
-       die "$(eval_gettext "Failed to register submodule '\$path'")"
+       die "$(eval_gettext "Failed to register submodule '\$sm_path'")"
 }
 
 #
@@ -340,23 +342,25 @@ cmd_foreach()
        exec 3<&0
 
        module_list |
-       while read mode sha1 stage path
+       while read mode sha1 stage sm_path
        do
-               if test -e "$path"/.git
+               if test -e "$sm_path"/.git
                then
-                       say "$(eval_gettext "Entering '\$prefix\$path'")"
-                       name=$(module_name "$path")
+                       say "$(eval_gettext "Entering '\$prefix\$sm_path'")"
+                       name=$(module_name "$sm_path")
                        (
-                               prefix="$prefix$path/"
+                               prefix="$prefix$sm_path/"
                                clear_local_git_env
-                               cd "$path" &&
+                               # we make $path available to scripts ...
+                               path=$sm_path
+                               cd "$sm_path" &&
                                eval "$@" &&
                                if test -n "$recursive"
                                then
                                        cmd_foreach "--recursive" "$@"
                                fi
                        ) <&3 3<&- ||
-                       die "$(eval_gettext "Stopping at '\$path'; script returned non-zero status.")"
+                       die "$(eval_gettext "Stopping at '\$sm_path'; script returned non-zero status.")"
                fi
        done
 }
@@ -390,15 +394,15 @@ cmd_init()
        done
 
        module_list "$@" |
-       while read mode sha1 stage path
+       while read mode sha1 stage sm_path
        do
                # Skip already registered paths
-               name=$(module_name "$path") || exit
+               name=$(module_name "$sm_path") || exit
                if test -z "$(git config "submodule.$name.url")"
                then
                        url=$(git config -f .gitmodules submodule."$name".url)
                        test -z "$url" &&
-                       die "$(eval_gettext "No url found for submodule path '\$path' in .gitmodules")"
+                       die "$(eval_gettext "No url found for submodule path '\$sm_path' in .gitmodules")"
 
                        # Possibly a url relative to parent
                        case "$url" in
@@ -407,7 +411,7 @@ cmd_init()
                                ;;
                        esac
                        git config submodule."$name".url "$url" ||
-                       die "$(eval_gettext "Failed to register url for submodule path '\$path'")"
+                       die "$(eval_gettext "Failed to register url for submodule path '\$sm_path'")"
                fi
 
                # Copy "update" setting when it is not set yet
@@ -415,9 +419,9 @@ cmd_init()
                test -z "$upd" ||
                test -n "$(git config submodule."$name".update)" ||
                git config submodule."$name".update "$upd" ||
-               die "$(eval_gettext "Failed to register update mode for submodule path '\$path'")"
+               die "$(eval_gettext "Failed to register update mode for submodule path '\$sm_path'")"
 
-               say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$path'")"
+               say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$sm_path'")"
        done
 }
 
@@ -489,14 +493,14 @@ cmd_update()
        cloned_modules=
        module_list "$@" | {
        err=
-       while read mode sha1 stage path
+       while read mode sha1 stage sm_path
        do
                if test "$stage" = U
                then
-                       echo >&2 "Skipping unmerged submodule $path"
+                       echo >&2 "Skipping unmerged submodule $sm_path"
                        continue
                fi
-               name=$(module_name "$path") || exit
+               name=$(module_name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                if ! test -z "$update"
                then
@@ -507,7 +511,7 @@ cmd_update()
 
                if test "$update_module" = "none"
                then
-                       echo "Skipping submodule '$path'"
+                       echo "Skipping submodule '$sm_path'"
                        continue
                fi
 
@@ -516,20 +520,20 @@ cmd_update()
                        # Only mention uninitialized submodules when its
                        # path have been specified
                        test "$#" != "0" &&
-                       say "$(eval_gettext "Submodule path '\$path' not initialized
+                       say "$(eval_gettext "Submodule path '\$sm_path' not initialized
 Maybe you want to use 'update --init'?")"
                        continue
                fi
 
-               if ! test -d "$path"/.git -o -f "$path"/.git
+               if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
                then
-                       module_clone "$path" "$url" "$reference"|| exit
+                       module_clone "$sm_path" "$url" "$reference"|| exit
                        cloned_modules="$cloned_modules;$name"
                        subsha1=
                else
-                       subsha1=$(clear_local_git_env; cd "$path" &&
+                       subsha1=$(clear_local_git_env; cd "$sm_path" &&
                                git rev-parse --verify HEAD) ||
-                       die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")"
+                       die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
                fi
 
                if test "$subsha1" != "$sha1"
@@ -545,10 +549,10 @@ Maybe you want to use 'update --init'?")"
                        then
                                # Run fetch only if $sha1 isn't present or it
                                # is not reachable from a ref.
-                               (clear_local_git_env; cd "$path" &&
+                               (clear_local_git_env; cd "$sm_path" &&
                                        ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) &&
                                         test -z "$rev") || git-fetch)) ||
-                               die "$(eval_gettext "Unable to fetch in submodule path '\$path'")"
+                               die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
                        fi
 
                        # Is this something we just cloned?
@@ -562,24 +566,24 @@ Maybe you want to use 'update --init'?")"
                        case "$update_module" in
                        rebase)
                                command="git rebase"
-                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$path'")"
-                               say_msg="$(eval_gettext "Submodule path '\$path': rebased into '\$sha1'")"
+                               die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$sm_path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$sm_path': rebased into '\$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        merge)
                                command="git merge"
-                               die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$path'")"
-                               say_msg="$(eval_gettext "Submodule path '\$path': merged in '\$sha1'")"
+                               die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$sm_path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$sm_path': merged in '\$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        *)
                                command="git checkout $subforce -q"
-                               die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$path'")"
-                               say_msg="$(eval_gettext "Submodule path '\$path': checked out '\$sha1'")"
+                               die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$sm_path'")"
+                               say_msg="$(eval_gettext "Submodule path '\$sm_path': checked out '\$sha1'")"
                                ;;
                        esac
 
-                       if (clear_local_git_env; cd "$path" && $command "$sha1")
+                       if (clear_local_git_env; cd "$sm_path" && $command "$sha1")
                        then
                                say "$say_msg"
                        elif test -n "$must_die_on_failure"
@@ -593,11 +597,11 @@ Maybe you want to use 'update --init'?")"
 
                if test -n "$recursive"
                then
-                       (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags")
+                       (clear_local_git_env; cd "$sm_path" && eval cmd_update "$orig_flags")
                        res=$?
                        if test $res -gt 0
                        then
-                               die_msg="$(eval_gettext "Failed to recurse into submodule path '\$path'")"
+                               die_msg="$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
                                if test $res -eq 1
                                then
                                        err="${err};$die_msg"
@@ -884,30 +888,30 @@ cmd_status()
        done
 
        module_list "$@" |
-       while read mode sha1 stage path
+       while read mode sha1 stage sm_path
        do
-               name=$(module_name "$path") || exit
+               name=$(module_name "$sm_path") || exit
                url=$(git config submodule."$name".url)
-               displaypath="$prefix$path"
+               displaypath="$prefix$sm_path"
                if test "$stage" = U
                then
                        say "U$sha1 $displaypath"
                        continue
                fi
-               if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
+               if test -z "$url" || ! test -d "$sm_path"/.git -o -f "$sm_path"/.git
                then
                        say "-$sha1 $displaypath"
                        continue;
                fi
-               set_name_rev "$path" "$sha1"
-               if git diff-files --ignore-submodules=dirty --quiet -- "$path"
+               set_name_rev "$sm_path" "$sha1"
+               if git diff-files --ignore-submodules=dirty --quiet -- "$sm_path"
                then
                        say " $sha1 $displaypath$revname"
                else
                        if test -z "$cached"
                        then
-                               sha1=$(clear_local_git_env; cd "$path" && git rev-parse --verify HEAD)
-                               set_name_rev "$path" "$sha1"
+                               sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD)
+                               set_name_rev "$sm_path" "$sha1"
                        fi
                        say "+$sha1 $displaypath$revname"
                fi
@@ -917,10 +921,10 @@ cmd_status()
                        (
                                prefix="$displaypath/"
                                clear_local_git_env
-                               cd "$path" &&
+                               cd "$sm_path" &&
                                eval cmd_status "$orig_args"
                        ) ||
-                       die "$(eval_gettext "Failed to recurse into submodule path '\$path'")"
+                       die "$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")"
                fi
        done
 }
@@ -952,9 +956,9 @@ cmd_sync()
        done
        cd_to_toplevel
        module_list "$@" |
-       while read mode sha1 stage path
+       while read mode sha1 stage sm_path
        do
-               name=$(module_name "$path")
+               name=$(module_name "$sm_path")
                url=$(git config -f .gitmodules --get submodule."$name".url)
 
                # Possibly a url relative to parent
@@ -969,11 +973,11 @@ cmd_sync()
                        say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
                        git config submodule."$name".url "$url"
 
-                       if test -e "$path"/.git
+                       if test -e "$sm_path"/.git
                        then
                        (
                                clear_local_git_env
-                               cd "$path"
+                               cd "$sm_path"
                                remote=$(get_default_remote)
                                git config remote."$remote".url "$url"
                        )
index 4334b95f70fab5c3cb9e446a9d6c418748b95bca..31d02b5f70f553e27e0bdc760355909cb535f813 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env perl
+#!/usr/bin/perl
 # Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
 # License: GPL v2 or later
 use 5.008;
@@ -36,11 +36,33 @@ $ENV{TZ} = 'UTC';
 $| = 1; # unbuffer STDOUT
 
 sub fatal (@) { print STDERR "@_\n"; exit 1 }
+
+# All SVN commands do it.  Otherwise we may die on SIGPIPE when the remote
+# repository decides to close the connection which we expect to be kept alive.
+$SIG{PIPE} = 'IGNORE';
+
+# Given a dot separated version number, "subtract" it from
+# the SVN::Core::VERSION; non-negaitive return means the SVN::Core
+# is at least at the version the caller asked for.
+sub compare_svn_version {
+       my (@ours) = split(/\./, $SVN::Core::VERSION);
+       my (@theirs) = split(/\./, $_[0]);
+       my ($i, $diff);
+
+       for ($i = 0; $i < @ours && $i < @theirs; $i++) {
+               $diff = $ours[$i] - $theirs[$i];
+               return $diff if ($diff);
+       }
+       return 1 if ($i < @ours);
+       return -1 if ($i < @theirs);
+       return 0;
+}
+
 sub _req_svn {
        require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
        require SVN::Ra;
        require SVN::Delta;
-       if ($SVN::Core::VERSION lt '1.1.0') {
+       if (::compare_svn_version('1.1.0') < 0) {
                fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
        }
 }
@@ -1469,7 +1491,7 @@ sub cmd_info {
        }
        ::_req_svn();
        $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
-               ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
+               (::compare_svn_version('1.5.4') <= 0 || $file_type ne "dir");
        $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
 
        $result .= "Node Kind: " .
@@ -2031,6 +2053,7 @@ use IPC::Open3;
 use Time::Local;
 use Memoize;  # core since 5.8.0, Jul 2002
 use Memoize::Storable;
+use POSIX qw(:signal_h);
 
 my ($_gc_nr, $_gc_period);
 
@@ -4059,11 +4082,14 @@ sub rev_map_set {
        length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
        my $db = $self->map_path($uuid);
        my $db_lock = "$db.lock";
-       my $sig;
+       my $sigmask;
        $update_ref ||= 0;
        if ($update_ref) {
-               $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
-                           $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
+               $sigmask = POSIX::SigSet->new();
+               my $signew = POSIX::SigSet->new(SIGINT, SIGHUP, SIGTERM,
+                       SIGALRM, SIGUSR1, SIGUSR2);
+               sigprocmask(SIG_BLOCK, $signew, $sigmask) or
+                       croak "Can't block signals: $!";
        }
        mkfile($db);
 
@@ -4102,9 +4128,8 @@ sub rev_map_set {
                                    "$db_lock => $db ($!)\n";
        delete $LOCKFILES{$db_lock};
        if ($update_ref) {
-               $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
-                           $SIG{USR1} = $SIG{USR2} = 'DEFAULT';
-               kill $sig, $$ if defined $sig;
+               sigprocmask(SIG_SETMASK, $sigmask) or
+                       croak "Can't restore signal mask: $!";
        }
 }
 
@@ -5436,7 +5461,7 @@ BEGIN {
 }
 
 sub _auth_providers () {
-       [
+       my @rv = (
          SVN::Client::get_simple_provider(),
          SVN::Client::get_ssl_server_trust_file_provider(),
          SVN::Client::get_simple_prompt_provider(
@@ -5452,7 +5477,23 @@ sub _auth_providers () {
            \&Git::SVN::Prompt::ssl_server_trust),
          SVN::Client::get_username_prompt_provider(
            \&Git::SVN::Prompt::username, 2)
-       ]
+       );
+
+       # earlier 1.6.x versions would segfault, and <= 1.5.x didn't have
+       # this function
+       if (::compare_svn_version('1.6.12') > 0) {
+               my $config = SVN::Core::config_get_config($config_dir);
+               my ($p, @a);
+               # config_get_config returns all config files from
+               # ~/.subversion, auth_get_platform_specific_client_providers
+               # just wants the config "file".
+               @a = ($config->{'config'}, undef);
+               $p = SVN::Core::auth_get_platform_specific_client_providers(@a);
+               # Insert the return value from
+               # auth_get_platform_specific_providers
+               unshift @rv, @$p;
+       }
+       \@rv;
 }
 
 sub escape_uri_only {
@@ -5599,7 +5640,7 @@ sub get_log {
        # drop it.  Therefore, the receiver callback passed to it
        # is made aware of this limitation by being wrapped if
        # the limit passed to is being wrapped.
-       if ($SVN::Core::VERSION le '1.2.0') {
+       if (::compare_svn_version('1.2.0') <= 0) {
                my $limit = splice(@args, 3, 1);
                if ($limit > 0) {
                        my $receiver = pop @args;
@@ -5631,7 +5672,8 @@ sub trees_match {
 
 sub get_commit_editor {
        my ($self, $log, $cb, $pool) = @_;
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+
+       my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef, 0) : ();
        $self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
 }
 
@@ -5649,7 +5691,7 @@ sub gs_do_update {
        my (@pc) = split m#/#, $path;
        my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),
                                        1, $editor, $pool);
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+       my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
 
        # Since we can't rely on svn_ra_reparent being available, we'll
        # just have to do some magic with set_path to make it so
@@ -5699,7 +5741,7 @@ sub gs_do_switch {
        $ra ||= $self;
        $url_b = escape_url($url_b);
        my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+       my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
        $reporter->set_path('', $rev_a, 0, @lock, $pool);
        $reporter->finish_report($pool);
 
diff --git a/git.c b/git.c
index 380561663061f011189de883a865fbd59a922190..d232de92e496bd5750015edce22d3b67f236b605 100644 (file)
--- a/git.c
+++ b/git.c
@@ -13,7 +13,7 @@ const char git_usage_string[] =
        "           <command> [<args>]";
 
 const char git_more_info_string[] =
-       "See 'git help <command>' for more information on a specific command.";
+       N_("See 'git help <command>' for more information on a specific command.");
 
 static struct startup_info git_startup_info;
 static int use_pager = -1;
@@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
                { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
                { "clone", cmd_clone },
+               { "column", cmd_column, RUN_SETUP_GENTLY },
                { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config, RUN_SETUP_GENTLY },
index a8b5fad26631207fcc17a6339ba735877c0902bd..55e0e9ea38b3080e32467b6faf56f40d45386b96 100755 (executable)
@@ -133,6 +133,12 @@ our $default_projects_order = "project";
 # (only effective if this variable evaluates to true)
 our $export_ok = "++GITWEB_EXPORT_OK++";
 
+# don't generate age column on the projects list page
+our $omit_age_column = 0;
+
+# don't generate information about owners of repositories
+our $omit_owner=0;
+
 # show repository only if this subroutine returns true
 # when given the path to the project, for example:
 #    sub { return -e "$_[0]/git-daemon-export-ok"; }
@@ -1732,20 +1738,29 @@ sub chop_and_escape_str {
 # '<span class="mark">foo</span>bar'
 sub esc_html_hl_regions {
        my ($str, $css_class, @sel) = @_;
-       return esc_html($str) unless @sel;
+       my %opts = grep { ref($_) ne 'ARRAY' } @sel;
+       @sel     = grep { ref($_) eq 'ARRAY' } @sel;
+       return esc_html($str, %opts) unless @sel;
 
        my $out = '';
        my $pos = 0;
 
        for my $s (@sel) {
-               $out .= esc_html(substr($str, $pos, $s->[0] - $pos))
-                       if ($s->[0] - $pos > 0);
-               $out .= $cgi->span({-class => $css_class},
-                                  esc_html(substr($str, $s->[0], $s->[1] - $s->[0])));
+               my ($begin, $end) = @$s;
+
+               # Don't create empty <span> elements.
+               next if $end <= $begin;
+
+               my $escaped = esc_html(substr($str, $begin, $end - $begin),
+                                      %opts);
 
-               $pos = $s->[1];
+               $out .= esc_html(substr($str, $pos, $begin - $pos), %opts)
+                       if ($begin - $pos > 0);
+               $out .= $cgi->span({-class => $css_class}, $escaped);
+
+               $pos = $end;
        }
-       $out .= esc_html(substr($str, $pos))
+       $out .= esc_html(substr($str, $pos), %opts)
                if ($pos < length($str));
 
        return $out;
@@ -2421,26 +2436,32 @@ sub format_cc_diff_chunk_header {
 }
 
 # process patch (diff) line (not to be used for diff headers),
-# returning class and HTML-formatted (but not wrapped) line
-sub process_diff_line {
-       my $line = shift;
-       my ($from, $to) = @_;
-
-       my $diff_class = diff_line_class($line, $from, $to);
-
-       chomp $line;
-       $line = untabify($line);
+# returning HTML-formatted (but not wrapped) line.
+# If the line is passed as a reference, it is treated as HTML and not
+# esc_html()'ed.
+sub format_diff_line {
+       my ($line, $diff_class, $from, $to) = @_;
+
+       if (ref($line)) {
+               $line = $$line;
+       } else {
+               chomp $line;
+               $line = untabify($line);
 
-       if ($from && $to && $line =~ m/^\@{2} /) {
-               $line = format_unidiff_chunk_header($line, $from, $to);
-               return $diff_class, $line;
+               if ($from && $to && $line =~ m/^\@{2} /) {
+                       $line = format_unidiff_chunk_header($line, $from, $to);
+               } elsif ($from && $to && $line =~ m/^\@{3}/) {
+                       $line = format_cc_diff_chunk_header($line, $from, $to);
+               } else {
+                       $line = esc_html($line, -nbsp=>1);
+               }
+       }
 
-       } elsif ($from && $to && $line =~ m/^\@{3}/) {
-               $line = format_cc_diff_chunk_header($line, $from, $to);
-               return $diff_class, $line;
+       my $diff_classes = "diff";
+       $diff_classes .= " $diff_class" if ($diff_class);
+       $line = "<div class=\"$diff_classes\">$line</div>\n";
 
-       }
-       return $diff_class, esc_html($line, -nbsp=>1);
+       return $line;
 }
 
 # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -2997,9 +3018,11 @@ sub git_get_projects_list {
                        }
                        if (check_export_ok("$projectroot/$path")) {
                                my $pr = {
-                                       path => $path,
-                                       owner => to_utf8($owner),
+                                       path => $path
                                };
+                               if ($owner) {
+                                       $pr->{'owner'} = to_utf8($owner);
+                               }
                                push @list, $pr;
                        }
                }
@@ -3886,6 +3909,7 @@ sub print_feed_meta {
                                '-type' => "application/$type+xml"
                        );
 
+                       $href_params{'extra_options'} = undef;
                        $href_params{'action'} = $type;
                        $link_attr{'-href'} = href(%href_params);
                        print "<link ".
@@ -4993,10 +5017,186 @@ sub git_difftree_body {
        print "</table>\n";
 }
 
-sub print_sidebyside_diff_chunk {
-       my @chunk = @_;
+# Print context lines and then rem/add lines in a side-by-side manner.
+sub print_sidebyside_diff_lines {
+       my ($ctx, $rem, $add) = @_;
+
+       # print context block before add/rem block
+       if (@$ctx) {
+               print join '',
+                       '<div class="chunk_block ctx">',
+                               '<div class="old">',
+                               @$ctx,
+                               '</div>',
+                               '<div class="new">',
+                               @$ctx,
+                               '</div>',
+                       '</div>';
+       }
+
+       if (!@$add) {
+               # pure removal
+               print join '',
+                       '<div class="chunk_block rem">',
+                               '<div class="old">',
+                               @$rem,
+                               '</div>',
+                       '</div>';
+       } elsif (!@$rem) {
+               # pure addition
+               print join '',
+                       '<div class="chunk_block add">',
+                               '<div class="new">',
+                               @$add,
+                               '</div>',
+                       '</div>';
+       } else {
+               print join '',
+                       '<div class="chunk_block chg">',
+                               '<div class="old">',
+                               @$rem,
+                               '</div>',
+                               '<div class="new">',
+                               @$add,
+                               '</div>',
+                       '</div>';
+       }
+}
+
+# Print context lines and then rem/add lines in inline manner.
+sub print_inline_diff_lines {
+       my ($ctx, $rem, $add) = @_;
+
+       print @$ctx, @$rem, @$add;
+}
+
+# Format removed and added line, mark changed part and HTML-format them.
+# Implementation is based on contrib/diff-highlight
+sub format_rem_add_lines_pair {
+       my ($rem, $add, $num_parents) = @_;
+
+       # We need to untabify lines before split()'ing them;
+       # otherwise offsets would be invalid.
+       chomp $rem;
+       chomp $add;
+       $rem = untabify($rem);
+       $add = untabify($add);
+
+       my @rem = split(//, $rem);
+       my @add = split(//, $add);
+       my ($esc_rem, $esc_add);
+       # Ignore leading +/- characters for each parent.
+       my ($prefix_len, $suffix_len) = ($num_parents, 0);
+       my ($prefix_has_nonspace, $suffix_has_nonspace);
+
+       my $shorter = (@rem < @add) ? @rem : @add;
+       while ($prefix_len < $shorter) {
+               last if ($rem[$prefix_len] ne $add[$prefix_len]);
+
+               $prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
+               $prefix_len++;
+       }
+
+       while ($prefix_len + $suffix_len < $shorter) {
+               last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
+
+               $suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
+               $suffix_len++;
+       }
+
+       # Mark lines that are different from each other, but have some common
+       # part that isn't whitespace.  If lines are completely different, don't
+       # mark them because that would make output unreadable, especially if
+       # diff consists of multiple lines.
+       if ($prefix_has_nonspace || $suffix_has_nonspace) {
+               $esc_rem = esc_html_hl_regions($rem, 'marked',
+                       [$prefix_len, @rem - $suffix_len], -nbsp=>1);
+               $esc_add = esc_html_hl_regions($add, 'marked',
+                       [$prefix_len, @add - $suffix_len], -nbsp=>1);
+       } else {
+               $esc_rem = esc_html($rem, -nbsp=>1);
+               $esc_add = esc_html($add, -nbsp=>1);
+       }
+
+       return format_diff_line(\$esc_rem, 'rem'),
+              format_diff_line(\$esc_add, 'add');
+}
+
+# HTML-format diff context, removed and added lines.
+sub format_ctx_rem_add_lines {
+       my ($ctx, $rem, $add, $num_parents) = @_;
+       my (@new_ctx, @new_rem, @new_add);
+       my $can_highlight = 0;
+       my $is_combined = ($num_parents > 1);
+
+       # Highlight if every removed line has a corresponding added line.
+       if (@$add > 0 && @$add == @$rem) {
+               $can_highlight = 1;
+
+               # Highlight lines in combined diff only if the chunk contains
+               # diff between the same version, e.g.
+               #
+               #    - a
+               #   -  b
+               #    + c
+               #   +  d
+               #
+               # Otherwise the highlightling would be confusing.
+               if ($is_combined) {
+                       for (my $i = 0; $i < @$add; $i++) {
+                               my $prefix_rem = substr($rem->[$i], 0, $num_parents);
+                               my $prefix_add = substr($add->[$i], 0, $num_parents);
+
+                               $prefix_rem =~ s/-/+/g;
+
+                               if ($prefix_rem ne $prefix_add) {
+                                       $can_highlight = 0;
+                                       last;
+                               }
+                       }
+               }
+       }
+
+       if ($can_highlight) {
+               for (my $i = 0; $i < @$add; $i++) {
+                       my ($line_rem, $line_add) = format_rem_add_lines_pair(
+                               $rem->[$i], $add->[$i], $num_parents);
+                       push @new_rem, $line_rem;
+                       push @new_add, $line_add;
+               }
+       } else {
+               @new_rem = map { format_diff_line($_, 'rem') } @$rem;
+               @new_add = map { format_diff_line($_, 'add') } @$add;
+       }
+
+       @new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
+
+       return (\@new_ctx, \@new_rem, \@new_add);
+}
+
+# Print context lines and then rem/add lines.
+sub print_diff_lines {
+       my ($ctx, $rem, $add, $diff_style, $num_parents) = @_;
+       my $is_combined = $num_parents > 1;
+
+       ($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
+               $num_parents);
+
+       if ($diff_style eq 'sidebyside' && !$is_combined) {
+               print_sidebyside_diff_lines($ctx, $rem, $add);
+       } else {
+               # default 'inline' style and unknown styles
+               print_inline_diff_lines($ctx, $rem, $add);
+       }
+}
+
+sub print_diff_chunk {
+       my ($diff_style, $num_parents, $from, $to, @chunk) = @_;
        my (@ctx, @rem, @add);
 
+       # The class of the previous line.
+       my $prev_class = '';
+
        return unless @chunk;
 
        # incomplete last line might be among removed or added lines,
@@ -5015,55 +5215,19 @@ sub print_sidebyside_diff_chunk {
 
                # print chunk headers
                if ($class && $class eq 'chunk_header') {
-                       print $line;
+                       print format_diff_line($line, $class, $from, $to);
                        next;
                }
 
-               ## print from accumulator when type of class of lines change
-               # empty contents block on start rem/add block, or end of chunk
-               if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) {
-                       print join '',
-                               '<div class="chunk_block ctx">',
-                                       '<div class="old">',
-                                       @ctx,
-                                       '</div>',
-                                       '<div class="new">',
-                                       @ctx,
-                                       '</div>',
-                               '</div>';
-                       @ctx = ();
-               }
-               # empty add/rem block on start context block, or end of chunk
-               if ((@rem || @add) && (!$class || $class eq 'ctx')) {
-                       if (!@add) {
-                               # pure removal
-                               print join '',
-                                       '<div class="chunk_block rem">',
-                                               '<div class="old">',
-                                               @rem,
-                                               '</div>',
-                                       '</div>';
-                       } elsif (!@rem) {
-                               # pure addition
-                               print join '',
-                                       '<div class="chunk_block add">',
-                                               '<div class="new">',
-                                               @add,
-                                               '</div>',
-                                       '</div>';
-                       } else {
-                               # assume that it is change
-                               print join '',
-                                       '<div class="chunk_block chg">',
-                                               '<div class="old">',
-                                               @rem,
-                                               '</div>',
-                                               '<div class="new">',
-                                               @add,
-                                               '</div>',
-                                       '</div>';
-                       }
-                       @rem = @add = ();
+               ## print from accumulator when have some add/rem lines or end
+               # of chunk (flush context lines), or when have add and rem
+               # lines and new block is reached (otherwise add/rem lines could
+               # be reordered)
+               if (!$class || ((@rem || @add) && $class eq 'ctx') ||
+                   (@rem && @add && $class ne $prev_class)) {
+                       print_diff_lines(\@ctx, \@rem, \@add,
+                                        $diff_style, $num_parents);
+                       @ctx = @rem = @add = ();
                }
 
                ## adding lines to accumulator
@@ -5079,6 +5243,8 @@ sub print_sidebyside_diff_chunk {
                if ($class eq 'ctx') {
                        push @ctx, $line;
                }
+
+               $prev_class = $class;
        }
 }
 
@@ -5200,27 +5366,19 @@ sub git_patchset_body {
 
                        next PATCH if ($patch_line =~ m/^diff /);
 
-                       my ($class, $line) = process_diff_line($patch_line, \%from, \%to);
-                       my $diff_classes = "diff";
-                       $diff_classes .= " $class" if ($class);
-                       $line = "<div class=\"$diff_classes\">$line</div>\n";
+                       my $class = diff_line_class($patch_line, \%from, \%to);
 
-                       if ($diff_style eq 'sidebyside' && !$is_combined) {
-                               if ($class eq 'chunk_header') {
-                                       print_sidebyside_diff_chunk(@chunk);
-                                       @chunk = ( [ $class, $line ] );
-                               } else {
-                                       push @chunk, [ $class, $line ];
-                               }
-                       } else {
-                               # default 'inline' style and unknown styles
-                               print $line;
+                       if ($class eq 'chunk_header') {
+                               print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
+                               @chunk = ();
                        }
+
+                       push @chunk, [ $class, $patch_line ];
                }
 
        } continue {
                if (@chunk) {
-                       print_sidebyside_diff_chunk(@chunk);
+                       print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
                        @chunk = ();
                }
                print "</div>\n"; # class="patch"
@@ -5460,11 +5618,15 @@ sub git_project_list_rows {
                                        ? esc_html_match_hl_chopped($pr->{'descr_long'},
                                                                    $pr->{'descr'}, $search_regexp)
                                        : esc_html($pr->{'descr'})) .
-                     "</td>\n" .
-                     "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
-               print "<td class=\"". age_class($pr->{'age'}) . "\">" .
-                     (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
-                     "<td class=\"link\">" .
+                     "</td>\n";
+               unless ($omit_owner) {
+                       print "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
+               }
+               unless ($omit_age_column) {
+                       print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+                           (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n";
+               }
+               print"<td class=\"link\">" .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
@@ -5495,7 +5657,10 @@ sub git_project_list_body {
                                         'tagfilter'  => $tagfilter)
                if ($tagfilter || $search_regexp);
        # fill the rest
-       @projects = fill_project_list_info(\@projects);
+       my @all_fields = ('descr', 'descr_long', 'ctags', 'category');
+       push @all_fields, ('age', 'age_string') unless($omit_age_column);
+       push @all_fields, 'owner' unless($omit_owner);
+       @projects = fill_project_list_info(\@projects, @all_fields);
 
        $order ||= $default_projects_order;
        $from = 0 unless defined $from;
@@ -5526,8 +5691,8 @@ sub git_project_list_body {
                }
                print_sort_th('project', $order, 'Project');
                print_sort_th('descr', $order, 'Description');
-               print_sort_th('owner', $order, 'Owner');
-               print_sort_th('age', $order, 'Last Change');
+               print_sort_th('owner', $order, 'Owner') unless $omit_owner;
+               print_sort_th('age', $order, 'Last Change') unless $omit_age_column;
                print "<th></th>\n" . # for links
                      "</tr>\n";
        }
@@ -6280,8 +6445,10 @@ sub git_summary {
 
        print "<div class=\"title\">&nbsp;</div>\n";
        print "<table class=\"projects_list\">\n" .
-             "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
-             "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
+             "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n";
+        unless ($omit_owner) {
+               print  "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
+        }
        if (defined $cd{'rfc2822'}) {
                print "<tr id=\"metadata_lchange\"><td>last change</td>" .
                      "<td>".format_timestamp_html(\%cd)."</td></tr>\n";
@@ -7003,6 +7170,28 @@ sub snapshot_name {
        return wantarray ? ($name, $name) : $name;
 }
 
+sub exit_if_unmodified_since {
+       my ($latest_epoch) = @_;
+       our $cgi;
+
+       my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
+       if (defined $if_modified) {
+               my $since;
+               if (eval { require HTTP::Date; 1; }) {
+                       $since = HTTP::Date::str2time($if_modified);
+               } elsif (eval { require Time::ParseDate; 1; }) {
+                       $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
+               }
+               if (defined $since && $latest_epoch <= $since) {
+                       my %latest_date = parse_date($latest_epoch);
+                       print $cgi->header(
+                               -last_modified => $latest_date{'rfc2822'},
+                               -status => '304 Not Modified');
+                       goto DONE_GITWEB;
+               }
+       }
+}
+
 sub git_snapshot {
        my $format = $input_params{'snapshot_format'};
        if (!@snapshot_fmts) {
@@ -7029,6 +7218,10 @@ sub git_snapshot {
 
        my ($name, $prefix) = snapshot_name($project, $hash);
        my $filename = "$name$known_snapshot_formats{$format}{'suffix'}";
+
+       my %co = parse_commit($hash);
+       exit_if_unmodified_since($co{'committer_epoch'}) if %co;
+
        my $cmd = quote_command(
                git_cmd(), 'archive',
                "--format=$known_snapshot_formats{$format}{'format'}",
@@ -7038,9 +7231,15 @@ sub git_snapshot {
        }
 
        $filename =~ s/(["\\])/\\$1/g;
+       my %latest_date;
+       if (%co) {
+               %latest_date = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+       }
+
        print $cgi->header(
                -type => $known_snapshot_formats{$format}{'type'},
                -content_disposition => 'inline; filename="' . $filename . '"',
+               %co ? (-last_modified => $latest_date{'rfc2822'}) : (),
                -status => '200 OK');
 
        open my $fd, "-|", $cmd
@@ -7820,33 +8019,14 @@ sub git_feed {
        if (defined($commitlist[0])) {
                %latest_commit = %{$commitlist[0]};
                my $latest_epoch = $latest_commit{'committer_epoch'};
-               %latest_date   = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
-               my $if_modified = $cgi->http('IF_MODIFIED_SINCE');
-               if (defined $if_modified) {
-                       my $since;
-                       if (eval { require HTTP::Date; 1; }) {
-                               $since = HTTP::Date::str2time($if_modified);
-                       } elsif (eval { require Time::ParseDate; 1; }) {
-                               $since = Time::ParseDate::parsedate($if_modified, GMT => 1);
-                       }
-                       if (defined $since && $latest_epoch <= $since) {
-                               print $cgi->header(
-                                       -type => $content_type,
-                                       -charset => 'utf-8',
-                                       -last_modified => $latest_date{'rfc2822'},
-                                       -status => '304 Not Modified');
-                               return;
-                       }
-               }
-               print $cgi->header(
-                       -type => $content_type,
-                       -charset => 'utf-8',
-                       -last_modified => $latest_date{'rfc2822'});
-       } else {
-               print $cgi->header(
-                       -type => $content_type,
-                       -charset => 'utf-8');
+               exit_if_unmodified_since($latest_epoch);
+               %latest_date = parse_date($latest_epoch, $latest_commit{'comitter_tz'});
        }
+       print $cgi->header(
+               -type => $content_type,
+               -charset => 'utf-8',
+               %latest_date ? (-last_modified => $latest_date{'rfc2822'}) : (),
+               -status => '200 OK');
 
        # Optimization: skip generating the body if client asks only
        # for Last-Modified date.
index c530355a7cefefe694f8cbdcc3a7acc5725bec3a..cb86d2d029637ec64f70cc9e1cdbeeea972e8771 100644 (file)
@@ -438,6 +438,10 @@ div.diff.add {
        color: #008800;
 }
 
+div.diff.add span.marked {
+       background-color: #aaffaa;
+}
+
 div.diff.from_file a.path,
 div.diff.from_file {
        color: #aa0000;
@@ -447,6 +451,10 @@ div.diff.rem {
        color: #cc0000;
 }
 
+div.diff.rem span.marked {
+       background-color: #ffaaaa;
+}
+
 div.diff.chunk_header a,
 div.diff.chunk_header {
        color: #990099;
diff --git a/graph.c b/graph.c
index 7358416a72e855b406e026036cf61bcdd15e5142..7e0a099f8329757b24770ec6788bf83a8398d76d 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -194,8 +194,10 @@ static struct strbuf *diff_output_prefix_callback(struct diff_options *opt, void
        struct git_graph *graph = data;
        static struct strbuf msgbuf = STRBUF_INIT;
 
+       assert(opt);
        assert(graph);
 
+       opt->output_prefix_length = graph->width;
        strbuf_reset(&msgbuf);
        graph_padding_line(graph, &msgbuf);
        return &msgbuf;
@@ -245,6 +247,7 @@ struct git_graph *graph_init(struct rev_info *opt)
         */
        opt->diffopt.output_prefix = diff_output_prefix_callback;
        opt->diffopt.output_prefix_data = graph;
+       opt->diffopt.output_prefix_length = 0;
 
        return graph;
 }
diff --git a/help.c b/help.c
index 14eefc91ced3890975d0833ffc83971c7986858b..69d483d8d3b7e109345b1e8124df6b023ed312fb 100644 (file)
--- a/help.c
+++ b/help.c
@@ -4,6 +4,8 @@
 #include "levenshtein.h"
 #include "help.h"
 #include "common-cmds.h"
+#include "string-list.h"
+#include "column.h"
 
 void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
@@ -70,31 +72,25 @@ void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
        cmds->cnt = cj;
 }
 
-static void pretty_print_string_list(struct cmdnames *cmds, int longest)
+static void pretty_print_string_list(struct cmdnames *cmds,
+                                    unsigned int colopts)
 {
-       int cols = 1, rows;
-       int space = longest + 1; /* min 1 SP between words */
-       int max_cols = term_columns() - 1; /* don't print *on* the edge */
-       int i, j;
-
-       if (space < max_cols)
-               cols = max_cols / space;
-       rows = DIV_ROUND_UP(cmds->cnt, cols);
-
-       for (i = 0; i < rows; i++) {
-               printf("  ");
+       struct string_list list = STRING_LIST_INIT_NODUP;
+       struct column_options copts;
+       int i;
 
-               for (j = 0; j < cols; j++) {
-                       int n = j * rows + i;
-                       int size = space;
-                       if (n >= cmds->cnt)
-                               break;
-                       if (j == cols-1 || n + rows >= cmds->cnt)
-                               size = 1;
-                       printf("%-*s", size, cmds->names[n]->name);
-               }
-               putchar('\n');
-       }
+       for (i = 0; i < cmds->cnt; i++)
+               string_list_append(&list, cmds->names[i]->name);
+       /*
+        * always enable column display, we only consult column.*
+        * about layout strategy and stuff
+        */
+       colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+       memset(&copts, 0, sizeof(copts));
+       copts.indent = "  ";
+       copts.padding = 2;
+       print_columns(&list, colopts, &copts);
+       string_list_clear(&list, 0);
 }
 
 static int is_executable(const char *name)
@@ -203,34 +199,21 @@ void load_command_list(const char *prefix,
        exclude_cmds(other_cmds, main_cmds);
 }
 
-void list_commands(const char *title, struct cmdnames *main_cmds,
-                  struct cmdnames *other_cmds)
+void list_commands(unsigned int colopts,
+                  struct cmdnames *main_cmds, struct cmdnames *other_cmds)
 {
-       int i, longest = 0;
-
-       for (i = 0; i < main_cmds->cnt; i++)
-               if (longest < main_cmds->names[i]->len)
-                       longest = main_cmds->names[i]->len;
-       for (i = 0; i < other_cmds->cnt; i++)
-               if (longest < other_cmds->names[i]->len)
-                       longest = other_cmds->names[i]->len;
-
        if (main_cmds->cnt) {
                const char *exec_path = git_exec_path();
-               printf("available %s in '%s'\n", title, exec_path);
-               printf("----------------");
-               mput_char('-', strlen(title) + strlen(exec_path));
+               printf_ln(_("available git commands in '%s'"), exec_path);
                putchar('\n');
-               pretty_print_string_list(main_cmds, longest);
+               pretty_print_string_list(main_cmds, colopts);
                putchar('\n');
        }
 
        if (other_cmds->cnt) {
-               printf("%s available from elsewhere on your $PATH\n", title);
-               printf("---------------------------------------");
-               mput_char('-', strlen(title));
+               printf_ln(_("git commands available from elsewhere on your $PATH"));
                putchar('\n');
-               pretty_print_string_list(other_cmds, longest);
+               pretty_print_string_list(other_cmds, colopts);
                putchar('\n');
        }
 }
@@ -341,7 +324,7 @@ const char *help_unknown_cmd(const char *cmd)
              sizeof(*main_cmds.names), levenshtein_compare);
 
        if (!main_cmds.cnt)
-               die ("Uh oh. Your system reports no Git commands at all.");
+               die(_("Uh oh. Your system reports no Git commands at all."));
 
        /* skip and count prefix matches */
        for (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)
@@ -362,23 +345,26 @@ const char *help_unknown_cmd(const char *cmd)
                const char *assumed = main_cmds.names[0]->name;
                main_cmds.names[0] = NULL;
                clean_cmdnames(&main_cmds);
-               fprintf(stderr, "WARNING: You called a Git command named '%s', "
-                       "which does not exist.\n"
-                       "Continuing under the assumption that you meant '%s'\n",
+               fprintf_ln(stderr,
+                          _("WARNING: You called a Git command named '%s', "
+                            "which does not exist.\n"
+                            "Continuing under the assumption that you meant '%s'"),
                        cmd, assumed);
                if (autocorrect > 0) {
-                       fprintf(stderr, "in %0.1f seconds automatically...\n",
+                       fprintf_ln(stderr, _("in %0.1f seconds automatically..."),
                                (float)autocorrect/10.0);
                        poll(NULL, 0, autocorrect * 100);
                }
                return assumed;
        }
 
-       fprintf(stderr, "git: '%s' is not a git command. See 'git --help'.\n", cmd);
+       fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
 
        if (SIMILAR_ENOUGH(best_similarity)) {
-               fprintf(stderr, "\nDid you mean %s?\n",
-                       n < 2 ? "this": "one of these");
+               fprintf_ln(stderr,
+                          Q_("\nDid you mean this?",
+                             "\nDid you mean one of these?",
+                          n));
 
                for (i = 0; i < n; i++)
                        fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
diff --git a/help.h b/help.h
index b6b12d5754888438085bb4e574b034951ffec473..0ae5a124a3af9912d551caed909aac77acc59b0b 100644 (file)
--- a/help.h
+++ b/help.h
@@ -25,8 +25,6 @@ extern void add_cmdname(struct cmdnames *cmds, const char *name, int len);
 /* Here we require that excludes is a sorted list. */
 extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
 extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);
-extern void list_commands(const char *title,
-                         struct cmdnames *main_cmds,
-                         struct cmdnames *other_cmds);
+extern void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
 
 #endif /* HELP_H */
index 869d515383b5fc9c562ea7987b1cd485cce12032..f50e77fb2890207bd7385b59bdd561133991c910 100644 (file)
@@ -7,6 +7,7 @@
 #include "run-command.h"
 #include "string-list.h"
 #include "url.h"
+#include "argv-array.h"
 
 static const char content_type[] = "Content-Type";
 static const char content_length[] = "Content-Length";
@@ -317,8 +318,7 @@ static void run_service(const char **argv)
        const char *encoding = getenv("HTTP_CONTENT_ENCODING");
        const char *user = getenv("REMOTE_USER");
        const char *host = getenv("REMOTE_ADDR");
-       char *env[3];
-       struct strbuf buf = STRBUF_INIT;
+       struct argv_array env = ARGV_ARRAY_INIT;
        int gzipped_request = 0;
        struct child_process cld;
 
@@ -332,17 +332,15 @@ static void run_service(const char **argv)
        if (!host || !*host)
                host = "(none)";
 
-       memset(&env, 0, sizeof(env));
-       strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user);
-       env[0] = strbuf_detach(&buf, NULL);
-
-       strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
-       env[1] = strbuf_detach(&buf, NULL);
-       env[2] = NULL;
+       if (!getenv("GIT_COMMITTER_NAME"))
+               argv_array_pushf(&env, "GIT_COMMITTER_NAME=%s", user);
+       if (!getenv("GIT_COMMITTER_EMAIL"))
+               argv_array_pushf(&env, "GIT_COMMITTER_EMAIL=%s@http.%s",
+                                user, host);
 
        memset(&cld, 0, sizeof(cld));
        cld.argv = argv;
-       cld.env = (const char *const *)env;
+       cld.env = env.argv;
        if (gzipped_request)
                cld.in = -1;
        cld.git_cmd = 1;
@@ -357,9 +355,7 @@ static void run_service(const char **argv)
 
        if (finish_command(&cld))
                exit(1);
-       free(env[0]);
-       free(env[1]);
-       strbuf_release(&buf);
+       argv_array_clear(&env);
 }
 
 static int show_text_ref(const char *name, const unsigned char *sha1,
index f22f7e43caa3e804c5c8275ba0312d58611d7da3..1df7ab5670d0ba2823da14083934925b0f05c926 100644 (file)
@@ -1108,7 +1108,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
                                if (repo->path)
                                        url = repo->path;
                                if (strncmp(path, url, repo->path_len))
-                                       error("Parsed path '%s' does not match url: '%s'\n",
+                                       error("Parsed path '%s' does not match url: '%s'",
                                              path, url);
                                else {
                                        path += repo->path_len;
@@ -1702,7 +1702,7 @@ static int delete_remote_branch(const char *pattern, int force)
                run_active_slot(slot);
                free(url);
                if (results.curl_result != CURLE_OK)
-                       return error("DELETE request failed (%d/%ld)\n",
+                       return error("DELETE request failed (%d/%ld)",
                                     results.curl_result, results.http_code);
        } else {
                free(url);
diff --git a/http.c b/http.c
index 2ec37891f36693b278b095b51331531a23d96d36..5cb87f16f25fe3d32e2594c8c6325d562eddc9ca 100644 (file)
--- a/http.c
+++ b/http.c
@@ -917,7 +917,7 @@ static char *fetch_pack_index(unsigned char *sha1, const char *base_url)
        tmp = strbuf_detach(&buf, NULL);
 
        if (http_get_file(url, tmp, 0) != HTTP_OK) {
-               error("Unable to get pack index %s\n", url);
+               error("Unable to get pack index %s", url);
                free(tmp);
                tmp = NULL;
        }
index 972ad62cd92ff2b09fb5caa21b24f471009ef19b..d42e4712972794f055aec6630ba86797d7e5343c 100644 (file)
@@ -1022,7 +1022,7 @@ static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const cha
 
        ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
        if (ret != strlen(response))
-               return error("IMAP error: sending response failed\n");
+               return error("IMAP error: sending response failed");
 
        free(response);
 
index da59738c9b787ae082ad062a91345a60628e704a..f3f7692158666ffd2ab6f65f4040462e4a7d2d00 100644 (file)
@@ -73,7 +73,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
        if (buffer_is_binary(orig->ptr, orig->size) ||
            buffer_is_binary(src1->ptr, src1->size) ||
            buffer_is_binary(src2->ptr, src2->size)) {
-               warning("Cannot merge binary files: %s (%s vs. %s)\n",
+               warning("Cannot merge binary files: %s (%s vs. %s)",
                        path, name1, name2);
                return ll_binary_merge(drv_unused, result,
                                       path,
index 34c49e7b3322d32ead03844bdc478a2e27836f16..44f02683722c5b9181f94f1a6380f8ad191eb08f 100644 (file)
@@ -682,7 +682,7 @@ void show_log(struct rev_info *opt)
        if (opt->use_terminator) {
                if (!opt->missing_newline)
                        graph_show_padding(opt->graph);
-               putchar('\n');
+               putchar(opt->diffopt.line_termination);
        }
 
        strbuf_release(&msgbuf);
index 0fb174359ac12ec9dc02078f870f750915557725..680937c39e2dacb7aaa008ee77b34eb9f7c208cb 100644 (file)
@@ -2069,9 +2069,9 @@ int parse_merge_opt(struct merge_options *o, const char *s)
        else if (!prefixcmp(s, "subtree="))
                o->subtree_shift = s + strlen("subtree=");
        else if (!strcmp(s, "patience"))
-               o->xdl_opts |= XDF_PATIENCE_DIFF;
+               o->xdl_opts = DIFF_WITH_ALG(o, PATIENCE_DIFF);
        else if (!strcmp(s, "histogram"))
-               o->xdl_opts |= XDF_HISTOGRAM_DIFF;
+               o->xdl_opts = DIFF_WITH_ALG(o, HISTOGRAM_DIFF);
        else if (!strcmp(s, "ignore-space-change"))
                o->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
        else if (!strcmp(s, "ignore-all-space"))
diff --git a/mergesort.c b/mergesort.c
new file mode 100644 (file)
index 0000000..e5fdf2e
--- /dev/null
@@ -0,0 +1,73 @@
+#include "cache.h"
+#include "mergesort.h"
+
+struct mergesort_sublist {
+       void *ptr;
+       unsigned long len;
+};
+
+static void *get_nth_next(void *list, unsigned long n,
+                         void *(*get_next_fn)(const void *))
+{
+       while (n-- && list)
+               list = get_next_fn(list);
+       return list;
+}
+
+static void *pop_item(struct mergesort_sublist *l,
+                     void *(*get_next_fn)(const void *))
+{
+       void *p = l->ptr;
+       l->ptr = get_next_fn(l->ptr);
+       l->len = l->ptr ? (l->len - 1) : 0;
+       return p;
+}
+
+void *llist_mergesort(void *list,
+                     void *(*get_next_fn)(const void *),
+                     void (*set_next_fn)(void *, void *),
+                     int (*compare_fn)(const void *, const void *))
+{
+       unsigned long l;
+
+       if (!list)
+               return NULL;
+       for (l = 1; ; l *= 2) {
+               void *curr;
+               struct mergesort_sublist p, q;
+
+               p.ptr = list;
+               q.ptr = get_nth_next(p.ptr, l, get_next_fn);
+               if (!q.ptr)
+                       break;
+               p.len = q.len = l;
+
+               if (compare_fn(p.ptr, q.ptr) > 0)
+                       list = curr = pop_item(&q, get_next_fn);
+               else
+                       list = curr = pop_item(&p, get_next_fn);
+
+               while (p.ptr) {
+                       while (p.len || q.len) {
+                               void *prev = curr;
+
+                               if (!p.len)
+                                       curr = pop_item(&q, get_next_fn);
+                               else if (!q.len)
+                                       curr = pop_item(&p, get_next_fn);
+                               else if (compare_fn(p.ptr, q.ptr) > 0)
+                                       curr = pop_item(&q, get_next_fn);
+                               else
+                                       curr = pop_item(&p, get_next_fn);
+                               set_next_fn(prev, curr);
+                       }
+                       p.ptr = q.ptr;
+                       p.len = l;
+                       q.ptr = get_nth_next(p.ptr, l, get_next_fn);
+                       q.len = q.ptr ? l : 0;
+
+               }
+               set_next_fn(curr, NULL);
+       }
+       return list;
+}
diff --git a/mergesort.h b/mergesort.h
new file mode 100644 (file)
index 0000000..644cff1
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef MERGESORT_H
+#define MERGESORT_H
+
+/*
+ * Sort linked list in place.
+ * - get_next_fn() returns the next element given an element of a linked list.
+ * - set_next_fn() takes two elements A and B, and makes B the "next" element
+ *   of A on the list.
+ * - compare_fn() takes two elements A and B, and returns negative, 0, positive
+ *   as the same sign as "subtracting" B from A.
+ */
+void *llist_mergesort(void *list,
+                     void *(*get_next_fn)(const void *),
+                     void (*set_next_fn)(void *, void *),
+                     int (*compare_fn)(const void *, const void *));
+
+#endif
index 6b06297a5f06cc35cb266d6dd36c92df75a82de7..4af3451bf89471a0ab7a148aad4924a8ac8ae053 100644 (file)
--- a/object.c
+++ b/object.c
@@ -176,7 +176,7 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
                        obj = &tag->object;
                }
        } else {
-               warning("object %s has unknown type id %d\n", sha1_to_hex(sha1), type);
+               warning("object %s has unknown type id %d", sha1_to_hex(sha1), type);
                obj = NULL;
        }
        if (obj && obj->type == OBJ_NONE)
@@ -198,11 +198,22 @@ struct object *parse_object(const unsigned char *sha1)
        if (obj && obj->parsed)
                return obj;
 
+       if ((obj && obj->type == OBJ_BLOB) ||
+           (!obj && has_sha1_file(sha1) &&
+            sha1_object_info(sha1, NULL) == OBJ_BLOB)) {
+               if (check_sha1_signature(repl, NULL, 0, NULL) < 0) {
+                       error("sha1 mismatch %s", sha1_to_hex(repl));
+                       return NULL;
+               }
+               parse_blob_buffer(lookup_blob(sha1), NULL, 0);
+               return lookup_object(sha1);
+       }
+
        buffer = read_sha1_file(sha1, &type, &size);
        if (buffer) {
                if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) {
                        free(buffer);
-                       error("sha1 mismatch %s\n", sha1_to_hex(repl));
+                       error("sha1 mismatch %s", sha1_to_hex(repl));
                        return NULL;
                }
 
@@ -275,3 +286,14 @@ void object_array_remove_duplicates(struct object_array *array)
                array->nr = dst;
        }
 }
+
+void clear_object_flags(unsigned flags)
+{
+       int i;
+
+       for (i=0; i < obj_hash_size; i++) {
+               struct object *obj = obj_hash[i];
+               if (obj)
+                       obj->flags &= ~flags;
+       }
+}
index b6618d92bf04d549350d83b6237770c48734686f..6a97b6ba1a43e1d38eb07515ad298e0067628127 100644 (file)
--- a/object.h
+++ b/object.h
@@ -76,4 +76,6 @@ void add_object_array(struct object *obj, const char *name, struct object_array
 void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
 void object_array_remove_duplicates(struct object_array *);
 
+void clear_object_flags(unsigned flags);
+
 #endif /* OBJECT_H */
diff --git a/pager.c b/pager.c
index 05584dead6728ceff818630fbccaa91bb6c6b686..4dcb08d9674c252cbe2c2725f92eca2a0350dc93 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -73,7 +73,7 @@ void setup_pager(void)
 {
        const char *pager = git_pager(isatty(1));
 
-       if (!pager)
+       if (!pager || pager_in_use())
                return;
 
        /*
index def9ced739b675cbcdac7598c3baebf5423239a0..da999f8995ee79865bd4c2f72a1e009502a69926 100644 (file)
@@ -234,5 +234,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);
          PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
 #define OPT__COLOR(var, h) \
        OPT_COLOR_FLAG(0, "color", (var), (h))
+#define OPT_COLUMN(s, l, v, h) \
+       { OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback }
 
 #endif
index 066f3efd15b3926153be570fae4cd4610400c751..a43f646450a168abc69096e61e35d68f3f826261 100644 (file)
--- a/po/de.po
+++ b/po/de.po
@@ -1,13 +1,13 @@
 # German translations for Git.
-# Copyright (C) 2012 Ralf Thielow <ralf.thielow@googlemail.com>
+# Copyright (C) 2010-2012 Ralf Thielow <ralf.thielow@googlemail.com>
 # This file is distributed under the same license as the Git package.
-# Ralf Thielow <ralf.thielow@googlemail.com>, 2012.
+# Ralf Thielow <ralf.thielow@googlemail.com>, 2010, 2011, 2012.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: git 1.7.10\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2012-04-28 20:33+0800\n"
+"POT-Creation-Date: 2012-04-28 20:17+0800\n"
 "PO-Revision-Date: 2012-03-28 18:46+0200\n"
 "Last-Translator: Ralf Thielow <ralf.thielow@googlemail.com>\n"
 "Language-Team: German\n"
@@ -17,7 +17,7 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: advice.c:34
+#: advice.c:40
 #, c-format
 msgid "hint: %.*s\n"
 msgstr "Hinweis: %.*s\n"
@@ -26,7 +26,7 @@ msgstr "Hinweis: %.*s\n"
 #. * Message used both when 'git commit' fails and when
 #. * other commands doing a merge do.
 #.
-#: advice.c:64
+#: advice.c:70
 msgid ""
 "Fix them up in the work tree,\n"
 "and then use 'git add/rm <file>' as\n"
@@ -38,12 +38,12 @@ msgstr ""
 "vorgesehen, um die Auflösung zu markieren und dann einzutragen,\n"
 "oder benutze 'git commit -a'."
 
-#: commit.c:47
+#: commit.c:48
 #, c-format
 msgid "could not parse %s"
 msgstr "konnte %s nicht parsen"
 
-#: commit.c:49
+#: commit.c:50
 #, c-format
 msgid "%s %s is not a commit!"
 msgstr "%s %s ist keine Version!"
@@ -188,14 +188,14 @@ msgstr[1] ""
 "Dein Zweig und '%s' sind divergiert,\n"
 "und haben jeweils %d und %d unterschiedliche Versionen.\n"
 
-#: sequencer.c:120 builtin/merge.c:864 builtin/merge.c:985
-#: builtin/merge.c:1095 builtin/merge.c:1105
+#: sequencer.c:120 builtin/merge.c:865 builtin/merge.c:978
+#: builtin/merge.c:1088 builtin/merge.c:1098
 #, c-format
 msgid "Could not open '%s' for writing"
 msgstr "Konnte '%s' nicht zum Schreiben öffnen."
 
-#: sequencer.c:122 builtin/merge.c:334 builtin/merge.c:867
-#: builtin/merge.c:1097 builtin/merge.c:1110
+#: sequencer.c:122 builtin/merge.c:333 builtin/merge.c:868
+#: builtin/merge.c:1090 builtin/merge.c:1103
 #, c-format
 msgid "Could not write to '%s'"
 msgstr "Konnte nicht nach '%s' schreiben."
@@ -294,8 +294,8 @@ msgstr "Konnte %s nicht zurücksetzen... %s"
 msgid "could not apply %s... %s"
 msgstr "Konnte %s nicht anwenden... %s"
 
-#: sequencer.c:450 sequencer.c:909 builtin/log.c:288 builtin/log.c:713
-#: builtin/log.c:1329 builtin/log.c:1548 builtin/merge.c:348
+#: sequencer.c:450 sequencer.c:909 builtin/log.c:289 builtin/log.c:719
+#: builtin/log.c:1335 builtin/log.c:1554 builtin/merge.c:347
 #: builtin/shortlog.c:181
 msgid "revision walk setup failed"
 msgstr "Einrichtung des Revisionsgangs fehlgeschlagen"
@@ -405,8 +405,9 @@ msgstr "Unerwartetes Dateiende"
 #: sequencer.c:742
 #, c-format
 msgid "stored pre-cherry-pick HEAD file '%s' is corrupt"
-msgstr "gespeicherte \"pre-cherry-pick\" Datei der Zweigspitze (HEAD) '%s' "
-"ist beschädigt"
+msgstr ""
+"gespeicherte \"pre-cherry-pick\" Datei der Zweigspitze (HEAD) '%s' ist "
+"beschädigt"
 
 #: sequencer.c:765
 #, c-format
@@ -419,7 +420,27 @@ msgstr "Kann nicht zu initialer Version zurücksetzen."
 
 #: sequencer.c:928
 msgid "Can't cherry-pick into empty head"
-msgstr "Kann \"cherry-pick\" nicht in einen leeren Kopf ausführen."
+msgstr "Kann \"cherry-pick\" nicht in einem leerem Kopf ausführen."
+
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "Zweigspitze (HEAD) zeigt auf keinen Zweig"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "Kein solcher Zweig '%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "Kein entferntes Projektarchiv für Zweig '%s' konfiguriert."
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr "Zweig '%s' des entfernten Projektarchivs ist nicht als entfernter "
+"Übernahmezweig gespeichert"
 
 #: wt-status.c:134
 msgid "Unmerged paths:"
@@ -693,7 +714,7 @@ msgstr "Pfadspezifikation '%s' stimmt mit keinen Dateien überein"
 #: builtin/add.c:209
 #, c-format
 msgid "'%s' is beyond a symbolic link"
-msgstr "'%s' ist über einer symbolischen Verknüpfung"
+msgstr "'%s' ist über einem symbolischen Link"
 
 #: builtin/add.c:276
 msgid "Could not read the index"
@@ -802,7 +823,7 @@ msgstr "git archive: Protokollfehler"
 
 #: builtin/archive.c:71
 msgid "git archive: expected a flush"
-msgstr "git archive: erwartete eine Leerung (flush)"
+msgstr "git archive: erwartete eine Spülung (flush)"
 
 #: builtin/branch.c:137
 #, c-format
@@ -824,34 +845,34 @@ msgstr ""
 "         '%s', obwohl er mit der Zweigspitze (HEAD) zusammengeführt wurde."
 
 #. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
-#: builtin/branch.c:163
+#: builtin/branch.c:164
 msgid "remote "
 msgstr "entfernter "
 
-#: builtin/branch.c:171
+#: builtin/branch.c:172
 msgid "cannot use -a with -d"
 msgstr "kann -a nicht mit -d benutzen"
 
-#: builtin/branch.c:177
+#: builtin/branch.c:178
 msgid "Couldn't look up commit object for HEAD"
 msgstr "Konnte Versionsobjekt für Zweigspitze (HEAD) nicht nachschlagen."
 
-#: builtin/branch.c:182
+#: builtin/branch.c:183
 #, c-format
 msgid "Cannot delete the branch '%s' which you are currently on."
 msgstr "Kann Zweig '%s' nicht entfernen auf dem du dich gerade befindest."
 
-#: builtin/branch.c:192
+#: builtin/branch.c:193
 #, c-format
 msgid "%sbranch '%s' not found."
 msgstr "%sZweig '%s' nicht gefunden."
 
-#: builtin/branch.c:200
+#: builtin/branch.c:201
 #, c-format
 msgid "Couldn't look up commit object for '%s'"
 msgstr "Konnte Versionsobjekt für '%s' nicht nachschlagen."
 
-#: builtin/branch.c:206
+#: builtin/branch.c:207
 #, c-format
 msgid ""
 "The branch '%s' is not fully merged.\n"
@@ -860,97 +881,97 @@ msgstr ""
 "Der Zweig '%s' ist nicht vollständig zusammengeführt.\n"
 "Wenn du sicher bist diesen Zweig zu entfernen, führe 'git branch -D %s' aus."
 
-#: builtin/branch.c:214
+#: builtin/branch.c:215
 #, c-format
 msgid "Error deleting %sbranch '%s'"
 msgstr "Fehler beim Löschen von %sZweig '%s'"
 
-#: builtin/branch.c:219
+#: builtin/branch.c:221
 #, c-format
 msgid "Deleted %sbranch %s (was %s).\n"
 msgstr "Entferne %sZweig %s (war %s).\n"
 
-#: builtin/branch.c:224
+#: builtin/branch.c:226
 msgid "Update of config-file failed"
 msgstr "Aktualisierung der Konfigurationsdatei fehlgeschlagen."
 
-#: builtin/branch.c:322
+#: builtin/branch.c:324
 #, c-format
 msgid "branch '%s' does not point at a commit"
 msgstr "Zweig '%s' zeigt auf keine Version"
 
-#: builtin/branch.c:394
+#: builtin/branch.c:396
 #, c-format
 msgid "behind %d] "
 msgstr "hinter %d] "
 
-#: builtin/branch.c:396
+#: builtin/branch.c:398
 #, c-format
 msgid "ahead %d] "
 msgstr "vor %d] "
 
-#: builtin/branch.c:398
+#: builtin/branch.c:400
 #, c-format
 msgid "ahead %d, behind %d] "
 msgstr "vor %d, hinter %d] "
 
-#: builtin/branch.c:501
+#: builtin/branch.c:503
 msgid "(no branch)"
 msgstr "(kein Zweig)"
 
-#: builtin/branch.c:566
+#: builtin/branch.c:568
 msgid "some refs could not be read"
 msgstr "einige Referenzen konnten nicht gelesen werden"
 
-#: builtin/branch.c:579
+#: builtin/branch.c:581
 msgid "cannot rename the current branch while not on any."
 msgstr ""
 "Kann aktuellen Zweig nicht umbennen, solange du dich auf keinem befindest."
 
-#: builtin/branch.c:589
+#: builtin/branch.c:591
 #, c-format
 msgid "Invalid branch name: '%s'"
 msgstr "Ungültiger Zweig-Name: '%s'"
 
-#: builtin/branch.c:604
+#: builtin/branch.c:606
 msgid "Branch rename failed"
 msgstr "Umbenennung des Zweiges fehlgeschlagen"
 
-#: builtin/branch.c:608
+#: builtin/branch.c:610
 #, c-format
 msgid "Renamed a misnamed branch '%s' away"
 msgstr "falsch benannten Zweig '%s' umbenannt"
 
-#: builtin/branch.c:612
+#: builtin/branch.c:614
 #, c-format
 msgid "Branch renamed to %s, but HEAD is not updated!"
 msgstr "Zweig umbenannt zu %s, aber Zweigspitze (HEAD) ist nicht aktualisiert!"
 
-#: builtin/branch.c:619
+#: builtin/branch.c:621
 msgid "Branch is renamed, but update of config-file failed"
 msgstr ""
 "Zweig ist umbenannt, aber die Aktualisierung der Konfigurationsdatei ist "
 "fehlgeschlagen."
 
-#: builtin/branch.c:634
+#: builtin/branch.c:636
 #, c-format
 msgid "malformed object name %s"
 msgstr "Missgebildeter Objektname %s"
 
-#: builtin/branch.c:658
+#: builtin/branch.c:660
 #, c-format
 msgid "could not write branch description template: %s\n"
 msgstr "Konnte Beschreibungsvorlage für Zweig nicht schreiben: %s\n"
 
-#: builtin/branch.c:746
+#: builtin/branch.c:750
 msgid "Failed to resolve HEAD as a valid ref."
 msgstr "Zweigspitze (HEAD) konnte nicht als gültige Referenz aufgelöst werden."
 
-#: builtin/branch.c:751 builtin/clone.c:558
+#: builtin/branch.c:755 builtin/clone.c:558
 msgid "HEAD not found below refs/heads!"
 msgstr "Zweigspitze (HEAD) wurde nicht unter \"refs/heads\" gefunden!"
 
-#: builtin/branch.c:809
+#: builtin/branch.c:813
 msgid "-a and -r options to 'git branch' do not make sense with a branch name"
 msgstr ""
 "Die Optionen -a und -r bei 'git branch' machen mit einem Zweignamen keinen "
@@ -1014,7 +1035,7 @@ msgid "path '%s' is unmerged"
 msgstr "Pfad '%s' ist nicht zusammengeführt."
 
 #: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
-#: builtin/merge.c:811
+#: builtin/merge.c:812
 msgid "unable to write new index file"
 msgstr "Konnte neue Bereitstellungsdatei nicht schreiben."
 
@@ -1031,42 +1052,42 @@ msgstr "Du musst zuerst deine aktuelle Bereitstellung auflösen."
 msgid "Can not do reflog for '%s'\n"
 msgstr "Konnte \"reflog\" für '%s' nicht durchführen\n"
 
-#: builtin/checkout.c:565
+#: builtin/checkout.c:566
 msgid "HEAD is now at"
 msgstr "Zweigspitze (HEAD) ist jetzt bei"
 
-#: builtin/checkout.c:572
+#: builtin/checkout.c:573
 #, c-format
 msgid "Reset branch '%s'\n"
 msgstr "Setze Zweig '%s' zurück\n"
 
-#: builtin/checkout.c:575
+#: builtin/checkout.c:576
 #, c-format
 msgid "Already on '%s'\n"
 msgstr "Bereits auf '%s'\n"
 
-#: builtin/checkout.c:579
+#: builtin/checkout.c:580
 #, c-format
 msgid "Switched to and reset branch '%s'\n"
 msgstr "Gewechselt zu zurückgesetztem Zweig '%s'\n"
 
-#: builtin/checkout.c:581
+#: builtin/checkout.c:582
 #, c-format
 msgid "Switched to a new branch '%s'\n"
 msgstr "Gewechselt zu einem neuen Zweig '%s'\n"
 
-#: builtin/checkout.c:583
+#: builtin/checkout.c:584
 #, c-format
 msgid "Switched to branch '%s'\n"
 msgstr "Gewechselt zu Zweig '%s'\n"
 
-#: builtin/checkout.c:639
+#: builtin/checkout.c:640
 #, c-format
 msgid " ... and %d more.\n"
 msgstr " ... und %d weitere.\n"
 
 #. The singular version
-#: builtin/checkout.c:645
+#: builtin/checkout.c:646
 #, c-format
 msgid ""
 "Warning: you are leaving %d commit behind, not connected to\n"
@@ -1089,7 +1110,7 @@ msgstr[1] ""
 "\n"
 "%s\n"
 
-#: builtin/checkout.c:663
+#: builtin/checkout.c:664
 #, c-format
 msgid ""
 "If you want to keep them by creating a new branch, this may be a good time\n"
@@ -1104,71 +1125,71 @@ msgstr ""
 " git branch neuer_zweig_name %s\n"
 "\n"
 
-#: builtin/checkout.c:692
+#: builtin/checkout.c:693
 msgid "internal error in revision walk"
 msgstr "interner Fehler im Revisionsgang"
 
-#: builtin/checkout.c:696
+#: builtin/checkout.c:697
 msgid "Previous HEAD position was"
 msgstr "Vorherige Position der Zweigspitze (HEAD) war"
 
-#: builtin/checkout.c:722
+#: builtin/checkout.c:723
 msgid "You are on a branch yet to be born"
 msgstr "Du bist auf einem Zweig, der noch nicht geboren wurde."
 
 #. case (1)
-#: builtin/checkout.c:853
+#: builtin/checkout.c:854
 #, c-format
 msgid "invalid reference: %s"
 msgstr "Ungültige Referenz: %s"
 
 #. case (1): want a tree
-#: builtin/checkout.c:892
+#: builtin/checkout.c:893
 #, c-format
 msgid "reference is not a tree: %s"
 msgstr "Referenz ist kein Baum: %s"
 
-#: builtin/checkout.c:972
+#: builtin/checkout.c:973
 msgid "-B cannot be used with -b"
 msgstr "-B kann nicht mit -b benutzt werden"
 
-#: builtin/checkout.c:981
+#: builtin/checkout.c:982
 msgid "--patch is incompatible with all other options"
 msgstr "--patch ist inkompatibel mit allen anderen Optionen"
 
-#: builtin/checkout.c:984
+#: builtin/checkout.c:985
 msgid "--detach cannot be used with -b/-B/--orphan"
 msgstr "--detach kann nicht mit -b/-B/--orphan benutzt werden"
 
-#: builtin/checkout.c:986
+#: builtin/checkout.c:987
 msgid "--detach cannot be used with -t"
 msgstr "--detach kann nicht mit -t benutzt werden"
 
-#: builtin/checkout.c:992
+#: builtin/checkout.c:993
 msgid "--track needs a branch name"
 msgstr "--track benötigt einen Zweignamen"
 
-#: builtin/checkout.c:999
+#: builtin/checkout.c:1000
 msgid "Missing branch name; try -b"
 msgstr "Vermisse Zweignamen; versuche -b"
 
-#: builtin/checkout.c:1005
+#: builtin/checkout.c:1006
 msgid "--orphan and -b|-B are mutually exclusive"
 msgstr "--orphan und -b|-B sind gegenseitig exklusiv"
 
-#: builtin/checkout.c:1007
+#: builtin/checkout.c:1008
 msgid "--orphan cannot be used with -t"
 msgstr "--orphan kann nicht mit -t benutzt werden"
 
-#: builtin/checkout.c:1017
+#: builtin/checkout.c:1018
 msgid "git checkout: -f and -m are incompatible"
 msgstr "git checkout: -f und -m sind inkompatibel"
 
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1052
 msgid "invalid path specification"
 msgstr "ungültige Pfadspezifikation"
 
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1060
 #, c-format
 msgid ""
 "git checkout: updating paths is incompatible with switching branches.\n"
@@ -1179,17 +1200,17 @@ msgstr ""
 "Hast du beabsichtigt '%s' auszuchecken, welcher nicht als Version aufgelöst "
 "werden kann?"
 
-#: builtin/checkout.c:1061
+#: builtin/checkout.c:1062
 msgid "git checkout: updating paths is incompatible with switching branches."
 msgstr ""
 "git checkout: Aktualisierung von Pfaden ist inkompatibel mit dem Wechsel von "
 "Zweigen."
 
-#: builtin/checkout.c:1066
+#: builtin/checkout.c:1067
 msgid "git checkout: --detach does not take a path argument"
 msgstr "git checkout: --detach nimmt kein Pfad-Argument"
 
-#: builtin/checkout.c:1069
+#: builtin/checkout.c:1070
 msgid ""
 "git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
 "checking out of the index."
@@ -1197,11 +1218,11 @@ msgstr ""
 "git checkout: --ours/--theirs, --force and --merge sind inkompatibel wenn\n"
 "du außerhalb der Bereitstellung auscheckst."
 
-#: builtin/checkout.c:1088
+#: builtin/checkout.c:1089
 msgid "Cannot switch branch to a non-commit."
 msgstr "Kann Zweig nur zu einer Version wechseln."
 
-#: builtin/checkout.c:1091
+#: builtin/checkout.c:1092
 msgid "--ours/--theirs is incompatible with switching branches."
 msgstr "--ours/--theirs ist inkompatibel mit den Wechseln von Zweigen."
 
@@ -1242,12 +1263,12 @@ msgstr "Fehler beim Entfernen von %s"
 #: builtin/clean.c:166
 #, c-format
 msgid "Would not remove %s\n"
-msgstr "Würde %s nicht entfernen\n"
+msgstr "Würde nicht entfernen %s\n"
 
 #: builtin/clean.c:168
 #, c-format
 msgid "Not removing %s\n"
-msgstr "Entferne %s nicht\n"
+msgstr "Entferne nicht %s\n"
 
 #: builtin/clone.c:243
 #, c-format
@@ -1370,7 +1391,7 @@ msgstr "Weiß nicht wie %s zu klonen ist."
 #: builtin/clone.c:835
 #, c-format
 msgid "Remote branch %s not found in upstream %s"
-msgstr "entfernten Zweig %s nicht im anderen Projektarchiv %s gefunden"
+msgstr "entfernter Zweig %s nicht im anderen Projektarchiv gefunden %s"
 
 #: builtin/clone.c:842
 msgid "You appear to have cloned an empty repository."
@@ -1478,7 +1499,7 @@ msgstr "Fehlerhafter --author Parameter"
 #: builtin/commit.c:651
 #, c-format
 msgid "Malformed ident string: '%s'"
-msgstr "Fehlerhafter Identifikations-String: '%s'"
+msgstr "Fehlerhafte Identifikations-String: '%s'"
 
 #: builtin/commit.c:689 builtin/commit.c:722 builtin/commit.c:1033
 #, c-format
@@ -1639,7 +1660,7 @@ msgstr "--include/--only machen ohne Pfade keinen Sinn."
 #: builtin/commit.c:1119
 msgid "Clever... amending the last one with dirty index."
 msgstr ""
-"Klug... nachbessern der letzten Version mit einer unsauberen Bereitstellung."
+"Klug... nachbessern der letzten Version mit einem unsauberen Bereitstellung."
 
 #: builtin/commit.c:1121
 msgid "Explicit paths specified without -i nor -o; assuming --only paths..."
@@ -1662,7 +1683,7 @@ msgstr "Konnte neu erstellte Version nicht nachschlagen."
 
 #: builtin/commit.c:1317
 msgid "could not parse newly created commit"
-msgstr "Konnte neu erstellte Version nicht analysieren."
+msgstr "Konnte neulich erstellte Version nicht analysieren."
 
 #: builtin/commit.c:1358
 msgid "detached HEAD"
@@ -1705,7 +1726,7 @@ msgstr "Eintragung abgebrochen; du hast die Beschreibung nicht editiert.\n"
 msgid "Aborting commit due to empty commit message.\n"
 msgstr "Eintragung aufgrund leerer Versionsbeschreibung abgebrochen.\n"
 
-#: builtin/commit.c:1554 builtin/merge.c:935 builtin/merge.c:968
+#: builtin/commit.c:1554 builtin/merge.c:936 builtin/merge.c:961
 msgid "failed to write commit object"
 msgstr "Fehler beim Schreiben des Versionsobjektes."
 
@@ -1725,7 +1746,7 @@ msgid ""
 msgstr ""
 "Das Projektarchiv wurde aktualisiert, aber die \"new_index\"-Datei\n"
 "konnte nicht geschrieben werden. Prüfe, dass dein Speicher nicht\n"
-"voll und dein Kontingent nicht aufgebraucht ist und führe\n"
+"voll und Dein Kontingent nicht aufgebraucht ist und führe\n"
 "anschließend \"git reset HEAD\" zu Wiederherstellung aus."
 
 #: builtin/describe.c:234
@@ -1815,7 +1836,7 @@ msgstr "--dirty ist inkompatibel mit \"committish\"-Werten"
 #: builtin/diff.c:77
 #, c-format
 msgid "'%s': not a regular file or symlink"
-msgstr "'%s': keine reguläre Datei oder symbolische Verknüpfung"
+msgstr "'%s': keine reguläre Datei oder symbolischer Link"
 
 #: builtin/diff.c:220
 #, c-format
@@ -1850,68 +1871,72 @@ msgstr "unbehandeltes Objekt '%s' gegeben"
 msgid "Couldn't find remote ref HEAD"
 msgstr "Konnte entfernte Referenz der Zweigspitze (HEAD) nicht finden."
 
-#: builtin/fetch.c:252
+#: builtin/fetch.c:253
 #, c-format
 msgid "object %s not found"
 msgstr "Objekt %s nicht gefunden"
 
-#: builtin/fetch.c:258
+#: builtin/fetch.c:259
 msgid "[up to date]"
 msgstr "[aktuell]"
 
-#: builtin/fetch.c:272
+#: builtin/fetch.c:273
 #, c-format
 msgid "! %-*s %-*s -> %s  (can't fetch in current branch)"
 msgstr "! %-*s %-*s -> %s  (kann nicht im aktuellen Zweig anfordern)"
 
-#: builtin/fetch.c:273 builtin/fetch.c:351
+#: builtin/fetch.c:274 builtin/fetch.c:360
 msgid "[rejected]"
 msgstr "[zurückgewiesen]"
 
-#: builtin/fetch.c:284
+#: builtin/fetch.c:285
 msgid "[tag update]"
 msgstr "[Markierungsaktualisierung]"
 
-#: builtin/fetch.c:286 builtin/fetch.c:313 builtin/fetch.c:331
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
 msgid "  (unable to update local ref)"
 msgstr "  (kann lokale Referenz nicht aktualisieren)"
 
-#: builtin/fetch.c:298
+#: builtin/fetch.c:305
 msgid "[new tag]"
 msgstr "[neue Markierung]"
 
-#: builtin/fetch.c:302
+#: builtin/fetch.c:308
 msgid "[new branch]"
 msgstr "[neuer Zweig]"
 
-#: builtin/fetch.c:347
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[neue Referenz]"
+
+#: builtin/fetch.c:356
 msgid "unable to update local ref"
 msgstr "kann lokale Referenz nicht aktualisieren"
 
-#: builtin/fetch.c:347
+#: builtin/fetch.c:356
 msgid "forced update"
 msgstr "Aktualisierung erzwungen"
 
-#: builtin/fetch.c:353
+#: builtin/fetch.c:362
 msgid "(non-fast-forward)"
 msgstr "(kein Vorspulen)"
 
-#: builtin/fetch.c:384 builtin/fetch.c:676
+#: builtin/fetch.c:393 builtin/fetch.c:685
 #, c-format
 msgid "cannot open %s: %s\n"
 msgstr "kann %s nicht öffnen: %s\n"
 
-#: builtin/fetch.c:393
+#: builtin/fetch.c:402
 #, c-format
 msgid "%s did not send all necessary objects\n"
 msgstr "%s hat nicht alle erforderlichen Objekte gesendet\n"
 
-#: builtin/fetch.c:479
+#: builtin/fetch.c:488
 #, c-format
 msgid "From %.*s\n"
 msgstr "Von %.*s\n"
 
-#: builtin/fetch.c:490
+#: builtin/fetch.c:499
 #, c-format
 msgid ""
 "some local refs could not be updated; try running\n"
@@ -1920,57 +1945,57 @@ msgstr ""
 "Einige lokale Referenzen konnten nicht aktualisiert werden; versuche\n"
 "'git remote prune %s' um jeden älteren, widersprüchlichen Zweig zu entfernen."
 
-#: builtin/fetch.c:540
+#: builtin/fetch.c:549
 #, c-format
 msgid "   (%s will become dangling)\n"
 msgstr "   (%s wird unreferenziert werden)\n"
 
-#: builtin/fetch.c:541
+#: builtin/fetch.c:550
 #, c-format
 msgid "   (%s has become dangling)\n"
 msgstr "   (%s wurde unreferenziert)\n"
 
-#: builtin/fetch.c:548
+#: builtin/fetch.c:557
 msgid "[deleted]"
 msgstr "[gelöscht]"
 
-#: builtin/fetch.c:549
+#: builtin/fetch.c:558
 msgid "(none)"
 msgstr "(keine)"
 
-#: builtin/fetch.c:666
+#: builtin/fetch.c:675
 #, c-format
 msgid "Refusing to fetch into current branch %s of non-bare repository"
 msgstr ""
 "Ablehnung des Anforderns in aktuellen Zweig %s von einem nicht-leeren "
 "Projektarchiv"
 
-#: builtin/fetch.c:700
+#: builtin/fetch.c:709
 #, c-format
 msgid "Don't know how to fetch from %s"
 msgstr "Weiß nicht wie von %s angefordert wird."
 
-#: builtin/fetch.c:777
+#: builtin/fetch.c:786
 #, c-format
 msgid "Option \"%s\" value \"%s\" is not valid for %s"
 msgstr "Option \"%s\" Wert \"%s\" ist nicht gültig für %s"
 
-#: builtin/fetch.c:780
+#: builtin/fetch.c:789
 #, c-format
 msgid "Option \"%s\" is ignored for %s\n"
 msgstr "Option \"%s\" wird ignoriert für %s\n"
 
-#: builtin/fetch.c:879
+#: builtin/fetch.c:888
 #, c-format
 msgid "Fetching %s\n"
 msgstr "Hole %s ab\n"
 
-#: builtin/fetch.c:881
+#: builtin/fetch.c:890
 #, c-format
 msgid "Could not fetch %s"
 msgstr "Konnte %s nicht anfordern"
 
-#: builtin/fetch.c:898
+#: builtin/fetch.c:907
 msgid ""
 "No remote repository specified.  Please, specify either a URL or a\n"
 "remote name from which new revisions should be fetched."
@@ -1979,24 +2004,24 @@ msgstr ""
 "eine URL oder einen Entfernungsname, von welchem neue Revisionen angefordert "
 "werden sollen."
 
-#: builtin/fetch.c:918
+#: builtin/fetch.c:927
 msgid "You need to specify a tag name."
 msgstr "Du musst den Namen der Markierung spezifizieren."
 
-#: builtin/fetch.c:970
+#: builtin/fetch.c:979
 msgid "fetch --all does not take a repository argument"
 msgstr "fetch -all nimmt kein Projektarchiv als Argument"
 
-#: builtin/fetch.c:972
+#: builtin/fetch.c:981
 msgid "fetch --all does not make sense with refspecs"
 msgstr "fetch --all macht keinen Sinn mit Referenzspezifikationen"
 
-#: builtin/fetch.c:983
+#: builtin/fetch.c:992
 #, c-format
 msgid "No such remote or remote group: %s"
 msgstr "Keine solche Entfernung oder Entfernungsgruppe: %s"
 
-#: builtin/fetch.c:991
+#: builtin/fetch.c:1000
 msgid "Fetching a group and specifying refspecs does not make sense"
 msgstr ""
 "Abholen einer Gruppe und Spezifizieren von Referenzspezifikationen macht "
@@ -2243,103 +2268,103 @@ msgstr "Kann nicht auf aktuelles Arbeitsverzeichnis zugreifen."
 msgid "Cannot access work tree '%s'"
 msgstr "Kann nicht auf Arbeitsbaum '%s' zugreifen."
 
-#: builtin/log.c:187
+#: builtin/log.c:188
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr "letzte Ausgabe: %d %s\n"
 
-#: builtin/log.c:395 builtin/log.c:483
+#: builtin/log.c:401 builtin/log.c:489
 #, c-format
 msgid "Could not read object %s"
 msgstr "Kann Objekt %s nicht lesen."
 
-#: builtin/log.c:507
+#: builtin/log.c:513
 #, c-format
 msgid "Unknown type: %d"
 msgstr "Unbekannter Typ: %d"
 
-#: builtin/log.c:596
+#: builtin/log.c:602
 msgid "format.headers without value"
 msgstr "format.headers ohne Wert"
 
-#: builtin/log.c:669
+#: builtin/log.c:675
 msgid "name of output directory is too long"
 msgstr "Name des Ausgabeverzeichnisses ist zu lang."
 
-#: builtin/log.c:680
+#: builtin/log.c:686
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr "Kann Patch-Datei %s nicht öffnen"
 
-#: builtin/log.c:694
+#: builtin/log.c:700
 msgid "Need exactly one range."
 msgstr "Brauche genau einen Bereich."
 
-#: builtin/log.c:702
+#: builtin/log.c:708
 msgid "Not a range."
 msgstr "Kein Bereich."
 
-#: builtin/log.c:739
+#: builtin/log.c:745
 msgid "Could not extract email from committer identity."
 msgstr "Konnte E-Mail von der Intentität des Einreichers nicht extrahieren."
 
-#: builtin/log.c:785
+#: builtin/log.c:791
 msgid "Cover letter needs email format"
-msgstr "Deckblatt benötigt E-Mail-Format"
+msgstr "Anschreiben benötigt E-Mail-Format"
 
-#: builtin/log.c:879
+#: builtin/log.c:885
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr "verrücktes in-reply-to: %s"
 
-#: builtin/log.c:952
+#: builtin/log.c:958
 msgid "Two output directories?"
 msgstr "Zwei Ausgabeverzeichnisse?"
 
-#: builtin/log.c:1173
+#: builtin/log.c:1179
 #, c-format
 msgid "bogus committer info %s"
-msgstr "unechte Einreicher-Information %s"
+msgstr "unechte Einreicher-Informationen %s"
 
-#: builtin/log.c:1218
+#: builtin/log.c:1224
 msgid "-n and -k are mutually exclusive."
 msgstr "-n und -k sind zueinander exklusiv"
 
-#: builtin/log.c:1220
+#: builtin/log.c:1226
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr "--subject-prefix und -k sind zueinander exklusiv"
 
-#: builtin/log.c:1225 builtin/shortlog.c:284
+#: builtin/log.c:1231 builtin/shortlog.c:284
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr "nicht erkanntes Argument: %s"
 
-#: builtin/log.c:1228
+#: builtin/log.c:1234
 msgid "--name-only does not make sense"
 msgstr "--name-only macht keinen Sinn"
 
-#: builtin/log.c:1230
+#: builtin/log.c:1236
 msgid "--name-status does not make sense"
 msgstr "--name-status macht keinen Sinn"
 
-#: builtin/log.c:1232
+#: builtin/log.c:1238
 msgid "--check does not make sense"
 msgstr "--check macht keinen Sinn"
 
-#: builtin/log.c:1255
+#: builtin/log.c:1261
 msgid "standard output, or directory, which one?"
 msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?"
 
-#: builtin/log.c:1257
+#: builtin/log.c:1263
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "Konnte Verzeichnis '%s' nicht erstellen."
 
-#: builtin/log.c:1410
+#: builtin/log.c:1416
 msgid "Failed to create output files"
 msgstr "Fehler beim Erstellen der Ausgabedateien."
 
-#: builtin/log.c:1514
+#: builtin/log.c:1520
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> manually.\n"
@@ -2347,69 +2372,69 @@ msgstr ""
 "Konnte gefolgten, entfernten Zweig nicht finden, bitte spezifiziere "
 "<upstream> manuell.\n"
 
-#: builtin/log.c:1530 builtin/log.c:1532 builtin/log.c:1544
+#: builtin/log.c:1536 builtin/log.c:1538 builtin/log.c:1550
 #, c-format
 msgid "Unknown commit %s"
 msgstr "Unbekannte Version %s"
 
-#: builtin/merge.c:91
+#: builtin/merge.c:90
 msgid "switch `m' requires a value"
 msgstr "Schalter 'm' erfordert einen Wert."
 
-#: builtin/merge.c:128
+#: builtin/merge.c:127
 #, c-format
 msgid "Could not find merge strategy '%s'.\n"
 msgstr "Konnte Zusammenführungsstrategie '%s' nicht finden.\n"
 
-#: builtin/merge.c:129
+#: builtin/merge.c:128
 #, c-format
 msgid "Available strategies are:"
 msgstr "Verfügbare Strategien sind:"
 
-#: builtin/merge.c:134
+#: builtin/merge.c:133
 #, c-format
 msgid "Available custom strategies are:"
 msgstr "Verfügbare benutzerdefinierte Strategien sind:"
 
-#: builtin/merge.c:241
+#: builtin/merge.c:240
 msgid "could not run stash."
 msgstr "Konnte \"stash\" nicht ausführen."
 
-#: builtin/merge.c:246
+#: builtin/merge.c:245
 msgid "stash failed"
 msgstr "\"stash\" fehlgeschlagen"
 
-#: builtin/merge.c:251
+#: builtin/merge.c:250
 #, c-format
 msgid "not a valid object: %s"
 msgstr "kein gültiges Objekt: %s"
 
-#: builtin/merge.c:270 builtin/merge.c:287
+#: builtin/merge.c:269 builtin/merge.c:286
 msgid "read-tree failed"
 msgstr "read-tree fehlgeschlagen"
 
-#: builtin/merge.c:317
+#: builtin/merge.c:316
 msgid " (nothing to squash)"
 msgstr " (nichts zu quetschen)"
 
-#: builtin/merge.c:330
+#: builtin/merge.c:329
 #, c-format
 msgid "Squash commit -- not updating HEAD\n"
 msgstr "Quetsche Version -- aktualisiere Zweigspitze (HEAD) nicht\n"
 
-#: builtin/merge.c:362
+#: builtin/merge.c:361
 msgid "Writing SQUASH_MSG"
 msgstr "Schreibe SQUASH_MSG"
 
-#: builtin/merge.c:364
+#: builtin/merge.c:363
 msgid "Finishing SQUASH_MSG"
 msgstr "Schließe SQUASH_MSG ab"
 
 #: builtin/merge.c:386
 #, c-format
 msgid "No merge message -- not updating HEAD\n"
-msgstr "Keine Zusammenführungsbeschreibung -- aktualisiere Zweigspitze (HEAD) "
-"nicht\n"
+msgstr ""
+"Keine Zusammenführungsbeschreibung -- aktualisiere Zweigspitze (HEAD) nicht\n"
 
 #: builtin/merge.c:437
 #, c-format
@@ -2429,37 +2454,37 @@ msgstr "\"git write-tree\" schlug beim Schreiben eines Baumes fehl"
 msgid "failed to read the cache"
 msgstr "Lesen des Zwischenspeichers fehlgeschlagen"
 
-#: builtin/merge.c:696
+#: builtin/merge.c:697
 msgid "Unable to write index."
 msgstr "Konnte Bereitstellung nicht schreiben."
 
-#: builtin/merge.c:709
+#: builtin/merge.c:710
 msgid "Not handling anything other than two heads merge."
 msgstr "Behandle nichts anderes als die Zusammenführung von zwei Köpfen."
 
-#: builtin/merge.c:723
+#: builtin/merge.c:724
 #, c-format
 msgid "Unknown option for merge-recursive: -X%s"
 msgstr "Unbekannte Option für merge-recursive: -X%s"
 
-#: builtin/merge.c:737
+#: builtin/merge.c:738
 #, c-format
 msgid "unable to write %s"
 msgstr "konnte %s nicht schreiben"
 
-#: builtin/merge.c:876
+#: builtin/merge.c:877
 #, c-format
 msgid "Could not read from '%s'"
 msgstr "konnte nicht von '%s' lesen"
 
-#: builtin/merge.c:885
+#: builtin/merge.c:886
 #, c-format
 msgid "Not committing merge; use 'git commit' to complete the merge.\n"
 msgstr ""
 "Zusammenführung nicht eingetragen; benutze 'git commit' um die "
 "Zusammenführung abzuschließen.\n"
 
-#: builtin/merge.c:891
+#: builtin/merge.c:892
 msgid ""
 "Please enter a commit message to explain why this merge is necessary,\n"
 "especially if it merges an updated upstream into a topic branch.\n"
@@ -2469,56 +2494,61 @@ msgid ""
 msgstr ""
 "Bitte gebe eine Versionsbeschreibung ein um zu erklären, warum diese "
 "Zusammenführung erforderlich ist,\n"
-"insbesondere wenn es einen aktualisierten entfernten Zweig mit einem "
-"Thema-Zweig zusammenführt.\n"
+"insbesondere wenn es einen aktualisierten entfernten Zweig mit einem Thema-"
+"Zweig zusammenführt.\n"
 "\n"
 "Zeilen beginnend mit '#' werden ignoriert, und eine leere Beschreibung "
 "bricht die Eintragung ab.\n"
 
-#: builtin/merge.c:915
+#: builtin/merge.c:916
 msgid "Empty commit message."
-msgstr "Leere Versionsbeschreibung."
+msgstr "Leere Versionsbeschreibung"
 
-#: builtin/merge.c:927
+#: builtin/merge.c:928
 #, c-format
 msgid "Wonderful.\n"
 msgstr "Wunderbar.\n"
 
-#: builtin/merge.c:1000
+#: builtin/merge.c:993
 #, c-format
 msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
 msgstr ""
 "Automatische Zusammenführung fehlgeschlagen; behebe die Konflikte und trage "
 "dann das Ergebnis ein.\n"
 
-#: builtin/merge.c:1016
+#: builtin/merge.c:1009
 #, c-format
 msgid "'%s' is not a commit"
 msgstr "'%s' ist keine Version"
 
-#: builtin/merge.c:1057
+#: builtin/merge.c:1050
 msgid "No current branch."
 msgstr "Kein aktueller Zweig."
 
-#: builtin/merge.c:1059
+#: builtin/merge.c:1052
 msgid "No remote for the current branch."
 msgstr "Kein anderes Archiv für den aktuellen Zweig."
 
-#: builtin/merge.c:1061
+#: builtin/merge.c:1054
 msgid "No default upstream defined for the current branch."
-msgstr "Kein entferntes Standard-Projektarchiv für den aktuellen Zweig "
-"definiert."
+msgstr ""
+"Kein entferntes Standard-Projektarchiv für den aktuellen Zweig definiert."
 
-#: builtin/merge.c:1066
+#: builtin/merge.c:1059
 #, c-format
 msgid "No remote tracking branch for %s from %s"
 msgstr "Kein entfernter Übernahmezweig für %s von %s"
 
-#: builtin/merge.c:1188
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr "%s - nichts was wir zusammenführen können"
+
+#: builtin/merge.c:1214
 msgid "There is no merge to abort (MERGE_HEAD missing)."
 msgstr "Es gibt keine Zusammenführung zum Abbrechen (vermisse MERGE_HEAD)"
 
-#: builtin/merge.c:1204 git-pull.sh:31
+#: builtin/merge.c:1230 git-pull.sh:31
 msgid ""
 "You have not concluded your merge (MERGE_HEAD exists).\n"
 "Please, commit your changes before you can merge."
@@ -2526,12 +2556,12 @@ msgstr ""
 "Du hast deine Zusammenführung nicht abgeschlossen (MERGE_HEAD existiert).\n"
 "Bitte trage deine Änderungen ein, bevor du zusammenführen kannst."
 
-#: builtin/merge.c:1207 git-pull.sh:34
+#: builtin/merge.c:1233 git-pull.sh:34
 msgid "You have not concluded your merge (MERGE_HEAD exists)."
 msgstr ""
 "Du hast deine Zusammenführung nicht abgeschlossen (MERGE_HEAD existiert)."
 
-#: builtin/merge.c:1211
+#: builtin/merge.c:1237
 msgid ""
 "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
 "Please, commit your changes before you can merge."
@@ -2540,87 +2570,82 @@ msgstr ""
 "existiert).\n"
 "Bitte trage deine Änderungen ein, bevor du zusammenführen kannst."
 
-#: builtin/merge.c:1214
+#: builtin/merge.c:1240
 msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
 msgstr ""
 "Du hast deinen \"cherry-pick\" nicht abgeschlossen (CHERRY_PICK_HEAD "
 "existiert)."
 
-#: builtin/merge.c:1223
+#: builtin/merge.c:1249
 msgid "You cannot combine --squash with --no-ff."
 msgstr "Du kannst --squash nicht mit --no-ff kombinieren."
 
-#: builtin/merge.c:1228
+#: builtin/merge.c:1254
 msgid "You cannot combine --no-ff with --ff-only."
 msgstr "Du kannst --no-ff nicht mit --ff--only kombinieren."
 
-#: builtin/merge.c:1235
+#: builtin/merge.c:1261
 msgid "No commit specified and merge.defaultToUpstream not set."
 msgstr "Keine Version spezifiziert und merge.defaultToUpstream nicht gesetzt."
 
-#: builtin/merge.c:1266
+#: builtin/merge.c:1293
 msgid "Can merge only exactly one commit into empty head"
 msgstr "Kann nur exakt eine Version in einem leeren Kopf zusammenführen."
 
-#: builtin/merge.c:1269
+#: builtin/merge.c:1296
 msgid "Squash commit into empty head not supported yet"
 msgstr ""
 "Quetschen einer Version in einen leeren Kopf wird momentan nicht unterstützt."
 
-#: builtin/merge.c:1271
+#: builtin/merge.c:1298
 msgid "Non-fast-forward commit does not make sense into an empty head"
 msgstr "nicht vorzuspulende Version macht in einem leeren Kopf keinen Sinn"
 
-#: builtin/merge.c:1275 builtin/merge.c:1319
-#, c-format
-msgid "%s - not something we can merge"
-msgstr "%s - nichts was wir zusammenführen können"
-
-#: builtin/merge.c:1382
+#: builtin/merge.c:1413
 #, c-format
 msgid "Updating %s..%s\n"
 msgstr "Aktualisiere %s..%s\n"
 
-#: builtin/merge.c:1420
+#: builtin/merge.c:1451
 #, c-format
 msgid "Trying really trivial in-index merge...\n"
 msgstr "Probiere wirklich triviale \"in-index\"-Zusammenführung...\n"
 
-#: builtin/merge.c:1427
+#: builtin/merge.c:1458
 #, c-format
 msgid "Nope.\n"
 msgstr "Nein.\n"
 
-#: builtin/merge.c:1459
+#: builtin/merge.c:1490
 msgid "Not possible to fast-forward, aborting."
 msgstr "Vorspulen nicht möglich, breche ab."
 
-#: builtin/merge.c:1482 builtin/merge.c:1559
+#: builtin/merge.c:1513 builtin/merge.c:1592
 #, c-format
 msgid "Rewinding the tree to pristine...\n"
 msgstr "Rücklauf des Baumes bis zum Ursprung...\n"
 
-#: builtin/merge.c:1486
+#: builtin/merge.c:1517
 #, c-format
 msgid "Trying merge strategy %s...\n"
 msgstr "Probiere Zusammenführungsstrategie %s...\n"
 
-#: builtin/merge.c:1550
+#: builtin/merge.c:1583
 #, c-format
 msgid "No merge strategy handled the merge.\n"
 msgstr "Keine Zusammenführungsstrategie behandelt die Zusammenführung.\n"
 
-#: builtin/merge.c:1552
+#: builtin/merge.c:1585
 #, c-format
 msgid "Merge with strategy %s failed.\n"
 msgstr "Zusammenführung mit Strategie %s fehlgeschlagen.\n"
 
-#: builtin/merge.c:1561
+#: builtin/merge.c:1594
 #, c-format
 msgid "Using the %s to prepare resolving by hand.\n"
 msgstr "Benutze \"%s\" um die Auflösung per Hand vorzubereiten.\n"
 
-#: builtin/merge.c:1572
+#: builtin/merge.c:1606
 #, c-format
 msgid "Automatic merge went well; stopped before committing as requested\n"
 msgstr ""
@@ -2647,7 +2672,7 @@ msgstr "kann Verzeichnis nicht über Datei verschieben"
 #: builtin/mv.c:128
 #, c-format
 msgid "Huh? %.*s is in index?"
-msgstr "Huh? %.*s ist bereitgestellt?"
+msgstr "Huh? %.*s ist in der Bereitstellung?"
 
 #: builtin/mv.c:140
 msgid "source directory is empty"
@@ -2868,15 +2893,15 @@ msgstr "Option %s akzeptiert keine negative Form"
 msgid "unable to parse value '%s' for option %s"
 msgstr "konnte Wert '%s' für Option %s nicht analysieren"
 
-#: builtin/push.c:44
+#: builtin/push.c:45
 msgid "tag shorthand without <tag>"
 msgstr "Kurzschrift für Markierung ohne <Markierung>"
 
-#: builtin/push.c:63
+#: builtin/push.c:64
 msgid "--delete only accepts plain target ref names"
 msgstr "--delete akzeptiert nur reine Referenz-Namen als Ziel"
 
-#: builtin/push.c:83
+#: builtin/push.c:84
 #, c-format
 msgid ""
 "You are not currently on a branch.\n"
@@ -2891,7 +2916,7 @@ msgstr ""
 "\n"
 "    git push %s HEAD:<Name-des-entfernten-Zweiges>\n"
 
-#: builtin/push.c:90
+#: builtin/push.c:91
 #, c-format
 msgid ""
 "The current branch %s has no upstream branch.\n"
@@ -2905,13 +2930,13 @@ msgstr ""
 "\n"
 "    git push --set-upstream %s %s\n"
 
-#: builtin/push.c:98
+#: builtin/push.c:99
 #, c-format
 msgid "The current branch %s has multiple upstream branches, refusing to push."
 msgstr ""
 "Der aktuelle Zweig %s hat mehrere entfernte Zweige, Versand verweigert."
 
-#: builtin/push.c:101
+#: builtin/push.c:102
 #, c-format
 msgid ""
 "You are pushing to remote '%s', which is not the upstream of\n"
@@ -2920,44 +2945,71 @@ msgid ""
 msgstr ""
 "Du versendest nach '%s', welches kein entferntes Projektarchiv deines\n"
 "aktuellen Zweiges '%s' ist, ohne mir mitzuteilen, was ich versenden\n"
-"soll, um welchen entfernten Zweig zu aktualisieren."
+"soll um welchen entfernten Zweig zu aktualisieren."
 
-#: builtin/push.c:127
+#: builtin/push.c:131
 msgid ""
 "You didn't specify any refspecs to push, and push.default is \"nothing\"."
 msgstr ""
-"Du hast keine Referenzspezifikationen zum Versenden spezifiziert, und "
-"push.default ist \"nothing\"."
+"Du hast keine Referenzspezifikationen zum Versenden spezifiziert, und push."
+"default ist \"nothing\"."
 
-#: builtin/push.c:147
+#: builtin/push.c:138
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"Aktualisierungen wurden zurückgewiesen, weil die Spitze deines aktuellen\n"
+"Zweiges hinter seinem entfernten Gegenstück ist. Führe die entfernten\n"
+"Änderungen zusammen (z.B. 'git pull') bevor du erneut versendest.\n"
+"Siehe auch die 'Note about fast-forwards' Sektion von 'git push --help'\n"
+"für weitere Details."
+
+#: builtin/push.c:144
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+"Aktualisierungen wurden zurückgewiesen, weil die Spitze eines versendeten\n"
+"Zweiges hinter seinem entfernten Gegenstück ist. Wenn du nicht beabsichtigt\n"
+"hast, diesen Zweig zu versenden, kannst du auch den zu versendenden Zweig\n"
+"spezifizieren oder die Konfigurationsvariable 'push.default' zu 'current'\n"
+"oder 'upstream' setze, um nur den aktuellen Zweig zu versenden."
+
+#: builtin/push.c:150
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"Aktualisierungen wurden zurückgewiesen, weil die Spitze eines versendeten\n"
+"Zweiges hinter seinem entfernten Gegenstück ist. Checke diesen Zweig aus\n"
+"und führe die entfernten Änderungen zusammen (z.B. 'git pull') bevor du\n"
+"erneut versendest.\n"
+"Sie auch die 'Note about fast-forwards' Sektion von 'git push --help'\n"
+"für weitere Details."
+
+#: builtin/push.c:190
 #, c-format
 msgid "Pushing to %s\n"
-msgstr "Sende nach %s\n"
+msgstr "Schiebe zu %s\n"
 
-#: builtin/push.c:151
+#: builtin/push.c:194
 #, c-format
 msgid "failed to push some refs to '%s'"
 msgstr "Fehler beim Versenden einiger Referenzen nach '%s'"
 
-#: builtin/push.c:159
-#, c-format
-msgid ""
-"To prevent you from losing history, non-fast-forward updates were rejected\n"
-"Merge the remote changes (e.g. 'git pull') before pushing again.  See the\n"
-"'Note about fast-forwards' section of 'git push --help' for details.\n"
-msgstr ""
-"Um dich vor Verlust von Historie zu bewahren, wurden nicht vorzuspulende "
-"Aktualisierungen zurückgewiesen.\n"
-"Führe die entfernten Änderungen zusammen (z.B. 'git pull') bevor du erneut "
-"versendest. Siehe auch die 'Note about fast-forwards' Sektion von \n"
-"'git push --help' für weitere Details.\n"
-
-#: builtin/push.c:176
+#: builtin/push.c:226
 #, c-format
 msgid "bad repository '%s'"
 msgstr "schlechtes Projektarchiv '%s'"
 
-#: builtin/push.c:177
+#: builtin/push.c:227
 msgid ""
 "No configured push destination.\n"
 "Either specify the URL from the command-line or configure a remote "
@@ -2979,31 +3031,31 @@ msgstr ""
 "\n"
 "    git push <Name>\n"
 
-#: builtin/push.c:192
+#: builtin/push.c:242
 msgid "--all and --tags are incompatible"
 msgstr "--all und --tags sind inkompatibel"
 
-#: builtin/push.c:193
+#: builtin/push.c:243
 msgid "--all can't be combined with refspecs"
 msgstr "--all kann nicht mit Referenzspezifikationen kombiniert werden"
 
-#: builtin/push.c:198
+#: builtin/push.c:248
 msgid "--mirror and --tags are incompatible"
 msgstr "--mirror und --tags sind inkompatibel"
 
-#: builtin/push.c:199
+#: builtin/push.c:249
 msgid "--mirror can't be combined with refspecs"
 msgstr "--mirror kann nicht mit Referenzspezifikationen kombiniert werden"
 
-#: builtin/push.c:204
+#: builtin/push.c:254
 msgid "--all and --mirror are incompatible"
 msgstr "--all und --mirror sind inkompatibel"
 
-#: builtin/push.c:284
+#: builtin/push.c:342
 msgid "--delete is incompatible with --all, --mirror and --tags"
 msgstr "--delete ist inkompatibel mit --all, --mirror und --tags"
 
-#: builtin/push.c:286
+#: builtin/push.c:344
 msgid "--delete doesn't make sense without any refs"
 msgstr "--delete macht ohne irgendeine Referenz ohne keinen Sinn"
 
@@ -3088,20 +3140,20 @@ msgstr "%s Zurücksetzung ist in einem leeren Projektarchiv nicht erlaubt"
 msgid "Could not reset index file to revision '%s'."
 msgstr "Konnte Bereitstellungsdatei nicht zu Revision '%s' zurücksetzen."
 
-#: builtin/revert.c:70 builtin/revert.c:91
+#: builtin/revert.c:70 builtin/revert.c:92
 #, c-format
 msgid "%s: %s cannot be used with %s"
 msgstr "%s: %s kann nicht mit %s benutzt werden"
 
-#: builtin/revert.c:126
+#: builtin/revert.c:127
 msgid "program error"
 msgstr "Programmfehler"
 
-#: builtin/revert.c:209
+#: builtin/revert.c:213
 msgid "revert failed"
 msgstr "\"revert\" fehlgeschlagen"
 
-#: builtin/revert.c:224
+#: builtin/revert.c:228
 msgid "cherry-pick failed"
 msgstr "\"cherry-pick\" fehlgeschlagen"
 
@@ -3227,7 +3279,7 @@ msgstr "keine Markierungsbeschreibung?"
 #: builtin/tag.c:372
 #, c-format
 msgid "The tag message has been left in %s\n"
-msgstr "Die Markierungsbeschreibung wurde in %s gelassen\n"
+msgstr "Die Markierungsbeschreibung wurde gelassen in %s\n"
 
 #: builtin/tag.c:421
 msgid "switch 'points-at' requires an object"
@@ -3283,17 +3335,17 @@ msgstr "%s: kann Referenz nicht aktualisieren"
 msgid "Updated tag '%s' (was %s)\n"
 msgstr "Aktualisierte Markierung '%s' (war %s)\n"
 
-#: git-am.sh:49
+#: git-am.sh:50
 msgid "You need to set your committer info first"
 msgstr "Du musst zuerst die Informationen des Eintragenden setzen."
 
-#: git-am.sh:136
+#: git-am.sh:137
 msgid "Repository lacks necessary blobs to fall back on 3-way merge."
 msgstr ""
 "Dem Projektarchiv fehlen notwendige Blobs um auf eine 3-Wege-Zusammenführung "
 "zurückzufallen."
 
-#: git-am.sh:147
+#: git-am.sh:154
 msgid ""
 "Did you hand edit your patch?\n"
 "It does not apply to blobs recorded in its index."
@@ -3302,49 +3354,49 @@ msgstr ""
 "Er kann nicht auf aufgezeichnete Blobs in seiner Bereitstellung angewendet "
 "werden."
 
-#: git-am.sh:156
+#: git-am.sh:163
 msgid "Falling back to patching base and 3-way merge..."
 msgstr "Falle zurück zum Patchen der Basis und der 3-Wege-Zusammenführung..."
 
-#: git-am.sh:268
+#: git-am.sh:275
 msgid "Only one StGIT patch series can be applied at once"
 msgstr "Es kann nur eine StGIT Patch-Serie auf einmal angewendet werden."
 
-#: git-am.sh:355
+#: git-am.sh:362
 #, sh-format
 msgid "Patch format $patch_format is not supported."
 msgstr "Patch-Format $patch_format wird nicht unterstützt."
 
-#: git-am.sh:357
+#: git-am.sh:364
 msgid "Patch format detection failed."
 msgstr "Patch-Formaterkennung fehlgeschlagen."
 
-#: git-am.sh:411
+#: git-am.sh:418
 msgid "-d option is no longer supported.  Do not use."
 msgstr "-d Option wird nicht länger unterstützt. Nicht benutzen."
 
-#: git-am.sh:474
+#: git-am.sh:481
 #, sh-format
 msgid "previous rebase directory $dotest still exists but mbox given."
 msgstr ""
 "Vorheriges Verzeichnis des Neuaufbaus $dotest existiert noch, aber mbox "
 "gegeben."
 
-#: git-am.sh:479
+#: git-am.sh:486
 msgid "Please make up your mind. --skip or --abort?"
 msgstr "Bitte werde dir klar. --skip oder --abort?"
 
-#: git-am.sh:506
+#: git-am.sh:513
 msgid "Resolve operation not in progress, we are not resuming."
 msgstr "keine Auflösung in Durchführung, wir setzen nicht fort."
 
-#: git-am.sh:572
+#: git-am.sh:579
 #, sh-format
 msgid "Dirty index: cannot apply patches (dirty: $files)"
 msgstr ""
 "Unsaubere Bereitstellung: kann Patches nicht anwenden (unsauber: $files)"
 
-#: git-am.sh:748
+#: git-am.sh:755
 msgid "cannot be interactive without stdin connected to a terminal."
 msgstr ""
 "Kann nicht interaktiv sein, ohne das die Standard-Eingabe mit einem Terminal "
@@ -3353,20 +3405,20 @@ 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:759
+#: git-am.sh:766
 msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
 msgstr "Anwenden? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
 
-#: git-am.sh:795
+#: git-am.sh:802
 #, sh-format
 msgid "Applying: $FIRSTLINE"
 msgstr "Wende an: $FIRSTLINE"
 
-#: git-am.sh:840
+#: git-am.sh:847
 msgid "No changes -- Patch already applied."
 msgstr "Keine Änderungen -- Patches bereits angewendet."
 
-#: git-am.sh:866
+#: git-am.sh:873
 msgid "applying to an empty history"
 msgstr "wende zu leerer Historie an"
 
@@ -3458,7 +3510,7 @@ msgstr "Keine Log-Datei gegeben"
 #: git-bisect.sh:391
 #, sh-format
 msgid "cannot read $file for replaying"
-msgstr "kann $file nicht zum neu abspielen lesen"
+msgstr "kann $file nicht für das Abspielen lesen"
 
 #: git-bisect.sh:408
 msgid "?? what are you talking about?"
@@ -3567,7 +3619,7 @@ msgstr "'$args' ist keine \"stash\"-Referenz"
 
 #: git-stash.sh:412
 msgid "unable to refresh index"
-msgstr "Konnte die Bereitstellung nicht aktualisieren"
+msgstr "unfähig die Bereitstellung zu aktualisieren"
 
 #: git-stash.sh:416
 msgid "Cannot apply a stash in the middle of a merge"
@@ -3601,171 +3653,173 @@ msgstr "Kein Zweigname spezifiziert"
 
 #: git-stash.sh:570
 msgid "(To restore them type \"git stash apply\")"
-msgstr "(Um es wiederherzustellen, schreibe \"git stash apply\")"
+msgstr "(Um es wiederherzustellen schreibe \"git stash apply\")"
 
 #: git-submodule.sh:56
 #, sh-format
 msgid "cannot strip one component off url '$remoteurl'"
-msgstr "Kann eine Komponente von URL '$remoteurl' nicht rausziehen"
+msgstr "Kann eine Komponente von URL '$remoteurl' nicht abstreifen"
 
-#: git-submodule.sh:108
+#: git-submodule.sh:109
 #, sh-format
-msgid "No submodule mapping found in .gitmodules for path '$path'"
-msgstr "Keine Unterprojekt-Zuordnung in .gitmodules für Pfad '$path' gefunden"
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr "Keine Unterprojekt-Zuordnung in .gitmodules für Pfad '$sm_path' gefunden"
 
-#: git-submodule.sh:149
+#: git-submodule.sh:150
 #, sh-format
-msgid "Clone of '$url' into submodule path '$path' failed"
-msgstr "Klonen von '$url' in Unterprojekt-Pfad '$path' fehlgeschlagen"
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr "Klonen von '$url' in Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
 
-#: git-submodule.sh:159
+#: git-submodule.sh:160
 #, sh-format
 msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
-msgstr "Git-Verzeichnis '$a' ist Teil des Unterprojekt-Pfades '$b' oder umgekehrt"
+msgstr ""
+"Git-Verzeichnis '$a' ist Teil des Unterprojekt-Pfades '$b' oder umgekehrt"
 
-#: git-submodule.sh:247
+#: git-submodule.sh:249
 #, sh-format
 msgid "repo URL: '$repo' must be absolute or begin with ./|../"
 msgstr "repo URL: '$repo' muss absolut sein oder mit ./|../ beginnen"
 
-#: git-submodule.sh:264
-#, sh-format
-msgid "'$path' already exists in the index"
-msgstr "'$path' existiert bereits in der Bereitstellung"
+#: git-submodule.sh:266
+#,  sh-format
+msgid "'$sm_path' already exists in the index"
+msgstr "'$sm_path' existiert bereits in der Bereitstellung"
 
-#: git-submodule.sh:281
+#: git-submodule.sh:283
 #, sh-format
-msgid "'$path' already exists and is not a valid git repo"
-msgstr "'$path' existiert bereits und ist kein gültiges Git-Projektarchiv"
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "'$sm_path' existiert bereits und ist kein gültiges Git-Projektarchiv"
 
-#: git-submodule.sh:295
+#: git-submodule.sh:297
 #, sh-format
-msgid "Unable to checkout submodule '$path'"
-msgstr "Unfähig Unterprojekt '$path' auszuchecken"
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr "Unfähig Unterprojekt '$sm_path' auszuchecken"
 
-#: git-submodule.sh:300
+#: git-submodule.sh:302
 #, sh-format
-msgid "Failed to add submodule '$path'"
-msgstr "Hinzufügen von Unterprojekt '$path' fehlgeschlagen"
+msgid "Failed to add submodule '$sm_path'"
+msgstr "Hinzufügen von Unterprojekt '$sm_path' fehlgeschlagen"
 
-#: git-submodule.sh:305
+#: git-submodule.sh:307
 #, sh-format
-msgid "Failed to register submodule '$path'"
-msgstr "Registrierung von Unterprojekt '$path' fehlgeschlagen"
+msgid "Failed to register submodule '$sm_path'"
+msgstr "Registierung von Unterprojekt '$sm_path' fehlgeschlagen"
 
-#: git-submodule.sh:347
+#: git-submodule.sh:349
 #, sh-format
-msgid "Entering '$prefix$path'"
-msgstr "Betrete '$prefix$path'"
+msgid "Entering '$prefix$sm_path'"
+msgstr "Betrete '$prefix$sm_path'"
 
-#: git-submodule.sh:359
+#: git-submodule.sh:363
 #, sh-format
-msgid "Stopping at '$path'; script returned non-zero status."
-msgstr "Stoppe bei '$path'; Skript gab nicht-Null Status zurück."
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr "Stoppe bei '$sm_path'; Skript gab nicht-Null Status zurück."
 
-#: git-submodule.sh:401
+#: git-submodule.sh:405
 #, sh-format
-msgid "No url found for submodule path '$path' in .gitmodules"
-msgstr "Keine URL für Unterprojekt-Pfad '$path' in .gitmodules gefunden"
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr "Keine URL für Unterprojekt-Pfad '$sm_path' in .gitmodules gefunden"
 
-#: git-submodule.sh:410
+#: git-submodule.sh:414
 #, sh-format
-msgid "Failed to register url for submodule path '$path'"
-msgstr "Fehler beim Registrieren der URL für Unterprojekt-Pfad '$path'"
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr "Fehler beim Registrieren der URL für Unterprojekt-Pfad '$sm_path'"
 
-#: git-submodule.sh:418
+#: git-submodule.sh:422
 #, sh-format
-msgid "Failed to register update mode for submodule path '$path'"
-msgstr "Fehler beim Registrieren des Aktualisierungsmodus für Unterprojekt-Pfad"
-" '$path'"
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr ""
+"Fehler beim Registrieren des Aktualisierungsmodus für Unterprojekt-Pfad "
+"'$sm_path'"
 
-#: git-submodule.sh:420
+#: git-submodule.sh:424
 #, sh-format
-msgid "Submodule '$name' ($url) registered for path '$path'"
-msgstr "Unterprojekt '$name' ($url) registriert für Pfad '$path'"
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr "Unterprojekt '$name' ($url) registriert für Pfad '$sm_path'"
 
-#: git-submodule.sh:519
+#: git-submodule.sh:523
 #, sh-format
 msgid ""
-"Submodule path '$path' not initialized\n"
+"Submodule path '$sm_path' not initialized\n"
 "Maybe you want to use 'update --init'?"
 msgstr ""
-"Unterprojekt-Pfad '$path' nicht initialisiert\n"
+"Unterprojekt-Pfad '$sm_path' nicht initialisiert\n"
 "Vielleicht möchtest du 'update --init' benutzen?"
 
-#: git-submodule.sh:532
+#: git-submodule.sh:536
 #, sh-format
-msgid "Unable to find current revision in submodule path '$path'"
-msgstr "Konnte aktuelle Revision in Unterprojekt-Pfad '$path' nicht finden"
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr "Konnte aktuelle Revision in Unterprojekt-Pfad '$sm_path' nicht finden"
 
-#: git-submodule.sh:551
+#: git-submodule.sh:555
 #, sh-format
-msgid "Unable to fetch in submodule path '$path'"
-msgstr "Konnte Unterprojekt-Pfad '$path' nicht anfordern"
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr "Konnte in Unterprojekt-Pfad '$sm_path' nicht anfordern"
 
-#: git-submodule.sh:565
+#: git-submodule.sh:569
 #, sh-format
-msgid "Unable to rebase '$sha1' in submodule path '$path'"
-msgstr "Neuaufbau von '$sha1' in Unterprojekt-Pfad '$path' nicht möglich"
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr "Neuaufbau von '$sha1' in Unterprojekt-Pfad '$sm_path' nicht möglich"
 
-#: git-submodule.sh:566
+#: git-submodule.sh:570
 #, sh-format
-msgid "Submodule path '$path': rebased into '$sha1'"
-msgstr "Unterprojekt-Pfad '$path': neu aufgebaut in '$sha1'"
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr "Unterprojekt-Pfad '$sm_path': neu aufgebaut in '$sha1'"
 
-#: git-submodule.sh:571
+#: git-submodule.sh:575
 #, sh-format
-msgid "Unable to merge '$sha1' in submodule path '$path'"
-msgstr "Konnte '$sha1' nicht in Unterprojekt-Pfad '$path' zusammenführen"
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr "Konnte '$sha1' nicht in Unterprojekt-Pfad '$sm_path' zusammenführen"
 
-#: git-submodule.sh:572
+#: git-submodule.sh:576
 #, sh-format
-msgid "Submodule path '$path': merged in '$sha1'"
-msgstr "Unterprojekt-Pfad '$path': zusammengeführt in '$sha1'"
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr "Unterprojekt-Pfad '$sm_path': zusammengeführt in '$sha1'"
 
-#: git-submodule.sh:577
+#: git-submodule.sh:581
 #, sh-format
-msgid "Unable to checkout '$sha1' in submodule path '$path'"
-msgstr "Konnte '$sha1' in Unterprojekt-Pfad '$path' nicht auschecken."
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr "Konnte '$sha1' in Unterprojekt-Pfad '$sm_path' nicht auschecken."
 
-#: git-submodule.sh:578
+#: git-submodule.sh:582
 #, sh-format
-msgid "Submodule path '$path': checked out '$sha1'"
-msgstr "Unterprojekt-Pfad: '$path': '$sha1' ausgecheckt"
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr "Unterprojekt-Pfad: '$sm_path': '$sha1' ausgecheckt"
 
-#: git-submodule.sh:600 git-submodule.sh:923
+#: git-submodule.sh:604 git-submodule.sh:927
 #, sh-format
-msgid "Failed to recurse into submodule path '$path'"
-msgstr "Fehler bei Rekursion in Unterprojekt-Pfad '$path'"
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr "Fehler bei Rekursion in Unterprojekt-Pfad '$sm_path'"
 
-#: git-submodule.sh:708
+#: git-submodule.sh:712
 msgid "--"
 msgstr "--"
 
-#: git-submodule.sh:766
+#: git-submodule.sh:770
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_src"
 msgstr "  Warnung: $name beinhaltet nicht Version $sha1_src"
 
-#: git-submodule.sh:769
+#: git-submodule.sh:773
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_dst"
 msgstr "  Warnung: $name beinhaltet nicht Version $sha1_dst"
 
-#: git-submodule.sh:772
+#: git-submodule.sh:776
 #, sh-format
 msgid "  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 msgstr "  Warnung: $name beinhaltet nich die Versionen $sha1_src und $sha1_dst"
 
-#: git-submodule.sh:797
+#: git-submodule.sh:801
 msgid "blob"
 msgstr "Blob"
 
-#: git-submodule.sh:798
+#: git-submodule.sh:802
 msgid "submodule"
 msgstr "Unterprojekt"
 
-#: git-submodule.sh:969
+#: git-submodule.sh:973
 #, sh-format
 msgid "Synchronizing submodule url for '$name'"
 msgstr "Synchronisiere Unterprojekt-URL für '$name'"
index 67acae411646befb342b238485aa4b45c62fa3a1..730cac7b83566027d8af1c76aab0c83f765e480e 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2012-04-28 20:33+0800\n"
+"POT-Creation-Date: 2012-04-28 20:17+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,7 +18,7 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
 
-#: advice.c:34
+#: advice.c:40
 #, c-format
 msgid "hint: %.*s\n"
 msgstr ""
@@ -27,7 +27,7 @@ msgstr ""
 #. * Message used both when 'git commit' fails and when
 #. * other commands doing a merge do.
 #.
-#: advice.c:64
+#: advice.c:70
 msgid ""
 "Fix them up in the work tree,\n"
 "and then use 'git add/rm <file>' as\n"
@@ -35,12 +35,12 @@ msgid ""
 "or use 'git commit -a'."
 msgstr ""
 
-#: commit.c:47
+#: commit.c:48
 #, c-format
 msgid "could not parse %s"
 msgstr ""
 
-#: commit.c:49
+#: commit.c:50
 #, c-format
 msgid "%s %s is not a commit!"
 msgstr ""
@@ -172,14 +172,14 @@ msgid_plural ""
 msgstr[0] ""
 msgstr[1] ""
 
-#: sequencer.c:120 builtin/merge.c:864 builtin/merge.c:985
-#: builtin/merge.c:1095 builtin/merge.c:1105
+#: sequencer.c:120 builtin/merge.c:865 builtin/merge.c:978
+#: builtin/merge.c:1088 builtin/merge.c:1098
 #, c-format
 msgid "Could not open '%s' for writing"
 msgstr ""
 
-#: sequencer.c:122 builtin/merge.c:334 builtin/merge.c:867
-#: builtin/merge.c:1097 builtin/merge.c:1110
+#: sequencer.c:122 builtin/merge.c:333 builtin/merge.c:868
+#: builtin/merge.c:1090 builtin/merge.c:1103
 #, c-format
 msgid "Could not write to '%s'"
 msgstr ""
@@ -270,8 +270,8 @@ msgstr ""
 msgid "could not apply %s... %s"
 msgstr ""
 
-#: sequencer.c:450 sequencer.c:909 builtin/log.c:288 builtin/log.c:713
-#: builtin/log.c:1329 builtin/log.c:1548 builtin/merge.c:348
+#: sequencer.c:450 sequencer.c:909 builtin/log.c:289 builtin/log.c:719
+#: builtin/log.c:1335 builtin/log.c:1554 builtin/merge.c:347
 #: builtin/shortlog.c:181
 msgid "revision walk setup failed"
 msgstr ""
@@ -396,6 +396,25 @@ msgstr ""
 msgid "Can't cherry-pick into empty head"
 msgstr ""
 
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr ""
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr ""
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr ""
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr ""
+
 #: wt-status.c:134
 msgid "Unmerged paths:"
 msgstr ""
@@ -779,128 +798,128 @@ msgid ""
 msgstr ""
 
 #. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
-#: builtin/branch.c:163
+#: builtin/branch.c:164
 msgid "remote "
 msgstr ""
 
-#: builtin/branch.c:171
+#: builtin/branch.c:172
 msgid "cannot use -a with -d"
 msgstr ""
 
-#: builtin/branch.c:177
+#: builtin/branch.c:178
 msgid "Couldn't look up commit object for HEAD"
 msgstr ""
 
-#: builtin/branch.c:182
+#: builtin/branch.c:183
 #, c-format
 msgid "Cannot delete the branch '%s' which you are currently on."
 msgstr ""
 
-#: builtin/branch.c:192
+#: builtin/branch.c:193
 #, c-format
 msgid "%sbranch '%s' not found."
 msgstr ""
 
-#: builtin/branch.c:200
+#: builtin/branch.c:201
 #, c-format
 msgid "Couldn't look up commit object for '%s'"
 msgstr ""
 
-#: builtin/branch.c:206
+#: builtin/branch.c:207
 #, c-format
 msgid ""
 "The branch '%s' is not fully merged.\n"
 "If you are sure you want to delete it, run 'git branch -D %s'."
 msgstr ""
 
-#: builtin/branch.c:214
+#: builtin/branch.c:215
 #, c-format
 msgid "Error deleting %sbranch '%s'"
 msgstr ""
 
-#: builtin/branch.c:219
+#: builtin/branch.c:221
 #, c-format
 msgid "Deleted %sbranch %s (was %s).\n"
 msgstr ""
 
-#: builtin/branch.c:224
+#: builtin/branch.c:226
 msgid "Update of config-file failed"
 msgstr ""
 
-#: builtin/branch.c:322
+#: builtin/branch.c:324
 #, c-format
 msgid "branch '%s' does not point at a commit"
 msgstr ""
 
-#: builtin/branch.c:394
+#: builtin/branch.c:396
 #, c-format
 msgid "behind %d] "
 msgstr ""
 
-#: builtin/branch.c:396
+#: builtin/branch.c:398
 #, c-format
 msgid "ahead %d] "
 msgstr ""
 
-#: builtin/branch.c:398
+#: builtin/branch.c:400
 #, c-format
 msgid "ahead %d, behind %d] "
 msgstr ""
 
-#: builtin/branch.c:501
+#: builtin/branch.c:503
 msgid "(no branch)"
 msgstr ""
 
-#: builtin/branch.c:566
+#: builtin/branch.c:568
 msgid "some refs could not be read"
 msgstr ""
 
-#: builtin/branch.c:579
+#: builtin/branch.c:581
 msgid "cannot rename the current branch while not on any."
 msgstr ""
 
-#: builtin/branch.c:589
+#: builtin/branch.c:591
 #, c-format
 msgid "Invalid branch name: '%s'"
 msgstr ""
 
-#: builtin/branch.c:604
+#: builtin/branch.c:606
 msgid "Branch rename failed"
 msgstr ""
 
-#: builtin/branch.c:608
+#: builtin/branch.c:610
 #, c-format
 msgid "Renamed a misnamed branch '%s' away"
 msgstr ""
 
-#: builtin/branch.c:612
+#: builtin/branch.c:614
 #, c-format
 msgid "Branch renamed to %s, but HEAD is not updated!"
 msgstr ""
 
-#: builtin/branch.c:619
+#: builtin/branch.c:621
 msgid "Branch is renamed, but update of config-file failed"
 msgstr ""
 
-#: builtin/branch.c:634
+#: builtin/branch.c:636
 #, c-format
 msgid "malformed object name %s"
 msgstr ""
 
-#: builtin/branch.c:658
+#: builtin/branch.c:660
 #, c-format
 msgid "could not write branch description template: %s\n"
 msgstr ""
 
-#: builtin/branch.c:746
+#: builtin/branch.c:750
 msgid "Failed to resolve HEAD as a valid ref."
 msgstr ""
 
-#: builtin/branch.c:751 builtin/clone.c:558
+#: builtin/branch.c:755 builtin/clone.c:558
 msgid "HEAD not found below refs/heads!"
 msgstr ""
 
-#: builtin/branch.c:809
+#: builtin/branch.c:813
 msgid "-a and -r options to 'git branch' do not make sense with a branch name"
 msgstr ""
 
@@ -962,7 +981,7 @@ msgid "path '%s' is unmerged"
 msgstr ""
 
 #: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
-#: builtin/merge.c:811
+#: builtin/merge.c:812
 msgid "unable to write new index file"
 msgstr ""
 
@@ -979,42 +998,42 @@ msgstr ""
 msgid "Can not do reflog for '%s'\n"
 msgstr ""
 
-#: builtin/checkout.c:565
+#: builtin/checkout.c:566
 msgid "HEAD is now at"
 msgstr ""
 
-#: builtin/checkout.c:572
+#: builtin/checkout.c:573
 #, c-format
 msgid "Reset branch '%s'\n"
 msgstr ""
 
-#: builtin/checkout.c:575
+#: builtin/checkout.c:576
 #, c-format
 msgid "Already on '%s'\n"
 msgstr ""
 
-#: builtin/checkout.c:579
+#: builtin/checkout.c:580
 #, c-format
 msgid "Switched to and reset branch '%s'\n"
 msgstr ""
 
-#: builtin/checkout.c:581
+#: builtin/checkout.c:582
 #, c-format
 msgid "Switched to a new branch '%s'\n"
 msgstr ""
 
-#: builtin/checkout.c:583
+#: builtin/checkout.c:584
 #, c-format
 msgid "Switched to branch '%s'\n"
 msgstr ""
 
-#: builtin/checkout.c:639
+#: builtin/checkout.c:640
 #, c-format
 msgid " ... and %d more.\n"
 msgstr ""
 
 #. The singular version
-#: builtin/checkout.c:645
+#: builtin/checkout.c:646
 #, c-format
 msgid ""
 "Warning: you are leaving %d commit behind, not connected to\n"
@@ -1029,7 +1048,7 @@ msgid_plural ""
 msgstr[0] ""
 msgstr[1] ""
 
-#: builtin/checkout.c:663
+#: builtin/checkout.c:664
 #, c-format
 msgid ""
 "If you want to keep them by creating a new branch, this may be a good time\n"
@@ -1039,96 +1058,96 @@ msgid ""
 "\n"
 msgstr ""
 
-#: builtin/checkout.c:692
+#: builtin/checkout.c:693
 msgid "internal error in revision walk"
 msgstr ""
 
-#: builtin/checkout.c:696
+#: builtin/checkout.c:697
 msgid "Previous HEAD position was"
 msgstr ""
 
-#: builtin/checkout.c:722
+#: builtin/checkout.c:723
 msgid "You are on a branch yet to be born"
 msgstr ""
 
 #. case (1)
-#: builtin/checkout.c:853
+#: builtin/checkout.c:854
 #, c-format
 msgid "invalid reference: %s"
 msgstr ""
 
 #. case (1): want a tree
-#: builtin/checkout.c:892
+#: builtin/checkout.c:893
 #, c-format
 msgid "reference is not a tree: %s"
 msgstr ""
 
-#: builtin/checkout.c:972
+#: builtin/checkout.c:973
 msgid "-B cannot be used with -b"
 msgstr ""
 
-#: builtin/checkout.c:981
+#: builtin/checkout.c:982
 msgid "--patch is incompatible with all other options"
 msgstr ""
 
-#: builtin/checkout.c:984
+#: builtin/checkout.c:985
 msgid "--detach cannot be used with -b/-B/--orphan"
 msgstr ""
 
-#: builtin/checkout.c:986
+#: builtin/checkout.c:987
 msgid "--detach cannot be used with -t"
 msgstr ""
 
-#: builtin/checkout.c:992
+#: builtin/checkout.c:993
 msgid "--track needs a branch name"
 msgstr ""
 
-#: builtin/checkout.c:999
+#: builtin/checkout.c:1000
 msgid "Missing branch name; try -b"
 msgstr ""
 
-#: builtin/checkout.c:1005
+#: builtin/checkout.c:1006
 msgid "--orphan and -b|-B are mutually exclusive"
 msgstr ""
 
-#: builtin/checkout.c:1007
+#: builtin/checkout.c:1008
 msgid "--orphan cannot be used with -t"
 msgstr ""
 
-#: builtin/checkout.c:1017
+#: builtin/checkout.c:1018
 msgid "git checkout: -f and -m are incompatible"
 msgstr ""
 
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1052
 msgid "invalid path specification"
 msgstr ""
 
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1060
 #, c-format
 msgid ""
 "git checkout: updating paths is incompatible with switching branches.\n"
 "Did you intend to checkout '%s' which can not be resolved as commit?"
 msgstr ""
 
-#: builtin/checkout.c:1061
+#: builtin/checkout.c:1062
 msgid "git checkout: updating paths is incompatible with switching branches."
 msgstr ""
 
-#: builtin/checkout.c:1066
+#: builtin/checkout.c:1067
 msgid "git checkout: --detach does not take a path argument"
 msgstr ""
 
-#: builtin/checkout.c:1069
+#: builtin/checkout.c:1070
 msgid ""
 "git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
 "checking out of the index."
 msgstr ""
 
-#: builtin/checkout.c:1088
+#: builtin/checkout.c:1089
 msgid "Cannot switch branch to a non-commit."
 msgstr ""
 
-#: builtin/checkout.c:1091
+#: builtin/checkout.c:1092
 msgid "--ours/--theirs is incompatible with switching branches."
 msgstr ""
 
@@ -1587,7 +1606,7 @@ msgstr ""
 msgid "Aborting commit due to empty commit message.\n"
 msgstr ""
 
-#: builtin/commit.c:1554 builtin/merge.c:935 builtin/merge.c:968
+#: builtin/commit.c:1554 builtin/merge.c:936 builtin/merge.c:961
 msgid "failed to write commit object"
 msgstr ""
 
@@ -1722,146 +1741,150 @@ msgstr ""
 msgid "Couldn't find remote ref HEAD"
 msgstr ""
 
-#: builtin/fetch.c:252
+#: builtin/fetch.c:253
 #, c-format
 msgid "object %s not found"
 msgstr ""
 
-#: builtin/fetch.c:258
+#: builtin/fetch.c:259
 msgid "[up to date]"
 msgstr ""
 
-#: builtin/fetch.c:272
+#: builtin/fetch.c:273
 #, c-format
 msgid "! %-*s %-*s -> %s  (can't fetch in current branch)"
 msgstr ""
 
-#: builtin/fetch.c:273 builtin/fetch.c:351
+#: builtin/fetch.c:274 builtin/fetch.c:360
 msgid "[rejected]"
 msgstr ""
 
-#: builtin/fetch.c:284
+#: builtin/fetch.c:285
 msgid "[tag update]"
 msgstr ""
 
-#: builtin/fetch.c:286 builtin/fetch.c:313 builtin/fetch.c:331
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
 msgid "  (unable to update local ref)"
 msgstr ""
 
-#: builtin/fetch.c:298
+#: builtin/fetch.c:305
 msgid "[new tag]"
 msgstr ""
 
-#: builtin/fetch.c:302
+#: builtin/fetch.c:308
 msgid "[new branch]"
 msgstr ""
 
-#: builtin/fetch.c:347
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr ""
+
+#: builtin/fetch.c:356
 msgid "unable to update local ref"
 msgstr ""
 
-#: builtin/fetch.c:347
+#: builtin/fetch.c:356
 msgid "forced update"
 msgstr ""
 
-#: builtin/fetch.c:353
+#: builtin/fetch.c:362
 msgid "(non-fast-forward)"
 msgstr ""
 
-#: builtin/fetch.c:384 builtin/fetch.c:676
+#: builtin/fetch.c:393 builtin/fetch.c:685
 #, c-format
 msgid "cannot open %s: %s\n"
 msgstr ""
 
-#: builtin/fetch.c:393
+#: builtin/fetch.c:402
 #, c-format
 msgid "%s did not send all necessary objects\n"
 msgstr ""
 
-#: builtin/fetch.c:479
+#: builtin/fetch.c:488
 #, c-format
 msgid "From %.*s\n"
 msgstr ""
 
-#: builtin/fetch.c:490
+#: builtin/fetch.c:499
 #, c-format
 msgid ""
 "some local refs could not be updated; try running\n"
 " 'git remote prune %s' to remove any old, conflicting branches"
 msgstr ""
 
-#: builtin/fetch.c:540
+#: builtin/fetch.c:549
 #, c-format
 msgid "   (%s will become dangling)\n"
 msgstr ""
 
-#: builtin/fetch.c:541
+#: builtin/fetch.c:550
 #, c-format
 msgid "   (%s has become dangling)\n"
 msgstr ""
 
-#: builtin/fetch.c:548
+#: builtin/fetch.c:557
 msgid "[deleted]"
 msgstr ""
 
-#: builtin/fetch.c:549
+#: builtin/fetch.c:558
 msgid "(none)"
 msgstr ""
 
-#: builtin/fetch.c:666
+#: builtin/fetch.c:675
 #, c-format
 msgid "Refusing to fetch into current branch %s of non-bare repository"
 msgstr ""
 
-#: builtin/fetch.c:700
+#: builtin/fetch.c:709
 #, c-format
 msgid "Don't know how to fetch from %s"
 msgstr ""
 
-#: builtin/fetch.c:777
+#: builtin/fetch.c:786
 #, c-format
 msgid "Option \"%s\" value \"%s\" is not valid for %s"
 msgstr ""
 
-#: builtin/fetch.c:780
+#: builtin/fetch.c:789
 #, c-format
 msgid "Option \"%s\" is ignored for %s\n"
 msgstr ""
 
-#: builtin/fetch.c:879
+#: builtin/fetch.c:888
 #, c-format
 msgid "Fetching %s\n"
 msgstr ""
 
-#: builtin/fetch.c:881
+#: builtin/fetch.c:890
 #, c-format
 msgid "Could not fetch %s"
 msgstr ""
 
-#: builtin/fetch.c:898
+#: builtin/fetch.c:907
 msgid ""
 "No remote repository specified.  Please, specify either a URL or a\n"
 "remote name from which new revisions should be fetched."
 msgstr ""
 
-#: builtin/fetch.c:918
+#: builtin/fetch.c:927
 msgid "You need to specify a tag name."
 msgstr ""
 
-#: builtin/fetch.c:970
+#: builtin/fetch.c:979
 msgid "fetch --all does not take a repository argument"
 msgstr ""
 
-#: builtin/fetch.c:972
+#: builtin/fetch.c:981
 msgid "fetch --all does not make sense with refspecs"
 msgstr ""
 
-#: builtin/fetch.c:983
+#: builtin/fetch.c:992
 #, c-format
 msgid "No such remote or remote group: %s"
 msgstr ""
 
-#: builtin/fetch.c:991
+#: builtin/fetch.c:1000
 msgid "Fetching a group and specifying refspecs does not make sense"
 msgstr ""
 
@@ -2097,163 +2120,163 @@ msgstr ""
 msgid "Cannot access work tree '%s'"
 msgstr ""
 
-#: builtin/log.c:187
+#: builtin/log.c:188
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr ""
 
-#: builtin/log.c:395 builtin/log.c:483
+#: builtin/log.c:401 builtin/log.c:489
 #, c-format
 msgid "Could not read object %s"
 msgstr ""
 
-#: builtin/log.c:507
+#: builtin/log.c:513
 #, c-format
 msgid "Unknown type: %d"
 msgstr ""
 
-#: builtin/log.c:596
+#: builtin/log.c:602
 msgid "format.headers without value"
 msgstr ""
 
-#: builtin/log.c:669
+#: builtin/log.c:675
 msgid "name of output directory is too long"
 msgstr ""
 
-#: builtin/log.c:680
+#: builtin/log.c:686
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr ""
 
-#: builtin/log.c:694
+#: builtin/log.c:700
 msgid "Need exactly one range."
 msgstr ""
 
-#: builtin/log.c:702
+#: builtin/log.c:708
 msgid "Not a range."
 msgstr ""
 
-#: builtin/log.c:739
+#: builtin/log.c:745
 msgid "Could not extract email from committer identity."
 msgstr ""
 
-#: builtin/log.c:785
+#: builtin/log.c:791
 msgid "Cover letter needs email format"
 msgstr ""
 
-#: builtin/log.c:879
+#: builtin/log.c:885
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr ""
 
-#: builtin/log.c:952
+#: builtin/log.c:958
 msgid "Two output directories?"
 msgstr ""
 
-#: builtin/log.c:1173
+#: builtin/log.c:1179
 #, c-format
 msgid "bogus committer info %s"
 msgstr ""
 
-#: builtin/log.c:1218
+#: builtin/log.c:1224
 msgid "-n and -k are mutually exclusive."
 msgstr ""
 
-#: builtin/log.c:1220
+#: builtin/log.c:1226
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr ""
 
-#: builtin/log.c:1225 builtin/shortlog.c:284
+#: builtin/log.c:1231 builtin/shortlog.c:284
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr ""
 
-#: builtin/log.c:1228
+#: builtin/log.c:1234
 msgid "--name-only does not make sense"
 msgstr ""
 
-#: builtin/log.c:1230
+#: builtin/log.c:1236
 msgid "--name-status does not make sense"
 msgstr ""
 
-#: builtin/log.c:1232
+#: builtin/log.c:1238
 msgid "--check does not make sense"
 msgstr ""
 
-#: builtin/log.c:1255
+#: builtin/log.c:1261
 msgid "standard output, or directory, which one?"
 msgstr ""
 
-#: builtin/log.c:1257
+#: builtin/log.c:1263
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr ""
 
-#: builtin/log.c:1410
+#: builtin/log.c:1416
 msgid "Failed to create output files"
 msgstr ""
 
-#: builtin/log.c:1514
+#: builtin/log.c:1520
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> manually.\n"
 msgstr ""
 
-#: builtin/log.c:1530 builtin/log.c:1532 builtin/log.c:1544
+#: builtin/log.c:1536 builtin/log.c:1538 builtin/log.c:1550
 #, c-format
 msgid "Unknown commit %s"
 msgstr ""
 
-#: builtin/merge.c:91
+#: builtin/merge.c:90
 msgid "switch `m' requires a value"
 msgstr ""
 
-#: builtin/merge.c:128
+#: builtin/merge.c:127
 #, c-format
 msgid "Could not find merge strategy '%s'.\n"
 msgstr ""
 
-#: builtin/merge.c:129
+#: builtin/merge.c:128
 #, c-format
 msgid "Available strategies are:"
 msgstr ""
 
-#: builtin/merge.c:134
+#: builtin/merge.c:133
 #, c-format
 msgid "Available custom strategies are:"
 msgstr ""
 
-#: builtin/merge.c:241
+#: builtin/merge.c:240
 msgid "could not run stash."
 msgstr ""
 
-#: builtin/merge.c:246
+#: builtin/merge.c:245
 msgid "stash failed"
 msgstr ""
 
-#: builtin/merge.c:251
+#: builtin/merge.c:250
 #, c-format
 msgid "not a valid object: %s"
 msgstr ""
 
-#: builtin/merge.c:270 builtin/merge.c:287
+#: builtin/merge.c:269 builtin/merge.c:286
 msgid "read-tree failed"
 msgstr ""
 
-#: builtin/merge.c:317
+#: builtin/merge.c:316
 msgid " (nothing to squash)"
 msgstr ""
 
-#: builtin/merge.c:330
+#: builtin/merge.c:329
 #, c-format
 msgid "Squash commit -- not updating HEAD\n"
 msgstr ""
 
-#: builtin/merge.c:362
+#: builtin/merge.c:361
 msgid "Writing SQUASH_MSG"
 msgstr ""
 
-#: builtin/merge.c:364
+#: builtin/merge.c:363
 msgid "Finishing SQUASH_MSG"
 msgstr ""
 
@@ -2280,35 +2303,35 @@ msgstr ""
 msgid "failed to read the cache"
 msgstr ""
 
-#: builtin/merge.c:696
+#: builtin/merge.c:697
 msgid "Unable to write index."
 msgstr ""
 
-#: builtin/merge.c:709
+#: builtin/merge.c:710
 msgid "Not handling anything other than two heads merge."
 msgstr ""
 
-#: builtin/merge.c:723
+#: builtin/merge.c:724
 #, c-format
 msgid "Unknown option for merge-recursive: -X%s"
 msgstr ""
 
-#: builtin/merge.c:737
+#: builtin/merge.c:738
 #, c-format
 msgid "unable to write %s"
 msgstr ""
 
-#: builtin/merge.c:876
+#: builtin/merge.c:877
 #, c-format
 msgid "Could not read from '%s'"
 msgstr ""
 
-#: builtin/merge.c:885
+#: builtin/merge.c:886
 #, c-format
 msgid "Not committing merge; use 'git commit' to complete the merge.\n"
 msgstr ""
 
-#: builtin/merge.c:891
+#: builtin/merge.c:892
 msgid ""
 "Please enter a commit message to explain why this merge is necessary,\n"
 "especially if it merges an updated upstream into a topic branch.\n"
@@ -2317,140 +2340,140 @@ msgid ""
 "the commit.\n"
 msgstr ""
 
-#: builtin/merge.c:915
+#: builtin/merge.c:916
 msgid "Empty commit message."
 msgstr ""
 
-#: builtin/merge.c:927
+#: builtin/merge.c:928
 #, c-format
 msgid "Wonderful.\n"
 msgstr ""
 
-#: builtin/merge.c:1000
+#: builtin/merge.c:993
 #, c-format
 msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
 msgstr ""
 
-#: builtin/merge.c:1016
+#: builtin/merge.c:1009
 #, c-format
 msgid "'%s' is not a commit"
 msgstr ""
 
-#: builtin/merge.c:1057
+#: builtin/merge.c:1050
 msgid "No current branch."
 msgstr ""
 
-#: builtin/merge.c:1059
+#: builtin/merge.c:1052
 msgid "No remote for the current branch."
 msgstr ""
 
-#: builtin/merge.c:1061
+#: builtin/merge.c:1054
 msgid "No default upstream defined for the current branch."
 msgstr ""
 
-#: builtin/merge.c:1066
+#: builtin/merge.c:1059
 #, c-format
 msgid "No remote tracking branch for %s from %s"
 msgstr ""
 
-#: builtin/merge.c:1188
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr ""
+
+#: builtin/merge.c:1214
 msgid "There is no merge to abort (MERGE_HEAD missing)."
 msgstr ""
 
-#: builtin/merge.c:1204 git-pull.sh:31
+#: builtin/merge.c:1230 git-pull.sh:31
 msgid ""
 "You have not concluded your merge (MERGE_HEAD exists).\n"
 "Please, commit your changes before you can merge."
 msgstr ""
 
-#: builtin/merge.c:1207 git-pull.sh:34
+#: builtin/merge.c:1233 git-pull.sh:34
 msgid "You have not concluded your merge (MERGE_HEAD exists)."
 msgstr ""
 
-#: builtin/merge.c:1211
+#: builtin/merge.c:1237
 msgid ""
 "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
 "Please, commit your changes before you can merge."
 msgstr ""
 
-#: builtin/merge.c:1214
+#: builtin/merge.c:1240
 msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
 msgstr ""
 
-#: builtin/merge.c:1223
+#: builtin/merge.c:1249
 msgid "You cannot combine --squash with --no-ff."
 msgstr ""
 
-#: builtin/merge.c:1228
+#: builtin/merge.c:1254
 msgid "You cannot combine --no-ff with --ff-only."
 msgstr ""
 
-#: builtin/merge.c:1235
+#: builtin/merge.c:1261
 msgid "No commit specified and merge.defaultToUpstream not set."
 msgstr ""
 
-#: builtin/merge.c:1266
+#: builtin/merge.c:1293
 msgid "Can merge only exactly one commit into empty head"
 msgstr ""
 
-#: builtin/merge.c:1269
+#: builtin/merge.c:1296
 msgid "Squash commit into empty head not supported yet"
 msgstr ""
 
-#: builtin/merge.c:1271
+#: builtin/merge.c:1298
 msgid "Non-fast-forward commit does not make sense into an empty head"
 msgstr ""
 
-#: builtin/merge.c:1275 builtin/merge.c:1319
-#, c-format
-msgid "%s - not something we can merge"
-msgstr ""
-
-#: builtin/merge.c:1382
+#: builtin/merge.c:1413
 #, c-format
 msgid "Updating %s..%s\n"
 msgstr ""
 
-#: builtin/merge.c:1420
+#: builtin/merge.c:1451
 #, c-format
 msgid "Trying really trivial in-index merge...\n"
 msgstr ""
 
-#: builtin/merge.c:1427
+#: builtin/merge.c:1458
 #, c-format
 msgid "Nope.\n"
 msgstr ""
 
-#: builtin/merge.c:1459
+#: builtin/merge.c:1490
 msgid "Not possible to fast-forward, aborting."
 msgstr ""
 
-#: builtin/merge.c:1482 builtin/merge.c:1559
+#: builtin/merge.c:1513 builtin/merge.c:1592
 #, c-format
 msgid "Rewinding the tree to pristine...\n"
 msgstr ""
 
-#: builtin/merge.c:1486
+#: builtin/merge.c:1517
 #, c-format
 msgid "Trying merge strategy %s...\n"
 msgstr ""
 
-#: builtin/merge.c:1550
+#: builtin/merge.c:1583
 #, c-format
 msgid "No merge strategy handled the merge.\n"
 msgstr ""
 
-#: builtin/merge.c:1552
+#: builtin/merge.c:1585
 #, c-format
 msgid "Merge with strategy %s failed.\n"
 msgstr ""
 
-#: builtin/merge.c:1561
+#: builtin/merge.c:1594
 #, c-format
 msgid "Using the %s to prepare resolving by hand.\n"
 msgstr ""
 
-#: builtin/merge.c:1572
+#: builtin/merge.c:1606
 #, c-format
 msgid "Automatic merge went well; stopped before committing as requested\n"
 msgstr ""
@@ -2689,15 +2712,15 @@ msgstr ""
 msgid "unable to parse value '%s' for option %s"
 msgstr ""
 
-#: builtin/push.c:44
+#: builtin/push.c:45
 msgid "tag shorthand without <tag>"
 msgstr ""
 
-#: builtin/push.c:63
+#: builtin/push.c:64
 msgid "--delete only accepts plain target ref names"
 msgstr ""
 
-#: builtin/push.c:83
+#: builtin/push.c:84
 #, c-format
 msgid ""
 "You are not currently on a branch.\n"
@@ -2707,7 +2730,7 @@ msgid ""
 "    git push %s HEAD:<name-of-remote-branch>\n"
 msgstr ""
 
-#: builtin/push.c:90
+#: builtin/push.c:91
 #, c-format
 msgid ""
 "The current branch %s has no upstream branch.\n"
@@ -2716,12 +2739,12 @@ msgid ""
 "    git push --set-upstream %s %s\n"
 msgstr ""
 
-#: builtin/push.c:98
+#: builtin/push.c:99
 #, c-format
 msgid "The current branch %s has multiple upstream branches, refusing to push."
 msgstr ""
 
-#: builtin/push.c:101
+#: builtin/push.c:102
 #, c-format
 msgid ""
 "You are pushing to remote '%s', which is not the upstream of\n"
@@ -2729,35 +2752,51 @@ msgid ""
 "to update which remote branch."
 msgstr ""
 
-#: builtin/push.c:127
+#: builtin/push.c:131
 msgid ""
 "You didn't specify any refspecs to push, and push.default is \"nothing\"."
 msgstr ""
 
-#: builtin/push.c:147
-#, c-format
-msgid "Pushing to %s\n"
+#: builtin/push.c:138
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:144
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
 msgstr ""
 
-#: builtin/push.c:151
+#: builtin/push.c:150
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+
+#: builtin/push.c:190
 #, c-format
-msgid "failed to push some refs to '%s'"
+msgid "Pushing to %s\n"
 msgstr ""
 
-#: builtin/push.c:159
+#: builtin/push.c:194
 #, c-format
-msgid ""
-"To prevent you from losing history, non-fast-forward updates were rejected\n"
-"Merge the remote changes (e.g. 'git pull') before pushing again.  See the\n"
-"'Note about fast-forwards' section of 'git push --help' for details.\n"
+msgid "failed to push some refs to '%s'"
 msgstr ""
 
-#: builtin/push.c:176
+#: builtin/push.c:226
 #, c-format
 msgid "bad repository '%s'"
 msgstr ""
 
-#: builtin/push.c:177
+#: builtin/push.c:227
 msgid ""
 "No configured push destination.\n"
 "Either specify the URL from the command-line or configure a remote "
@@ -2770,31 +2809,31 @@ msgid ""
 "    git push <name>\n"
 msgstr ""
 
-#: builtin/push.c:192
+#: builtin/push.c:242
 msgid "--all and --tags are incompatible"
 msgstr ""
 
-#: builtin/push.c:193
+#: builtin/push.c:243
 msgid "--all can't be combined with refspecs"
 msgstr ""
 
-#: builtin/push.c:198
+#: builtin/push.c:248
 msgid "--mirror and --tags are incompatible"
 msgstr ""
 
-#: builtin/push.c:199
+#: builtin/push.c:249
 msgid "--mirror can't be combined with refspecs"
 msgstr ""
 
-#: builtin/push.c:204
+#: builtin/push.c:254
 msgid "--all and --mirror are incompatible"
 msgstr ""
 
-#: builtin/push.c:284
+#: builtin/push.c:342
 msgid "--delete is incompatible with --all, --mirror and --tags"
 msgstr ""
 
-#: builtin/push.c:286
+#: builtin/push.c:344
 msgid "--delete doesn't make sense without any refs"
 msgstr ""
 
@@ -2877,20 +2916,20 @@ msgstr ""
 msgid "Could not reset index file to revision '%s'."
 msgstr ""
 
-#: builtin/revert.c:70 builtin/revert.c:91
+#: builtin/revert.c:70 builtin/revert.c:92
 #, c-format
 msgid "%s: %s cannot be used with %s"
 msgstr ""
 
-#: builtin/revert.c:126
+#: builtin/revert.c:127
 msgid "program error"
 msgstr ""
 
-#: builtin/revert.c:209
+#: builtin/revert.c:213
 msgid "revert failed"
 msgstr ""
 
-#: builtin/revert.c:224
+#: builtin/revert.c:228
 msgid "cherry-pick failed"
 msgstr ""
 
@@ -3053,80 +3092,80 @@ msgstr ""
 msgid "Updated tag '%s' (was %s)\n"
 msgstr ""
 
-#: git-am.sh:49
+#: git-am.sh:50
 msgid "You need to set your committer info first"
 msgstr ""
 
-#: git-am.sh:136
+#: git-am.sh:137
 msgid "Repository lacks necessary blobs to fall back on 3-way merge."
 msgstr ""
 
-#: git-am.sh:147
+#: git-am.sh:154
 msgid ""
 "Did you hand edit your patch?\n"
 "It does not apply to blobs recorded in its index."
 msgstr ""
 
-#: git-am.sh:156
+#: git-am.sh:163
 msgid "Falling back to patching base and 3-way merge..."
 msgstr ""
 
-#: git-am.sh:268
+#: git-am.sh:275
 msgid "Only one StGIT patch series can be applied at once"
 msgstr ""
 
-#: git-am.sh:355
+#: git-am.sh:362
 #, sh-format
 msgid "Patch format $patch_format is not supported."
 msgstr ""
 
-#: git-am.sh:357
+#: git-am.sh:364
 msgid "Patch format detection failed."
 msgstr ""
 
-#: git-am.sh:411
+#: git-am.sh:418
 msgid "-d option is no longer supported.  Do not use."
 msgstr ""
 
-#: git-am.sh:474
+#: git-am.sh:481
 #, sh-format
 msgid "previous rebase directory $dotest still exists but mbox given."
 msgstr ""
 
-#: git-am.sh:479
+#: git-am.sh:486
 msgid "Please make up your mind. --skip or --abort?"
 msgstr ""
 
-#: git-am.sh:506
+#: git-am.sh:513
 msgid "Resolve operation not in progress, we are not resuming."
 msgstr ""
 
-#: git-am.sh:572
+#: git-am.sh:579
 #, sh-format
 msgid "Dirty index: cannot apply patches (dirty: $files)"
 msgstr ""
 
-#: git-am.sh:748
+#: git-am.sh:755
 msgid "cannot be interactive without stdin connected to a terminal."
 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:759
+#: git-am.sh:766
 msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
 msgstr ""
 
-#: git-am.sh:795
+#: git-am.sh:802
 #, sh-format
 msgid "Applying: $FIRSTLINE"
 msgstr ""
 
-#: git-am.sh:840
+#: git-am.sh:847
 msgid "No changes -- Patch already applied."
 msgstr ""
 
-#: git-am.sh:866
+#: git-am.sh:873
 msgid "applying to an empty history"
 msgstr ""
 
@@ -3358,161 +3397,161 @@ msgstr ""
 msgid "cannot strip one component off url '$remoteurl'"
 msgstr ""
 
-#: git-submodule.sh:108
+#: git-submodule.sh:109
 #, sh-format
-msgid "No submodule mapping found in .gitmodules for path '$path'"
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:149
+#: git-submodule.sh:150
 #, sh-format
-msgid "Clone of '$url' into submodule path '$path' failed"
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
 msgstr ""
 
-#: git-submodule.sh:159
+#: git-submodule.sh:160
 #, sh-format
 msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
 msgstr ""
 
-#: git-submodule.sh:247
+#: git-submodule.sh:249
 #, sh-format
 msgid "repo URL: '$repo' must be absolute or begin with ./|../"
 msgstr ""
 
-#: git-submodule.sh:264
+#: git-submodule.sh:266
 #, sh-format
-msgid "'$path' already exists in the index"
+msgid "'$sm_path' already exists in the index"
 msgstr ""
 
-#: git-submodule.sh:281
+#: git-submodule.sh:283
 #, sh-format
-msgid "'$path' already exists and is not a valid git repo"
+msgid "'$sm_path' already exists and is not a valid git repo"
 msgstr ""
 
-#: git-submodule.sh:295
+#: git-submodule.sh:297
 #, sh-format
-msgid "Unable to checkout submodule '$path'"
+msgid "Unable to checkout submodule '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:300
+#: git-submodule.sh:302
 #, sh-format
-msgid "Failed to add submodule '$path'"
+msgid "Failed to add submodule '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:305
+#: git-submodule.sh:307
 #, sh-format
-msgid "Failed to register submodule '$path'"
+msgid "Failed to register submodule '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:347
+#: git-submodule.sh:349
 #, sh-format
-msgid "Entering '$prefix$path'"
+msgid "Entering '$prefix$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:359
+#: git-submodule.sh:363
 #, sh-format
-msgid "Stopping at '$path'; script returned non-zero status."
+msgid "Stopping at '$sm_path'; script returned non-zero status."
 msgstr ""
 
-#: git-submodule.sh:401
+#: git-submodule.sh:405
 #, sh-format
-msgid "No url found for submodule path '$path' in .gitmodules"
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
 msgstr ""
 
-#: git-submodule.sh:410
+#: git-submodule.sh:414
 #, sh-format
-msgid "Failed to register url for submodule path '$path'"
+msgid "Failed to register url for submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:418
+#: git-submodule.sh:422
 #, sh-format
-msgid "Failed to register update mode for submodule path '$path'"
+msgid "Failed to register update mode for submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:420
+#: git-submodule.sh:424
 #, sh-format
-msgid "Submodule '$name' ($url) registered for path '$path'"
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:519
+#: git-submodule.sh:523
 #, sh-format
 msgid ""
-"Submodule path '$path' not initialized\n"
+"Submodule path '$sm_path' not initialized\n"
 "Maybe you want to use 'update --init'?"
 msgstr ""
 
-#: git-submodule.sh:532
+#: git-submodule.sh:536
 #, sh-format
-msgid "Unable to find current revision in submodule path '$path'"
+msgid "Unable to find current revision in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:551
+#: git-submodule.sh:555
 #, sh-format
-msgid "Unable to fetch in submodule path '$path'"
+msgid "Unable to fetch in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:565
+#: git-submodule.sh:569
 #, sh-format
-msgid "Unable to rebase '$sha1' in submodule path '$path'"
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:566
+#: git-submodule.sh:570
 #, sh-format
-msgid "Submodule path '$path': rebased into '$sha1'"
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
 msgstr ""
 
-#: git-submodule.sh:571
+#: git-submodule.sh:575
 #, sh-format
-msgid "Unable to merge '$sha1' in submodule path '$path'"
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:572
+#: git-submodule.sh:576
 #, sh-format
-msgid "Submodule path '$path': merged in '$sha1'"
+msgid "Submodule path '$sm_path': merged in '$sha1'"
 msgstr ""
 
-#: git-submodule.sh:577
+#: git-submodule.sh:581
 #, sh-format
-msgid "Unable to checkout '$sha1' in submodule path '$path'"
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:578
+#: git-submodule.sh:582
 #, sh-format
-msgid "Submodule path '$path': checked out '$sha1'"
+msgid "Submodule path '$sm_path': checked out '$sha1'"
 msgstr ""
 
-#: git-submodule.sh:600 git-submodule.sh:923
+#: git-submodule.sh:604 git-submodule.sh:927
 #, sh-format
-msgid "Failed to recurse into submodule path '$path'"
+msgid "Failed to recurse into submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:708
+#: git-submodule.sh:712
 msgid "--"
 msgstr ""
 
-#: git-submodule.sh:766
+#: git-submodule.sh:770
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_src"
 msgstr ""
 
-#: git-submodule.sh:769
+#: git-submodule.sh:773
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_dst"
 msgstr ""
 
-#: git-submodule.sh:772
+#: git-submodule.sh:776
 #, sh-format
 msgid "  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 msgstr ""
 
-#: git-submodule.sh:797
+#: git-submodule.sh:801
 msgid "blob"
 msgstr ""
 
-#: git-submodule.sh:798
+#: git-submodule.sh:802
 msgid "submodule"
 msgstr ""
 
-#: git-submodule.sh:969
+#: git-submodule.sh:973
 #, sh-format
 msgid "Synchronizing submodule url for '$name'"
 msgstr ""
index 58c450877865cc49c6d9766f67a1edfe2b0269ec..701b693378e45cb7ac009f918d385ed2232911f4 100644 (file)
@@ -12,7 +12,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Git\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2012-04-28 20:33+0800\n"
+"POT-Creation-Date: 2012-04-28 20:17+0800\n"
 "PO-Revision-Date: 2012-01-30 00:00+0800\n"
 "Last-Translator: Jiang Xin <worldhello.net@gmail.com>\n"
 "Language-Team: GitHub <https://github.com/gotgit/git/>\n"
@@ -22,7 +22,7 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 
-#: advice.c:34
+#: advice.c:40
 #, c-format
 msgid "hint: %.*s\n"
 msgstr "提示:%.*s\n"
@@ -31,7 +31,7 @@ msgstr "提示:%.*s\n"
 #. * Message used both when 'git commit' fails and when
 #. * other commands doing a merge do.
 #.
-#: advice.c:64
+#: advice.c:70
 msgid ""
 "Fix them up in the work tree,\n"
 "and then use 'git add/rm <file>' as\n"
@@ -42,12 +42,12 @@ msgstr ""
 "'git add/rm <file>' 标记解决方案,\n"
 "或使用 'git commit -a'。"
 
-#: commit.c:47
+#: commit.c:48
 #, c-format
 msgid "could not parse %s"
 msgstr "不能解析 %s"
 
-#: commit.c:49
+#: commit.c:50
 #, c-format
 msgid "%s %s is not a commit!"
 msgstr "%s %s 不是一个提交!"
@@ -183,14 +183,14 @@ msgstr[0] ""
 "您的分支和 '%s' 出现了偏离,\n"
 "并且各自分别有 %d 和 %d 处不同的提交。\n"
 
-#: sequencer.c:120 builtin/merge.c:864 builtin/merge.c:985
-#: builtin/merge.c:1095 builtin/merge.c:1105
+#: sequencer.c:120 builtin/merge.c:865 builtin/merge.c:978
+#: builtin/merge.c:1088 builtin/merge.c:1098
 #, c-format
 msgid "Could not open '%s' for writing"
 msgstr "不能为写入打开 '%s'"
 
-#: sequencer.c:122 builtin/merge.c:334 builtin/merge.c:867
-#: builtin/merge.c:1097 builtin/merge.c:1110
+#: sequencer.c:122 builtin/merge.c:333 builtin/merge.c:868
+#: builtin/merge.c:1090 builtin/merge.c:1103
 #, c-format
 msgid "Could not write to '%s'"
 msgstr "不能写入 '%s'"
@@ -285,8 +285,8 @@ msgstr "不能还原 %s... %s"
 msgid "could not apply %s... %s"
 msgstr "不能应用 %s... %s"
 
-#: sequencer.c:450 sequencer.c:909 builtin/log.c:288 builtin/log.c:713
-#: builtin/log.c:1329 builtin/log.c:1548 builtin/merge.c:348
+#: sequencer.c:450 sequencer.c:909 builtin/log.c:289 builtin/log.c:719
+#: builtin/log.c:1335 builtin/log.c:1554 builtin/merge.c:347
 #: builtin/shortlog.c:181
 msgid "revision walk setup failed"
 msgstr "版本遍历设置失败"
@@ -411,6 +411,25 @@ msgstr "不能作为初始提交还原"
 msgid "Can't cherry-pick into empty head"
 msgstr "不能拣选到空分支"
 
+#: sha1_name.c:864
+msgid "HEAD does not point to a branch"
+msgstr "HEAD 没有指向一个分支"
+
+#: sha1_name.c:867
+#, c-format
+msgid "No such branch: '%s'"
+msgstr "没有此分支:'%s'"
+
+#: sha1_name.c:869
+#, c-format
+msgid "No upstream configured for branch '%s'"
+msgstr "尚未给分支 '%s' 设置上游"
+
+#: sha1_name.c:872
+#, c-format
+msgid "Upstream branch '%s' not stored as a remote-tracking branch"
+msgstr "上游分支 '%s' 没有存储为一个远程跟踪分支"
+
 #: wt-status.c:134
 msgid "Unmerged paths:"
 msgstr "未合并的路径:"
@@ -830,34 +849,34 @@ msgstr ""
 
 #  译者:汉字之间无空格,故删除尾部空格
 #. TRANSLATORS: This is "remote " in "remote branch '%s' not found"
-#: builtin/branch.c:163
+#: builtin/branch.c:164
 msgid "remote "
 msgstr "远程"
 
-#: builtin/branch.c:171
+#: builtin/branch.c:172
 msgid "cannot use -a with -d"
 msgstr "不能将 -a 和 -d 共用"
 
-#: builtin/branch.c:177
+#: builtin/branch.c:178
 msgid "Couldn't look up commit object for HEAD"
 msgstr "无法查询 HEAD 指向的提交对象"
 
-#: builtin/branch.c:182
+#: builtin/branch.c:183
 #, c-format
 msgid "Cannot delete the branch '%s' which you are currently on."
 msgstr "无法删除您当前所在的分支 '%s'。"
 
-#: builtin/branch.c:192
+#: builtin/branch.c:193
 #, c-format
 msgid "%sbranch '%s' not found."
 msgstr "%s分支 '%s' 未发现。"
 
-#: builtin/branch.c:200
+#: builtin/branch.c:201
 #, c-format
 msgid "Couldn't look up commit object for '%s'"
 msgstr "无法查询 '%s' 指向的提交对象"
 
-#: builtin/branch.c:206
+#: builtin/branch.c:207
 #, c-format
 msgid ""
 "The branch '%s' is not fully merged.\n"
@@ -866,97 +885,97 @@ msgstr ""
 "分支 '%s' 没有完全合并。\n"
 "如果您确认要删除它,执行 'git branch -D %s'。"
 
-#: builtin/branch.c:214
+#: builtin/branch.c:215
 #, c-format
 msgid "Error deleting %sbranch '%s'"
 msgstr "删除 %s分支 '%s' 时出错"
 
-#: builtin/branch.c:219
+#: builtin/branch.c:221
 #, c-format
 msgid "Deleted %sbranch %s (was %s).\n"
 msgstr "已删除 %s分支 %s(曾为 %s)。\n"
 
-#: builtin/branch.c:224
+#: builtin/branch.c:226
 msgid "Update of config-file failed"
 msgstr "无法更新 config 文件"
 
-#: builtin/branch.c:322
+#: builtin/branch.c:324
 #, c-format
 msgid "branch '%s' does not point at a commit"
 msgstr "分支 '%s' 未指向一个提交"
 
 #  译者:注意保持句尾空格
-#: builtin/branch.c:394
+#: builtin/branch.c:396
 #, c-format
 msgid "behind %d] "
 msgstr "落后 %d] "
 
 #  译者:注意保持句尾空格
-#: builtin/branch.c:396
+#: builtin/branch.c:398
 #, c-format
 msgid "ahead %d] "
 msgstr "领先 %d] "
 
 #  译者:注意保持句尾空格
-#: builtin/branch.c:398
+#: builtin/branch.c:400
 #, c-format
 msgid "ahead %d, behind %d] "
 msgstr "领先 %d,落后 %d] "
 
-#: builtin/branch.c:501
+#: builtin/branch.c:503
 msgid "(no branch)"
 msgstr "(非分支)"
 
-#: builtin/branch.c:566
+#: builtin/branch.c:568
 msgid "some refs could not be read"
 msgstr "一些引用不能读取"
 
-#: builtin/branch.c:579
+#: builtin/branch.c:581
 msgid "cannot rename the current branch while not on any."
 msgstr "无法重命名当前分支因为不处于任何分支上。"
 
-#: builtin/branch.c:589
+#: builtin/branch.c:591
 #, c-format
 msgid "Invalid branch name: '%s'"
 msgstr "无效的分支名:'%s'"
 
-#: builtin/branch.c:604
+#: builtin/branch.c:606
 msgid "Branch rename failed"
 msgstr "分支重命名失败"
 
-#: builtin/branch.c:608
+#: builtin/branch.c:610
 #, c-format
 msgid "Renamed a misnamed branch '%s' away"
 msgstr "重命名掉一个错误命名的旧分支 '%s'"
 
-#: builtin/branch.c:612
+#: builtin/branch.c:614
 #, c-format
 msgid "Branch renamed to %s, but HEAD is not updated!"
 msgstr "分支重命名为 %s,但 HEAD 没有更新!"
 
-#: builtin/branch.c:619
+#: builtin/branch.c:621
 msgid "Branch is renamed, but update of config-file failed"
 msgstr "分支被重命名,但更新 config 文件失败"
 
-#: builtin/branch.c:634
+#: builtin/branch.c:636
 #, c-format
 msgid "malformed object name %s"
 msgstr "非法的对象名 %s"
 
-#: builtin/branch.c:658
+#: builtin/branch.c:660
 #, c-format
 msgid "could not write branch description template: %s\n"
 msgstr "不能写分支描述模版:%s\n"
 
-#: builtin/branch.c:746
+#: builtin/branch.c:750
 msgid "Failed to resolve HEAD as a valid ref."
 msgstr "无法将 HEAD 解析为有效引用。"
 
-#: builtin/branch.c:751 builtin/clone.c:558
+#: builtin/branch.c:755 builtin/clone.c:558
 msgid "HEAD not found below refs/heads!"
 msgstr "HEAD 没有位于 /refs/heads 之下!"
 
-#: builtin/branch.c:809
+#: builtin/branch.c:813
 msgid "-a and -r options to 'git branch' do not make sense with a branch name"
 msgstr "'git branch' 的 -a 和 -r 选项带一个分支名参数没有意义"
 
@@ -1018,7 +1037,7 @@ msgid "path '%s' is unmerged"
 msgstr "路径 '%s' 未合并"
 
 #: builtin/checkout.c:302 builtin/checkout.c:498 builtin/clone.c:583
-#: builtin/merge.c:811
+#: builtin/merge.c:812
 msgid "unable to write new index file"
 msgstr "无法写新的索引文件"
 
@@ -1035,43 +1054,43 @@ msgstr "您需要先解决当前索引的冲突"
 msgid "Can not do reflog for '%s'\n"
 msgstr "不能对 '%s' 执行 reflog 操作\n"
 
-#: builtin/checkout.c:565
+#: builtin/checkout.c:566
 msgid "HEAD is now at"
 msgstr "HEAD 目前位于"
 
-#: builtin/checkout.c:572
+#: builtin/checkout.c:573
 #, c-format
 msgid "Reset branch '%s'\n"
 msgstr "重置分支 '%s'\n"
 
-#: builtin/checkout.c:575
+#: builtin/checkout.c:576
 #, c-format
 msgid "Already on '%s'\n"
 msgstr "已经位于 '%s'\n"
 
-#: builtin/checkout.c:579
+#: builtin/checkout.c:580
 #, c-format
 msgid "Switched to and reset branch '%s'\n"
 msgstr "切换并重置分支 '%s'\n"
 
-#: builtin/checkout.c:581
+#: builtin/checkout.c:582
 #, c-format
 msgid "Switched to a new branch '%s'\n"
 msgstr "切换到一个新分支 '%s'\n"
 
-#: builtin/checkout.c:583
+#: builtin/checkout.c:584
 #, c-format
 msgid "Switched to branch '%s'\n"
 msgstr "切换到分支 '%s'\n"
 
 #  译者:注意保持前导空格
-#: builtin/checkout.c:639
+#: builtin/checkout.c:640
 #, c-format
 msgid " ... and %d more.\n"
 msgstr " ... 及其它 %d 个。\n"
 
 #. The singular version
-#: builtin/checkout.c:645
+#: builtin/checkout.c:646
 #, c-format
 msgid ""
 "Warning: you are leaving %d commit behind, not connected to\n"
@@ -1088,7 +1107,7 @@ msgstr[0] ""
 "\n"
 "%s\n"
 
-#: builtin/checkout.c:663
+#: builtin/checkout.c:664
 #, c-format
 msgid ""
 "If you want to keep them by creating a new branch, this may be a good time\n"
@@ -1103,71 +1122,71 @@ msgstr ""
 " git branch new_branch_name %s\n"
 "\n"
 
-#: builtin/checkout.c:692
+#: builtin/checkout.c:693
 msgid "internal error in revision walk"
 msgstr "在版本遍历时遇到内部错误"
 
-#: builtin/checkout.c:696
+#: builtin/checkout.c:697
 msgid "Previous HEAD position was"
 msgstr "之前的 HEAD 位置是"
 
-#: builtin/checkout.c:722
+#: builtin/checkout.c:723
 msgid "You are on a branch yet to be born"
 msgstr "您位于一个尚未初始化的分支"
 
 #. case (1)
-#: builtin/checkout.c:853
+#: builtin/checkout.c:854
 #, c-format
 msgid "invalid reference: %s"
 msgstr "无效引用:%s"
 
 #. case (1): want a tree
-#: builtin/checkout.c:892
+#: builtin/checkout.c:893
 #, c-format
 msgid "reference is not a tree: %s"
 msgstr "引用不是一个树:%s"
 
-#: builtin/checkout.c:972
+#: builtin/checkout.c:973
 msgid "-B cannot be used with -b"
 msgstr "-B 不能和 -b 共用"
 
-#: builtin/checkout.c:981
+#: builtin/checkout.c:982
 msgid "--patch is incompatible with all other options"
 msgstr "--patch 选项和其他选项不兼容"
 
-#: builtin/checkout.c:984
+#: builtin/checkout.c:985
 msgid "--detach cannot be used with -b/-B/--orphan"
 msgstr "--detach 不能和 -b/-B/--orphan 共用"
 
-#: builtin/checkout.c:986
+#: builtin/checkout.c:987
 msgid "--detach cannot be used with -t"
 msgstr "--detach 不能和 -t 共用"
 
-#: builtin/checkout.c:992
+#: builtin/checkout.c:993
 msgid "--track needs a branch name"
 msgstr "--track 需要一个分支名"
 
-#: builtin/checkout.c:999
+#: builtin/checkout.c:1000
 msgid "Missing branch name; try -b"
 msgstr "缺少分支名;尝试 -b"
 
-#: builtin/checkout.c:1005
+#: builtin/checkout.c:1006
 msgid "--orphan and -b|-B are mutually exclusive"
 msgstr "--orphan 和 -b|-B 互斥"
 
-#: builtin/checkout.c:1007
+#: builtin/checkout.c:1008
 msgid "--orphan cannot be used with -t"
 msgstr "--orphan 不能和 -t 共用"
 
-#: builtin/checkout.c:1017
+#: builtin/checkout.c:1018
 msgid "git checkout: -f and -m are incompatible"
 msgstr "git checkout:-f 和 -m 不兼容"
 
-#: builtin/checkout.c:1051
+#: builtin/checkout.c:1052
 msgid "invalid path specification"
 msgstr "无效的路径规格"
 
-#: builtin/checkout.c:1059
+#: builtin/checkout.c:1060
 #, c-format
 msgid ""
 "git checkout: updating paths is incompatible with switching branches.\n"
@@ -1176,26 +1195,26 @@ msgstr ""
 "git checkout:更新路径和切换分支不兼容。\n"
 "您是想要检出 '%s' 但未能将其解析为提交么?"
 
-#: builtin/checkout.c:1061
+#: builtin/checkout.c:1062
 msgid "git checkout: updating paths is incompatible with switching branches."
 msgstr "git checkout:更新路径和切换分支不兼容。"
 
-#: builtin/checkout.c:1066
+#: builtin/checkout.c:1067
 msgid "git checkout: --detach does not take a path argument"
 msgstr "git checkout:--detach 不跟路径参数"
 
-#: builtin/checkout.c:1069
+#: builtin/checkout.c:1070
 msgid ""
 "git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
 "checking out of the index."
 msgstr ""
 "git checkout:在从索引检出时,--ours/--theirs、--force 和 --merge 不兼容。"
 
-#: builtin/checkout.c:1088
+#: builtin/checkout.c:1089
 msgid "Cannot switch branch to a non-commit."
 msgstr "无法切换分支到一个非提交。"
 
-#: builtin/checkout.c:1091
+#: builtin/checkout.c:1092
 msgid "--ours/--theirs is incompatible with switching branches."
 msgstr "--ours/--theirs 和切换分支不兼容。"
 
@@ -1691,7 +1710,7 @@ msgstr "终止提交;您未更改来自模版的提交说明。\n"
 msgid "Aborting commit due to empty commit message.\n"
 msgstr "终止提交因为提交说明为空。\n"
 
-#: builtin/commit.c:1554 builtin/merge.c:935 builtin/merge.c:968
+#: builtin/commit.c:1554 builtin/merge.c:936 builtin/merge.c:961
 msgid "failed to write commit object"
 msgstr "无法写提交对象"
 
@@ -1834,69 +1853,73 @@ msgstr "提供了无法处理的对象 '%s'。"
 msgid "Couldn't find remote ref HEAD"
 msgstr "无法发现远程 HEAD 引用"
 
-#: builtin/fetch.c:252
+#: builtin/fetch.c:253
 #, c-format
 msgid "object %s not found"
 msgstr "对象 %s 未发现"
 
-#: builtin/fetch.c:258
+#: builtin/fetch.c:259
 msgid "[up to date]"
 msgstr "[最新]"
 
-#: builtin/fetch.c:272
+#: builtin/fetch.c:273
 #, c-format
 msgid "! %-*s %-*s -> %s  (can't fetch in current branch)"
 msgstr "! %-*s %-*s -> %s  (在当前分支下不能获取)"
 
-#: builtin/fetch.c:273 builtin/fetch.c:351
+#: builtin/fetch.c:274 builtin/fetch.c:360
 msgid "[rejected]"
 msgstr "[已拒绝]"
 
-#: builtin/fetch.c:284
+#: builtin/fetch.c:285
 msgid "[tag update]"
 msgstr "[tag更新]"
 
 #  译者:注意保持前导空格
-#: builtin/fetch.c:286 builtin/fetch.c:313 builtin/fetch.c:331
+#: builtin/fetch.c:287 builtin/fetch.c:322 builtin/fetch.c:340
 msgid "  (unable to update local ref)"
 msgstr "  (不能更新本地引用)"
 
-#: builtin/fetch.c:298
+#: builtin/fetch.c:305
 msgid "[new tag]"
 msgstr "[新tag]"
 
-#: builtin/fetch.c:302
+#: builtin/fetch.c:308
 msgid "[new branch]"
 msgstr "[新分支]"
 
-#: builtin/fetch.c:347
+#: builtin/fetch.c:311
+msgid "[new ref]"
+msgstr "[新引用]"
+
+#: builtin/fetch.c:356
 msgid "unable to update local ref"
 msgstr "不能更新本地引用"
 
-#: builtin/fetch.c:347
+#: builtin/fetch.c:356
 msgid "forced update"
 msgstr "强制更新"
 
-#: builtin/fetch.c:353
+#: builtin/fetch.c:362
 msgid "(non-fast-forward)"
 msgstr "(非快进式)"
 
-#: builtin/fetch.c:384 builtin/fetch.c:676
+#: builtin/fetch.c:393 builtin/fetch.c:685
 #, c-format
 msgid "cannot open %s: %s\n"
 msgstr "无法打开 %s:%s\n"
 
-#: builtin/fetch.c:393
+#: builtin/fetch.c:402
 #, c-format
 msgid "%s did not send all necessary objects\n"
 msgstr "%s 未发送所有必须的对象\n"
 
-#: builtin/fetch.c:479
+#: builtin/fetch.c:488
 #, c-format
 msgid "From %.*s\n"
 msgstr "来自 %.*s\n"
 
-#: builtin/fetch.c:490
+#: builtin/fetch.c:499
 #, c-format
 msgid ""
 "some local refs could not be updated; try running\n"
@@ -1906,79 +1929,79 @@ msgstr ""
 " 'git remote prune %s' 来删除旧的、有冲突的分支"
 
 #  译者:注意保持前导空格
-#: builtin/fetch.c:540
+#: builtin/fetch.c:549
 #, c-format
 msgid "   (%s will become dangling)\n"
 msgstr "   (%s 将成为悬空状态)\n"
 
 #  译者:注意保持前导空格
-#: builtin/fetch.c:541
+#: builtin/fetch.c:550
 #, c-format
 msgid "   (%s has become dangling)\n"
 msgstr "   (%s 已成为悬空状态)\n"
 
-#: builtin/fetch.c:548
+#: builtin/fetch.c:557
 msgid "[deleted]"
 msgstr "[已删除]"
 
-#: builtin/fetch.c:549
+#: builtin/fetch.c:558
 msgid "(none)"
 msgstr "(无)"
 
-#: builtin/fetch.c:666
+#: builtin/fetch.c:675
 #, c-format
 msgid "Refusing to fetch into current branch %s of non-bare repository"
 msgstr "拒绝获取到非裸版本库的当前分支 %s"
 
-#: builtin/fetch.c:700
+#: builtin/fetch.c:709
 #, c-format
 msgid "Don't know how to fetch from %s"
 msgstr "不知道如何从 %s 获取"
 
-#: builtin/fetch.c:777
+#: builtin/fetch.c:786
 #, c-format
 msgid "Option \"%s\" value \"%s\" is not valid for %s"
 msgstr "选项 \"%s\" 的值 \"%s\" 对于 %s 是无效的"
 
-#: builtin/fetch.c:780
+#: builtin/fetch.c:789
 #, c-format
 msgid "Option \"%s\" is ignored for %s\n"
 msgstr "选项 \"%s\" 对于 %s 被忽略\n"
 
-#: builtin/fetch.c:879
+#: builtin/fetch.c:888
 #, c-format
 msgid "Fetching %s\n"
 msgstr "正在获取 %s\n"
 
-#: builtin/fetch.c:881
+#: builtin/fetch.c:890
 #, c-format
 msgid "Could not fetch %s"
 msgstr "不能获取 %s"
 
-#: builtin/fetch.c:898
+#: builtin/fetch.c:907
 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:918
+#: builtin/fetch.c:927
 msgid "You need to specify a tag name."
 msgstr "您需要指定一个 tag 名称。"
 
-#: builtin/fetch.c:970
+#: builtin/fetch.c:979
 msgid "fetch --all does not take a repository argument"
 msgstr "fetch --all 不能带一个版本库参数"
 
-#: builtin/fetch.c:972
+#: builtin/fetch.c:981
 msgid "fetch --all does not make sense with refspecs"
 msgstr "fetch --all 带引用表达式没有任何意义"
 
-#: builtin/fetch.c:983
+#: builtin/fetch.c:992
 #, c-format
 msgid "No such remote or remote group: %s"
 msgstr "没有这样的远程或远程组:%s"
 
-#: builtin/fetch.c:991
+#: builtin/fetch.c:1000
 msgid "Fetching a group and specifying refspecs does not make sense"
 msgstr "获取组并指定引用表达式没有意义"
 
@@ -2219,164 +2242,164 @@ msgstr "不能访问当前工作目录"
 msgid "Cannot access work tree '%s'"
 msgstr "不能访问工作区 '%s'"
 
-#: builtin/log.c:187
+#: builtin/log.c:188
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr "最终输出:%d %s\n"
 
-#: builtin/log.c:395 builtin/log.c:483
+#: builtin/log.c:401 builtin/log.c:489
 #, c-format
 msgid "Could not read object %s"
 msgstr "不能读取对象 %s"
 
-#: builtin/log.c:507
+#: builtin/log.c:513
 #, c-format
 msgid "Unknown type: %d"
 msgstr "未知类型:%d"
 
-#: builtin/log.c:596
+#: builtin/log.c:602
 msgid "format.headers without value"
 msgstr "format.headers 没有值"
 
-#: builtin/log.c:669
+#: builtin/log.c:675
 msgid "name of output directory is too long"
 msgstr "输出目录名太长"
 
-#: builtin/log.c:680
+#: builtin/log.c:686
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr "无法打开补丁文件 %s"
 
-#: builtin/log.c:694
+#: builtin/log.c:700
 msgid "Need exactly one range."
 msgstr "只需要一个范围。"
 
-#: builtin/log.c:702
+#: builtin/log.c:708
 msgid "Not a range."
 msgstr "不是一个范围。"
 
-#: builtin/log.c:739
+#: builtin/log.c:745
 msgid "Could not extract email from committer identity."
 msgstr "不能从提交者身份中提取邮件地址。"
 
-#: builtin/log.c:785
+#: builtin/log.c:791
 msgid "Cover letter needs email format"
 msgstr "信封需要邮件地址格式"
 
-#: builtin/log.c:879
+#: builtin/log.c:885
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr "不正常的 in-reply-to:%s"
 
-#: builtin/log.c:952
+#: builtin/log.c:958
 msgid "Two output directories?"
 msgstr "两个输出目录?"
 
-#: builtin/log.c:1173
+#: builtin/log.c:1179
 #, c-format
 msgid "bogus committer info %s"
 msgstr "虚假的提交者信息 %s"
 
-#: builtin/log.c:1218
+#: builtin/log.c:1224
 msgid "-n and -k are mutually exclusive."
 msgstr "-n 和 -k 互斥。"
 
-#: builtin/log.c:1220
+#: builtin/log.c:1226
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr "--subject-prefix 和 -k 互斥。"
 
-#: builtin/log.c:1225 builtin/shortlog.c:284
+#: builtin/log.c:1231 builtin/shortlog.c:284
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr "未识别的参数:%s"
 
-#: builtin/log.c:1228
+#: builtin/log.c:1234
 msgid "--name-only does not make sense"
 msgstr "--name-only 无意义"
 
-#: builtin/log.c:1230
+#: builtin/log.c:1236
 msgid "--name-status does not make sense"
 msgstr "--name-status 无意义"
 
-#: builtin/log.c:1232
+#: builtin/log.c:1238
 msgid "--check does not make sense"
 msgstr "--check 无意义"
 
-#: builtin/log.c:1255
+#: builtin/log.c:1261
 msgid "standard output, or directory, which one?"
 msgstr "标准输出或目录,哪一个?"
 
-#: builtin/log.c:1257
+#: builtin/log.c:1263
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "不能创建目录 '%s'"
 
-#: builtin/log.c:1410
+#: builtin/log.c:1416
 msgid "Failed to create output files"
 msgstr "无法创建输出文件"
 
-#: builtin/log.c:1514
+#: builtin/log.c:1520
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> manually.\n"
 msgstr "不能找到跟踪的远程分支,请手工指定 <upstream>。\n"
 
-#: builtin/log.c:1530 builtin/log.c:1532 builtin/log.c:1544
+#: builtin/log.c:1536 builtin/log.c:1538 builtin/log.c:1550
 #, c-format
 msgid "Unknown commit %s"
 msgstr "未知提交 %s"
 
-#: builtin/merge.c:91
+#: builtin/merge.c:90
 msgid "switch `m' requires a value"
 msgstr "开关 `m' 需要一个值"
 
-#: builtin/merge.c:128
+#: builtin/merge.c:127
 #, c-format
 msgid "Could not find merge strategy '%s'.\n"
 msgstr "不能找到合并策略 '%s'。\n"
 
-#: builtin/merge.c:129
+#: builtin/merge.c:128
 #, c-format
 msgid "Available strategies are:"
 msgstr "可用的策略有:"
 
-#: builtin/merge.c:134
+#: builtin/merge.c:133
 #, c-format
 msgid "Available custom strategies are:"
 msgstr "可用的自定义策略有:"
 
-#: builtin/merge.c:241
+#: builtin/merge.c:240
 msgid "could not run stash."
 msgstr "不能进行进度保存。"
 
-#: builtin/merge.c:246
+#: builtin/merge.c:245
 msgid "stash failed"
 msgstr "进度保存失败"
 
-#: builtin/merge.c:251
+#: builtin/merge.c:250
 #, c-format
 msgid "not a valid object: %s"
 msgstr "不是一个有效对象:%s"
 
-#: builtin/merge.c:270 builtin/merge.c:287
+#: builtin/merge.c:269 builtin/merge.c:286
 msgid "read-tree failed"
 msgstr "读取树失败"
 
 #  译者:注意保持前导空格
-#: builtin/merge.c:317
+#: builtin/merge.c:316
 msgid " (nothing to squash)"
 msgstr " (无可压缩)"
 
-#: builtin/merge.c:330
+#: builtin/merge.c:329
 #, c-format
 msgid "Squash commit -- not updating HEAD\n"
 msgstr "压缩提交 -- 未更新 HEAD\n"
 
-#: builtin/merge.c:362
+#: builtin/merge.c:361
 msgid "Writing SQUASH_MSG"
 msgstr "写入 SQUASH_MSG"
 
-#: builtin/merge.c:364
+#: builtin/merge.c:363
 msgid "Finishing SQUASH_MSG"
 msgstr "完成 SQUASH_MSG"
 
@@ -2403,35 +2426,35 @@ msgstr "git write-tree 无法写入一树对象"
 msgid "failed to read the cache"
 msgstr "无法读取缓存"
 
-#: builtin/merge.c:696
+#: builtin/merge.c:697
 msgid "Unable to write index."
 msgstr "不能写索引。"
 
-#: builtin/merge.c:709
+#: builtin/merge.c:710
 msgid "Not handling anything other than two heads merge."
 msgstr "不能处理两个头合并之外的任何操作。"
 
-#: builtin/merge.c:723
+#: builtin/merge.c:724
 #, c-format
 msgid "Unknown option for merge-recursive: -X%s"
 msgstr "merge-recursive 的未知选项:-X%s"
 
-#: builtin/merge.c:737
+#: builtin/merge.c:738
 #, c-format
 msgid "unable to write %s"
 msgstr "不能写 %s"
 
-#: builtin/merge.c:876
+#: builtin/merge.c:877
 #, c-format
 msgid "Could not read from '%s'"
 msgstr "不能从 '%s' 读取"
 
-#: builtin/merge.c:885
+#: builtin/merge.c:886
 #, c-format
 msgid "Not committing merge; use 'git commit' to complete the merge.\n"
 msgstr "未提交合并,使用 'git commit' 完成此次合并。\n"
 
-#: builtin/merge.c:891
+#: builtin/merge.c:892
 msgid ""
 "Please enter a commit message to explain why this merge is necessary,\n"
 "especially if it merges an updated upstream into a topic branch.\n"
@@ -2444,47 +2467,52 @@ msgstr ""
 "\n"
 "以 '#' 开头的行将被忽略,而且空提交说明将会终止提交。\n"
 
-#: builtin/merge.c:915
+#: builtin/merge.c:916
 msgid "Empty commit message."
 msgstr "空提交信息。"
 
-#: builtin/merge.c:927
+#: builtin/merge.c:928
 #, c-format
 msgid "Wonderful.\n"
 msgstr "太棒了。\n"
 
-#: builtin/merge.c:1000
+#: builtin/merge.c:993
 #, c-format
 msgid "Automatic merge failed; fix conflicts and then commit the result.\n"
 msgstr "自动合并失败,修正冲突然后提交修正的结果。\n"
 
-#: builtin/merge.c:1016
+#: builtin/merge.c:1009
 #, c-format
 msgid "'%s' is not a commit"
 msgstr "'%s' 不是一个提交"
 
-#: builtin/merge.c:1057
+#: builtin/merge.c:1050
 msgid "No current branch."
 msgstr "没有当前分支。"
 
-#: builtin/merge.c:1059
+#: builtin/merge.c:1052
 msgid "No remote for the current branch."
 msgstr "当前分支没有对应的远程版本库。"
 
-#: builtin/merge.c:1061
+#: builtin/merge.c:1054
 msgid "No default upstream defined for the current branch."
 msgstr "当前分支没有定义默认的上游分支。"
 
-#: builtin/merge.c:1066
+#: builtin/merge.c:1059
 #, c-format
 msgid "No remote tracking branch for %s from %s"
 msgstr "%s 没有来自 %s 的远程跟踪分支"
 
-#: builtin/merge.c:1188
+#: builtin/merge.c:1146 builtin/merge.c:1303
+#, c-format
+msgid "%s - not something we can merge"
+msgstr "%s - 不能被合并"
+
+#: builtin/merge.c:1214
 msgid "There is no merge to abort (MERGE_HEAD missing)."
 msgstr "没有要终止的合并(MERGE_HEAD 丢失)。"
 
-#: builtin/merge.c:1204 git-pull.sh:31
+#: builtin/merge.c:1230 git-pull.sh:31
 msgid ""
 "You have not concluded your merge (MERGE_HEAD exists).\n"
 "Please, commit your changes before you can merge."
@@ -2492,11 +2520,11 @@ msgstr ""
 "您尚未结束您的合并(存在 MERGE_HEAD)。\n"
 "请在合并前先提交您的修改。"
 
-#: builtin/merge.c:1207 git-pull.sh:34
+#: builtin/merge.c:1233 git-pull.sh:34
 msgid "You have not concluded your merge (MERGE_HEAD exists)."
 msgstr "您尚未结束您的合并(存在 MERGE_HEAD)。"
 
-#: builtin/merge.c:1211
+#: builtin/merge.c:1237
 msgid ""
 "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
 "Please, commit your changes before you can merge."
@@ -2504,84 +2532,79 @@ msgstr ""
 "您尚未结束您的拣选(存在 CHERRY_PICK_HEAD)。\n"
 "请在合并前先提交您的修改。"
 
-#: builtin/merge.c:1214
+#: builtin/merge.c:1240
 msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."
 msgstr "您尚未结束您的拣选(存在 CHERRY_PICK_HEAD)。"
 
-#: builtin/merge.c:1223
+#: builtin/merge.c:1249
 msgid "You cannot combine --squash with --no-ff."
 msgstr "您不能将 --squash 与 --no-ff 共用。"
 
-#: builtin/merge.c:1228
+#: builtin/merge.c:1254
 msgid "You cannot combine --no-ff with --ff-only."
 msgstr "您不能将 --no-ff 与 --ff-only 共用。"
 
-#: builtin/merge.c:1235
+#: builtin/merge.c:1261
 msgid "No commit specified and merge.defaultToUpstream not set."
 msgstr "未指定提交并且 merge.defaultToUpstream 未设置。"
 
-#: builtin/merge.c:1266
+#: builtin/merge.c:1293
 msgid "Can merge only exactly one commit into empty head"
 msgstr "只能将一个提交合并到空分支上"
 
-#: builtin/merge.c:1269
+#: builtin/merge.c:1296
 msgid "Squash commit into empty head not supported yet"
 msgstr "尚不支持到空分支的压缩提交"
 
-#: builtin/merge.c:1271
+#: builtin/merge.c:1298
 msgid "Non-fast-forward commit does not make sense into an empty head"
 msgstr "到空分支的非快进式提交没有意义"
 
-#: builtin/merge.c:1275 builtin/merge.c:1319
-#, c-format
-msgid "%s - not something we can merge"
-msgstr "%s - 不能被合并"
-
-#: builtin/merge.c:1382
+#: builtin/merge.c:1413
 #, c-format
 msgid "Updating %s..%s\n"
 msgstr "更新 %s..%s\n"
 
-#: builtin/merge.c:1420
+#: builtin/merge.c:1451
 #, c-format
 msgid "Trying really trivial in-index merge...\n"
 msgstr "尝试非常小的索引内合并...\n"
 
-#: builtin/merge.c:1427
+#: builtin/merge.c:1458
 #, c-format
 msgid "Nope.\n"
 msgstr "无。\n"
 
-#: builtin/merge.c:1459
+#: builtin/merge.c:1490
 msgid "Not possible to fast-forward, aborting."
 msgstr "无法快进,终止。"
 
-#: builtin/merge.c:1482 builtin/merge.c:1559
+#: builtin/merge.c:1513 builtin/merge.c:1592
 #, c-format
 msgid "Rewinding the tree to pristine...\n"
 msgstr "将树回滚至原始状态...\n"
 
-#: builtin/merge.c:1486
+#: builtin/merge.c:1517
 #, c-format
 msgid "Trying merge strategy %s...\n"
 msgstr "尝试合并策略 %s...\n"
 
-#: builtin/merge.c:1550
+#: builtin/merge.c:1583
 #, c-format
 msgid "No merge strategy handled the merge.\n"
 msgstr "没有合并策略处理此合并。\n"
 
-#: builtin/merge.c:1552
+#: builtin/merge.c:1585
 #, c-format
 msgid "Merge with strategy %s failed.\n"
 msgstr "使用策略 %s 合并失败。\n"
 
-#: builtin/merge.c:1561
+#: builtin/merge.c:1594
 #, c-format
 msgid "Using the %s to prepare resolving by hand.\n"
 msgstr "使用 %s 以准备手工解决。\n"
 
-#: builtin/merge.c:1572
+#: builtin/merge.c:1606
 #, c-format
 msgid "Automatic merge went well; stopped before committing as requested\n"
 msgstr "自动合并进展顺利,按要求在提交前停止\n"
@@ -2822,15 +2845,15 @@ msgstr "选项 %s 不接受否定格式"
 msgid "unable to parse value '%s' for option %s"
 msgstr "不能解析值 '%s' 针对于选项 %s"
 
-#: builtin/push.c:44
+#: builtin/push.c:45
 msgid "tag shorthand without <tag>"
 msgstr "tag 简写没有跟 <tag> 参数"
 
-#: builtin/push.c:63
+#: builtin/push.c:64
 msgid "--delete only accepts plain target ref names"
 msgstr "--delete 只接受简单的目标引用名"
 
-#: builtin/push.c:83
+#: builtin/push.c:84
 #, c-format
 msgid ""
 "You are not currently on a branch.\n"
@@ -2844,7 +2867,7 @@ msgstr ""
 "\n"
 "    git push %s HEAD:<name-of-remote-branch>\n"
 
-#: builtin/push.c:90
+#: builtin/push.c:91
 #, c-format
 msgid ""
 "The current branch %s has no upstream branch.\n"
@@ -2857,12 +2880,12 @@ msgstr ""
 "\n"
 "    git push --set-upstream %s %s\n"
 
-#: builtin/push.c:98
+#: builtin/push.c:99
 #, c-format
 msgid "The current branch %s has multiple upstream branches, refusing to push."
 msgstr "当前分支 %s 有多个上游分支,拒绝推送。"
 
-#: builtin/push.c:101
+#: builtin/push.c:102
 #, c-format
 msgid ""
 "You are pushing to remote '%s', which is not the upstream of\n"
@@ -2872,38 +2895,60 @@ msgstr ""
 "您正推送至远程 '%s'(其并非当前分支 '%s' 的上游),\n"
 "而没有告诉我要推送什么、更新哪个远程分支。"
 
-#: builtin/push.c:127
+#: builtin/push.c:131
 msgid ""
 "You didn't specify any refspecs to push, and push.default is \"nothing\"."
 msgstr "您没有为推送指定任何引用表达式,并且 push.default 为 \"nothing\"。"
 
-#: builtin/push.c:147
+#: builtin/push.c:138
+msgid ""
+"Updates were rejected because the tip of your current branch is behind\n"
+"its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+"before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"更新被拒绝,因为您当前分支落后于对应的远程分支。再次推送前先与远程变更\n"
+"合并(如 'git pull')。\n"
+"详见 'git push --help' 中的 'Note about fast-forwards' 的内容。"
+
+#: builtin/push.c:144
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. If you did not intend to push that branch, you may want to\n"
+"specify branches to push or set the 'push.default' configuration\n"
+"variable to 'current' or 'upstream' to push only the current branch."
+msgstr ""
+"更新被拒绝,因为推送的一个分支落后于对应的远程分支。如果您并非有意推送\n"
+"该分支,您可以指定要推送的分支或者设置 'push.default' 配置变量为\n"
+"'current' 或 'upstream' 以便只推送当前分支。"
+
+#: builtin/push.c:150
+msgid ""
+"Updates were rejected because a pushed branch tip is behind its remote\n"
+"counterpart. Check out this branch and merge the remote changes\n"
+"(e.g. 'git pull') before pushing again.\n"
+"See the 'Note about fast-forwards' in 'git push --help' for details."
+msgstr ""
+"更新被拒绝,因为推送的一个分支落后于对应的远程分支。检出该分支并在再次\n"
+"推送之前与远程变更合并(如 'git pull')。\n"
+"详见 'git push --help' 中的 'Note about fast-forwards' 的内容。"
+
+#: builtin/push.c:190
 #, c-format
 msgid "Pushing to %s\n"
 msgstr "推送到 %s\n"
 
-#: builtin/push.c:151
+#: builtin/push.c:194
 #, c-format
 msgid "failed to push some refs to '%s'"
 msgstr "无法推送一些引用到 '%s'"
 
-#: builtin/push.c:159
-#, c-format
-msgid ""
-"To prevent you from losing history, non-fast-forward updates were rejected\n"
-"Merge the remote changes (e.g. 'git pull') before pushing again.  See the\n"
-"'Note about fast-forwards' section of 'git push --help' for details.\n"
-msgstr ""
-"为了防止您丢失提交历史,非快进式更新被拒绝。\n"
-"再次推送前先与远程变更合并(如 'git pull')。详见\n"
-"'git push --help' 中的 'Note about fast-forwards' 小节。\n"
-
-#: builtin/push.c:176
+#: builtin/push.c:226
 #, c-format
 msgid "bad repository '%s'"
 msgstr "坏的版本库 '%s'"
 
-#: builtin/push.c:177
+#: builtin/push.c:227
 msgid ""
 "No configured push destination.\n"
 "Either specify the URL from the command-line or configure a remote "
@@ -2924,31 +2969,31 @@ msgstr ""
 "\n"
 "    git push <name>\n"
 
-#: builtin/push.c:192
+#: builtin/push.c:242
 msgid "--all and --tags are incompatible"
 msgstr "--all 和 --tags 不兼容"
 
-#: builtin/push.c:193
+#: builtin/push.c:243
 msgid "--all can't be combined with refspecs"
 msgstr "--all 不能和引用表达式共用"
 
-#: builtin/push.c:198
+#: builtin/push.c:248
 msgid "--mirror and --tags are incompatible"
 msgstr "--mirror 和 --tags 不兼容"
 
-#: builtin/push.c:199
+#: builtin/push.c:249
 msgid "--mirror can't be combined with refspecs"
 msgstr "--mirror 不能和引用表达式共用"
 
-#: builtin/push.c:204
+#: builtin/push.c:254
 msgid "--all and --mirror are incompatible"
 msgstr "--all 和 --mirror 不兼容"
 
-#: builtin/push.c:284
+#: builtin/push.c:342
 msgid "--delete is incompatible with --all, --mirror and --tags"
 msgstr "--delete 与 --all、--mirror 及 --tags 不兼容"
 
-#: builtin/push.c:286
+#: builtin/push.c:344
 msgid "--delete doesn't make sense without any refs"
 msgstr "--delete 未接任何引用没有意义"
 
@@ -3034,20 +3079,20 @@ msgstr "不能对裸版本库进行%s重置"
 msgid "Could not reset index file to revision '%s'."
 msgstr "不能重置索引文件至版本 '%s'。"
 
-#: builtin/revert.c:70 builtin/revert.c:91
+#: builtin/revert.c:70 builtin/revert.c:92
 #, c-format
 msgid "%s: %s cannot be used with %s"
 msgstr "%s:%s 不能和 %s 共用"
 
-#: builtin/revert.c:126
+#: builtin/revert.c:127
 msgid "program error"
 msgstr "程序错误"
 
-#: builtin/revert.c:209
+#: builtin/revert.c:213
 msgid "revert failed"
 msgstr "还原失败"
 
-#: builtin/revert.c:224
+#: builtin/revert.c:228
 msgid "cherry-pick failed"
 msgstr "拣选失败"
 
@@ -3226,15 +3271,15 @@ msgstr "%s:不能更新引用"
 msgid "Updated tag '%s' (was %s)\n"
 msgstr "已更新tag '%s'(曾为 %s)\n"
 
-#: git-am.sh:49
+#: git-am.sh:50
 msgid "You need to set your committer info first"
 msgstr "您需要先设置你的提交者信息"
 
-#: git-am.sh:136
+#: git-am.sh:137
 msgid "Repository lacks necessary blobs to fall back on 3-way merge."
 msgstr "版本库缺乏必要的 blob 数据以进行三路合并。"
 
-#: git-am.sh:147
+#: git-am.sh:154
 msgid ""
 "Did you hand edit your patch?\n"
 "It does not apply to blobs recorded in its index."
@@ -3242,46 +3287,46 @@ msgstr ""
 "您是否曾手动编辑过您的补丁?\n"
 "无法应用补丁到索引中的数据上。"
 
-#: git-am.sh:156
+#: git-am.sh:163
 msgid "Falling back to patching base and 3-way merge..."
 msgstr "回退到补丁基础版本并使用三路合并..."
 
-#: git-am.sh:268
+#: git-am.sh:275
 msgid "Only one StGIT patch series can be applied at once"
 msgstr "一次只能有一个 StGIT 补丁队列被应用"
 
-#: git-am.sh:355
+#: git-am.sh:362
 #, sh-format
 msgid "Patch format $patch_format is not supported."
 msgstr "不支持 $patch_format 补丁格式。"
 
-#: git-am.sh:357
+#: git-am.sh:364
 msgid "Patch format detection failed."
 msgstr "补丁格式检测失败。"
 
-#: git-am.sh:411
+#: git-am.sh:418
 msgid "-d option is no longer supported.  Do not use."
 msgstr "不再支持 -d 选项。不要使用。"
 
-#: git-am.sh:474
+#: git-am.sh:481
 #, sh-format
 msgid "previous rebase directory $dotest still exists but mbox given."
 msgstr "之前的变基目录 $dotest 仍然存在但给出了mbox。"
 
-#: git-am.sh:479
+#: git-am.sh:486
 msgid "Please make up your mind. --skip or --abort?"
 msgstr "请下决心。--skip 或是 --abort ?"
 
-#: git-am.sh:506
+#: git-am.sh:513
 msgid "Resolve operation not in progress, we are not resuming."
 msgstr "解决操作未进行,我们不会继续。"
 
-#: git-am.sh:572
+#: git-am.sh:579
 #, sh-format
 msgid "Dirty index: cannot apply patches (dirty: $files)"
 msgstr "脏的索引:不能应用补丁(脏文件:$files)"
 
-#: git-am.sh:748
+#: git-am.sh:755
 msgid "cannot be interactive without stdin connected to a terminal."
 msgstr "标准输入没有和终端关联,不能进行交互式操作。"
 
@@ -3289,20 +3334,20 @@ 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:759
+#: git-am.sh:766
 msgid "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
 msgstr "应用?[y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
 
-#: git-am.sh:795
+#: git-am.sh:802
 #, sh-format
 msgid "Applying: $FIRSTLINE"
 msgstr "正应用:$FIRSTLINE"
 
-#: git-am.sh:840
+#: git-am.sh:847
 msgid "No changes -- Patch already applied."
 msgstr "没有变更 -- 补丁已经应用过。"
 
-#: git-am.sh:866
+#: git-am.sh:873
 msgid "applying to an empty history"
 msgstr "正应用到一个空历史上"
 
@@ -3541,166 +3586,166 @@ msgstr "(为恢复数据输入 \"git stash apply\")"
 msgid "cannot strip one component off url '$remoteurl'"
 msgstr "无法从 url '$remoteurl' 剥离一个组件"
 
-#: git-submodule.sh:108
+#: git-submodule.sh:109
 #, sh-format
-msgid "No submodule mapping found in .gitmodules for path '$path'"
-msgstr "未在 .gitmodules 中发现路径 '$path' 的子模组映射"
+msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
+msgstr "未在 .gitmodules 中发现路径 '$sm_path' 的子模组映射"
 
-#: git-submodule.sh:149
+#: git-submodule.sh:150
 #, sh-format
-msgid "Clone of '$url' into submodule path '$path' failed"
-msgstr "无法克隆 '$url' 到子模组路径 '$path'"
+msgid "Clone of '$url' into submodule path '$sm_path' failed"
+msgstr "无法克隆 '$url' 到子模组路径 '$sm_path'"
 
-#: git-submodule.sh:159
+#: git-submodule.sh:160
 #, sh-format
 msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
 msgstr "Gitdir '$a' 在子模组路径 '$b' 之下或者相反"
 
-#: git-submodule.sh:247
+#: git-submodule.sh:249
 #, sh-format
 msgid "repo URL: '$repo' must be absolute or begin with ./|../"
 msgstr "版本库URL:'$repo' 必须是绝对路径或以 ./|../ 起始"
 
-#: git-submodule.sh:264
+#: git-submodule.sh:266
 #, sh-format
-msgid "'$path' already exists in the index"
-msgstr "'$path' 已经存在于索引中"
+msgid "'$sm_path' already exists in the index"
+msgstr "'$sm_path' 已经存在于索引中"
 
-#: git-submodule.sh:281
+#: git-submodule.sh:283
 #, sh-format
-msgid "'$path' already exists and is not a valid git repo"
-msgstr "'$path' 已存在且不是一个有效的 git 版本库"
+msgid "'$sm_path' already exists and is not a valid git repo"
+msgstr "'$sm_path' 已存在且不是一个有效的 git 版本库"
 
-#: git-submodule.sh:295
+#: git-submodule.sh:297
 #, sh-format
-msgid "Unable to checkout submodule '$path'"
-msgstr "不能检出子模组 '$path'"
+msgid "Unable to checkout submodule '$sm_path'"
+msgstr "不能检出子模组 '$sm_path'"
 
-#: git-submodule.sh:300
+#: git-submodule.sh:302
 #, sh-format
-msgid "Failed to add submodule '$path'"
-msgstr "无法添加子模组 '$path'"
+msgid "Failed to add submodule '$sm_path'"
+msgstr "无法添加子模组 '$sm_path'"
 
-#: git-submodule.sh:305
+#: git-submodule.sh:307
 #, sh-format
-msgid "Failed to register submodule '$path'"
-msgstr "无法注册子模组 '$path'"
+msgid "Failed to register submodule '$sm_path'"
+msgstr "无法注册子模组 '$sm_path'"
 
-#: git-submodule.sh:347
+#: git-submodule.sh:349
 #, sh-format
-msgid "Entering '$prefix$path'"
-msgstr "正在进入 '$prefix$path'"
+msgid "Entering '$prefix$sm_path'"
+msgstr "正在进入 '$prefix$sm_path'"
 
-#: git-submodule.sh:359
+#: git-submodule.sh:363
 #, sh-format
-msgid "Stopping at '$path'; script returned non-zero status."
-msgstr "停止于 '$path',脚本返回非零值。"
+msgid "Stopping at '$sm_path'; script returned non-zero status."
+msgstr "停止于 '$sm_path',脚本返回非零值。"
 
-#: git-submodule.sh:401
+#: git-submodule.sh:405
 #, sh-format
-msgid "No url found for submodule path '$path' in .gitmodules"
-msgstr "在 .gitmodules 中未找到子模组路径 '$path' 的 url"
+msgid "No url found for submodule path '$sm_path' in .gitmodules"
+msgstr "在 .gitmodules 中未找到子模组路径 '$sm_path' 的 url"
 
-#: git-submodule.sh:410
+#: git-submodule.sh:414
 #, sh-format
-msgid "Failed to register url for submodule path '$path'"
-msgstr "无法为子模组路径 '$path' 注册 url"
+msgid "Failed to register url for submodule path '$sm_path'"
+msgstr "无法为子模组路径 '$sm_path' 注册 url"
 
-#: git-submodule.sh:418
+#: git-submodule.sh:422
 #, sh-format
-msgid "Failed to register update mode for submodule path '$path'"
-msgstr "无法为子模组路径 '$path' 注册更新模式"
+msgid "Failed to register update mode for submodule path '$sm_path'"
+msgstr "无法为子模组路径 '$sm_path' 注册更新模式"
 
-#: git-submodule.sh:420
+#: git-submodule.sh:424
 #, sh-format
-msgid "Submodule '$name' ($url) registered for path '$path'"
-msgstr "子模组 '$name' ($url) 已为路径 '$path' 注册"
+msgid "Submodule '$name' ($url) registered for path '$sm_path'"
+msgstr "子模组 '$name' ($url) 已为路径 '$sm_path' 注册"
 
-#: git-submodule.sh:519
+#: git-submodule.sh:523
 #, sh-format
 msgid ""
-"Submodule path '$path' not initialized\n"
+"Submodule path '$sm_path' not initialized\n"
 "Maybe you want to use 'update --init'?"
 msgstr ""
-"子模组路径 '$path' 没有初始化\n"
+"子模组路径 '$sm_path' 没有初始化\n"
 "也许您想用 'update --init'?"
 
-#: git-submodule.sh:532
+#: git-submodule.sh:536
 #, sh-format
-msgid "Unable to find current revision in submodule path '$path'"
-msgstr "无法在子模组路径 '$path' 中找到当前版本"
+msgid "Unable to find current revision in submodule path '$sm_path'"
+msgstr "无法在子模组路径 '$sm_path' 中找到当前版本"
 
-#: git-submodule.sh:551
+#: git-submodule.sh:555
 #, sh-format
-msgid "Unable to fetch in submodule path '$path'"
-msgstr "无法在子模组路径 '$path' 中获取"
+msgid "Unable to fetch in submodule path '$sm_path'"
+msgstr "无法在子模组路径 '$sm_path' 中获取"
 
-#: git-submodule.sh:565
+#: git-submodule.sh:569
 #, sh-format
-msgid "Unable to rebase '$sha1' in submodule path '$path'"
-msgstr "无法在子模组路径 '$path' 中变基 '$sha1'"
+msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
+msgstr "无法在子模组路径 '$sm_path' 中变基 '$sha1'"
 
-#: git-submodule.sh:566
+#: git-submodule.sh:570
 #, sh-format
-msgid "Submodule path '$path': rebased into '$sha1'"
-msgstr "子模组路径 '$path':变基至 '$sha1'"
+msgid "Submodule path '$sm_path': rebased into '$sha1'"
+msgstr "子模组路径 '$sm_path':变基至 '$sha1'"
 
-#: git-submodule.sh:571
+#: git-submodule.sh:575
 #, sh-format
-msgid "Unable to merge '$sha1' in submodule path '$path'"
-msgstr "无法合并 '$sha1' 到子模组路径 '$path' 中"
+msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
+msgstr "无法合并 '$sha1' 到子模组路径 '$sm_path' 中"
 
-#: git-submodule.sh:572
+#: git-submodule.sh:576
 #, sh-format
-msgid "Submodule path '$path': merged in '$sha1'"
-msgstr "子模组路径 '$path':已合并入 '$sha1'"
+msgid "Submodule path '$sm_path': merged in '$sha1'"
+msgstr "子模组路径 '$sm_path':已合并入 '$sha1'"
 
-#: git-submodule.sh:577
+#: git-submodule.sh:581
 #, sh-format
-msgid "Unable to checkout '$sha1' in submodule path '$path'"
-msgstr "无法在子模组路径 '$path' 中检出 '$sha1'"
+msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
+msgstr "无法在子模组路径 '$sm_path' 中检出 '$sha1'"
 
-#: git-submodule.sh:578
+#: git-submodule.sh:582
 #, sh-format
-msgid "Submodule path '$path': checked out '$sha1'"
-msgstr "子模组路径 '$path':检出 '$sha1'"
+msgid "Submodule path '$sm_path': checked out '$sha1'"
+msgstr "子模组路径 '$sm_path':检出 '$sha1'"
 
-#: git-submodule.sh:600 git-submodule.sh:923
+#: git-submodule.sh:604 git-submodule.sh:927
 #, sh-format
-msgid "Failed to recurse into submodule path '$path'"
-msgstr "无法递归进子模组路径 '$path'"
+msgid "Failed to recurse into submodule path '$sm_path'"
+msgstr "无法递归进子模组路径 '$sm_path'"
 
-#: git-submodule.sh:708
+#: git-submodule.sh:712
 msgid "--"
 msgstr "--"
 
 #  译者:注意保持前导空格
-#: git-submodule.sh:766
+#: git-submodule.sh:770
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_src"
 msgstr "  警告:$name 未包含提交 $sha1_src"
 
 #  译者:注意保持前导空格
-#: git-submodule.sh:769
+#: git-submodule.sh:773
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_dst"
 msgstr "  警告:$name 未包含提交 $sha1_dst"
 
 #  译者:注意保持前导空格
-#: git-submodule.sh:772
+#: git-submodule.sh:776
 #, sh-format
 msgid "  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 msgstr "  警告:$name 未包含提交 $sha1_src 和 $sha1_dst"
 
-#: git-submodule.sh:797
+#: git-submodule.sh:801
 msgid "blob"
 msgstr "blob"
 
-#: git-submodule.sh:798
+#: git-submodule.sh:802
 msgid "submodule"
 msgstr "子模组"
 
-#: git-submodule.sh:969
+#: git-submodule.sh:973
 #, sh-format
 msgid "Synchronizing submodule url for '$name'"
 msgstr "为 '$name' 同步子模组 url"
index 6c8f3958369b0a2afd0ad85aea5ab23a44678f70..ef355cc9a89b948688756b8c3681ac607518492b 100644 (file)
@@ -12,6 +12,8 @@
 #include "commit.h"
 #include "blob.h"
 #include "resolve-undo.h"
+#include "strbuf.h"
+#include "varint.h"
 
 static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 
@@ -1179,15 +1181,74 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int reall
        return refresh_cache_ent(&the_index, ce, really, NULL, NULL);
 }
 
+
+/*****************************************************************
+ * Index File I/O
+ *****************************************************************/
+
+#define INDEX_FORMAT_DEFAULT 3
+
+/*
+ * dev/ino/uid/gid/size are also just tracked to the low 32 bits
+ * Again - this is just a (very strong in practice) heuristic that
+ * the inode hasn't changed.
+ *
+ * We save the fields in big-endian order to allow using the
+ * index file over NFS transparently.
+ */
+struct ondisk_cache_entry {
+       struct cache_time ctime;
+       struct cache_time mtime;
+       unsigned int dev;
+       unsigned int ino;
+       unsigned int mode;
+       unsigned int uid;
+       unsigned int gid;
+       unsigned int size;
+       unsigned char sha1[20];
+       unsigned short flags;
+       char name[FLEX_ARRAY]; /* more */
+};
+
+/*
+ * This struct is used when CE_EXTENDED bit is 1
+ * The struct must match ondisk_cache_entry exactly from
+ * ctime till flags
+ */
+struct ondisk_cache_entry_extended {
+       struct cache_time ctime;
+       struct cache_time mtime;
+       unsigned int dev;
+       unsigned int ino;
+       unsigned int mode;
+       unsigned int uid;
+       unsigned int gid;
+       unsigned int size;
+       unsigned char sha1[20];
+       unsigned short flags;
+       unsigned short flags2;
+       char name[FLEX_ARRAY]; /* more */
+};
+
+/* These are only used for v3 or lower */
+#define align_flex_name(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
+#define ondisk_cache_entry_size(len) align_flex_name(ondisk_cache_entry,len)
+#define ondisk_cache_entry_extended_size(len) align_flex_name(ondisk_cache_entry_extended,len)
+#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
+                           ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
+                           ondisk_cache_entry_size(ce_namelen(ce)))
+
 static int verify_hdr(struct cache_header *hdr, unsigned long size)
 {
        git_SHA_CTX c;
        unsigned char sha1[20];
+       int hdr_version;
 
        if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
                return error("bad signature");
-       if (hdr->hdr_version != htonl(2) && hdr->hdr_version != htonl(3))
-               return error("bad index version");
+       hdr_version = ntohl(hdr->hdr_version);
+       if (hdr_version < 2 || 4 < hdr_version)
+               return error("bad index version %d", hdr_version);
        git_SHA1_Init(&c);
        git_SHA1_Update(&c, hdr, size - 20);
        git_SHA1_Final(sha1, &c);
@@ -1221,7 +1282,74 @@ int read_index(struct index_state *istate)
        return read_index_from(istate, get_index_file());
 }
 
-static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk)
+#ifndef NEEDS_ALIGNED_ACCESS
+#define ntoh_s(var) ntohs(var)
+#define ntoh_l(var) ntohl(var)
+#else
+static inline uint16_t ntoh_s_force_align(void *p)
+{
+       uint16_t x;
+       memcpy(&x, p, sizeof(x));
+       return ntohs(x);
+}
+static inline uint32_t ntoh_l_force_align(void *p)
+{
+       uint32_t x;
+       memcpy(&x, p, sizeof(x));
+       return ntohl(x);
+}
+#define ntoh_s(var) ntoh_s_force_align(&(var))
+#define ntoh_l(var) ntoh_l_force_align(&(var))
+#endif
+
+static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *ondisk,
+                                                  unsigned int flags,
+                                                  const char *name,
+                                                  size_t len)
+{
+       struct cache_entry *ce = xmalloc(cache_entry_size(len));
+
+       ce->ce_ctime.sec = ntoh_l(ondisk->ctime.sec);
+       ce->ce_mtime.sec = ntoh_l(ondisk->mtime.sec);
+       ce->ce_ctime.nsec = ntoh_l(ondisk->ctime.nsec);
+       ce->ce_mtime.nsec = ntoh_l(ondisk->mtime.nsec);
+       ce->ce_dev   = ntoh_l(ondisk->dev);
+       ce->ce_ino   = ntoh_l(ondisk->ino);
+       ce->ce_mode  = ntoh_l(ondisk->mode);
+       ce->ce_uid   = ntoh_l(ondisk->uid);
+       ce->ce_gid   = ntoh_l(ondisk->gid);
+       ce->ce_size  = ntoh_l(ondisk->size);
+       ce->ce_flags = flags;
+       hashcpy(ce->sha1, ondisk->sha1);
+       memcpy(ce->name, name, len);
+       ce->name[len] = '\0';
+       return ce;
+}
+
+/*
+ * Adjacent cache entries tend to share the leading paths, so it makes
+ * sense to only store the differences in later entries.  In the v4
+ * on-disk format of the index, each on-disk cache entry stores the
+ * number of bytes to be stripped from the end of the previous name,
+ * and the bytes to append to the result, to come up with its name.
+ */
+static unsigned long expand_name_field(struct strbuf *name, const char *cp_)
+{
+       const unsigned char *ep, *cp = (const unsigned char *)cp_;
+       size_t len = decode_varint(&cp);
+
+       if (name->len < len)
+               die("malformed name field in the index");
+       strbuf_remove(name, name->len - len, len);
+       for (ep = cp; *ep; ep++)
+               ; /* find the end */
+       strbuf_add(name, cp, ep - cp);
+       return (const char *)ep + 1 - cp_;
+}
+
+static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk,
+                                           unsigned long *ent_size,
+                                           struct strbuf *previous_name)
 {
        struct cache_entry *ce;
        size_t len;
@@ -1229,14 +1357,14 @@ static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk)
        unsigned int flags;
 
        /* On-disk flags are just 16 bits */
-       flags = ntohs(ondisk->flags);
+       flags = ntoh_s(ondisk->flags);
        len = flags & CE_NAMEMASK;
 
        if (flags & CE_EXTENDED) {
                struct ondisk_cache_entry_extended *ondisk2;
                int extended_flags;
                ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
-               extended_flags = ntohs(ondisk2->flags2) << 16;
+               extended_flags = ntoh_s(ondisk2->flags2) << 16;
                /* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
                if (extended_flags & ~CE_EXTENDED_FLAGS)
                        die("Unknown index entry format %08x", extended_flags);
@@ -1246,27 +1374,22 @@ static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk)
        else
                name = ondisk->name;
 
-       if (len == CE_NAMEMASK)
-               len = strlen(name);
-
-       ce = xmalloc(cache_entry_size(len));
-
-       ce->ce_ctime.sec = ntohl(ondisk->ctime.sec);
-       ce->ce_mtime.sec = ntohl(ondisk->mtime.sec);
-       ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec);
-       ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec);
-       ce->ce_dev   = ntohl(ondisk->dev);
-       ce->ce_ino   = ntohl(ondisk->ino);
-       ce->ce_mode  = ntohl(ondisk->mode);
-       ce->ce_uid   = ntohl(ondisk->uid);
-       ce->ce_gid   = ntohl(ondisk->gid);
-       ce->ce_size  = ntohl(ondisk->size);
-       ce->ce_flags = flags;
-
-       hashcpy(ce->sha1, ondisk->sha1);
-
-       memcpy(ce->name, name, len);
-       ce->name[len] = '\0';
+       if (!previous_name) {
+               /* v3 and earlier */
+               if (len == CE_NAMEMASK)
+                       len = strlen(name);
+               ce = cache_entry_from_ondisk(ondisk, flags, name, len);
+
+               *ent_size = ondisk_ce_size(ce);
+       } else {
+               unsigned long consumed;
+               consumed = expand_name_field(previous_name, name);
+               ce = cache_entry_from_ondisk(ondisk, flags,
+                                            previous_name->buf,
+                                            previous_name->len);
+
+               *ent_size = (name - ((char *)ondisk)) + consumed;
+       }
        return ce;
 }
 
@@ -1279,6 +1402,7 @@ int read_index_from(struct index_state *istate, const char *path)
        struct cache_header *hdr;
        void *mmap;
        size_t mmap_size;
+       struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
 
        errno = EBUSY;
        if (istate->initialized)
@@ -1311,22 +1435,30 @@ int read_index_from(struct index_state *istate, const char *path)
        if (verify_hdr(hdr, mmap_size) < 0)
                goto unmap;
 
+       istate->version = ntohl(hdr->hdr_version);
        istate->cache_nr = ntohl(hdr->hdr_entries);
        istate->cache_alloc = alloc_nr(istate->cache_nr);
        istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
        istate->initialized = 1;
 
+       if (istate->version == 4)
+               previous_name = &previous_name_buf;
+       else
+               previous_name = NULL;
+
        src_offset = sizeof(*hdr);
        for (i = 0; i < istate->cache_nr; i++) {
                struct ondisk_cache_entry *disk_ce;
                struct cache_entry *ce;
+               unsigned long consumed;
 
                disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
-               ce = create_from_disk(disk_ce);
+               ce = create_from_disk(disk_ce, &consumed, previous_name);
                set_index_entry(istate, i, ce);
 
-               src_offset += ondisk_ce_size(ce);
+               src_offset += consumed;
        }
+       strbuf_release(&previous_name_buf);
        istate->timestamp.sec = st.st_mtime;
        istate->timestamp.nsec = ST_MTIME_NSEC(st);
 
@@ -1510,13 +1642,10 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
        }
 }
 
-static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
+/* Copy miscellaneous fields but not the name */
+static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
+                                      struct cache_entry *ce)
 {
-       int size = ondisk_ce_size(ce);
-       struct ondisk_cache_entry *ondisk = xcalloc(1, size);
-       char *name;
-       int result;
-
        ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
        ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
        ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
@@ -1533,11 +1662,52 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
                struct ondisk_cache_entry_extended *ondisk2;
                ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
                ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16);
-               name = ondisk2->name;
+               return ondisk2->name;
+       }
+       else {
+               return ondisk->name;
+       }
+}
+
+static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
+                         struct strbuf *previous_name)
+{
+       int size;
+       struct ondisk_cache_entry *ondisk;
+       char *name;
+       int result;
+
+       if (!previous_name) {
+               size = ondisk_ce_size(ce);
+               ondisk = xcalloc(1, size);
+               name = copy_cache_entry_to_ondisk(ondisk, ce);
+               memcpy(name, ce->name, ce_namelen(ce));
+       } else {
+               int common, to_remove, prefix_size;
+               unsigned char to_remove_vi[16];
+               for (common = 0;
+                    (ce->name[common] &&
+                     common < previous_name->len &&
+                     ce->name[common] == previous_name->buf[common]);
+                    common++)
+                       ; /* still matching */
+               to_remove = previous_name->len - common;
+               prefix_size = encode_varint(to_remove, to_remove_vi);
+
+               if (ce->ce_flags & CE_EXTENDED)
+                       size = offsetof(struct ondisk_cache_entry_extended, name);
+               else
+                       size = offsetof(struct ondisk_cache_entry, name);
+               size += prefix_size + (ce_namelen(ce) - common + 1);
+
+               ondisk = xcalloc(1, size);
+               name = copy_cache_entry_to_ondisk(ondisk, ce);
+               memcpy(name, to_remove_vi, prefix_size);
+               memcpy(name + prefix_size, ce->name + common, ce_namelen(ce) - common);
+
+               strbuf_splice(previous_name, common, to_remove,
+                             ce->name + common, ce_namelen(ce) - common);
        }
-       else
-               name = ondisk->name;
-       memcpy(name, ce->name, ce_namelen(ce));
 
        result = ce_write(c, fd, ondisk, size);
        free(ondisk);
@@ -1573,10 +1743,11 @@ int write_index(struct index_state *istate, int newfd)
 {
        git_SHA_CTX c;
        struct cache_header hdr;
-       int i, err, removed, extended;
+       int i, err, removed, extended, hdr_version;
        struct cache_entry **cache = istate->cache;
        int entries = istate->cache_nr;
        struct stat st;
+       struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
 
        for (i = removed = extended = 0; i < entries; i++) {
                if (cache[i]->ce_flags & CE_REMOVE)
@@ -1590,24 +1761,34 @@ int write_index(struct index_state *istate, int newfd)
                }
        }
 
+       if (!istate->version)
+               istate->version = INDEX_FORMAT_DEFAULT;
+
+       /* demote version 3 to version 2 when the latter suffices */
+       if (istate->version == 3 || istate->version == 2)
+               istate->version = extended ? 3 : 2;
+
+       hdr_version = istate->version;
+
        hdr.hdr_signature = htonl(CACHE_SIGNATURE);
-       /* for extended format, increase version so older git won't try to read it */
-       hdr.hdr_version = htonl(extended ? 3 : 2);
+       hdr.hdr_version = htonl(hdr_version);
        hdr.hdr_entries = htonl(entries - removed);
 
        git_SHA1_Init(&c);
        if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
                return -1;
 
+       previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
        for (i = 0; i < entries; i++) {
                struct cache_entry *ce = cache[i];
                if (ce->ce_flags & CE_REMOVE)
                        continue;
                if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
                        ce_smudge_racily_clean_entry(ce);
-               if (ce_write_entry(&c, newfd, ce) < 0)
+               if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
                        return -1;
        }
+       strbuf_release(&previous_name_buf);
 
        /* Write extension data here */
        if (istate->cache_tree) {
diff --git a/refs.c b/refs.c
index c9f68353517bb6dcd4b19114a5191fdf9075a8d1..a5802e19029747fc95939bc9110ea151c080c30c 100644 (file)
--- a/refs.c
+++ b/refs.c
 #include "tag.h"
 #include "dir.h"
 
-/* ISSYMREF=0x01, ISPACKED=0x02 and ISBROKEN=0x04 are public interfaces */
-#define REF_KNOWS_PEELED 0x10
+/*
+ * Make sure "ref" is something reasonable to have under ".git/refs/";
+ * We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
+ * - it ends with a "/".
+ * - it ends with ".lock"
+ * - it contains a "\" (backslash)
+ */
 
-struct ref_entry {
-       unsigned char flag; /* ISSYMREF? ISPACKED? */
+/* Return true iff ch is not allowed in reference names. */
+static inline int bad_ref_char(int ch)
+{
+       if (((unsigned) ch) <= ' ' || ch == 0x7f ||
+           ch == '~' || ch == '^' || ch == ':' || ch == '\\')
+               return 1;
+       /* 2.13 Pattern Matching Notation */
+       if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
+               return 1;
+       return 0;
+}
+
+/*
+ * Try to read one refname component from the front of refname.  Return
+ * the length of the component found, or -1 if the component is not
+ * legal.
+ */
+static int check_refname_component(const char *refname, int flags)
+{
+       const char *cp;
+       char last = '\0';
+
+       for (cp = refname; ; cp++) {
+               char ch = *cp;
+               if (ch == '\0' || ch == '/')
+                       break;
+               if (bad_ref_char(ch))
+                       return -1; /* Illegal character in refname. */
+               if (last == '.' && ch == '.')
+                       return -1; /* Refname contains "..". */
+               if (last == '@' && ch == '{')
+                       return -1; /* Refname contains "@{". */
+               last = ch;
+       }
+       if (cp == refname)
+               return 0; /* Component has zero length. */
+       if (refname[0] == '.') {
+               if (!(flags & REFNAME_DOT_COMPONENT))
+                       return -1; /* Component starts with '.'. */
+               /*
+                * Even if leading dots are allowed, don't allow "."
+                * as a component (".." is prevented by a rule above).
+                */
+               if (refname[1] == '\0')
+                       return -1; /* Component equals ".". */
+       }
+       if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
+               return -1; /* Refname ends with ".lock". */
+       return cp - refname;
+}
+
+int check_refname_format(const char *refname, int flags)
+{
+       int component_len, component_count = 0;
+
+       while (1) {
+               /* We are at the start of a path component. */
+               component_len = check_refname_component(refname, flags);
+               if (component_len <= 0) {
+                       if ((flags & REFNAME_REFSPEC_PATTERN) &&
+                                       refname[0] == '*' &&
+                                       (refname[1] == '\0' || refname[1] == '/')) {
+                               /* Accept one wildcard as a full refname component. */
+                               flags &= ~REFNAME_REFSPEC_PATTERN;
+                               component_len = 1;
+                       } else {
+                               return -1;
+                       }
+               }
+               component_count++;
+               if (refname[component_len] == '\0')
+                       break;
+               /* Skip to next component. */
+               refname += component_len + 1;
+       }
+
+       if (refname[component_len - 1] == '.')
+               return -1; /* Refname ends with '.'. */
+       if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
+               return -1; /* Refname has only one component. */
+       return 0;
+}
+
+struct ref_entry;
+
+struct ref_value {
        unsigned char sha1[20];
        unsigned char peeled[20];
-       /* The full name of the reference (e.g., "refs/heads/master"): */
-       char name[FLEX_ARRAY];
 };
 
-struct ref_array {
+struct ref_dir {
        int nr, alloc;
 
        /*
@@ -26,41 +117,59 @@ struct ref_array {
         */
        int sorted;
 
-       struct ref_entry **refs;
+       struct ref_entry **entries;
 };
 
+/* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */
+#define REF_KNOWS_PEELED 0x08
+#define REF_DIR 0x10
+
 /*
- * Parse one line from a packed-refs file.  Write the SHA1 to sha1.
- * Return a pointer to the refname within the line (null-terminated),
- * or NULL if there was a problem.
+ * A ref_entry represents either a reference or a "subdirectory" of
+ * references.  Each directory in the reference namespace is
+ * represented by a ref_entry with (flags & REF_DIR) set and
+ * containing a subdir member that holds the entries in that
+ * directory.  References are represented by a ref_entry with (flags &
+ * REF_DIR) unset and a value member that describes the reference's
+ * value.  The flag member is at the ref_entry level, but it is also
+ * needed to interpret the contents of the value field (in other
+ * words, a ref_value object is not very much use without the
+ * enclosing ref_entry).
+ *
+ * Reference names cannot end with slash and directories' names are
+ * always stored with a trailing slash (except for the top-level
+ * directory, which is always denoted by "").  This has two nice
+ * consequences: (1) when the entries in each subdir are sorted
+ * lexicographically by name (as they usually are), the references in
+ * a whole tree can be generated in lexicographic order by traversing
+ * the tree in left-to-right, depth-first order; (2) the names of
+ * references and subdirectories cannot conflict, and therefore the
+ * presence of an empty subdirectory does not block the creation of a
+ * similarly-named reference.  (The fact that reference names with the
+ * same leading components can conflict *with each other* is a
+ * separate issue that is regulated by is_refname_available().)
+ *
+ * Please note that the name field contains the fully-qualified
+ * reference (or subdirectory) name.  Space could be saved by only
+ * storing the relative names.  But that would require the full names
+ * to be generated on the fly when iterating in do_for_each_ref(), and
+ * would break callback functions, who have always been able to assume
+ * that the name strings that they are passed will not be freed during
+ * the iteration.
  */
-static const char *parse_ref_line(char *line, unsigned char *sha1)
-{
+struct ref_entry {
+       unsigned char flag; /* ISSYMREF? ISPACKED? */
+       union {
+               struct ref_value value; /* if not (flags&REF_DIR) */
+               struct ref_dir subdir; /* if (flags&REF_DIR) */
+       } u;
        /*
-        * 42: the answer to everything.
-        *
-        * In this case, it happens to be the answer to
-        *  40 (length of sha1 hex representation)
-        *  +1 (space in between hex and name)
-        *  +1 (newline at the end of the line)
+        * The full name of the reference (e.g., "refs/heads/master")
+        * or the full name of the directory with a trailing slash
+        * (e.g., "refs/heads/"):
         */
-       int len = strlen(line) - 42;
-
-       if (len <= 0)
-               return NULL;
-       if (get_sha1_hex(line, sha1) < 0)
-               return NULL;
-       if (!isspace(line[40]))
-               return NULL;
-       line += 41;
-       if (isspace(*line))
-               return NULL;
-       if (line[len] != '\n')
-               return NULL;
-       line[len] = 0;
-
-       return line;
-}
+       char name[FLEX_ARRAY];
+};
 
 static struct ref_entry *create_ref_entry(const char *refname,
                                          const unsigned char *sha1, int flag,
@@ -74,18 +183,59 @@ static struct ref_entry *create_ref_entry(const char *refname,
                die("Reference has invalid format: '%s'", refname);
        len = strlen(refname) + 1;
        ref = xmalloc(sizeof(struct ref_entry) + len);
-       hashcpy(ref->sha1, sha1);
-       hashclr(ref->peeled);
+       hashcpy(ref->u.value.sha1, sha1);
+       hashclr(ref->u.value.peeled);
        memcpy(ref->name, refname, len);
        ref->flag = flag;
        return ref;
 }
 
-/* Add a ref_entry to the end of the ref_array (unsorted). */
-static void add_ref(struct ref_array *refs, struct ref_entry *ref)
+static void clear_ref_dir(struct ref_dir *dir);
+
+static void free_ref_entry(struct ref_entry *entry)
+{
+       if (entry->flag & REF_DIR)
+               clear_ref_dir(&entry->u.subdir);
+       free(entry);
+}
+
+/*
+ * Add a ref_entry to the end of dir (unsorted).  Entry is always
+ * stored directly in dir; no recursion into subdirectories is
+ * done.
+ */
+static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry)
 {
-       ALLOC_GROW(refs->refs, refs->nr + 1, refs->alloc);
-       refs->refs[refs->nr++] = ref;
+       ALLOC_GROW(dir->entries, dir->nr + 1, dir->alloc);
+       dir->entries[dir->nr++] = entry;
+}
+
+/*
+ * Clear and free all entries in dir, recursively.
+ */
+static void clear_ref_dir(struct ref_dir *dir)
+{
+       int i;
+       for (i = 0; i < dir->nr; i++)
+               free_ref_entry(dir->entries[i]);
+       free(dir->entries);
+       dir->sorted = dir->nr = dir->alloc = 0;
+       dir->entries = NULL;
+}
+
+/*
+ * Create a struct ref_entry object for the specified dirname.
+ * dirname is the name of the directory with a trailing slash (e.g.,
+ * "refs/heads/") or "" for the top-level directory.
+ */
+static struct ref_entry *create_dir_entry(const char *dirname)
+{
+       struct ref_entry *direntry;
+       int len = strlen(dirname);
+       direntry = xcalloc(1, sizeof(struct ref_entry) + len + 1);
+       memcpy(direntry->name, dirname, len + 1);
+       direntry->flag = REF_DIR;
+       return direntry;
 }
 
 static int ref_entry_cmp(const void *a, const void *b)
@@ -95,6 +245,102 @@ static int ref_entry_cmp(const void *a, const void *b)
        return strcmp(one->name, two->name);
 }
 
+static void sort_ref_dir(struct ref_dir *dir);
+
+/*
+ * Return the entry with the given refname from the ref_dir
+ * (non-recursively), sorting dir if necessary.  Return NULL if no
+ * such entry is found.
+ */
+static struct ref_entry *search_ref_dir(struct ref_dir *dir, const char *refname)
+{
+       struct ref_entry *e, **r;
+       int len;
+
+       if (refname == NULL || !dir->nr)
+               return NULL;
+
+       sort_ref_dir(dir);
+
+       len = strlen(refname) + 1;
+       e = xmalloc(sizeof(struct ref_entry) + len);
+       memcpy(e->name, refname, len);
+
+       r = bsearch(&e, dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp);
+
+       free(e);
+
+       if (r == NULL)
+               return NULL;
+
+       return *r;
+}
+
+/*
+ * If refname is a reference name, find the ref_dir within the dir
+ * tree that should hold refname.  If refname is a directory name
+ * (i.e., ends in '/'), then return that ref_dir itself.  dir must
+ * represent the top-level directory.  Sort ref_dirs and recurse into
+ * subdirectories as necessary.  If mkdir is set, then create any
+ * missing directories; otherwise, return NULL if the desired
+ * directory cannot be found.
+ */
+static struct ref_dir *find_containing_dir(struct ref_dir *dir,
+                                          const char *refname, int mkdir)
+{
+       char *refname_copy = xstrdup(refname);
+       char *slash;
+       struct ref_entry *entry;
+       for (slash = strchr(refname_copy, '/'); slash; slash = strchr(slash + 1, '/')) {
+               char tmp = slash[1];
+               slash[1] = '\0';
+               entry = search_ref_dir(dir, refname_copy);
+               if (!entry) {
+                       if (!mkdir) {
+                               dir = NULL;
+                               break;
+                       }
+                       entry = create_dir_entry(refname_copy);
+                       add_entry_to_dir(dir, entry);
+               }
+               slash[1] = tmp;
+               assert(entry->flag & REF_DIR);
+               dir = &entry->u.subdir;
+       }
+
+       free(refname_copy);
+       return dir;
+}
+
+/*
+ * Find the value entry with the given name in dir, sorting ref_dirs
+ * and recursing into subdirectories as necessary.  If the name is not
+ * found or it corresponds to a directory entry, return NULL.
+ */
+static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname)
+{
+       struct ref_entry *entry;
+       dir = find_containing_dir(dir, refname, 0);
+       if (!dir)
+               return NULL;
+       entry = search_ref_dir(dir, refname);
+       return (entry && !(entry->flag & REF_DIR)) ? entry : NULL;
+}
+
+/*
+ * Add a ref_entry to the ref_dir (unsorted), recursing into
+ * subdirectories as necessary.  dir must represent the top-level
+ * directory.  Return 0 on success.
+ */
+static int add_ref(struct ref_dir *dir, struct ref_entry *ref)
+{
+       dir = find_containing_dir(dir, ref->name, 1);
+       if (!dir)
+               return -1;
+       add_entry_to_dir(dir, ref);
+       return 0;
+}
+
 /*
  * Emit a warning and return true iff ref1 and ref2 have the same name
  * and the same sha1.  Die if they have the same name but different
@@ -102,69 +348,242 @@ static int ref_entry_cmp(const void *a, const void *b)
  */
 static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2)
 {
-       if (!strcmp(ref1->name, ref2->name)) {
-               /* Duplicate name; make sure that the SHA1s match: */
-               if (hashcmp(ref1->sha1, ref2->sha1))
-                       die("Duplicated ref, and SHA1s don't match: %s",
-                           ref1->name);
-               warning("Duplicated ref: %s", ref1->name);
-               return 1;
-       } else {
+       if (strcmp(ref1->name, ref2->name))
                return 0;
-       }
+
+       /* Duplicate name; make sure that they don't conflict: */
+
+       if ((ref1->flag & REF_DIR) || (ref2->flag & REF_DIR))
+               /* This is impossible by construction */
+               die("Reference directory conflict: %s", ref1->name);
+
+       if (hashcmp(ref1->u.value.sha1, ref2->u.value.sha1))
+               die("Duplicated ref, and SHA1s don't match: %s", ref1->name);
+
+       warning("Duplicated ref: %s", ref1->name);
+       return 1;
 }
 
 /*
- * Sort the entries in array (if they are not already sorted).
+ * Sort the entries in dir non-recursively (if they are not already
+ * sorted) and remove any duplicate entries.
  */
-static void sort_ref_array(struct ref_array *array)
+static void sort_ref_dir(struct ref_dir *dir)
 {
        int i, j;
+       struct ref_entry *last = NULL;
 
        /*
         * This check also prevents passing a zero-length array to qsort(),
         * which is a problem on some platforms.
         */
-       if (array->sorted == array->nr)
+       if (dir->sorted == dir->nr)
                return;
 
-       qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
+       qsort(dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp);
 
-       /* Remove any duplicates from the ref_array */
-       i = 0;
-       for (j = 1; j < array->nr; j++) {
-               if (is_dup_ref(array->refs[i], array->refs[j])) {
-                       free(array->refs[j]);
-                       continue;
+       /* Remove any duplicates: */
+       for (i = 0, j = 0; j < dir->nr; j++) {
+               struct ref_entry *entry = dir->entries[j];
+               if (last && is_dup_ref(last, entry))
+                       free_ref_entry(entry);
+               else
+                       last = dir->entries[i++] = entry;
+       }
+       dir->sorted = dir->nr = i;
+}
+
+#define DO_FOR_EACH_INCLUDE_BROKEN 01
+
+static struct ref_entry *current_ref;
+
+static int do_one_ref(const char *base, each_ref_fn fn, int trim,
+                     int flags, void *cb_data, struct ref_entry *entry)
+{
+       int retval;
+       if (prefixcmp(entry->name, base))
+               return 0;
+
+       if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+               if (entry->flag & REF_ISBROKEN)
+                       return 0; /* ignore broken refs e.g. dangling symref */
+               if (!has_sha1_file(entry->u.value.sha1)) {
+                       error("%s does not point to a valid object!", entry->name);
+                       return 0;
                }
-               array->refs[++i] = array->refs[j];
        }
-       array->sorted = array->nr = i + 1;
+       current_ref = entry;
+       retval = fn(entry->name + trim, entry->u.value.sha1, entry->flag, cb_data);
+       current_ref = NULL;
+       return retval;
 }
 
-static struct ref_entry *search_ref_array(struct ref_array *array, const char *refname)
+/*
+ * Call fn for each reference in dir that has index in the range
+ * offset <= index < dir->nr.  Recurse into subdirectories that are in
+ * that index range, sorting them before iterating.  This function
+ * does not sort dir itself; it should be sorted beforehand.
+ */
+static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
+                                 const char *base,
+                                 each_ref_fn fn, int trim, int flags, void *cb_data)
 {
-       struct ref_entry *e, **r;
-       int len;
+       int i;
+       assert(dir->sorted == dir->nr);
+       for (i = offset; i < dir->nr; i++) {
+               struct ref_entry *entry = dir->entries[i];
+               int retval;
+               if (entry->flag & REF_DIR) {
+                       sort_ref_dir(&entry->u.subdir);
+                       retval = do_for_each_ref_in_dir(&entry->u.subdir, 0,
+                                                       base, fn, trim, flags, cb_data);
+               } else {
+                       retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
+               }
+               if (retval)
+                       return retval;
+       }
+       return 0;
+}
 
-       if (refname == NULL)
-               return NULL;
+/*
+ * Call fn for each reference in the union of dir1 and dir2, in order
+ * by refname.  Recurse into subdirectories.  If a value entry appears
+ * in both dir1 and dir2, then only process the version that is in
+ * dir2.  The input dirs must already be sorted, but subdirs will be
+ * sorted as needed.
+ */
+static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
+                                  struct ref_dir *dir2,
+                                  const char *base, each_ref_fn fn, int trim,
+                                  int flags, void *cb_data)
+{
+       int retval;
+       int i1 = 0, i2 = 0;
 
-       if (!array->nr)
-               return NULL;
-       sort_ref_array(array);
-       len = strlen(refname) + 1;
-       e = xmalloc(sizeof(struct ref_entry) + len);
-       memcpy(e->name, refname, len);
+       assert(dir1->sorted == dir1->nr);
+       assert(dir2->sorted == dir2->nr);
+       while (1) {
+               struct ref_entry *e1, *e2;
+               int cmp;
+               if (i1 == dir1->nr) {
+                       return do_for_each_ref_in_dir(dir2, i2,
+                                                     base, fn, trim, flags, cb_data);
+               }
+               if (i2 == dir2->nr) {
+                       return do_for_each_ref_in_dir(dir1, i1,
+                                                     base, fn, trim, flags, cb_data);
+               }
+               e1 = dir1->entries[i1];
+               e2 = dir2->entries[i2];
+               cmp = strcmp(e1->name, e2->name);
+               if (cmp == 0) {
+                       if ((e1->flag & REF_DIR) && (e2->flag & REF_DIR)) {
+                               /* Both are directories; descend them in parallel. */
+                               sort_ref_dir(&e1->u.subdir);
+                               sort_ref_dir(&e2->u.subdir);
+                               retval = do_for_each_ref_in_dirs(
+                                               &e1->u.subdir, &e2->u.subdir,
+                                               base, fn, trim, flags, cb_data);
+                               i1++;
+                               i2++;
+                       } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) {
+                               /* Both are references; ignore the one from dir1. */
+                               retval = do_one_ref(base, fn, trim, flags, cb_data, e2);
+                               i1++;
+                               i2++;
+                       } else {
+                               die("conflict between reference and directory: %s",
+                                   e1->name);
+                       }
+               } else {
+                       struct ref_entry *e;
+                       if (cmp < 0) {
+                               e = e1;
+                               i1++;
+                       } else {
+                               e = e2;
+                               i2++;
+                       }
+                       if (e->flag & REF_DIR) {
+                               sort_ref_dir(&e->u.subdir);
+                               retval = do_for_each_ref_in_dir(
+                                               &e->u.subdir, 0,
+                                               base, fn, trim, flags, cb_data);
+                       } else {
+                               retval = do_one_ref(base, fn, trim, flags, cb_data, e);
+                       }
+               }
+               if (retval)
+                       return retval;
+       }
+       if (i1 < dir1->nr)
+               return do_for_each_ref_in_dir(dir1, i1,
+                                             base, fn, trim, flags, cb_data);
+       if (i2 < dir2->nr)
+               return do_for_each_ref_in_dir(dir2, i2,
+                                             base, fn, trim, flags, cb_data);
+       return 0;
+}
 
-       r = bsearch(&e, array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
+/*
+ * Return true iff refname1 and refname2 conflict with each other.
+ * Two reference names conflict if one of them exactly matches the
+ * leading components of the other; e.g., "foo/bar" conflicts with
+ * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
+ * "foo/barbados".
+ */
+static int names_conflict(const char *refname1, const char *refname2)
+{
+       for (; *refname1 && *refname1 == *refname2; refname1++, refname2++)
+               ;
+       return (*refname1 == '\0' && *refname2 == '/')
+               || (*refname1 == '/' && *refname2 == '\0');
+}
 
-       free(e);
+struct name_conflict_cb {
+       const char *refname;
+       const char *oldrefname;
+       const char *conflicting_refname;
+};
 
-       if (r == NULL)
-               return NULL;
+static int name_conflict_fn(const char *existingrefname, const unsigned char *sha1,
+                           int flags, void *cb_data)
+{
+       struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
+       if (data->oldrefname && !strcmp(data->oldrefname, existingrefname))
+               return 0;
+       if (names_conflict(data->refname, existingrefname)) {
+               data->conflicting_refname = existingrefname;
+               return 1;
+       }
+       return 0;
+}
 
-       return *r;
+/*
+ * Return true iff a reference named refname could be created without
+ * conflicting with the name of an existing reference in array.  If
+ * oldrefname is non-NULL, ignore potential conflicts with oldrefname
+ * (e.g., because oldrefname is scheduled for deletion in the same
+ * operation).
+ */
+static int is_refname_available(const char *refname, const char *oldrefname,
+                               struct ref_dir *dir)
+{
+       struct name_conflict_cb data;
+       data.refname = refname;
+       data.oldrefname = oldrefname;
+       data.conflicting_refname = NULL;
+
+       sort_ref_dir(dir);
+       if (do_for_each_ref_in_dir(dir, 0, "", name_conflict_fn,
+                                  0, DO_FOR_EACH_INCLUDE_BROKEN,
+                                  &data)) {
+               error("'%s' exists; cannot create '%s'",
+                     data.conflicting_refname, refname);
+               return 0;
+       }
+       return 1;
 }
 
 /*
@@ -175,35 +594,23 @@ static struct ref_cache {
        struct ref_cache *next;
        char did_loose;
        char did_packed;
-       struct ref_array loose;
-       struct ref_array packed;
+       struct ref_dir loose;
+       struct ref_dir packed;
        /* The submodule name, or "" for the main repo. */
        char name[FLEX_ARRAY];
 } *ref_cache;
 
-static struct ref_entry *current_ref;
-
-static void clear_ref_array(struct ref_array *array)
-{
-       int i;
-       for (i = 0; i < array->nr; i++)
-               free(array->refs[i]);
-       free(array->refs);
-       array->sorted = array->nr = array->alloc = 0;
-       array->refs = NULL;
-}
-
 static void clear_packed_ref_cache(struct ref_cache *refs)
 {
        if (refs->did_packed)
-               clear_ref_array(&refs->packed);
+               clear_ref_dir(&refs->packed);
        refs->did_packed = 0;
 }
 
 static void clear_loose_ref_cache(struct ref_cache *refs)
 {
        if (refs->did_loose)
-               clear_ref_array(&refs->loose);
+               clear_ref_dir(&refs->loose);
        refs->did_loose = 0;
 }
 
@@ -236,20 +643,53 @@ static struct ref_cache *get_ref_cache(const char *submodule)
                refs = refs->next;
        }
 
-       refs = create_ref_cache(submodule);
-       refs->next = ref_cache;
-       ref_cache = refs;
-       return refs;
-}
+       refs = create_ref_cache(submodule);
+       refs->next = ref_cache;
+       ref_cache = refs;
+       return refs;
+}
+
+void invalidate_ref_cache(const char *submodule)
+{
+       struct ref_cache *refs = get_ref_cache(submodule);
+       clear_packed_ref_cache(refs);
+       clear_loose_ref_cache(refs);
+}
+
+/*
+ * Parse one line from a packed-refs file.  Write the SHA1 to sha1.
+ * Return a pointer to the refname within the line (null-terminated),
+ * or NULL if there was a problem.
+ */
+static const char *parse_ref_line(char *line, unsigned char *sha1)
+{
+       /*
+        * 42: the answer to everything.
+        *
+        * In this case, it happens to be the answer to
+        *  40 (length of sha1 hex representation)
+        *  +1 (space in between hex and name)
+        *  +1 (newline at the end of the line)
+        */
+       int len = strlen(line) - 42;
+
+       if (len <= 0)
+               return NULL;
+       if (get_sha1_hex(line, sha1) < 0)
+               return NULL;
+       if (!isspace(line[40]))
+               return NULL;
+       line += 41;
+       if (isspace(*line))
+               return NULL;
+       if (line[len] != '\n')
+               return NULL;
+       line[len] = 0;
 
-void invalidate_ref_cache(const char *submodule)
-{
-       struct ref_cache *refs = get_ref_cache(submodule);
-       clear_packed_ref_cache(refs);
-       clear_loose_ref_cache(refs);
+       return line;
 }
 
-static void read_packed_refs(FILE *f, struct ref_array *array)
+static void read_packed_refs(FILE *f, struct ref_dir *dir)
 {
        struct ref_entry *last = NULL;
        char refline[PATH_MAX];
@@ -271,7 +711,7 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
                refname = parse_ref_line(refline, sha1);
                if (refname) {
                        last = create_ref_entry(refname, sha1, flag, 1);
-                       add_ref(array, last);
+                       add_ref(dir, last);
                        continue;
                }
                if (last &&
@@ -279,11 +719,11 @@ static void read_packed_refs(FILE *f, struct ref_array *array)
                    strlen(refline) == 42 &&
                    refline[41] == '\n' &&
                    !get_sha1_hex(refline + 1, sha1))
-                       hashcpy(last->peeled, sha1);
+                       hashcpy(last->u.value.peeled, sha1);
        }
 }
 
-static struct ref_array *get_packed_refs(struct ref_cache *refs)
+static struct ref_dir *get_packed_refs(struct ref_cache *refs)
 {
        if (!refs->did_packed) {
                const char *packed_refs_file;
@@ -310,9 +750,9 @@ void add_packed_ref(const char *refname, const unsigned char *sha1)
 }
 
 static void get_ref_dir(struct ref_cache *refs, const char *base,
-                       struct ref_array *array)
+                       struct ref_dir *dir)
 {
-       DIR *dir;
+       DIR *d;
        const char *path;
 
        if (*refs->name)
@@ -320,10 +760,8 @@ static void get_ref_dir(struct ref_cache *refs, const char *base,
        else
                path = git_path("%s", base);
 
-
-       dir = opendir(path);
-
-       if (dir) {
+       d = opendir(path);
+       if (d) {
                struct dirent *de;
                int baselen = strlen(base);
                char *refname = xmalloc(baselen + 257);
@@ -332,7 +770,7 @@ static void get_ref_dir(struct ref_cache *refs, const char *base,
                if (baselen && base[baselen-1] != '/')
                        refname[baselen++] = '/';
 
-               while ((de = readdir(dir)) != NULL) {
+               while ((de = readdir(d)) != NULL) {
                        unsigned char sha1[20];
                        struct stat st;
                        int flag;
@@ -353,7 +791,7 @@ static void get_ref_dir(struct ref_cache *refs, const char *base,
                        if (stat(refdir, &st) < 0)
                                continue;
                        if (S_ISDIR(st.st_mode)) {
-                               get_ref_dir(refs, refname, array);
+                               get_ref_dir(refs, refname, dir);
                                continue;
                        }
                        if (*refs->name) {
@@ -367,48 +805,14 @@ static void get_ref_dir(struct ref_cache *refs, const char *base,
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
                        }
-                       add_ref(array, create_ref_entry(refname, sha1, flag, 1));
+                       add_ref(dir, create_ref_entry(refname, sha1, flag, 1));
                }
                free(refname);
-               closedir(dir);
+               closedir(d);
        }
 }
 
-struct warn_if_dangling_data {
-       FILE *fp;
-       const char *refname;
-       const char *msg_fmt;
-};
-
-static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
-                                  int flags, void *cb_data)
-{
-       struct warn_if_dangling_data *d = cb_data;
-       const char *resolves_to;
-       unsigned char junk[20];
-
-       if (!(flags & REF_ISSYMREF))
-               return 0;
-
-       resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
-       if (!resolves_to || strcmp(resolves_to, d->refname))
-               return 0;
-
-       fprintf(d->fp, d->msg_fmt, refname);
-       return 0;
-}
-
-void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
-{
-       struct warn_if_dangling_data data;
-
-       data.fp = fp;
-       data.refname = refname;
-       data.msg_fmt = msg_fmt;
-       for_each_rawref(warn_if_dangling_symref, &data);
-}
-
-static struct ref_array *get_loose_refs(struct ref_cache *refs)
+static struct ref_dir *get_loose_refs(struct ref_cache *refs)
 {
        if (!refs->did_loose) {
                get_ref_dir(refs, "refs", &refs->loose);
@@ -430,13 +834,13 @@ static int resolve_gitlink_packed_ref(struct ref_cache *refs,
                                      const char *refname, unsigned char *sha1)
 {
        struct ref_entry *ref;
-       struct ref_array *array = get_packed_refs(refs);
+       struct ref_dir *dir = get_packed_refs(refs);
 
-       ref = search_ref_array(array, refname);
+       ref = find_ref(dir, refname);
        if (ref == NULL)
                return -1;
 
-       memcpy(sha1, ref->sha1, 20);
+       memcpy(sha1, ref->u.value.sha1, 20);
        return 0;
 }
 
@@ -503,10 +907,10 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sh
  */
 static int get_packed_ref(const char *refname, unsigned char *sha1)
 {
-       struct ref_array *packed = get_packed_refs(get_ref_cache(NULL));
-       struct ref_entry *entry = search_ref_array(packed, refname);
+       struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
+       struct ref_entry *entry = find_ref(packed, refname);
        if (entry) {
-               hashcpy(sha1, entry->sha1);
+               hashcpy(sha1, entry->u.value.sha1);
                return 0;
        }
        return -1;
@@ -645,23 +1049,10 @@ int read_ref(const char *refname, unsigned char *sha1)
        return read_ref_full(refname, sha1, 1, NULL);
 }
 
-#define DO_FOR_EACH_INCLUDE_BROKEN 01
-static int do_one_ref(const char *base, each_ref_fn fn, int trim,
-                     int flags, void *cb_data, struct ref_entry *entry)
+int ref_exists(const char *refname)
 {
-       if (prefixcmp(entry->name, base))
-               return 0;
-
-       if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
-               if (entry->flag & REF_ISBROKEN)
-                       return 0; /* ignore broken refs e.g. dangling symref */
-               if (!has_sha1_file(entry->sha1)) {
-                       error("%s does not point to a valid object!", entry->name);
-                       return 0;
-               }
-       }
-       current_ref = entry;
-       return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
+       unsigned char sha1[20];
+       return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
 }
 
 static int filter_refs(const char *refname, const unsigned char *sha1, int flags,
@@ -682,10 +1073,10 @@ int peel_ref(const char *refname, unsigned char *sha1)
        if (current_ref && (current_ref->name == refname
                || !strcmp(current_ref->name, refname))) {
                if (current_ref->flag & REF_KNOWS_PEELED) {
-                       hashcpy(sha1, current_ref->peeled);
+                       hashcpy(sha1, current_ref->u.value.peeled);
                        return 0;
                }
-               hashcpy(base, current_ref->sha1);
+               hashcpy(base, current_ref->u.value.sha1);
                goto fallback;
        }
 
@@ -693,11 +1084,11 @@ int peel_ref(const char *refname, unsigned char *sha1)
                return -1;
 
        if ((flag & REF_ISPACKED)) {
-               struct ref_array *array = get_packed_refs(get_ref_cache(NULL));
-               struct ref_entry *r = search_ref_array(array, refname);
+               struct ref_dir *dir = get_packed_refs(get_ref_cache(NULL));
+               struct ref_entry *r = find_ref(dir, refname);
 
                if (r != NULL && r->flag & REF_KNOWS_PEELED) {
-                       hashcpy(sha1, r->peeled);
+                       hashcpy(sha1, r->u.value.peeled);
                        return 0;
                }
        }
@@ -714,50 +1105,75 @@ fallback:
        return -1;
 }
 
+struct warn_if_dangling_data {
+       FILE *fp;
+       const char *refname;
+       const char *msg_fmt;
+};
+
+static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1,
+                                  int flags, void *cb_data)
+{
+       struct warn_if_dangling_data *d = cb_data;
+       const char *resolves_to;
+       unsigned char junk[20];
+
+       if (!(flags & REF_ISSYMREF))
+               return 0;
+
+       resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
+       if (!resolves_to || strcmp(resolves_to, d->refname))
+               return 0;
+
+       fprintf(d->fp, d->msg_fmt, refname);
+       fputc('\n', d->fp);
+       return 0;
+}
+
+void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
+{
+       struct warn_if_dangling_data data;
+
+       data.fp = fp;
+       data.refname = refname;
+       data.msg_fmt = msg_fmt;
+       for_each_rawref(warn_if_dangling_symref, &data);
+}
+
 static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
                           int trim, int flags, void *cb_data)
 {
-       int retval = 0, p = 0, l = 0;
        struct ref_cache *refs = get_ref_cache(submodule);
-       struct ref_array *packed = get_packed_refs(refs);
-       struct ref_array *loose = get_loose_refs(refs);
-
-       sort_ref_array(packed);
-       sort_ref_array(loose);
-       while (p < packed->nr && l < loose->nr) {
-               struct ref_entry *entry;
-               int cmp = strcmp(packed->refs[p]->name, loose->refs[l]->name);
-               if (!cmp) {
-                       p++;
-                       continue;
-               }
-               if (cmp > 0) {
-                       entry = loose->refs[l++];
-               } else {
-                       entry = packed->refs[p++];
-               }
-               retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
-               if (retval)
-                       goto end_each;
-       }
-
-       if (l < loose->nr) {
-               p = l;
-               packed = loose;
-       }
+       struct ref_dir *packed_dir = get_packed_refs(refs);
+       struct ref_dir *loose_dir = get_loose_refs(refs);
+       int retval = 0;
 
-       for (; p < packed->nr; p++) {
-               retval = do_one_ref(base, fn, trim, flags, cb_data, packed->refs[p]);
-               if (retval)
-                       goto end_each;
+       if (base && *base) {
+               packed_dir = find_containing_dir(packed_dir, base, 0);
+               loose_dir = find_containing_dir(loose_dir, base, 0);
+       }
+
+       if (packed_dir && loose_dir) {
+               sort_ref_dir(packed_dir);
+               sort_ref_dir(loose_dir);
+               retval = do_for_each_ref_in_dirs(
+                               packed_dir, loose_dir,
+                               base, fn, trim, flags, cb_data);
+       } else if (packed_dir) {
+               sort_ref_dir(packed_dir);
+               retval = do_for_each_ref_in_dir(
+                               packed_dir, 0,
+                               base, fn, trim, flags, cb_data);
+       } else if (loose_dir) {
+               sort_ref_dir(loose_dir);
+               retval = do_for_each_ref_in_dir(
+                               loose_dir, 0,
+                               base, fn, trim, flags, cb_data);
        }
 
-end_each:
-       current_ref = NULL;
        return retval;
 }
 
-
 static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
 {
        unsigned char sha1[20];
@@ -908,101 +1324,6 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
-/*
- * Make sure "ref" is something reasonable to have under ".git/refs/";
- * We do not like it if:
- *
- * - any path component of it begins with ".", or
- * - it has double dots "..", or
- * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
- * - it ends with a "/".
- * - it ends with ".lock"
- * - it contains a "\" (backslash)
- */
-
-/* Return true iff ch is not allowed in reference names. */
-static inline int bad_ref_char(int ch)
-{
-       if (((unsigned) ch) <= ' ' || ch == 0x7f ||
-           ch == '~' || ch == '^' || ch == ':' || ch == '\\')
-               return 1;
-       /* 2.13 Pattern Matching Notation */
-       if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
-               return 1;
-       return 0;
-}
-
-/*
- * Try to read one refname component from the front of refname.  Return
- * the length of the component found, or -1 if the component is not
- * legal.
- */
-static int check_refname_component(const char *refname, int flags)
-{
-       const char *cp;
-       char last = '\0';
-
-       for (cp = refname; ; cp++) {
-               char ch = *cp;
-               if (ch == '\0' || ch == '/')
-                       break;
-               if (bad_ref_char(ch))
-                       return -1; /* Illegal character in refname. */
-               if (last == '.' && ch == '.')
-                       return -1; /* Refname contains "..". */
-               if (last == '@' && ch == '{')
-                       return -1; /* Refname contains "@{". */
-               last = ch;
-       }
-       if (cp == refname)
-               return -1; /* Component has zero length. */
-       if (refname[0] == '.') {
-               if (!(flags & REFNAME_DOT_COMPONENT))
-                       return -1; /* Component starts with '.'. */
-               /*
-                * Even if leading dots are allowed, don't allow "."
-                * as a component (".." is prevented by a rule above).
-                */
-               if (refname[1] == '\0')
-                       return -1; /* Component equals ".". */
-       }
-       if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
-               return -1; /* Refname ends with ".lock". */
-       return cp - refname;
-}
-
-int check_refname_format(const char *refname, int flags)
-{
-       int component_len, component_count = 0;
-
-       while (1) {
-               /* We are at the start of a path component. */
-               component_len = check_refname_component(refname, flags);
-               if (component_len < 0) {
-                       if ((flags & REFNAME_REFSPEC_PATTERN) &&
-                                       refname[0] == '*' &&
-                                       (refname[1] == '\0' || refname[1] == '/')) {
-                               /* Accept one wildcard as a full refname component. */
-                               flags &= ~REFNAME_REFSPEC_PATTERN;
-                               component_len = 1;
-                       } else {
-                               return -1;
-                       }
-               }
-               component_count++;
-               if (refname[component_len] == '\0')
-                       break;
-               /* Skip to next component. */
-               refname += component_len + 1;
-       }
-
-       if (refname[component_len - 1] == '.')
-               return -1; /* Refname ends with '.'. */
-       if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
-               return -1; /* Refname has only one component. */
-       return 0;
-}
-
 const char *prettify_refname(const char *name)
 {
        return name + (
@@ -1072,35 +1393,6 @@ static int remove_empty_directories(const char *file)
        return result;
 }
 
-/*
- * Return true iff a reference named refname could be created without
- * conflicting with the name of an existing reference.  If oldrefname
- * is non-NULL, ignore potential conflicts with oldrefname (e.g.,
- * because oldrefname is scheduled for deletion in the same
- * operation).
- */
-static int is_refname_available(const char *refname, const char *oldrefname,
-                               struct ref_array *array)
-{
-       int i, namlen = strlen(refname); /* e.g. 'foo/bar' */
-       for (i = 0; i < array->nr; i++ ) {
-               struct ref_entry *entry = array->refs[i];
-               /* entry->name could be 'foo' or 'foo/bar/baz' */
-               if (!oldrefname || strcmp(oldrefname, entry->name)) {
-                       int len = strlen(entry->name);
-                       int cmplen = (namlen < len) ? namlen : len;
-                       const char *lead = (namlen < len) ? entry->name : refname;
-                       if (!strncmp(refname, entry->name, cmplen) &&
-                           lead[cmplen] == '/') {
-                               error("'%s' exists; cannot create '%s'",
-                                     entry->name, refname);
-                               return 0;
-                       }
-               }
-       }
-       return 1;
-}
-
 /*
  * *string and *len will only be substituted, and *string returned (for
  * later free()ing) if the string passed in is a magic short-hand form
@@ -1286,36 +1578,44 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
        return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
 }
 
+struct repack_without_ref_sb {
+       const char *refname;
+       int fd;
+};
+
+static int repack_without_ref_fn(const char *refname, const unsigned char *sha1,
+                                int flags, void *cb_data)
+{
+       struct repack_without_ref_sb *data = cb_data;
+       char line[PATH_MAX + 100];
+       int len;
+
+       if (!strcmp(data->refname, refname))
+               return 0;
+       len = snprintf(line, sizeof(line), "%s %s\n",
+                      sha1_to_hex(sha1), refname);
+       /* this should not happen but just being defensive */
+       if (len > sizeof(line))
+               die("too long a refname '%s'", refname);
+       write_or_die(data->fd, line, len);
+       return 0;
+}
+
 static struct lock_file packlock;
 
 static int repack_without_ref(const char *refname)
 {
-       struct ref_array *packed;
-       int fd, i;
-
-       packed = get_packed_refs(get_ref_cache(NULL));
-       if (search_ref_array(packed, refname) == NULL)
+       struct repack_without_ref_sb data;
+       struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
+       if (find_ref(packed, refname) == NULL)
                return 0;
-       fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
-       if (fd < 0) {
+       data.refname = refname;
+       data.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
+       if (data.fd < 0) {
                unable_to_lock_error(git_path("packed-refs"), errno);
                return error("cannot delete '%s' from packed refs", refname);
        }
-
-       for (i = 0; i < packed->nr; i++) {
-               char line[PATH_MAX + 100];
-               int len;
-               struct ref_entry *ref = packed->refs[i];
-
-               if (!strcmp(refname, ref->name))
-                       continue;
-               len = snprintf(line, sizeof(line), "%s %s\n",
-                              sha1_to_hex(ref->sha1), ref->name);
-               /* this should not happen but just being defensive */
-               if (len > sizeof(line))
-                       die("too long a refname '%s'", ref->name);
-               write_or_die(fd, line, len);
-       }
+       do_for_each_ref_in_dir(packed, 0, "", repack_without_ref_fn, 0, 0, &data);
        return commit_lock_file(&packlock);
 }
 
@@ -1926,10 +2226,10 @@ int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_dat
 
 static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
 {
-       DIR *dir = opendir(git_path("logs/%s", base));
+       DIR *d = opendir(git_path("logs/%s", base));
        int retval = 0;
 
-       if (dir) {
+       if (d) {
                struct dirent *de;
                int baselen = strlen(base);
                char *log = xmalloc(baselen + 257);
@@ -1938,7 +2238,7 @@ static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
                if (baselen && base[baselen-1] != '/')
                        log[baselen++] = '/';
 
-               while ((de = readdir(dir)) != NULL) {
+               while ((de = readdir(d)) != NULL) {
                        struct stat st;
                        int namelen;
 
@@ -1965,7 +2265,7 @@ static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
                                break;
                }
                free(log);
-               closedir(dir);
+               closedir(d);
        }
        else if (*base)
                return errno;
@@ -2004,12 +2304,6 @@ int update_ref(const char *action, const char *refname,
        return 0;
 }
 
-int ref_exists(const char *refname)
-{
-       unsigned char sha1[20];
-       return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
-}
-
 struct ref *find_ref_by_name(const struct ref *list, const char *name)
 {
        for ( ; list; list = list->next)
diff --git a/refs.h b/refs.h
index 33202b0d4c85cafdaf60b568a6f728dd83462c46..d6c2fe2dfbd9e16a871ac960c27d9a68cc45102f 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -15,8 +15,11 @@ struct ref_lock {
 #define REF_ISBROKEN 0x04
 
 /*
- * Calls the specified function for each ref file until it returns nonzero,
- * and returns the value
+ * Calls the specified function for each ref file until it returns
+ * nonzero, and returns the value.  Please note that it is not safe to
+ * modify references while an iteration is in progress, unless the
+ * same callback function invocation that modifies the reference also
+ * returns a nonzero value to immediately stop the iteration.
  */
 typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
 extern int head_ref(each_ref_fn, void *);
index 08962214db6f715c0416c61ce0ab70b50b7888fc..04a9d6277db8fac78cb223a906b12744aab32193 100644 (file)
@@ -782,6 +782,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
                argv[argc++] = "--quiet";
        else if (options.verbosity > 1)
                argv[argc++] = "--verbose";
+       argv[argc++] = options.progress ? "--progress" : "--no-progress";
        argv[argc++] = url;
        for (i = 0; i < nr_spec; i++)
                argv[argc++] = specs[i];
index 9a0d9c7de2af43c7ae082a892a2a9cfd6f4b4761..935e7a7ba413668c95a6c3e846b9058be07f0425 100644 (file)
@@ -2066,10 +2066,16 @@ static void set_children(struct rev_info *revs)
        }
 }
 
+void reset_revision_walk(void)
+{
+       clear_object_flags(SEEN | ADDED | SHOWN);
+}
+
 int prepare_revision_walk(struct rev_info *revs)
 {
        int nr = revs->pending.nr;
        struct object_array_entry *e, *list;
+       struct commit_list **next = &revs->commits;
 
        e = list = revs->pending.objects;
        revs->pending.nr = 0;
@@ -2080,11 +2086,12 @@ int prepare_revision_walk(struct rev_info *revs)
                if (commit) {
                        if (!(commit->object.flags & SEEN)) {
                                commit->object.flags |= SEEN;
-                               commit_list_insert_by_date(commit, &revs->commits);
+                               next = commit_list_append(commit, next);
                        }
                }
                e++;
        }
+       commit_list_sort_by_date(&revs->commits);
        if (!revs->leak_pending)
                free(list);
 
index 1a0838473f41edad56014f63629dcb0820c9e216..863f4f64543bd9f9e830e7c9567def23e906c7d9 100644 (file)
@@ -193,6 +193,7 @@ extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ct
                                 const char * const usagestr[]);
 extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
 
+extern void reset_revision_walk(void);
 extern int prepare_revision_walk(struct rev_info *revs);
 extern struct commit *get_revision(struct rev_info *revs);
 extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);
index 9c5a564ece033e8584be14c640f6a9995ecd1cff..606791dc674a1d24459d85504f0c981634b52020 100644 (file)
@@ -4,6 +4,10 @@
 #include "sigchain.h"
 #include "argv-array.h"
 
+#ifndef SHELL_PATH
+# define SHELL_PATH "/bin/sh"
+#endif
+
 struct child_to_clean {
        pid_t pid;
        struct child_to_clean *next;
@@ -152,7 +156,11 @@ static const char **prepare_shell_cmd(const char **argv)
                die("BUG: shell command is empty");
 
        if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
+#ifndef WIN32
+               nargv[nargc++] = SHELL_PATH;
+#else
                nargv[nargc++] = "sh";
+#endif
                nargv[nargc++] = "-c";
 
                if (argc < 2)
index 4307364b261bcbe7a2e97116aa631aa7aa35613c..3c384b94d24e3099cb149c66347386d1430fadef 100644 (file)
@@ -13,6 +13,7 @@
 #include "rerere.h"
 #include "merge-recursive.h"
 #include "refs.h"
+#include "argv-array.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -234,7 +235,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 
        if (!clean) {
                int i;
-               strbuf_addstr(msgbuf, "\nConflicts:\n\n");
+               strbuf_addstr(msgbuf, "\nConflicts:\n");
                for (i = 0; i < active_nr;) {
                        struct cache_entry *ce = active_cache[i++];
                        if (ce_stage(ce)) {
@@ -251,6 +252,38 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        return !clean;
 }
 
+static int is_index_unchanged(void)
+{
+       unsigned char head_sha1[20];
+       struct commit *head_commit;
+
+       if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+               return error(_("Could not resolve HEAD commit\n"));
+
+       head_commit = lookup_commit(head_sha1);
+
+       /*
+        * If head_commit is NULL, check_commit, called from
+        * lookup_commit, would have indicated that head_commit is not
+        * a commit object already.  parse_commit() will return failure
+        * without further complaints in such a case.  Otherwise, if
+        * the commit is invalid, parse_commit() will complain.  So
+        * there is nothing for us to say here.  Just return failure.
+        */
+       if (parse_commit(head_commit))
+               return -1;
+
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+
+       if (!cache_tree_fully_valid(active_cache_tree))
+               if (cache_tree_update(active_cache_tree, active_cache,
+                                 active_nr, 0))
+                       return error(_("Unable to update cache tree\n"));
+
+       return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.sha1);
+}
+
 /*
  * If we are cherry-pick, and if the merge did not result in
  * hand-editing, we will hit this commit and inherit the original
@@ -260,21 +293,46 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  */
 static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
-       /* 6 is max possible length of our args array including NULL */
-       const char *args[6];
-       int i = 0;
+       struct argv_array array;
+       int rc;
+
+       argv_array_init(&array);
+       argv_array_push(&array, "commit");
+       argv_array_push(&array, "-n");
 
-       args[i++] = "commit";
-       args[i++] = "-n";
        if (opts->signoff)
-               args[i++] = "-s";
+               argv_array_push(&array, "-s");
        if (!opts->edit) {
-               args[i++] = "-F";
-               args[i++] = defmsg;
+               argv_array_push(&array, "-F");
+               argv_array_push(&array, defmsg);
+       }
+
+       if (opts->allow_empty)
+               argv_array_push(&array, "--allow-empty");
+
+       rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
+       argv_array_clear(&array);
+       return rc;
+}
+
+static int is_original_commit_empty(struct commit *commit)
+{
+       const unsigned char *ptree_sha1;
+
+       if (parse_commit(commit))
+               return error(_("Could not parse commit %s\n"),
+                            sha1_to_hex(commit->object.sha1));
+       if (commit->parents) {
+               struct commit *parent = commit->parents->item;
+               if (parse_commit(parent))
+                       return error(_("Could not parse parent commit %s\n"),
+                               sha1_to_hex(parent->object.sha1));
+               ptree_sha1 = parent->tree->object.sha1;
+       } else {
+               ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
        }
-       args[i] = NULL;
 
-       return run_command_v_opt(args, RUN_GIT_CMD);
+       return !hashcmp(ptree_sha1, commit->tree->object.sha1);
 }
 
 static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
@@ -286,6 +344,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
        char *defmsg = NULL;
        struct strbuf msgbuf = STRBUF_INIT;
        int res;
+       int empty_commit;
+       int index_unchanged;
 
        if (opts->no_commit) {
                /*
@@ -411,6 +471,10 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                free_commit_list(remotes);
        }
 
+       empty_commit = is_original_commit_empty(commit);
+       if (empty_commit < 0)
+               return empty_commit;
+
        /*
         * If the merge was clean or if it failed due to conflict, we write
         * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
@@ -431,6 +495,25 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                print_advice(res == 1, opts);
                rerere(opts->allow_rerere_auto);
        } else {
+               index_unchanged = is_index_unchanged();
+               /*
+                * If index_unchanged is less than 0, that indicates we either
+                * couldn't parse HEAD or the index, so error out here.
+                */
+               if (index_unchanged < 0)
+                       return index_unchanged;
+
+               if (!empty_commit && !opts->keep_redundant_commits && index_unchanged)
+                       /*
+                        * The head tree and the index match
+                        * meaning the commit is empty.  Since it wasn't created
+                        * empty (based on the previous test), we can conclude
+                        * the commit has been made redundant.  Since we don't
+                        * want to keep redundant commits, we can just return
+                        * here, skipping this commit
+                        */
+                       return 0;
+
                if (!opts->no_commit)
                        res = run_git_commit(defmsg, opts);
        }
@@ -468,33 +551,6 @@ static void read_and_refresh_cache(struct replay_opts *opts)
        rollback_lock_file(&index_lock);
 }
 
-/*
- * Append a commit to the end of the commit_list.
- *
- * next starts by pointing to the variable that holds the head of an
- * empty commit_list, and is updated to point to the "next" field of
- * the last item on the list as new commits are appended.
- *
- * Usage example:
- *
- *     struct commit_list *list;
- *     struct commit_list **next = &list;
- *
- *     next = commit_list_append(c1, next);
- *     next = commit_list_append(c2, next);
- *     assert(commit_list_count(list) == 2);
- *     return list;
- */
-static struct commit_list **commit_list_append(struct commit *commit,
-                                              struct commit_list **next)
-{
-       struct commit_list *new = xmalloc(sizeof(struct commit_list));
-       new->item = commit;
-       *next = new;
-       new->next = NULL;
-       return &new->next;
-}
-
 static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
                struct replay_opts *opts)
 {
index bb4b13830e157dafb468f8256bab25110058ba9b..aa5f17cc3079f39000e9d7526d24354a027daabd 100644 (file)
@@ -29,6 +29,8 @@ struct replay_opts {
        int signoff;
        int allow_ff;
        int allow_rerere_auto;
+       int allow_empty;
+       int keep_redundant_commits;
 
        int mainline;
 
diff --git a/setup.c b/setup.c
index 7a3618fab774c0c26a99c6e23b4fcf726e5dc1ad..731851a4a85161af49a38c481672615a6ac0bbc9 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -569,13 +569,15 @@ static const char *setup_nongit(const char *cwd, int *nongit_ok)
        return NULL;
 }
 
-static dev_t get_device_or_die(const char *path, const char *prefix)
+static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_len)
 {
        struct stat buf;
-       if (stat(path, &buf))
-               die_errno("failed to stat '%s%s%s'",
+       if (stat(path, &buf)) {
+               die_errno("failed to stat '%*s%s%s'",
+                               prefix_len,
                                prefix ? prefix : "",
                                prefix ? "/" : "", path);
+       }
        return buf.st_dev;
 }
 
@@ -589,7 +591,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
        static char cwd[PATH_MAX+1];
        const char *gitdirenv, *ret;
        char *gitfile;
-       int len, offset, ceil_offset;
+       int len, offset, offset_parent, ceil_offset;
        dev_t current_device = 0;
        int one_filesystem = 1;
 
@@ -631,7 +633,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
         */
        one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0);
        if (one_filesystem)
-               current_device = get_device_or_die(".", NULL);
+               current_device = get_device_or_die(".", NULL, 0);
        for (;;) {
                gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
                if (gitfile)
@@ -653,11 +655,12 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
                if (is_git_directory("."))
                        return setup_bare_git_dir(cwd, offset, len, nongit_ok);
 
-               while (--offset > ceil_offset && cwd[offset] != '/');
-               if (offset <= ceil_offset)
+               offset_parent = offset;
+               while (--offset_parent > ceil_offset && cwd[offset_parent] != '/');
+               if (offset_parent <= ceil_offset)
                        return setup_nongit(cwd, nongit_ok);
                if (one_filesystem) {
-                       dev_t parent_device = get_device_or_die("..", cwd);
+                       dev_t parent_device = get_device_or_die("..", cwd, offset);
                        if (parent_device != current_device) {
                                if (nongit_ok) {
                                        if (chdir(cwd))
@@ -666,7 +669,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
                                        return NULL;
                                }
                                cwd[offset] = '\0';
-                               die("Not a git repository (or any parent up to mount parent %s)\n"
+                               die("Not a git repository (or any parent up to mount point %s)\n"
                                "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd);
                        }
                }
@@ -674,6 +677,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
                        cwd[offset] = '\0';
                        die_errno("Cannot change to '%s/..'", cwd);
                }
+               offset = offset_parent;
        }
 }
 
index 4f06a0e450359744528d3b125fb09eacebf1eb4a..3c4f1652f13479238dee56674cbb86d299363be1 100644 (file)
@@ -19,6 +19,7 @@
 #include "pack-revindex.h"
 #include "sha1-lookup.h"
 #include "bulk-checkin.h"
+#include "streaming.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -1146,10 +1147,47 @@ static const struct packed_git *has_packed_and_bad(const unsigned char *sha1)
        return NULL;
 }
 
-int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
+/*
+ * With an in-core object data in "map", rehash it to make sure the
+ * object name actually matches "sha1" to detect object corruption.
+ * With "map" == NULL, try reading the object named with "sha1" using
+ * the streaming interface and rehash it to do the same.
+ */
+int check_sha1_signature(const unsigned char *sha1, void *map,
+                        unsigned long size, const char *type)
 {
        unsigned char real_sha1[20];
-       hash_sha1_file(map, size, type, real_sha1);
+       enum object_type obj_type;
+       struct git_istream *st;
+       git_SHA_CTX c;
+       char hdr[32];
+       int hdrlen;
+
+       if (map) {
+               hash_sha1_file(map, size, type, real_sha1);
+               return hashcmp(sha1, real_sha1) ? -1 : 0;
+       }
+
+       st = open_istream(sha1, &obj_type, &size, NULL);
+       if (!st)
+               return -1;
+
+       /* Generate the header */
+       hdrlen = sprintf(hdr, "%s %lu", typename(obj_type), size) + 1;
+
+       /* Sha1.. */
+       git_SHA1_Init(&c);
+       git_SHA1_Update(&c, hdr, hdrlen);
+       for (;;) {
+               char buf[1024 * 16];
+               ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+               if (!readlen)
+                       break;
+               git_SHA1_Update(&c, buf, readlen);
+       }
+       git_SHA1_Final(real_sha1, &c);
+       close_istream(st);
        return hashcmp(sha1, real_sha1) ? -1 : 0;
 }
 
@@ -2379,7 +2417,7 @@ int move_temp_to_file(const char *tmpfile, const char *filename)
        unlink_or_warn(tmpfile);
        if (ret) {
                if (ret != EEXIST) {
-                       return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret));
+                       return error("unable to write sha1 filename %s: %s", filename, strerror(ret));
                }
                /* FIXME!!! Collision check here ? */
        }
@@ -2471,9 +2509,9 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
        fd = create_tmpfile(tmp_file, sizeof(tmp_file), filename);
        if (fd < 0) {
                if (errno == EACCES)
-                       return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
+                       return error("insufficient permission for adding an object to repository database %s", get_object_directory());
                else
-                       return error("unable to create temporary sha1 filename %s: %s\n", tmp_file, strerror(errno));
+                       return error("unable to create temporary file: %s", strerror(errno));
        }
 
        /* Set it up */
index 03ffc2caaa6524a3361bc47a89e101ced2f0e987..c6331136d19c5224078fa78b6e5e794fcc587fe2 100644 (file)
@@ -856,10 +856,22 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
        len = cp + tmp_len - name;
        cp = xstrndup(name, cp - name);
        upstream = branch_get(*cp ? cp : NULL);
-       if (!upstream
-           || !upstream->merge
-           || !upstream->merge[0]->dst)
-               return error("No upstream branch found for '%s'", cp);
+       /*
+        * Upstream can be NULL only if cp refers to HEAD and HEAD
+        * points to something different than a branch.
+        */
+       if (!upstream)
+               return error(_("HEAD does not point to a branch"));
+       if (!upstream->merge || !upstream->merge[0]->dst) {
+               if (!ref_exists(upstream->refname))
+                       return error(_("No such branch: '%s'"), cp);
+               if (!upstream->merge)
+                       return error(_("No upstream configured for branch '%s'"),
+                                    upstream->name);
+               return error(
+                       _("Upstream branch '%s' not stored as a remote-tracking branch"),
+                       upstream->merge[0]->src);
+       }
        free(cp);
        cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
        strbuf_reset(buf);
index 5135d5950d9a5ea3ce8064e5491e53da17645da9..ec88266718879ebcf89ab95b4c89f83004467869 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -464,3 +464,36 @@ void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
 {
        strbuf_add_urlencode(sb, s, strlen(s), reserved);
 }
+
+void strbuf_addf_ln(struct strbuf *sb, const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+       strbuf_vaddf(sb, fmt, ap);
+       va_end(ap);
+       strbuf_addch(sb, '\n');
+}
+
+int printf_ln(const char *fmt, ...)
+{
+       int ret;
+       va_list ap;
+       va_start(ap, fmt);
+       ret = vprintf(fmt, ap);
+       va_end(ap);
+       if (ret < 0 || putchar('\n') == EOF)
+               return -1;
+       return ret + 1;
+}
+
+int fprintf_ln(FILE *fp, const char *fmt, ...)
+{
+       int ret;
+       va_list ap;
+       va_start(ap, fmt);
+       ret = vfprintf(fp, fmt, ap);
+       va_end(ap);
+       if (ret < 0 || putc('\n', fp) == EOF)
+               return -1;
+       return ret + 1;
+}
index 3effaa86b68f7b600dfd854558b7ee6f5d6f2f5e..b888d405db492c9851696cf6efe66e33d5e1dca8 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -99,6 +99,8 @@ __attribute__((format (printf,2,3)))
 extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
 __attribute__((format (printf,2,0)))
 extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
+__attribute__((format (printf,2,3)))
+extern void strbuf_addf_ln(struct strbuf *sb, const char *fmt, ...);
 
 extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size);
 
@@ -129,4 +131,9 @@ extern void strbuf_add_urlencode(struct strbuf *, const char *, size_t,
 extern void strbuf_addstr_urlencode(struct strbuf *, const char *,
                                    int reserved);
 
+__attribute__((format (printf,1,2)))
+extern int printf_ln(const char *fmt, ...);
+__attribute__((format (printf,2,3)))
+extern int fprintf_ln(FILE *fp, const char *fmt, ...);
+
 #endif /* STRBUF_H */
index 71072e1b1da670cdb4b048a3a6e83a4ae806bf5f..7e7ee2be6fe147ff660f8ab25618fbe4d4d0f11c 100644 (file)
@@ -489,3 +489,58 @@ static open_method_decl(incore)
 
        return st->u.incore.buf ? 0 : -1;
 }
+
+
+/****************************************************************
+ * Users of streaming interface
+ ****************************************************************/
+
+int stream_blob_to_fd(int fd, unsigned const char *sha1, struct stream_filter *filter,
+                     int can_seek)
+{
+       struct git_istream *st;
+       enum object_type type;
+       unsigned long sz;
+       ssize_t kept = 0;
+       int result = -1;
+
+       st = open_istream(sha1, &type, &sz, filter);
+       if (!st)
+               return result;
+       if (type != OBJ_BLOB)
+               goto close_and_exit;
+       for (;;) {
+               char buf[1024 * 16];
+               ssize_t wrote, holeto;
+               ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+               if (!readlen)
+                       break;
+               if (can_seek && sizeof(buf) == readlen) {
+                       for (holeto = 0; holeto < readlen; holeto++)
+                               if (buf[holeto])
+                                       break;
+                       if (readlen == holeto) {
+                               kept += holeto;
+                               continue;
+                       }
+               }
+
+               if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
+                       goto close_and_exit;
+               else
+                       kept = 0;
+               wrote = write_in_full(fd, buf, readlen);
+
+               if (wrote != readlen)
+                       goto close_and_exit;
+       }
+       if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
+                    write(fd, "", 1) != 1))
+               goto close_and_exit;
+       result = 0;
+
+ close_and_exit:
+       close_istream(st);
+       return result;
+}
index 589e857b8c4ad68e30b91da2eb29a076b98ef903..3e827709c85eeaf6669d05d0d59e288541ceb579 100644 (file)
@@ -12,4 +12,6 @@ extern struct git_istream *open_istream(const unsigned char *, enum object_type
 extern int close_istream(struct git_istream *);
 extern ssize_t read_istream(struct git_istream *, char *, size_t);
 
+extern int stream_blob_to_fd(int fd, const unsigned char *, struct stream_filter *, int can_seek);
+
 #endif /* STREAMING_H */
index 9a2806067954c55a27c068706b5bfe67a1189fd5..784b58039dd078fc1e0c4554820cd0e99c8e41d2 100644 (file)
@@ -357,21 +357,19 @@ static void collect_submodules_from_diff(struct diff_queue_struct *q,
                                         void *data)
 {
        int i;
-       int *needs_pushing = data;
+       struct string_list *needs_pushing = data;
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                if (!S_ISGITLINK(p->two->mode))
                        continue;
-               if (submodule_needs_pushing(p->two->path, p->two->sha1)) {
-                       *needs_pushing = 1;
-                       break;
-               }
+               if (submodule_needs_pushing(p->two->path, p->two->sha1))
+                       string_list_insert(needs_pushing, p->two->path);
        }
 }
 
-
-static void commit_need_pushing(struct commit *commit, int *needs_pushing)
+static void find_unpushed_submodule_commits(struct commit *commit,
+               struct string_list *needs_pushing)
 {
        struct rev_info rev;
 
@@ -382,14 +380,15 @@ static void commit_need_pushing(struct commit *commit, int *needs_pushing)
        diff_tree_combined_merge(commit, 1, &rev);
 }
 
-int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
+int find_unpushed_submodules(unsigned char new_sha1[20],
+               const char *remotes_name, struct string_list *needs_pushing)
 {
        struct rev_info rev;
        struct commit *commit;
        const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
        int argc = ARRAY_SIZE(argv) - 1;
        char *sha1_copy;
-       int needs_pushing = 0;
+
        struct strbuf remotes_arg = STRBUF_INIT;
 
        strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
@@ -401,13 +400,62 @@ int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remote
        if (prepare_revision_walk(&rev))
                die("revision walk setup failed");
 
-       while ((commit = get_revision(&rev)) && !needs_pushing)
-               commit_need_pushing(commit, &needs_pushing);
+       while ((commit = get_revision(&rev)) != NULL)
+               find_unpushed_submodule_commits(commit, needs_pushing);
 
+       reset_revision_walk();
        free(sha1_copy);
        strbuf_release(&remotes_arg);
 
-       return needs_pushing;
+       return needs_pushing->nr;
+}
+
+static int push_submodule(const char *path)
+{
+       if (add_submodule_odb(path))
+               return 1;
+
+       if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
+               struct child_process cp;
+               const char *argv[] = {"push", NULL};
+
+               memset(&cp, 0, sizeof(cp));
+               cp.argv = argv;
+               cp.env = local_repo_env;
+               cp.git_cmd = 1;
+               cp.no_stdin = 1;
+               cp.dir = path;
+               if (run_command(&cp))
+                       return 0;
+               close(cp.out);
+       }
+
+       return 1;
+}
+
+int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name)
+{
+       int i, ret = 1;
+       struct string_list needs_pushing;
+
+       memset(&needs_pushing, 0, sizeof(struct string_list));
+       needs_pushing.strdup_strings = 1;
+
+       if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing))
+               return 1;
+
+       for (i = 0; i < needs_pushing.nr; i++) {
+               const char *path = needs_pushing.items[i].string;
+               fprintf(stderr, "Pushing submodule '%s'\n", path);
+               if (!push_submodule(path)) {
+                       fprintf(stderr, "Unable to push submodule '%s'\n", path);
+                       ret = 0;
+               }
+       }
+
+       string_list_clear(&needs_pushing, 0);
+
+       return ret;
 }
 
 static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
@@ -741,6 +789,7 @@ static int find_first_merges(struct object_array *result, const char *path,
                if (in_merge_bases(b, &commit, 1))
                        add_object_array(o, NULL, &merges);
        }
+       reset_revision_walk();
 
        /* Now we've got all merges that contain a and b. Prune all
         * merges that contain another found merge and save them in
index 9c5e5c0c30676d5dc8be685c97b7d7f303dd7842..e105b0ebe6c06a03af7f82bdfbc9beb66377544f 100644 (file)
@@ -29,6 +29,8 @@ int fetch_populated_submodules(int num_options, const char **options,
 unsigned is_submodule_modified(const char *path, int ignore_untracked);
 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
                    const unsigned char a[20], const unsigned char b[20], int search);
-int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);
+int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
+               struct string_list *needs_pushing);
+int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);
 
 #endif
index 21d11d6c2d65982d94f933b2a673b744cbc2e28a..ae2dc4604f708d320d88d4f99ca9d575d88b6124 100644 (file)
@@ -69,7 +69,7 @@ gitweb_run () {
        # written to web server logs, so we are not interested in that:
        # we are interested only in properly formatted errors/warnings
        rm -f gitweb.log &&
-       perl -- "$SCRIPT_NAME" \
+       "$PERL_PATH" -- "$SCRIPT_NAME" \
                >gitweb.output 2>gitweb.log &&
        perl -w -e '
                open O, ">gitweb.headers";
index ef2d01f36939199f9720faad06d2b3a01c7cd7f5..87f0ad8f4182b13903b9649308e36e34a6122494 100644 (file)
@@ -31,19 +31,19 @@ start_git_daemon() {
                >&3 2>git_daemon_output &
        GIT_DAEMON_PID=$!
        {
-               read line
+               read line <&7
                echo >&4 "$line"
-               cat >&4 &
+               cat <&7 >&4 &
+       } 7<git_daemon_output &&
 
-               # Check expected output
-               if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble"
-               then
-                       kill "$GIT_DAEMON_PID"
-                       wait "$GIT_DAEMON_PID"
-                       trap 'die' EXIT
-                       error "git daemon failed to start"
-               fi
-       } <git_daemon_output
+       # Check expected output
+       if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble"
+       then
+               kill "$GIT_DAEMON_PID"
+               wait "$GIT_DAEMON_PID"
+               trap 'die' EXIT
+               error "git daemon failed to start"
+       fi
 }
 
 stop_git_daemon() {
index a870f9a5d280e3809267b5970f8b9372b5d260b1..121e38002bc73807649ebcc5df2799f6f35a9c67 100644 (file)
@@ -1,20 +1,18 @@
 #
-# Library code for git-p4 tests
+# Library code for git p4 tests
 #
 
 . ./test-lib.sh
 
 if ! test_have_prereq PYTHON; then
-       skip_all='skipping git-p4 tests; python not available'
+       skip_all='skipping git p4 tests; python not available'
        test_done
 fi
 ( p4 -h && p4d -h ) >/dev/null 2>&1 || {
-       skip_all='skipping git-p4 tests; no p4 or p4d'
+       skip_all='skipping git p4 tests; no p4 or p4d'
        test_done
 }
 
-GITP4="$GIT_BUILD_DIR/contrib/fast-import/git-p4"
-
 # Try to pick a unique port: guess a large number, then hope
 # no more than one of each test is running.
 #
@@ -26,6 +24,7 @@ P4DPORT=$((10669 + ($testid - $git_p4_test_start)))
 
 export P4PORT=localhost:$P4DPORT
 export P4CLIENT=client
+export P4EDITOR=:
 
 db="$TRASH_DIRECTORY/db"
 cli="$TRASH_DIRECTORY/cli"
index f7dc0781d5d0ec5afd7f1d0898bffa17a9b1b00e..094d49089389c9e8dbf8c561ccc133065552fc81 100644 (file)
@@ -160,6 +160,6 @@ test_http_push_nonff() {
        '
 
        test_expect_success 'non-fast-forward push shows help message' '
-               test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" output
+               test_i18ngrep "Updates were rejected because" output
        '
 }
index 3c12b05d60849b4f3063527338140c717b720c5d..de3762e24762692733f7a42f58b139e279b29f3d 100644 (file)
@@ -52,8 +52,15 @@ Alias /auth/ www/auth/
 <Location /smart_noexport/>
        SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
 </Location>
+<Location /smart_custom_env/>
+       SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+       SetEnv GIT_HTTP_EXPORT_ALL
+       SetEnv GIT_COMMITTER_NAME "Custom User"
+       SetEnv GIT_COMMITTER_EMAIL custom@example.com
+</Location>
 ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
 ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
+ScriptAlias /smart_custom_env/ ${GIT_EXEC_PATH}/git-http-backend/
 <Directory ${GIT_EXEC_PATH}>
        Options None
 </Directory>
index aaed7254023b86a30c499db7c4b069c9d08b1085..f9bbb91f64e35d284a4ac79a6b2ecaa1d2913455 100755 (executable)
@@ -11,7 +11,7 @@ Date: Thu, 23 Aug 2007 13:00:00 +0200
 Subject: test1
 
 ---
- foo |    1 +
+ foo | 1 +
  1 files changed, 1 insertions(+), 0 deletions(-)
  create mode 100644 foo
 
diff --git a/t/t0062-revision-walking.sh b/t/t0062-revision-walking.sh
new file mode 100755 (executable)
index 0000000..3d98eb8
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Heiko Voigt
+#
+
+test_description='Test revision walking api'
+
+. ./test-lib.sh
+
+cat >run_twice_expected <<-EOF
+1st
+ > add b
+ > add a
+2nd
+ > add b
+ > add a
+EOF
+
+test_expect_success 'setup' '
+       echo a > a &&
+       git add a &&
+       git commit -m "add a" &&
+       echo b > b &&
+       git add b &&
+       git commit -m "add b"
+'
+
+test_expect_success 'revision walking can be done twice' '
+       test-revision-walking run-twice > run_twice_actual
+       test_cmp run_twice_expected run_twice_actual
+'
+
+test_done
index 267f4c8ba3284d30492d8907b6fc3230f40e02fc..f028fd1418285107a90e170a6ea1cd7657e31bb8 100755 (executable)
@@ -1,39 +1,60 @@
 #!/bin/sh
 
-test_description='external credential helper tests'
-. ./test-lib.sh
-. "$TEST_DIRECTORY"/lib-credential.sh
+test_description='external credential helper tests
 
-pre_test() {
-       test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
-       eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP"
+This is a tool for authors of external helper tools to sanity-check
+their helpers. If you have written the "git-credential-foo" helper,
+you check it with:
+
+  make GIT_TEST_CREDENTIAL_HELPER=foo t0303-credential-external.sh
+
+This assumes that your helper is capable of both storing and
+retrieving credentials (some helpers may be read-only, and they will
+fail these tests).
+
+Please note that the individual tests do not verify all of the
+preconditions themselves, but rather build on each other. A failing
+test means that tests later in the sequence can return false "OK"
+results.
+
+If your helper supports time-based expiration with a configurable
+timeout, you can test that feature with:
+
+  make GIT_TEST_CREDENTIAL_HELPER=foo \
+       GIT_TEST_CREDENTIAL_HELPER_TIMEOUT="foo --timeout=1" \
+       t0303-credential-external.sh
 
-       # clean before the test in case there is cruft left
-       # over from a previous run that would impact results
-       helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
-}
+If your helper requires additional setup before the tests are started,
+you can set GIT_TEST_CREDENTIAL_HELPER_SETUP to a sequence of shell
+commands.
+'
 
-post_test() {
-       # clean afterwards so that we are good citizens
-       # and don't leave cruft in the helper's storage, which
-       # might be long-term system storage
-       helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
-}
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-credential.sh
 
 if test -z "$GIT_TEST_CREDENTIAL_HELPER"; then
-       say "# skipping external helper tests (set GIT_TEST_CREDENTIAL_HELPER)"
-else
-       pre_test
-       helper_test "$GIT_TEST_CREDENTIAL_HELPER"
-       post_test
+       skip_all="used to test external credential helpers"
+       test_done
 fi
 
+test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
+       eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP"
+
+# clean before the test in case there is cruft left
+# over from a previous run that would impact results
+helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+
+helper_test "$GIT_TEST_CREDENTIAL_HELPER"
+
 if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then
-       say "# skipping external helper timeout tests"
+       say "# skipping timeout tests (GIT_TEST_CREDENTIAL_HELPER_TIMEOUT not set)"
 else
-       pre_test
        helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"
-       post_test
 fi
 
+# clean afterwards so that we are good citizens
+# and don't leave cruft in the helper's storage, which
+# might be long-term system storage
+helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
+
 test_done
index 29d6024b7f1b55c09cbd7e9ed682a3e745c550d6..4d127f19b78cc76018e316532c905137e9c7ab08 100755 (executable)
@@ -6,11 +6,15 @@ test_description='adding and checking out large blobs'
 . ./test-lib.sh
 
 test_expect_success setup '
-       git config core.bigfilethreshold 200k &&
+       # clone does not allow us to pass core.bigfilethreshold to
+       # new repos, so set core.bigfilethreshold globally
+       git config --global core.bigfilethreshold 200k &&
        echo X | dd of=large1 bs=1k seek=2000 &&
        echo X | dd of=large2 bs=1k seek=2000 &&
        echo X | dd of=large3 bs=1k seek=2000 &&
-       echo Y | dd of=huge bs=1k seek=2500
+       echo Y | dd of=huge bs=1k seek=2500 &&
+       GIT_ALLOC_LIMIT=1500 &&
+       export GIT_ALLOC_LIMIT
 '
 
 test_expect_success 'add a large file or two' '
@@ -100,4 +104,34 @@ test_expect_success 'packsize limit' '
        )
 '
 
+test_expect_success 'diff --raw' '
+       git commit -q -m initial &&
+       echo modified >>large1 &&
+       git add large1 &&
+       git commit -q -m modified &&
+       git diff --raw HEAD^
+'
+
+test_expect_success 'hash-object' '
+       git hash-object large1
+'
+
+test_expect_success 'cat-file a large file' '
+       git cat-file blob :large1 >/dev/null
+'
+
+test_expect_success 'cat-file a large file from a tag' '
+       git tag -m largefile largefiletag :large1 &&
+       git cat-file blob largefiletag >/dev/null
+'
+
+test_expect_success 'git-show a large file' '
+       git show :large1 >/dev/null
+
+'
+
+test_expect_success 'repack' '
+       git repack -ad
+'
+
 test_done
index 9356beaf4be45c32d10209c5063b7a9c5aa06d42..397ccb690908dad2d45bab3d249f9c62f2dc91d1 100755 (executable)
@@ -154,8 +154,8 @@ test_expect_success 'git show-branch' '
 cat > resolve.expect << EOF
 Updating VARIABLE..VARIABLE
 FASTFORWARD (no commit created; -m option ignored)
- example |    1 +
- hello   |    1 +
+ example | 1 +
+ hello   | 1 +
  2 files changed, 2 insertions(+)
 EOF
 
index 36e227b3bb25cb17dabc5e205e63056e7fd0b370..a477453e2ef8ffedafb2dd05e00f507cd112bc97 100755 (executable)
@@ -550,6 +550,14 @@ EOF
 
 test_expect_success "rename succeeded" "test_cmp expect .git/config"
 
+test_expect_success 'renaming empty section name is rejected' '
+       test_must_fail git config --rename-section branch.zwei ""
+'
+
+test_expect_success 'renaming to bogus section is rejected' '
+       test_must_fail git config --rename-section branch.zwei "bogus name"
+'
+
 cat >> .git/config << EOF
   [branch "zwei"] a = 1 [branch "vier"]
 EOF
index 4b1cbaa0284f9b9cc3e29bf5f07b1a6a1f3a283c..a70707620f146d3fce69f77e08cae3a47253f157 100755 (executable)
@@ -29,6 +29,14 @@ test_expect_success 'chained relative paths' '
        test_cmp expect actual
 '
 
+test_expect_success 'include paths get tilde-expansion' '
+       echo "[test]one = 1" >one &&
+       echo "[include]path = ~/one" >.gitconfig &&
+       echo 1 >expect &&
+       git config test.one >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'include options can still be examined' '
        echo "[test]one = 1" >one &&
        echo "[include]path = one" >.gitconfig &&
index 252fc828374583cfb4c2346853bb87560efdf01d..236b13a3ab27f54808fa88574738747e3c3936b1 100755 (executable)
@@ -100,8 +100,7 @@ test_expect_success setup '
 
        check_fsck &&
 
-       loglen=$(wc -l <.git/logs/refs/heads/master) &&
-       test $loglen = 4
+       test_line_count = 4 .git/logs/refs/heads/master
 '
 
 test_expect_success rewind '
@@ -117,8 +116,7 @@ test_expect_success rewind '
 
        check_have A B C D E F G H I J K L &&
 
-       loglen=$(wc -l <.git/logs/refs/heads/master) &&
-       test $loglen = 5
+       test_line_count = 5 .git/logs/refs/heads/master
 '
 
 test_expect_success 'corrupt and check' '
@@ -136,8 +134,7 @@ test_expect_success 'reflog expire --dry-run should not touch reflog' '
                --stale-fix \
                --all &&
 
-       loglen=$(wc -l <.git/logs/refs/heads/master) &&
-       test $loglen = 5 &&
+       test_line_count = 5 .git/logs/refs/heads/master &&
 
        check_fsck "missing blob $F"
 '
@@ -150,8 +147,7 @@ test_expect_success 'reflog expire' '
                --stale-fix \
                --all &&
 
-       loglen=$(wc -l <.git/logs/refs/heads/master) &&
-       test $loglen = 2 &&
+       test_line_count = 2 .git/logs/refs/heads/master &&
 
        check_fsck "dangling commit $K"
 '
@@ -217,9 +213,7 @@ test_expect_success 'delete' '
 test_expect_success 'rewind2' '
 
        test_tick && git reset --hard HEAD~2 &&
-       loglen=$(wc -l <.git/logs/refs/heads/master) &&
-       test $loglen = 4
-
+       test_line_count = 4 .git/logs/refs/heads/master
 '
 
 test_expect_success '--expire=never' '
@@ -228,9 +222,7 @@ test_expect_success '--expire=never' '
                --expire=never \
                --expire-unreachable=never \
                --all &&
-       loglen=$(wc -l <.git/logs/refs/heads/master) &&
-       test $loglen = 4
-
+       test_line_count = 4 .git/logs/refs/heads/master
 '
 
 test_expect_success 'gc.reflogexpire=never' '
@@ -238,8 +230,7 @@ test_expect_success 'gc.reflogexpire=never' '
        git config gc.reflogexpire never &&
        git config gc.reflogexpireunreachable never &&
        git reflog expire --verbose --all &&
-       loglen=$(wc -l <.git/logs/refs/heads/master) &&
-       test $loglen = 4
+       test_line_count = 4 .git/logs/refs/heads/master
 '
 
 test_expect_success 'gc.reflogexpire=false' '
@@ -247,8 +238,7 @@ test_expect_success 'gc.reflogexpire=false' '
        git config gc.reflogexpire false &&
        git config gc.reflogexpireunreachable false &&
        git reflog expire --verbose --all &&
-       loglen=$(wc -l <.git/logs/refs/heads/master) &&
-       test $loglen = 4 &&
+       test_line_count = 4 .git/logs/refs/heads/master &&
 
        git config --unset gc.reflogexpire &&
        git config --unset gc.reflogexpireunreachable
index e661147c573fa7312e8f533a4a4c8ea1eda7763f..8f36aa9fc4d2bdd8b590e3241eccf88609db4ee2 100755 (executable)
@@ -68,7 +68,7 @@ test_expect_success 'inside work tree' '
        )
 '
 
-test_expect_failure 'empty prefix is actually written out' '
+test_expect_success 'empty prefix is actually written out' '
        echo >expected &&
        (
                cd work &&
index a4555510c37276d36387d4c5c818503bb286dbd1..d6e576192fcd014ed5f570ceeab1152b93d91d1f 100755 (executable)
@@ -15,10 +15,18 @@ test_expect_success 'setup' '
        test_commit 3 &&
        (cd clone &&
         test_commit 4 &&
-        git branch --track my-side origin/side)
-
+        git branch --track my-side origin/side &&
+        git branch --track local-master master &&
+        git remote add -t master master-only .. &&
+        git fetch master-only &&
+        git branch bad-upstream &&
+        git config branch.bad-upstream.remote master-only &&
+        git config branch.bad-upstream.merge refs/heads/side
+       )
 '
 
+sq="'"
+
 full_name () {
        (cd clone &&
         git rev-parse --symbolic-full-name "$@")
@@ -29,6 +37,11 @@ commit_subject () {
         git show -s --pretty=format:%s "$@")
 }
 
+error_message () {
+       (cd clone &&
+        test_must_fail git rev-parse --verify "$@")
+}
+
 test_expect_success '@{upstream} resolves to correct full name' '
        test refs/remotes/origin/master = "$(full_name @{upstream})"
 '
@@ -78,7 +91,6 @@ test_expect_success 'checkout -b new my-side@{u} forks from the same' '
 
 test_expect_success 'merge my-side@{u} records the correct name' '
 (
-       sq="'\''" &&
        cd clone || exit
        git checkout master || exit
        git branch -D new ;# can fail but is ok
@@ -107,6 +119,69 @@ test_expect_success 'checkout other@{u}' '
        test_cmp expect actual
 '
 
+test_expect_success 'branch@{u} works when tracking a local branch' '
+       test refs/heads/master = "$(full_name local-master@{u})"
+'
+
+test_expect_success 'branch@{u} error message when no upstream' '
+       cat >expect <<-EOF &&
+       error: No upstream configured for branch ${sq}non-tracking${sq}
+       fatal: Needed a single revision
+       EOF
+       error_message non-tracking@{u} 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success '@{u} error message when no upstream' '
+       cat >expect <<-EOF &&
+       error: No upstream configured for branch ${sq}master${sq}
+       fatal: Needed a single revision
+       EOF
+       test_must_fail git rev-parse --verify @{u} 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'branch@{u} error message with misspelt branch' '
+       cat >expect <<-EOF &&
+       error: No such branch: ${sq}no-such-branch${sq}
+       fatal: Needed a single revision
+       EOF
+       error_message no-such-branch@{u} 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success '@{u} error message when not on a branch' '
+       cat >expect <<-EOF &&
+       error: HEAD does not point to a branch
+       fatal: Needed a single revision
+       EOF
+       git checkout HEAD^0 &&
+       test_must_fail git rev-parse --verify @{u} 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'branch@{u} error message if upstream branch not fetched' '
+       cat >expect <<-EOF &&
+       error: Upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
+       fatal: Needed a single revision
+       EOF
+       error_message bad-upstream@{u} 2>actual &&
+       test_i18ncmp expect actual
+'
+
+test_expect_success 'pull works when tracking a local branch' '
+(
+       cd clone &&
+       git checkout local-master &&
+       git pull
+)
+'
+
+# makes sense if the previous one succeeded
+test_expect_success '@{u} works when tracking a local branch' '
+       test refs/heads/master = "$(full_name @{u})"
+'
+
 cat >expect <<EOF
 commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
 Reflog: master@{0} (C O Mitter <committer@example.com>)
index 36cca14d957f85733174d6ce514e22acfff3b1c9..0f4b2896af8b73edcd5bd60631405a430803a40f 100755 (executable)
@@ -40,7 +40,7 @@ test_expect_success \
 rm -f path* .merge_* out .git/index &&
 git read-tree $t1 &&
 git checkout-index --temp -- path1 >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
 test $(cut "-d " -f2 out) = path1 &&
 p=$(cut "-d    " -f1 out) &&
 test -f $p &&
@@ -51,7 +51,7 @@ test_expect_success \
 rm -f path* .merge_* out .git/index &&
 git read-tree $t1 &&
 git checkout-index -a --temp >out &&
-test $(wc -l <out) = 5 &&
+test_line_count = 5 out &&
 for f in path0 path1 path3 path4 asubdir/path5
 do
        test $(grep $f out | cut "-d    " -f2) = $f &&
@@ -69,7 +69,7 @@ test_expect_success \
 'checkout one stage 2 to temporary file' '
 rm -f path* .merge_* out &&
 git checkout-index --stage=2 --temp -- path1 >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
 test $(cut "-d " -f2 out) = path1 &&
 p=$(cut "-d    " -f1 out) &&
 test -f $p &&
@@ -79,7 +79,7 @@ test_expect_success \
 'checkout all stage 2 to temporary files' '
 rm -f path* .merge_* out &&
 git checkout-index --all --stage=2 --temp >out &&
-test $(wc -l <out) = 3 &&
+test_line_count = 3 out &&
 for f in path1 path2 path4
 do
        test $(grep $f out | cut "-d    " -f2) = $f &&
@@ -92,13 +92,13 @@ test_expect_success \
 'checkout all stages/one file to nothing' '
 rm -f path* .merge_* out &&
 git checkout-index --stage=all --temp -- path0 >out &&
-test $(wc -l <out) = 0'
+test_line_count = 0 out'
 
 test_expect_success \
 'checkout all stages/one file to temporary files' '
 rm -f path* .merge_* out &&
 git checkout-index --stage=all --temp -- path1 >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
 test $(cut "-d " -f2 out) = path1 &&
 cut "-d        " -f1 out | (read s1 s2 s3 &&
 test -f $s1 &&
@@ -112,7 +112,7 @@ test_expect_success \
 'checkout some stages/one file to temporary files' '
 rm -f path* .merge_* out &&
 git checkout-index --stage=all --temp -- path2 >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
 test $(cut "-d " -f2 out) = path2 &&
 cut "-d        " -f1 out | (read s1 s2 s3 &&
 test $s1 = . &&
@@ -125,7 +125,7 @@ test_expect_success \
 'checkout all stages/all files to temporary files' '
 rm -f path* .merge_* out &&
 git checkout-index -a --stage=all --temp >out &&
-test $(wc -l <out) = 5'
+test_line_count = 5 out'
 
 test_expect_success \
 '-- path0: no entry' '
@@ -185,7 +185,7 @@ test_expect_success \
 'checkout --temp within subdir' '
 (cd asubdir &&
  git checkout-index -a --stage=all >out &&
- test $(wc -l <out) = 1 &&
+ test_line_count = 1 out &&
  test $(grep path5 out | cut "-d       " -f2) = path5 &&
  grep path5 out | cut "-d      " -f1 | (read s1 s2 s3 &&
  test -f ../$s1 &&
@@ -203,7 +203,7 @@ t4=$(git write-tree) &&
 rm -f .git/index &&
 git read-tree $t4 &&
 git checkout-index --temp -a >out &&
-test $(wc -l <out) = 1 &&
+test_line_count = 1 out &&
 test $(cut "-d " -f2 out) = a &&
 p=$(cut "-d    " -f1 out) &&
 test -f $p &&
index 068fba4c8e8706aa615d0401da1da47901113156..b37ce25c42cd2fb0e2868c19f66902b319526de2 100755 (executable)
@@ -148,7 +148,7 @@ test_expect_success 'tracking count is accurate after orphan check' '
        git config branch.child.merge refs/heads/master &&
        git checkout child^ &&
        git checkout child >stdout &&
-       test_cmp expect stdout
+       test_i18ncmp expect stdout
 '
 
 test_done
index cb7effe0a3e38eeba92b43682de9be68e677099e..f2620650ce1d25252210c07db20e54f99bd515c6 100755 (executable)
@@ -113,7 +113,7 @@ test_expect_success 'unmerge with plumbing' '
        prime_resolve_undo &&
        git update-index --unresolve fi/le &&
        git ls-files -u >actual &&
-       test $(wc -l <actual) = 3
+       test_line_count = 3 actual
 '
 
 test_expect_success 'rerere and rerere forget' '
index 9fe1d8feab419e1a8065b2ea5881f991edc68855..a17f8b2a407c2de2bf81d8a858957da409f1c980 100755 (executable)
@@ -160,6 +160,83 @@ test_expect_success 'git branch --list -d t should fail' '
        test_path_is_missing .git/refs/heads/t
 '
 
+test_expect_success 'git branch --column' '
+       COLUMNS=81 git branch --column=column >actual &&
+       cat >expected <<\EOF &&
+  a/b/c     bam       foo       l       * master    n         o/p       r
+  abc       bar       j/k       m/m       master2   o/o       q
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'git branch --column with an extremely long branch name' '
+       long=this/is/a/part/of/long/branch/name &&
+       long=z$long/$long/$long/$long &&
+       test_when_finished "git branch -d $long" &&
+       git branch $long &&
+       COLUMNS=80 git branch --column=column >actual &&
+       cat >expected <<EOF &&
+  a/b/c
+  abc
+  bam
+  bar
+  foo
+  j/k
+  l
+  m/m
+* master
+  master2
+  n
+  o/o
+  o/p
+  q
+  r
+  $long
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'git branch with column.*' '
+       git config column.ui column &&
+       git config column.branch "dense" &&
+       COLUMNS=80 git branch >actual &&
+       git config --unset column.branch &&
+       git config --unset column.ui &&
+       cat >expected <<\EOF &&
+  a/b/c   bam   foo   l   * master    n     o/p   r
+  abc     bar   j/k   m/m   master2   o/o   q
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'git branch --column -v should fail' '
+       test_must_fail git branch --column -v
+'
+
+test_expect_success 'git branch -v with column.ui ignored' '
+       git config column.ui column &&
+       COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual &&
+       git config --unset column.ui &&
+       cat >expected <<\EOF &&
+  a/b/c
+  abc
+  bam
+  bar
+  foo
+  j/k
+  l
+  m/m
+* master
+  master2
+  n
+  o/o
+  o/p
+  q
+  r
+EOF
+       test_cmp expected actual
+'
+
 mv .git/config .git/config-saved
 
 test_expect_success 'git branch -m q q2 without config should succeed' '
index 9f00ada5f7776b2377993cc9392b89e5312a513f..c53c9f65ebd2824d4a0d528b25d85e1e0b26f4df 100755 (executable)
@@ -15,184 +15,204 @@ p0='no-funny'
 p1='tabs       ," (dq) and spaces'
 p2='just space'
 
-cat >"$p0" <<\EOF
-1. A quick brown fox jumps over the lazy cat, oops dog.
-2. A quick brown fox jumps over the lazy cat, oops dog.
-3. A quick brown fox jumps over the lazy cat, oops dog.
-EOF
-
-cat 2>/dev/null >"$p1" "$p0"
-echo 'Foo Bar Baz' >"$p2"
+test_expect_success 'setup' '
+       cat >"$p0" <<-\EOF &&
+       1. A quick brown fox jumps over the lazy cat, oops dog.
+       2. A quick brown fox jumps over the lazy cat, oops dog.
+       3. A quick brown fox jumps over the lazy cat, oops dog.
+       EOF
+
+       { cat "$p0" >"$p1" || :; } &&
+       { echo "Foo Bar Baz" >"$p2" || :; } &&
+
+       if test -f "$p1" && cmp "$p0" "$p1"
+       then
+               test_set_prereq TABS_IN_FILENAMES
+       fi
+'
 
-if test -f "$p1" && cmp "$p0" "$p1"
+if ! test_have_prereq TABS_IN_FILENAMES
 then
-    test_set_prereq TABS_IN_FILENAMES
-else
        # since FAT/NTFS does not allow tabs in filenames, skip this test
-       say 'Your filesystem does not allow tabs in filenames'
+       skip_all='Your filesystem does not allow tabs in filenames'
+       test_done
 fi
 
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'just space
-no-funny' >expected
-"
+test_expect_success 'setup: populate index and tree' '
+       git update-index --add "$p0" "$p2" &&
+       t0=$(git write-tree)
+'
 
-test_expect_success TABS_IN_FILENAMES 'git ls-files no-funny' \
-       'git update-index --add "$p0" "$p2" &&
+test_expect_success 'ls-files prints space in filename verbatim' '
+       printf "%s\n" "just space" no-funny >expected &&
        git ls-files >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-t0=`git write-tree` &&
-echo "$t0" >t0 &&
+       test_cmp expected current
+'
 
-cat > expected <<\EOF
-just space
-no-funny
-"tabs\t,\" (dq) and spaces"
-EOF
+test_expect_success 'setup: add funny filename' '
+       git update-index --add "$p1" &&
+       t1=$(git write-tree)
 '
 
-test_expect_success TABS_IN_FILENAMES 'git ls-files with-funny' \
-       'git update-index --add "$p1" &&
+test_expect_success 'ls-files quotes funny filename' '
+       cat >expected <<-\EOF &&
+       just space
+       no-funny
+       "tabs\t,\" (dq) and spaces"
+       EOF
        git ls-files >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'just space
-no-funny
-tabs   ,\" (dq) and spaces' >expected
-"
-
-test_expect_success TABS_IN_FILENAMES 'git ls-files -z with-funny' \
-       'git ls-files -z | perl -pe y/\\000/\\012/ >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-t1=`git write-tree` &&
-echo "$t1" >t1 &&
-
-cat > expected <<\EOF
-just space
-no-funny
-"tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git ls-tree with funny' \
-       'git ls-tree -r $t1 | sed -e "s/^[^     ]*      //" >current &&
-        test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-A      "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-index with-funny' \
-       'git diff-index --name-status $t0 >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree with-funny' \
-       'git diff-tree --name-status $t0 $t1 >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' "
-echo 'A
-tabs   ,\" (dq) and spaces' >expected
-"
-
-test_expect_success TABS_IN_FILENAMES 'git diff-index -z with-funny' \
-       'git diff-index -z --name-status $t0 | perl -pe y/\\000/\\012/ >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree -z with-funny' \
-       'git diff-tree -z --name-status $t0 $t1 | perl -pe y/\\000/\\012/ >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-CNUM   no-funny        "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree -C with-funny' \
-       'git diff-tree -C --find-copies-harder --name-status \
-               $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-RNUM   no-funny        "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
-       'git update-index --force-remove "$p0" &&
-       git diff-index -M --name-status \
-               $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
-       test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
-diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
-similarity index NUM%
-rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
-       'git diff-index -M -p $t0 |
-        sed -e "s/index [0-9]*%/index NUM%/" >current &&
-        test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-chmod +x "$p1" &&
-cat > expected <<\EOF
-diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
-old mode 100644
-new mode 100755
-similarity index NUM%
-rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny' \
-       'git diff-index -M -p $t0 |
-        sed -e "s/index [0-9]*%/index NUM%/" >current &&
-        test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat >expected <<\EOF
- "tabs\t,\" (dq) and spaces"
- 1 file changed, 0 insertions(+), 0 deletions(-)
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree rename with-funny applied' \
-       'git diff-index -M -p $t0 |
-        git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
-        test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'setup expect' '
-cat > expected <<\EOF
- no-funny
- "tabs\t,\" (dq) and spaces"
- 2 files changed, 3 insertions(+), 3 deletions(-)
-EOF
-'
-
-test_expect_success TABS_IN_FILENAMES 'git diff-tree delete with-funny applied' \
-       'git diff-index -p $t0 |
-        git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
-        test_cmp expected current'
-
-test_expect_success TABS_IN_FILENAMES 'git apply non-git diff' \
-       'git diff-index -p $t0 |
-        sed -ne "/^[-+@]/p" |
-        git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
-        test_cmp expected current'
+       test_cmp expected current
+'
+
+test_expect_success 'ls-files -z does not quote funny filename' '
+       cat >expected <<-\EOF &&
+       just space
+       no-funny
+       tabs    ," (dq) and spaces
+       EOF
+       git ls-files -z >ls-files.z &&
+       perl -pe "y/\000/\012/" <ls-files.z >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'ls-tree quotes funny filename' '
+       cat >expected <<-\EOF &&
+       just space
+       no-funny
+       "tabs\t,\" (dq) and spaces"
+       EOF
+       git ls-tree -r $t1 >ls-tree &&
+       sed -e "s/^[^   ]*      //" <ls-tree >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'diff-index --name-status quotes funny filename' '
+       cat >expected <<-\EOF &&
+       A       "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-index --name-status $t0 >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'diff-tree --name-status quotes funny filename' '
+       cat >expected <<-\EOF &&
+       A       "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-tree --name-status $t0 $t1 >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'diff-index -z does not quote funny filename' '
+       cat >expected <<-\EOF &&
+       A
+       tabs    ," (dq) and spaces
+       EOF
+       git diff-index -z --name-status $t0 >diff-index.z &&
+       perl -pe "y/\000/\012/" <diff-index.z >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'diff-tree -z does not quote funny filename' '
+       cat >expected <<-\EOF &&
+       A
+       tabs    ," (dq) and spaces
+       EOF
+       git diff-tree -z --name-status $t0 $t1 >diff-tree.z &&
+       perl -pe y/\\000/\\012/ <diff-tree.z >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'diff-tree --find-copies-harder quotes funny filename' '
+       cat >expected <<-\EOF &&
+       CNUM    no-funny        "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-tree -C --find-copies-harder --name-status $t0 $t1 >out &&
+       sed -e "s/^C[0-9]*/CNUM/" <out >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'setup: remove unfunny index entry' '
+       git update-index --force-remove "$p0"
+'
+
+test_expect_success 'diff-tree -M quotes funny filename' '
+       cat >expected <<-\EOF &&
+       RNUM    no-funny        "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-index -M --name-status $t0 >out &&
+       sed -e "s/^R[0-9]*/RNUM/" <out >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'diff-index -M -p quotes funny filename' '
+       cat >expected <<-\EOF &&
+       diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+       similarity index NUM%
+       rename from no-funny
+       rename to "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-index -M -p $t0 >diff &&
+       sed -e "s/index [0-9]*%/index NUM%/" <diff >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'setup: mode change' '
+       chmod +x "$p1"
+'
+
+test_expect_success 'diff-index -M -p with mode change quotes funny filename' '
+       cat >expected <<-\EOF &&
+       diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+       old mode 100644
+       new mode 100755
+       similarity index NUM%
+       rename from no-funny
+       rename to "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-index -M -p $t0 >diff &&
+       sed -e "s/index [0-9]*%/index NUM%/" <diff >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'diffstat for rename quotes funny filename' '
+       cat >expected <<-\EOF &&
+        "tabs\t,\" (dq) and spaces"
+        1 file changed, 0 insertions(+), 0 deletions(-)
+       EOF
+       git diff-index -M -p $t0 >diff &&
+       git apply --stat <diff >diffstat &&
+       sed -e "s/|.*//" -e "s/ *\$//" <diffstat >current &&
+       test_i18ncmp expected current
+'
+
+test_expect_success 'numstat for rename quotes funny filename' '
+       cat >expected <<-\EOF &&
+       0       0       "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-index -M -p $t0 >diff &&
+       git apply --numstat <diff >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'numstat without -M quotes funny filename' '
+       cat >expected <<-\EOF &&
+       0       3       no-funny
+       3       0       "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-index -p $t0 >diff &&
+       git apply --numstat <diff >current &&
+       test_cmp expected current
+'
+
+test_expect_success 'numstat for non-git rename diff quotes funny filename' '
+       cat >expected <<-\EOF &&
+       0       3       no-funny
+       3       0       "tabs\t,\" (dq) and spaces"
+       EOF
+       git diff-index -p $t0 >git-diff &&
+       sed -ne "/^[-+@]/p" <git-diff >diff &&
+       git apply --numstat <diff >current &&
+       test_cmp expected current
+'
 
 test_done
index 7fd2127625506c39371bda873ec2f56593b65aca..4983bd77753307a7f93963b02534f36ae6fc71bb 100755 (executable)
@@ -323,7 +323,7 @@ test_expect_success 'verbose flag is heeded, even after --continue' '
        echo resolved > file1 &&
        git add file1 &&
        git rebase --continue > output &&
-       grep "^ file1 |    2 +-$" output
+       grep "^ file1 | 2 +-$" output
 '
 
 test_expect_success 'multi-squash only fires up editor once' '
index b38be8e93723991d717b6b7fb690560efb58c36d..a1e86c4097ba096245b11a991c39e79886ca2b91 100755 (executable)
@@ -33,7 +33,7 @@ test_auto_fixup () {
        test_tick &&
        git rebase $2 -i HEAD^^^ &&
        git log --oneline >actual &&
-       test 3 = $(wc -l <actual) &&
+       test_line_count = 3 actual &&
        git diff --exit-code $1 &&
        test 1 = "$(git cat-file blob HEAD^:file1)" &&
        test 1 = $(git cat-file commit HEAD^ | grep first | wc -l)
@@ -62,7 +62,7 @@ test_auto_squash () {
        test_tick &&
        git rebase $2 -i HEAD^^^ &&
        git log --oneline >actual &&
-       test 3 = $(wc -l <actual) &&
+       test_line_count = 3 actual &&
        git diff --exit-code $1 &&
        test 1 = "$(git cat-file blob HEAD^:file1)" &&
        test 2 = $(git cat-file commit HEAD^ | grep first | wc -l)
@@ -90,7 +90,7 @@ test_expect_success 'misspelled auto squash' '
        test_tick &&
        git rebase --autosquash -i HEAD^^^ &&
        git log --oneline >actual &&
-       test 4 = $(wc -l <actual) &&
+       test_line_count = 4 actual &&
        git diff --exit-code final-missquash &&
        test 0 = $(git rev-list final-missquash...HEAD | wc -l)
 '
@@ -109,7 +109,7 @@ test_expect_success 'auto squash that matches 2 commits' '
        test_tick &&
        git rebase --autosquash -i HEAD~4 &&
        git log --oneline >actual &&
-       test 4 = $(wc -l <actual) &&
+       test_line_count = 4 actual &&
        git diff --exit-code final-multisquash &&
        test 1 = "$(git cat-file blob HEAD^^:file1)" &&
        test 2 = $(git cat-file commit HEAD^^ | grep first | wc -l) &&
@@ -130,7 +130,7 @@ test_expect_success 'auto squash that matches a commit after the squash' '
        test_tick &&
        git rebase --autosquash -i HEAD~4 &&
        git log --oneline >actual &&
-       test 5 = $(wc -l <actual) &&
+       test_line_count = 5 actual &&
        git diff --exit-code final-presquash &&
        test 0 = "$(git cat-file blob HEAD^^:file1)" &&
        test 1 = "$(git cat-file blob HEAD^:file1)" &&
@@ -147,7 +147,7 @@ test_expect_success 'auto squash that matches a sha1' '
        test_tick &&
        git rebase --autosquash -i HEAD^^^ &&
        git log --oneline >actual &&
-       test 3 = $(wc -l <actual) &&
+       test_line_count = 3 actual &&
        git diff --exit-code final-shasquash &&
        test 1 = "$(git cat-file blob HEAD^:file1)" &&
        test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
@@ -163,7 +163,7 @@ test_expect_success 'auto squash that matches longer sha1' '
        test_tick &&
        git rebase --autosquash -i HEAD^^^ &&
        git log --oneline >actual &&
-       test 3 = $(wc -l <actual) &&
+       test_line_count = 3 actual &&
        git diff --exit-code final-longshasquash &&
        test 1 = "$(git cat-file blob HEAD^:file1)" &&
        test 1 = $(git cat-file commit HEAD^ | grep squash | wc -l)
@@ -179,7 +179,7 @@ test_auto_commit_flags () {
        test_tick &&
        git rebase --autosquash -i HEAD^^^ &&
        git log --oneline >actual &&
-       test 3 = $(wc -l <actual) &&
+       test_line_count = 3 actual &&
        git diff --exit-code final-commit-$1 &&
        test 1 = "$(git cat-file blob HEAD^:file1)" &&
        test $2 = $(git cat-file commit HEAD^ | grep first | wc -l)
index c10b28cf5731705b437793a58f5acd6c605ad579..92f00cdf84993da995f074efa12344c26b627d67 100755 (executable)
@@ -18,7 +18,12 @@ test_expect_success setup '
        echo third >> file1 &&
        git add file1 &&
        test_tick &&
-       git commit --allow-empty-message -m ""
+       git commit --allow-empty-message -m "" &&
+
+       git checkout master &&
+       git checkout -b empty-branch2 &&
+       test_tick &&
+       git commit --allow-empty -m "empty"
 
 '
 
@@ -48,4 +53,22 @@ test_expect_success 'index lockfile was removed' '
 
 '
 
+test_expect_success 'cherry pick an empty non-ff commit without --allow-empty' '
+       git checkout master &&
+       echo fourth >>file2 &&
+       git add file2 &&
+       git commit -m "fourth" &&
+       test_must_fail git cherry-pick empty-branch2
+'
+
+test_expect_success 'cherry pick an empty non-ff commit with --allow-empty' '
+       git checkout master &&
+       git cherry-pick --allow-empty empty-branch2
+'
+
+test_expect_success 'cherry pick with --keep-redundant-commits' '
+       git checkout master &&
+       git cherry-pick --keep-redundant-commits HEAD^
+'
+
 test_done
index 1b3a344158aa8077c1e5b47f9ab8bd6394e153ed..75f7ff4f2fe21e86e0a26fe5a6c2119bef38404c 100755 (executable)
@@ -35,6 +35,16 @@ test_expect_success setup '
 '
 
 test_expect_success 'cherry-pick first..fourth works' '
+       git checkout -f master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick first..fourth &&
+       git diff --quiet other &&
+       git diff --quiet HEAD other &&
+       check_head_differs_from fourth
+'
+
+test_expect_success 'output to keep user entertained during multi-pick' '
        cat <<-\EOF >expected &&
        [master OBJID] second
         Author: A U Thor <author@example.com>
@@ -51,15 +61,22 @@ test_expect_success 'cherry-pick first..fourth works' '
        git reset --hard first &&
        test_tick &&
        git cherry-pick first..fourth >actual &&
+       sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
+       test_line_count -ge 3 actual.fuzzy &&
+       test_i18ncmp expected actual.fuzzy
+'
+
+test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
+       git checkout -f master &&
+       git reset --hard first &&
+       test_tick &&
+       git cherry-pick --strategy resolve first..fourth &&
        git diff --quiet other &&
        git diff --quiet HEAD other &&
-
-       sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
-       test_cmp expected actual.fuzzy &&
        check_head_differs_from fourth
 '
 
-test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
+test_expect_success 'output during multi-pick indicates merge strategy' '
        cat <<-\EOF >expected &&
        Trying simple merge.
        [master OBJID] second
@@ -79,11 +96,8 @@ test_expect_success 'cherry-pick --strategy resolve first..fourth works' '
        git reset --hard first &&
        test_tick &&
        git cherry-pick --strategy resolve first..fourth >actual &&
-       git diff --quiet other &&
-       git diff --quiet HEAD other &&
        sed -e "s/$_x05[0-9a-f][0-9a-f]/OBJID/" <actual >actual.fuzzy &&
-       test_cmp expected actual.fuzzy &&
-       check_head_differs_from fourth
+       test_i18ncmp expected actual.fuzzy
 '
 
 test_expect_success 'cherry-pick --ff first..fourth works' '
index d48a7c002d622ffac5087be9a7998f781a242731..37ddabba2d60956a4d8da63585bec5a622e12a5d 100755 (executable)
@@ -160,7 +160,7 @@ test_commit_autosquash_flags () {
                git config --unset-all i18n.commitencoding &&
                git rebase --autosquash -i HEAD^^^ &&
                git log --oneline >actual &&
-               test 3 = $(wc -l <actual)
+               test_line_count = 3 actual
        '
 }
 
index 663c60a12e82c96065e60fd448a6583c91e5a2cd..cd042633ba5ec36be89507d25254b32a8f0b98ae 100755 (executable)
@@ -432,7 +432,7 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' '
        test $(git ls-files --modified | wc -l) -eq 1
 '
 
-test_expect_success 'stash show - stashes on stack, stash-like argument' '
+test_expect_success 'stash show format defaults to --stat' '
        git stash clear &&
        test_when_finished "git reset --hard HEAD" &&
        git reset --hard &&
@@ -443,10 +443,25 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' '
        STASH_ID=$(git stash create) &&
        git reset --hard &&
        cat >expected <<-EOF &&
-        file |    1 +
+        file | 1 +
         1 file changed, 1 insertion(+)
        EOF
        git stash show ${STASH_ID} >actual &&
+       test_i18ncmp expected actual
+'
+
+test_expect_success 'stash show - stashes on stack, stash-like argument' '
+       git stash clear &&
+       test_when_finished "git reset --hard HEAD" &&
+       git reset --hard &&
+       echo foo >> file &&
+       git stash &&
+       test_when_finished "git stash drop" &&
+       echo bar >> file &&
+       STASH_ID=$(git stash create) &&
+       git reset --hard &&
+       echo "1 0       file" >expected &&
+       git stash show --numstat ${STASH_ID} >actual &&
        test_cmp expected actual
 '
 
@@ -480,11 +495,8 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' '
        echo foo >> file &&
        STASH_ID=$(git stash create) &&
        git reset --hard &&
-       cat >expected <<-EOF &&
-        file |    1 +
-        1 file changed, 1 insertion(+)
-       EOF
-       git stash show ${STASH_ID} >actual &&
+       echo "1 0       file" >expected &&
+       git stash show --numstat ${STASH_ID} >actual &&
        test_cmp expected actual
 '
 
index ff8c2f7532aa6fd748dbbdc0322384576f3a35ed..7a3e1f9a24e4be3759c29acc632fc461ede2b74b 100755 (executable)
@@ -8,23 +8,52 @@ test_description='Test mode change diffs.
 '
 . ./test-lib.sh
 
-test_expect_success \
-    'setup' \
-    'echo frotz >rezrov &&
-     git update-index --add rezrov &&
-     tree=`git write-tree` &&
-     echo $tree'
-
-test_expect_success \
-    'chmod' \
-    'test_chmod +x rezrov &&
-     git diff-index $tree >current'
-
-sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
-echo ":100644 100755 X X M     rezrov" >expected
-
-test_expect_success \
-    'verify' \
-    'test_cmp expected check'
+sed_script='s/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /'
+
+test_expect_success 'setup' '
+       echo frotz >rezrov &&
+       git update-index --add rezrov &&
+       tree=`git write-tree` &&
+       echo $tree
+'
+
+test_expect_success 'chmod' '
+       test_chmod +x rezrov &&
+       git diff-index $tree >current &&
+       sed -e "$sed_script" <current >check &&
+       echo ":100644 100755 X X M      rezrov" >expected &&
+       test_cmp expected check
+'
+
+test_expect_success 'prepare binary file' '
+       git commit -m rezrov &&
+       printf "\00\01\02\03\04\05\06" >binbin &&
+       git add binbin &&
+       git commit -m binbin
+'
+
+test_expect_success '--stat output after text chmod' '
+       test_chmod -x rezrov &&
+       echo " 0 files changed" >expect &&
+       git diff HEAD --stat >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--shortstat output after text chmod' '
+       git diff HEAD --shortstat >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--stat output after binary chmod' '
+       test_chmod +x binbin &&
+       echo " 0 files changed" >expect &&
+       git diff HEAD --stat >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--shortstat output after binary chmod' '
+       git diff HEAD --shortstat >actual &&
+       test_cmp expect actual
+'
 
 test_done
index 2d9f9a0cf1555cab24af19f264d495e134c27352..8b4e80de9627d9a8fa90a31edbe05c2c70f1d4b3 100755 (executable)
@@ -8,6 +8,13 @@ test_description='Binary diff and apply
 
 . ./test-lib.sh
 
+cat >expect.binary-numstat <<\EOF
+1      1       a
+-      -       b
+1      1       c
+-      -       d
+EOF
+
 test_expect_success 'prepare repository' \
        'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
         git update-index --add a b c d &&
@@ -23,13 +30,23 @@ cat > expected <<\EOF
  d |  Bin
  4 files changed, 2 insertions(+), 2 deletions(-)
 EOF
-test_expect_success 'diff without --binary' \
-       'git diff | git apply --stat --summary >current &&
-        test_cmp expected current'
+test_expect_success '"apply --stat" output for binary file change' '
+       git diff >diff &&
+       git apply --stat --summary <diff >current &&
+       test_i18ncmp expected current
+'
+
+test_expect_success 'apply --numstat notices binary file change' '
+       git diff >diff &&
+       git apply --numstat <diff >current &&
+       test_cmp expect.binary-numstat current
+'
 
-test_expect_success 'diff with --binary' \
-       'git diff --binary | git apply --stat --summary >current &&
-        test_cmp expected current'
+test_expect_success 'apply --numstat understands diff --binary format' '
+       git diff --binary >diff &&
+       git apply --numstat <diff >current &&
+       test_cmp expect.binary-numstat current
+'
 
 # apply needs to be able to skip the binary material correctly
 # in order to report the line number of a corrupt patch.
@@ -90,4 +107,23 @@ test_expect_success 'diff --no-index with binary creation' '
        test_cmp expected actual
 '
 
+cat >expect <<EOF
+ binfile  |   Bin 0 -> 1026 bytes
+ textfile | 10000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
+
+test_expect_success 'diff --stat with binary files and big change count' '
+       echo X | dd of=binfile bs=1k seek=1 &&
+       git add binfile &&
+       i=0 &&
+       while test $i -lt 10000; do
+               echo $i &&
+               i=$(($i + 1))
+       done >textfile &&
+       git add textfile &&
+       git diff --cached --stat binfile textfile >output &&
+       grep " | " output >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 93a6f208710befc064b7b99bcd758bb8b6381918..e77c09c37eede2f039610199ba8e3c45e94213d4 100755 (executable)
@@ -128,7 +128,12 @@ do
                } >"$actual" &&
                if test -f "$expect"
                then
-                       test_cmp "$expect" "$actual" &&
+                       case $cmd in
+                       *format-patch* | *-stat*)
+                               test_i18ncmp "$expect" "$actual";;
+                       *)
+                               test_cmp "$expect" "$actual";;
+                       esac &&
                        rm -f "$actual"
                else
                        # this is to help developing new tests.
index 2f8560c369c01e72b8ec11d43e04df0a0e9ce60c..9951e3677d694304e3d52448e7a12d881c1943af 100644 (file)
@@ -1,7 +1,7 @@
 $ git diff-tree --cc --patch-with-stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
index 72e03c14fb4edb2812202da8acd837b064131510..cec33fa3f02b2b8990b51b61c6e1f361e9c9b208 100644 (file)
@@ -1,8 +1,8 @@
 $ git diff-tree --cc --patch-with-stat --summary side
 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 8b357d964b4773c3078e2c32bd3e765f5749841c..db3c0a7b2cc5d98b85e22074df924f3cb666b840 100644 (file)
@@ -1,7 +1,7 @@
 $ git diff-tree --cc --patch-with-stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
index e0568d688311059ad04558f50bb3ddacd21fd403..d019867dd9415bc9fef748e4204c2376fa97b2f1 100644 (file)
@@ -1,6 +1,6 @@
 $ git diff-tree --cc --stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 $
index 5afc8239a11f7f7fc20a3fabfad5213eb49fd153..12b2eee17ec04a40a10f0c7300379e01755c0114 100644 (file)
@@ -1,8 +1,8 @@
 $ git diff-tree --cc --stat --summary side
 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index f48367a89a64945875a81024e444b35771800855..40b91796b34a1f22b51414d48c76152b4dc881ca 100644 (file)
@@ -1,6 +1,6 @@
 $ git diff-tree --cc --stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 $
index 590864c2d798472a3fed9fd6bf1fa41545fd572d..817ed06f822e28a0b15a71c2c8bb519e3cdcde67 100644 (file)
@@ -1,8 +1,8 @@
 $ git diff-tree --pretty=oneline --root --patch-with-stat initial
 444ac553ac7612cc88969031b02b3767fb8a353a Initial
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index e05e77875ccdaa8a258310f85f5753ae84b4c6c4..fe3f6b7c7e799cd3e99b46742feccec2cfce5634 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index 0e2c956633d28897e7bbddb58ad1cc053e55a8a6..06eb77e3861c321765bb77e701703db9400919a3 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index 384fa44ddd1ab9dbf0d38caf1f445fed1a0b5030..680eab5f27961e0a1ee6c26e0bd28f0aaa4a2c62 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
index 10384a83d31da363cec05f2fecb9def43ae158d7..9722d1b3a76ad89bedaab114a1800a88157bf8d1 100644 (file)
@@ -5,8 +5,8 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
 $
index f57062ea07ac63aa7c700e470aa43d1ede20e09b..ad69ffe647bbd692d307c0b7a8d047b8c79b3a2d 100644 (file)
@@ -1,8 +1,8 @@
 $ git diff-tree --root --patch-with-stat initial
 444ac553ac7612cc88969031b02b3767fb8a353a
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index 7088683444e54a026e201849ef5d635fab66fc3b..81c3021541bc61ec36557822d314c2e57e0a1265 100644 (file)
@@ -1,6 +1,6 @@
 $ git diff-tree -c --stat --summary master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 $
index ef216abb1dcaa91d1aa521599b25faf16f704124..e8dc12bfbf65577ad74bfe5bc9b2c8a838531eef 100644 (file)
@@ -1,8 +1,8 @@
 $ git diff-tree -c --stat --summary side
 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index ad19f103eb3323d1c84719cec13a09885e3b47f4..89d59b15480f073d026cfbe6fca6ae31522cef22 100644 (file)
@@ -1,6 +1,6 @@
 $ git diff-tree -c --stat master
 59d314ad6f356dd08601a4cd5e530381da3e3c64
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 $
index ddad917ae80a5c80b2c7b7f65a85a19404b59945..be8d1ea1bd2d2b0b254b2483162df6d2735af8f8 100644 (file)
@@ -1,7 +1,7 @@
 $ git diff --patch-with-stat -r initial..side
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index bdbd114d8eaf5dd6f40502300c479839a61f0d6b..5424e6d5669ac11b55376f414439213643d6eade 100644 (file)
@@ -1,7 +1,7 @@
 $ git diff --patch-with-stat initial..side
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index 6d08f3d3550413a23c08e264b4707dafeba21acf..b7741e2b8367196c04366ac911c6837531b8a34d 100644 (file)
@@ -1,6 +1,6 @@
 $ git diff --stat initial..side
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 $
index 2ddb2540e640584532dd7b22d1618b35b322a20d..5d514f55b9382aa83f9415e65cc9039eb9b5f5c4 100644 (file)
@@ -1,6 +1,6 @@
 $ git diff -r --stat initial..side
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 $
index 3cab049f7d98bfe9988c9f19fe477d0d29c8c2d5..547ca065a59cd7ed4cb74dffe570db81e9fd9c0e 100644 (file)
@@ -12,9 +12,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 564a4d38f229946ea08e9c0110a5ad6a20acce0b..52fedc179e50de8f6dc61dbe069dc2737099f0b9 100644 (file)
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -73,8 +73,8 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -121,9 +121,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 4f28460b83dd47a1446fc5c110025816611e2a54..1c3cde251b53cc8dfe476210411d22d61f24d473 100644 (file)
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -73,8 +73,8 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
index b10cc2e251cf7e38592ae26279b9613758a47b35..4717bd8313ee7201d7c3be79439d658a73440a37 100644 (file)
@@ -12,9 +12,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index a976a8aaf42b2fa03fd35d215de9cafee99b946c..02c4db7ec582426bf447783d347592c1884e9f89 100644 (file)
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -73,8 +73,8 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -121,9 +121,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index b4fd66477aa251c56da1e532d83b7d56ec79d25b..c7677c5951c3949ffa9789d68dff031f55247108 100644 (file)
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -73,8 +73,8 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -121,9 +121,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 0d31036e7f573d70f0ec3c00b67b61291a663671..5b3e34e2c0c6e063ff03e32c07ed0435e9650348 100644 (file)
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -73,8 +73,8 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -121,9 +121,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 18d47144231ab36e6f020071b1b8599dd2e76ace..d13f8a81280100a0ed18b547dfc7baf7bde0eb6e 100644 (file)
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -73,8 +73,8 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
index 29e00ab8af8503e6da3b15c55147da550d4919f7..caec5537de3f158166aae80a99f68f99c619eccc 100644 (file)
@@ -14,9 +14,9 @@ Content-Transfer-Encoding: 8bit
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
index 3572f20b5d4a1834d1c60ca05eca11efd8b46375..d3a6762130cdd168733e7f510e84b02778411aca 100644 (file)
@@ -12,9 +12,9 @@ Content-Type: text/plain; charset=UTF-8; format=fixed
 Content-Transfer-Encoding: 8bit
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 54cdcdab40f321559038c954fab572e532801b8b..244d964fc67d3f2cec759eabdc2f245a72549563 100644 (file)
@@ -10,10 +10,10 @@ A U Thor (2):
   Second
   Third
 
- dir/sub |    4 ++++
- file0   |    3 +++
- file1   |    3 +++
- file2   |    3 ---
+ dir/sub | 4 ++++
+ file0   | 3 +++
+ file1   | 3 +++
+ file2   | 3 ---
  4 files changed, 10 insertions(+), 3 deletions(-)
  create mode 100644 file1
  delete mode 100644 file2
@@ -28,9 +28,9 @@ Subject: [DIFFERENT_PREFIX 1/2] Second
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -73,8 +73,8 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
 Subject: [DIFFERENT_PREFIX 2/2] Third
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
index 23194ebdaa8defaca544a0f6d18d449a44fcd5f1..bfc287a147d25d37e5a0ae111e38182c1075778d 100644 (file)
@@ -6,9 +6,9 @@ Subject: [PATCH] Second
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -51,8 +51,8 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
 Subject: [PATCH] Third
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -85,9 +85,9 @@ Date: Mon, 26 Jun 2006 00:03:00 +0000
 Subject: [PATCH] Side
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 78f1a80a976ac54cc53602f203cae96f12be65dc..568f6f584e63a4b711fd341b02a359270ef62b87 100644 (file)
@@ -6,9 +6,9 @@ Subject: [PATCH 1/3] Second
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -51,8 +51,8 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
 Subject: [PATCH 2/3] Third
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -85,9 +85,9 @@ Date: Mon, 26 Jun 2006 00:03:00 +0000
 Subject: [PATCH 3/3] Side
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index a3dab7f773d71da1f1b1ade10b24cf9562ff7d1b..5f0352f9f70ceb2c40e2a24160c14887e4a6ca8f 100644 (file)
@@ -6,9 +6,9 @@ Subject: [PATCH 1/3] Second
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -51,8 +51,8 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
 Subject: [PATCH 2/3] Third
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -85,9 +85,9 @@ Date: Mon, 26 Jun 2006 00:03:00 +0000
 Subject: [PATCH 3/3] Side
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 39f4a3f2d1f50ce49792f4fcc6373d003ebf971b..2ae454d807370143aacf2b6655d80361cb975c6f 100644 (file)
@@ -6,9 +6,9 @@ Subject: [PATCH 1/2] Second
 
 This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -51,8 +51,8 @@ Date: Mon, 26 Jun 2006 00:02:00 +0000
 Subject: [PATCH 2/2] Third
 
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
index 88109209db005188c9e70fdd40b906756119dd69..a7d52fbeeaf15b8146e431c90387ccd55754db59 100644 (file)
@@ -5,9 +5,9 @@ Date: Mon, 26 Jun 2006 00:03:00 +0000
 Subject: [PATCH] Side
 
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index 4085bbde870d0810c69e7d78ef94052df385826c..a18f1472a9a4b5b098a2c342a9f77fc68a3b6dc0 100644 (file)
@@ -12,7 +12,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -31,7 +31,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -53,7 +53,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index 458627953e29c80b8bed0e640db1c580946a0194..ae425c4672200ad73cb2db228bd7385757673ee3 100644 (file)
@@ -12,9 +12,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -54,8 +54,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -86,9 +86,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
 
 diff --git a/dir/sub b/dir/sub
index 6e172cfadd04785269e0060fa55e86b3df117195..d5207cadf4483a7e63b6085a9007623c73b1a694 100644 (file)
@@ -12,7 +12,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -31,7 +31,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -53,7 +53,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index 48b0d4b91dfa427c1f0971bedbad61931e103bb7..0fc1e8cd71fee38125e77487da29c12c5863a054 100644 (file)
@@ -6,8 +6,8 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
     Merge branch 'side'
 
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
@@ -44,9 +44,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
@@ -87,8 +87,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -120,9 +120,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -162,9 +162,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
index f9dc5122e2a436a62fa80bdf820b4d44cacdfe8e..dffc09dde9e03179f48cea86e04aac34f1d30504 100644 (file)
@@ -12,9 +12,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
@@ -55,8 +55,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -88,9 +88,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -130,9 +130,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
index 0807ece2347ac54f87c860904ef257c766da5dd0..55aa98012dece9e96d1092f442b6235d420ca42f 100644 (file)
@@ -12,9 +12,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -54,8 +54,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -86,9 +86,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
 
 diff --git a/dir/sub b/dir/sub
@@ -127,9 +127,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index 84f5ef69119edf44e967fa09e3c6acd0bdc6b715..019d85f7de6c2d360ae2f9addd65bacd9bee318b 100644 (file)
@@ -6,8 +6,8 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
     Merge branch 'side'
 
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --combined dir/sub
@@ -44,9 +44,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
@@ -87,8 +87,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -120,9 +120,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -162,9 +162,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
index e60384d24de317e9f9d6dac222d8123ae4d68e02..95a474ef1d54889bb8fd599a34c2efe101697b6e 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
index a3a3255fd3f508ddd47a92cb47552dde3ab8156a..974e99be820e40c68f5ca290519375a8a14cb0c6 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index d16f464aca8f83264f53f0355bb3be84eddf1bd8..a71492f9bf4e96e2017a3343cff97038f7ee6610 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 $
index 6300c0535fff7e4a82fdfb6511392b006f5dfa0c..9be712458f4494f0b5f79030ccc9252fc9f1525c 100644 (file)
@@ -5,8 +5,8 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 $
index 16ae54345f6a1e93fae3ad7269829b32eaab3739..c8b6af2f4381571b5168bc78ab57cd5f5adac4d1 100644 (file)
@@ -5,7 +5,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -24,7 +24,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -46,7 +46,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index f3e45ec270ace186d58f40a21a9763598d6dc0ac..1ac431ba9212d3ba8147ab44a657293e5b9b2558 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -47,8 +47,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -79,9 +79,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
 
 diff --git a/dir/sub b/dir/sub
index c77f0bc320f0241550e9027f7ecb191d77f85b43..b30c28588f9ae44c9fcf2cf96feea9db949361e5 100644 (file)
@@ -5,7 +5,7 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -24,7 +24,7 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -46,7 +46,7 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
+ dir/sub | 2 ++
  1 file changed, 2 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index 8d03efea6ce33bb52018eeedc02629c6bd9253aa..30aae7817b952d1088c872453a6a38a681fcd946 100644 (file)
@@ -6,8 +6,8 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
     Merge branch 'side'
 
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --cc dir/sub
@@ -44,9 +44,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
@@ -87,8 +87,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -120,9 +120,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -162,9 +162,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
index 1874d0616c4d6cf308a6fcc0f59a0d552664777f..db90e5152547bc92c4ff158e044aa3d18c764783 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
@@ -48,8 +48,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -81,9 +81,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -123,9 +123,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
index 5211ff2a757b8db234ec1f4b14ae33300c21296a..9a6cc92ce7de88b564c1d95d611ba1ae632571af 100644 (file)
@@ -5,9 +5,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -47,8 +47,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
@@ -79,9 +79,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
 
 diff --git a/dir/sub b/dir/sub
@@ -120,9 +120,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
 
 diff --git a/dir/sub b/dir/sub
index ad30245a59b94ed18bf9e1bfca3eb0077cd3a14a..d1d32bd34c333411661355685368130faec79764 100644 (file)
@@ -6,8 +6,8 @@ Date:   Mon Jun 26 00:04:00 2006 +0000
 
     Merge branch 'side'
 
- dir/sub |    2 ++
- file0   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
  2 files changed, 5 insertions(+)
 
 diff --combined dir/sub
@@ -44,9 +44,9 @@ Date:   Mon Jun 26 00:03:00 2006 +0000
 
     Side
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file3   |    4 ++++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file3   | 4 ++++
  3 files changed, 9 insertions(+)
  create mode 100644 file3
 
@@ -87,8 +87,8 @@ Date:   Mon Jun 26 00:02:00 2006 +0000
 
     Third
 ---
- dir/sub |    2 ++
- file1   |    3 +++
+ dir/sub | 2 ++
+ file1   | 3 +++
  2 files changed, 5 insertions(+)
  create mode 100644 file1
 
@@ -120,9 +120,9 @@ Date:   Mon Jun 26 00:01:00 2006 +0000
     
     This is the second commit.
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 ---
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 
@@ -162,9 +162,9 @@ Date:   Mon Jun 26 00:00:00 2006 +0000
 
     Initial
 ---
- dir/sub |    2 ++
- file0   |    3 +++
- file2   |    3 +++
+ dir/sub | 2 ++
+ file0   | 3 +++
+ file2   | 3 +++
  3 files changed, 8 insertions(+)
  create mode 100644 dir/sub
  create mode 100644 file0
index 7dfe716cf9ed63f08f512cfa123d9bf93fa92839..b473b6d6ebbf75f77be99085c26b68781b7f5fc6 100755 (executable)
@@ -518,11 +518,6 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 '
 
 cat > expect << EOF
----
- file |   16 ++++++++++++++++
- 1 file changed, 16 insertions(+)
-
-diff --git a/file b/file
 index 40f36c6..2dc5c23 100644
 --- a/file
 +++ b/file
@@ -537,7 +532,9 @@ EOF
 test_expect_success 'format-patch respects -U' '
 
        git format-patch -U4 -2 &&
-       sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+       sed -e "1,/^diff/d" -e "/^+5/q" \
+               <0001-This-is-an-excessively-long-subject-line-for-a-messa.patch \
+               >output &&
        test_cmp expect output
 
 '
index ab0c2f0574f915296b885a21c6151a6947bdae7e..97b81778cb8c9f3c06b551b1f7031c944ced70d0 100755 (executable)
@@ -57,22 +57,33 @@ test_expect_success TABS_IN_FILENAMES 'git diff --summary -M HEAD' '
        test_cmp expect actual
 '
 
-test_expect_success TABS_IN_FILENAMES 'setup expected files' '
-cat >expect <<\EOF
- pathname.1 => "Rpathname\twith HT.0"            |    0
- pathname.3 => "Rpathname\nwith LF.0"            |    0
- "pathname\twith HT.3" => "Rpathname\nwith LF.1" |    0
- pathname.2 => Rpathname with SP.0               |    0
- "pathname\twith HT.2" => Rpathname with SP.1    |    0
- pathname.0 => Rpathname.0                       |    0
- "pathname\twith HT.0" => Rpathname.1            |    0
- 7 files changed, 0 insertions(+), 0 deletions(-)
-EOF
+test_expect_success TABS_IN_FILENAMES 'git diff --numstat -M HEAD' '
+       cat >expect <<-\EOF &&
+       0       0       pathname.1 => "Rpathname\twith HT.0"
+       0       0       pathname.3 => "Rpathname\nwith LF.0"
+       0       0       "pathname\twith HT.3" => "Rpathname\nwith LF.1"
+       0       0       pathname.2 => Rpathname with SP.0
+       0       0       "pathname\twith HT.2" => Rpathname with SP.1
+       0       0       pathname.0 => Rpathname.0
+       0       0       "pathname\twith HT.0" => Rpathname.1
+       EOF
+       git diff --numstat -M HEAD >actual &&
+       test_cmp expect actual
 '
 
 test_expect_success TABS_IN_FILENAMES 'git diff --stat -M HEAD' '
+       cat >expect <<-\EOF &&
+        pathname.1 => "Rpathname\twith HT.0"            | 0
+        pathname.3 => "Rpathname\nwith LF.0"            | 0
+        "pathname\twith HT.3" => "Rpathname\nwith LF.1" | 0
+        pathname.2 => Rpathname with SP.0               | 0
+        "pathname\twith HT.2" => Rpathname with SP.1    | 0
+        pathname.0 => Rpathname.0                       | 0
+        "pathname\twith HT.0" => Rpathname.1            | 0
+        7 files changed, 0 insertions(+), 0 deletions(-)
+       EOF
        git diff --stat -M HEAD >actual &&
-       test_cmp expect actual
+       test_i18ncmp expect actual
 '
 
 test_done
index 4ac162cfcf891e5fe0a36a3074db0d486ffa5cf0..d4ab4f2ccf9a1bbbc1e6af84df5f09c373f9f465 100755 (executable)
@@ -85,13 +85,17 @@ test_expect_success 'status -v produces text' '
 '
 
 cat >expect.stat <<'EOF'
- file |  Bin 2 -> 4 bytes
+ file | Bin 2 -> 4 bytes
  1 file changed, 0 insertions(+), 0 deletions(-)
 EOF
 test_expect_success 'diffstat does not run textconv' '
        echo file diff=fail >.gitattributes &&
        git diff --stat HEAD^ HEAD >actual &&
-       test_cmp expect.stat actual
+       test_i18ncmp expect.stat actual &&
+
+       head -n1 <expect.stat >expect.line1 &&
+       head -n1 <actual >actual.line1 &&
+       test_cmp expect.line1 actual.line1
 '
 # restore working setup
 echo file diff=foo >.gitattributes
index 7d7470f21b66a937e7414f4fe5419f8830fd8e86..c8296fa4fc1fbfe2645554d4818ae586d1cc2a14 100755 (executable)
@@ -44,10 +44,16 @@ test_expect_success 'rewrite diff can show binary patch' '
        grep "GIT binary patch" diff
 '
 
-test_expect_success 'rewrite diff --stat shows binary changes' '
+test_expect_success 'rewrite diff --numstat shows binary changes' '
+       git diff -B --numstat --summary >diff &&
+       grep -e "-      -       " diff &&
+       grep " rewrite file" diff
+'
+
+test_expect_success 'diff --stat counts binary rewrite as 0 lines' '
        git diff -B --stat --summary >diff &&
        grep "Bin" diff &&
-       grep "0 insertions.*0 deletions" diff &&
+       test_i18ngrep "0 insertions.*0 deletions" diff &&
        grep " rewrite file" diff
 '
 
index e747e842272df5935f863f78ccfc3b311f64228b..cdb9202f57ea75983cf8a25f892bbf868724250d 100755 (executable)
@@ -15,65 +15,65 @@ test_expect_success 'setup' '
 
 test_expect_success 'git diff-tree HEAD^ HEAD' '
        git diff-tree --quiet HEAD^ HEAD >cnt
-       test $? = 1 && test $(wc -l <cnt) = 0
+       test $? = 1 && test_line_count = 0 cnt
 '
 test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
        git diff-tree --quiet HEAD^ HEAD -- a >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
+       test $? = 0 && test_line_count = 0 cnt
 '
 test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
        git diff-tree --quiet HEAD^ HEAD -- b >cnt
-       test $? = 1 && test $(wc -l <cnt) = 0
+       test $? = 1 && test_line_count = 0 cnt
 '
 # this diff outputs one line: sha1 of the given head
 test_expect_success 'echo HEAD | git diff-tree --stdin' '
        echo $(git rev-parse HEAD) | git diff-tree --quiet --stdin >cnt
-       test $? = 1 && test $(wc -l <cnt) = 1
+       test $? = 1 && test_line_count = 1 cnt
 '
 test_expect_success 'git diff-tree HEAD HEAD' '
        git diff-tree --quiet HEAD HEAD >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
+       test $? = 0 && test_line_count = 0 cnt
 '
 test_expect_success 'git diff-files' '
        git diff-files --quiet >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
+       test $? = 0 && test_line_count = 0 cnt
 '
 test_expect_success 'git diff-index --cached HEAD' '
        git diff-index --quiet --cached HEAD >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
+       test $? = 0 && test_line_count = 0 cnt
 '
 test_expect_success 'git diff-index --cached HEAD^' '
        git diff-index --quiet --cached HEAD^ >cnt
-       test $? = 1 && test $(wc -l <cnt) = 0
+       test $? = 1 && test_line_count = 0 cnt
 '
 test_expect_success 'git diff-index --cached HEAD^' '
        echo text >>b &&
        echo 3 >c &&
        git add . && {
                git diff-index --quiet --cached HEAD^ >cnt
-               test $? = 1 && test $(wc -l <cnt) = 0
+               test $? = 1 && test_line_count = 0 cnt
        }
 '
 test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
        git commit -m "text in b" && {
                git diff-tree --quiet -Stext HEAD^ HEAD -- b >cnt
-               test $? = 1 && test $(wc -l <cnt) = 0
+               test $? = 1 && test_line_count = 0 cnt
        }
 '
 test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
        git diff-tree --quiet -Snot-found HEAD^ HEAD -- b >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
+       test $? = 0 && test_line_count = 0 cnt
 '
 test_expect_success 'git diff-files' '
        echo 3 >>c && {
                git diff-files --quiet >cnt
-               test $? = 1 && test $(wc -l <cnt) = 0
+               test $? = 1 && test_line_count = 0 cnt
        }
 '
 test_expect_success 'git diff-index --cached HEAD' '
        git update-index c && {
                git diff-index --quiet --cached HEAD >cnt
-               test $? = 1 && test $(wc -l <cnt) = 0
+               test $? = 1 && test_line_count = 0 cnt
        }
 '
 
index 06012811a1abf2e7d9c766e84b32dae8a95a7321..2a2cf91352037b6f2c238237474aa1d78928f5ad 100755 (executable)
@@ -23,9 +23,8 @@ test_expect_success 'move the files into a "sub" directory' '
 '
 
 cat > expected <<\EOF
- bar => sub/bar |  Bin 5 -> 5 bytes
- foo => sub/foo |    0
- 2 files changed, 0 insertions(+), 0 deletions(-)
+-      -       bar => sub/bar
+0      0       foo => sub/foo
 
 diff --git a/bar b/sub/bar
 similarity index 100%
@@ -38,7 +37,8 @@ rename to sub/foo
 EOF
 
 test_expect_success 'git show -C -C report renames' '
-       git show -C -C --raw --binary --stat | tail -n 12 > current &&
+       git show -C -C --raw --binary --numstat >patch-with-stat &&
+       tail -n 11 patch-with-stat >current &&
        test_cmp expected current
 '
 
index bd119be106f1a2d6d91f982a1bf8afcbf6b8c831..3950f5034d319be99695c9332b22a8d26e20ff32 100755 (executable)
@@ -29,15 +29,27 @@ test_expect_success "-p $*" "
 "
 }
 
+check_numstat() {
+expect=$1; shift
+cat >expected <<EOF
+1      0       $expect
+EOF
+test_expect_success "--numstat $*" "
+       echo '1 0       $expect' >expected &&
+       git diff --numstat $* HEAD^ >actual &&
+       test_cmp expected actual
+"
+}
+
 check_stat() {
 expect=$1; shift
 cat >expected <<EOF
- $expect |    1 +
+ $expect | 1 +
  1 file changed, 1 insertion(+)
 EOF
 test_expect_success "--stat $*" "
        git diff --stat $* HEAD^ >actual &&
-       test_cmp expected actual
+       test_i18ncmp expected actual
 "
 }
 
@@ -52,7 +64,7 @@ test_expect_success "--raw $*" "
 "
 }
 
-for type in diff stat raw; do
+for type in diff numstat stat raw; do
        check_$type file2 --relative=subdir/
        check_$type file2 --relative=subdir
        check_$type dir/file2 --relative=sub
index 29e80a58cdcf43077bcc5bf42834aa8b4daad93d..ed7e093366bcbdaa177bac4294a07fc52d4233ed 100755 (executable)
@@ -252,50 +252,47 @@ EOF
 '
 
 cat <<EOF >expect_diff_stat
- changed/text             |    2 +-
- dst/copy/changed/text    |   10 ++++++++++
- dst/copy/rearranged/text |   10 ++++++++++
- dst/copy/unchanged/text  |   10 ++++++++++
- dst/move/changed/text    |   10 ++++++++++
- dst/move/rearranged/text |   10 ++++++++++
- dst/move/unchanged/text  |   10 ++++++++++
- rearranged/text          |    2 +-
- src/move/changed/text    |   10 ----------
- src/move/rearranged/text |   10 ----------
- src/move/unchanged/text  |   10 ----------
- 11 files changed, 62 insertions(+), 32 deletions(-)
+1      1       changed/text
+10     0       dst/copy/changed/text
+10     0       dst/copy/rearranged/text
+10     0       dst/copy/unchanged/text
+10     0       dst/move/changed/text
+10     0       dst/move/rearranged/text
+10     0       dst/move/unchanged/text
+1      1       rearranged/text
+0      10      src/move/changed/text
+0      10      src/move/rearranged/text
+0      10      src/move/unchanged/text
 EOF
 
 cat <<EOF >expect_diff_stat_M
- changed/text                      |    2 +-
- dst/copy/changed/text             |   10 ++++++++++
- dst/copy/rearranged/text          |   10 ++++++++++
- dst/copy/unchanged/text           |   10 ++++++++++
- {src => dst}/move/changed/text    |    2 +-
- {src => dst}/move/rearranged/text |    2 +-
- {src => dst}/move/unchanged/text  |    0
- rearranged/text                   |    2 +-
- 8 files changed, 34 insertions(+), 4 deletions(-)
+1      1       changed/text
+10     0       dst/copy/changed/text
+10     0       dst/copy/rearranged/text
+10     0       dst/copy/unchanged/text
+1      1       {src => dst}/move/changed/text
+1      1       {src => dst}/move/rearranged/text
+0      0       {src => dst}/move/unchanged/text
+1      1       rearranged/text
 EOF
 
 cat <<EOF >expect_diff_stat_CC
- changed/text                      |    2 +-
- {src => dst}/copy/changed/text    |    2 +-
- {src => dst}/copy/rearranged/text |    2 +-
- {src => dst}/copy/unchanged/text  |    0
- {src => dst}/move/changed/text    |    2 +-
- {src => dst}/move/rearranged/text |    2 +-
- {src => dst}/move/unchanged/text  |    0
- rearranged/text                   |    2 +-
- 8 files changed, 6 insertions(+), 6 deletions(-)
-EOF
-
-test_expect_success 'sanity check setup (--stat)' '
-       git diff --stat HEAD^..HEAD >actual_diff_stat &&
+1      1       changed/text
+1      1       {src => dst}/copy/changed/text
+1      1       {src => dst}/copy/rearranged/text
+0      0       {src => dst}/copy/unchanged/text
+1      1       {src => dst}/move/changed/text
+1      1       {src => dst}/move/rearranged/text
+0      0       {src => dst}/move/unchanged/text
+1      1       rearranged/text
+EOF
+
+test_expect_success 'sanity check setup (--numstat)' '
+       git diff --numstat HEAD^..HEAD >actual_diff_stat &&
        test_cmp expect_diff_stat actual_diff_stat &&
-       git diff --stat -M HEAD^..HEAD >actual_diff_stat_M &&
+       git diff --numstat -M HEAD^..HEAD >actual_diff_stat_M &&
        test_cmp expect_diff_stat_M actual_diff_stat_M &&
-       git diff --stat -C -C HEAD^..HEAD >actual_diff_stat_CC &&
+       git diff --numstat -C -C HEAD^..HEAD >actual_diff_stat_CC &&
        test_cmp expect_diff_stat_CC actual_diff_stat_CC
 '
 
index a6d1887536e240e89b8e2263e5f0a643e9a55f71..b41eb61ca8b1e66f618d480a28b2bc14815df2f7 100755 (executable)
@@ -14,12 +14,12 @@ test_expect_success setup '
        echo a >a &&
        echo b >b &&
        cat >expect <<-\EOF
-        a |    1 +
-        b |    1 +
+        a | 1 +
+        b | 1 +
         2 files changed, 2 insertions(+)
        EOF
        git diff --stat --stat-count=2 >actual &&
-       test_cmp expect actual
+       test_i18ncmp expect actual
 '
 
 test_done
index 328aa8f39865fb90d4b54991f158affa9cf2e1dc..b68afefa3ce2bf8ad810cee2d6ab108d4f1219ba 100755 (executable)
@@ -22,7 +22,7 @@ test_expect_success 'preparation' '
 while read cmd args
 do
        cat >expect <<-'EOF'
-        ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |    1 +
+        ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
        EOF
        test_expect_success "$cmd: small change with long name gives more space to the name" '
                git $cmd $args >output &&
@@ -31,7 +31,7 @@ do
        '
 
        cat >expect <<-'EOF'
-        ...aaaaaaaaaaaaaaaaaaaaaaaaaa |    1 +
+        ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
        EOF
        test_expect_success "$cmd --stat=width: a long name is given more room when the bar is short" '
                git $cmd $args --stat=40 >output &&
@@ -46,7 +46,7 @@ do
        '
 
        cat >expect <<-'EOF'
-        ...aaaaaaaaaaaaaaaaaaaaaaaaaaa |    1 +
+        ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
        EOF
        test_expect_success "$cmd --stat=...,name-width with long name" '
                git $cmd $args --stat=60,30 >output &&
@@ -82,11 +82,15 @@ test_expect_success 'preparation for big change tests' '
 cat >expect80 <<'EOF'
  abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 EOF
-
+cat >expect80-graph <<'EOF'
+|  abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
 cat >expect200 <<'EOF'
  abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 EOF
-
+cat >expect200-graph <<'EOF'
+|  abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
 while read verb expect cmd args
 do
        test_expect_success "$cmd $verb COLUMNS (big change)" '
@@ -94,6 +98,14 @@ do
                grep " | " output >actual &&
                test_cmp "$expect" actual
        '
+
+       test "$cmd" != diff || continue
+
+       test_expect_success "$cmd --graph $verb COLUMNS (big change)" '
+               COLUMNS=200 git $cmd $args --graph >output
+               grep " | " output >actual &&
+               test_cmp "$expect-graph" actual
+       '
 done <<\EOF
 ignores expect80 format-patch -1 --stdout
 respects expect200 diff HEAD^ HEAD --stat
@@ -104,7 +116,9 @@ EOF
 cat >expect40 <<'EOF'
  abcd | 1000 ++++++++++++++++++++++++++
 EOF
-
+cat >expect40-graph <<'EOF'
+|  abcd | 1000 ++++++++++++++++++++++++
+EOF
 while read verb expect cmd args
 do
        test_expect_success "$cmd $verb not enough COLUMNS (big change)" '
@@ -113,11 +127,41 @@ do
                test_cmp "$expect" actual
        '
 
+       test "$cmd" != diff || continue
+
+       test_expect_success "$cmd --graph $verb not enough COLUMNS (big change)" '
+               COLUMNS=40 git $cmd $args --graph >output
+               grep " | " output >actual &&
+               test_cmp "$expect-graph" actual
+       '
+done <<\EOF
+ignores expect80 format-patch -1 --stdout
+respects expect40 diff HEAD^ HEAD --stat
+respects expect40 show --stat
+respects expect40 log -1 --stat
+EOF
+
+cat >expect40 <<'EOF'
+ abcd | 1000 ++++++++++++++++++++++++++
+EOF
+cat >expect40-graph <<'EOF'
+|  abcd | 1000 ++++++++++++++++++++++++++
+EOF
+while read verb expect cmd args
+do
        test_expect_success "$cmd $verb statGraphWidth config" '
                git -c diff.statGraphWidth=26 $cmd $args >output
                grep " | " output >actual &&
                test_cmp "$expect" actual
        '
+
+       test "$cmd" != diff || continue
+
+       test_expect_success "$cmd --graph $verb statGraphWidth config" '
+               git -c diff.statGraphWidth=26 $cmd $args --graph >output
+               grep " | " output >actual &&
+               test_cmp "$expect-graph" actual
+       '
 done <<\EOF
 ignores expect80 format-patch -1 --stdout
 respects expect40 diff HEAD^ HEAD --stat
@@ -129,6 +173,9 @@ EOF
 cat >expect <<'EOF'
  abcd | 1000 ++++++++++++++++++++++++++
 EOF
+cat >expect-graph <<'EOF'
+|  abcd | 1000 ++++++++++++++++++++++++++
+EOF
 while read cmd args
 do
        test_expect_success "$cmd --stat=width with big change" '
@@ -143,11 +190,25 @@ do
                test_cmp expect actual
        '
 
-       test_expect_success "$cmd --stat-graph--width with big change" '
+       test_expect_success "$cmd --stat-graph-width with big change" '
                git $cmd $args --stat-graph-width=26 >output
                grep " | " output >actual &&
                test_cmp expect actual
        '
+
+       test "$cmd" != diff || continue
+
+       test_expect_success "$cmd --stat-width=width --graph with big change" '
+               git $cmd $args --stat-width=40 --graph >output
+               grep " | " output >actual &&
+               test_cmp expect-graph actual
+       '
+
+       test_expect_success "$cmd --stat-graph-width --graph with big change" '
+               git $cmd $args --stat-graph-width=26 --graph >output
+               grep " | " output >actual &&
+               test_cmp expect-graph actual
+       '
 done <<\EOF
 format-patch -1 --stdout
 diff HEAD^ HEAD --stat
@@ -164,6 +225,9 @@ test_expect_success 'preparation for long filename tests' '
 cat >expect <<'EOF'
  ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++
 EOF
+cat >expect-graph <<'EOF'
+|  ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++
+EOF
 while read cmd args
 do
        test_expect_success "$cmd --stat=width with big change is more balanced" '
@@ -171,6 +235,14 @@ do
                grep " | " output >actual &&
                test_cmp expect actual
        '
+
+       test "$cmd" != diff || continue
+
+       test_expect_success "$cmd --stat=width --graph with big change is balanced" '
+               git $cmd $args --stat-width=60 --graph >output &&
+               grep " | " output >actual &&
+               test_cmp expect-graph actual
+       '
 done <<\EOF
 format-patch -1 --stdout
 diff HEAD^ HEAD --stat
@@ -181,9 +253,15 @@ EOF
 cat >expect80 <<'EOF'
  ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++
 EOF
+cat >expect80-graph <<'EOF'
+|  ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++
+EOF
 cat >expect200 <<'EOF'
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 EOF
+cat >expect200-graph <<'EOF'
+|  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+EOF
 while read verb expect cmd args
 do
        test_expect_success "$cmd $verb COLUMNS (long filename)" '
@@ -191,6 +269,14 @@ do
                grep " | " output >actual &&
                test_cmp "$expect" actual
        '
+
+       test "$cmd" != diff || continue
+
+       test_expect_success "$cmd --graph $verb COLUMNS (long filename)" '
+               COLUMNS=200 git $cmd $args --graph >output
+               grep " | " output >actual &&
+               test_cmp "$expect-graph" actual
+       '
 done <<\EOF
 ignores expect80 format-patch -1 --stdout
 respects expect200 diff HEAD^ HEAD --stat
@@ -198,6 +284,36 @@ respects expect200 show --stat
 respects expect200 log -1 --stat
 EOF
 
+cat >expect1 <<'EOF'
+ ...aaaaaaa | 1000 ++++++
+EOF
+cat >expect1-graph <<'EOF'
+|  ...aaaaaaa | 1000 ++++++
+EOF
+while read verb expect cmd args
+do
+       test_expect_success COLUMNS_CAN_BE_1 \
+               "$cmd $verb prefix greater than COLUMNS (big change)" '
+               COLUMNS=1 git $cmd $args >output
+               grep " | " output >actual &&
+               test_cmp "$expect" actual
+       '
+
+       test "$cmd" != diff || continue
+
+       test_expect_success COLUMNS_CAN_BE_1 \
+               "$cmd --graph $verb prefix greater than COLUMNS (big change)" '
+               COLUMNS=1 git $cmd $args --graph >output
+               grep " | " output >actual &&
+               test_cmp "$expect-graph" actual
+       '
+done <<\EOF
+ignores expect80 format-patch -1 --stdout
+respects expect1 diff HEAD^ HEAD --stat
+respects expect1 show --stat
+respects expect1 log -1 --stat
+EOF
+
 cat >expect <<'EOF'
  abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 EOF
index 9b433de83630774206fb89dfae1a4396264390cc..744b8e51beab59c78807e0622f10bf421b3142ec 100755 (executable)
@@ -17,13 +17,13 @@ do
        test_expect_success "$title" '
                git apply --stat --summary \
                        <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" >current &&
-               test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
+               test_i18ncmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
        '
 
        test_expect_success "$title with recount" '
                sed -e "$UNC" <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" |
                git apply --recount --stat --summary >current &&
-               test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
+               test_i18ncmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current
        '
 done <<\EOF
 rename
index ccc0280f52232e845f9d7c7bd5c3326b412d4a96..cdafd7e7c1e6c73a97c36a60f77810badff603f2 100755 (executable)
@@ -525,9 +525,9 @@ test_expect_success 'am empty-file does not infloop' '
        git reset --hard &&
        touch empty-file &&
        test_tick &&
-       { git am empty-file > actual 2>&1 && false || :; } &&
+       test_must_fail git am empty-file 2>actual &&
        echo Patch format detection failed. >expected &&
-       test_cmp expected actual
+       test_i18ncmp expected actual
 '
 
 test_done
index 32cf0bd218ea081e3e1d73400943b30a946f9dd5..71be59d446f773c4b567f00c469ac26225d11cb8 100755 (executable)
@@ -528,7 +528,7 @@ cat >expect <<\EOF
 | |
 | |     reach
 | | ---
-| |  reach.t |    1 +
+| |  reach.t | 1 +
 | |  1 file changed, 1 insertion(+)
 | |
 | | diff --git a/reach.t b/reach.t
@@ -551,7 +551,7 @@ cat >expect <<\EOF
 | | |
 | | |       octopus-b
 | | |   ---
-| | |    octopus-b.t |    1 +
+| | |    octopus-b.t | 1 +
 | | |    1 file changed, 1 insertion(+)
 | | |
 | | |   diff --git a/octopus-b.t b/octopus-b.t
@@ -567,7 +567,7 @@ cat >expect <<\EOF
 | |
 | |       octopus-a
 | |   ---
-| |    octopus-a.t |    1 +
+| |    octopus-a.t | 1 +
 | |    1 file changed, 1 insertion(+)
 | |
 | |   diff --git a/octopus-a.t b/octopus-a.t
@@ -583,7 +583,7 @@ cat >expect <<\EOF
 |
 |       seventh
 |   ---
-|    seventh.t |    1 +
+|    seventh.t | 1 +
 |    1 file changed, 1 insertion(+)
 |
 |   diff --git a/seventh.t b/seventh.t
@@ -617,7 +617,7 @@ cat >expect <<\EOF
 | | | |
 | | | |     tangle-a
 | | | | ---
-| | | |  tangle-a |    1 +
+| | | |  tangle-a | 1 +
 | | | |  1 file changed, 1 insertion(+)
 | | | |
 | | | | diff --git a/tangle-a b/tangle-a
@@ -639,7 +639,7 @@ cat >expect <<\EOF
 | |/| |
 | | | |       side-2
 | | | |   ---
-| | | |    2 |    1 +
+| | | |    2 | 1 +
 | | | |    1 file changed, 1 insertion(+)
 | | | |
 | | | |   diff --git a/2 b/2
@@ -655,7 +655,7 @@ cat >expect <<\EOF
 | | | |
 | | | |     side-1
 | | | | ---
-| | | |  1 |    1 +
+| | | |  1 | 1 +
 | | | |  1 file changed, 1 insertion(+)
 | | | |
 | | | | diff --git a/1 b/1
@@ -671,7 +671,7 @@ cat >expect <<\EOF
 | | | |
 | | | |     Second
 | | | | ---
-| | | |  one |    1 +
+| | | |  one | 1 +
 | | | |  1 file changed, 1 insertion(+)
 | | | |
 | | | | diff --git a/one b/one
@@ -687,7 +687,7 @@ cat >expect <<\EOF
 |/| |
 | | |       sixth
 | | |   ---
-| | |    a/two |    1 -
+| | |    a/two | 1 -
 | | |    1 file changed, 1 deletion(-)
 | | |
 | | |   diff --git a/a/two b/a/two
@@ -703,7 +703,7 @@ cat >expect <<\EOF
 | | |
 | | |     fifth
 | | | ---
-| | |  a/two |    1 +
+| | |  a/two | 1 +
 | | |  1 file changed, 1 insertion(+)
 | | |
 | | | diff --git a/a/two b/a/two
@@ -719,7 +719,7 @@ cat >expect <<\EOF
 | |
 | |       fourth
 | |   ---
-| |    ein |    1 +
+| |    ein | 1 +
 | |    1 file changed, 1 insertion(+)
 | |
 | |   diff --git a/ein b/ein
@@ -735,8 +735,8 @@ cat >expect <<\EOF
 |
 |       third
 |   ---
-|    ichi |    1 +
-|    one  |    1 -
+|    ichi | 1 +
+|    one  | 1 -
 |    2 files changed, 1 insertion(+), 1 deletion(-)
 |
 |   diff --git a/ichi b/ichi
@@ -759,7 +759,7 @@ cat >expect <<\EOF
 |
 |     second
 | ---
-|  one |    2 +-
+|  one | 2 +-
 |  1 file changed, 1 insertion(+), 1 deletion(-)
 |
 | diff --git a/one b/one
@@ -775,7 +775,7 @@ cat >expect <<\EOF
 
       initial
   ---
-   one |    1 +
+   one | 1 +
    1 file changed, 1 insertion(+)
 
   diff --git a/one b/one
index 2ae9faa8b37821db6e7c28ae3d98e53cb25264b1..4afd77815f6bf698d4b865bad2300706d2a8e0fc 100755 (executable)
@@ -71,4 +71,32 @@ test_expect_success 'alias loop' '
        test_must_fail git log --pretty=test-foo
 '
 
+test_expect_success 'NUL separation' '
+       printf "add bar\0initial" >expected &&
+       git log -z --pretty="format:%s" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'NUL termination' '
+       printf "add bar\0initial\0" >expected &&
+       git log -z --pretty="tformat:%s" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'NUL separation with --stat' '
+       stat0_part=$(git diff --stat HEAD^ HEAD) &&
+       stat1_part=$(git diff --stat --root HEAD^) &&
+       printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n" >expected &&
+       git log -z --stat --pretty="format:%s" >actual &&
+       test_cmp expected actual
+'
+
+test_expect_failure 'NUL termination with --stat' '
+       stat0_part=$(git diff --stat HEAD^ HEAD) &&
+       stat1_part=$(git diff --stat --root HEAD^) &&
+       printf "add bar\n$stat0_part\n\0initial\n$stat1_part\n\0" >expected &&
+       git log -z --stat --pretty="tformat:%s" >actual &&
+       test_cmp expected actual
+'
+
 test_done
index ebc36c1758372f484055b62080d3ce81ae7c69b4..81904d9ec8d341399b534c5d576262439d0e1ad6 100755 (executable)
@@ -65,7 +65,7 @@ test_expect_success 'respect NULs' '
        git mailsplit -d3 -o. "$TEST_DIRECTORY"/t5100/nul-plain &&
        test_cmp "$TEST_DIRECTORY"/t5100/nul-plain 001 &&
        (cat 001 | git mailinfo msg patch) &&
-       test 4 = $(wc -l < patch)
+       test_line_count = 4 patch
 
 '
 
index 8ce155167d51de1868fdd2395c312c46b9cc2c63..02c97746d64fbfe13007a1ab4e9b9e4bbd99f42f 100644 (file)
@@ -1,5 +1,5 @@
 ---
- foo |    2 +-
+ foo | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/foo b/foo
index 8ce155167d51de1868fdd2395c312c46b9cc2c63..02c97746d64fbfe13007a1ab4e9b9e4bbd99f42f 100644 (file)
@@ -1,5 +1,5 @@
 ---
- foo |    2 +-
+ foo | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/foo b/foo
index 8ce155167d51de1868fdd2395c312c46b9cc2c63..02c97746d64fbfe13007a1ab4e9b9e4bbd99f42f 100644 (file)
@@ -1,5 +1,5 @@
 ---
- foo |    2 +-
+ foo | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/foo b/foo
index 7d24b24af83c861a1dc615c19cbaa392fdd69dec..ab7a38373bd857271c4d74d52e8dbda57a964ff3 100644 (file)
@@ -1,7 +1,7 @@
 ---
 
- Documentation/git-cvsimport-script.txt |    9 ++++++++-
- git-cvsimport-script                   |    4 ++--
+ Documentation/git-cvsimport-script.txt | 9 ++++++++-
+ git-cvsimport-script                   | 4 ++--
  2 files changed, 10 insertions(+), 3 deletions(-)
 
 50452f9c0c2df1f04d83a26266ba704b13861632
index 8ce155167d51de1868fdd2395c312c46b9cc2c63..02c97746d64fbfe13007a1ab4e9b9e4bbd99f42f 100644 (file)
@@ -1,5 +1,5 @@
 ---
- foo |    2 +-
+ foo | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/foo b/foo
index f055481d567212b7b43e7ca8251351b3f83b143b..436821c97aa52d7ab599dc43315a0379de32cdb6 100644 (file)
@@ -1,5 +1,5 @@
 ---
- builtin-mailinfo.c |    2 +-
+ builtin-mailinfo.c | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 8841d3c13916a85dec9e14762eb1406735f4744e..0988713761d4a6adc1039f8f4edbe77ea58d5ea4 100644 (file)
@@ -1,5 +1,5 @@
 ---
- builtin-mailinfo.c  |    4 ++--
+ builtin-mailinfo.c  | 4 ++--
 
 diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
 index 3e5fe51..aabfe5c 100644
index 124efd234f905c4981718ee7574e213316a31acb..3f3825f9f297983f5270f09971ecef00cac7ae15 100644 (file)
@@ -1,5 +1,5 @@
 ---
- builtin-mailinfo.c |   37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
  1 files changed, 36 insertions(+), 1 deletions(-)
 
 diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 124efd234f905c4981718ee7574e213316a31acb..3f3825f9f297983f5270f09971ecef00cac7ae15 100644 (file)
@@ -1,5 +1,5 @@
 ---
- builtin-mailinfo.c |   37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
  1 files changed, 36 insertions(+), 1 deletions(-)
 
 diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index de1031241d99a66db0653875ea163785691d12d6..34a09a0fc1c0ca9d1e68ed8bbf56e592c54e7227 100644 (file)
@@ -12,7 +12,7 @@ Subject: [PATCH] a commit.
 Here is a patch from A U Thor.
 
 ---
- foo |    2 +-
+ foo | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/foo b/foo
@@ -52,7 +52,7 @@ two truly blank and another full of spaces in between.
 Hope this helps.
 
 ---
- foo |    2 +-
+ foo | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/foo b/foo
@@ -83,7 +83,7 @@ Message-Id: <nitpicker.12121212@example.net>
 Hopefully this would fix the problem stated there.
 
 ---
- foo |    2 +-
+ foo | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/foo b/foo
@@ -249,8 +249,8 @@ actual flags.
 Signed-off-by: David K=E5gedal <davidk@lysator.liu.se>
 ---
 
- Documentation/git-cvsimport-script.txt |    9 ++++++++-
- git-cvsimport-script                   |    4 ++--
+ Documentation/git-cvsimport-script.txt | 9 ++++++++-
+ git-cvsimport-script                   | 4 ++--
  2 files changed, 10 insertions(+), 3 deletions(-)
 
 50452f9c0c2df1f04d83a26266ba704b13861632
@@ -379,7 +379,7 @@ Subject: [PATCH] a commit.
 Here is a patch from A U Thor.
 
 ---
- foo |    2 +-
+ foo | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/foo b/foo
@@ -449,7 +449,7 @@ memcmp("Subject: ", header[i], 7) will never match.
 Signed-off-by: Lukas Sandström <lukass@etek.chalmers.se>
 Signed-off-by: Junio C Hamano <gitster@pobox.com>
 ---
- builtin-mailinfo.c |    2 +-
+ builtin-mailinfo.c | 2 +-
  1 files changed, 1 insertions(+), 1 deletions(-)
 
 diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
@@ -482,7 +482,7 @@ Content-Transfer-Encoding: quoted-printable
 Here comes a commit log message, and
 its second line is here.
 ---
- builtin-mailinfo.c  |    4 ++--
+ builtin-mailinfo.c  | 4 ++--
 
 diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
 index 3e5fe51..aabfe5c 100644
@@ -587,7 +587,7 @@ everything before it in the message body.
 
 Signed-off-by: Junio C Hamano <gitster@pobox.com>
 ---
- builtin-mailinfo.c |   37 ++++++++++++++++++++++++++++++++++++-
+ builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++-
  1 files changed, 36 insertions(+), 1 deletions(-)
 
 diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 2af8947eebb3e9ee45f83acb398335ec163a521c..432f98c357601057cb89f9dd6bfbe1ab02e9477a 100755 (executable)
@@ -216,7 +216,7 @@ test_expect_success 'pull request format' '
                git request-pull initial "$downstream_url" >../request
        ) &&
        <request sed -nf fuzz.sed >request.fuzzy &&
-       test_cmp expect request.fuzzy
+       test_i18ncmp expect request.fuzzy
 
 '
 
index c334c51a0740966090abb6f55fb90914734ff787..4736da8f36fd4e69fea0008829f4304020ea338d 100755 (executable)
@@ -13,16 +13,36 @@ test_expect_success 'setup bare remotes' '
        git push parent2 HEAD
 '
 
+# $1 = local revision
+# $2 = remote revision (tested to be equal to the local one)
+check_pushed_commit () {
+       git log -1 --format='%h %s' "$1" >expect &&
+       git --git-dir=repo1 log -1 --format='%h %s' "$2" >actual &&
+       test_cmp expect actual
+}
+
+# $1 = push.default value
+# $2 = expected target branch for the push
+test_push_success () {
+       git -c push.default="$1" push &&
+       check_pushed_commit HEAD "$2"
+}
+
+# $1 = push.default value
+# check that push fails and does not modify any remote branch
+test_push_failure () {
+       git --git-dir=repo1 log --no-walk --format='%h %s' --all >expect &&
+       test_must_fail git -c push.default="$1" push &&
+       git --git-dir=repo1 log --no-walk --format='%h %s' --all >actual &&
+       test_cmp expect actual
+}
+
 test_expect_success '"upstream" pushes to configured upstream' '
        git checkout master &&
        test_config branch.master.remote parent1 &&
        test_config branch.master.merge refs/heads/foo &&
-       test_config push.default upstream &&
        test_commit two &&
-       git push &&
-       echo two >expect &&
-       git --git-dir=repo1 log -1 --format=%s foo >actual &&
-       test_cmp expect actual
+       test_push_success upstream foo
 '
 
 test_expect_success '"upstream" does not push on unconfigured remote' '
@@ -30,7 +50,7 @@ test_expect_success '"upstream" does not push on unconfigured remote' '
        test_unconfig branch.master.remote &&
        test_config push.default upstream &&
        test_commit three &&
-       test_must_fail git push
+       test_push_failure upstream
 '
 
 test_expect_success '"upstream" does not push on unconfigured branch' '
@@ -39,7 +59,7 @@ test_expect_success '"upstream" does not push on unconfigured branch' '
        test_unconfig branch.master.merge &&
        test_config push.default upstream
        test_commit four &&
-       test_must_fail git push
+       test_push_failure upstream
 '
 
 test_expect_success '"upstream" does not push when remotes do not match' '
@@ -51,4 +71,48 @@ test_expect_success '"upstream" does not push when remotes do not match' '
        test_must_fail git push parent2
 '
 
+test_expect_success 'push from/to new branch with upstream, matching and simple' '
+       git checkout -b new-branch &&
+       test_push_failure simple &&
+       test_push_failure matching &&
+       test_push_failure upstream
+'
+
+test_expect_success 'push from/to new branch with current creates remote branch' '
+       test_config branch.new-branch.remote repo1 &&
+       git checkout new-branch &&
+       test_push_success current new-branch
+'
+
+test_expect_success 'push to existing branch, with no upstream configured' '
+       test_config branch.master.remote repo1 &&
+       git checkout master &&
+       test_push_failure simple &&
+       test_push_failure upstream
+'
+
+test_expect_success 'push to existing branch, upstream configured with same name' '
+       test_config branch.master.remote repo1 &&
+       test_config branch.master.merge refs/heads/master &&
+       git checkout master &&
+       test_commit six &&
+       test_push_success upstream master &&
+       test_commit seven &&
+       test_push_success simple master
+'
+
+test_expect_success 'push to existing branch, upstream configured with different name' '
+       test_config branch.master.remote repo1 &&
+       test_config branch.master.merge refs/heads/other-name &&
+       git checkout master &&
+       test_commit eight &&
+       test_push_success upstream other-name &&
+       test_commit nine &&
+       test_push_failure simple &&
+       git --git-dir=repo1 log -1 --format="%h %s" "other-name" >expect-other-name &&
+       test_push_success current master &&
+       git --git-dir=repo1 log -1 --format="%h %s" "other-name" >actual-other-name &&
+       test_cmp expect-other-name actual-other-name
+'
+
 test_done
index 30bec4b5f9f286cd97c57b70e3635c6c5c8cf85a..1947c28c6466d46c79c7b1b093488c1620324172 100755 (executable)
@@ -119,4 +119,98 @@ test_expect_success 'push succeeds if submodule has no remote and is on the firs
        )
 '
 
+test_expect_success 'push unpushed submodules when not needed' '
+       (
+               cd work &&
+               (
+                       cd gar/bage &&
+                       git checkout master &&
+                       >junk5 &&
+                       git add junk5 &&
+                       git commit -m "Fifth junk" &&
+                       git push &&
+                       git rev-parse origin/master >../../../expected
+               ) &&
+               git checkout master &&
+               git add gar/bage &&
+               git commit -m "Fifth commit for gar/bage" &&
+               git push --recurse-submodules=on-demand ../pub.git master
+       ) &&
+       (
+               cd submodule.git &&
+               git rev-parse master >../actual
+       ) &&
+       test_cmp expected actual
+'
+
+test_expect_success 'push unpushed submodules when not needed 2' '
+       (
+               cd submodule.git &&
+               git rev-parse master >../expected
+       ) &&
+       (
+               cd work &&
+               (
+                       cd gar/bage &&
+                       >junk6 &&
+                       git add junk6 &&
+                       git commit -m "Sixth junk"
+               ) &&
+               >junk2 &&
+               git add junk2 &&
+               git commit -m "Second junk for work" &&
+               git push --recurse-submodules=on-demand ../pub.git master
+       ) &&
+       (
+               cd submodule.git &&
+               git rev-parse master >../actual
+       ) &&
+       test_cmp expected actual
+'
+
+test_expect_success 'push unpushed submodules recursively' '
+       (
+               cd work &&
+               (
+                       cd gar/bage &&
+                       git checkout master &&
+                       > junk7 &&
+                       git add junk7 &&
+                       git commit -m "Seventh junk" &&
+                       git rev-parse master >../../../expected
+               ) &&
+               git checkout master &&
+               git add gar/bage &&
+               git commit -m "Seventh commit for gar/bage" &&
+               git push --recurse-submodules=on-demand ../pub.git master
+       ) &&
+       (
+               cd submodule.git &&
+               git rev-parse master >../actual
+       ) &&
+       test_cmp expected actual
+'
+
+test_expect_success 'push unpushable submodule recursively fails' '
+       (
+               cd work &&
+               (
+                       cd gar/bage &&
+                       git rev-parse origin/master >../../../expected &&
+                       git checkout master~0 &&
+                       > junk8 &&
+                       git add junk8 &&
+                       git commit -m "Eighth junk"
+               ) &&
+               git add gar/bage &&
+               git commit -m "Eighth commit for gar/bage" &&
+               test_must_fail git push --recurse-submodules=on-demand ../pub.git master
+       ) &&
+       (
+               cd submodule.git &&
+               git rev-parse master >../actual
+       ) &&
+       test_cmp expected actual
+'
+
 test_done
index cc6f081711002b42bcf6b2cb26287dcc56852a06..312e484090d1ceb392399768f2229ae553182fe7 100755 (executable)
@@ -30,6 +30,7 @@ test_expect_success 'setup remote repository' '
        git clone --bare test_repo test_repo.git &&
        cd test_repo.git &&
        git config http.receivepack true &&
+       git config core.logallrefupdates true &&
        ORIG_HEAD=$(git rev-parse --verify HEAD) &&
        cd - &&
        mv test_repo.git "$HTTPD_DOCUMENT_ROOT_PATH"
@@ -167,7 +168,7 @@ test_expect_success 'push fails for non-fast-forward refs unmatched by remote he
 '
 
 test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: our output' '
-       test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" \
+       test_i18ngrep "Updates were rejected because" \
                output
 '
 
@@ -215,12 +216,55 @@ test_expect_success 'push --mirror to repo with alternates' '
        git push --mirror "$HTTPD_URL"/smart/alternates-mirror.git
 '
 
-test_expect_success TTY 'quiet push' '
+test_expect_success TTY 'push shows progress when stderr is a tty' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       test_commit noisy &&
+       test_terminal git push >output 2>&1 &&
+       grep "^Writing objects" output
+'
+
+test_expect_success TTY 'push --quiet silences status and progress' '
        cd "$ROOT_PATH"/test_repo_clone &&
        test_commit quiet &&
-       test_terminal git push --quiet --no-progress 2>&1 | tee output &&
+       test_terminal git push --quiet >output 2>&1 &&
        test_cmp /dev/null output
 '
 
+test_expect_success TTY 'push --no-progress silences progress but not status' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       test_commit no-progress &&
+       test_terminal git push --no-progress >output 2>&1 &&
+       grep "^To http" output &&
+       ! grep "^Writing objects"
+'
+
+test_expect_success 'push --progress shows progress to non-tty' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       test_commit progress &&
+       git push --progress >output 2>&1 &&
+       grep "^To http" output &&
+       grep "^Writing objects" output
+'
+
+test_expect_success 'http push gives sane defaults to reflog' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       test_commit reflog-test &&
+       git push "$HTTPD_URL"/smart/test_repo.git &&
+       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
+               log -g -1 --format="%gn <%ge>" >actual &&
+       echo "anonymous <anonymous@http.127.0.0.1>" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'http push respects GIT_COMMITTER_* in reflog' '
+       cd "$ROOT_PATH"/test_repo_clone &&
+       test_commit custom-reflog-test &&
+       git push "$HTTPD_URL"/smart_custom_env/test_repo.git &&
+       git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
+               log -g -1 --format="%gn <%ge>" >actual &&
+       echo "Custom User <custom@example.com>" >expect &&
+       test_cmp expect actual
+'
+
 stop_httpd
 test_done
index 7cbc9994a3d99530d57a4552cfb0ee7dd29c99de..a3a4e47e1d25379e981373e67124c9efe5c76213 100755 (executable)
@@ -103,14 +103,12 @@ test_remote_error()
                esac
        done
 
-       if test $# -ne 3
-       then
-               error "invalid number of arguments"
-       fi
-
+       msg=$1
+       shift
        cmd=$1
-       repo=$2
-       msg=$3
+       shift
+       repo=$1
+       shift || error "invalid number of arguments"
 
        if test -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo"
        then
@@ -122,7 +120,7 @@ test_remote_error()
                fi
        fi
 
-       test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" 2>output &&
+       test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" "$@" 2>output &&
        echo "fatal: remote error: $msg: /$repo" >expect &&
        test_cmp expect output
        ret=$?
@@ -131,18 +129,18 @@ test_remote_error()
 }
 
 msg="access denied or repository not exported"
-test_expect_success 'clone non-existent' "test_remote_error    clone nowhere.git '$msg'"
-test_expect_success 'push disabled'      "test_remote_error    push  repo.git    '$msg'"
-test_expect_success 'read access denied' "test_remote_error -x fetch repo.git    '$msg'"
-test_expect_success 'not exported'       "test_remote_error -n fetch repo.git    '$msg'"
+test_expect_success 'clone non-existent' "test_remote_error    '$msg' clone nowhere.git    "
+test_expect_success 'push disabled'      "test_remote_error    '$msg' push  repo.git master"
+test_expect_success 'read access denied' "test_remote_error -x '$msg' fetch repo.git       "
+test_expect_success 'not exported'       "test_remote_error -n '$msg' fetch repo.git       "
 
 stop_git_daemon
 start_git_daemon --informative-errors
 
-test_expect_success 'clone non-existent' "test_remote_error    clone nowhere.git 'no such repository'"
-test_expect_success 'push disabled'      "test_remote_error    push  repo.git    'service not enabled'"
-test_expect_success 'read access denied' "test_remote_error -x fetch repo.git    'no such repository'"
-test_expect_success 'not exported'       "test_remote_error -n fetch repo.git    'repository not exported'"
+test_expect_success 'clone non-existent' "test_remote_error    'no such repository'      clone nowhere.git    "
+test_expect_success 'push disabled'      "test_remote_error    'service not enabled'     push  repo.git master"
+test_expect_success 'read access denied' "test_remote_error -x 'no such repository'      fetch repo.git       "
+test_expect_success 'not exported'       "test_remote_error -n 'repository not exported' fetch repo.git       "
 
 stop_git_daemon
 test_done
index bbc4691bd7ef1e3633d4a66440211179fae42a84..c47d450cc3731cb471aa8485178f517bb0d6cbf5 100755 (executable)
@@ -34,7 +34,7 @@ test_expect_success 'cloning with reference (-l -s)' \
 cd "$base_dir"
 
 test_expect_success 'existence of info/alternates' \
-'test `wc -l <C/.git/objects/info/alternates` = 2'
+'test_line_count = 2 C/.git/objects/info/alternates'
 
 cd "$base_dir"
 
@@ -63,7 +63,7 @@ test_expect_success 'fetched no objects' \
 cd "$base_dir"
 
 test_expect_success 'existence of info/alternates' \
-'test `wc -l <D/.git/objects/info/alternates` = 1'
+'test_line_count = 1 D/.git/objects/info/alternates'
 
 cd "$base_dir"
 
index ef7127c1b3943a494692ac8027ec321608a31b9c..aa045295dec5af9dedc25495668d4afd6022d2cd 100755 (executable)
@@ -18,7 +18,7 @@ reachable_via() {
 
 test_valid_repo() {
        git fsck --full > fsck.log &&
-       test `wc -l < fsck.log` = 0
+       test_line_count = 0 fsck.log
 }
 
 base_dir=`pwd`
index 1c62001fce76a73cc951dd18bcf6cb6f650f69e8..57023345100d3c04f9bfb1b2a701c53b6e55deed 100755 (executable)
@@ -72,6 +72,19 @@ test_expect_success 'pushing to local repo' '
        compare_refs localclone HEAD server HEAD
 '
 
+# Generally, skip this test.  It demonstrates a now-fixed race in
+# git-remote-testgit, but is too slow to leave in for general use.
+: test_expect_success 'racily pushing to local repo' '
+       test_when_finished "rm -rf server2 localclone2" &&
+       cp -a server server2 &&
+       git clone "testgit::${PWD}/server2" localclone2 &&
+       (cd localclone2 &&
+       echo content >>file &&
+       git commit -a -m three &&
+       GIT_REMOTE_TESTGIT_SLEEPY=2 git push) &&
+       compare_refs localclone2 HEAD server2 HEAD
+'
+
 test_expect_success 'synch with changes from localclone' '
        (cd clone &&
         git pull)
index 444279077e803ca96e48281ae956ea6536317608..a01d2445022ecb82fe0e31739527339fa02e44a5 100755 (executable)
@@ -188,23 +188,23 @@ test_expect_success 'empty email' '
 
 test_expect_success 'del LF before empty (1)' '
        git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD^^ >actual &&
-       test $(wc -l <actual) = 2
+       test_line_count = 2 actual
 '
 
 test_expect_success 'del LF before empty (2)' '
        git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD >actual &&
-       test $(wc -l <actual) = 6 &&
+       test_line_count = 6 actual &&
        grep "^$" actual
 '
 
 test_expect_success 'add LF before non-empty (1)' '
        git show -s --pretty=format:"%s%+b%nThanks%n" HEAD^^ >actual &&
-       test $(wc -l <actual) = 2
+       test_line_count = 2 actual
 '
 
 test_expect_success 'add LF before non-empty (2)' '
        git show -s --pretty=format:"%s%+b%nThanks%n" HEAD >actual &&
-       test $(wc -l <actual) = 6 &&
+       test_line_count = 6 actual &&
        grep "^$" actual
 '
 
@@ -278,8 +278,9 @@ test_expect_success 'oneline with empty message' '
        git commit -m "dummy" --allow-empty &&
        git filter-branch --msg-filter "sed -e s/dummy//" HEAD^^.. &&
        git rev-list --oneline HEAD >test.txt &&
-       test $(git rev-list --oneline HEAD | wc -l) -eq 5 &&
-       test $(git rev-list --oneline --graph HEAD | wc -l) -eq 5
+       test_line_count = 5 test.txt &&
+       git rev-list --oneline --graph HEAD >testg.txt &&
+       test_line_count = 5 testg.txt
 '
 
 test_done
index 691e4a4481eba2994ec40e498d4cd25fcff67f26..72e28ee5350926f3c4f27e2c99f8323a3eb8e57c 100755 (executable)
@@ -480,7 +480,7 @@ test_expect_success 'many merge bases creation' '
        git merge -m "merge HASH7 and SIDE_HASH7" "$HASH7" &&
        B_HASH=$(git rev-parse --verify HEAD) &&
        git merge-base --all "$A_HASH" "$B_HASH" > merge_bases.txt &&
-       test $(wc -l < merge_bases.txt) = "2" &&
+       test_line_count = 2 merge_bases.txt &&
        grep "$HASH5" merge_bases.txt &&
        grep "$SIDE_HASH5" merge_bases.txt
 '
index 94f010be8a17b8aaa8099d1a54ad2bd4317b67dc..15beecc3c6391fea89ffd5f0b6a091f19f9fce19 100755 (executable)
@@ -97,7 +97,7 @@ test_expect_success 'setup large simple rename' '
 test_expect_success 'massive simple rename does not spam added files' '
        sane_unset GIT_MERGE_VERBOSITY &&
        git merge --no-stat simple-rename | grep -v Removing >output &&
-       test 5 -gt "$(wc -l < output)"
+       test_line_count -lt 5 output
 '
 
 test_done
index 19272bc551277903bc1c444f4f0f05d8f2d7d672..ec2b516c3f79901ca5593f1edb97455e3fa8389e 100755 (executable)
@@ -71,13 +71,13 @@ test_expect_success 'checkout' '
        (
                cd test && git checkout b1
        ) >actual &&
-       grep "have 1 and 1 different" actual
+       test_i18ngrep "have 1 and 1 different" actual
 '
 
 test_expect_success 'checkout with local tracked branch' '
        git checkout master &&
        git checkout follower >actual &&
-       grep "is ahead of" actual
+       test_i18ngrep "is ahead of" actual
 '
 
 test_expect_success 'status' '
@@ -87,14 +87,14 @@ test_expect_success 'status' '
                # reports nothing to commit
                test_must_fail git commit --dry-run
        ) >actual &&
-       grep "have 1 and 1 different" actual
+       test_i18ngrep "have 1 and 1 different" actual
 '
 
 test_expect_success 'fail to track lightweight tags' '
        git checkout master &&
        git tag light &&
        test_must_fail git branch --track lighttrack light >actual &&
-       test_must_fail grep "set up to track" actual &&
+       test_i18ngrep ! "set up to track" actual &&
        test_must_fail git checkout lighttrack
 '
 
@@ -102,7 +102,7 @@ test_expect_success 'fail to track annotated tags' '
        git checkout master &&
        git tag -m heavy heavy &&
        test_must_fail git branch --track heavytrack heavy >actual &&
-       test_must_fail grep "set up to track" actual &&
+       test_i18ngrep ! "set up to track" actual &&
        test_must_fail git checkout heavytrack
 '
 
index 32591f94135755847b406389aba5779e73cff5e0..466fa3804bc8a840d994e9d2e9092f599cc87eab 100755 (executable)
@@ -104,7 +104,7 @@ test_expect_failure 'conflict caused if rename not detected' '
        test 0 -eq $(git ls-files -u | wc -l) &&
        test 0 -eq $(git ls-files -o | wc -l) &&
 
-       test 6 -eq $(wc -l < c) &&
+       test_line_count = 6 c &&
        test $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&
        test $(git rev-parse HEAD:b) = $(git rev-parse A:b)
 '
index 9a168069217ef8d82173e563a04eaefe58d99f2a..9b50f54cc2d1cfb790b0fb68f71b9c1719061b7f 100755 (executable)
@@ -35,15 +35,18 @@ test_expect_success setup '
 
        echo "l3" >two &&
        test_tick &&
-       git commit -a -m "Left #3" &&
+       GIT_COMMITTER_NAME="Another Committer" \
+       GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #3" &&
 
        echo "l4" >two &&
        test_tick &&
-       git commit -a -m "Left #4" &&
+       GIT_COMMITTER_NAME="Another Committer" \
+       GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #4" &&
 
        echo "l5" >two &&
        test_tick &&
-       git commit -a -m "Left #5" &&
+       GIT_COMMITTER_NAME="Another Committer" \
+       GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #5" &&
        git tag tag-l5 &&
 
        git checkout right &&
@@ -99,6 +102,8 @@ test_expect_success '[merge] summary/log configuration' '
        cat >expected <<-EOF &&
        Merge branch ${apos}left${apos}
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * left:
          Left #5
          Left #4
@@ -144,6 +149,8 @@ test_expect_success 'merge.log=3 limits shortlog length' '
        cat >expected <<-EOF &&
        Merge branch ${apos}left${apos}
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * left: (5 commits)
          Left #5
          Left #4
@@ -159,6 +166,8 @@ test_expect_success 'merge.log=5 shows all 5 commits' '
        cat >expected <<-EOF &&
        Merge branch ${apos}left${apos}
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * left:
          Left #5
          Left #4
@@ -181,6 +190,8 @@ test_expect_success '--log=3 limits shortlog length' '
        cat >expected <<-EOF &&
        Merge branch ${apos}left${apos}
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * left: (5 commits)
          Left #5
          Left #4
@@ -196,6 +207,8 @@ test_expect_success '--log=5 shows all 5 commits' '
        cat >expected <<-EOF &&
        Merge branch ${apos}left${apos}
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * left:
          Left #5
          Left #4
@@ -225,6 +238,8 @@ test_expect_success 'fmt-merge-msg -m' '
        cat >expected.log <<-EOF &&
        Sync with left
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * ${apos}left${apos} of $(pwd):
          Left #5
          Left #4
@@ -256,6 +271,8 @@ test_expect_success 'setup: expected shortlog for two branches' '
        cat >expected <<-EOF
        Merge branches ${apos}left${apos} and ${apos}right${apos}
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * left:
          Left #5
          Left #4
@@ -379,6 +396,8 @@ test_expect_success 'merge-msg two tags' '
          Common #2
          Common #1
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * tag ${apos}tag-l5${apos}:
          Left #5
          Left #4
@@ -407,6 +426,8 @@ test_expect_success 'merge-msg tag and branch' '
          Common #2
          Common #1
 
+       By Another Author (3) and A U Thor (2)
+       via Another Committer
        * left:
          Left #5
          Left #4
index f8c247a7500d723e46796e7b9b76b9812e35db9b..518944653477225da94db0826d77936eaa0d249a 100755 (executable)
@@ -263,6 +263,50 @@ test_expect_success 'tag -l can accept multiple patterns' '
        test_cmp expect actual
 '
 
+test_expect_success 'listing tags in column' '
+       COLUMNS=40 git tag -l --column=row >actual &&
+       cat >expected <<\EOF &&
+a1      aa1     cba     t210    t211
+v0.2.1  v1.0    v1.0.1  v1.1.3
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'listing tags in column with column.*' '
+       git config column.tag row &&
+       git config column.ui dense &&
+       COLUMNS=40 git tag -l >actual &&
+       git config --unset column.ui &&
+       git config --unset column.tag &&
+       cat >expected <<\EOF &&
+a1      aa1   cba     t210    t211
+v0.2.1  v1.0  v1.0.1  v1.1.3
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'listing tag with -n --column should fail' '
+       test_must_fail git tag --column -n
+'
+
+test_expect_success 'listing tags -n in column with column.ui ignored' '
+       git config column.ui "row dense" &&
+       COLUMNS=40 git tag -l -n >actual &&
+       git config --unset column.ui &&
+       cat >expected <<\EOF &&
+a1              Foo
+aa1             Foo
+cba             Foo
+t210            Foo
+t211            Foo
+v0.2.1          Foo
+v1.0            Foo
+v1.0.1          Foo
+v1.1.3          Foo
+EOF
+       test_cmp expected actual
+'
+
 # creating and verifying lightweight tags:
 
 test_expect_success \
index 07fb53adcbc06e260b078de546bd07f11093071d..be9672e5a0222f0a796f400b2c22c615fff195a4 100755 (executable)
@@ -229,7 +229,7 @@ test_expect_success 'checkout to detach HEAD (with advice declined)' '
        git checkout -f renamer && git clean -f &&
        git checkout renamer^ 2>messages &&
        test_i18ngrep "HEAD is now at 7329388" messages &&
-       test 1 -eq $(wc -l <messages) &&
+       test_line_count = 1 messages &&
        H=$(git rev-parse --verify HEAD) &&
        M=$(git show-ref -s --verify refs/heads/master) &&
        test "z$H" = "z$M" &&
@@ -247,7 +247,7 @@ test_expect_success 'checkout to detach HEAD' '
        git checkout -f renamer && git clean -f &&
        git checkout renamer^ 2>messages &&
        test_i18ngrep "HEAD is now at 7329388" messages &&
-       test 1 -lt $(wc -l <messages) &&
+       test_line_count -gt 1 messages &&
        H=$(git rev-parse --verify HEAD) &&
        M=$(git show-ref -s --verify refs/heads/master) &&
        test "z$H" = "z$M" &&
index b377a7af28c9dde11bd4cf6adcf0d7eae31a0754..81827e696f21f598357313d6dad94400d8562718 100755 (executable)
@@ -234,7 +234,7 @@ EOF
 
 test_expect_success 'status should only print one line' '
        git submodule status >lines &&
-       test $(wc -l <lines) = 1
+       test_line_count = 1 lines
 '
 
 test_expect_success 'setup - fetch commit name from submodule' '
index ab37c368d071236d0a2851417e85d2a216c7f4fc..a45fadc58e047aa0e6637b9ef5dce86a28ddc4f1 100755 (executable)
@@ -43,7 +43,7 @@ git commit -m B-super-added'
 cd "$base_dir"
 
 test_expect_success 'after add: existence of info/alternates' \
-'test `wc -l <super/.git/modules/sub/objects/info/alternates` = 1'
+'test_line_count = 1 super/.git/modules/sub/objects/info/alternates'
 
 cd "$base_dir"
 
@@ -66,7 +66,7 @@ test_expect_success 'update with reference' \
 cd "$base_dir"
 
 test_expect_success 'after update: existence of info/alternates' \
-'test `wc -l <super-clone/.git/modules/sub/objects/info/alternates` = 1'
+'test_line_count = 1 super-clone/.git/modules/sub/objects/info/alternates'
 
 cd "$base_dir"
 
index 3f3adc31b98773d26715089c25d8d923dd342717..181456aa9a80893e93477302516a7f00594eba85 100755 (executable)
@@ -335,7 +335,7 @@ test_expect_success 'A single-liner subject with a token plus colon is not a foo
        git reset --hard &&
        git commit -s -m "hello: kitty" --allow-empty &&
        git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
-       test $(wc -l <actual) = 3
+       test_line_count = 3 actual
 
 '
 
index 5d0e79fe2a09c469c61b54957f5098c0928bca9c..28e184829c0f12d4060f0b793db8511259bef91e 100755 (executable)
@@ -59,6 +59,30 @@ test_expect_success 'status (1)' '
        test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output
 '
 
+test_expect_success 'status --column' '
+       COLUMNS=50 git status --column="column dense" >output &&
+       cat >expect <<\EOF &&
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#
+# Changes not staged for commit:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked dir2/untracked untracked
+#      dir2/modified  output
+EOF
+       test_cmp expect output
+'
+
 cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
index 7117b57ccc9e20a2a6f4d10705b8a5665db8b30e..955f09f8e818cea8d32e453cd4971ff362d69bea 100755 (executable)
@@ -54,9 +54,9 @@ Trying simple merge with c2
 Trying simple merge with c3
 Trying simple merge with c4
 Merge made by the 'octopus' strategy.
- c2.c |    1 +
- c3.c |    1 +
- c4.c |    1 +
+ c2.c | 1 +
+ c3.c | 1 +
+ c4.c | 1 +
  3 files changed, 3 insertions(+)
  create mode 100644 c2.c
  create mode 100644 c3.c
@@ -66,27 +66,27 @@ EOF
 test_expect_success 'merge output uses pretty names' '
        git reset --hard c1 &&
        git merge c2 c3 c4 >actual &&
-       test_cmp actual expected
+       test_i18ncmp expected actual
 '
 
 cat >expected <<\EOF
 Merge made by the 'recursive' strategy.
- c5.c |    1 +
+ c5.c | 1 +
  1 file changed, 1 insertion(+)
  create mode 100644 c5.c
 EOF
 
 test_expect_success 'merge reduces irrelevant remote heads' '
        GIT_MERGE_VERBOSITY=0 git merge c4 c5 >actual &&
-       test_cmp expected actual
+       test_i18ncmp expected actual
 '
 
 cat >expected <<\EOF
 Fast-forwarding to: c1
 Trying simple merge with c2
 Merge made by the 'octopus' strategy.
- c1.c |    1 +
- c2.c |    1 +
+ c1.c | 1 +
+ c2.c | 1 +
  2 files changed, 2 insertions(+)
  create mode 100644 c1.c
  create mode 100644 c2.c
@@ -95,7 +95,7 @@ EOF
 test_expect_success 'merge fast-forward output uses pretty names' '
        git reset --hard c0 &&
        git merge c1 c2 >actual &&
-       test_cmp actual expected
+       test_i18ncmp expected actual
 '
 
 test_done
index aa74184c31cd6b2bd2e0e566e6805e60eed7aff8..6547eb8f5459d4d95113469d338e1879f86b79ea 100755 (executable)
@@ -92,6 +92,15 @@ test_expect_success 'will not overwrite removed file with staged changes' '
        test_cmp important c1.c
 '
 
+test_expect_failure 'will not overwrite unstaged changes in renamed file' '
+       git reset --hard c1 &&
+       git mv c1.c other.c &&
+       git commit -m rename &&
+       cp important other.c &&
+       git merge c1a &&
+       test_cmp important other.c
+'
+
 test_expect_success 'will not overwrite untracked subtree' '
        git reset --hard c0 &&
        rm -rf sub &&
index 200ab61278643e8b9deb4f624a95378e7e4c5b67..b8d4cdea8cc661e27367bc942587b3b80b433051 100755 (executable)
@@ -95,4 +95,18 @@ test_expect_success 'unpacked objects receive timestamp of pack file' '
        compare_mtimes < mtimes
 '
 
+test_expect_success 'do not bother loosening old objects' '
+       obj1=$(echo one | git hash-object -w --stdin) &&
+       obj2=$(echo two | git hash-object -w --stdin) &&
+       pack1=$(echo $obj1 | git pack-objects .git/objects/pack/pack) &&
+       pack2=$(echo $obj2 | git pack-objects .git/objects/pack/pack) &&
+       git prune-packed &&
+       git cat-file -p $obj1 &&
+       git cat-file -p $obj2 &&
+       test-chmtime =-86400 .git/objects/pack/pack-$pack2.pack &&
+       git repack -A -d --unpack-unreachable=1.hour.ago &&
+       git cat-file -p $obj1 &&
+       test_must_fail git cat-file -p $obj2
+'
+
 test_done
index 4fb4c9384a0045d3b041d627e9d814637d9268e2..9c3e997b9d6be69467a78f9ff8b1ff746be8e9b6 100755 (executable)
@@ -83,6 +83,17 @@ test_expect_success PERL 'difftool ignores bad --tool values' '
        test "$diff" = ""
 '
 
+test_expect_success PERL 'difftool forwards arguments to diff' '
+       >for-diff &&
+       git add for-diff &&
+       echo changes>for-diff &&
+       git add for-diff &&
+       diff=$(git difftool --cached --no-prompt -- for-diff) &&
+       test "$diff" = "" &&
+       git reset -- for-diff &&
+       rm for-diff
+'
+
 test_expect_success PERL 'difftool honors --gui' '
        git config merge.tool bogus-tool &&
        git config diff.tool bogus-tool &&
@@ -94,6 +105,19 @@ test_expect_success PERL 'difftool honors --gui' '
        restore_test_defaults
 '
 
+test_expect_success PERL 'difftool --gui last setting wins' '
+       git config diff.guitool bogus-tool &&
+       git difftool --no-prompt --gui --no-gui &&
+
+       git config merge.tool bogus-tool &&
+       git config diff.tool bogus-tool &&
+       git config diff.guitool test-tool &&
+       diff=$(git difftool --no-prompt --no-gui --gui branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
 test_expect_success PERL 'difftool --gui works without configured diff.guitool' '
        git config diff.tool test-tool &&
 
@@ -306,4 +330,48 @@ test_expect_success PERL 'say no to the second file' '
        echo "$diff" | stdin_doesnot_contain br2
 '
 
+test_expect_success PERL 'difftool --tool-help' '
+       tool_help=$(git difftool --tool-help) &&
+       echo "$tool_help" | stdin_contains tool
+'
+
+test_expect_success PERL 'setup change in subdirectory' '
+       git checkout master &&
+       mkdir sub &&
+       echo master >sub/sub &&
+       git add sub/sub &&
+       git commit -m "added sub/sub" &&
+       echo test >>file &&
+       echo test >>sub/sub &&
+       git add . &&
+       git commit -m "modified both"
+'
+
+test_expect_success PERL 'difftool -d' '
+       diff=$(git difftool -d --extcmd ls branch) &&
+       echo "$diff" | stdin_contains sub &&
+       echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff' '
+       diff=$(git difftool --dir-diff --extcmd ls branch) &&
+       echo "$diff" | stdin_contains sub &&
+       echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff ignores --prompt' '
+       diff=$(git difftool --dir-diff --prompt --extcmd ls branch) &&
+       echo "$diff" | stdin_contains sub &&
+       echo "$diff" | stdin_contains file
+'
+
+test_expect_success PERL 'difftool --dir-diff from subdirectory' '
+       (
+               cd sub &&
+               diff=$(git difftool --dir-diff --extcmd ls branch) &&
+               echo "$diff" | stdin_contains sub &&
+               echo "$diff" | stdin_contains file
+       )
+'
+
 test_done
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
new file mode 100755 (executable)
index 0000000..8998352
--- /dev/null
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+test_description='git column'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       cat >lista <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+'
+
+test_expect_success 'never' '
+       git column --indent=Z --mode=never <lista >actual &&
+       test_cmp lista actual
+'
+
+test_expect_success 'always' '
+       cat >expected <<\EOF &&
+Zone
+Ztwo
+Zthree
+Zfour
+Zfive
+Zsix
+Zseven
+Zeight
+Znine
+Zten
+Zeleven
+EOF
+       git column --indent=Z --mode=plain <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '80 columns' '
+       cat >expected <<\EOF &&
+one    two    three  four   five   six    seven  eight  nine   ten    eleven
+EOF
+       COLUMNS=80 git column --mode=column <lista >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+
+test_expect_success COLUMNS_CAN_BE_1 'COLUMNS = 1' '
+       COLUMNS=1 git column --mode=column <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'width = 1' '
+       git column --mode=column --width=1 <lista >actual &&
+       test_cmp expected actual
+'
+
+COLUMNS=20
+export COLUMNS
+
+test_expect_success '20 columns' '
+       cat >expected <<\EOF &&
+one    seven
+two    eight
+three  nine
+four   ten
+five   eleven
+six
+EOF
+       git column --mode=column <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, nodense' '
+       cat >expected <<\EOF &&
+one    seven
+two    eight
+three  nine
+four   ten
+five   eleven
+six
+EOF
+       git column --mode=column,nodense < lista > actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, dense' '
+       cat >expected <<\EOF &&
+one   five  nine
+two   six   ten
+three seven eleven
+four  eight
+EOF
+       git column --mode=column,dense < lista > actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, padding 2' '
+       cat >expected <<\EOF &&
+one     seven
+two     eight
+three   nine
+four    ten
+five    eleven
+six
+EOF
+       git column --mode=column --padding 2 <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, indented' '
+       cat >expected <<\EOF &&
+  one    seven
+  two    eight
+  three  nine
+  four   ten
+  five   eleven
+  six
+EOF
+       git column --mode=column --indent="  " <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first' '
+       cat >expected <<\EOF &&
+one    two
+three  four
+five   six
+seven  eight
+nine   ten
+eleven
+EOF
+       git column --mode=row <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, nodense' '
+       cat >expected <<\EOF &&
+one    two
+three  four
+five   six
+seven  eight
+nine   ten
+eleven
+EOF
+       git column --mode=row,nodense <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, dense' '
+       cat >expected <<\EOF &&
+one   two    three
+four  five   six
+seven eight  nine
+ten   eleven
+EOF
+       git column --mode=row,dense <lista >actual &&
+       test_cmp expected actual
+'
+
+test_done
index 924c8848608719183e1e402796ff038ca43784d3..7da0e8da7bfd68c0ac4e0b987664c7340078eb99 100755 (executable)
@@ -24,6 +24,13 @@ head_c () {
        ' - "$1"
 }
 
+verify_packs () {
+       for p in .git/objects/pack/*.pack
+       do
+               git verify-pack "$@" "$p" || return
+       done
+}
+
 file2_data='file2
 second line of EOF'
 
@@ -105,9 +112,10 @@ test_expect_success \
     'A: create pack from stdin' \
     'git fast-import --export-marks=marks.out <input &&
         git whatchanged master'
-test_expect_success \
-       'A: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'A: verify pack' '
+       verify_packs
+'
 
 cat >expect <<EOF
 author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
@@ -252,9 +260,11 @@ test_expect_success \
        'A: verify marks import does not crash' \
        'git fast-import --import-marks=marks.out <input &&
         git whatchanged verify--import-marks'
-test_expect_success \
-       'A: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'A: verify pack' '
+       verify_packs
+'
+
 cat >expect <<EOF
 :000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A     copy-of-file2
 EOF
@@ -514,9 +524,11 @@ test_expect_success \
     'C: incremental import create pack from stdin' \
     'git fast-import <input &&
         git whatchanged branch'
-test_expect_success \
-       'C: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'C: verify pack' '
+       verify_packs
+'
+
 test_expect_success \
        'C: validate reuse existing blob' \
        'test $newf = `git rev-parse --verify branch:file2/newf` &&
@@ -572,9 +584,10 @@ test_expect_success \
     'D: inline data in commit' \
     'git fast-import <input &&
         git whatchanged branch'
-test_expect_success \
-       'D: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'D: verify pack' '
+       verify_packs
+'
 
 cat >expect <<EOF
 :000000 100755 0000000000000000000000000000000000000000 35a59026a33beac1569b1c7f66f3090ce9c09afc A     newdir/exec.sh
@@ -618,9 +631,10 @@ test_expect_success 'E: rfc2822 date, --date-format=raw' '
 test_expect_success \
     'E: rfc2822 date, --date-format=rfc2822' \
     'git fast-import --date-format=rfc2822 <input'
-test_expect_success \
-       'E: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'E: verify pack' '
+       verify_packs
+'
 
 cat >expect <<EOF
 author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500
@@ -669,9 +683,10 @@ test_expect_success \
                fi
         fi
        '
-test_expect_success \
-       'F: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'F: verify pack' '
+       verify_packs
+'
 
 cat >expect <<EOF
 tree `git rev-parse branch~1^{tree}`
@@ -705,9 +720,11 @@ INPUT_END
 test_expect_success \
     'G: non-fast-forward update forced' \
     'git fast-import --force <input'
-test_expect_success \
-       'G: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'G: verify pack' '
+       verify_packs
+'
+
 test_expect_success \
        'G: branch changed, but logged' \
        'test $old_branch != `git rev-parse --verify branch^0` &&
@@ -742,9 +759,10 @@ test_expect_success \
     'H: deletall, add 1' \
     'git fast-import <input &&
         git whatchanged H'
-test_expect_success \
-       'H: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'H: verify pack' '
+       verify_packs
+'
 
 cat >expect <<EOF
 :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D     file2/newf
@@ -1857,9 +1875,10 @@ test_expect_success \
        'Q: commit notes' \
        'git fast-import <input &&
         git whatchanged notes-test'
-test_expect_success \
-       'Q: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
+
+test_expect_success 'Q: verify pack' '
+       verify_packs
+'
 
 commit1=$(git rev-parse notes-test~2)
 commit2=$(git rev-parse notes-test^)
@@ -2616,13 +2635,14 @@ test_expect_success \
        'R: blob bigger than threshold' \
        'test_create_repo R &&
         git --git-dir=R/.git fast-import --big-file-threshold=1 <input'
-test_expect_success \
-       'R: verify created pack' \
-       ': >verify &&
-        for p in R/.git/objects/pack/*.pack;
-        do
-          git verify-pack -v $p >>verify || exit;
-        done'
+
+test_expect_success 'R: verify created pack' '
+       (
+               cd R &&
+               verify_packs -v > ../verify
+       )
+'
+
 test_expect_success \
        'R: verify written objects' \
        'git --git-dir=R/.git cat-file blob big-file:big1 >actual &&
index 950d0ff498fda58c2d3d68dfb2cf50512f8f81ba..b00196bd238f538f78ce421979e725ce501e84e1 100755 (executable)
@@ -86,7 +86,7 @@ test_expect_success 'import/export-marks' '
        git checkout -b marks master &&
        git fast-export --export-marks=tmp-marks HEAD &&
        test -s tmp-marks &&
-       test $(wc -l < tmp-marks) -eq 3 &&
+       test_line_count = 3 tmp-marks &&
        test $(
                git fast-export --import-marks=tmp-marks\
                --export-marks=tmp-marks HEAD |
@@ -101,7 +101,7 @@ test_expect_success 'import/export-marks' '
                grep ^commit\  |
                wc -l) \
        -eq 1 &&
-       test $(wc -l < tmp-marks) -eq 4
+       test_line_count = 4 tmp-marks
 
 '
 
index 9199550ef4ffa39e4ce8bdb36badfd723e95e55f..806623e8858eef6ebe224ea14a38732f63fd6e49 100755 (executable)
@@ -476,14 +476,14 @@ test_expect_success 'cvs status' '
     cd cvswork &&
     GIT_CONFIG="$git_config" cvs update &&
     GIT_CONFIG="$git_config" cvs status | grep "^File: status.file" >../out &&
-    test $(wc -l <../out) = 2
+    test_line_count = 2 ../out
 '
 
 cd "$WORKDIR"
 test_expect_success 'cvs status (nonrecursive)' '
     cd cvswork &&
     GIT_CONFIG="$git_config" cvs status -l | grep "^File: status.file" >../out &&
-    test $(wc -l <../out) = 1
+    test_line_count = 1 ../out
 '
 
 cd "$WORKDIR"
@@ -500,8 +500,8 @@ test_expect_success 'cvs status (no subdirs in header)' '
 cd "$WORKDIR"
 test_expect_success 'cvs co -c (shows module database)' '
     GIT_CONFIG="$git_config" cvs co -c > out &&
-    grep "^master[      ]\+master$" < out &&
-    ! grep -v "^master[         ]\+master$" < out
+    grep "^master[      ][     ]*master$" <out &&
+    ! grep -v "^master[         ][     ]*master$" <out
 '
 
 #------------
index 31076edc5bd45261f5874b10dad6376e49fb9002..fa2f65f6be44fb7d6c4c22b9642b33cd90d51646 100755 (executable)
@@ -92,7 +92,7 @@ test_debug 'cat gitweb.output'
 test_expect_success 'snapshots: bad tree-ish id (tagged object)' '
        echo object > tag-object &&
        git add tag-object &&
-       git commit -m "Object to be tagged" &&
+       test_tick && git commit -m "Object to be tagged" &&
        git tag tagged-object `git hash-object tag-object` &&
        gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" &&
        grep "400 - Object is not a tree-ish" gitweb.output
@@ -112,6 +112,64 @@ test_expect_success 'snapshots: bad object id' '
 '
 test_debug 'cat gitweb.output'
 
+# ----------------------------------------------------------------------
+# modification times (Last-Modified and If-Modified-Since)
+
+test_expect_success 'modification: feed last-modified' '
+       gitweb_run "p=.git;a=atom;h=master" &&
+       grep "Status: 200 OK" gitweb.headers &&
+       grep "Last-modified: Thu, 7 Apr 2005 22:14:13 +0000" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: feed if-modified-since (modified)' '
+       export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+       test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+       gitweb_run "p=.git;a=atom;h=master" &&
+       grep "Status: 200 OK" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: feed if-modified-since (unmodified)' '
+       export HTTP_IF_MODIFIED_SINCE="Thu, 7 Apr 2005 22:14:13 +0000" &&
+       test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+       gitweb_run "p=.git;a=atom;h=master" &&
+       grep "Status: 304 Not Modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: snapshot last-modified' '
+       gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+       grep "Status: 200 OK" gitweb.headers &&
+       grep "Last-modified: Thu, 7 Apr 2005 22:14:13 +0000" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: snapshot if-modified-since (modified)' '
+       export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+       test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+       gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+       grep "Status: 200 OK" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: snapshot if-modified-since (unmodified)' '
+       export HTTP_IF_MODIFIED_SINCE="Thu, 7 Apr 2005 22:14:13 +0000" &&
+       test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+       gitweb_run "p=.git;a=snapshot;h=master;sf=tgz" &&
+       grep "Status: 304 Not Modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
+
+test_expect_success 'modification: tree snapshot' '
+       ID=`git rev-parse --verify HEAD^{tree}` &&
+       export HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" &&
+       test_when_finished "unset HTTP_IF_MODIFIED_SINCE" &&
+       gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" &&
+       grep "Status: 200 OK" gitweb.headers &&
+       ! grep -i "last-modified" gitweb.headers
+'
+test_debug 'cat gitweb.headers'
 
 # ----------------------------------------------------------------------
 # load checking
index 486c8eeb7e616c5dd964da25b068a8432e4c0c6f..0f410c45f7005e7de1237e556396db5726a22e50 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 tests'
+test_description='git p4 tests'
 
 . ./lib-git-p4.sh
 
@@ -20,8 +20,8 @@ test_expect_success 'add p4 files' '
        )
 '
 
-test_expect_success 'basic git-p4 clone' '
-       "$GITP4" clone --dest="$git" //depot &&
+test_expect_success 'basic git p4 clone' '
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -30,8 +30,8 @@ test_expect_success 'basic git-p4 clone' '
        )
 '
 
-test_expect_success 'git-p4 clone @all' '
-       "$GITP4" clone --dest="$git" //depot@all &&
+test_expect_success 'git p4 clone @all' '
+       git p4 clone --dest="$git" //depot@all &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -40,12 +40,12 @@ test_expect_success 'git-p4 clone @all' '
        )
 '
 
-test_expect_success 'git-p4 sync uninitialized repo' '
+test_expect_success 'git p4 sync uninitialized repo' '
        test_create_repo "$git" &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
-               test_must_fail "$GITP4" sync
+               test_must_fail git p4 sync
        )
 '
 
@@ -53,13 +53,13 @@ test_expect_success 'git-p4 sync uninitialized repo' '
 # Create a git repo by hand.  Add a commit so that HEAD is valid.
 # Test imports a new p4 repository into a new git branch.
 #
-test_expect_success 'git-p4 sync new branch' '
+test_expect_success 'git p4 sync new branch' '
        test_create_repo "$git" &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
                test_commit head &&
-               "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
+               git p4 sync --branch=refs/remotes/p4/depot //depot@all &&
                git log --oneline p4/depot >lines &&
                test_line_count = 2 lines
        )
@@ -76,7 +76,7 @@ test_expect_success 'clone two dirs' '
                p4 add sub2/f2 &&
                p4 submit -d "sub2/f2"
        ) &&
-       "$GITP4" clone --dest="$git" //depot/sub1 //depot/sub2 &&
+       git p4 clone --dest="$git" //depot/sub1 //depot/sub2 &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -94,7 +94,7 @@ test_expect_success 'clone two dirs, @all' '
                p4 add sub1/f3 &&
                p4 submit -d "sub1/f3"
        ) &&
-       "$GITP4" clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
+       git p4 clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -112,7 +112,7 @@ test_expect_success 'clone two dirs, @all, conflicting files' '
                p4 add sub2/f3 &&
                p4 submit -d "sub2/f3"
        ) &&
-       "$GITP4" clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
+       git p4 clone --dest="$git" //depot/sub1@all //depot/sub2@all &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -134,7 +134,7 @@ test_expect_success 'exit when p4 fails to produce marshaled output' '
        exit 1
        EOF
        chmod 755 "$badp4dir"/p4 &&
-       PATH="$badp4dir:$PATH" "$GITP4" clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
+       PATH="$badp4dir:$PATH" git p4 clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
        test $retval -eq 1 &&
        test_must_fail grep -q Traceback errs
 '
@@ -151,8 +151,8 @@ test_expect_success 'add p4 files with wildcards in the names' '
        )
 '
 
-test_expect_success 'wildcard files git-p4 clone' '
-       "$GITP4" clone --dest="$git" //depot &&
+test_expect_success 'wildcard files git p4 clone' '
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -163,8 +163,114 @@ test_expect_success 'wildcard files git-p4 clone' '
        )
 '
 
+test_expect_success 'wildcard files submit back to p4, add' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo git-wild-hash >git-wild#hash &&
+               echo git-wild-star >git-wild\*star &&
+               echo git-wild-at >git-wild@at &&
+               echo git-wild-percent >git-wild%percent &&
+               git add git-wild* &&
+               git commit -m "add some wildcard filenames" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file git-wild#hash &&
+               test_path_is_file git-wild\*star &&
+               test_path_is_file git-wild@at &&
+               test_path_is_file git-wild%percent
+       )
+'
+
+test_expect_success 'wildcard files submit back to p4, modify' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo new-line >>git-wild#hash &&
+               echo new-line >>git-wild\*star &&
+               echo new-line >>git-wild@at &&
+               echo new-line >>git-wild%percent &&
+               git add git-wild* &&
+               git commit -m "modify the wildcard files" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_line_count = 2 git-wild#hash &&
+               test_line_count = 2 git-wild\*star &&
+               test_line_count = 2 git-wild@at &&
+               test_line_count = 2 git-wild%percent
+       )
+'
+
+test_expect_success 'wildcard files submit back to p4, copy' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               cp file2 git-wild-cp#hash &&
+               git add git-wild-cp#hash &&
+               cp git-wild\*star file-wild-3 &&
+               git add file-wild-3 &&
+               git commit -m "wildcard copies" &&
+               git config git-p4.detectCopies true &&
+               git config git-p4.detectCopiesHarder true &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file git-wild-cp#hash &&
+               test_path_is_file file-wild-3
+       )
+'
+
+test_expect_success 'wildcard files submit back to p4, rename' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git mv git-wild@at file-wild-4 &&
+               git mv file-wild-3 git-wild-cp%percent &&
+               git commit -m "wildcard renames" &&
+               git config git-p4.detectRenames true &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing git-wild@at &&
+               test_path_is_file git-wild-cp%percent
+       )
+'
+
+test_expect_success 'wildcard files submit back to p4, delete' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git rm git-wild* &&
+               git commit -m "delete the wildcard files" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing git-wild#hash &&
+               test_path_is_missing git-wild\*star &&
+               test_path_is_missing git-wild@at &&
+               test_path_is_missing git-wild%percent
+       )
+'
+
 test_expect_success 'clone bare' '
-       "$GITP4" clone --dest="$git" --bare //depot &&
+       git p4 clone --dest="$git" --bare //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -209,7 +315,7 @@ test_expect_success 'preserve users' '
        p4_add_user alice Alice &&
        p4_add_user bob Bob &&
        p4_grant_admin alice &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -218,7 +324,7 @@ test_expect_success 'preserve users' '
                git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
                git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
                git config git-p4.skipSubmitEditCheck true &&
-               P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
+               P4EDITOR=touch P4USER=alice P4PASSWD=secret git p4 commit --preserve-user &&
                p4_check_commit_author file1 alice &&
                p4_check_commit_author file2 bob
        )
@@ -227,7 +333,7 @@ test_expect_success 'preserve users' '
 # Test username support, submitting as bob, who lacks admin rights. Should
 # not submit change to p4 (git diff should show deltas).
 test_expect_success 'refuse to preserve users without perms' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -236,14 +342,14 @@ test_expect_success 'refuse to preserve users without perms' '
                git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
                P4EDITOR=touch P4USER=bob P4PASSWD=secret &&
                export P4EDITOR P4USER P4PASSWD &&
-               test_must_fail "$GITP4" commit --preserve-user &&
+               test_must_fail git p4 commit --preserve-user &&
                ! git diff --exit-code HEAD..p4/master
        )
 '
 
 # What happens with unknown author? Without allowMissingP4Users it should fail.
 test_expect_success 'preserve user where author is unknown to p4' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -254,24 +360,24 @@ test_expect_success 'preserve user where author is unknown to p4' '
                git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
                P4EDITOR=touch P4USER=alice P4PASSWD=secret &&
                export P4EDITOR P4USER P4PASSWD &&
-               test_must_fail "$GITP4" commit --preserve-user &&
+               test_must_fail git p4 commit --preserve-user &&
                ! git diff --exit-code HEAD..p4/master &&
 
                echo "$0: repeat with allowMissingP4Users enabled" &&
                git config git-p4.allowMissingP4Users true &&
                git config git-p4.preserveUser true &&
-               "$GITP4" commit &&
+               git p4 commit &&
                git diff --exit-code HEAD..p4/master &&
                p4_check_commit_author file1 alice
        )
 '
 
-# If we're *not* using --preserve-user, git-p4 should warn if we're submitting
+# If we're *not* using --preserve-user, git p4 should warn if we're submitting
 # changes that are not all ours.
 # Test: user in p4 and user unknown to p4.
 # Test: warning disabled and user is the same.
 test_expect_success 'not preserving user with mixed authorship' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -281,20 +387,20 @@ test_expect_success 'not preserving user with mixed authorship' '
                make_change_by_user usernamefile3 Derek derek@localhost &&
                P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
                export P4EDITOR P4USER P4PASSWD &&
-               "$GITP4" commit |\
+               git p4 commit |\
                grep "git author derek@localhost does not match" &&
 
                make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               "$GITP4" commit |\
+               git p4 commit |\
                grep "git author charlie@localhost does not match" &&
 
                make_change_by_user usernamefile3 alice alice@localhost &&
-               "$GITP4" commit |\
+               git p4 commit |\
                test_must_fail grep "git author.*does not match" &&
 
                git config git-p4.skipUserNameCheck true &&
                make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               "$GITP4" commit |\
+               git p4 commit |\
                test_must_fail grep "git author.*does not match" &&
 
                p4_check_commit_author usernamefile3 alice
@@ -313,7 +419,7 @@ test_expect_success 'initial import time from top change time' '
        p4change=$(p4 -G changes -m 1 //depot/... | marshal_dump change) &&
        p4time=$(p4 -G changes -m 1 //depot/... | marshal_dump time) &&
        sleep 3 &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -331,16 +437,16 @@ test_expect_success 'initial import time from top change time' '
 # Repeat, this time with a smaller threshold and confirm that the rename is
 # detected in P4.
 test_expect_success 'detect renames' '
-       "$GITP4" clone --dest="$git" //depot@all &&
+       git p4 clone --dest="$git" //depot@all &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
-               git config git-p4.skipSubmitEditCheck true &&
+               git config git-p4.skipSubmitEdit true &&
 
                git mv file1 file4 &&
                git commit -a -m "Rename file1 to file4" &&
                git diff-tree -r -M HEAD &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file4 &&
                p4 filelog //depot/file4 | test_must_fail grep -q "branch from" &&
 
@@ -348,7 +454,7 @@ test_expect_success 'detect renames' '
                git commit -a -m "Rename file4 to file5" &&
                git diff-tree -r -M HEAD &&
                git config git-p4.detectRenames true &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file5 &&
                p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
 
@@ -360,7 +466,7 @@ test_expect_success 'detect renames' '
                level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
                test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
                git config git-p4.detectRenames $(($level + 2)) &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file6 &&
                p4 filelog //depot/file6 | test_must_fail grep -q "branch from" &&
 
@@ -372,7 +478,7 @@ test_expect_success 'detect renames' '
                level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
                test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
                git config git-p4.detectRenames $(($level - 2)) &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file7 &&
                p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
        )
@@ -390,17 +496,17 @@ test_expect_success 'detect renames' '
 # Modify and copy a file, configure a smaller threshold in detectCopies and
 # confirm that copy is detected in P4.
 test_expect_success 'detect copies' '
-       "$GITP4" clone --dest="$git" //depot@all &&
+       git p4 clone --dest="$git" //depot@all &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
-               git config git-p4.skipSubmitEditCheck true &&
+               git config git-p4.skipSubmitEdit true &&
 
                cp file2 file8 &&
                git add file8 &&
                git commit -a -m "Copy file2 to file8" &&
                git diff-tree -r -C HEAD &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file8 &&
                p4 filelog //depot/file8 | test_must_fail grep -q "branch from" &&
 
@@ -409,7 +515,7 @@ test_expect_success 'detect copies' '
                git commit -a -m "Copy file2 to file9" &&
                git diff-tree -r -C HEAD &&
                git config git-p4.detectCopies true &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file9 &&
                p4 filelog //depot/file9 | test_must_fail grep -q "branch from" &&
 
@@ -418,7 +524,7 @@ test_expect_success 'detect copies' '
                git add file2 file10 &&
                git commit -a -m "Modify and copy file2 to file10" &&
                git diff-tree -r -C HEAD &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file10 &&
                p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
 
@@ -429,7 +535,7 @@ test_expect_success 'detect copies' '
                src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
                test "$src" = file10 &&
                git config git-p4.detectCopiesHarder true &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file11 &&
                p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
 
@@ -443,7 +549,7 @@ test_expect_success 'detect copies' '
                src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
                test "$src" = file10 &&
                git config git-p4.detectCopies $(($level + 2)) &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file12 &&
                p4 filelog //depot/file12 | test_must_fail grep -q "branch from" &&
 
@@ -457,7 +563,7 @@ test_expect_success 'detect copies' '
                src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
                test "$src" = file10 &&
                git config git-p4.detectCopies $(($level - 2)) &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 filelog //depot/file13 &&
                p4 filelog //depot/file13 | grep -q "branch from //depot/file"
        )
index d41470541650590355bf0de1a1b556b3502492b5..2859256de30deec3bdb7ceeef51b12342a901ed0 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 p4 branching tests'
+test_description='git p4 tests for p4 branches'
 
 . ./lib-git-p4.sh
 
@@ -63,7 +63,7 @@ test_expect_success 'basic p4 branches' '
 
 test_expect_success 'import main, no branch detection' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot/main@all &&
+       git p4 clone --dest="$git" //depot/main@all &&
        (
                cd "$git" &&
                git log --oneline --graph --decorate --all &&
@@ -74,7 +74,7 @@ test_expect_success 'import main, no branch detection' '
 
 test_expect_success 'import branch1, no branch detection' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot/branch1@all &&
+       git p4 clone --dest="$git" //depot/branch1@all &&
        (
                cd "$git" &&
                git log --oneline --graph --decorate --all &&
@@ -85,7 +85,7 @@ test_expect_success 'import branch1, no branch detection' '
 
 test_expect_success 'import branch2, no branch detection' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot/branch2@all &&
+       git p4 clone --dest="$git" //depot/branch2@all &&
        (
                cd "$git" &&
                git log --oneline --graph --decorate --all &&
@@ -96,7 +96,7 @@ test_expect_success 'import branch2, no branch detection' '
 
 test_expect_success 'import depot, no branch detection' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot@all &&
+       git p4 clone --dest="$git" //depot@all &&
        (
                cd "$git" &&
                git log --oneline --graph --decorate --all &&
@@ -107,7 +107,7 @@ test_expect_success 'import depot, no branch detection' '
 
 test_expect_success 'import depot, branch detection' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" --detect-branches //depot@all &&
+       git p4 clone --dest="$git" --detect-branches //depot@all &&
        (
                cd "$git" &&
 
@@ -132,7 +132,7 @@ test_expect_success 'import depot, branch detection, branchList branch definitio
        (
                cd "$git" &&
                git config git-p4.branchList main:branch1 &&
-               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+               git p4 clone --dest=. --detect-branches //depot@all &&
 
                git log --oneline --graph --decorate --all &&
 
@@ -189,15 +189,15 @@ test_expect_success 'add simple p4 branches' '
 # Configure branches through git-config and clone them.
 # All files are tested to make sure branches were cloned correctly.
 # Finally, make an update to branch1 on P4 side to check if it is imported
-# correctly by git-p4.
-test_expect_success 'git-p4 clone simple branches' '
+# correctly by git p4.
+test_expect_success 'git p4 clone simple branches' '
        test_when_finished cleanup_git &&
        test_create_repo "$git" &&
        (
                cd "$git" &&
                git config git-p4.branchList branch1:branch2 &&
                git config --add git-p4.branchList branch1:branch3 &&
-               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+               git p4 clone --dest=. --detect-branches //depot@all &&
                git log --all --graph --decorate --stat &&
                git reset --hard p4/depot/branch1 &&
                test -f file1 &&
@@ -221,13 +221,13 @@ test_expect_success 'git-p4 clone simple branches' '
                p4 submit -d "update file2 in branch3" &&
                cd "$git" &&
                git reset --hard p4/depot/branch1 &&
-               "$GITP4" rebase &&
+               git p4 rebase &&
                grep file2_ file2
        )
 '
 
 # Create a complex branch structure in P4 depot to check if they are correctly
-# cloned. The branches are created from older changelists to check if git-p4 is
+# cloned. The branches are created from older changelists to check if git p4 is
 # able to correctly detect them.
 # The final expected structure is:
 # `branch1
@@ -248,7 +248,7 @@ test_expect_success 'git-p4 clone simple branches' '
 #   `- file1
 #   `- file2
 #   `- file3
-test_expect_success 'git-p4 add complex branches' '
+test_expect_success 'git p4 add complex branches' '
        test_when_finished cleanup_git &&
        test_create_repo "$git" &&
        (
@@ -263,10 +263,10 @@ test_expect_success 'git-p4 add complex branches' '
        )
 '
 
-# Configure branches through git-config and clone them. git-p4 will only be able
+# Configure branches through git-config and clone them. git p4 will only be able
 # to clone the original structure if it is able to detect the origin changelist
 # of each branch.
-test_expect_success 'git-p4 clone complex branches' '
+test_expect_success 'git p4 clone complex branches' '
        test_when_finished cleanup_git &&
        test_create_repo "$git" &&
        (
@@ -275,7 +275,7 @@ test_expect_success 'git-p4 clone complex branches' '
                git config --add git-p4.branchList branch1:branch3 &&
                git config --add git-p4.branchList branch1:branch4 &&
                git config --add git-p4.branchList branch1:branch5 &&
-               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+               git p4 clone --dest=. --detect-branches //depot@all &&
                git log --all --graph --decorate --stat &&
                git reset --hard p4/depot/branch1 &&
                test_path_is_file file1 &&
index 992bb8cf0ba40104e4c6c43babcd2edbb4ac90f1..21924dfd7db4fd5b8c0eb2c2823580ae33e73cc1 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 p4 filetype tests'
+test_description='git p4 filetype tests'
 
 . ./lib-git-p4.sh
 
@@ -37,7 +37,7 @@ test_expect_success 'utf-16 file create' '
 
 test_expect_success 'utf-16 file test' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot@all &&
+       git p4 clone --dest="$git" //depot@all &&
        (
                cd "$git" &&
 
@@ -84,7 +84,7 @@ test_expect_success 'keyword file test' '
        build_smush &&
        test_when_finished rm -f k_smush.py ko_smush.py &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot@all &&
+       git p4 clone --dest="$git" //depot@all &&
        (
                cd "$git" &&
 
@@ -94,7 +94,7 @@ test_expect_success 'keyword file test' '
                "$PYTHON_PATH" "$TRASH_DIRECTORY/ko_smush.py" <"$cli/k-text-ko" >cli-k-text-ko-smush &&
                test_cmp cli-k-text-ko-smush k-text-ko &&
 
-               # utf16, even though p4 expands keywords, git-p4 does not
+               # utf16, even though p4 expands keywords, git p4 does not
                # try to undo that
                test_cmp "$cli/k-utf16-k" k-utf16-k &&
                test_cmp "$cli/k-utf16-ko" k-utf16-ko
@@ -125,7 +125,7 @@ test_expect_success 'ignore apple' '
                p4 submit -d appledouble
        ) &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot@all &&
+       git p4 clone --dest="$git" //depot@all &&
        (
                cd "$git" &&
                test ! -f double.png
index db670207bde72177bff683863057d71cea34e6ae..fbacff34fed6607ec3d0470a28a9fde31bfa7c0c 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 transparency to shell metachars in filenames'
+test_description='git p4 transparency to shell metachars in filenames'
 
 . ./lib-git-p4.sh
 
@@ -18,7 +18,7 @@ test_expect_success 'init depot' '
 '
 
 test_expect_success 'shell metachars in filenames' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -28,7 +28,7 @@ test_expect_success 'shell metachars in filenames' '
                echo f2 >"file with spaces" &&
                git add "file with spaces" &&
                git commit -m "add files" &&
-               P4EDITOR=touch "$GITP4" submit
+               P4EDITOR=touch git p4 submit
        ) &&
        (
                cd "$cli" &&
@@ -39,7 +39,7 @@ test_expect_success 'shell metachars in filenames' '
 '
 
 test_expect_success 'deleting with shell metachars' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -47,7 +47,7 @@ test_expect_success 'deleting with shell metachars' '
                git rm foo\$bar &&
                git rm file\ with\ spaces &&
                git commit -m "remove files" &&
-               P4EDITOR=touch "$GITP4" submit
+               P4EDITOR=touch git p4 submit
        ) &&
        (
                cd "$cli" &&
@@ -97,7 +97,7 @@ test_expect_success 'branch with shell char' '
                cd "$git" &&
 
                git config git-p4.branchList main:branch\$3 &&
-               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+               git p4 clone --dest=. --detect-branches //depot@all &&
                git log --all --graph --decorate --stat &&
                git reset --hard p4/depot/branch\$3 &&
                test -f shell_char_branch_file &&
index a9e04efb889c07037b6800171f9675c8afb68e18..e30f80e617674967b1d474c1485a6570f1ef2903 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 p4 label tests'
+test_description='git p4 label tests'
 
 . ./lib-git-p4.sh
 
@@ -50,7 +50,7 @@ test_expect_success 'basic p4 labels' '
 
                p4 labels ... &&
 
-               "$GITP4" clone --dest="$git" --detect-labels //depot@all &&
+               git p4 clone --dest="$git" --detect-labels //depot@all &&
                cd "$git" &&
 
                git tag &&
@@ -89,7 +89,7 @@ test_expect_failure 'two labels on the same changelist' '
 
                p4 labels ... &&
 
-               "$GITP4" clone --dest="$git" --detect-labels //depot@all &&
+               git p4 clone --dest="$git" --detect-labels //depot@all &&
                cd "$git" &&
 
                git tag | grep tag_f1 &&
index df929e05558bbe84d78a35cedc273cb77b2d2c29..353dcfbe09fec2c2a85427e3e5cb378b9a8318d8 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 skipSubmitEdit config variables'
+test_description='git p4 skipSubmitEdit config variables'
 
 . ./lib-git-p4.sh
 
@@ -19,33 +19,33 @@ test_expect_success 'init depot' '
 
 # this works because EDITOR is set to :
 test_expect_success 'no config, unedited, say yes' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
                echo line >>file1 &&
                git commit -a -m "change 2" &&
-               echo y | "$GITP4" submit &&
+               echo y | git p4 submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 2 wc
        )
 '
 
 test_expect_success 'no config, unedited, say no' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
                echo line >>file1 &&
                git commit -a -m "change 3 (not really)" &&
-               printf "bad response\nn\n" | "$GITP4" submit &&
+               printf "bad response\nn\n" | git p4 submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 2 wc
        )
 '
 
 test_expect_success 'skipSubmitEdit' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -54,21 +54,21 @@ test_expect_success 'skipSubmitEdit' '
                git config core.editor /bin/false &&
                echo line >>file1 &&
                git commit -a -m "change 3" &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 3 wc
        )
 '
 
 test_expect_success 'skipSubmitEditCheck' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEditCheck true &&
                echo line >>file1 &&
                git commit -a -m "change 4" &&
-               "$GITP4" submit &&
+               git p4 submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 4 wc
        )
@@ -76,7 +76,7 @@ test_expect_success 'skipSubmitEditCheck' '
 
 # check the normal case, where the template really is edited
 test_expect_success 'no config, edited' '
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        ed="$TRASH_DIRECTORY/ed.sh" &&
        test_when_finished "rm \"$ed\"" &&
@@ -91,7 +91,7 @@ test_expect_success 'no config, edited' '
                cd "$git" &&
                echo line >>file1 &&
                git commit -a -m "change 5" &&
-               EDITOR="\"$ed\"" "$GITP4" submit &&
+               P4EDITOR="" EDITOR="\"$ed\"" git p4 submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 5 wc
        )
index 0571602129306f89292c482a7dc4858ea08a9867..2892367830c90353698ef0554b84c1e66d36a0a2 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 options'
+test_description='git p4 options'
 
 . ./lib-git-p4.sh
 
@@ -24,11 +24,11 @@ test_expect_success 'init depot' '
 '
 
 test_expect_success 'clone no --git-dir' '
-       test_must_fail "$GITP4" clone --git-dir=xx //depot
+       test_must_fail git p4 clone --git-dir=xx //depot
 '
 
 test_expect_success 'clone --branch' '
-       "$GITP4" clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
+       git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -42,7 +42,7 @@ test_expect_success 'clone --changesfile' '
        cf="$TRASH_DIRECTORY/cf" &&
        test_when_finished "rm \"$cf\"" &&
        printf "1\n3\n" >"$cf" &&
-       "$GITP4" clone --changesfile="$cf" --dest="$git" //depot &&
+       git p4 clone --changesfile="$cf" --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -58,14 +58,14 @@ test_expect_success 'clone --changesfile, @all' '
        cf="$TRASH_DIRECTORY/cf" &&
        test_when_finished "rm \"$cf\"" &&
        printf "1\n3\n" >"$cf" &&
-       test_must_fail "$GITP4" clone --changesfile="$cf" --dest="$git" //depot@all
+       test_must_fail git p4 clone --changesfile="$cf" --dest="$git" //depot@all
 '
 
 # imports both master and p4/master in refs/heads
 # requires --import-local on sync to find p4 refs/heads
 # does not update master on sync, just p4/master
 test_expect_success 'clone/sync --import-local' '
-       "$GITP4" clone --import-local --dest="$git" //depot@1,2 &&
+       git p4 clone --import-local --dest="$git" //depot@1,2 &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -73,9 +73,9 @@ test_expect_success 'clone/sync --import-local' '
                test_line_count = 2 lines &&
                git log --oneline refs/heads/p4/master >lines &&
                test_line_count = 2 lines &&
-               test_must_fail "$GITP4" sync &&
+               test_must_fail git p4 sync &&
 
-               "$GITP4" sync --import-local &&
+               git p4 sync --import-local &&
                git log --oneline refs/heads/master >lines &&
                test_line_count = 2 lines &&
                git log --oneline refs/heads/p4/master >lines &&
@@ -84,7 +84,7 @@ test_expect_success 'clone/sync --import-local' '
 '
 
 test_expect_success 'clone --max-changes' '
-       "$GITP4" clone --dest="$git" --max-changes 2 //depot@all &&
+       git p4 clone --dest="$git" --max-changes 2 //depot@all &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -101,7 +101,7 @@ test_expect_success 'clone --keep-path' '
                p4 add sub/dir/f4 &&
                p4 submit -d "change 4"
        ) &&
-       "$GITP4" clone --dest="$git" --keep-path //depot/sub/dir@all &&
+       git p4 clone --dest="$git" --keep-path //depot/sub/dir@all &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -109,7 +109,7 @@ test_expect_success 'clone --keep-path' '
                test_path_is_file sub/dir/f4
        ) &&
        cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot/sub/dir@all &&
+       git p4 clone --dest="$git" //depot/sub/dir@all &&
        (
                cd "$git" &&
                test_path_is_file f4 &&
@@ -126,7 +126,7 @@ test_expect_success 'clone --use-client-spec' '
        (
                # big usage message
                exec >/dev/null &&
-               test_must_fail "$GITP4" clone --dest="$git" --use-client-spec
+               test_must_fail git p4 clone --dest="$git" --use-client-spec
        ) &&
        cli2="$TRASH_DIRECTORY/cli2" &&
        mkdir -p "$cli2" &&
@@ -142,7 +142,7 @@ test_expect_success 'clone --use-client-spec' '
        ) &&
        P4CLIENT=client2 &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" --use-client-spec //depot/... &&
+       git p4 clone --dest="$git" --use-client-spec //depot/... &&
        (
                cd "$git" &&
                test_path_is_file bus/dir/f4 &&
@@ -156,7 +156,7 @@ test_expect_success 'clone --use-client-spec' '
                cd "$git" &&
                git init &&
                git config git-p4.useClientSpec true &&
-               "$GITP4" sync //depot/... &&
+               git p4 sync //depot/... &&
                git checkout -b master p4/master &&
                test_path_is_file bus/dir/f4 &&
                test_path_is_missing file1
index b1f61e3db555cc939135d3c40b78300103bcc1eb..f23b4c3620592704cac35839a010ce55de139da7 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 submit'
+test_description='git p4 submit'
 
 . ./lib-git-p4.sh
 
@@ -19,7 +19,7 @@ test_expect_success 'init depot' '
 
 test_expect_success 'submit with no client dir' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                echo file2 >file2 &&
@@ -27,24 +27,28 @@ test_expect_success 'submit with no client dir' '
                git commit -m "git commit 2" &&
                rm -rf "$cli" &&
                git config git-p4.skipSubmitEdit true &&
-               "$GITP4" submit
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file file1 &&
+               test_path_is_file file2
        )
 '
 
 # make two commits, but tell it to apply only from HEAD^
 test_expect_success 'submit --origin' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                test_commit "file3" &&
                test_commit "file4" &&
                git config git-p4.skipSubmitEdit true &&
-               "$GITP4" submit --origin=HEAD^
+               git p4 submit --origin=HEAD^
        ) &&
        (
                cd "$cli" &&
-               p4 sync &&
                test_path_is_missing "file3.t" &&
                test_path_is_file "file4.t"
        )
@@ -52,39 +56,132 @@ test_expect_success 'submit --origin' '
 
 test_expect_success 'submit with allowSubmit' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                test_commit "file5" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.allowSubmit "nobranch" &&
-               test_must_fail "$GITP4" submit &&
+               test_must_fail git p4 submit &&
                git config git-p4.allowSubmit "nobranch,master" &&
-               "$GITP4" submit
+               git p4 submit
        )
 '
 
 test_expect_success 'submit with master branch name from argv' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                test_commit "file6" &&
                git config git-p4.skipSubmitEdit true &&
-               test_must_fail "$GITP4" submit nobranch &&
+               test_must_fail git p4 submit nobranch &&
                git branch otherbranch &&
                git reset --hard HEAD^ &&
                test_commit "file7" &&
-               "$GITP4" submit otherbranch
+               git p4 submit otherbranch
        ) &&
        (
                cd "$cli" &&
-               p4 sync &&
                test_path_is_file "file6.t" &&
                test_path_is_missing "file7.t"
        )
 '
 
+#
+# Basic submit tests, the five handled cases
+#
+
+test_expect_success 'submit modify' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               echo line >>file1 &&
+               git add file1 &&
+               git commit -m file1 &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file file1 &&
+               test_line_count = 2 file1
+       )
+'
+
+test_expect_success 'submit add' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               echo file13 >file13 &&
+               git add file13 &&
+               git commit -m file13 &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file file13
+       )
+'
+
+test_expect_success 'submit delete' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git rm file4.t &&
+               git commit -m "delete file4.t" &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing file4.t
+       )
+'
+
+test_expect_success 'submit copy' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.detectCopies true &&
+               git config git-p4.detectCopiesHarder true &&
+               cp file5.t file5.ta &&
+               git add file5.ta &&
+               git commit -m "copy to file5.ta" &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file file5.ta &&
+               test ! -w file5.ta
+       )
+'
+
+test_expect_success 'submit rename' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               git config git-p4.detectRenames true &&
+               git mv file6.t file6.ta &&
+               git commit -m "rename file6.t to file6.ta" &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_missing file6.t &&
+               test_path_is_file file6.ta &&
+               test ! -w file6.ta
+       )
+'
+
 test_expect_success 'kill p4d' '
        kill_p4d
 '
index f0022839c76ede4b5f135a7318b9c53ede4a944a..2f8014a60ef07d84430b9a696d46cd63c4822524 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 relative chdir'
+test_description='git p4 relative chdir'
 
 . ./lib-git-p4.sh
 
@@ -26,7 +26,7 @@ test_expect_success 'P4CONFIG and absolute dir clone' '
        (
                P4CONFIG=p4config && export P4CONFIG &&
                sane_unset P4PORT P4CLIENT &&
-               "$GITP4" clone --verbose --dest="$git" //depot
+               git p4 clone --verbose --dest="$git" //depot
        )
 '
 
@@ -38,7 +38,7 @@ test_expect_success 'P4CONFIG and relative dir clone' '
        (
                P4CONFIG=p4config && export P4CONFIG &&
                sane_unset P4PORT P4CLIENT &&
-               "$GITP4" clone --verbose --dest="git" //depot
+               git p4 clone --verbose --dest="git" //depot
        )
 '
 
index 773a516ff0f40d396cb04cc474c697617192ae71..7d993ef80acdf1775d70713d2b9abfb984c47c1e 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 client view'
+test_description='git p4 client view'
 
 . ./lib-git-p4.sh
 
@@ -96,25 +96,25 @@ test_expect_success 'init depot' '
 test_expect_success 'unsupported view wildcard %%n' '
        client_view "//depot/%%%%1/sub/... //client/sub/%%%%1/..." &&
        test_when_finished cleanup_git &&
-       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+       test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
 '
 
 test_expect_success 'unsupported view wildcard *' '
        client_view "//depot/*/bar/... //client/*/bar/..." &&
        test_when_finished cleanup_git &&
-       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+       test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
 '
 
 test_expect_success 'wildcard ... only supported at end of spec 1' '
        client_view "//depot/.../file11 //client/.../file11" &&
        test_when_finished cleanup_git &&
-       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+       test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
 '
 
 test_expect_success 'wildcard ... only supported at end of spec 2' '
        client_view "//depot/.../a/... //client/.../a/..." &&
        test_when_finished cleanup_git &&
-       test_must_fail "$GITP4" clone --use-client-spec --dest="$git" //depot
+       test_must_fail git p4 clone --use-client-spec --dest="$git" //depot
 '
 
 test_expect_success 'basic map' '
@@ -122,7 +122,7 @@ test_expect_success 'basic map' '
        files="cli1/file11 cli1/file12" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -130,7 +130,7 @@ test_expect_success 'client view with no mappings' '
        client_view &&
        client_verify &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify
 '
 
@@ -139,7 +139,7 @@ test_expect_success 'single file map' '
        files="file11" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -150,7 +150,7 @@ test_expect_success 'later mapping takes precedence (entire repo)' '
               cli2/dir2/file21 cli2/dir2/file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -160,7 +160,7 @@ test_expect_success 'later mapping takes precedence (partial repo)' '
        files="file21 file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -176,7 +176,7 @@ test_expect_success 'depot path matching rejected client path' '
        files="cli12/file21 cli12/file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -187,7 +187,7 @@ test_expect_success 'exclusion wildcard, client rhs same (odd)' '
                    "-//depot/dir2/... //client/..." &&
        client_verify &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify
 '
 
@@ -197,7 +197,7 @@ test_expect_success 'exclusion wildcard, client rhs different (normal)' '
        files="dir1/file11 dir1/file12" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -207,7 +207,7 @@ test_expect_success 'exclusion single file' '
        files="dir1/file11 dir1/file12 dir2/file21" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -217,7 +217,7 @@ test_expect_success 'overlay wildcard' '
        files="cli/file11 cli/file12 cli/file21 cli/file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -227,7 +227,7 @@ test_expect_success 'overlay single file' '
        files="cli/file11 cli/file12 cli/file21" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -238,7 +238,7 @@ test_expect_success 'exclusion with later inclusion' '
        files="dir1/file11 dir1/file12 dir2incl/file21 dir2incl/file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -246,7 +246,7 @@ test_expect_success 'quotes on rhs only' '
        client_view "//depot/dir1/... \"//client/cdir 1/...\"" &&
        client_verify "cdir 1/file11" "cdir 1/file12" &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify "cdir 1/file11" "cdir 1/file12"
 '
 
@@ -258,7 +258,7 @@ test_expect_success 'quotes on rhs only' '
 test_expect_success 'clone --use-client-spec sets useClientSpec' '
        client_view "//depot/... //client/..." &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        (
                cd "$git" &&
                git config --bool git-p4.useClientSpec >actual &&
@@ -273,7 +273,7 @@ test_expect_success 'subdir clone' '
        files="dir1/file11 dir1/file12 dir2/file21 dir2/file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
        git_verify dir1/file11 dir1/file12
 '
 
@@ -283,14 +283,14 @@ test_expect_success 'subdir clone' '
 test_expect_success 'subdir clone, submit modify' '
        client_view "//depot/... //client/..." &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                echo line >>dir1/file12 &&
                git add dir1/file12 &&
                git commit -m dir1/file12 &&
-               "$GITP4" submit
+               git p4 submit
        ) &&
        (
                cd "$cli" &&
@@ -302,14 +302,14 @@ test_expect_success 'subdir clone, submit modify' '
 test_expect_success 'subdir clone, submit add' '
        client_view "//depot/... //client/..." &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                echo file13 >dir1/file13 &&
                git add dir1/file13 &&
                git commit -m dir1/file13 &&
-               "$GITP4" submit
+               git p4 submit
        ) &&
        (
                cd "$cli" &&
@@ -320,13 +320,13 @@ test_expect_success 'subdir clone, submit add' '
 test_expect_success 'subdir clone, submit delete' '
        client_view "//depot/... //client/..." &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                git rm dir1/file12 &&
                git commit -m "delete dir1/file12" &&
-               "$GITP4" submit
+               git p4 submit
        ) &&
        (
                cd "$cli" &&
@@ -337,7 +337,7 @@ test_expect_success 'subdir clone, submit delete' '
 test_expect_success 'subdir clone, submit copy' '
        client_view "//depot/... //client/..." &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
@@ -345,37 +345,71 @@ test_expect_success 'subdir clone, submit copy' '
                cp dir1/file11 dir1/file11a &&
                git add dir1/file11a &&
                git commit -m "copy to dir1/file11a" &&
-               "$GITP4" submit
+               git p4 submit
        ) &&
        (
                cd "$cli" &&
-               test_path_is_file dir1/file11a
+               test_path_is_file dir1/file11a &&
+               test ! -w dir1/file11a
        )
 '
 
 test_expect_success 'subdir clone, submit rename' '
        client_view "//depot/... //client/..." &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot/dir1 &&
+       git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.detectRenames true &&
                git mv dir1/file13 dir1/file13a &&
                git commit -m "rename dir1/file13 to dir1/file13a" &&
-               "$GITP4" submit
+               git p4 submit
        ) &&
        (
                cd "$cli" &&
                test_path_is_missing dir1/file13 &&
-               test_path_is_file dir1/file13a
+               test_path_is_file dir1/file13a &&
+               test ! -w dir1/file13a
+       )
+'
+
+# see t9800 for the non-client-spec case, and the rest of the wildcard tests
+test_expect_success 'wildcard files submit back to p4, client-spec case' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       git p4 clone --use-client-spec --dest="$git" //depot/dir1 &&
+       (
+               cd "$git" &&
+               echo git-wild-hash >dir1/git-wild#hash &&
+               echo git-wild-star >dir1/git-wild\*star &&
+               echo git-wild-at >dir1/git-wild@at &&
+               echo git-wild-percent >dir1/git-wild%percent &&
+               git add dir1/git-wild* &&
+               git commit -m "add some wildcard filenames" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               git p4 submit
+       ) &&
+       (
+               cd "$cli" &&
+               test_path_is_file dir1/git-wild#hash &&
+               test_path_is_file dir1/git-wild\*star &&
+               test_path_is_file dir1/git-wild@at &&
+               test_path_is_file dir1/git-wild%percent
+       ) &&
+       (
+               # delete these carefully, cannot just do "p4 delete"
+               # on files with wildcards; but git-p4 knows how
+               cd "$git" &&
+               git rm dir1/git-wild* &&
+               git commit -m "clean up the wildcards" &&
+               git p4 submit
        )
 '
 
 test_expect_success 'reinit depot' '
        (
                cd "$cli" &&
-               p4 sync -f &&
                rm files &&
                p4 delete */* &&
                p4 submit -d "delete all files" &&
@@ -419,7 +453,7 @@ test_expect_success 'overlay collision 1 to 2' '
        client_verify $files &&
        test_cmp actual "$cli"/filecollide &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files &&
        test_cmp actual "$git"/filecollide
 '
@@ -432,7 +466,7 @@ test_expect_failure 'overlay collision 2 to 1' '
        client_verify $files &&
        test_cmp actual "$cli"/filecollide &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files &&
        test_cmp actual "$git"/filecollide
 '
@@ -454,7 +488,7 @@ test_expect_failure 'overlay collision 1 to 2, but 2 deleted' '
        files="file11 file12 file21 file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -477,7 +511,7 @@ test_expect_failure 'overlay collision 1 to 2, but 2 deleted, then 1 updated' '
        files="file11 file12 file21 file22" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files
 '
 
@@ -533,7 +567,7 @@ test_expect_success 'overlay sync: initial git checkout' '
        echo dir1/colA >actual &&
        client_verify $files &&
        test_cmp actual "$cli"/colA &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files &&
        test_cmp actual "$git"/colA
 '
@@ -558,7 +592,7 @@ test_expect_success 'overlay sync: colA content switch' '
        test_cmp actual "$cli"/colA &&
        (
                cd "$git" &&
-               "$GITP4" sync --use-client-spec &&
+               git p4 sync --use-client-spec &&
                git merge --ff-only p4/master
        ) &&
        git_verify $files &&
@@ -585,7 +619,7 @@ test_expect_success 'overlay sync: colB appears' '
        test_cmp actual "$cli"/colB &&
        (
                cd "$git" &&
-               "$GITP4" sync --use-client-spec &&
+               git p4 sync --use-client-spec &&
                git merge --ff-only p4/master
        ) &&
        git_verify $files &&
@@ -613,7 +647,7 @@ test_expect_success 'overlay sync: colB disappears' '
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
-               "$GITP4" sync --use-client-spec &&
+               git p4 sync --use-client-spec &&
                git merge --ff-only p4/master
        ) &&
        git_verify $files
@@ -671,7 +705,7 @@ test_expect_success 'overlay sync swap: initial git checkout' '
        echo dir1/colA >actual &&
        client_verify $files &&
        test_cmp actual "$cli"/colA &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify $files &&
        test_cmp actual "$git"/colA
 '
@@ -696,7 +730,7 @@ test_expect_failure 'overlay sync swap: colA no content switch' '
        test_cmp actual "$cli"/colA &&
        (
                cd "$git" &&
-               "$GITP4" sync --use-client-spec &&
+               git p4 sync --use-client-spec &&
                git merge --ff-only p4/master
        ) &&
        git_verify $files &&
@@ -723,7 +757,7 @@ test_expect_success 'overlay sync swap: colB appears' '
        test_cmp actual "$cli"/colB &&
        (
                cd "$git" &&
-               "$GITP4" sync --use-client-spec &&
+               git p4 sync --use-client-spec &&
                git merge --ff-only p4/master
        ) &&
        git_verify $files &&
@@ -753,7 +787,7 @@ test_expect_failure 'overlay sync swap: colB no change' '
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
-               "$GITP4" sync --use-client-spec &&
+               git p4 sync --use-client-spec &&
                git merge --ff-only p4/master
        ) &&
        git_verify $files &&
@@ -801,7 +835,7 @@ test_expect_success 'quotes on lhs only' '
        files="cdir1/file11 cdir1/file12" &&
        client_verify $files &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        client_verify $files
 '
 
@@ -809,7 +843,7 @@ test_expect_success 'quotes on both sides' '
        client_view "\"//depot/dir 1/...\" \"//client/cdir 1/...\"" &&
        client_verify "cdir 1/file11" "cdir 1/file12" &&
        test_when_finished cleanup_git &&
-       "$GITP4" clone --use-client-spec --dest="$git" //depot &&
+       git p4 clone --use-client-spec --dest="$git" //depot &&
        git_verify "cdir 1/file11" "cdir 1/file12"
 '
 
index 49dfde061613ccb848421d1dfe489e4e592a36ee..d8d9ca46793a6c09a19beb350e2f18dd199eb2c4 100755 (executable)
@@ -84,13 +84,13 @@ scrub_ko_check () {
 #
 test_expect_success 'edit far away from RCS lines' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                sed -i "s/^line7/line7 edit/" filek &&
                git commit -m "filek line7 edit" filek &&
-               "$GITP4" submit &&
+               git p4 submit &&
                scrub_k_check filek
        )
 '
@@ -100,14 +100,14 @@ test_expect_success 'edit far away from RCS lines' '
 #
 test_expect_success 'edit near RCS lines' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.attemptRCSCleanup true &&
                sed -i "s/^line4/line4 edit/" filek &&
                git commit -m "filek line4 edit" filek &&
-               "$GITP4" submit &&
+               git p4 submit &&
                scrub_k_check filek
        )
 '
@@ -117,14 +117,14 @@ test_expect_success 'edit near RCS lines' '
 #
 test_expect_success 'edit keyword lines' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.attemptRCSCleanup true &&
                sed -i "/Revision/d" filek &&
                git commit -m "filek remove Revision line" filek &&
-               "$GITP4" submit &&
+               git p4 submit &&
                scrub_k_check filek
        )
 '
@@ -134,14 +134,14 @@ test_expect_success 'edit keyword lines' '
 #
 test_expect_success 'scrub ko files differently' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.attemptRCSCleanup true &&
                sed -i "s/^line4/line4 edit/" fileko &&
                git commit -m "fileko line4 edit" fileko &&
-               "$GITP4" submit &&
+               git p4 submit &&
                scrub_ko_check fileko &&
                ! scrub_k_check fileko
        )
@@ -168,7 +168,7 @@ test_expect_success 'cleanup after failure' '
 #
 test_expect_success 'do not scrub plain text' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
@@ -181,7 +181,7 @@ test_expect_success 'do not scrub plain text' '
                        sed -i "s/^line5/line5 p4 edit/" file_text &&
                        p4 submit -d "file5 p4 edit"
                ) &&
-               ! "$GITP4" submit &&
+               ! git p4 submit &&
                (
                        # exepct something like:
                        #    file_text - file(s) not opened on this client
@@ -239,7 +239,7 @@ p4_append_to_file () {
 # even though the change itself would otherwise apply cleanly.
 test_expect_success 'cope with rcs keyword expansion damage' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
@@ -252,10 +252,10 @@ test_expect_success 'cope with rcs keyword expansion damage' '
 
                git add kwfile1.c &&
                git commit -m "Zap an RCS kw line" &&
-               "$GITP4" submit &&
-               "$GITP4" rebase &&
+               git p4 submit &&
+               git p4 rebase &&
                git diff p4/master &&
-               "$GITP4" commit &&
+               git p4 commit &&
                echo "try modifying in both" &&
                cd "$cli" &&
                p4 edit kwfile1.c &&
@@ -265,8 +265,8 @@ test_expect_success 'cope with rcs keyword expansion damage' '
                echo "line from git at the top" | cat - kwfile1.c >kwfile1.c.new &&
                mv kwfile1.c.new kwfile1.c &&
                git commit -m "Add line in git at the top" kwfile1.c &&
-               "$GITP4" rebase &&
-               "$GITP4" submit
+               git p4 rebase &&
+               git p4 submit
        )
 '
 
@@ -280,7 +280,7 @@ test_expect_success 'cope with rcs keyword file deletion' '
                cat kwdelfile.c &&
                grep 1 kwdelfile.c
        ) &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                grep Revision kwdelfile.c &&
@@ -288,7 +288,7 @@ test_expect_success 'cope with rcs keyword file deletion' '
                git commit -m "Delete a file containing RCS keywords" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.attemptRCSCleanup true &&
-               "$GITP4" submit
+               git p4 submit
        ) &&
        (
                cd "$cli" &&
@@ -301,7 +301,7 @@ test_expect_success 'cope with rcs keyword file deletion' '
 # work fine without any special handling.
 test_expect_success 'Add keywords in git which match the default p4 values' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                echo "NewKW: \$Revision\$" >>kwfile1.c &&
@@ -309,7 +309,7 @@ test_expect_success 'Add keywords in git which match the default p4 values' '
                git commit -m "Adding RCS keywords in git" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.attemptRCSCleanup true &&
-               "$GITP4" submit
+               git p4 submit
        ) &&
        (
                cd "$cli" &&
@@ -325,7 +325,7 @@ test_expect_success 'Add keywords in git which match the default p4 values' '
 #
 test_expect_failure 'Add keywords in git which do not match the default p4 values' '
        test_when_finished cleanup_git &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                echo "NewKW2: \$Revision:1\$" >>kwfile1.c &&
@@ -333,7 +333,7 @@ test_expect_failure 'Add keywords in git which do not match the default p4 value
                git commit -m "Adding RCS keywords in git" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.attemptRCSCleanup true &&
-               "$GITP4" submit
+               git p4 submit
        ) &&
        (
                cd "$cli" &&
@@ -356,7 +356,7 @@ test_expect_success 'merge conflict handling still works' '
                p4 add -t ktext merge2.c &&
                p4 submit -d "add merge test file"
        ) &&
-       "$GITP4" clone --dest="$git" //depot &&
+       git p4 clone --dest="$git" //depot &&
        (
                cd "$git" &&
                sed -e "/Hello/d" merge2.c >merge2.c.tmp &&
@@ -374,7 +374,7 @@ test_expect_success 'merge conflict handling still works' '
                test -f merge2.c &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.attemptRCSCleanup true &&
-               !(echo "s" | "$GITP4" submit) &&
+               !(echo "s" | git p4 submit) &&
                git rebase --skip &&
                ! test -f merge2.c
        )
diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh
new file mode 100755 (executable)
index 0000000..fb00ffa
--- /dev/null
@@ -0,0 +1,202 @@
+#!/bin/sh
+
+test_description='git p4 label tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+# Basic p4 label import tests.
+#
+test_expect_success 'basic p4 labels' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$cli" &&
+               mkdir -p main &&
+
+               echo f1 >main/f1 &&
+               p4 add main/f1 &&
+               p4 submit -d "main/f1" &&
+
+               echo f2 >main/f2 &&
+               p4 add main/f2 &&
+               p4 submit -d "main/f2" &&
+
+               echo f3 >main/file_with_\$metachar &&
+               p4 add main/file_with_\$metachar &&
+               p4 submit -d "file with metachar" &&
+
+               p4 tag -l TAG_F1_ONLY main/f1 &&
+               p4 tag -l TAG_WITH\$_SHELL_CHAR main/... &&
+               p4 tag -l this_tag_will_be\ skipped main/... &&
+
+               echo f4 >main/f4 &&
+               p4 add main/f4 &&
+               p4 submit -d "main/f4" &&
+
+               p4 label -i <<-EOF &&
+               Label: TAG_LONG_LABEL
+               Description:
+                  A Label first line
+                  A Label second line
+               View:   //depot/...
+               EOF
+
+               p4 tag -l TAG_LONG_LABEL ... &&
+
+               p4 labels ... &&
+
+               git p4 clone --dest="$git" //depot@all &&
+               cd "$git" &&
+               git config git-p4.labelImportRegexp ".*TAG.*" &&
+               git p4 sync --import-labels --verbose &&
+
+               git tag &&
+               git tag >taglist &&
+               test_line_count = 3 taglist &&
+
+               cd main &&
+               git checkout TAG_F1_ONLY &&
+               ! test -f f2 &&
+               git checkout TAG_WITH\$_SHELL_CHAR &&
+               test -f f1 && test -f f2 && test -f file_with_\$metachar &&
+
+               git show TAG_LONG_LABEL | grep -q "A Label second line"
+       )
+'
+# Test some label corner cases:
+#
+# - two tags on the same file; both should be available
+# - a tag that is only on one file; this kind of tag
+#   cannot be imported (at least not easily).
+
+test_expect_success 'two labels on the same changelist' '
+       test_when_finished cleanup_git &&
+       (
+               cd "$cli" &&
+               mkdir -p main &&
+
+               p4 edit main/f1 main/f2 &&
+               echo "hello world" >main/f1 &&
+               echo "not in the tag" >main/f2 &&
+               p4 submit -d "main/f[12]: testing two labels" &&
+
+               p4 tag -l TAG_F1_1 main/... &&
+               p4 tag -l TAG_F1_2 main/... &&
+
+               p4 labels ... &&
+
+               git p4 clone --dest="$git" //depot@all &&
+               cd "$git" &&
+               git p4 sync --import-labels &&
+
+               git tag | grep TAG_F1 &&
+               git tag | grep -q TAG_F1_1 &&
+               git tag | grep -q TAG_F1_2 &&
+
+               cd main &&
+
+               git checkout TAG_F1_1 &&
+               ls &&
+               test -f f1 &&
+
+               git checkout TAG_F1_2 &&
+               ls &&
+               test -f f1
+       )
+'
+
+# Export some git tags to p4
+test_expect_success 'export git tags to p4' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               git tag -m "A tag created in git:xyzzy" GIT_TAG_1 &&
+               echo "hello world" >main/f10 &&
+               git add main/f10 &&
+               git commit -m "Adding file for export test" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit &&
+               git tag -m "Another git tag" GIT_TAG_2 &&
+               git tag LIGHTWEIGHT_TAG &&
+               git p4 rebase --import-labels --verbose &&
+               git p4 submit --export-labels --verbose
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync ... &&
+               p4 labels ... | grep GIT_TAG_1 &&
+               p4 labels ... | grep GIT_TAG_2 &&
+               p4 labels ... | grep LIGHTWEIGHT_TAG &&
+               p4 label -o GIT_TAG_1 | grep "tag created in git:xyzzy" &&
+               p4 sync ...@GIT_TAG_1 &&
+               ! test -f main/f10
+               p4 sync ...@GIT_TAG_2 &&
+               test -f main/f10
+       )
+'
+
+# Export a tag from git where an affected file is deleted later on
+# Need to create git tags after rebase, since only then can the
+# git commits be mapped to p4 changelists.
+test_expect_success 'export git tags to p4 with deletion' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               git p4 sync --import-labels &&
+               echo "deleted file" >main/deleted_file &&
+               git add main/deleted_file &&
+               git commit -m "create deleted file" &&
+               git rm main/deleted_file &&
+               echo "new file" >main/f11 &&
+               git add main/f11 &&
+               git commit -m "delete the deleted file" &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit &&
+               git p4 rebase --import-labels --verbose &&
+               git tag -m "tag on deleted file" GIT_TAG_ON_DELETED HEAD~1 &&
+               git tag -m "tag after deletion" GIT_TAG_AFTER_DELETION HEAD &&
+               git p4 submit --export-labels --verbose
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync ... &&
+               p4 sync ...@GIT_TAG_ON_DELETED &&
+               test -f main/deleted_file &&
+               p4 sync ...@GIT_TAG_AFTER_DELETION &&
+               ! test -f main/deleted_file &&
+               echo "checking label contents" &&
+               p4 label -o GIT_TAG_ON_DELETED | grep "tag on deleted file"
+       )
+'
+
+# Create a tag in git that cannot be exported to p4
+test_expect_success 'tag that cannot be exported' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               git checkout -b a_branch &&
+               echo "hello" >main/f12 &&
+               git add main/f12 &&
+               git commit -m "adding f12" &&
+               git tag -m "tag on a_branch" GIT_TAG_ON_A_BRANCH &&
+               git checkout master &&
+               git p4 submit --export-labels
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync ... &&
+               !(p4 labels | grep GIT_TAG_ON_A_BRANCH)
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
new file mode 100755 (executable)
index 0000000..5bda6b6
--- /dev/null
@@ -0,0 +1,243 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Felipe Contreras
+#
+
+if test -n "$BASH" && test -z "$POSIXLY_CORRECT"; then
+       # we are in full-on bash mode
+       true
+elif type bash >/dev/null 2>&1; then
+       # execute in full-on bash mode
+       unset POSIXLY_CORRECT
+       exec bash "$0" "$@"
+else
+       echo '1..0 #SKIP skipping bash completion tests; bash not available'
+       exit 0
+fi
+
+test_description='test bash completion'
+
+. ./test-lib.sh
+
+complete ()
+{
+       # do nothing
+       return 0
+}
+
+. "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
+
+# We don't need this function to actually join words or do anything special.
+# Also, it's cleaner to avoid touching bash's internal completion variables.
+# So let's override it with a minimal version for testing purposes.
+_get_comp_words_by_ref ()
+{
+       while [ $# -gt 0 ]; do
+               case "$1" in
+               cur)
+                       cur=${_words[_cword]}
+                       ;;
+               prev)
+                       prev=${_words[_cword-1]}
+                       ;;
+               words)
+                       words=("${_words[@]}")
+                       ;;
+               cword)
+                       cword=$_cword
+                       ;;
+               esac
+               shift
+       done
+}
+
+print_comp ()
+{
+       local IFS=$'\n'
+       echo "${COMPREPLY[*]}" > out
+}
+
+run_completion ()
+{
+       local -a COMPREPLY _words
+       local _cword
+       _words=( $1 )
+       (( _cword = ${#_words[@]} - 1 ))
+       _git && print_comp
+}
+
+test_completion ()
+{
+       test $# -gt 1 && echo "$2" > expected
+       run_completion "$@" &&
+       test_cmp expected out
+}
+
+newline=$'\n'
+
+test_expect_success '__gitcomp - trailing space - options' '
+       sed -e "s/Z$//" >expected <<-\EOF &&
+       --reuse-message=Z
+       --reedit-message=Z
+       --reset-author Z
+       EOF
+       (
+               local -a COMPREPLY &&
+               cur="--re" &&
+               __gitcomp "--dry-run --reuse-message= --reedit-message=
+                               --reset-author" &&
+               IFS="$newline" &&
+               echo "${COMPREPLY[*]}" > out
+       ) &&
+       test_cmp expected out
+'
+
+test_expect_success '__gitcomp - trailing space - config keys' '
+       sed -e "s/Z$//" >expected <<-\EOF &&
+       branch.Z
+       branch.autosetupmerge Z
+       branch.autosetuprebase Z
+       browser.Z
+       EOF
+       (
+               local -a COMPREPLY &&
+               cur="br" &&
+               __gitcomp "branch. branch.autosetupmerge
+                               branch.autosetuprebase browser." &&
+               IFS="$newline" &&
+               echo "${COMPREPLY[*]}" > out
+       ) &&
+       test_cmp expected out
+'
+
+test_expect_success '__gitcomp - option parameter' '
+       sed -e "s/Z$//" >expected <<-\EOF &&
+       recursive Z
+       resolve Z
+       EOF
+       (
+               local -a COMPREPLY &&
+               cur="--strategy=re" &&
+               __gitcomp "octopus ours recursive resolve subtree
+                       " "" "re" &&
+               IFS="$newline" &&
+               echo "${COMPREPLY[*]}" > out
+       ) &&
+       test_cmp expected out
+'
+
+test_expect_success '__gitcomp - prefix' '
+       sed -e "s/Z$//" >expected <<-\EOF &&
+       branch.maint.merge Z
+       branch.maint.mergeoptions Z
+       EOF
+       (
+               local -a COMPREPLY &&
+               cur="branch.me" &&
+               __gitcomp "remote merge mergeoptions rebase
+                       " "branch.maint." "me" &&
+               IFS="$newline" &&
+               echo "${COMPREPLY[*]}" > out
+       ) &&
+       test_cmp expected out
+'
+
+test_expect_success '__gitcomp - suffix' '
+       sed -e "s/Z$//" >expected <<-\EOF &&
+       branch.master.Z
+       branch.maint.Z
+       EOF
+       (
+               local -a COMPREPLY &&
+               cur="branch.me" &&
+               __gitcomp "master maint next pu
+                       " "branch." "ma" "." &&
+               IFS="$newline" &&
+               echo "${COMPREPLY[*]}" > out
+       ) &&
+       test_cmp expected out
+'
+
+test_expect_success 'basic' '
+       run_completion "git \"\"" &&
+       # built-in
+       grep -q "^add \$" out &&
+       # script
+       grep -q "^filter-branch \$" out &&
+       # plumbing
+       ! grep -q "^ls-files \$" out &&
+
+       run_completion "git f" &&
+       ! grep -q -v "^f" out
+'
+
+test_expect_success 'double dash "git" itself' '
+       sed -e "s/Z$//" >expected <<-\EOF &&
+       --paginate Z
+       --no-pager Z
+       --git-dir=
+       --bare Z
+       --version Z
+       --exec-path Z
+       --exec-path=
+       --html-path Z
+       --info-path Z
+       --work-tree=
+       --namespace=
+       --no-replace-objects Z
+       --help Z
+       EOF
+       test_completion "git --"
+'
+
+test_expect_success 'double dash "git checkout"' '
+       sed -e "s/Z$//" >expected <<-\EOF &&
+       --quiet Z
+       --ours Z
+       --theirs Z
+       --track Z
+       --no-track Z
+       --merge Z
+       --conflict=
+       --orphan Z
+       --patch Z
+       EOF
+       test_completion "git checkout --"
+'
+
+test_expect_success 'general options' '
+       test_completion "git --ver" "--version " &&
+       test_completion "git --hel" "--help " &&
+       sed -e "s/Z$//" >expected <<-\EOF &&
+       --exec-path Z
+       --exec-path=
+       EOF
+       test_completion "git --exe" &&
+       test_completion "git --htm" "--html-path " &&
+       test_completion "git --pag" "--paginate " &&
+       test_completion "git --no-p" "--no-pager " &&
+       test_completion "git --git" "--git-dir=" &&
+       test_completion "git --wor" "--work-tree=" &&
+       test_completion "git --nam" "--namespace=" &&
+       test_completion "git --bar" "--bare " &&
+       test_completion "git --inf" "--info-path " &&
+       test_completion "git --no-r" "--no-replace-objects "
+'
+
+test_expect_success 'general options plus command' '
+       test_completion "git --version check" "checkout " &&
+       test_completion "git --paginate check" "checkout " &&
+       test_completion "git --git-dir=foo check" "checkout " &&
+       test_completion "git --bare check" "checkout " &&
+       test_completion "git --help des" "describe " &&
+       test_completion "git --exec-path=foo check" "checkout " &&
+       test_completion "git --html-path check" "checkout " &&
+       test_completion "git --no-pager check" "checkout " &&
+       test_completion "git --work-tree=foo check" "checkout " &&
+       test_completion "git --namespace=foo check" "checkout " &&
+       test_completion "git --paginate check" "checkout " &&
+       test_completion "git --info-path check" "checkout " &&
+       test_completion "git --no-replace-objects check" "checkout "
+'
+
+test_done
index b7d7100c4e5c060c1956e5d4f372fa1fa6ee8c85..9e2b71132ab7bca267be755999211783c9b77f43 100644 (file)
@@ -615,6 +615,7 @@ case $(uname -s) in
        ;;
 esac
 
+( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
 test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
index 6bcd5b03c078bc532e074ef8e8775a51d0eef219..10afaabbfaed9ee1b4a6a498a2110fb904d48645 100644 (file)
@@ -7,13 +7,14 @@ static const char *usage_msg = "\n"
 
 static void show_dates(char **argv, struct timeval *now)
 {
-       char buf[128];
+       struct strbuf buf = STRBUF_INIT;
 
        for (; *argv; argv++) {
                time_t t = atoi(*argv);
-               show_date_relative(t, 0, now, buf, sizeof(buf));
-               printf("%s -> %s\n", *argv, buf);
+               show_date_relative(t, 0, now, &buf);
+               printf("%s -> %s\n", *argv, buf.buf);
        }
+       strbuf_release(&buf);
 }
 
 static void parse_dates(char **argv, struct timeval *now)
diff --git a/test-mergesort.c b/test-mergesort.c
new file mode 100644 (file)
index 0000000..3f388b4
--- /dev/null
@@ -0,0 +1,52 @@
+#include "cache.h"
+#include "mergesort.h"
+
+struct line {
+       char *text;
+       struct line *next;
+};
+
+static void *get_next(const void *a)
+{
+       return ((const struct line *)a)->next;
+}
+
+static void set_next(void *a, void *b)
+{
+       ((struct line *)a)->next = b;
+}
+
+static int compare_strings(const void *a, const void *b)
+{
+       const struct line *x = a, *y = b;
+       return strcmp(x->text, y->text);
+}
+
+int main(int argc, const char **argv)
+{
+       struct line *line, *p = NULL, *lines = NULL;
+       struct strbuf sb = STRBUF_INIT;
+
+       for (;;) {
+               if (strbuf_getwholeline(&sb, stdin, '\n'))
+                       break;
+               line = xmalloc(sizeof(struct line));
+               line->text = strbuf_detach(&sb, NULL);
+               if (p) {
+                       line->next = p->next;
+                       p->next = line;
+               } else {
+                       line->next = NULL;
+                       lines = line;
+               }
+               p = line;
+       }
+
+       lines = llist_mergesort(lines, get_next, set_next, compare_strings);
+
+       while (lines) {
+               printf("%s", lines->text);
+               lines = lines->next;
+       }
+       return 0;
+}
diff --git a/test-revision-walking.c b/test-revision-walking.c
new file mode 100644 (file)
index 0000000..3ade02c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * test-revision-walking.c: test revision walking API.
+ *
+ * (C) 2012 Heiko Voigt <hvoigt@hvoigt.net>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+
+static void print_commit(struct commit *commit)
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct pretty_print_context ctx = {0};
+       ctx.date_mode = DATE_NORMAL;
+       format_commit_message(commit, " %m %s", &sb, &ctx);
+       printf("%s\n", sb.buf);
+       strbuf_release(&sb);
+}
+
+static int run_revision_walk(void)
+{
+       struct rev_info rev;
+       struct commit *commit;
+       const char *argv[] = {NULL, "--all", NULL};
+       int argc = ARRAY_SIZE(argv) - 1;
+       int got_revision = 0;
+
+       init_revisions(&rev, NULL);
+       setup_revisions(argc, argv, &rev, NULL);
+       if (prepare_revision_walk(&rev))
+               die("revision walk setup failed");
+
+       while ((commit = get_revision(&rev)) != NULL) {
+               print_commit(commit);
+               got_revision = 1;
+       }
+
+       reset_revision_walk();
+       return got_revision;
+}
+
+int main(int argc, char **argv)
+{
+       if (argc < 2)
+               return 1;
+
+       if (!strcmp(argv[1], "run-twice")) {
+               printf("1st\n");
+               if (!run_revision_walk())
+                       return 1;
+               printf("2nd\n");
+               if (!run_revision_walk())
+                       return 1;
+
+               return 0;
+       }
+
+       fprintf(stderr, "check usage\n");
+       return 1;
+}
index f6b3b1fb79468ef85eb9581c7d44ff04c1bb61bc..61c928f6cd85aa9cf60942208423e615c9b99352 100644 (file)
@@ -199,7 +199,7 @@ static struct child_process *get_helper(struct transport *transport)
                        data->import_marks = strbuf_detach(&arg, NULL);
                } else if (mandatory) {
                        die("Unknown mandatory capability %s. This remote "
-                           "helper probably needs newer version of Git.\n",
+                           "helper probably needs newer version of Git.",
                            capname);
                }
        }
@@ -599,7 +599,7 @@ static void push_update_ref_status(struct strbuf *buf,
                status = REF_STATUS_REMOTE_REJECT;
                refname = buf->buf + 6;
        } else
-               die("expected ok/error, helper said '%s'\n", buf->buf);
+               die("expected ok/error, helper said '%s'", buf->buf);
 
        msg = strchr(refname, ' ');
        if (msg) {
index ea9dcb6612d92c123c66c2eee03bc933ccb2b13a..1811b500d92b1120a01d0ac09f86c0218f3d163b 100644 (file)
@@ -11,6 +11,7 @@
 #include "branch.h"
 #include "url.h"
 #include "submodule.h"
+#include "string-list.h"
 
 /* rsync support */
 
@@ -721,6 +722,10 @@ void transport_print_push_status(const char *dest, struct ref *refs,
 {
        struct ref *ref;
        int n = 0;
+       unsigned char head_sha1[20];
+       char *head;
+
+       head = resolve_refdup("HEAD", head_sha1, 1, NULL);
 
        if (verbose) {
                for (ref = refs; ref; ref = ref->next)
@@ -738,8 +743,13 @@ void transport_print_push_status(const char *dest, struct ref *refs,
                    ref->status != REF_STATUS_UPTODATE &&
                    ref->status != REF_STATUS_OK)
                        n += print_one_push_status(ref, dest, n, porcelain);
-               if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
-                       *nonfastforward = 1;
+               if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD &&
+                   *nonfastforward != NON_FF_HEAD) {
+                       if (!strcmp(head, ref->name))
+                               *nonfastforward = NON_FF_HEAD;
+                       else
+                               *nonfastforward = NON_FF_OTHER;
+               }
        }
 }
 
@@ -1004,6 +1014,25 @@ void transport_set_verbosity(struct transport *transport, int verbosity,
                transport->progress = verbosity >= 0 && isatty(2);
 }
 
+static void die_with_unpushed_submodules(struct string_list *needs_pushing)
+{
+       int i;
+
+       fprintf(stderr, "The following submodule paths contain changes that can\n"
+                       "not be found on any remote:\n");
+       for (i = 0; i < needs_pushing->nr; i++)
+               printf("  %s\n", needs_pushing->items[i].string);
+       fprintf(stderr, "\nPlease try\n\n"
+                       "       git push --recurse-submodules=on-demand\n\n"
+                       "or cd to the path and use\n\n"
+                       "       git push\n\n"
+                       "to push them to a remote.\n\n");
+
+       string_list_clear(needs_pushing, 0);
+
+       die("Aborting.");
+}
+
 int transport_push(struct transport *transport,
                   int refspec_nr, const char **refspec, int flags,
                   int *nonfastforward)
@@ -1044,12 +1073,27 @@ int transport_push(struct transport *transport,
                        flags & TRANSPORT_PUSH_MIRROR,
                        flags & TRANSPORT_PUSH_FORCE);
 
-               if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) {
+               if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
                        struct ref *ref = remote_refs;
                        for (; ref; ref = ref->next)
                                if (!is_null_sha1(ref->new_sha1) &&
-                                   check_submodule_needs_pushing(ref->new_sha1,transport->remote->name))
-                                       die("There are unpushed submodules, aborting.");
+                                   !push_unpushed_submodules(ref->new_sha1,
+                                           transport->remote->name))
+                                   die ("Failed to push all needed submodules!");
+               }
+
+               if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
+                             TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) {
+                       struct ref *ref = remote_refs;
+                       struct string_list needs_pushing;
+
+                       memset(&needs_pushing, 0, sizeof(struct string_list));
+                       needs_pushing.strdup_strings = 1;
+                       for (; ref; ref = ref->next)
+                               if (!is_null_sha1(ref->new_sha1) &&
+                                   find_unpushed_submodules(ref->new_sha1,
+                                           transport->remote->name, &needs_pushing))
+                                       die_with_unpushed_submodules(&needs_pushing);
                }
 
                push_ret = transport->push_refs(transport, remote_refs, flags);
index ce99ef8b7e1692b6b77bd7fbdee5858ce4bfc408..b866c126e695810131cdab537b8b994c0c32e14e 100644 (file)
@@ -103,6 +103,7 @@ struct transport {
 #define TRANSPORT_PUSH_SET_UPSTREAM 32
 #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
 #define TRANSPORT_PUSH_PRUNE 128
+#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 
@@ -138,6 +139,8 @@ int transport_set_option(struct transport *transport, const char *name,
 void transport_set_verbosity(struct transport *transport, int verbosity,
        int force_progress);
 
+#define NON_FF_HEAD 1
+#define NON_FF_OTHER 2
 int transport_push(struct transport *connection,
                   int refspec_nr, const char **refspec, int flags,
                   int * nonfastforward);
index 7c9ecf665d062d79e9208875d9bf2577e98f4fb2..bcee99c52e3a9ee8d67f70c577e57bbde8a8b610 100644 (file)
@@ -102,21 +102,28 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
                opts->unpack_rejects[i].strdup_strings = 1;
 }
 
-static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
-       unsigned int set, unsigned int clear)
+static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
+                        unsigned int set, unsigned int clear)
 {
-       unsigned int size = ce_size(ce);
-       struct cache_entry *new = xmalloc(size);
-
        clear |= CE_HASHED | CE_UNHASHED;
 
        if (set & CE_REMOVE)
                set |= CE_WT_REMOVE;
 
+       ce->next = NULL;
+       ce->ce_flags = (ce->ce_flags & ~clear) | set;
+       add_index_entry(&o->result, ce,
+                       ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+}
+
+static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
+       unsigned int set, unsigned int clear)
+{
+       unsigned int size = ce_size(ce);
+       struct cache_entry *new = xmalloc(size);
+
        memcpy(new, ce, size);
-       new->next = NULL;
-       new->ce_flags = (new->ce_flags & ~clear) | set;
-       add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
+       do_add_entry(o, new, set, clear);
 }
 
 /*
@@ -587,7 +594,7 @@ static int unpack_nondirectories(int n, unsigned long mask,
 
        for (i = 0; i < n; i++)
                if (src[i] && src[i] != o->df_conflict_entry)
-                       add_entry(o, src[i], 0, 0);
+                       do_add_entry(o, src[i], 0, 0);
        return 0;
 }
 
@@ -772,7 +779,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
        if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
                return -1;
 
-       if (src[0]) {
+       if (o->merge && src[0]) {
                if (ce_stage(src[0]))
                        mark_ce_used_same_name(src[0], o);
                else
@@ -1020,6 +1027,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
        o->result.initialized = 1;
        o->result.timestamp.sec = o->src_index->timestamp.sec;
        o->result.timestamp.nsec = o->src_index->timestamp.nsec;
+       o->result.version = o->src_index->version;
        o->merge_size = len;
        mark_all_ce_unused(o->src_index);
 
@@ -1785,7 +1793,7 @@ int bind_merge(struct cache_entry **src,
        struct cache_entry *a = src[1];
 
        if (o->merge_size != 1)
-               return error("Cannot do a bind merge of %d trees\n",
+               return error("Cannot do a bind merge of %d trees",
                             o->merge_size);
        if (a && old)
                return o->gently ? -1 :
diff --git a/varint.c b/varint.c
new file mode 100644 (file)
index 0000000..4ed7729
--- /dev/null
+++ b/varint.c
@@ -0,0 +1,29 @@
+#include "varint.h"
+
+uintmax_t decode_varint(const unsigned char **bufp)
+{
+       const unsigned char *buf = *bufp;
+       unsigned char c = *buf++;
+       uintmax_t val = c & 127;
+       while (c & 128) {
+               val += 1;
+               if (!val || MSB(val, 7))
+                       return 0; /* overflow */
+               c = *buf++;
+               val = (val << 7) + (c & 127);
+       }
+       *bufp = buf;
+       return val;
+}
+
+int encode_varint(uintmax_t value, unsigned char *buf)
+{
+       unsigned char varint[16];
+       unsigned pos = sizeof(varint) - 1;
+       varint[pos] = value & 127;
+       while (value >>= 7)
+               varint[--pos] = 128 | (--value & 127);
+       if (buf)
+               memcpy(buf, varint + pos, sizeof(varint) - pos);
+       return sizeof(varint) - pos;
+}
diff --git a/varint.h b/varint.h
new file mode 100644 (file)
index 0000000..0321195
--- /dev/null
+++ b/varint.h
@@ -0,0 +1,9 @@
+#ifndef VARINT_H
+#define VARINT_H
+
+#include "git-compat-util.h"
+
+extern int encode_varint(uintmax_t, unsigned char *);
+extern uintmax_t decode_varint(const unsigned char **);
+
+#endif /* VARINT_H */
index 644fdc71ba8c665f5dfadd552c3da63f4d8498e9..0899790a3331d82b72ba3e58fd7ff6ff83b38466 100644 (file)
@@ -175,7 +175,7 @@ static void read_props(void)
                int ch;
 
                if (!type || t[1] != ' ')
-                       die("invalid property line: %s\n", t);
+                       die("invalid property line: %s", t);
                len = atoi(&t[2]);
                strbuf_reset(&val);
                buffer_read_binary(&input, &val, len);
@@ -201,7 +201,7 @@ static void read_props(void)
                        strbuf_reset(&key);
                        continue;
                default:
-                       die("invalid property line: %s\n", t);
+                       die("invalid property line: %s", t);
                }
        }
 }
index 85f09df747637b94e0488ad65984c3f97c732034..6ccd0595f43d0ef62bd60a5863804f9a842a4235 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -9,6 +9,18 @@ static void do_nothing(size_t size)
 
 static void (*try_to_free_routine)(size_t size) = do_nothing;
 
+static void memory_limit_check(size_t size)
+{
+       static int limit = -1;
+       if (limit == -1) {
+               const char *env = getenv("GIT_ALLOC_LIMIT");
+               limit = env ? atoi(env) * 1024 : 0;
+       }
+       if (limit && size > limit)
+               die("attempting to allocate %"PRIuMAX" over limit %d",
+                   (intmax_t)size, limit);
+}
+
 try_to_free_t set_try_to_free_routine(try_to_free_t routine)
 {
        try_to_free_t old = try_to_free_routine;
@@ -32,7 +44,10 @@ char *xstrdup(const char *str)
 
 void *xmalloc(size_t size)
 {
-       void *ret = malloc(size);
+       void *ret;
+
+       memory_limit_check(size);
+       ret = malloc(size);
        if (!ret && !size)
                ret = malloc(1);
        if (!ret) {
@@ -79,7 +94,10 @@ char *xstrndup(const char *str, size_t len)
 
 void *xrealloc(void *ptr, size_t size)
 {
-       void *ret = realloc(ptr, size);
+       void *ret;
+
+       memory_limit_check(size);
+       ret = realloc(ptr, size);
        if (!ret && !size)
                ret = realloc(ptr, 1);
        if (!ret) {
@@ -95,7 +113,10 @@ void *xrealloc(void *ptr, size_t size)
 
 void *xcalloc(size_t nmemb, size_t size)
 {
-       void *ret = calloc(nmemb, size);
+       void *ret;
+
+       memory_limit_check(size * nmemb);
+       ret = calloc(nmemb, size);
        if (!ret && (!nmemb || !size))
                ret = calloc(1, 1);
        if (!ret) {
index bc268ceda0c639fdefd5ba70552a2ea250ddb8e6..dd6d8c41068e6664cff1c07100e482e7ca9f0959 100644 (file)
@@ -11,6 +11,7 @@
 #include "remote.h"
 #include "refs.h"
 #include "submodule.h"
+#include "column.h"
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -641,6 +642,8 @@ static void wt_status_print_other(struct wt_status *s,
 {
        int i;
        struct strbuf buf = STRBUF_INIT;
+       static struct string_list output = STRING_LIST_INIT_DUP;
+       struct column_options copts;
 
        if (!l->nr)
                return;
@@ -649,12 +652,33 @@ static void wt_status_print_other(struct wt_status *s,
 
        for (i = 0; i < l->nr; i++) {
                struct string_list_item *it;
+               const char *path;
                it = &(l->items[i]);
+               path = quote_path(it->string, strlen(it->string),
+                                 &buf, s->prefix);
+               if (column_active(s->colopts)) {
+                       string_list_append(&output, path);
+                       continue;
+               }
                status_printf(s, color(WT_STATUS_HEADER, s), "\t");
                status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
-                       "%s\n", quote_path(it->string, strlen(it->string),
-                                           &buf, s->prefix));
+                                  "%s\n", path);
        }
+
+       strbuf_release(&buf);
+       if (!column_active(s->colopts))
+               return;
+
+       strbuf_addf(&buf, "%s#\t%s",
+                   color(WT_STATUS_HEADER, s),
+                   color(WT_STATUS_UNTRACKED, s));
+       memset(&copts, 0, sizeof(copts));
+       copts.padding = 1;
+       copts.indent = buf.buf;
+       if (want_color(s->use_color))
+               copts.nl = GIT_COLOR_RESET "\n";
+       print_columns(&output, s->colopts, &copts);
+       string_list_clear(&output, 0);
        strbuf_release(&buf);
 }
 
index ab3c7cc8a130002c855a4f398c8ee9372c79144b..f4c949bed21093189d2f5cefb61ec6c9c7b8abdd 100644 (file)
@@ -56,6 +56,7 @@ struct wt_status {
        enum untracked_status_type show_untracked_files;
        const char *ignore_submodule_arg;
        char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
+       int colopts;
        int null_termination;
        int show_branch;
 
index 00d36c3ac7a7642831d2ffc49647caf77a4d066c..09215afe6e0250fd29897390f074234a7b89f4d8 100644 (file)
@@ -32,14 +32,12 @@ extern "C" {
 #define XDF_IGNORE_WHITESPACE (1 << 2)
 #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
 #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
-#define XDF_PATIENCE_DIFF (1 << 5)
-#define XDF_HISTOGRAM_DIFF (1 << 6)
 #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
 
-#define XDL_PATCH_NORMAL '-'
-#define XDL_PATCH_REVERSE '+'
-#define XDL_PATCH_MODEMASK ((1 << 8) - 1)
-#define XDL_PATCH_IGNOREBSPACE (1 << 8)
+#define XDF_PATIENCE_DIFF (1 << 5)
+#define XDF_HISTOGRAM_DIFF (1 << 6)
+#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
+#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
 
 #define XDL_EMIT_FUNCNAMES (1 << 0)
 #define XDL_EMIT_COMMON (1 << 1)
index 75a39227501715504cdd12ccc1b4854568a54ad7..bc889e87894fbd261db8aaf29723e8df35f913da 100644 (file)
@@ -328,10 +328,10 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
        xdalgoenv_t xenv;
        diffdata_t dd1, dd2;
 
-       if (xpp->flags & XDF_PATIENCE_DIFF)
+       if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF)
                return xdl_do_patience_diff(mf1, mf2, xpp, xe);
 
-       if (xpp->flags & XDF_HISTOGRAM_DIFF)
+       if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
                return xdl_do_histogram_diff(mf1, mf2, xpp, xe);
 
        if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
index 18f6f997c321b5ac1f4d4211a4d448dc8542c22f..bf99787c3e4c791426311495dda9d4da81cbb571 100644 (file)
@@ -252,7 +252,7 @@ static int fall_back_to_classic_diff(struct histindex *index,
                int line1, int count1, int line2, int count2)
 {
        xpparam_t xpp;
-       xpp.flags = index->xpp->flags & ~XDF_HISTOGRAM_DIFF;
+       xpp.flags = index->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK;
 
        return xdl_fall_back_diff(index->env, &xpp,
                                  line1, count1, line2, count2);
index fdd7d0263f576a8dc1a8e791ef50f8dbe25c7ee5..04e1a1ab2a863814df3b9a91d4e854704d47f3f5 100644 (file)
@@ -288,7 +288,7 @@ static int fall_back_to_classic_diff(struct hashmap *map,
                int line1, int count1, int line2, int count2)
 {
        xpparam_t xpp;
-       xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF;
+       xpp.flags = map->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK;
 
        return xdl_fall_back_diff(map->env, &xpp,
                                  line1, count1, line2, count2);
index e419f4f726019a5b0365c589285439fb3bfb8db2..63a22c630e521969b08c8ecb1ce9fa3e0f3ff513 100644 (file)
@@ -181,7 +181,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
        if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *))))
                goto abort;
 
-       if (xpp->flags & XDF_HISTOGRAM_DIFF)
+       if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
                hbits = hsize = 0;
        else {
                hbits = xdl_hashbits((unsigned int) narec);
@@ -209,8 +209,8 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_
                        crec->ha = hav;
                        recs[nrec++] = crec;
 
-                       if (!(xpp->flags & XDF_HISTOGRAM_DIFF) &&
-                               xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+                       if ((XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+                           xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
                                goto abort;
                }
        }
@@ -273,16 +273,15 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
         * (nrecs) will be updated correctly anyway by
         * xdl_prepare_ctx().
         */
-       sample = xpp->flags & XDF_HISTOGRAM_DIFF ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1;
+       sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
+                 ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
 
        enl1 = xdl_guess_lines(mf1, sample) + 1;
        enl2 = xdl_guess_lines(mf2, sample) + 1;
 
-       if (!(xpp->flags & XDF_HISTOGRAM_DIFF) &&
-               xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) {
-
+       if (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF &&
+           xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
                return -1;
-       }
 
        if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
 
@@ -296,9 +295,9 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
                return -1;
        }
 
-       if (!(xpp->flags & XDF_PATIENCE_DIFF) &&
-                       !(xpp->flags & XDF_HISTOGRAM_DIFF) &&
-                       xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
+       if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
+           (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+           xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
 
                xdl_free_ctx(&xe->xdf2);
                xdl_free_ctx(&xe->xdf1);
index 0de084e53f5144153373cc66cae7b523b4ae2812..1b3b471ac86a30474e3acabea3d2df3b6da072d8 100644 (file)
@@ -20,6 +20,8 @@
  *
  */
 
+#include <limits.h>
+#include <assert.h>
 #include "xinclude.h"
 
 
@@ -276,6 +278,109 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
        return ha;
 }
 
+#ifdef XDL_FAST_HASH
+
+#define ONEBYTES       0x0101010101010101ul
+#define NEWLINEBYTES   0x0a0a0a0a0a0a0a0aul
+#define HIGHBITS       0x8080808080808080ul
+
+/* Return the high bit set in the first byte that is a zero */
+static inline unsigned long has_zero(unsigned long a)
+{
+       return ((a - ONEBYTES) & ~a) & HIGHBITS;
+}
+
+static inline long count_masked_bytes(unsigned long mask)
+{
+       if (sizeof(long) == 8) {
+               /*
+                * Jan Achrenius on G+: microoptimized version of
+                * the simpler "(mask & ONEBYTES) * ONEBYTES >> 56"
+                * that works for the bytemasks without having to
+                * mask them first.
+                */
+               return mask * 0x0001020304050608 >> 56;
+       } else {
+               /*
+                * Modified Carl Chatfield G+ version for 32-bit *
+                *
+                * (a) gives us
+                *   -1 (0, ff), 0 (ffff) or 1 (ffffff)
+                * (b) gives us
+                *   0 for 0, 1 for (ff ffff ffffff)
+                * (a+b+1) gives us
+                *   correct 0-3 bytemask count result
+                */
+               long a = (mask - 256) >> 23;
+               long b = mask & 1;
+               return a + b + 1;
+       }
+}
+
+unsigned long xdl_hash_record(char const **data, char const *top, long flags)
+{
+       unsigned long hash = 5381;
+       unsigned long a = 0, mask = 0;
+       char const *ptr = *data;
+       char const *end = top - sizeof(unsigned long) + 1;
+
+       if (flags & XDF_WHITESPACE_FLAGS)
+               return xdl_hash_record_with_whitespace(data, top, flags);
+
+       ptr -= sizeof(unsigned long);
+       do {
+               hash += hash << 5;
+               hash ^= a;
+               ptr += sizeof(unsigned long);
+               if (ptr >= end)
+                       break;
+               a = *(unsigned long *)ptr;
+               /* Do we have any '\n' bytes in this word? */
+               mask = has_zero(a ^ NEWLINEBYTES);
+       } while (!mask);
+
+       if (ptr >= end) {
+               /*
+                * There is only a partial word left at the end of the
+                * buffer. Because we may work with a memory mapping,
+                * we have to grab the rest byte by byte instead of
+                * blindly reading it.
+                *
+                * To avoid problems with masking in a signed value,
+                * we use an unsigned char here.
+                */
+               const char *p;
+               for (p = top - 1; p >= ptr; p--)
+                       a = (a << 8) + *((const unsigned char *)p);
+               mask = has_zero(a ^ NEWLINEBYTES);
+               if (!mask)
+                       /*
+                        * No '\n' found in the partial word.  Make a
+                        * mask that matches what we read.
+                        */
+                       mask = 1UL << (8 * (top - ptr) + 7);
+       }
+
+       /* The mask *below* the first high bit set */
+       mask = (mask - 1) & ~mask;
+       mask >>= 7;
+       hash += hash << 5;
+       hash ^= a & mask;
+
+       /* Advance past the last (possibly partial) word */
+       ptr += count_masked_bytes(mask);
+
+       if (ptr < top) {
+               assert(*ptr == '\n');
+               ptr++;
+       }
+
+       *data = ptr;
+
+       return hash;
+}
+
+#else /* XDL_FAST_HASH */
 
 unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
        unsigned long ha = 5381;
@@ -293,6 +398,7 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
        return ha;
 }
 
+#endif /* XDL_FAST_HASH */
 
 unsigned int xdl_hashbits(unsigned int size) {
        unsigned int val = 1, bits = 0;