Merge branch 'jc/mergetool-tool-help'
authorJunio C Hamano <gitster@pobox.com>
Tue, 24 Jul 2012 21:05:26 +0000 (14:05 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 24 Jul 2012 21:05:26 +0000 (14:05 -0700)
"git mergetool" did not support --tool-help option to give the list
of supported backends, like "git difftool" does.

* jc/mergetool-tool-help:
mergetool: support --tool-help option like difftool does

173 files changed:
.gitignore
Documentation/Makefile
Documentation/RelNotes/1.7.12.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/git-apply.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-config.txt
Documentation/git-credential.txt [new file with mode: 0644]
Documentation/git-daemon.txt
Documentation/git-merge.txt
Documentation/git-p4.txt
Documentation/git-rebase.txt
Documentation/git-rev-parse.txt
Documentation/gitattributes.txt
Documentation/gitignore.txt
Documentation/glossary-content.txt
Documentation/rev-list-options.txt
Documentation/technical/api-credentials.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
RelNotes
advice.c
attr.c
block-sha1/sha1.c
branch.c
builtin.h
builtin/apply.c
builtin/blame.c
builtin/cat-file.c
builtin/checkout.c
builtin/clone.c
builtin/commit-tree.c
builtin/commit.c
builtin/config.c
builtin/credential.c [new file with mode: 0644]
builtin/help.c
builtin/index-pack.c
builtin/init-db.c
builtin/log.c
builtin/pack-objects.c
builtin/reflog.c
builtin/reset.c
builtin/rev-parse.c
builtin/update-index.c
cache.h
commit.c
compat/precompose_utf8.c [new file with mode: 0644]
compat/precompose_utf8.h [new file with mode: 0644]
config.c
config.mak.in
configure.ac
connect.c
contrib/completion/git-completion.bash [changed mode: 0755->0644]
contrib/completion/git-prompt.sh [new file with mode: 0644]
contrib/emacs/git-blame.el
contrib/mw-to-git/Makefile [new file with mode: 0644]
contrib/mw-to-git/git-remote-mediawiki
contrib/mw-to-git/t/.gitignore [new file with mode: 0644]
contrib/mw-to-git/t/Makefile [new file with mode: 0644]
contrib/mw-to-git/t/README [new file with mode: 0644]
contrib/mw-to-git/t/install-wiki.sh [new file with mode: 0755]
contrib/mw-to-git/t/install-wiki/.gitignore [new file with mode: 0644]
contrib/mw-to-git/t/install-wiki/LocalSettings.php [new file with mode: 0644]
contrib/mw-to-git/t/install-wiki/db_install.php [new file with mode: 0644]
contrib/mw-to-git/t/push-pull-tests.sh [new file with mode: 0644]
contrib/mw-to-git/t/t9360-mw-to-git-clone.sh [new file with mode: 0755]
contrib/mw-to-git/t/t9361-mw-to-git-push-pull.sh [new file with mode: 0755]
contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh [new file with mode: 0755]
contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh [new file with mode: 0755]
contrib/mw-to-git/t/t9364-pull-by-rev.sh [new file with mode: 0755]
contrib/mw-to-git/t/test-gitmw-lib.sh [new file with mode: 0755]
contrib/mw-to-git/t/test-gitmw.pl [new file with mode: 0755]
contrib/mw-to-git/t/test.config [new file with mode: 0644]
credential.c
credential.h
date.c
diff.c
dir.c
dir.h
environment.c
git-am.sh
git-compat-util.h
git-difftool.perl
git-filter-branch.sh
git-p4.py
git-rebase--am.sh
git-rebase--interactive.sh
git-rebase--merge.sh
git-rebase.sh
git-submodule.sh
git-svn.perl
git.c
gitweb/gitweb.perl
help.c
http.c
notes-merge.c
parse-options.c
path.c
perl/Makefile
pkt-line.c
pkt-line.h
po/de.po
po/git.pot
po/sv.po
po/vi.po
po/zh_CN.po
read-cache.c
rerere.c
revision.c
revision.h
setup.c
sha1_name.c
t/lib-bash.sh [new file with mode: 0644]
t/lib-credential.sh
t/lib-git-p4.sh
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/t0300-credentials.sh
t/t1050-large.sh
t/t1100-commit-tree-options.sh
t/t1304-default-acl.sh
t/t1306-xdg-files.sh [new file with mode: 0755]
t/t1512-rev-parse-disambiguation.sh [new file with mode: 0755]
t/t3404-rebase-interactive.sh
t/t3405-rebase-malformed.sh
t/t3412-rebase-root.sh
t/t3910-mac-os-precompose.sh [new file with mode: 0755]
t/t4012-diff-binary.sh
t/t4020-diff-external.sh
t/t4108-apply-threeway.sh [new file with mode: 0755]
t/t4117-apply-reject.sh
t/t5300-pack-object.sh
t/t5512-ls-remote.sh
t/t5701-clone-local.sh
t/t7003-filter-branch.sh
t/t7060-wtstatus.sh
t/t7400-submodule-basic.sh
t/t7403-submodule-sync.sh
t/t7406-submodule-update.sh
t/t7512-status-help.sh [new file with mode: 0755]
t/t9800-git-p4-basic.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/t9810-git-p4-rcs.sh
t/t9812-git-p4-wildcards.sh [new file with mode: 0755]
t/t9813-git-p4-preserve-users.sh [new file with mode: 0755]
t/t9814-git-p4-rename.sh [new file with mode: 0755]
t/t9902-completion.sh
t/t9903-bash-prompt.sh [new file with mode: 0755]
t/test-lib-functions.sh
test-credential.c [deleted file]
test-line-buffer.c
test-svn-fe.c
tree.c
unpack-trees.c
utf8.c
utf8.h
vcs-svn/fast_export.c
vcs-svn/fast_export.h
vcs-svn/line_buffer.c
vcs-svn/line_buffer.h
vcs-svn/sliding_window.c
vcs-svn/svndiff.c
vcs-svn/svndump.c
version.c [new file with mode: 0644]
version.h [new file with mode: 0644]
wt-status.c
wt-status.h
index bf66648e2c5f59cb92470dbda546c5348bcc85ef..bb5c91e712349686ccd008d9ccfd5fa8c2320834 100644 (file)
@@ -2,6 +2,9 @@
 /GIT-CFLAGS
 /GIT-LDFLAGS
 /GIT-GUI-VARS
+/GIT-PREFIX
+/GIT-SCRIPT-DEFINES
+/GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
 /git
@@ -31,6 +34,7 @@
 /git-commit-tree
 /git-config
 /git-count-objects
+/git-credential
 /git-credential-cache
 /git-credential-cache--daemon
 /git-credential-store
 /gitweb/static/gitweb.js
 /gitweb/static/gitweb.min.*
 /test-chmtime
-/test-credential
 /test-ctype
 /test-date
 /test-delta
index 5d76a840781bcd4b48157057bb439bdac691766e..063fa696c9eff617e4fc6ad37f0b6e49d32d58f9 100644 (file)
@@ -65,12 +65,6 @@ endif
 -include ../config.mak.autogen
 -include ../config.mak
 
-#
-# For asciidoc ...
-#      -7.1.2, set ASCIIDOC7
-#      8.0-,   no extra settings are needed
-#
-
 #
 # For docbook-xsl ...
 #      -1.68.1,        no extra settings are needed?
@@ -81,9 +75,6 @@ endif
 #      1.73.0-,        no extra settings are needed
 #
 
-ifndef ASCIIDOC7
-ASCIIDOC_EXTRA += -a asciidoc7compatible
-endif
 ifdef DOCBOOK_XSL_172
 ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
 MANPAGE_XSL = manpage-1.72.xsl
@@ -134,15 +125,6 @@ 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:
-#   http://marc.theaimsgroup.com/?l=git&m=111558757202243&w=2
-#
-# Until that version is released you may have to apply the patch
-# yourself - yes, all 6 characters of it!
-#
-
 QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
 QUIET_SUBDIR1  =
 
diff --git a/Documentation/RelNotes/1.7.12.txt b/Documentation/RelNotes/1.7.12.txt
new file mode 100644 (file)
index 0000000..f86e537
--- /dev/null
@@ -0,0 +1,152 @@
+Git v1.7.12 Release Notes
+=========================
+
+Updates since v1.7.11
+---------------------
+
+UI, Workflows & Features
+
+ * Git can be told to normalize pathnames it read from readdir(3) and
+   all arguments it got from the command line into precomposed UTF-8
+   (assuming that they come as decomposed UTF-8), in order to work
+   around issues on Mac OS.
+
+   I think there still are other places that need conversion
+   (e.g. paths that are read from stdin for some commands), but this
+   should be a good first step in the right direction.
+
+ * Per-user $HOME/.gitconfig file can optionally be stored in
+   $HOME/.config/git/config instead, which is in line with XDG.
+
+ * The value of core.attributesfile and core.excludesfile default to
+   $HOME/.config/attributes and $HOME/.config/ignore respectively when
+   these files exist.
+
+ * Logic to disambiguate abbreviated object names have been taught to
+   take advantage of object types that are expected in the context,
+   e.g. XXXXXX in the "git describe" output v1.2.3-gXXXXXX must be a
+   commit object, not a blob nor a tree.  This will help us prolong
+   the lifetime of abbreviated object names.
+
+ * "git apply" learned to wiggle the base version and perform three-way
+   merge when a patch does not exactly apply to the version you have.
+
+ * Scripted Porcelain writers now have access to the credential API via
+   the "git credential" plumbing command.
+
+ * "git help" used to always default to "man" format even on platforms
+   where "man" viewer is not widely available.
+
+ * "git clone --local $path" started its life as an experiment to
+   optionally use link/copy when cloning a repository on the disk, but
+   we didn't deprecate it after we made the option a no-op to always
+   use the optimization.  The command learned "--no-local" option to
+   turn this off, as a more explicit alternative over use of file://
+   URL.
+
+ * "git fetch" and friends used to say "remote side hung up
+   unexpectedly" when they failed to get response they expect from the
+   other side, but one common reason why they don't get expected
+   response is that the remote repository does not exist or cannot be
+   read. The error message in this case was updated to give better
+   hints to the user.
+
+ * git native protocol agents learned to show software version over
+   the wire, so that the server log can be examined to see the vintage
+   distribution of clients.
+
+ * "git help -w $cmd" can show HTML version of documentation for
+   "git-$cmd" by setting help.htmlpath to somewhere other than the
+   default location where the build procedure installs them locally;
+   the variable can even point at a http:// URL.
+
+ * "git rebase [-i] --root $tip" can now be used to rewrite all the
+   history leading to "$tip" down to the root commit.
+
+ * "git rebase -i" learned "-x <cmd>" to insert "exec <cmd>" after
+   each commit in the resulting history.
+
+ * "git status" gives finer classification to various states of paths
+   in conflicted state and offer advice messages in its output.
+
+ * "git submodule" learned to deal with nested submodule structure
+   where a module is contained within a module whose origin is
+   specified as a relative URL to its superproject's origin.
+
+ * A rather heavy-ish "git completion" script has been split to create
+   a separate "git prompting" script, to help lazy-autoloading of the
+   completion part while making prompting part always available.
+
+ * "gitweb" pays attention to various forms of credits that are
+   similar to "Signed-off-by:" lines in the commit objects and
+   highlights them accordingly.
+
+
+Foreign Interface
+
+ * "mediawiki" remote helper (in contrib/) learned to handle file
+   attachments.
+
+ * "git p4" now uses "Jobs:" and "p4 move" when appropriate.
+
+ * vcs-svn has been updated to clean-up compilation, lift 32-bit
+   limitations, etc.
+
+
+Performance, Internal Implementation, etc. (please report possible regressions)
+
+ * Some tests showed false failures caused by a bug in ecryptofs.
+
+ * We no longer use AsciiDoc7 syntax in our documentation and favor a
+   more modern style.
+
+ * "git am --rebasing" codepath was taught to grab authorship, log
+   message and the patch text directly out of existing commits.  This
+   will help rebasing commits that have confusing "diff" output in
+   their log messages.
+
+ * "git index-pack" and "git pack-objects" use streaming API to read
+   from the object store to avoid having to hold a large blob object
+   in-core while they are doing their thing.
+
+ * Code to match paths with exclude patterns learned to avoid calling
+   fnmatch() by comparing fixed leading substring literally when
+   possible.
+
+ * "git log -n 1 -- rarely-touched-path" was spending unnecessary
+   cycles after showing the first change to find the next one, only to
+   discard it.
+
+
+Also contains minor documentation updates and code clean-ups.
+
+
+Fixes since v1.7.11
+-------------------
+
+Unless otherwise noted, all the fixes since v1.7.11 in the maintenance
+releases are contained in this release (see release notes to them for
+details).
+
+ * "git commit-tree" learned a more natural "-p <parent> <tree>" order
+   of arguments long time ago, but recently forgot it by mistake.
+   (merge 4b7518a kk/maint-commit-tree later to maint).
+
+ * "git diff --no-ext-diff" did not output anything for a typechange
+   filepair when GIT_EXTERNAL_DIFF is in effect.
+   (merge c12f82a jv/maint-no-ext-diff later to maint).
+
+ * When "git am" failed, old timers knew to check .git/rebase-apply/patch
+   to see what went wrong, but we never told the users about it.
+   (merge 14bf2d5 pg/maint-1.7.9-am-where-is-patch later to maint).
+
+ * When "git submodule add" clones a submodule repository, it can get
+   confused where to store the resulting submodule repository in the
+   superproject's .git/ directory when there is a symbolic link in the
+   path to the current directory.
+   (merge 6eafa6d jl/maint-1.7.10-recurse-submodules-with-symlink later to maint).
+
+ * In 1.7.9 era, we taught "git rebase" about the raw timestamp format
+   but we did not teach the same trick to "filter-branch", which rolled
+   a similar logic on its own.
+   (merge 44b85e89 jc/maint-filter-branch-epoch-date later to maint).
index 915cb5a547896966377e9f39059527e3142b27c8..a95e5a4ac9a7e35623ada63d7919b193d7f86a6e 100644 (file)
@@ -159,9 +159,10 @@ advice.*::
                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
-               when writing commit messages.
+               Show directions on how to proceed from the current
+               state in the output of linkgit:git-status[1] and in
+               the template shown when writing commit messages in
+               linkgit:git-commit[1].
        commitBeforeMerge::
                Advice shown when linkgit:git-merge[1] refuses to
                merge to avoid overwriting local changes.
@@ -176,6 +177,9 @@ advice.*::
                Advice shown when you used linkgit:git-checkout[1] to
                move to the detach HEAD state, to instruct how to create
                a local branch after the fact.
+       amWorkDir::
+               Advice that shows the location of the patch file when
+               linkgit:git-am[1] fails to apply it.
 --
 
 core.fileMode::
@@ -210,6 +214,15 @@ The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
 will probe and set core.ignorecase true if appropriate when the repository
 is created.
 
+core.precomposeunicode::
+       This option is only used by Mac OS implementation of git.
+       When core.precomposeunicode=true, git reverts the unicode decomposition
+       of filenames done by Mac OS. This is useful when sharing a repository
+       between Mac OS and Linux or Windows.
+       (Git for Windows 1.7.10 or higher is needed, or git under cygwin 1.7).
+       When false, file names are handled fully transparent by git,
+       which is backward compatible with older versions of git.
+
 core.trustctime::
        If false, the ctime differences between the index and the
        working tree are ignored; useful when the inode change time
@@ -483,7 +496,9 @@ core.excludesfile::
        '.git/info/exclude', git looks into this file for patterns
        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].
+       home directory. Its default value is $XDG_CONFIG_HOME/git/ignore.
+       If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/ignore
+       is used instead. See linkgit:gitignore[5].
 
 core.askpass::
        Some commands (e.g. svn and http interfaces) that interactively
@@ -498,7 +513,9 @@ core.attributesfile::
        In addition to '.gitattributes' (per-directory) and
        '.git/info/attributes', git looks into this file for attributes
        (see linkgit:gitattributes[5]). Path expansions are made the same
-       way as for `core.excludesfile`.
+       way as for `core.excludesfile`. Its default value is
+       $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME is either not
+       set or empty, $HOME/.config/git/attributes is used instead.
 
 core.editor::
        Commands such as `commit` and `tag` that lets you edit
@@ -880,7 +897,7 @@ column.ui::
        make equal size columns
 --
 +
-       This option defaults to 'never'.
+This option defaults to 'never'.
 
 column.branch::
        Specify whether to output branch listing in `git branch` in columns.
@@ -1720,6 +1737,7 @@ push.default::
        no refspec is implied by any of the options given on the command
        line. Possible values are:
 +
+--
 * `nothing` - do not push anything.
 * `matching` - push all branches having the same name in both ends.
   This is for those who prepare all the branches into a publishable
@@ -1739,12 +1757,13 @@ push.default::
   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.
+--
++
+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
index afd2c9ae59e9145f377d5c702df27bf861c9b2f5..634b84e4b9a06b58f4c818a314c5cb2d5880e1bd 100644 (file)
@@ -9,7 +9,7 @@ git-apply - Apply a patch to files and/or to the index
 SYNOPSIS
 --------
 [verse]
-'git apply' [--stat] [--numstat] [--summary] [--check] [--index]
+'git apply' [--stat] [--numstat] [--summary] [--check] [--index] [--3way]
          [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
          [--allow-binary-replacement | --binary] [--reject] [-z]
          [-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]
@@ -72,6 +72,15 @@ OPTIONS
        cached data, apply the patch, and store the result in the index
        without using the working tree. This implies `--index`.
 
+-3::
+--3way::
+       When the patch does not apply cleanly, fall back on 3-way merge if
+       the patch records the identity of blobs it is supposed to apply to,
+       and we have those blobs available locally, possibly leaving the
+       conflict markers in the files in the working tree for the user to
+       resolve.  This option implies the `--index` option, and is incompatible
+       with the `--reject` and the `--cached` options.
+
 --build-fake-ancestor=<file>::
        Newer 'git diff' output has embedded 'index information'
        for each blob to help identify the original version that
index 6e22522c4f7e97dbab42b3cbb5b538cdea3b4d74..c1ddd4c2cc78e078a0849fc8fb82f8de0aa7fc9b 100644 (file)
@@ -46,13 +46,18 @@ OPTIONS
        mechanism and clones the repository by making a copy of
        HEAD and everything under objects and refs directories.
        The files under `.git/objects/` directory are hardlinked
-       to save space when possible.  This is now the default when
-       the source repository is specified with `/path/to/repo`
-       syntax, so it essentially is a no-op option.  To force
-       copying instead of hardlinking (which may be desirable
-       if you are trying to make a back-up of your repository),
-       but still avoid the usual "git aware" transport
-       mechanism, `--no-hardlinks` can be used.
+       to save space when possible.
++
+If the repository is specified as a local path (e.g., `/path/to/repo`),
+this is the default, and --local is essentially a no-op.  If the
+repository is specified as a URL, then this flag is ignored (and we
+never use the local optimizations).  Specifying `--no-local` will
+override the default when `/path/to/repo` is given, using the regular
+git transport instead.
++
+To force copying instead of hardlinking (which may be desirable if you
+are trying to make a back-up of your repository), but still avoid the
+usual "git aware" transport mechanism, `--no-hardlinks` can be used.
 
 --no-hardlinks::
        Optimize the cloning process from a repository on a
index ff73286509782bf3c7d6168066358b02830db797..6d5a04c83b6461396b44b3972a539eaae6368a19 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git commit-tree' <tree> [(-p <parent>)...] < changelog
-'git commit-tree' <tree> [(-p <parent>)...] [(-m <message>)...] [(-F <file>)...]
+'git commit-tree' [(-p <parent>)...] [(-m <message>)...] [(-F <file>)...] <tree>
 
 DESCRIPTION
 -----------
index 2d695f619ceff358a3bc7c1e66532cc077ca3065..4622297ec98cddc2e1f2e4417b9eeec11a3c91b1 100644 (file)
@@ -101,12 +101,16 @@ OPTIONS
        When doing a dry-run, give the output in the short-format. See
        linkgit:git-status[1] for details. Implies `--dry-run`.
 
+--branch::
+       Show the branch and tracking info even in short-format.
+
 --porcelain::
        When doing a dry-run, give the output in a porcelain-ready
        format. See linkgit:git-status[1] for details. Implies
        `--dry-run`.
 
 -z::
+--null::
        When showing `short` or `porcelain` status output, terminate
        entries in the status output with NUL, instead of LF. If no
        format is given, implies the `--porcelain` output format.
@@ -189,6 +193,10 @@ OPTIONS
        current tip -- if it was a merge, it will have the parents of
        the current tip as parents -- so the current top commit is
        discarded.
+
+--no-post-rewrite::
+       Bypass the post-rewrite hook.
+
 +
 --
 It is a rough equivalent for:
@@ -399,6 +407,15 @@ This command can run `commit-msg`, `prepare-commit-msg`, `pre-commit`,
 and `post-commit` hooks.  See linkgit:githooks[5] for more
 information.
 
+FILES
+-----
+
+`$GIT_DIR/COMMIT_EDITMSG`::
+       This file contains the commit message of a commit in progress.
+       If `git commit` exits due to an error before creating a commit,
+       any commit message that has been provided by the user (e.g., in
+       an editor session) will be available in this file, but will be
+       overwritten by the next invocation of `git commit`.
 
 SEE ALSO
 --------
index d9463cb3874181456138d4ca54da4e3540bbeb4f..2d6ef32a087f3f7355251a84a7d0d294f18479a5 100644 (file)
@@ -97,10 +97,11 @@ OPTIONS
 
 --global::
        For writing options: write to global ~/.gitconfig file rather than
-       the repository .git/config.
+       the repository .git/config, write to $XDG_CONFIG_HOME/git/config file
+       if this file exists and the ~/.gitconfig file doesn't.
 +
-For reading options: read only from global ~/.gitconfig rather than
-from all available files.
+For reading options: read only from global ~/.gitconfig and from
+$XDG_CONFIG_HOME/git/config rather than from all available files.
 +
 See also <<FILES>>.
 
@@ -194,7 +195,7 @@ See also <<FILES>>.
 FILES
 -----
 
-If not set explicitly with '--file', there are three files where
+If not set explicitly with '--file', there are four files where
 'git config' will search for configuration options:
 
 $GIT_DIR/config::
@@ -204,6 +205,14 @@ $GIT_DIR/config::
        User-specific configuration file. Also called "global"
        configuration file.
 
+$XDG_CONFIG_HOME/git/config::
+       Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
+       or empty, $HOME/.config/git/config will be used. Any single-valued
+       variable set in this file will be overwritten by whatever is in
+       ~/.gitconfig.  It is a good idea not to create this file if
+       you sometimes use older versions of Git, as support for this
+       file was added fairly recently.
+
 $(prefix)/etc/gitconfig::
        System-wide configuration file.
 
diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt
new file mode 100644 (file)
index 0000000..53adee3
--- /dev/null
@@ -0,0 +1,154 @@
+git-credential(1)
+=================
+
+NAME
+----
+git-credential - retrieve and store user credentials
+
+SYNOPSIS
+--------
+------------------
+git credential <fill|approve|reject>
+------------------
+
+DESCRIPTION
+-----------
+
+Git has an internal interface for storing and retrieving credentials
+from system-specific helpers, as well as prompting the user for
+usernames and passwords. The git-credential command exposes this
+interface to scripts which may want to retrieve, store, or prompt for
+credentials in the same manner as git. The design of this scriptable
+interface models the internal C API; see
+link:technical/api-credentials.txt[the git credential API] for more
+background on the concepts.
+
+git-credential takes an "action" option on the command-line (one of
+`fill`, `approve`, or `reject`) and reads a credential description
+on stdin (see <<IOFMT,INPUT/OUTPUT FORMAT>>).
+
+If the action is `fill`, git-credential will attempt to add "username"
+and "password" attributes to the description by reading config files,
+by contacting any configured credential helpers, or by prompting the
+user. The username and password attributes of the credential
+description are then printed to stdout together with the attributes
+already provided.
+
+If the action is `approve`, git-credential will send the description
+to any configured credential helpers, which may store the credential
+for later use.
+
+If the action is `reject`, git-credential will send the description to
+any configured credential helpers, which may erase any stored
+credential matching the description.
+
+If the action is `approve` or `reject`, no output should be emitted.
+
+TYPICAL USE OF GIT CREDENTIAL
+-----------------------------
+
+An application using git-credential will typically use `git
+credential` following these steps:
+
+  1. Generate a credential description based on the context.
++
+For example, if we want a password for
+`https://example.com/foo.git`, we might generate the following
+credential description (don't forget the blank line at the end; it
+tells `git credential` that the application finished feeding all the
+infomation it has):
+
+        protocol=https
+        host=example.com
+        path=foo.git
+
+  2. Ask git-credential to give us a username and password for this
+     description. This is done by running `git credential fill`,
+     feeding the description from step (1) to its standard input. The complete
+     credential description (including the credential per se, i.e. the
+     login and password) will be produced on standard output, like:
+
+       protocol=https
+       host=example.com
+       username=bob
+       password=secr3t
++
+In most cases, this means the attributes given in the input will be
+repeated in the output, but git may also modify the credential
+description, for example by removing the `path` attribute when the
+protocol is HTTP(s) and `credential.useHttpPath` is false.
++
+If the `git credential` knew about the password, this step may
+not have involved the user actually typing this password (the
+user may have typed a password to unlock the keychain instead,
+or no user interaction was done if the keychain was already
+unlocked) before it returned `password=secr3t`.
+
+  3. Use the credential (e.g., access the URL with the username and
+     password from step (2)), and see if it's accepted.
+
+  4. Report on the success or failure of the password. If the
+     credential allowed the operation to complete successfully, then
+     it can be marked with an "approve" action to tell `git
+     credential` to reuse it in its next invocation. If the credential
+     was rejected during the operation, use the "reject" action so
+     that `git credential` will ask for a new password in its next
+     invocation. In either case, `git credential` should be fed with
+     the credential description obtained from step (2) (which also
+     contain the ones provided in step (1)).
+
+[[IOFMT]]
+INPUT/OUTPUT FORMAT
+-------------------
+
+`git credential` reads and/or writes (depending on the action used)
+credential information in its standard input/output. This information
+can correspond either to keys for which `git credential` will obtain
+the login/password information (e.g. host, protocol, path), or to the
+actual credential data to be obtained (login/password).
+
+The credential is split into a set of named attributes, with one
+attribute per line. Each attribute is
+specified by a key-value pair, separated by an `=` (equals) sign,
+followed by a newline. The key may contain any bytes except `=`,
+newline, or NUL. The value may contain any bytes except newline or NUL.
+In both cases, all bytes are treated as-is (i.e., there is no quoting,
+and one cannot transmit a value with newline or NUL in it). The list of
+attributes is terminated by a blank line or end-of-file.
+Git understands the following attributes:
+
+`protocol`::
+
+       The protocol over which the credential will be used (e.g.,
+       `https`).
+
+`host`::
+
+       The remote hostname for a network credential.
+
+`path`::
+
+       The path with which the credential will be used. E.g., for
+       accessing a remote https repository, this will be the
+       repository's path on the server.
+
+`username`::
+
+       The credential's username, if we already have one (e.g., from a
+       URL, from the user, or from a previously run helper).
+
+`password`::
+
+       The credential's password, if we are asking it to be stored.
+
+`url`::
+
+       When this special attribute is read by `git credential`, the
+       value is parsed as a URL and treated as if its constituent parts
+       were read (e.g., `url=https://example.com` would behave as if
+       `protocol=https` and `host=example.com` had been provided). This
+       can help callers avoid parsing URLs themselves.  Note that any
+       components which are missing from the URL (e.g., there is no
+       username in the example above) will be set to empty; if you want
+       to provide a URL and override some attributes, provide the URL
+       attribute first, followed by any overrides.
index 31b28fc29fc6e01e8505f2179b08082cf734377c..e8f757704c7fcf93e8e19f66c34557b8887ef29a 100644 (file)
@@ -204,7 +204,7 @@ receive-pack::
        can push anything into the repository, including removal
        of refs).  This is solely meant for a closed LAN setting
        where everybody is friendly.  This service can be
-       enabled by `daemon.receivepack` configuration item to
+       enabled by setting `daemon.receivepack` configuration item to
        `true`.
 
 EXAMPLES
index 3ceefb8a1f3b17f4301d89f8eec1cc4bb103a24a..20f9228511e82ac9ced81889bcc66f41504710a3 100644 (file)
@@ -181,7 +181,7 @@ final result verbatim.  When both sides made changes to the same area,
 however, git cannot randomly pick one side over the other, and asks you to
 resolve it by leaving what both sides did to that area.
 
-By default, git uses the same style as that is used by "merge" program
+By default, git uses the same style as the one used by the "merge" program
 from the RCS suite to present such a conflicted hunk, like this:
 
 ------------
index fe1f49bc6f26950987bc5520267663f4f335dab0..8228f33e3fdf0e6ec9c66dd3f55ad8094dfeb6aa 100644 (file)
@@ -255,7 +255,7 @@ These options can be used to modify 'git p4 submit' behavior.
        p4.  By default, this is the most recent p4 commit reachable
        from 'HEAD'.
 
--M[<n>]::
+-M::
        Detect renames.  See linkgit:git-diff[1].  Renames will be
        represented in p4 using explicit 'move' operations.  There
        is no corresponding option to detect copies, but there are
@@ -465,13 +465,15 @@ git-p4.useClientSpec::
 Submit variables
 ~~~~~~~~~~~~~~~~
 git-p4.detectRenames::
-       Detect renames.  See linkgit:git-diff[1].
+       Detect renames.  See linkgit:git-diff[1].  This can be true,
+       false, or a score as expected by 'git diff -M'.
 
 git-p4.detectCopies::
-       Detect copies.  See linkgit:git-diff[1].
+       Detect copies.  See linkgit:git-diff[1].  This can be true,
+       false, or a score as expected by 'git diff -C'.
 
 git-p4.detectCopiesHarder::
-       Detect copies harder.  See linkgit:git-diff[1].
+       Detect copies harder.  See linkgit:git-diff[1].  A boolean.
 
 git-p4.preserveUser::
        On submit, re-author changes to reflect the git author,
index 147fa1a8e002daba2a465ed1f56c88838500cb53..fd535b06abf85ae166413ad0d24226240f8dd066 100644 (file)
@@ -8,9 +8,9 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git rebase' [-i | --interactive] [options] [--onto <newbase>]
+'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
        [<upstream>] [<branch>]
-'git rebase' [-i | --interactive] [options] --onto <newbase>
+'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
 'git rebase' --continue | --skip | --abort
 
@@ -210,7 +210,7 @@ rebase.autosquash::
 
 OPTIONS
 -------
-<newbase>::
+--onto <newbase>::
        Starting point at which to create the new commits. If the
        --onto option is not specified, the starting point is
        <upstream>.  May be any valid commit, and not just an
@@ -273,7 +273,7 @@ which makes little sense.
        Pass the <strategy-option> through to the merge strategy.
        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.
+       'theirs' as noted above for the `-m` option.
 
 -q::
 --quiet::
@@ -344,14 +344,36 @@ This uses the `--interactive` machinery internally, but combining it
 with the `--interactive` option explicitly is generally not a good
 idea unless you know what you are doing (see BUGS below).
 
+-x <cmd>::
+--exec <cmd>::
+       Append "exec <cmd>" after each line creating a commit in the
+       final history. <cmd> will be interpreted as one or more shell
+       commands.
++
+This option can only be used with the `--interactive` option
+(see INTERACTIVE MODE below).
++
+You may execute several commands by either using one instance of `--exec`
+with several commands:
++
+       git rebase -i --exec "cmd1 && cmd2 && ..."
++
+or by giving more than one `--exec`:
++
+       git rebase -i --exec "cmd1" --exec "cmd2" --exec ...
++
+If `--autosquash` is used, "exec" lines will not be appended for
+the intermediate commits, and will only appear at the end of each
+squash/fixup series.
 
 --root::
        Rebase all commits reachable from <branch>, instead of
        limiting them with an <upstream>.  This allows you to rebase
-       the root commit(s) on a branch.  Must be used with --onto, and
+       the root commit(s) on a branch.  When used with --onto, it
        will skip changes already contained in <newbase> (instead of
-       <upstream>).  When used together with --preserve-merges, 'all'
-       root commits will be rewritten to have <newbase> as parent
+       <upstream>) whereas without --onto it will operate on every change.
+       When used together with both --onto and --preserve-merges,
+       'all' root commits will be rewritten to have <newbase> as parent
        instead.
 
 --autosquash::
@@ -521,6 +543,24 @@ in `$SHELL`, or the default shell if `$SHELL` is not set), so you can
 use shell features (like "cd", ">", ";" ...). The command is run from
 the root of the working tree.
 
+----------------------------------
+$ git rebase -i --exec "make test"
+----------------------------------
+
+This command lets you check that intermediate commits are compilable.
+The todo list becomes like that:
+
+--------------------
+pick 5928aea one
+exec make test
+pick 04d0fda two
+exec make test
+pick ba46169 three
+exec make test
+pick f4593f9 four
+exec make test
+--------------------
+
 SPLITTING COMMITS
 -----------------
 
index 4cc3e9586fcfd79ddeb7f636141e1a878285b0b5..3c63561f02aca6f934c47a2897f8e50cf4ac5fe5 100644 (file)
@@ -101,6 +101,12 @@ OPTIONS
        The option core.warnAmbiguousRefs is used to select the strict
        abbreviation mode.
 
+--disambiguate=<prefix>::
+       Show every object whose name begins with the given prefix.
+       The <prefix> must be at least 4 hexadecimal digits long to
+       avoid listing each and every object in the repository by
+       mistake.
+
 --all::
        Show all refs found in `refs/`.
 
index 80120ea14f1ccd2784405c2bf2d54863bb52e8d3..e16f3e175bd8915d139961c649be209b0afc535f 100644 (file)
@@ -75,6 +75,8 @@ repositories (i.e., attributes of interest to all users) should go into
 `.gitattributes` files. Attributes that should affect all repositories
 for a single user should be placed in a file specified by the
 `core.attributesfile` configuration option (see linkgit:git-config[1]).
+Its default value is $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME
+is either not set or empty, $HOME/.config/git/attributes is used instead.
 Attributes for all users on a system should be placed in the
 `$(prefix)/etc/gitattributes` file.
 
index 2e7328b8306f4e949e22456b33d495226c1f014b..c1f692a71e54e01f813d84f83eaff4a9514a27b3 100644 (file)
@@ -50,7 +50,9 @@ the repository but are specific to one user's workflow) should go into
 the `$GIT_DIR/info/exclude` file.  Patterns which a user wants git to
 ignore in all situations (e.g., backup or temporary files generated by
 the user's editor of choice) generally go into a file specified by
-`core.excludesfile` in the user's `~/.gitconfig`.
+`core.excludesfile` in the user's `~/.gitconfig`. Its default value is
+$XDG_CONFIG_HOME/git/ignore. If $XDG_CONFIG_HOME is either not set or empty,
+$HOME/.config/git/ignore is used instead.
 
 The underlying git plumbing tools, such as
 'git ls-files' and 'git read-tree', read
index 3595b586bc35d865a08ab538a1908cb2abe8e1a8..f928b57f90b0950e9297cff160741683754a454d 100644 (file)
@@ -117,7 +117,7 @@ to point at the new commit.
 
 [[def_ent]]ent::
        Favorite synonym to "<<def_tree-ish,tree-ish>>" by some total geeks. See
-       `http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
+       http://en.wikipedia.org/wiki/Ent_(Middle-earth) for an in-depth
        explanation. Avoid this term, not to confuse people.
 
 [[def_evil_merge]]evil merge::
index 84e34b1abaf23ef87fcccd21b0500fd21c5a1912..d9b2b5b2e07827ff0a8a276d2593f5e197e029b3 100644 (file)
@@ -760,7 +760,7 @@ options may be given. See linkgit:git-diff-files[1] for more options.
 
 --cc::
 
-       This flag implies the '-c' options and further compresses the
+       This flag implies the '-c' option and further compresses the
        patch output by omitting uninteresting hunks whose contents in
        the parents have only two variants and the merge result picks
        one of them without modification.
index adb6f0c8962377b3dc04fcdc948e40bfbd132a1b..5977b58e57344e60bd683553d9b1b9a44b3c31a7 100644 (file)
@@ -241,42 +241,9 @@ appended to its command line, which is one of:
        Remove a matching credential, if any, from the helper's storage.
 
 The details of the credential will be provided on the helper's stdin
-stream. The credential is split into a set of named attributes.
-Attributes are provided to the helper, one per line. Each attribute is
-specified by a key-value pair, separated by an `=` (equals) sign,
-followed by a newline. The key may contain any bytes except `=`,
-newline, or NUL. The value may contain any bytes except newline or NUL.
-In both cases, all bytes are treated as-is (i.e., there is no quoting,
-and one cannot transmit a value with newline or NUL in it). The list of
-attributes is terminated by a blank line or end-of-file.
-
-Git will send the following attributes (but may not send all of
-them for a given credential; for example, a `host` attribute makes no
-sense when dealing with a non-network protocol):
-
-`protocol`::
-
-       The protocol over which the credential will be used (e.g.,
-       `https`).
-
-`host`::
-
-       The remote hostname for a network credential.
-
-`path`::
-
-       The path with which the credential will be used. E.g., for
-       accessing a remote https repository, this will be the
-       repository's path on the server.
-
-`username`::
-
-       The credential's username, if we already have one (e.g., from a
-       URL, from the user, or from a previously run helper).
-
-`password`::
-
-       The credential's password, if we are asking it to be stored.
+stream. The exact format is the same as the input/output format of the
+`git credential` plumbing command (see the section `INPUT/OUTPUT
+FORMAT` in linkgit:git-credential[7] for a detailed specification).
 
 For a `get` operation, the helper should produce a list of attributes
 on stdout in the same format. A helper is free to produce a subset, or
index 02ed5668e151354d23991e7b272611c32741cb12..03d95dc290cecd2223116be90b591f75d40ced2b 100644 (file)
@@ -2870,7 +2870,7 @@ $ git fetch example
 You can also add a "+" to force the update each time:
 
 -------------------------------------------------
-$ git config remote.example.fetch +master:ref/remotes/example/master
+$ git config remote.example.fetch +master:refs/remotes/example/master
 -------------------------------------------------
 
 Don't do this unless you're sure you won't mind "git fetch" possibly
@@ -2966,7 +2966,7 @@ As you can see, a commit is defined by:
 
 - a tree: The SHA-1 name of a tree object (as defined below), representing
   the contents of a directory at a certain point in time.
-- parent(s): The SHA-1 name of some number of commits which represent the
+- parent(s): The SHA-1 name(s) of some number of commits which represent the
   immediately previous step(s) in the history of the project.  The
   example above has one parent; merge commits may have more than
   one.  A commit with no parents is called a "root" commit, and
@@ -3363,8 +3363,8 @@ Date:
 :100644 100644 oldsha... 4b9458b... M somedirectory/myfile
 ------------------------------------------------
 
-This tells you that the immediately preceding version of the file was
-"newsha", and that the immediately following version was "oldsha".
+This tells you that the immediately following version of the file was
+"newsha", and that the immediately preceding version was "oldsha".
 You also know the commit messages that went with the change from oldsha
 to 4b9458b and with the change from 4b9458b to newsha.
 
@@ -4035,8 +4035,8 @@ $ git ls-files --unmerged
 Each line of the `git ls-files --unmerged` output begins with
 the blob mode bits, blob SHA-1, 'stage number', and the
 filename.  The 'stage number' is git's way to say which tree it
-came from: stage 1 corresponds to `$orig` tree, stage 2 `HEAD`
-tree, and stage3 `$target` tree.
+came from: stage 1 corresponds to the `$orig` tree, stage 2 to
+the `HEAD` tree, and stage 3 to the `$target` tree.
 
 Earlier we said that trivial merges are done inside
 `git read-tree -m`.  For example, if the file did not change
index 9f0d5507f2f63ee332519370f4a68c0d72bbd61d..22bb2298ae9087a6db826e231fb803d803b11494 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.11.3
+DEF_VER=v1.7.12-rc0
 
 LF='
 '
index 67d761e83adad8e3332b22fbe753e0cdb81b1a83..b0b3493364550f9f443039708974902df1896413 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -127,9 +127,8 @@ all::
 # specify your own (or DarwinPort's) include directories and
 # library directories by defining CFLAGS and LDFLAGS appropriately.
 #
-# Define BLK_SHA1 environment variable if you want the C version
-# of the SHA1 that assumes you can do unaligned 32-bit loads and
-# have a fast htonl() function.
+# Define BLK_SHA1 environment variable to make use of the bundled
+# optimized C SHA1 routine.
 #
 # Define PPC_SHA1 environment variable when running make to make use of
 # a bundled SHA1 routine optimized for PowerPC.
@@ -206,8 +205,6 @@ all::
 # Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks
 # field that counts the on-disk footprint in 512-byte blocks.
 #
-# Define ASCIIDOC7 if you want to format documentation with AsciiDoc 7
-#
 # Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72
 # (not v1.73 or v1.71).
 #
@@ -299,6 +296,13 @@ all::
 # 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.
+#
+# Define GIT_USER_AGENT if you want to change how git identifies itself during
+# network interactions.  The default is "git/$(GIT_VERSION)".
+#
+# Define DEFAULT_HELP_FORMAT to "man", "info" or "html"
+# (defaults to "man") if you want to have a different default when
+# "git help" is called without a parameter specifying the format.
 
 GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -392,12 +396,9 @@ BUILTIN_OBJS =
 BUILT_INS =
 COMPAT_CFLAGS =
 COMPAT_OBJS =
-XDIFF_H =
 XDIFF_OBJS =
-VCSSVN_H =
 VCSSVN_OBJS =
-VCSSVN_TEST_OBJS =
-MISC_H =
+GENERATED_H =
 EXTRA_CPPFLAGS =
 LIB_H =
 LIB_OBJS =
@@ -483,7 +484,6 @@ X =
 PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 
 TEST_PROGRAMS_NEED_X += test-chmtime
-TEST_PROGRAMS_NEED_X += test-credential
 TEST_PROGRAMS_NEED_X += test-ctype
 TEST_PROGRAMS_NEED_X += test-date
 TEST_PROGRAMS_NEED_X += test-delta
@@ -558,51 +558,44 @@ 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 += xdiff/xinclude.h
+LIB_H += xdiff/xmacros.h
+LIB_H += xdiff/xdiff.h
+LIB_H += xdiff/xtypes.h
+LIB_H += xdiff/xutils.h
+LIB_H += xdiff/xprepare.h
+LIB_H += xdiff/xdiffi.h
+LIB_H += xdiff/xemit.h
+
+LIB_H += vcs-svn/line_buffer.h
+LIB_H += vcs-svn/sliding_window.h
+LIB_H += vcs-svn/repo_tree.h
+LIB_H += vcs-svn/fast_export.h
+LIB_H += vcs-svn/svndiff.h
+LIB_H += vcs-svn/svndump.h
+
+GENERATED_H += common-cmds.h
 
 LIB_H += advice.h
 LIB_H += archive.h
 LIB_H += argv-array.h
 LIB_H += attr.h
+LIB_H += bisect.h
 LIB_H += blob.h
+LIB_H += branch.h
 LIB_H += builtin.h
 LIB_H += bulk-checkin.h
-LIB_H += cache.h
+LIB_H += bundle.h
 LIB_H += cache-tree.h
+LIB_H += cache.h
 LIB_H += color.h
+LIB_H += column.h
 LIB_H += commit.h
 LIB_H += compat/bswap.h
 LIB_H += compat/cygwin.h
 LIB_H += compat/mingw.h
 LIB_H += compat/obstack.h
+LIB_H += compat/precompose_utf8.h
 LIB_H += compat/terminal.h
 LIB_H += compat/win32/dirent.h
 LIB_H += compat/win32/poll.h
@@ -618,6 +611,7 @@ LIB_H += diff.h
 LIB_H += diffcore.h
 LIB_H += dir.h
 LIB_H += exec_cmd.h
+LIB_H += fetch-pack.h
 LIB_H += fmt-merge-msg.h
 LIB_H += fsck.h
 LIB_H += gettext.h
@@ -627,6 +621,7 @@ LIB_H += graph.h
 LIB_H += grep.h
 LIB_H += hash.h
 LIB_H += help.h
+LIB_H += http.h
 LIB_H += kwset.h
 LIB_H += levenshtein.h
 LIB_H += list-objects.h
@@ -636,19 +631,20 @@ 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
+LIB_H += notes.h
 LIB_H += object.h
-LIB_H += pack.h
 LIB_H += pack-refs.h
 LIB_H += pack-revindex.h
+LIB_H += pack.h
 LIB_H += parse-options.h
 LIB_H += patch-ids.h
 LIB_H += pkt-line.h
 LIB_H += progress.h
 LIB_H += prompt.h
 LIB_H += quote.h
+LIB_H += reachable.h
 LIB_H += reflog-walk.h
 LIB_H += refs.h
 LIB_H += remote.h
@@ -656,9 +652,11 @@ LIB_H += rerere.h
 LIB_H += resolve-undo.h
 LIB_H += revision.h
 LIB_H += run-command.h
+LIB_H += send-pack.h
 LIB_H += sequencer.h
 LIB_H += sha1-array.h
 LIB_H += sha1-lookup.h
+LIB_H += shortlog.h
 LIB_H += sideband.h
 LIB_H += sigchain.h
 LIB_H += strbuf.h
@@ -666,14 +664,18 @@ LIB_H += streaming.h
 LIB_H += string-list.h
 LIB_H += submodule.h
 LIB_H += tag.h
+LIB_H += tar.h
 LIB_H += thread-utils.h
 LIB_H += transport.h
-LIB_H += tree.h
 LIB_H += tree-walk.h
+LIB_H += tree.h
 LIB_H += unpack-trees.h
+LIB_H += url.h
 LIB_H += userdiff.h
 LIB_H += utf8.h
 LIB_H += varint.h
+LIB_H += walker.h
+LIB_H += wt-status.h
 LIB_H += xdiff-interface.h
 LIB_H += xdiff/xdiff.h
 
@@ -802,6 +804,7 @@ LIB_OBJS += usage.o
 LIB_OBJS += userdiff.o
 LIB_OBJS += utf8.o
 LIB_OBJS += varint.o
+LIB_OBJS += version.o
 LIB_OBJS += walker.o
 LIB_OBJS += wrapper.o
 LIB_OBJS += write_or_die.o
@@ -830,6 +833,7 @@ BUILTIN_OBJS += builtin/commit-tree.o
 BUILTIN_OBJS += builtin/commit.o
 BUILTIN_OBJS += builtin/config.o
 BUILTIN_OBJS += builtin/count-objects.o
+BUILTIN_OBJS += builtin/credential.o
 BUILTIN_OBJS += builtin/describe.o
 BUILTIN_OBJS += builtin/diff-files.o
 BUILTIN_OBJS += builtin/diff-index.o
@@ -907,6 +911,8 @@ BUILTIN_OBJS += builtin/write-tree.o
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 EXTLIBS =
 
+GIT_USER_AGENT = git/$(GIT_VERSION)
+
 #
 # Platform specific tweaks
 #
@@ -993,6 +999,8 @@ ifeq ($(uname_S),Darwin)
        NO_MEMMEM = YesPlease
        USE_ST_TIMESPEC = YesPlease
        HAVE_DEV_TTY = YesPlease
+       COMPAT_OBJS += compat/precompose_utf8.o
+       BASIC_CFLAGS += -DPRECOMPOSE_UNICODE
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -1240,6 +1248,7 @@ ifeq ($(uname_S),Windows)
        BLK_SHA1 = YesPlease
        NO_POSIX_GOODIES = UnfortunatelyYes
        NATIVE_CRLF = YesPlease
+       DEFAULT_HELP_FORMAT = html
 
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
@@ -1842,10 +1851,6 @@ ifndef V
 endif
 endif
 
-ifdef ASCIIDOC7
-       export ASCIIDOC7
-endif
-
 ifdef NO_INSTALL_HARDLINKS
        export NO_INSTALL_HARDLINKS
 endif
@@ -1923,6 +1928,18 @@ SHELL_PATH_CQ_SQ = $(subst ','\'',$(SHELL_PATH_CQ))
 BASIC_CFLAGS += -DSHELL_PATH='$(SHELL_PATH_CQ_SQ)'
 endif
 
+GIT_USER_AGENT_SQ = $(subst ','\'',$(GIT_USER_AGENT))
+GIT_USER_AGENT_CQ = "$(subst ",\",$(subst \,\\,$(GIT_USER_AGENT)))"
+GIT_USER_AGENT_CQ_SQ = $(subst ','\'',$(GIT_USER_AGENT_CQ))
+GIT-USER-AGENT: FORCE
+       @if test x'$(GIT_USER_AGENT_SQ)' != x"`cat GIT-USER-AGENT 2>/dev/null`"; then \
+               echo '$(GIT_USER_AGENT_SQ)' >GIT-USER-AGENT; \
+       fi
+
+ifdef DEFAULT_HELP_FORMAT
+BASIC_CFLAGS += -DDEFAULT_HELP_FORMAT='"$(DEFAULT_HELP_FORMAT)"'
+endif
+
 ALL_CFLAGS += $(BASIC_CFLAGS)
 ALL_LDFLAGS += $(BASIC_LDFLAGS)
 
@@ -1969,8 +1986,41 @@ shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
 strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
-git.o: common-cmds.h
-git.sp git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \
+### Target-specific flags and dependencies
+
+# The generic compilation pattern rule and automatically
+# computed header dependencies (falling back to a dependency on
+# LIB_H) are enough to describe how most targets should be built,
+# but some targets are special enough to need something a little
+# different.
+#
+# - When a source file "foo.c" #includes a generated header file,
+#   we need to list that dependency for the "foo.o" target.
+#
+#   We also list it from other targets that are built from foo.c
+#   like "foo.sp" and "foo.s", even though that is easy to forget
+#   to do because the generated header is already present around
+#   after a regular build attempt.
+#
+# - Some code depends on configuration kept in makefile
+#   variables. The target-specific variable EXTRA_CPPFLAGS can
+#   be used to convey that information to the C preprocessor
+#   using -D options.
+#
+#   The "foo.o" target should have a corresponding dependency on
+#   a file that changes when the value of the makefile variable
+#   changes.  For example, targets making use of the
+#   $(GIT_VERSION) variable depend on GIT-VERSION-FILE.
+#
+#   Technically the ".sp" and ".s" targets do not need this
+#   dependency because they are force-built, but they get the
+#   same dependency for consistency. This way, you do not have to
+#   know how each target is implemented. And it means the
+#   dependencies here will not need to change if the force-build
+#   details change some day.
+
+git.sp git.s git.o: GIT-PREFIX
+git.sp git.s git.o: EXTRA_CPPFLAGS = \
        '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
        '-DGIT_MAN_PATH="$(mandir_SQ)"' \
        '-DGIT_INFO_PATH="$(infodir_SQ)"'
@@ -1979,14 +2029,19 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
                $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
 
-help.sp help.o: common-cmds.h
+help.sp help.s help.o: common-cmds.h
 
-builtin/help.sp builtin/help.o: common-cmds.h
+builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX
 builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
        '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
        '-DGIT_MAN_PATH="$(mandir_SQ)"' \
        '-DGIT_INFO_PATH="$(infodir_SQ)"'
 
+version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT
+version.sp version.s version.o: EXTRA_CPPFLAGS = \
+       '-DGIT_VERSION="$(GIT_VERSION)"' \
+       '-DGIT_USER_AGENT=$(GIT_USER_AGENT_CQ_SQ)'
+
 $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && \
        ln git$X $@ 2>/dev/null || \
@@ -1998,35 +2053,47 @@ common-cmds.h: ./generate-cmdlist.sh command-list.txt
 common-cmds.h: $(wildcard Documentation/git-*.txt)
        $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
 
+SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\
+       $(localedir_SQ):$(NO_CURL):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\
+       $(gitwebdir_SQ):$(PERL_PATH_SQ)
 define cmd_munge_script
 $(RM) $@ $@+ && \
 sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
     -e 's|@@DIFF@@|$(DIFF_SQ)|' \
-    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
     -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e 's/@@USE_GETTEXT_SCHEME@@/$(USE_GETTEXT_SCHEME)/g' \
     -e $(BROKEN_PATH_FIX) \
+    -e 's|@@GITWEBDIR@@|$(gitwebdir_SQ)|g' \
+    -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
     $@.sh >$@+
 endef
 
-$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
+GIT-SCRIPT-DEFINES: FORCE
+       @FLAGS='$(SCRIPT_DEFINES)'; \
+           if test x"$$FLAGS" != x"`cat $@ 2>/dev/null`" ; then \
+               echo 1>&2 "    * new script parameters"; \
+               echo "$$FLAGS" >$@; \
+            fi
+
+
+$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh GIT-SCRIPT-DEFINES
        $(QUIET_GEN)$(cmd_munge_script) && \
        chmod +x $@+ && \
        mv $@+ $@
 
-$(SCRIPT_LIB) : % : %.sh
+$(SCRIPT_LIB) : % : %.sh GIT-SCRIPT-DEFINES
        $(QUIET_GEN)$(cmd_munge_script) && \
        mv $@+ $@
 
 ifndef NO_PERL
 $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 
-perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL
+perl/perl.mak: GIT-CFLAGS GIT-PREFIX perl/Makefile perl/Makefile.PL
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
-$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
+$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl GIT-VERSION-FILE
        $(QUIET_GEN)$(RM) $@ $@+ && \
        INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
        sed -e '1{' \
@@ -2046,14 +2113,8 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
 gitweb:
        $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all
 
-git-instaweb: git-instaweb.sh gitweb
-       $(QUIET_GEN)$(RM) $@ $@+ && \
-       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-           -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           -e 's|@@GITWEBDIR@@|$(gitwebdir_SQ)|g' \
-           -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
-           $@.sh > $@+ && \
+git-instaweb: git-instaweb.sh gitweb GIT-SCRIPT-DEFINES
+       $(QUIET_GEN)$(cmd_munge_script) && \
        chmod +x $@+ && \
        mv $@+ $@
 else # NO_PERL
@@ -2067,7 +2128,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
 endif # NO_PERL
 
 ifndef NO_PYTHON
-$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX
 $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
        $(QUIET_GEN)$(RM) $@ $@+ && \
        INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
@@ -2089,24 +2150,23 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
        mv $@+ $@
 endif # NO_PYTHON
 
-configure: configure.ac
+configure: configure.ac GIT-VERSION-FILE
        $(QUIET_GEN)$(RM) $@ $<+ && \
        sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $< > $<+ && \
        autoconf -o $@ $<+ && \
        $(RM) $<+
 
-# These can record GIT_VERSION
-git.o git.spec http.o \
-       $(patsubst %.sh,%,$(SCRIPT_SH)) \
-       $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-       : GIT-VERSION-FILE
-
-TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
-GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
-       git.o
-ifndef NO_CURL
-       GIT_OBJS += http.o http-walker.o remote-curl.o
+ifdef AUTOCONFIGURED
+config.status: configure
+       $(QUIET_GEN)if test -f config.status; then \
+         ./config.status --recheck; \
+       else \
+         ./configure; \
+       fi
+reconfigure config.mak.autogen: config.status
+       $(QUIET_GEN)./config.status
+.PHONY: reconfigure # This is a convenience target.
 endif
 
 XDIFF_OBJS += xdiff/xdiffi.o
@@ -2124,9 +2184,14 @@ VCSSVN_OBJS += vcs-svn/fast_export.o
 VCSSVN_OBJS += vcs-svn/svndiff.o
 VCSSVN_OBJS += vcs-svn/svndump.o
 
-VCSSVN_TEST_OBJS += test-line-buffer.o
-
-OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
+TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
+OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
+       $(XDIFF_OBJS) \
+       $(VCSSVN_OBJS) \
+       git.o
+ifndef NO_CURL
+       OBJECTS += http.o http-walker.o remote-curl.o
+endif
 
 dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
 dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
@@ -2209,7 +2274,7 @@ $(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs)
 endif
 
 %.s: %.c GIT-CFLAGS FORCE
-       $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
+       $(QUIET_CC)$(CC) -o $@ -S $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
 
 ifdef USE_COMPUTED_HEADER_DEPENDENCIES
 # Take advantage of gcc's on-the-fly dependency generation
@@ -2225,51 +2290,32 @@ else
 # Dependencies on automatically generated headers such as common-cmds.h
 # should _not_ be included here, since they are necessary even when
 # building an object for the first time.
-#
-# XXX. Please check occasionally that these include all dependencies
-# gcc detects!
-
-$(GIT_OBJS): $(LIB_H)
-builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o transport.o: branch.h
-builtin/bundle.o bundle.o transport.o: bundle.h
-builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
-builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
-builtin/index-pack.o builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
-builtin/send-pack.o transport.o: send-pack.h
-builtin/log.o builtin/shortlog.o: shortlog.h
-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-interface.o $(XDIFF_OBJS): $(XDIFF_H)
-
-$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) $(VCSSVN_H)
+$(OBJECTS): $(LIB_H)
 endif
 
+exec_cmd.sp exec_cmd.s exec_cmd.o: GIT-PREFIX
 exec_cmd.sp exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \
        '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \
        '-DBINDIR="$(bindir_relative_SQ)"' \
        '-DPREFIX="$(prefix_SQ)"'
 
+builtin/init-db.sp builtin/init-db.s builtin/init-db.o: GIT-PREFIX
 builtin/init-db.sp builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \
        -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"'
 
+config.sp config.s config.o: GIT-PREFIX
 config.sp config.s config.o: EXTRA_CPPFLAGS = \
        -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
 
+attr.sp attr.s attr.o: GIT-PREFIX
 attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \
        -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
 
+gettext.sp gettext.s gettext.o: GIT-PREFIX
 gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \
        -DGIT_LOCALE_PATH='"$(localedir_SQ)"'
 
-http.sp http.s http.o: EXTRA_CPPFLAGS = \
-       -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
-
 ifdef NO_EXPAT
 http-walker.sp http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT
 endif
@@ -2343,7 +2389,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) $(LIB_H) $(XDIFF_H) $(VCSSVN_H) $(MISC_H)
+LOCALIZED_C := $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
 LOCALIZED_SH := $(SCRIPT_SH)
 LOCALIZED_PERL := $(SCRIPT_PERL)
 
@@ -2390,14 +2436,22 @@ cscope:
        $(FIND_SOURCE_FILES) | xargs cscope -b
 
 ### Detect prefix changes
-TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
-             $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
-             $(localedir_SQ):$(USE_GETTEXT_SCHEME)
+TRACK_PREFIX = $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
+               $(localedir_SQ)
+
+GIT-PREFIX: FORCE
+       @FLAGS='$(TRACK_PREFIX)'; \
+       if test x"$$FLAGS" != x"`cat GIT-PREFIX 2>/dev/null`" ; then \
+               echo 1>&2 "    * new prefix flags"; \
+               echo "$$FLAGS" >GIT-PREFIX; \
+       fi
+
+TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):$(USE_GETTEXT_SCHEME)
 
 GIT-CFLAGS: FORCE
        @FLAGS='$(TRACK_CFLAGS)'; \
            if test x"$$FLAGS" != x"`cat GIT-CFLAGS 2>/dev/null`" ; then \
-               echo 1>&2 "    * new build flags or prefix"; \
+               echo 1>&2 "    * new build flags"; \
                echo "$$FLAGS" >GIT-CFLAGS; \
             fi
 
@@ -2649,7 +2703,7 @@ quick-install-html:
 
 ### Maintainer's dist rules
 
-git.spec: git.spec.in
+git.spec: git.spec.in GIT-VERSION-FILE
        sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@+
        mv $@+ $@
 
@@ -2699,6 +2753,9 @@ dist-doc:
 
 distclean: clean
        $(RM) configure
+       $(RM) config.log config.status config.cache
+       $(RM) config.mak.autogen config.mak.append
+       $(RM) -r autom4te.cache
 
 profile-clean:
        $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
@@ -2713,8 +2770,6 @@ clean: profile-clean
        $(RM) -r $(dep_dirs)
        $(RM) -r po/build/
        $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
-       $(RM) -r autom4te.cache
-       $(RM) config.log config.mak.autogen config.mak.append config.status config.cache
        $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
        $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
@@ -2733,6 +2788,7 @@ ifndef NO_TCLTK
        $(MAKE) -C git-gui clean
 endif
        $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
+       $(RM) GIT-USER-AGENT GIT-PREFIX GIT-SCRIPT-DEFINES
 
 .PHONY: all install profile-clean clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
index 8f994bcfa03bb1a92ca833d16cfd5db53ab3519a..19bb2ebbe687d9ded1a5c82d28d4410de1e2f19f 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.11.3.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.12.txt
\ No newline at end of file
index a492eea24f71ad2d2082efce9d1d925a5766b111..edfbd4a6fb01a6905671d6d842d22c4869fad729 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -32,7 +32,7 @@ void advise(const char *advice, ...)
        const char *cp, *np;
 
        va_start(params, advice);
-       strbuf_addf(&buf, advice, params);
+       strbuf_vaddf(&buf, advice, params);
        va_end(params);
 
        for (cp = buf.buf; *cp; cp = np) {
diff --git a/attr.c b/attr.c
index 303751f6c2bd4d558cffbb928c636581efb2b310..aef93d896f7e72ec534c0c2c576e418bedc5cb30 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -497,6 +497,7 @@ static int git_attr_system(void)
 static void bootstrap_attr_stack(void)
 {
        struct attr_stack *elem;
+       char *xdg_attributes_file;
 
        if (attr_stack)
                return;
@@ -515,13 +516,15 @@ static void bootstrap_attr_stack(void)
                }
        }
 
-       if (git_attributes_file) {
-               elem = read_attr_from_file(git_attributes_file, 1);
-               if (elem) {
-                       elem->origin = NULL;
-                       elem->prev = attr_stack;
-                       attr_stack = elem;
-               }
+       if (!git_attributes_file) {
+               home_config_paths(NULL, &xdg_attributes_file, "attributes");
+               git_attributes_file = xdg_attributes_file;
+       }
+       elem = read_attr_from_file(git_attributes_file, 1);
+       if (elem) {
+               elem->origin = NULL;
+               elem->prev = attr_stack;
+               attr_stack = elem;
        }
 
        if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
index c0054a0b0a441090184a141ee73954a94a2904d5..a8d4bf9301d25de60c02718b9d446edf5c665184 100644 (file)
  * Where do we get the source from? The first 16 iterations get it from
  * the input data, the next mix it from the 512-bit array.
  */
-#define SHA_SRC(t) get_be32(data + t)
-#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1)
+#define SHA_SRC(t) get_be32((unsigned char *) block + (t)*4)
+#define SHA_MIX(t) SHA_ROL(W((t)+13) ^ W((t)+8) ^ W((t)+2) ^ W(t), 1);
 
 #define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \
        unsigned int TEMP = input(t); setW(t, TEMP); \
 #define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E )
 #define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) ,  0xca62c1d6, A, B, C, D, E )
 
-static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data)
+static void blk_SHA1_Block(blk_SHA_CTX *ctx, const void *block)
 {
        unsigned int A,B,C,D,E;
        unsigned int array[16];
@@ -126,7 +126,7 @@ static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data)
        D = ctx->H[3];
        E = ctx->H[4];
 
-       /* Round 1 - iterations 0-16 take their input from 'data' */
+       /* Round 1 - iterations 0-16 take their input from 'block' */
        T_0_15( 0, A, B, C, D, E);
        T_0_15( 1, E, A, B, C, D);
        T_0_15( 2, D, E, A, B, C);
index eccdaf93924334137833e5acf7541aa514588c2d..2bef1e7e71b7cb3375b3d96fab5c4f20e0c3adff 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -74,25 +74,33 @@ void install_branch_config(int flag, const char *local, const char *origin, cons
                strbuf_addf(&key, "branch.%s.rebase", local);
                git_config_set(key.buf, "true");
        }
+       strbuf_release(&key);
 
        if (flag & BRANCH_CONFIG_VERBOSE) {
-               strbuf_reset(&key);
-
-               strbuf_addstr(&key, origin ? "remote" : "local");
-
-               /* Are we tracking a proper "branch"? */
-               if (remote_is_branch) {
-                       strbuf_addf(&key, " branch %s", shortname);
-                       if (origin)
-                               strbuf_addf(&key, " from %s", origin);
-               }
+               if (remote_is_branch && origin)
+                       printf(rebasing ?
+                              "Branch %s set up to track remote branch %s from %s by rebasing.\n" :
+                              "Branch %s set up to track remote branch %s from %s.\n",
+                              local, shortname, origin);
+               else if (remote_is_branch && !origin)
+                       printf(rebasing ?
+                              "Branch %s set up to track local branch %s by rebasing.\n" :
+                              "Branch %s set up to track local branch %s.\n",
+                              local, shortname);
+               else if (!remote_is_branch && origin)
+                       printf(rebasing ?
+                              "Branch %s set up to track remote ref %s by rebasing.\n" :
+                              "Branch %s set up to track remote ref %s.\n",
+                              local, remote);
+               else if (!remote_is_branch && !origin)
+                       printf(rebasing ?
+                              "Branch %s set up to track local ref %s by rebasing.\n" :
+                              "Branch %s set up to track local ref %s.\n",
+                              local, remote);
                else
-                       strbuf_addf(&key, " ref %s", remote);
-               printf("Branch %s set up to track %s%s.\n",
-                      local, key.buf,
-                      rebasing ? " by rebasing" : "");
+                       die("BUG: impossible combination of %d and %p",
+                           remote_is_branch, origin);
        }
-       strbuf_release(&key);
 }
 
 /*
index e426de3ff7148e03dfe76665bd0b7bc3f94d1227..ba6626b03505dd0622faea4e2bea51ed4b6720f5 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -9,7 +9,6 @@
 
 #define DEFAULT_MERGE_LOG_LEN 20
 
-extern const char git_version_string[];
 extern const char git_usage_string[];
 extern const char git_more_info_string[];
 
@@ -68,6 +67,7 @@ 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);
 extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
+extern int cmd_credential(int argc, const char **argv, const char *prefix);
 extern int cmd_describe(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
@@ -85,7 +85,6 @@ extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix
 extern int cmd_grep(int argc, const char **argv, const char *prefix);
 extern int cmd_hash_object(int argc, const char **argv, const char *prefix);
 extern int cmd_help(int argc, const char **argv, const char *prefix);
-extern int cmd_http_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_index_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_init_db(int argc, const char **argv, const char *prefix);
 extern int cmd_log(int argc, const char **argv, const char *prefix);
@@ -110,7 +109,6 @@ extern int cmd_notes(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);
 extern int cmd_patch_id(int argc, const char **argv, const char *prefix);
-extern int cmd_pickaxe(int argc, const char **argv, const char *prefix);
 extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
@@ -143,7 +141,6 @@ extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_update_server_info(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix);
-extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
 extern int cmd_var(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
index b4428ea34f53d94e3733796777866e73531f06b5..d453c833782c6aae4dd04fb9f9f68c3a8211d3d5 100644 (file)
@@ -16,6 +16,9 @@
 #include "dir.h"
 #include "diff.h"
 #include "parse-options.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "rerere.h"
 
 /*
  *  --check turns on checking that the working tree matches the
@@ -46,6 +49,7 @@ static int apply_with_reject;
 static int apply_verbosely;
 static int allow_overlap;
 static int no_add;
+static int threeway;
 static const char *fake_ancestor;
 static int line_termination = '\n';
 static unsigned int p_context = UINT_MAX;
@@ -193,12 +197,17 @@ struct patch {
        unsigned int is_copy:1;
        unsigned int is_rename:1;
        unsigned int recount:1;
+       unsigned int conflicted_threeway:1;
+       unsigned int direct_to_threeway:1;
        struct fragment *fragments;
        char *result;
        size_t resultsize;
        char old_sha1_prefix[41];
        char new_sha1_prefix[41];
        struct patch *next;
+
+       /* three-way fallback result */
+       unsigned char threeway_stage[3][20];
 };
 
 static void free_fragment_list(struct fragment *list)
@@ -371,8 +380,8 @@ static void prepare_image(struct image *image, char *buf, size_t len,
 static void clear_image(struct image *image)
 {
        free(image->buf);
-       image->buf = NULL;
-       image->len = 0;
+       free(image->line_allocated);
+       memset(image, 0, sizeof(*image));
 }
 
 /* fmt must contain _one_ %s and no other substitution */
@@ -2937,20 +2946,17 @@ static int apply_fragments(struct image *img, struct patch *patch)
        return 0;
 }
 
-static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode)
 {
-       if (!ce)
-               return 0;
-
-       if (S_ISGITLINK(ce->ce_mode)) {
+       if (S_ISGITLINK(mode)) {
                strbuf_grow(buf, 100);
-               strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
+               strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(sha1));
        } else {
                enum object_type type;
                unsigned long sz;
                char *result;
 
-               result = read_sha1_file(ce->sha1, &type, &sz);
+               result = read_sha1_file(sha1, &type, &sz);
                if (!result)
                        return -1;
                /* XXX read_sha1_file NUL-terminates */
@@ -2959,6 +2965,13 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
        return 0;
 }
 
+static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+{
+       if (!ce)
+               return 0;
+       return read_blob_object(buf, ce->sha1, ce->ce_mode);
+}
+
 static struct patch *in_fn_table(const char *name)
 {
        struct string_list_item *item;
@@ -2977,9 +2990,15 @@ static struct patch *in_fn_table(const char *name)
  * item->util in the filename table records the status of the path.
  * Usually it points at a patch (whose result records the contents
  * of it after applying it), but it could be PATH_WAS_DELETED for a
- * path that a previously applied patch has already removed.
+ * path that a previously applied patch has already removed, or
+ * PATH_TO_BE_DELETED for a path that a later patch would remove.
+ *
+ * The latter is needed to deal with a case where two paths A and B
+ * are swapped by first renaming A to B and then renaming B to A;
+ * moving A to B should not be prevented due to presense of B as we
+ * will remove it in a later patch.
  */
- #define PATH_TO_BE_DELETED ((struct patch *) -2)
+#define PATH_TO_BE_DELETED ((struct patch *) -2)
 #define PATH_WAS_DELETED ((struct patch *) -1)
 
 static int to_be_deleted(struct patch *patch)
@@ -3031,127 +3050,324 @@ static void prepare_fn_table(struct patch *patch)
        }
 }
 
-static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
+static int checkout_target(struct cache_entry *ce, struct stat *st)
+{
+       struct checkout costate;
+
+       memset(&costate, 0, sizeof(costate));
+       costate.base_dir = "";
+       costate.refresh_cache = 1;
+       if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st))
+               return error(_("cannot checkout %s"), ce->name);
+       return 0;
+}
+
+static struct patch *previous_patch(struct patch *patch, int *gone)
+{
+       struct patch *previous;
+
+       *gone = 0;
+       if (patch->is_copy || patch->is_rename)
+               return NULL; /* "git" patches do not depend on the order */
+
+       previous = in_fn_table(patch->old_name);
+       if (!previous)
+               return NULL;
+
+       if (to_be_deleted(previous))
+               return NULL; /* the deletion hasn't happened yet */
+
+       if (was_deleted(previous))
+               *gone = 1;
+
+       return previous;
+}
+
+static int verify_index_match(struct cache_entry *ce, struct stat *st)
+{
+       if (S_ISGITLINK(ce->ce_mode)) {
+               if (!S_ISDIR(st->st_mode))
+                       return -1;
+               return 0;
+       }
+       return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+}
+
+#define SUBMODULE_PATCH_WITHOUT_INDEX 1
+
+static int load_patch_target(struct strbuf *buf,
+                            struct cache_entry *ce,
+                            struct stat *st,
+                            const char *name,
+                            unsigned expected_mode)
+{
+       if (cached) {
+               if (read_file_or_gitlink(ce, buf))
+                       return error(_("read of %s failed"), name);
+       } else if (name) {
+               if (S_ISGITLINK(expected_mode)) {
+                       if (ce)
+                               return read_file_or_gitlink(ce, buf);
+                       else
+                               return SUBMODULE_PATCH_WITHOUT_INDEX;
+               } else {
+                       if (read_old_data(st, name, buf))
+                               return error(_("read of %s failed"), name);
+               }
+       }
+       return 0;
+}
+
+/*
+ * We are about to apply "patch"; populate the "image" with the
+ * current version we have, from the working tree or from the index,
+ * depending on the situation e.g. --cached/--index.  If we are
+ * applying a non-git patch that incrementally updates the tree,
+ * we read from the result of a previous diff.
+ */
+static int load_preimage(struct image *image,
+                        struct patch *patch, struct stat *st, struct cache_entry *ce)
 {
        struct strbuf buf = STRBUF_INIT;
-       struct image image;
        size_t len;
        char *img;
-       struct patch *tpatch;
+       struct patch *previous;
+       int status;
 
-       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"),
-                               patch->old_name);
-               }
+       previous = previous_patch(patch, &status);
+       if (status)
+               return error(_("path %s has been renamed/deleted"),
+                            patch->old_name);
+       if (previous) {
                /* We have a patched copy in memory; use that. */
-               strbuf_add(&buf, tpatch->result, tpatch->resultsize);
-       } else if (cached) {
-               if (read_file_or_gitlink(ce, &buf))
+               strbuf_add(&buf, previous->result, previous->resultsize);
+       } else {
+               status = load_patch_target(&buf, ce, st,
+                                          patch->old_name, patch->old_mode);
+               if (status < 0)
+                       return status;
+               else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) {
+                       /*
+                        * There is no way to apply subproject
+                        * patch without looking at the index.
+                        * NEEDSWORK: shouldn't this be flagged
+                        * as an error???
+                        */
+                       free_fragment_list(patch->fragments);
+                       patch->fragments = NULL;
+               } else if (status) {
                        return error(_("read of %s failed"), patch->old_name);
-       } else if (patch->old_name) {
-               if (S_ISGITLINK(patch->old_mode)) {
-                       if (ce) {
-                               read_file_or_gitlink(ce, &buf);
-                       } else {
-                               /*
-                                * 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);
                }
        }
 
        img = strbuf_detach(&buf, &len);
-       prepare_image(&image, img, len, !patch->is_binary);
+       prepare_image(image, img, len, !patch->is_binary);
+       return 0;
+}
 
-       if (apply_fragments(&image, patch) < 0)
-               return -1; /* note with --reject this succeeds. */
-       patch->result = image.buf;
-       patch->resultsize = image.len;
-       add_to_fn_table(patch);
-       free(image.line_allocated);
+static int three_way_merge(struct image *image,
+                          char *path,
+                          const unsigned char *base,
+                          const unsigned char *ours,
+                          const unsigned char *theirs)
+{
+       mmfile_t base_file, our_file, their_file;
+       mmbuffer_t result = { NULL };
+       int status;
 
-       if (0 < patch->is_delete && patch->resultsize)
-               return error(_("removal patch leaves file contents"));
+       read_mmblob(&base_file, base);
+       read_mmblob(&our_file, ours);
+       read_mmblob(&their_file, theirs);
+       status = ll_merge(&result, path,
+                         &base_file, "base",
+                         &our_file, "ours",
+                         &their_file, "theirs", NULL);
+       free(base_file.ptr);
+       free(our_file.ptr);
+       free(their_file.ptr);
+       if (status < 0 || !result.ptr) {
+               free(result.ptr);
+               return -1;
+       }
+       clear_image(image);
+       image->buf = result.ptr;
+       image->len = result.size;
 
+       return status;
+}
+
+/*
+ * When directly falling back to add/add three-way merge, we read from
+ * the current contents of the new_name.  In no cases other than that
+ * this function will be called.
+ */
+static int load_current(struct image *image, struct patch *patch)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int status, pos;
+       size_t len;
+       char *img;
+       struct stat st;
+       struct cache_entry *ce;
+       char *name = patch->new_name;
+       unsigned mode = patch->new_mode;
+
+       if (!patch->is_new)
+               die("BUG: patch to %s is not a creation", patch->old_name);
+
+       pos = cache_name_pos(name, strlen(name));
+       if (pos < 0)
+               return error(_("%s: does not exist in index"), name);
+       ce = active_cache[pos];
+       if (lstat(name, &st)) {
+               if (errno != ENOENT)
+                       return error(_("%s: %s"), name, strerror(errno));
+               if (checkout_target(ce, &st))
+                       return -1;
+       }
+       if (verify_index_match(ce, &st))
+               return error(_("%s: does not match index"), name);
+
+       status = load_patch_target(&buf, ce, &st, name, mode);
+       if (status < 0)
+               return status;
+       else if (status)
+               return -1;
+       img = strbuf_detach(&buf, &len);
+       prepare_image(image, img, len, !patch->is_binary);
        return 0;
 }
 
-static int check_to_create_blob(const char *new_name, int ok_if_exists)
+static int try_threeway(struct image *image, struct patch *patch,
+                       struct stat *st, struct cache_entry *ce)
 {
-       struct stat nst;
-       if (!lstat(new_name, &nst)) {
-               if (S_ISDIR(nst.st_mode) || ok_if_exists)
-                       return 0;
-               /*
-                * A leading component of new_name might be a symlink
-                * that is going to be removed with this patch, but
-                * still pointing at somewhere that has the path.
-                * In such a case, path "new_name" does not exist as
-                * far as git is concerned.
-                */
-               if (has_symlink_leading_path(new_name, strlen(new_name)))
-                       return 0;
+       unsigned char pre_sha1[20], post_sha1[20], our_sha1[20];
+       struct strbuf buf = STRBUF_INIT;
+       size_t len;
+       int status;
+       char *img;
+       struct image tmp_image;
+
+       /* No point falling back to 3-way merge in these cases */
+       if (patch->is_delete ||
+           S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode))
+               return -1;
 
-               return error(_("%s: already exists in working directory"), new_name);
+       /* Preimage the patch was prepared for */
+       if (patch->is_new)
+               write_sha1_file("", 0, blob_type, pre_sha1);
+       else if (get_sha1(patch->old_sha1_prefix, pre_sha1) ||
+                read_blob_object(&buf, pre_sha1, patch->old_mode))
+               return error("repository lacks the necessary blob to fall back on 3-way merge.");
+
+       fprintf(stderr, "Falling back to three-way merge...\n");
+
+       img = strbuf_detach(&buf, &len);
+       prepare_image(&tmp_image, img, len, 1);
+       /* Apply the patch to get the post image */
+       if (apply_fragments(&tmp_image, patch) < 0) {
+               clear_image(&tmp_image);
+               return -1;
+       }
+       /* post_sha1[] is theirs */
+       write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_sha1);
+       clear_image(&tmp_image);
+
+       /* our_sha1[] is ours */
+       if (patch->is_new) {
+               if (load_current(&tmp_image, patch))
+                       return error("cannot read the current contents of '%s'",
+                                    patch->new_name);
+       } else {
+               if (load_preimage(&tmp_image, patch, st, ce))
+                       return error("cannot read the current contents of '%s'",
+                                    patch->old_name);
+       }
+       write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_sha1);
+       clear_image(&tmp_image);
+
+       /* in-core three-way merge between post and our using pre as base */
+       status = three_way_merge(image, patch->new_name,
+                                pre_sha1, our_sha1, post_sha1);
+       if (status < 0) {
+               fprintf(stderr, "Failed to fall back on three-way merge...\n");
+               return status;
+       }
+
+       if (status) {
+               patch->conflicted_threeway = 1;
+               if (patch->is_new)
+                       hashclr(patch->threeway_stage[0]);
+               else
+                       hashcpy(patch->threeway_stage[0], pre_sha1);
+               hashcpy(patch->threeway_stage[1], our_sha1);
+               hashcpy(patch->threeway_stage[2], post_sha1);
+               fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name);
+       } else {
+               fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name);
        }
-       else if ((errno != ENOENT) && (errno != ENOTDIR))
-               return error("%s: %s", new_name, strerror(errno));
        return 0;
 }
 
-static int verify_index_match(struct cache_entry *ce, struct stat *st)
+static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
 {
-       if (S_ISGITLINK(ce->ce_mode)) {
-               if (!S_ISDIR(st->st_mode))
+       struct image image;
+
+       if (load_preimage(&image, patch, st, ce) < 0)
+               return -1;
+
+       if (patch->direct_to_threeway ||
+           apply_fragments(&image, patch) < 0) {
+               /* Note: with --reject, apply_fragments() returns 0 */
+               if (!threeway || try_threeway(&image, patch, st, ce) < 0)
                        return -1;
-               return 0;
        }
-       return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+       patch->result = image.buf;
+       patch->resultsize = image.len;
+       add_to_fn_table(patch);
+       free(image.line_allocated);
+
+       if (0 < patch->is_delete && patch->resultsize)
+               return error(_("removal patch leaves file contents"));
+
+       return 0;
 }
 
+/*
+ * If "patch" that we are looking at modifies or deletes what we have,
+ * we would want it not to lose any local modification we have, either
+ * in the working tree or in the index.
+ *
+ * This also decides if a non-git patch is a creation patch or a
+ * modification to an existing empty file.  We do not check the state
+ * of the current tree for a creation patch in this function; the caller
+ * check_patch() separately makes sure (and errors out otherwise) that
+ * the path the patch creates does not exist in the current tree.
+ */
 static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
 {
        const char *old_name = patch->old_name;
-       struct patch *tpatch = NULL;
-       int stat_ret = 0;
+       struct patch *previous = NULL;
+       int stat_ret = 0, status;
        unsigned st_mode = 0;
 
-       /*
-        * Make sure that we do not have local modifications from the
-        * index when we are looking at the index.  Also make sure
-        * we have the preimage file to be patched in the work tree,
-        * unless --cached, which tells git to apply only in the index.
-        */
        if (!old_name)
                return 0;
 
        assert(patch->is_new <= 0);
+       previous = previous_patch(patch, &status);
 
-       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);
-               st_mode = tpatch->new_mode;
+       if (status)
+               return error(_("path %s has been renamed/deleted"), old_name);
+       if (previous) {
+               st_mode = previous->new_mode;
        } else if (!cached) {
                stat_ret = lstat(old_name, st);
                if (stat_ret && errno != ENOENT)
                        return error(_("%s: %s"), old_name, strerror(errno));
        }
 
-       if (to_be_deleted(tpatch))
-               tpatch = NULL;
-
-       if (check_index && !tpatch) {
+       if (check_index && !previous) {
                int pos = cache_name_pos(old_name, strlen(old_name));
                if (pos < 0) {
                        if (patch->is_new < 0)
@@ -3160,13 +3376,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
                }
                *ce = active_cache[pos];
                if (stat_ret < 0) {
-                       struct checkout costate;
-                       /* checkout */
-                       memset(&costate, 0, sizeof(costate));
-                       costate.base_dir = "";
-                       costate.refresh_cache = 1;
-                       if (checkout_entry(*ce, &costate, NULL) ||
-                           lstat(old_name, st))
+                       if (checkout_target(*ce, st))
                                return -1;
                }
                if (!cached && verify_index_match(*ce, st))
@@ -3179,7 +3389,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
                return error(_("%s: %s"), old_name, strerror(errno));
        }
 
-       if (!cached && !tpatch)
+       if (!cached && !previous)
                st_mode = ce_mode_from_stat(*ce, st->st_mode);
 
        if (patch->is_new < 0)
@@ -3203,6 +3413,41 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
        return 0;
 }
 
+
+#define EXISTS_IN_INDEX 1
+#define EXISTS_IN_WORKTREE 2
+
+static int check_to_create(const char *new_name, int ok_if_exists)
+{
+       struct stat nst;
+
+       if (check_index &&
+           cache_name_pos(new_name, strlen(new_name)) >= 0 &&
+           !ok_if_exists)
+               return EXISTS_IN_INDEX;
+       if (cached)
+               return 0;
+
+       if (!lstat(new_name, &nst)) {
+               if (S_ISDIR(nst.st_mode) || ok_if_exists)
+                       return 0;
+               /*
+                * A leading component of new_name might be a symlink
+                * that is going to be removed with this patch, but
+                * still pointing at somewhere that has the path.
+                * In such a case, path "new_name" does not exist as
+                * far as git is concerned.
+                */
+               if (has_symlink_leading_path(new_name, strlen(new_name)))
+                       return 0;
+
+               return EXISTS_IN_WORKTREE;
+       } else if ((errno != ENOENT) && (errno != ENOTDIR)) {
+               return error("%s: %s", new_name, strerror(errno));
+       }
+       return 0;
+}
+
 /*
  * Check and apply the patch in-core; leave the result in patch->result
  * for the caller to write it out to the final destination.
@@ -3225,31 +3470,45 @@ static int check_patch(struct patch *patch)
                return status;
        old_name = patch->old_name;
 
+       /*
+        * A type-change diff is always split into a patch to delete
+        * old, immediately followed by a patch to create new (see
+        * diff.c::run_diff()); in such a case it is Ok that the entry
+        * to be deleted by the previous patch is still in the working
+        * tree and in the index.
+        *
+        * A patch to swap-rename between A and B would first rename A
+        * to B and then rename B to A.  While applying the first one,
+        * the presense of B should not stop A from getting renamed to
+        * B; ask to_be_deleted() about the later rename.  Removal of
+        * B and rename from A to B is handled the same way by asking
+        * was_deleted().
+        */
        if ((tpatch = in_fn_table(new_name)) &&
-                       (was_deleted(tpatch) || to_be_deleted(tpatch)))
-               /*
-                * A type-change diff is always split into a patch to
-                * delete old, immediately followed by a patch to
-                * create new (see diff.c::run_diff()); in such a case
-                * it is Ok that the entry to be deleted by the
-                * previous patch is still in the working tree and in
-                * the index.
-                */
+           (was_deleted(tpatch) || to_be_deleted(tpatch)))
                ok_if_exists = 1;
        else
                ok_if_exists = 0;
 
        if (new_name &&
            ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
-               if (check_index &&
-                   cache_name_pos(new_name, strlen(new_name)) >= 0 &&
-                   !ok_if_exists)
+               int err = check_to_create(new_name, ok_if_exists);
+
+               if (err && threeway) {
+                       patch->direct_to_threeway = 1;
+               } else switch (err) {
+               case 0:
+                       break; /* happy */
+               case EXISTS_IN_INDEX:
                        return error(_("%s: already exists in index"), new_name);
-               if (!cached) {
-                       int err = check_to_create_blob(new_name, ok_if_exists);
-                       if (err)
-                               return err;
+                       break;
+               case EXISTS_IN_WORKTREE:
+                       return error(_("%s: already exists in working directory"),
+                                    new_name);
+               default:
+                       return err;
                }
+
                if (!patch->new_mode) {
                        if (0 < patch->is_new)
                                patch->new_mode = S_IFREG | 0644;
@@ -3330,7 +3589,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename)
                name = patch->old_name ? patch->old_name : patch->new_name;
                if (0 < patch->is_new)
                        continue;
-               else if (get_sha1(patch->old_sha1_prefix, sha1))
+               else if (get_sha1_blob(patch->old_sha1_prefix, sha1))
                        /* git diff has no index line for mode/type changes */
                        if (!patch->lines_added && !patch->lines_deleted) {
                                if (get_current_sha1(patch->old_name, sha1))
@@ -3510,7 +3769,8 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
        ce = xcalloc(1, ce_size);
        memcpy(ce->name, path, namelen);
        ce->ce_mode = create_ce_mode(mode);
-       ce->ce_flags = namelen;
+       ce->ce_flags = create_ce_flags(0);
+       ce->ce_namelen = namelen;
        if (S_ISGITLINK(mode)) {
                const char *s = buf;
 
@@ -3612,6 +3872,33 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
        die_errno(_("unable to write file '%s' mode %o"), path, mode);
 }
 
+static void add_conflicted_stages_file(struct patch *patch)
+{
+       int stage, namelen;
+       unsigned ce_size, mode;
+       struct cache_entry *ce;
+
+       if (!update_index)
+               return;
+       namelen = strlen(patch->new_name);
+       ce_size = cache_entry_size(namelen);
+       mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644);
+
+       remove_file_from_cache(patch->new_name);
+       for (stage = 1; stage < 4; stage++) {
+               if (is_null_sha1(patch->threeway_stage[stage - 1]))
+                       continue;
+               ce = xcalloc(1, ce_size);
+               memcpy(ce->name, patch->new_name, namelen);
+               ce->ce_mode = create_ce_mode(mode);
+               ce->ce_flags = create_ce_flags(stage);
+               ce->ce_namelen = namelen;
+               hashcpy(ce->sha1, patch->threeway_stage[stage - 1]);
+               if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
+                       die(_("unable to add cache entry for %s"), patch->new_name);
+       }
+}
+
 static void create_file(struct patch *patch)
 {
        char *path = patch->new_name;
@@ -3622,7 +3909,11 @@ static void create_file(struct patch *patch)
        if (!mode)
                mode = S_IFREG | 0644;
        create_one_file(path, mode, buf, size);
-       add_index_file(path, mode, buf, size);
+
+       if (patch->conflicted_threeway)
+               add_conflicted_stages_file(patch);
+       else
+               add_index_file(path, mode, buf, size);
 }
 
 /* phase zero is to remove, phase one is to create */
@@ -3724,6 +4015,7 @@ static int write_out_results(struct patch *list)
        int phase;
        int errs = 0;
        struct patch *l;
+       struct string_list cpath = STRING_LIST_INIT_DUP;
 
        for (phase = 0; phase < 2; phase++) {
                l = list;
@@ -3732,12 +4024,30 @@ static int write_out_results(struct patch *list)
                                errs = 1;
                        else {
                                write_out_one_result(l, phase);
-                               if (phase == 1 && write_out_one_reject(l))
-                                       errs = 1;
+                               if (phase == 1) {
+                                       if (write_out_one_reject(l))
+                                               errs = 1;
+                                       if (l->conflicted_threeway) {
+                                               string_list_append(&cpath, l->new_name);
+                                               errs = 1;
+                                       }
+                               }
                        }
                        l = l->next;
                }
        }
+
+       if (cpath.nr) {
+               struct string_list_item *item;
+
+               sort_string_list(&cpath);
+               for_each_string_list_item(item, &cpath)
+                       fprintf(stderr, "U %s\n", item->string);
+               string_list_clear(&cpath, 0);
+
+               rerere(0);
+       }
+
        return errs;
 }
 
@@ -3860,8 +4170,12 @@ static int apply_patch(int fd, const char *filename, int options)
            !apply_with_reject)
                exit(1);
 
-       if (apply && write_out_results(list))
-               exit(1);
+       if (apply && write_out_results(list)) {
+               if (apply_with_reject)
+                       exit(1);
+               /* with --3way, we still need to write the index out */
+               return 1;
+       }
 
        if (fake_ancestor)
                build_fake_ancestor(list, fake_ancestor);
@@ -3994,6 +4308,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
                        N_("apply a patch without touching the working tree")),
                OPT_BOOLEAN(0, "apply", &force_apply,
                        N_("also apply the patch (use with --stat/--summary/--check)")),
+               OPT_BOOL('3', "3way", &threeway,
+                        N_( "attempt three-way merge if a patch does not apply")),
                OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
                        N_("build a temporary index based on embedded index information")),
                { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
@@ -4042,6 +4358,15 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
        argc = parse_options(argc, argv, prefix, builtin_apply_options,
                        apply_usage, 0);
 
+       if (apply_with_reject && threeway)
+               die("--reject and --3way cannot be used together.");
+       if (cached && threeway)
+               die("--cached and --3way cannot be used together.");
+       if (threeway) {
+               if (is_not_gitdir)
+                       die(_("--3way outside a repository"));
+               check_index = 1;
+       }
        if (apply_with_reject)
                apply = apply_verbosely = 1;
        if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
index 960c58d855a6f1a04ad1d08637fc75c3da240a30..0d50273ce975d956cba3c17160bf3704b57f3f9c 100644 (file)
@@ -2171,7 +2171,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        ce = xcalloc(1, size);
        hashcpy(ce->sha1, origin->blob_sha1);
        memcpy(ce->name, path, len);
-       ce->ce_flags = create_ce_flags(len, 0);
+       ce->ce_flags = create_ce_flags(0);
+       ce->ce_namelen = len;
        ce->ce_mode = create_ce_mode(mode);
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
 
index 36a9104433e23422aab39b1912e998a7f54cd3f4..af74e775a182bdf764436362cb37471e74930459 100644 (file)
@@ -91,7 +91,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
        unsigned long size;
        struct object_context obj_context;
 
-       if (get_sha1_with_context(obj_name, sha1, &obj_context))
+       if (get_sha1_with_context(obj_name, 0, sha1, &obj_context))
                die("Not a valid object name %s", obj_name);
 
        buf = NULL;
index 3980d5d06ea5aba4f4d6d30089307fe76c8adff9..6acca75f4740e9412c418124ce791c00f7b4ba4b 100644 (file)
@@ -73,7 +73,8 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
        hashcpy(ce->sha1, sha1);
        memcpy(ce->name, base, baselen);
        memcpy(ce->name + baselen, pathname, len - baselen);
-       ce->ce_flags = create_ce_flags(len, 0) | CE_UPDATE;
+       ce->ce_flags = create_ce_flags(0) | CE_UPDATE;
+       ce->ce_namelen = len;
        ce->ce_mode = create_ce_mode(mode);
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
        return 0;
index 920ef7f8c9fff25f613b4261f215a0cb7dc83b61..e314b0b6d2133b09307dc09fa256577e6cdba03a 100644 (file)
@@ -38,7 +38,7 @@ static const char * const builtin_clone_usage[] = {
 };
 
 static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
-static int option_local, option_no_hardlinks, option_shared, option_recursive;
+static int option_local = -1, option_no_hardlinks, option_shared, option_recursive;
 static char *option_template, *option_depth;
 static char *option_origin = NULL;
 static char *option_branch = NULL;
@@ -70,8 +70,8 @@ static struct option builtin_clone_options[] = {
                PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
        OPT_BOOLEAN(0, "mirror", &option_mirror,
                    "create a mirror repository (implies bare)"),
-       OPT_BOOLEAN('l', "local", &option_local,
-                   "to clone from a local repository"),
+       OPT_BOOL('l', "local", &option_local,
+               "to clone from a local repository"),
        OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
                    "don't use local hardlinks, always copy"),
        OPT_BOOLEAN('s', "shared", &option_shared,
@@ -342,7 +342,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
                if (!option_no_hardlinks) {
                        if (!link(src->buf, dest->buf))
                                continue;
-                       if (option_local)
+                       if (option_local > 0)
                                die_errno(_("failed to create link '%s'"), dest->buf);
                        option_no_hardlinks = 1;
                }
@@ -671,7 +671,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                die(_("repository '%s' does not exist"), repo_name);
        else
                repo = repo_name;
-       is_local = path && !is_bundle;
+       is_local = option_local != 0 && path && !is_bundle;
        if (is_local && option_depth)
                warning(_("--depth is ignored in local clones; use file:// instead."));
 
index 164b655df93fea1ec2f63f5238c1ad7a377c8385..eac901a0ee15f72eacffea32d1b327c7a336f19c 100644 (file)
@@ -48,16 +48,13 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
        if (argc < 2 || !strcmp(argv[1], "-h"))
                usage(commit_tree_usage);
 
-       if (get_sha1(argv[1], tree_sha1))
-               die("Not a valid object name %s", argv[1]);
-
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (!strcmp(arg, "-p")) {
                        unsigned char sha1[20];
                        if (argc <= ++i)
                                usage(commit_tree_usage);
-                       if (get_sha1(argv[i], sha1))
+                       if (get_sha1_commit(argv[i], sha1))
                                die("Not a valid object name %s", argv[i]);
                        assert_sha1_type(sha1, OBJ_COMMIT);
                        new_parent(lookup_commit(sha1), &parents);
@@ -104,7 +101,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
                        continue;
                }
 
-               if (get_sha1(arg, tree_sha1))
+               if (get_sha1_tree(arg, tree_sha1))
                        die("Not a valid object name %s", arg);
                if (got_tree)
                        die("Cannot give more than one trees");
index 95eeab1d5146628277b0ba2c4e4bc7a6f77f3a4a..20cef95d600aeb15b716793beb5fb2534cc2b682 100644 (file)
@@ -725,7 +725,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
        strbuf_release(&sb);
 
        /* This checks if committer ident is explicitly given */
-       strbuf_addstr(&committer_ident, git_committer_info(0));
+       strbuf_addstr(&committer_ident, git_committer_info(IDENT_STRICT));
        if (use_editor && include_status) {
                char *ai_tmp, *ci_tmp;
                if (whence != FROM_COMMIT)
index 33c8820af6fc73453b749ec6026077f76180e26c..8cd08da99122bc79025d2a78204d316f1b7ba478 100644 (file)
@@ -161,7 +161,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
 static int get_value(const char *key_, const char *regex_)
 {
        int ret = -1;
-       char *global = NULL, *repo_config = NULL;
+       char *global = NULL, *xdg = NULL, *repo_config = NULL;
        const char *system_wide = NULL, *local;
        struct config_include_data inc = CONFIG_INCLUDE_INIT;
        config_fn_t fn;
@@ -169,12 +169,10 @@ static int get_value(const char *key_, const char *regex_)
 
        local = given_config_file;
        if (!local) {
-               const char *home = getenv("HOME");
                local = repo_config = git_pathdup("config");
-               if (home)
-                       global = xstrdup(mkpath("%s/.gitconfig", home));
                if (git_config_system())
                        system_wide = git_etc_gitconfig();
+               home_config_paths(&global, &xdg, "config");
        }
 
        if (use_key_regexp) {
@@ -229,6 +227,8 @@ static int get_value(const char *key_, const char *regex_)
 
        if (do_all && system_wide)
                git_config_from_file(fn, system_wide, data);
+       if (do_all && xdg)
+               git_config_from_file(fn, xdg, data);
        if (do_all && global)
                git_config_from_file(fn, global, data);
        if (do_all)
@@ -238,6 +238,8 @@ static int get_value(const char *key_, const char *regex_)
                git_config_from_file(fn, local, data);
        if (!do_all && !seen && global)
                git_config_from_file(fn, global, data);
+       if (!do_all && !seen && xdg)
+               git_config_from_file(fn, xdg, data);
        if (!do_all && !seen && system_wide)
                git_config_from_file(fn, system_wide, data);
 
@@ -255,6 +257,7 @@ static int get_value(const char *key_, const char *regex_)
 free_strings:
        free(repo_config);
        free(global);
+       free(xdg);
        return ret;
 }
 
@@ -379,13 +382,25 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        }
 
        if (use_global_config) {
-               char *home = getenv("HOME");
-               if (home) {
-                       char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
-                       given_config_file = user_config;
-               } else {
+               char *user_config = NULL;
+               char *xdg_config = NULL;
+
+               home_config_paths(&user_config, &xdg_config, "config");
+
+               if (!user_config)
+                       /*
+                        * It is unknown if HOME/.gitconfig exists, so
+                        * we do not know if we should write to XDG
+                        * location; error out even if XDG_CONFIG_HOME
+                        * is set and points at a sane location.
+                        */
                        die("$HOME not set");
-               }
+
+               if (access(user_config, R_OK) &&
+                   xdg_config && !access(xdg_config, R_OK))
+                       given_config_file = xdg_config;
+               else
+                       given_config_file = user_config;
        }
        else if (use_system_config)
                given_config_file = git_etc_gitconfig();
diff --git a/builtin/credential.c b/builtin/credential.c
new file mode 100644 (file)
index 0000000..0412fa0
--- /dev/null
@@ -0,0 +1,31 @@
+#include "git-compat-util.h"
+#include "credential.h"
+#include "builtin.h"
+
+static const char usage_msg[] =
+       "git credential [fill|approve|reject]";
+
+int cmd_credential(int argc, const char **argv, const char *prefix)
+{
+       const char *op;
+       struct credential c = CREDENTIAL_INIT;
+
+       op = argv[1];
+       if (!op)
+               usage(usage_msg);
+
+       if (credential_read(&c, stdin) < 0)
+               die("unable to read credential from stdin");
+
+       if (!strcmp(op, "fill")) {
+               credential_fill(&c);
+               credential_write(&c, stdout);
+       } else if (!strcmp(op, "approve")) {
+               credential_approve(&c);
+       } else if (!strcmp(op, "reject")) {
+               credential_reject(&c);
+       } else {
+               usage(usage_msg);
+       }
+       return 0;
+}
index 43d3c84449a57ec7028acb663d4ab4bea134c97e..efea4f55e173a5ff2d7e1748316d6a94266e915a 100644 (file)
 #include "column.h"
 #include "help.h"
 
+#ifndef DEFAULT_HELP_FORMAT
+#define DEFAULT_HELP_FORMAT "man"
+#endif
+
 static struct man_viewer_list {
        struct man_viewer_list *next;
        char name[FLEX_ARRAY];
@@ -30,6 +34,8 @@ enum help_format {
        HELP_FORMAT_WEB
 };
 
+static const char *html_path;
+
 static int show_all = 0;
 static unsigned int colopts;
 static enum help_format help_format = HELP_FORMAT_NONE;
@@ -261,6 +267,12 @@ static int git_help_config(const char *var, const char *value, void *cb)
                help_format = parse_help_format(value);
                return 0;
        }
+       if (!strcmp(var, "help.htmlpath")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               html_path = xstrdup(value);
+               return 0;
+       }
        if (!strcmp(var, "man.viewer")) {
                if (!value)
                        return config_error_nonbool(var);
@@ -383,12 +395,15 @@ static void show_info_page(const char *git_cmd)
 static void get_html_page_path(struct strbuf *page_path, const char *page)
 {
        struct stat st;
-       const char *html_path = system_path(GIT_HTML_PATH);
+       if (!html_path)
+               html_path = system_path(GIT_HTML_PATH);
 
        /* 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);
+       if (!strstr(html_path, "://")) {
+               if (stat(mkpath("%s/git.html", html_path), &st)
+                   || !S_ISREG(st.st_mode))
+                       die("'%s': not a documentation directory.", html_path);
+       }
 
        strbuf_init(page_path, 0);
        strbuf_addf(page_path, "%s/%s.html", html_path, page);
@@ -447,6 +462,8 @@ int cmd_help(int argc, const char **argv, const char *prefix)
 
        if (parsed_help_format != HELP_FORMAT_NONE)
                help_format = parsed_help_format;
+       if (help_format == HELP_FORMAT_NONE)
+               help_format = parse_help_format(DEFAULT_HELP_FORMAT);
 
        alias = alias_lookup(argv[0]);
        if (alias && !is_git_command(argv[0])) {
index 470547835ca41888817b85dc4b7ed39273ede784..953dd3004e285ce7aa0432f4fe0eacb0d0c2c887 100644 (file)
@@ -9,6 +9,7 @@
 #include "progress.h"
 #include "fsck.h"
 #include "exec_cmd.h"
+#include "streaming.h"
 #include "thread-utils.h"
 
 static const char index_pack_usage[] =
@@ -384,30 +385,62 @@ static void unlink_base_data(struct base_data *c)
        free_base_data(c);
 }
 
-static void *unpack_entry_data(unsigned long offset, unsigned long size)
+static int is_delta_type(enum object_type type)
+{
+       return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+}
+
+static void *unpack_entry_data(unsigned long offset, unsigned long size,
+                              enum object_type type, unsigned char *sha1)
 {
+       static char fixed_buf[8192];
        int status;
        git_zstream stream;
-       void *buf = xmalloc(size);
+       void *buf;
+       git_SHA_CTX c;
+       char hdr[32];
+       int hdrlen;
+
+       if (!is_delta_type(type)) {
+               hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1;
+               git_SHA1_Init(&c);
+               git_SHA1_Update(&c, hdr, hdrlen);
+       } else
+               sha1 = NULL;
+       if (type == OBJ_BLOB && size > big_file_threshold)
+               buf = fixed_buf;
+       else
+               buf = xmalloc(size);
 
        memset(&stream, 0, sizeof(stream));
        git_inflate_init(&stream);
        stream.next_out = buf;
-       stream.avail_out = size;
+       stream.avail_out = buf == fixed_buf ? sizeof(fixed_buf) : size;
 
        do {
+               unsigned char *last_out = stream.next_out;
                stream.next_in = fill(1);
                stream.avail_in = input_len;
                status = git_inflate(&stream, 0);
                use(input_len - stream.avail_in);
+               if (sha1)
+                       git_SHA1_Update(&c, last_out, stream.next_out - last_out);
+               if (buf == fixed_buf) {
+                       stream.next_out = buf;
+                       stream.avail_out = sizeof(fixed_buf);
+               }
        } while (status == Z_OK);
        if (stream.total_out != size || status != Z_STREAM_END)
                bad_object(offset, _("inflate returned %d"), status);
        git_inflate_end(&stream);
-       return buf;
+       if (sha1)
+               git_SHA1_Final(sha1, &c);
+       return buf == fixed_buf ? NULL : buf;
 }
 
-static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
+static void *unpack_raw_entry(struct object_entry *obj,
+                             union delta_base *delta_base,
+                             unsigned char *sha1)
 {
        unsigned char *p;
        unsigned long size, c;
@@ -467,12 +500,14 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        }
        obj->hdr_size = consumed_bytes - obj->idx.offset;
 
-       data = unpack_entry_data(obj->idx.offset, obj->size);
+       data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, sha1);
        obj->idx.crc32 = input_crc32;
        return data;
 }
 
-static void *get_data_from_pack(struct object_entry *obj)
+static void *unpack_data(struct object_entry *obj,
+                        int (*consume)(const unsigned char *, unsigned long, void *),
+                        void *cb_data)
 {
        off_t from = obj[0].idx.offset + obj[0].hdr_size;
        unsigned long len = obj[1].idx.offset - from;
@@ -480,13 +515,13 @@ static void *get_data_from_pack(struct object_entry *obj)
        git_zstream stream;
        int status;
 
-       data = xmalloc(obj->size);
+       data = xmalloc(consume ? 64*1024 : obj->size);
        inbuf = xmalloc((len < 64*1024) ? len : 64*1024);
 
        memset(&stream, 0, sizeof(stream));
        git_inflate_init(&stream);
        stream.next_out = data;
-       stream.avail_out = obj->size;
+       stream.avail_out = consume ? 64*1024 : obj->size;
 
        do {
                ssize_t n = (len < 64*1024) ? len : 64*1024;
@@ -502,7 +537,20 @@ static void *get_data_from_pack(struct object_entry *obj)
                len -= n;
                stream.next_in = inbuf;
                stream.avail_in = n;
-               status = git_inflate(&stream, 0);
+               if (!consume)
+                       status = git_inflate(&stream, 0);
+               else {
+                       do {
+                               status = git_inflate(&stream, 0);
+                               if (consume(data, stream.next_out - data, cb_data)) {
+                                       free(inbuf);
+                                       free(data);
+                                       return NULL;
+                               }
+                               stream.next_out = data;
+                               stream.avail_out = 64*1024;
+                       } while (status == Z_OK && stream.avail_in);
+               }
        } while (len && status == Z_OK && !stream.avail_in);
 
        /* This has been inflated OK when first encountered, so... */
@@ -511,9 +559,18 @@ static void *get_data_from_pack(struct object_entry *obj)
 
        git_inflate_end(&stream);
        free(inbuf);
+       if (consume) {
+               free(data);
+               data = NULL;
+       }
        return data;
 }
 
+static void *get_data_from_pack(struct object_entry *obj)
+{
+       return unpack_data(obj, NULL, NULL);
+}
+
 static int compare_delta_bases(const union delta_base *base1,
                               const union delta_base *base2,
                               enum object_type type1,
@@ -568,25 +625,102 @@ static void find_delta_children(const union delta_base *base,
        *last_index = last;
 }
 
-static void sha1_object(const void *data, unsigned long size,
-                       enum object_type type, unsigned char *sha1)
+struct compare_data {
+       struct object_entry *entry;
+       struct git_istream *st;
+       unsigned char *buf;
+       unsigned long buf_size;
+};
+
+static int compare_objects(const unsigned char *buf, unsigned long size,
+                          void *cb_data)
+{
+       struct compare_data *data = cb_data;
+
+       if (data->buf_size < size) {
+               free(data->buf);
+               data->buf = xmalloc(size);
+               data->buf_size = size;
+       }
+
+       while (size) {
+               ssize_t len = read_istream(data->st, data->buf, size);
+               if (len == 0)
+                       die(_("SHA1 COLLISION FOUND WITH %s !"),
+                           sha1_to_hex(data->entry->idx.sha1));
+               if (len < 0)
+                       die(_("unable to read %s"),
+                           sha1_to_hex(data->entry->idx.sha1));
+               if (memcmp(buf, data->buf, len))
+                       die(_("SHA1 COLLISION FOUND WITH %s !"),
+                           sha1_to_hex(data->entry->idx.sha1));
+               size -= len;
+               buf += len;
+       }
+       return 0;
+}
+
+static int check_collison(struct object_entry *entry)
+{
+       struct compare_data data;
+       enum object_type type;
+       unsigned long size;
+
+       if (entry->size <= big_file_threshold || entry->type != OBJ_BLOB)
+               return -1;
+
+       memset(&data, 0, sizeof(data));
+       data.entry = entry;
+       data.st = open_istream(entry->idx.sha1, &type, &size, NULL);
+       if (!data.st)
+               return -1;
+       if (size != entry->size || type != entry->type)
+               die(_("SHA1 COLLISION FOUND WITH %s !"),
+                   sha1_to_hex(entry->idx.sha1));
+       unpack_data(entry, compare_objects, &data);
+       close_istream(data.st);
+       free(data.buf);
+       return 0;
+}
+
+static void sha1_object(const void *data, struct object_entry *obj_entry,
+                       unsigned long size, enum object_type type,
+                       const unsigned char *sha1)
 {
-       hash_sha1_file(data, size, typename(type), sha1);
+       void *new_data = NULL;
+       int collision_test_needed;
+
+       assert(data || obj_entry);
+
        read_lock();
-       if (has_sha1_file(sha1)) {
+       collision_test_needed = has_sha1_file(sha1);
+       read_unlock();
+
+       if (collision_test_needed && !data) {
+               read_lock();
+               if (!check_collison(obj_entry))
+                       collision_test_needed = 0;
+               read_unlock();
+       }
+       if (collision_test_needed) {
                void *has_data;
                enum object_type has_type;
                unsigned long has_size;
+               read_lock();
+               has_type = sha1_object_info(sha1, &has_size);
+               if (has_type != type || has_size != size)
+                       die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1));
                has_data = read_sha1_file(sha1, &has_type, &has_size);
                read_unlock();
+               if (!data)
+                       data = new_data = get_data_from_pack(obj_entry);
                if (!has_data)
                        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));
                free(has_data);
-       } else
-               read_unlock();
+       }
 
        if (strict) {
                read_lock();
@@ -601,6 +735,9 @@ static void sha1_object(const void *data, unsigned long size,
                        int eaten;
                        void *buf = (void *) data;
 
+                       if (!buf)
+                               buf = new_data = get_data_from_pack(obj_entry);
+
                        /*
                         * we do not need to free the memory here, as the
                         * buf is deleted by the caller.
@@ -625,11 +762,8 @@ static void sha1_object(const void *data, unsigned long size,
                }
                read_unlock();
        }
-}
 
-static int is_delta_type(enum object_type type)
-{
-       return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+       free(new_data);
 }
 
 /*
@@ -711,7 +845,9 @@ static void resolve_delta(struct object_entry *delta_obj,
        free(delta_data);
        if (!result->data)
                bad_object(delta_obj->idx.offset, _("failed to apply delta"));
-       sha1_object(result->data, result->size, delta_obj->real_type,
+       hash_sha1_file(result->data, result->size,
+                      typename(delta_obj->real_type), delta_obj->idx.sha1);
+       sha1_object(result->data, NULL, result->size, delta_obj->real_type,
                    delta_obj->idx.sha1);
        counter_lock();
        nr_resolved_deltas++;
@@ -841,7 +977,7 @@ static void *threaded_second_pass(void *data)
  */
 static void parse_pack_objects(unsigned char *sha1)
 {
-       int i;
+       int i, nr_delays = 0;
        struct delta_entry *delta = deltas;
        struct stat st;
 
@@ -851,14 +987,18 @@ static void parse_pack_objects(unsigned char *sha1)
                                nr_objects);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
-               void *data = unpack_raw_entry(obj, &delta->base);
+               void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1);
                obj->real_type = obj->type;
                if (is_delta_type(obj->type)) {
                        nr_deltas++;
                        delta->obj_no = i;
                        delta++;
+               } else if (!data) {
+                       /* large blobs, check later */
+                       obj->real_type = OBJ_BAD;
+                       nr_delays++;
                } else
-                       sha1_object(data, obj->size, obj->type, obj->idx.sha1);
+                       sha1_object(data, NULL, obj->size, obj->type, obj->idx.sha1);
                free(data);
                display_progress(progress, i+1);
        }
@@ -878,6 +1018,17 @@ static void parse_pack_objects(unsigned char *sha1)
        if (S_ISREG(st.st_mode) &&
                        lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
                die(_("pack has junk at the end"));
+
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               if (obj->real_type != OBJ_BAD)
+                       continue;
+               obj->real_type = obj->type;
+               sha1_object(NULL, obj, obj->size, obj->type, obj->idx.sha1);
+               nr_delays--;
+       }
+       if (nr_delays)
+               die(_("confusion beyond insanity in parse_pack_objects()"));
 }
 
 /*
index 0dacb8b79c57cae2b789eb84d7cfbdb1654ba52f..244fb7fc32e8263c9ab92ca10a440625f4c2944c 100644 (file)
@@ -290,6 +290,7 @@ static int create_default_files(const char *template_path)
                strcpy(path + len, "CoNfIg");
                if (!access(path, F_OK))
                        git_config_set("core.ignorecase", "true");
+               probe_utf8_pathname_composition(path, len);
        }
 
        return reinit;
index 54f24e2088651b70fadb6996f9558989b19d723f..ecc2793690496531546765516d80032a1cbf8844 100644 (file)
@@ -21,6 +21,7 @@
 #include "parse-options.h"
 #include "branch.h"
 #include "streaming.h"
+#include "version.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -366,6 +367,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
        rev.simplify_history = 0;
        memset(&opt, 0, sizeof(opt));
        opt.def = "HEAD";
+       opt.revarg_opt = REVARG_COMMITTISH;
        cmd_log_init(argc, argv, prefix, &rev, &opt);
        if (!rev.diffopt.output_format)
                rev.diffopt.output_format = DIFF_FORMAT_RAW;
@@ -556,6 +558,7 @@ int cmd_log(int argc, const char **argv, const char *prefix)
        rev.always_show_header = 1;
        memset(&opt, 0, sizeof(opt));
        opt.def = "HEAD";
+       opt.revarg_opt = REVARG_COMMITTISH;
        cmd_log_init(argc, argv, prefix, &rev, &opt);
        return cmd_log_walk(&rev);
 }
@@ -1131,6 +1134,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        rev.subject_prefix = fmt_patch_subject_prefix;
        memset(&s_r_opt, 0, sizeof(s_r_opt));
        s_r_opt.def = "HEAD";
+       s_r_opt.revarg_opt = REVARG_COMMITTISH;
 
        if (default_attach) {
                rev.mime_boundary = default_attach;
index ccfcbad14647eff80131cd99c22a18f13016b4af..782e7d0c38aa939a7db03266c3e6ff4993e4d2bd 100644 (file)
@@ -16,6 +16,7 @@
 #include "list-objects.h"
 #include "progress.h"
 #include "refs.h"
+#include "streaming.h"
 #include "thread-utils.h"
 
 static const char *pack_usage[] = {
@@ -150,6 +151,46 @@ static unsigned long do_compress(void **pptr, unsigned long size)
        return stream.total_out;
 }
 
+static unsigned long write_large_blob_data(struct git_istream *st, struct sha1file *f,
+                                          const unsigned char *sha1)
+{
+       git_zstream stream;
+       unsigned char ibuf[1024 * 16];
+       unsigned char obuf[1024 * 16];
+       unsigned long olen = 0;
+
+       memset(&stream, 0, sizeof(stream));
+       git_deflate_init(&stream, pack_compression_level);
+
+       for (;;) {
+               ssize_t readlen;
+               int zret = Z_OK;
+               readlen = read_istream(st, ibuf, sizeof(ibuf));
+               if (readlen == -1)
+                       die(_("unable to read %s"), sha1_to_hex(sha1));
+
+               stream.next_in = ibuf;
+               stream.avail_in = readlen;
+               while ((stream.avail_in || readlen == 0) &&
+                      (zret == Z_OK || zret == Z_BUF_ERROR)) {
+                       stream.next_out = obuf;
+                       stream.avail_out = sizeof(obuf);
+                       zret = git_deflate(&stream, readlen ? 0 : Z_FINISH);
+                       sha1write(f, obuf, stream.next_out - obuf);
+                       olen += stream.next_out - obuf;
+               }
+               if (stream.avail_in)
+                       die(_("deflate error (%d)"), zret);
+               if (readlen == 0) {
+                       if (zret != Z_STREAM_END)
+                               die(_("deflate error (%d)"), zret);
+                       break;
+               }
+       }
+       git_deflate_end(&stream);
+       return olen;
+}
+
 /*
  * we are going to reuse the existing object data as is.  make
  * sure it is not corrupt.
@@ -208,11 +249,18 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent
        unsigned hdrlen;
        enum object_type type;
        void *buf;
+       struct git_istream *st = NULL;
 
        if (!usable_delta) {
-               buf = read_sha1_file(entry->idx.sha1, &type, &size);
-               if (!buf)
-                       die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+               if (entry->type == OBJ_BLOB &&
+                   entry->size > big_file_threshold &&
+                   (st = open_istream(entry->idx.sha1, &type, &size, NULL)) != NULL)
+                       buf = NULL;
+               else {
+                       buf = read_sha1_file(entry->idx.sha1, &type, &size);
+                       if (!buf)
+                               die(_("unable to read %s"), sha1_to_hex(entry->idx.sha1));
+               }
                /*
                 * make sure no cached delta data remains from a
                 * previous attempt before a pack split occurred.
@@ -233,7 +281,9 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent
                        OBJ_OFS_DELTA : OBJ_REF_DELTA;
        }
 
-       if (entry->z_delta_size)
+       if (st) /* large blob case, just assume we don't compress well */
+               datalen = size;
+       else if (entry->z_delta_size)
                datalen = entry->z_delta_size;
        else
                datalen = do_compress(&buf, size);
@@ -256,6 +306,8 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent
                while (ofs >>= 7)
                        dheader[--pos] = 128 | (--ofs & 127);
                if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+                       if (st)
+                               close_istream(st);
                        free(buf);
                        return 0;
                }
@@ -268,6 +320,8 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent
                 * an additional 20 bytes for the base sha1.
                 */
                if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+                       if (st)
+                               close_istream(st);
                        free(buf);
                        return 0;
                }
@@ -276,13 +330,20 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent
                hdrlen += 20;
        } else {
                if (limit && hdrlen + datalen + 20 >= limit) {
+                       if (st)
+                               close_istream(st);
                        free(buf);
                        return 0;
                }
                sha1write(f, header, hdrlen);
        }
-       sha1write(f, buf, datalen);
-       free(buf);
+       if (st) {
+               datalen = write_large_blob_data(st, f, entry->idx.sha1);
+               close_istream(st);
+       } else {
+               sha1write(f, buf, datalen);
+               free(buf);
+       }
 
        return hdrlen + datalen;
 }
@@ -2312,7 +2373,7 @@ static void get_object_list(int ac, const char **av)
                        }
                        die("not a rev '%s'", line);
                }
-               if (handle_revision_arg(line, &revs, flags, 1))
+               if (handle_revision_arg(line, &revs, flags, REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", line);
        }
 
index 062d7dad1b5af720e70adcaa05b60bf68977b05c..b3c9e27bde653bf01acc6126deeb5f508fa0b26e 100644 (file)
@@ -330,8 +330,10 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
                printf("keep %s", message);
        return 0;
  prune:
-       if (!cb->newlog || cb->cmd->verbose)
-               printf("%sprune %s", cb->newlog ? "" : "would ", message);
+       if (!cb->newlog)
+               printf("would prune %s", message);
+       else if (cb->cmd->verbose)
+               printf("prune %s", message);
        return 0;
 }
 
index 4cc34c908446fe2d3db5acf315e47f2768ba07bd..74442bd7663387ab9864927f9c0d511e04e6e379 100644 (file)
@@ -276,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                 * Otherwise, argv[i] could be either <rev> or <paths> and
                 * has to be unambiguous.
                 */
-               else if (!get_sha1(argv[i], sha1)) {
+               else if (!get_sha1_committish(argv[i], sha1)) {
                        /*
                         * Ok, argv[i] looks like a rev; it should not
                         * be a filename.
@@ -289,9 +289,15 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                }
        }
 
-       if (get_sha1(rev, sha1))
+       if (get_sha1_committish(rev, sha1))
                die(_("Failed to resolve '%s' as a valid ref."), rev);
 
+       /*
+        * NOTE: As "git reset $treeish -- $path" should be usable on
+        * any tree-ish, this is not strictly correct. We are not
+        * moving the HEAD to any commit; we are merely resetting the
+        * entries in the index to that of a treeish.
+        */
        commit = lookup_commit_reference(sha1);
        if (!commit)
                die(_("Could not parse object '%s'."), rev);
index 13495b88f5da1efc2094c0e69abfe93605ee8c03..32788a9f86427237d0f4838362c1e3bc722fa258 100644 (file)
@@ -195,6 +195,12 @@ static int anti_reference(const char *refname, const unsigned char *sha1, int fl
        return 0;
 }
 
+static int show_abbrev(const unsigned char *sha1, void *cb_data)
+{
+       show_rev(NORMAL, sha1, NULL);
+       return 0;
+}
+
 static void show_datestring(const char *flag, const char *datestr)
 {
        static char buffer[100];
@@ -238,7 +244,7 @@ static int try_difference(const char *arg)
                next = "HEAD";
        if (dotdot == arg)
                this = "HEAD";
-       if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
+       if (!get_sha1_committish(this, sha1) && !get_sha1_committish(next, end)) {
                show_rev(NORMAL, end, next);
                show_rev(symmetric ? NORMAL : REVERSED, sha1, this);
                if (symmetric) {
@@ -278,7 +284,7 @@ static int try_parent_shorthands(const char *arg)
                return 0;
 
        *dotdot = 0;
-       if (get_sha1(arg, sha1))
+       if (get_sha1_committish(arg, sha1))
                return 0;
 
        if (!parents_only)
@@ -589,6 +595,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                for_each_ref(show_reference, NULL);
                                continue;
                        }
+                       if (!prefixcmp(arg, "--disambiguate=")) {
+                               for_each_abbrev(arg + 15, show_abbrev, NULL);
+                               continue;
+                       }
                        if (!strcmp(arg, "--bisect")) {
                                for_each_ref_in("refs/bisect/bad", show_reference, NULL);
                                for_each_ref_in("refs/bisect/good", anti_reference, NULL);
index 5a4e9ea55a10afe2eb0f6e138cb7fce1ef74393f..4ce341ceebbe27cc21b0341e4a4339af84af51de 100644 (file)
@@ -95,7 +95,8 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru
        size = cache_entry_size(len);
        ce = xcalloc(1, size);
        memcpy(ce->name, path, len);
-       ce->ce_flags = len;
+       ce->ce_flags = create_ce_flags(0);
+       ce->ce_namelen = len;
        fill_stat_cache_info(ce, st);
        ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
 
@@ -229,7 +230,8 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
 
        hashcpy(ce->sha1, sha1);
        memcpy(ce->name, path, len);
-       ce->ce_flags = create_ce_flags(len, stage);
+       ce->ce_flags = create_ce_flags(stage);
+       ce->ce_namelen = len;
        ce->ce_mode = create_ce_mode(mode);
        if (assume_unchanged)
                ce->ce_flags |= CE_VALID;
@@ -427,7 +429,8 @@ static struct cache_entry *read_one_ent(const char *which,
 
        hashcpy(ce->sha1, sha1);
        memcpy(ce->name, path, namelen);
-       ce->ce_flags = create_ce_flags(namelen, stage);
+       ce->ce_flags = create_ce_flags(stage);
+       ce->ce_namelen = namelen;
        ce->ce_mode = create_ce_mode(mode);
        return ce;
 }
diff --git a/cache.h b/cache.h
index 89581041ce82603ae82866f8f8a5c01b4f7f6d0d..67f28b4da97b5aded337e1594043a73c075ea02f 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -128,13 +128,13 @@ struct cache_entry {
        unsigned int ce_gid;
        unsigned int ce_size;
        unsigned int ce_flags;
+       unsigned int ce_namelen;
        unsigned char sha1[20];
        struct cache_entry *next;
        struct cache_entry *dir_next;
        char name[FLEX_ARRAY]; /* more */
 };
 
-#define CE_NAMEMASK  (0x0fff)
 #define CE_STAGEMASK (0x3000)
 #define CE_EXTENDED  (0x4000)
 #define CE_VALID     (0x8000)
@@ -198,21 +198,12 @@ static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry
        dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
 }
 
-static inline unsigned create_ce_flags(size_t len, unsigned stage)
+static inline unsigned create_ce_flags(unsigned stage)
 {
-       if (len >= CE_NAMEMASK)
-               len = CE_NAMEMASK;
-       return (len | (stage << CE_STAGESHIFT));
-}
-
-static inline size_t ce_namelen(const struct cache_entry *ce)
-{
-       size_t len = ce->ce_flags & CE_NAMEMASK;
-       if (len < CE_NAMEMASK)
-               return len;
-       return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK;
+       return (stage << CE_STAGESHIFT);
 }
 
+#define ce_namelen(ce) ((ce)->ce_namelen)
 #define ce_size(ce) 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)
@@ -451,6 +442,7 @@ extern int discard_index(struct index_state *);
 extern int unmerged_index(const struct index_state *);
 extern int verify_path(const char *path);
 extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase);
+extern int index_name_stage_pos(const struct index_state *, const char *name, int namelen, int stage);
 extern int index_name_pos(const struct index_state *, const char *name, int namelen);
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
@@ -563,6 +555,7 @@ extern int read_replace_refs;
 extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
+extern int precomposed_unicode;
 
 enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
@@ -622,6 +615,8 @@ extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
+extern char *mkpathdup(const char *fmt, ...)
+       __attribute__((format (printf, 1, 2)));
 
 /* Return a statically allocated filename matching the sha1 signature */
 extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
@@ -711,6 +706,7 @@ int set_shared_perm(const char *path, int mode);
 int safe_create_leading_directories(char *path);
 int safe_create_leading_directories_const(const char *path);
 int mkdir_in_gitdir(const char *path);
+extern void home_config_paths(char **global, char **xdg, char *file);
 extern char *expand_user_path(const char *path);
 const char *enter_repo(const char *path, int strict);
 static inline int is_absolute_path(const char *path)
@@ -786,17 +782,25 @@ struct object_context {
        unsigned mode;
 };
 
+#define GET_SHA1_QUIETLY        01
+#define GET_SHA1_COMMIT         02
+#define GET_SHA1_COMMITTISH     04
+#define GET_SHA1_TREE          010
+#define GET_SHA1_TREEISH       020
+#define GET_SHA1_BLOB         040
+#define GET_SHA1_ONLY_TO_DIE 04000
+
 extern int get_sha1(const char *str, unsigned char *sha1);
-extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int only_to_die, const char *prefix);
-static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode)
-{
-       return get_sha1_with_mode_1(str, sha1, mode, 0, NULL);
-}
-extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int only_to_die, const char *prefix);
-static inline int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc)
-{
-       return get_sha1_with_context_1(str, sha1, orc, 0, NULL);
-}
+extern int get_sha1_commit(const char *str, unsigned char *sha1);
+extern int get_sha1_committish(const char *str, unsigned char *sha1);
+extern int get_sha1_tree(const char *str, unsigned char *sha1);
+extern int get_sha1_treeish(const char *str, unsigned char *sha1);
+extern int get_sha1_blob(const char *str, unsigned char *sha1);
+extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
+extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc);
+
+typedef int each_abbrev_fn(const unsigned char *sha1, void *);
+extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
 
 /*
  * Try to read a SHA1 in hexadecimal format from the 40 characters
@@ -860,6 +864,7 @@ extern int validate_headref(const char *ref);
 extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
 extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
 extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
+extern int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2);
 
 extern void *read_object_with_reference(const unsigned char *sha1,
                                        const char *required_type,
index 8248a994a50ab91874600567e544aa3735e4aa98..42af4c1f238b96eeb9e88b227b6f6c10ef317058 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -68,7 +68,7 @@ struct commit *lookup_commit_reference_by_name(const char *name)
        unsigned char sha1[20];
        struct commit *commit;
 
-       if (get_sha1(name, sha1))
+       if (get_sha1_committish(name, sha1))
                return NULL;
        commit = lookup_commit_reference(sha1);
        if (!commit || parse_commit(commit))
diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c
new file mode 100644 (file)
index 0000000..d40d1b3
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Converts filenames from decomposed unicode into precomposed unicode.
+ * Used on MacOS X.
+*/
+
+
+#define PRECOMPOSE_UNICODE_C
+
+#include "cache.h"
+#include "utf8.h"
+#include "precompose_utf8.h"
+
+typedef char *iconv_ibp;
+const static char *repo_encoding = "UTF-8";
+const static char *path_encoding = "UTF-8-MAC";
+
+
+static size_t has_utf8(const char *s, size_t maxlen, size_t *strlen_c)
+{
+       const uint8_t *utf8p = (const uint8_t*) s;
+       size_t strlen_chars = 0;
+       size_t ret = 0;
+
+       if ((!utf8p) || (!*utf8p)) {
+               return 0;
+       }
+
+       while((*utf8p) && maxlen) {
+               if (*utf8p & 0x80)
+                       ret++;
+               strlen_chars++;
+               utf8p++;
+               maxlen--;
+       }
+       if (strlen_c)
+               *strlen_c = strlen_chars;
+
+       return ret;
+}
+
+
+void probe_utf8_pathname_composition(char *path, int len)
+{
+       const static char *auml_nfc = "\xc3\xa4";
+       const static char *auml_nfd = "\x61\xcc\x88";
+       int output_fd;
+       if (precomposed_unicode != -1)
+               return; /* We found it defined in the global config, respect it */
+       path[len] = 0;
+       strcpy(path + len, auml_nfc);
+       output_fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0600);
+       if (output_fd >=0) {
+               close(output_fd);
+               path[len] = 0;
+               strcpy(path + len, auml_nfd);
+               /* Indicate to the user, that we can configure it to true */
+               if (0 == access(path, R_OK))
+                       git_config_set("core.precomposeunicode", "false");
+                       /* To be backward compatible, set precomposed_unicode to 0 */
+               precomposed_unicode = 0;
+               path[len] = 0;
+               strcpy(path + len, auml_nfc);
+               unlink(path);
+       }
+}
+
+
+void precompose_argv(int argc, const char **argv)
+{
+       int i = 0;
+       const char *oldarg;
+       char *newarg;
+       iconv_t ic_precompose;
+
+       if (precomposed_unicode != 1)
+               return;
+
+       ic_precompose = iconv_open(repo_encoding, path_encoding);
+       if (ic_precompose == (iconv_t) -1)
+               return;
+
+       while (i < argc) {
+               size_t namelen;
+               oldarg = argv[i];
+               if (has_utf8(oldarg, (size_t)-1, &namelen)) {
+                       newarg = reencode_string_iconv(oldarg, namelen, ic_precompose);
+                       if (newarg)
+                               argv[i] = newarg;
+               }
+               i++;
+       }
+       iconv_close(ic_precompose);
+}
+
+
+PREC_DIR *precompose_utf8_opendir(const char *dirname)
+{
+       PREC_DIR *prec_dir = xmalloc(sizeof(PREC_DIR));
+       prec_dir->dirent_nfc = xmalloc(sizeof(dirent_prec_psx));
+       prec_dir->dirent_nfc->max_name_len = sizeof(prec_dir->dirent_nfc->d_name);
+
+       prec_dir->dirp = opendir(dirname);
+       if (!prec_dir->dirp) {
+               free(prec_dir->dirent_nfc);
+               free(prec_dir);
+               return NULL;
+       } else {
+               int ret_errno = errno;
+               prec_dir->ic_precompose = iconv_open(repo_encoding, path_encoding);
+               /* if iconv_open() fails, die() in readdir() if needed */
+               errno = ret_errno;
+       }
+
+       return prec_dir;
+}
+
+struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir)
+{
+       struct dirent *res;
+       res = readdir(prec_dir->dirp);
+       if (res) {
+               size_t namelenz = strlen(res->d_name) + 1; /* \0 */
+               size_t new_maxlen = namelenz;
+
+               int ret_errno = errno;
+
+               if (new_maxlen > prec_dir->dirent_nfc->max_name_len) {
+                       size_t new_len = sizeof(dirent_prec_psx) + new_maxlen -
+                               sizeof(prec_dir->dirent_nfc->d_name);
+
+                       prec_dir->dirent_nfc = xrealloc(prec_dir->dirent_nfc, new_len);
+                       prec_dir->dirent_nfc->max_name_len = new_maxlen;
+               }
+
+               prec_dir->dirent_nfc->d_ino  = res->d_ino;
+               prec_dir->dirent_nfc->d_type = res->d_type;
+
+               if ((precomposed_unicode == 1) && has_utf8(res->d_name, (size_t)-1, NULL)) {
+                       if (prec_dir->ic_precompose == (iconv_t)-1) {
+                               die("iconv_open(%s,%s) failed, but needed:\n"
+                                               "    precomposed unicode is not supported.\n"
+                                               "    If you wnat to use decomposed unicode, run\n"
+                                               "    \"git config core.precomposeunicode false\"\n",
+                                               repo_encoding, path_encoding);
+                       } else {
+                               iconv_ibp       cp = (iconv_ibp)res->d_name;
+                               size_t inleft = namelenz;
+                               char *outpos = &prec_dir->dirent_nfc->d_name[0];
+                               size_t outsz = prec_dir->dirent_nfc->max_name_len;
+                               size_t cnt;
+                               errno = 0;
+                               cnt = iconv(prec_dir->ic_precompose, &cp, &inleft, &outpos, &outsz);
+                               if (errno || inleft) {
+                                       /*
+                                        * iconv() failed and errno could be E2BIG, EILSEQ, EINVAL, EBADF
+                                        * MacOS X avoids illegal byte sequemces.
+                                        * If they occur on a mounted drive (e.g. NFS) it is not worth to
+                                        * die() for that, but rather let the user see the original name
+                                       */
+                                       namelenz = 0; /* trigger strlcpy */
+                               }
+                       }
+               }
+               else
+                       namelenz = 0;
+
+               if (!namelenz)
+                       strlcpy(prec_dir->dirent_nfc->d_name, res->d_name,
+                                                       prec_dir->dirent_nfc->max_name_len);
+
+               errno = ret_errno;
+               return prec_dir->dirent_nfc;
+       }
+       return NULL;
+}
+
+
+int precompose_utf8_closedir(PREC_DIR *prec_dir)
+{
+       int ret_value;
+       int ret_errno;
+       ret_value = closedir(prec_dir->dirp);
+       ret_errno = errno;
+       if (prec_dir->ic_precompose != (iconv_t)-1)
+               iconv_close(prec_dir->ic_precompose);
+       free(prec_dir->dirent_nfc);
+       free(prec_dir);
+       errno = ret_errno;
+       return ret_value;
+}
diff --git a/compat/precompose_utf8.h b/compat/precompose_utf8.h
new file mode 100644 (file)
index 0000000..3b73585
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef PRECOMPOSE_UNICODE_H
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <iconv.h>
+
+
+typedef struct dirent_prec_psx {
+       ino_t d_ino;            /* Posix */
+       size_t max_name_len;    /* See below */
+       unsigned char d_type;   /* available on all systems git runs on */
+
+       /*
+        * See http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/dirent.h.html
+        * NAME_MAX + 1 should be enough, but some systems have
+        * NAME_MAX=255 and strlen(d_name) may return 508 or 510
+        * Solution: allocate more when needed, see precompose_utf8_readdir()
+        */
+       char   d_name[NAME_MAX+1];
+} dirent_prec_psx;
+
+
+typedef struct {
+       iconv_t ic_precompose;
+       DIR *dirp;
+       struct dirent_prec_psx *dirent_nfc;
+} PREC_DIR;
+
+void precompose_argv(int argc, const char **argv);
+void probe_utf8_pathname_composition(char *, int);
+
+PREC_DIR *precompose_utf8_opendir(const char *dirname);
+struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *dirp);
+int precompose_utf8_closedir(PREC_DIR *dirp);
+
+#ifndef PRECOMPOSE_UNICODE_C
+#define dirent dirent_prec_psx
+#define opendir(n) precompose_utf8_opendir(n)
+#define readdir(d) precompose_utf8_readdir(d)
+#define closedir(d) precompose_utf8_closedir(d)
+#define DIR PREC_DIR
+#endif /* PRECOMPOSE_UNICODE_C */
+
+#define  PRECOMPOSE_UNICODE_H
+#endif /* PRECOMPOSE_UNICODE_H */
index 71ef171cab78bfdb9d6421f2a2b84acbce0bfc00..2b706ea2053714fdcec8997d978ce5688d072941 100644 (file)
--- a/config.c
+++ b/config.c
@@ -758,6 +758,11 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.precomposeunicode")) {
+               precomposed_unicode = git_config_bool(var, value);
+               return 0;
+       }
+
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
@@ -929,7 +934,10 @@ int git_config_system(void)
 int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 {
        int ret = 0, found = 0;
-       const char *home = NULL;
+       char *xdg_config = NULL;
+       char *user_config = NULL;
+
+       home_config_paths(&user_config, &xdg_config, "config");
 
        if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
@@ -937,14 +945,14 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
                found += 1;
        }
 
-       home = getenv("HOME");
-       if (home) {
-               char buf[PATH_MAX];
-               char *user_config = mksnpath(buf, sizeof(buf), "%s/.gitconfig", home);
-               if (!access(user_config, R_OK)) {
-                       ret += git_config_from_file(fn, user_config, data);
-                       found += 1;
-               }
+       if (xdg_config && !access(xdg_config, R_OK)) {
+               ret += git_config_from_file(fn, xdg_config, data);
+               found += 1;
+       }
+
+       if (user_config && !access(user_config, R_OK)) {
+               ret += git_config_from_file(fn, user_config, data);
+               found += 1;
        }
 
        if (repo_config && !access(repo_config, R_OK)) {
@@ -963,6 +971,8 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
                break;
        }
 
+       free(xdg_config);
+       free(user_config);
        return ret == 0 ? found : ret;
 }
 
index b2ba7104ebc87c0f2fe261f16461793fcc8b8cd3..802d34223a2859ee1341d94ee722d7939b7276aa 100644 (file)
@@ -28,7 +28,6 @@ VPATH = @srcdir@
 export exec_prefix mandir
 export srcdir VPATH
 
-ASCIIDOC7=@ASCIIDOC7@
 NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
 NO_OPENSSL=@NO_OPENSSL@
 NO_CURL=@NO_CURL@
index e1255506a636722031c58398cb33056c43cffed3..df7e376590b34b92bfd899d794bdd2627f3e31e7 100644 (file)
@@ -3,11 +3,24 @@
 
 ## Definitions of private macros.
 
-# GIT_CONF_APPEND_LINE(LINE)
-# --------------------------
-# Append LINE to file ${config_append}
-AC_DEFUN([GIT_CONF_APPEND_LINE],
-         [echo "$1" >> "${config_append}"])
+# GIT_CONF_SUBST(VAL, VAR)
+# ------------------------
+# Cause the line "VAR=VAL" to be eventually appended to ${config_file}.
+AC_DEFUN([GIT_CONF_SUBST],
+   [AC_REQUIRE([GIT_CONF_SUBST_INIT])
+   config_appended_defs="$config_appended_defs${newline}$1=$2"])
+
+# GIT_CONF_SUBST_INIT
+# -------------------
+# Prepare shell variables and autoconf machine required by later calls
+# to GIT_CONF_SUBST.
+AC_DEFUN([GIT_CONF_SUBST_INIT],
+    [config_appended_defs=; newline='
+'
+    AC_CONFIG_COMMANDS([$config_file],
+                       [echo "$config_appended_defs" >> "$config_file"],
+                       [config_file=$config_file
+                        config_appended_defs="$config_appended_defs"])])
 
 # GIT_ARG_SET_PATH(PROGRAM)
 # -------------------------
@@ -29,13 +42,12 @@ AC_DEFUN([GIT_ARG_SET_PATH],
 # --without-PROGRAM is used.
 AC_DEFUN([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=)
+               AC_MSG_NOTICE([Disabling use of GIT_UC_PROGRAM])
+               GIT_CONF_SUBST([NO_]GIT_UC_PROGRAM, [YesPlease])
+               GIT_CONF_SUBST(GIT_UC_PROGRAM[]_PATH, [])
        else
                AC_MSG_ERROR([You cannot use git without $1])
        fi
@@ -45,7 +57,7 @@ AC_DEFUN([GIT_CONF_APPEND_PATH],
        else
                GIT_UC_PROGRAM[]_PATH=$withval
                AC_MSG_NOTICE([Setting GIT_UC_PROGRAM[]_PATH to $withval])
-               GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval)
+               GIT_CONF_SUBST(GIT_UC_PROGRAM[]_PATH, [$withval])
        fi
     fi
     m4_popdef([GIT_UC_PROGRAM])])
@@ -58,7 +70,6 @@ AC_DEFUN([GIT_CONF_APPEND_PATH],
 # * Unset NO_PACKAGE for --with-PACKAGE without ARG
 AC_DEFUN([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
@@ -67,7 +78,7 @@ AC_DEFUN([GIT_PARSE_WITH],
        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)
+       GIT_CONF_SUBST(GIT_UC_PACKAGE[DIR], [$withval])
     fi
     m4_popdef([GIT_UC_PACKAGE])])
 
@@ -87,7 +98,7 @@ AC_DEFUN([GIT_PARSE_WITH_SET_MAKE_VAR],
                     [a value for $1 ($2).  Maybe you do...?])
   fi
   AC_MSG_NOTICE([Setting $2 to $withval])
-  GIT_CONF_APPEND_LINE($2=$withval)
+  GIT_CONF_SUBST([$2], [$withval])
  fi)])# GIT_PARSE_WITH_SET_MAKE_VAR
 
 #
@@ -135,10 +146,9 @@ 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}"
+GIT_CONF_SUBST([AUTOCONFIGURED], [YesPlease])
 
 # Directories holding "saner" versions of common or POSIX binaries.
 AC_ARG_WITH([sane-tool-path],
@@ -150,7 +160,7 @@ AC_ARG_WITH([sane-tool-path],
   else
     AC_MSG_NOTICE([Setting SANE_TOOL_PATH to '$withval'])
   fi
-  GIT_CONF_APPEND_LINE([SANE_TOOL_PATH=$withval])],
+  GIT_CONF_SUBST([SANE_TOOL_PATH], [$withval])],
   [# If the "--with-sane-tool-path" option was not given, don't touch
    # SANE_TOOL_PATH here, but let defaults in Makefile take care of it.
    # This should minimize spurious differences in the behaviour of the
@@ -169,7 +179,7 @@ AC_ARG_WITH([lib],
   else
        lib=$withval
        AC_MSG_NOTICE([Setting lib to '$lib'])
-       GIT_CONF_APPEND_LINE(lib=$withval)
+       GIT_CONF_SUBST([lib], [$withval])
   fi])
 
 if test -z "$lib"; then
@@ -205,7 +215,7 @@ AC_ARG_ENABLE([jsmin],
 [
   JSMIN=$enableval;
   AC_MSG_NOTICE([Setting JSMIN to '$JSMIN' to enable JavaScript minifying])
-  GIT_CONF_APPEND_LINE(JSMIN=$enableval);
+  GIT_CONF_SUBST([JSMIN], [$enableval])
 ])
 
 # Define option to enable CSS minification
@@ -215,7 +225,7 @@ AC_ARG_ENABLE([cssmin],
 [
   CSSMIN=$enableval;
   AC_MSG_NOTICE([Setting CSSMIN to '$CSSMIN' to enable CSS minifying])
-  GIT_CONF_APPEND_LINE(CSSMIN=$enableval);
+  GIT_CONF_SUBST([CSSMIN], [$enableval])
 ])
 
 ## Site configuration (override autodetection)
@@ -256,7 +266,7 @@ AS_HELP_STRING([],           [ARG can be also prefix for libpcre library and hea
        USE_LIBPCRE=YesPlease
        LIBPCREDIR=$withval
        AC_MSG_NOTICE([Setting LIBPCREDIR to $withval])
-       GIT_CONF_APPEND_LINE(LIBPCREDIR=$withval)
+       GIT_CONF_SUBST([LIBPCREDIR], [$withval])
     fi)
 #
 # Define NO_CURL if you do not have curl installed.  git-http-pull and
@@ -437,21 +447,14 @@ if test -n "$ASCIIDOC"; then
        AC_MSG_CHECKING([for asciidoc version])
        asciidoc_version=`$ASCIIDOC --version 2>/dev/null`
        case "${asciidoc_version}" in
-       asciidoc' '7*)
-               ASCIIDOC7=YesPlease
-               AC_MSG_RESULT([${asciidoc_version} > 7])
-               ;;
        asciidoc' '8*)
-               ASCIIDOC7=
                AC_MSG_RESULT([${asciidoc_version}])
                ;;
        *)
-               ASCIIDOC7=
                AC_MSG_RESULT([${asciidoc_version} (unknown)])
                ;;
        esac
 fi
-AC_SUBST(ASCIIDOC7)
 
 
 ## Checks for libraries.
@@ -1050,9 +1053,5 @@ AC_SUBST(PTHREAD_LIBS)
 AC_SUBST(NO_PTHREADS)
 
 ## Output files
-AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"])
+AC_CONFIG_FILES(["${config_file}":"${config_in}"])
 AC_OUTPUT
-
-
-## Cleanup
-rm -f "${config_append}"
index 912cddeea8c4f09ec523ce19e677358c84eda9b7..55a85adea94c87c912280079ee13afd6926901aa 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -49,6 +49,16 @@ static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1
        extra->nr++;
 }
 
+static void die_initial_contact(int got_at_least_one_head)
+{
+       if (got_at_least_one_head)
+               die("The remote end hung up upon initial contact");
+       else
+               die("Could not read from remote repository.\n\n"
+                   "Please make sure you have the correct access rights\n"
+                   "and the repository exists.");
+}
+
 /*
  * Read all the refs from the other end
  */
@@ -56,6 +66,8 @@ struct ref **get_remote_heads(int in, struct ref **list,
                              unsigned int flags,
                              struct extra_have_objects *extra_have)
 {
+       int got_at_least_one_head = 0;
+
        *list = NULL;
        for (;;) {
                struct ref *ref;
@@ -64,7 +76,10 @@ struct ref **get_remote_heads(int in, struct ref **list,
                char *name;
                int len, name_len;
 
-               len = packet_read_line(in, buffer, sizeof(buffer));
+               len = packet_read(in, buffer, sizeof(buffer));
+               if (len < 0)
+                       die_initial_contact(got_at_least_one_head);
+
                if (!len)
                        break;
                if (buffer[len-1] == '\n')
@@ -95,6 +110,7 @@ struct ref **get_remote_heads(int in, struct ref **list,
                hashcpy(ref->old_sha1, old_sha1);
                *list = ref;
                list = &ref->next;
+               got_at_least_one_head = 1;
        }
        return list;
 }
@@ -536,7 +552,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
         * Add support for ssh port: ssh://host.xy:<port>/...
         */
        if (protocol == PROTO_SSH && host != url)
-               port = get_port(host);
+               port = get_port(end);
 
        if (protocol == PROTO_GIT) {
                /* These underlying connection commands die() if they
old mode 100755 (executable)
new mode 100644 (file)
index 2e1b5e1..ffedce7
 #    1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
 #    2) Add the following line to your .bashrc/.zshrc:
 #        source ~/.git-completion.sh
-#
-#    3) Consider changing your PS1 to also show the current branch:
-#         Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
-#         ZSH:  PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
-#
-#       The argument to __git_ps1 will be displayed only if you
-#       are currently in a git repository.  The %s token will be
-#       the name of the current branch.
-#
-#       In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
-#       value, unstaged (*) and staged (+) changes will be shown next
-#       to the branch name.  You can configure this per-repository
-#       with the bash.showDirtyState variable, which defaults to true
-#       once GIT_PS1_SHOWDIRTYSTATE is enabled.
-#
-#       You can also see if currently something is stashed, by setting
-#       GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
-#       then a '$' will be shown next to the branch name.
-#
-#       If you would like to see if there're untracked files, then you can
-#       set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're
-#       untracked files, then a '%' will be shown next to the branch name.
-#
-#       If you would like to see the difference between HEAD and its
-#       upstream, set GIT_PS1_SHOWUPSTREAM="auto".  A "<" indicates
-#       you are behind, ">" indicates you are ahead, and "<>"
-#       indicates you have diverged.  You can further control
-#       behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
-#       list of values:
-#           verbose       show number of commits ahead/behind (+/-) upstream
-#           legacy        don't use the '--count' option available in recent
-#                         versions of git-rev-list
-#           git           always compare HEAD to @{upstream}
-#           svn           always compare HEAD to your SVN upstream
-#       By default, __git_ps1 will compare HEAD to your SVN upstream
-#       if it can find one, or @{upstream} otherwise.  Once you have
-#       set GIT_PS1_SHOWUPSTREAM, you can override it on a
-#       per-repository basis by setting the bash.showUpstream config
-#       variable.
-#
+#    3) Consider changing your PS1 to also show the current branch,
+#       see git-prompt.sh for details.
 
 if [[ -n ${ZSH_VERSION-} ]]; then
        autoload -U +X bashcompinit && bashcompinit
@@ -74,9 +36,14 @@ esac
 # returns location of .git repo
 __gitdir ()
 {
+       # Note: this function is duplicated in git-prompt.sh
+       # When updating it, make sure you update the other one to match.
        if [ -z "${1-}" ]; then
                if [ -n "${__git_dir-}" ]; then
                        echo "$__git_dir"
+               elif [ -n "${GIT_DIR-}" ]; then
+                       test -d "${GIT_DIR-}" || return 1
+                       echo "$GIT_DIR"
                elif [ -d .git ]; then
                        echo .git
                else
@@ -89,221 +56,6 @@ __gitdir ()
        fi
 }
 
-# stores the divergence from upstream in $p
-# used by GIT_PS1_SHOWUPSTREAM
-__git_ps1_show_upstream ()
-{
-       local key value
-       local svn_remote svn_url_pattern count n
-       local upstream=git legacy="" verbose=""
-
-       svn_remote=()
-       # get some config options from git-config
-       local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
-       while read -r key value; do
-               case "$key" in
-               bash.showupstream)
-                       GIT_PS1_SHOWUPSTREAM="$value"
-                       if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
-                               p=""
-                               return
-                       fi
-                       ;;
-               svn-remote.*.url)
-                       svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
-                       svn_url_pattern+="\\|$value"
-                       upstream=svn+git # default upstream is SVN if available, else git
-                       ;;
-               esac
-       done <<< "$output"
-
-       # parse configuration values
-       for option in ${GIT_PS1_SHOWUPSTREAM}; do
-               case "$option" in
-               git|svn) upstream="$option" ;;
-               verbose) verbose=1 ;;
-               legacy)  legacy=1  ;;
-               esac
-       done
-
-       # Find our upstream
-       case "$upstream" in
-       git)    upstream="@{upstream}" ;;
-       svn*)
-               # get the upstream from the "git-svn-id: ..." in a commit message
-               # (git-svn uses essentially the same procedure internally)
-               local svn_upstream=($(git log --first-parent -1 \
-                                       --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
-               if [[ 0 -ne ${#svn_upstream[@]} ]]; then
-                       svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
-                       svn_upstream=${svn_upstream%@*}
-                       local n_stop="${#svn_remote[@]}"
-                       for ((n=1; n <= n_stop; n++)); do
-                               svn_upstream=${svn_upstream#${svn_remote[$n]}}
-                       done
-
-                       if [[ -z "$svn_upstream" ]]; then
-                               # default branch name for checkouts with no layout:
-                               upstream=${GIT_SVN_ID:-git-svn}
-                       else
-                               upstream=${svn_upstream#/}
-                       fi
-               elif [[ "svn+git" = "$upstream" ]]; then
-                       upstream="@{upstream}"
-               fi
-               ;;
-       esac
-
-       # Find how many commits we are ahead/behind our upstream
-       if [[ -z "$legacy" ]]; then
-               count="$(git rev-list --count --left-right \
-                               "$upstream"...HEAD 2>/dev/null)"
-       else
-               # produce equivalent output to --count for older versions of git
-               local commits
-               if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
-               then
-                       local commit behind=0 ahead=0
-                       for commit in $commits
-                       do
-                               case "$commit" in
-                               "<"*) ((behind++)) ;;
-                               *)    ((ahead++))  ;;
-                               esac
-                       done
-                       count="$behind  $ahead"
-               else
-                       count=""
-               fi
-       fi
-
-       # calculate the result
-       if [[ -z "$verbose" ]]; then
-               case "$count" in
-               "") # no upstream
-                       p="" ;;
-               "0      0") # equal to upstream
-                       p="=" ;;
-               "0      "*) # ahead of upstream
-                       p=">" ;;
-               *"      0") # behind upstream
-                       p="<" ;;
-               *)          # diverged from upstream
-                       p="<>" ;;
-               esac
-       else
-               case "$count" in
-               "") # no upstream
-                       p="" ;;
-               "0      0") # equal to upstream
-                       p=" u=" ;;
-               "0      "*) # ahead of upstream
-                       p=" u+${count#0 }" ;;
-               *"      0") # behind upstream
-                       p=" u-${count%  0}" ;;
-               *)          # diverged from upstream
-                       p=" u+${count#* }-${count%      *}" ;;
-               esac
-       fi
-
-}
-
-
-# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
-# returns text to add to bash PS1 prompt (includes branch name)
-__git_ps1 ()
-{
-       local g="$(__gitdir)"
-       if [ -n "$g" ]; then
-               local r=""
-               local b=""
-               if [ -f "$g/rebase-merge/interactive" ]; then
-                       r="|REBASE-i"
-                       b="$(cat "$g/rebase-merge/head-name")"
-               elif [ -d "$g/rebase-merge" ]; then
-                       r="|REBASE-m"
-                       b="$(cat "$g/rebase-merge/head-name")"
-               else
-                       if [ -d "$g/rebase-apply" ]; then
-                               if [ -f "$g/rebase-apply/rebasing" ]; then
-                                       r="|REBASE"
-                               elif [ -f "$g/rebase-apply/applying" ]; then
-                                       r="|AM"
-                               else
-                                       r="|AM/REBASE"
-                               fi
-                       elif [ -f "$g/MERGE_HEAD" ]; then
-                               r="|MERGING"
-                       elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
-                               r="|CHERRY-PICKING"
-                       elif [ -f "$g/BISECT_LOG" ]; then
-                               r="|BISECTING"
-                       fi
-
-                       b="$(git symbolic-ref HEAD 2>/dev/null)" || {
-
-                               b="$(
-                               case "${GIT_PS1_DESCRIBE_STYLE-}" in
-                               (contains)
-                                       git describe --contains HEAD ;;
-                               (branch)
-                                       git describe --contains --all HEAD ;;
-                               (describe)
-                                       git describe HEAD ;;
-                               (* | default)
-                                       git describe --tags --exact-match HEAD ;;
-                               esac 2>/dev/null)" ||
-
-                               b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
-                               b="unknown"
-                               b="($b)"
-                       }
-               fi
-
-               local w=""
-               local i=""
-               local s=""
-               local u=""
-               local c=""
-               local p=""
-
-               if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
-                       if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
-                               c="BARE:"
-                       else
-                               b="GIT_DIR!"
-                       fi
-               elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
-                       if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
-                               if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
-                                       git diff --no-ext-diff --quiet --exit-code || w="*"
-                                       if git rev-parse --quiet --verify HEAD >/dev/null; then
-                                               git diff-index --cached --quiet HEAD -- || i="+"
-                                       else
-                                               i="#"
-                                       fi
-                               fi
-                       fi
-                       if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
-                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
-                       fi
-
-                       if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
-                               if [ -n "$(git ls-files --others --exclude-standard)" ]; then
-                                       u="%"
-                               fi
-                       fi
-
-                       if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
-                               __git_ps1_show_upstream
-                       fi
-               fi
-
-               local f="$w$i$s$u"
-               printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
-       fi
-}
-
 __gitcomp_1 ()
 {
        local c IFS=$' \t\n'
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
new file mode 100644 (file)
index 0000000..29b1ec9
--- /dev/null
@@ -0,0 +1,289 @@
+# bash/zsh git prompt support
+#
+# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
+# Distributed under the GNU General Public License, version 2.0.
+#
+# This script allows you to see the current branch in your prompt.
+#
+# To enable:
+#
+#    1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
+#    2) Add the following line to your .bashrc/.zshrc:
+#        source ~/.git-prompt.sh
+#    3) Change your PS1 to also show the current branch:
+#         Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+#         ZSH:  PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
+#
+# The argument to __git_ps1 will be displayed only if you are currently
+# in a git repository.  The %s token will be the name of the current
+# branch.
+#
+# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
+# unstaged (*) and staged (+) changes will be shown next to the branch
+# name.  You can configure this per-repository with the
+# bash.showDirtyState variable, which defaults to true once
+# GIT_PS1_SHOWDIRTYSTATE is enabled.
+#
+# You can also see if currently something is stashed, by setting
+# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
+# then a '$' will be shown next to the branch name.
+#
+# If you would like to see if there're untracked files, then you can set
+# GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
+# files, then a '%' will be shown next to the branch name.
+#
+# If you would like to see the difference between HEAD and its upstream,
+# set GIT_PS1_SHOWUPSTREAM="auto".  A "<" indicates you are behind, ">"
+# indicates you are ahead, and "<>" indicates you have diverged.  You
+# can further control behaviour by setting GIT_PS1_SHOWUPSTREAM to a
+# space-separated list of values:
+#
+#     verbose       show number of commits ahead/behind (+/-) upstream
+#     legacy        don't use the '--count' option available in recent
+#                   versions of git-rev-list
+#     git           always compare HEAD to @{upstream}
+#     svn           always compare HEAD to your SVN upstream
+#
+# By default, __git_ps1 will compare HEAD to your SVN upstream if it can
+# find one, or @{upstream} otherwise.  Once you have set
+# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
+# setting the bash.showUpstream config variable.
+
+# __gitdir accepts 0 or 1 arguments (i.e., location)
+# returns location of .git repo
+__gitdir ()
+{
+       # Note: this function is duplicated in git-completion.bash
+       # When updating it, make sure you update the other one to match.
+       if [ -z "${1-}" ]; then
+               if [ -n "${__git_dir-}" ]; then
+                       echo "$__git_dir"
+               elif [ -n "${GIT_DIR-}" ]; then
+                       test -d "${GIT_DIR-}" || return 1
+                       echo "$GIT_DIR"
+               elif [ -d .git ]; then
+                       echo .git
+               else
+                       git rev-parse --git-dir 2>/dev/null
+               fi
+       elif [ -d "$1/.git" ]; then
+               echo "$1/.git"
+       else
+               echo "$1"
+       fi
+}
+
+# stores the divergence from upstream in $p
+# used by GIT_PS1_SHOWUPSTREAM
+__git_ps1_show_upstream ()
+{
+       local key value
+       local svn_remote svn_url_pattern count n
+       local upstream=git legacy="" verbose=""
+
+       svn_remote=()
+       # get some config options from git-config
+       local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
+       while read -r key value; do
+               case "$key" in
+               bash.showupstream)
+                       GIT_PS1_SHOWUPSTREAM="$value"
+                       if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+                               p=""
+                               return
+                       fi
+                       ;;
+               svn-remote.*.url)
+                       svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+                       svn_url_pattern+="\\|$value"
+                       upstream=svn+git # default upstream is SVN if available, else git
+                       ;;
+               esac
+       done <<< "$output"
+
+       # parse configuration values
+       for option in ${GIT_PS1_SHOWUPSTREAM}; do
+               case "$option" in
+               git|svn) upstream="$option" ;;
+               verbose) verbose=1 ;;
+               legacy)  legacy=1  ;;
+               esac
+       done
+
+       # Find our upstream
+       case "$upstream" in
+       git)    upstream="@{upstream}" ;;
+       svn*)
+               # get the upstream from the "git-svn-id: ..." in a commit message
+               # (git-svn uses essentially the same procedure internally)
+               local svn_upstream=($(git log --first-parent -1 \
+                                       --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
+               if [[ 0 -ne ${#svn_upstream[@]} ]]; then
+                       svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
+                       svn_upstream=${svn_upstream%@*}
+                       local n_stop="${#svn_remote[@]}"
+                       for ((n=1; n <= n_stop; n++)); do
+                               svn_upstream=${svn_upstream#${svn_remote[$n]}}
+                       done
+
+                       if [[ -z "$svn_upstream" ]]; then
+                               # default branch name for checkouts with no layout:
+                               upstream=${GIT_SVN_ID:-git-svn}
+                       else
+                               upstream=${svn_upstream#/}
+                       fi
+               elif [[ "svn+git" = "$upstream" ]]; then
+                       upstream="@{upstream}"
+               fi
+               ;;
+       esac
+
+       # Find how many commits we are ahead/behind our upstream
+       if [[ -z "$legacy" ]]; then
+               count="$(git rev-list --count --left-right \
+                               "$upstream"...HEAD 2>/dev/null)"
+       else
+               # produce equivalent output to --count for older versions of git
+               local commits
+               if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
+               then
+                       local commit behind=0 ahead=0
+                       for commit in $commits
+                       do
+                               case "$commit" in
+                               "<"*) ((behind++)) ;;
+                               *)    ((ahead++))  ;;
+                               esac
+                       done
+                       count="$behind  $ahead"
+               else
+                       count=""
+               fi
+       fi
+
+       # calculate the result
+       if [[ -z "$verbose" ]]; then
+               case "$count" in
+               "") # no upstream
+                       p="" ;;
+               "0      0") # equal to upstream
+                       p="=" ;;
+               "0      "*) # ahead of upstream
+                       p=">" ;;
+               *"      0") # behind upstream
+                       p="<" ;;
+               *)          # diverged from upstream
+                       p="<>" ;;
+               esac
+       else
+               case "$count" in
+               "") # no upstream
+                       p="" ;;
+               "0      0") # equal to upstream
+                       p=" u=" ;;
+               "0      "*) # ahead of upstream
+                       p=" u+${count#0 }" ;;
+               *"      0") # behind upstream
+                       p=" u-${count%  0}" ;;
+               *)          # diverged from upstream
+                       p=" u+${count#* }-${count%      *}" ;;
+               esac
+       fi
+
+}
+
+
+# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
+# returns text to add to bash PS1 prompt (includes branch name)
+__git_ps1 ()
+{
+       local g="$(__gitdir)"
+       if [ -n "$g" ]; then
+               local r=""
+               local b=""
+               if [ -f "$g/rebase-merge/interactive" ]; then
+                       r="|REBASE-i"
+                       b="$(cat "$g/rebase-merge/head-name")"
+               elif [ -d "$g/rebase-merge" ]; then
+                       r="|REBASE-m"
+                       b="$(cat "$g/rebase-merge/head-name")"
+               else
+                       if [ -d "$g/rebase-apply" ]; then
+                               if [ -f "$g/rebase-apply/rebasing" ]; then
+                                       r="|REBASE"
+                               elif [ -f "$g/rebase-apply/applying" ]; then
+                                       r="|AM"
+                               else
+                                       r="|AM/REBASE"
+                               fi
+                       elif [ -f "$g/MERGE_HEAD" ]; then
+                               r="|MERGING"
+                       elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
+                               r="|CHERRY-PICKING"
+                       elif [ -f "$g/BISECT_LOG" ]; then
+                               r="|BISECTING"
+                       fi
+
+                       b="$(git symbolic-ref HEAD 2>/dev/null)" || {
+
+                               b="$(
+                               case "${GIT_PS1_DESCRIBE_STYLE-}" in
+                               (contains)
+                                       git describe --contains HEAD ;;
+                               (branch)
+                                       git describe --contains --all HEAD ;;
+                               (describe)
+                                       git describe HEAD ;;
+                               (* | default)
+                                       git describe --tags --exact-match HEAD ;;
+                               esac 2>/dev/null)" ||
+
+                               b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
+                               b="unknown"
+                               b="($b)"
+                       }
+               fi
+
+               local w=""
+               local i=""
+               local s=""
+               local u=""
+               local c=""
+               local p=""
+
+               if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
+                       if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
+                               c="BARE:"
+                       else
+                               b="GIT_DIR!"
+                       fi
+               elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
+                       if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
+                               if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
+                                       git diff --no-ext-diff --quiet --exit-code || w="*"
+                                       if git rev-parse --quiet --verify HEAD >/dev/null; then
+                                               git diff-index --cached --quiet HEAD -- || i="+"
+                                       else
+                                               i="#"
+                                       fi
+                               fi
+                       fi
+                       if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
+                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+                       fi
+
+                       if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
+                               if [ -n "$(git ls-files --others --exclude-standard)" ]; then
+                                       u="%"
+                               fi
+                       fi
+
+                       if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+                               __git_ps1_show_upstream
+                       fi
+               fi
+
+               local f="$w$i$s$u"
+               printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
+       fi
+}
index d351cfb6e7e818f5f760e83889586c6dbf6e3a11..e671f6c1c62956e34c935b24da6dfc617230ce61 100644 (file)
@@ -304,7 +304,7 @@ See also function `git-blame-mode'."
 
 (defun git-blame-cleanup ()
   "Remove all blame properties"
-    (mapcar 'delete-overlay git-blame-overlays)
+    (mapc 'delete-overlay git-blame-overlays)
     (setq git-blame-overlays nil)
     (remove-git-blame-text-properties (point-min) (point-max)))
 
@@ -337,16 +337,16 @@ See also function `git-blame-mode'."
 (defvar in-blame-filter nil)
 
 (defun git-blame-filter (proc str)
-  (save-excursion
-    (set-buffer (process-buffer proc))
-    (goto-char (process-mark proc))
-    (insert-before-markers str)
-    (goto-char 0)
-    (unless in-blame-filter
-      (let ((more t)
-            (in-blame-filter t))
-        (while more
-          (setq more (git-blame-parse)))))))
+  (with-current-buffer (process-buffer proc)
+    (save-excursion
+      (goto-char (process-mark proc))
+      (insert-before-markers str)
+      (goto-char (point-min))
+      (unless in-blame-filter
+        (let ((more t)
+              (in-blame-filter t))
+          (while more
+            (setq more (git-blame-parse))))))))
 
 (defun git-blame-parse ()
   (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n")
@@ -385,32 +385,33 @@ See also function `git-blame-mode'."
           info))))
 
 (defun git-blame-create-overlay (info start-line num-lines)
-  (save-excursion
-    (set-buffer git-blame-file)
-    (let ((inhibit-point-motion-hooks t)
-          (inhibit-modification-hooks t))
-      (goto-line start-line)
-      (let* ((start (point))
-             (end (progn (forward-line num-lines) (point)))
-             (ovl (make-overlay start end))
-             (hash (car info))
-             (spec `((?h . ,(substring hash 0 6))
-                     (?H . ,hash)
-                     (?a . ,(git-blame-get-info info 'author))
-                     (?A . ,(git-blame-get-info info 'author-mail))
-                     (?c . ,(git-blame-get-info info 'committer))
-                     (?C . ,(git-blame-get-info info 'committer-mail))
-                     (?s . ,(git-blame-get-info info 'summary)))))
-        (push ovl git-blame-overlays)
-        (overlay-put ovl 'git-blame info)
-        (overlay-put ovl 'help-echo
-                     (format-spec git-blame-mouseover-format spec))
-        (if git-blame-use-colors
-            (overlay-put ovl 'face (list :background
-                                         (cdr (assq 'color (cdr info))))))
-        (overlay-put ovl 'line-prefix
-                     (propertize (format-spec git-blame-prefix-format spec)
-                                 'face 'git-blame-prefix-face))))))
+  (with-current-buffer git-blame-file
+    (save-excursion
+      (let ((inhibit-point-motion-hooks t)
+            (inhibit-modification-hooks t))
+        (goto-char (point-min))
+        (forward-line (1- start-line))
+        (let* ((start (point))
+               (end (progn (forward-line num-lines) (point)))
+               (ovl (make-overlay start end))
+               (hash (car info))
+               (spec `((?h . ,(substring hash 0 6))
+                       (?H . ,hash)
+                       (?a . ,(git-blame-get-info info 'author))
+                       (?A . ,(git-blame-get-info info 'author-mail))
+                       (?c . ,(git-blame-get-info info 'committer))
+                       (?C . ,(git-blame-get-info info 'committer-mail))
+                       (?s . ,(git-blame-get-info info 'summary)))))
+          (push ovl git-blame-overlays)
+          (overlay-put ovl 'git-blame info)
+          (overlay-put ovl 'help-echo
+                       (format-spec git-blame-mouseover-format spec))
+          (if git-blame-use-colors
+              (overlay-put ovl 'face (list :background
+                                           (cdr (assq 'color (cdr info))))))
+          (overlay-put ovl 'line-prefix
+                       (propertize (format-spec git-blame-prefix-format spec)
+                                   'face 'git-blame-prefix-face)))))))
 
 (defun git-blame-add-info (info key value)
   (nconc info (list (cons (intern key) value))))
diff --git a/contrib/mw-to-git/Makefile b/contrib/mw-to-git/Makefile
new file mode 100644 (file)
index 0000000..3ed728b
--- /dev/null
@@ -0,0 +1,47 @@
+#
+# Copyright (C) 2012
+#     Charles Roussel <charles.roussel@ensimag.imag.fr>
+#     Simon Cathebras <simon.cathebras@ensimag.imag.fr>
+#     Julien Khayat <julien.khayat@ensimag.imag.fr>
+#     Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
+#     Simon Perrat <simon.perrat@ensimag.imag.fr>
+#
+## Build git-remote-mediawiki
+
+-include ../../config.mak.autogen
+-include ../../config.mak
+
+ifndef PERL_PATH
+       PERL_PATH = /usr/bin/perl
+endif
+ifndef gitexecdir
+       gitexecdir = $(shell git --exec-path)
+endif
+
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
+SCRIPT = git-remote-mediawiki
+
+.PHONY: install help doc test clean
+
+help:
+       @echo 'This is the help target of the Makefile. Current configuration:'
+       @echo '  gitexecdir = $(gitexecdir_SQ)'
+       @echo '  PERL_PATH = $(PERL_PATH_SQ)'
+       @echo 'Run "$(MAKE) install" to install $(SCRIPT) in gitexecdir'
+       @echo 'Run "$(MAKE) test" to run the testsuite'
+
+install:
+       sed -e '1s|#!.*/perl|#!$(PERL_PATH_SQ)|' $(SCRIPT) \
+               > '$(gitexecdir_SQ)/$(SCRIPT)'
+       chmod +x '$(gitexecdir)/$(SCRIPT)'
+
+doc:
+       @echo 'Sorry, "make doc" is not implemented yet for $(SCRIPT)'
+
+test:
+       $(MAKE) -C t/ test
+
+clean:
+       $(RM) '$(gitexecdir)/$(SCRIPT)'
+       $(MAKE) -C t/ clean
index c18bfa1f1515a8edb27c2d468a2982860a561939..8647c92df82657d5d3e2c6a43fb315f270548226 100755 (executable)
 #
 # Known limitations:
 #
-# - Only wiki pages are managed, no support for [[File:...]]
-#   attachments.
-#
-# - Poor performance in the best case: it takes forever to check
-#   whether we're up-to-date (on fetch or push) or to fetch a few
-#   revisions from a large wiki, because we use exclusively a
-#   page-based synchronization. We could switch to a wiki-wide
-#   synchronization when the synchronization involves few revisions
-#   but the wiki is large.
+# - Several strategies are provided to fetch modifications from the
+#   wiki, but no automatic heuristics is provided, the user has
+#   to understand and chose which strategy is appropriate for him.
 #
 # - Git renames could be turned into MediaWiki renames (see TODO
 #   below)
 #
-# - login/password support requires the user to write the password
-#   cleartext in a file (see TODO below).
-#
 # - No way to import "one page, and all pages included in it"
 #
 # - Multiple remote MediaWikis have not been very well tested.
 use strict;
 use MediaWiki::API;
 use DateTime::Format::ISO8601;
-use encoding 'utf8';
 
-# use encoding 'utf8' doesn't change STDERROR
-# but we're going to output UTF-8 filenames to STDERR
+# By default, use UTF-8 to communicate with Git and the user
 binmode STDERR, ":utf8";
+binmode STDOUT, ":utf8";
 
 use URI::Escape;
+use IPC::Open2;
+
 use warnings;
 
 # Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
@@ -59,6 +51,9 @@ use constant EMPTY_CONTENT => "<!-- empty page -->\n";
 # used to reflect file creation or deletion in diff.
 use constant NULL_SHA1 => "0000000000000000000000000000000000000000";
 
+# Used on Git's side to reflect empty edit messages on the wiki
+use constant EMPTY_MESSAGE => '*Empty MediaWiki Message*';
+
 my $remotename = $ARGV[0];
 my $url = $ARGV[1];
 
@@ -71,10 +66,18 @@ chomp(@tracked_pages);
 my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories"));
 chomp(@tracked_categories);
 
+# Import media files on pull
+my $import_media = run_git("config --get --bool remote.". $remotename .".mediaimport");
+chomp($import_media);
+$import_media = ($import_media eq "true");
+
+# Export media files on push
+my $export_media = run_git("config --get --bool remote.". $remotename .".mediaexport");
+chomp($export_media);
+$export_media = !($export_media eq "false");
+
 my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin");
-# TODO: ideally, this should be able to read from keyboard, but we're
-# inside a remote helper, so our stdin is connect to git, not to a
-# terminal.
+# Note: mwPassword is discourraged. Use the credential system instead.
 my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword");
 my $wiki_domain = run_git("config --get remote.". $remotename .".mwDomain");
 chomp($wiki_login);
@@ -86,6 +89,21 @@ my $shallow_import = run_git("config --get --bool remote.". $remotename .".shall
 chomp($shallow_import);
 $shallow_import = ($shallow_import eq "true");
 
+# Fetch (clone and pull) by revisions instead of by pages. This behavior
+# is more efficient when we have a wiki with lots of pages and we fetch
+# the revisions quite often so that they concern only few pages.
+# Possible values:
+# - by_rev: perform one query per new revision on the remote wiki
+# - by_page: query each tracked page for new revision
+my $fetch_strategy = run_git("config --get remote.$remotename.fetchStrategy");
+unless ($fetch_strategy) {
+       $fetch_strategy = run_git("config --get mediawiki.fetchStrategy");
+}
+chomp($fetch_strategy);
+unless ($fetch_strategy) {
+       $fetch_strategy = "by_page";
+}
+
 # Dumb push: don't update notes and mediawiki ref to reflect the last push.
 #
 # Configurable with mediawiki.dumbPush, or per-remote with
@@ -151,32 +169,152 @@ while (<STDIN>) {
 
 ########################## Functions ##############################
 
+## credential API management (generic functions)
+
+sub credential_read {
+       my %credential;
+       my $reader = shift;
+       my $op = shift;
+       while (<$reader>) {
+               my ($key, $value) = /([^=]*)=(.*)/;
+               if (not defined $key) {
+                       die "ERROR receiving response from git credential $op:\n$_\n";
+               }
+               $credential{$key} = $value;
+       }
+       return %credential;
+}
+
+sub credential_write {
+       my $credential = shift;
+       my $writer = shift;
+       # url overwrites other fields, so it must come first
+       print $writer "url=$credential->{url}\n" if exists $credential->{url};
+       while (my ($key, $value) = each(%$credential) ) {
+               if (length $value && $key ne 'url') {
+                       print $writer "$key=$value\n";
+               }
+       }
+}
+
+sub credential_run {
+       my $op = shift;
+       my $credential = shift;
+       my $pid = open2(my $reader, my $writer, "git credential $op");
+       credential_write($credential, $writer);
+       print $writer "\n";
+       close($writer);
+
+       if ($op eq "fill") {
+               %$credential = credential_read($reader, $op);
+       } else {
+               if (<$reader>) {
+                       die "ERROR while running git credential $op:\n$_";
+               }
+       }
+       close($reader);
+       waitpid($pid, 0);
+       my $child_exit_status = $? >> 8;
+       if ($child_exit_status != 0) {
+               die "'git credential $op' failed with code $child_exit_status.";
+       }
+}
+
 # MediaWiki API instance, created lazily.
 my $mediawiki;
 
 sub mw_connect_maybe {
        if ($mediawiki) {
-           return;
+               return;
        }
        $mediawiki = MediaWiki::API->new;
        $mediawiki->{config}->{api_url} = "$url/api.php";
        if ($wiki_login) {
-               if (!$mediawiki->login({
-                       lgname => $wiki_login,
-                       lgpassword => $wiki_passwd,
-                       lgdomain => $wiki_domain,
-               })) {
-                       print STDERR "Failed to log in mediawiki user \"$wiki_login\" on $url\n";
-                       print STDERR "(error " .
-                           $mediawiki->{error}->{code} . ': ' .
-                           $mediawiki->{error}->{details} . ")\n";
-                       exit 1;
+               my %credential = (url => $url);
+               $credential{username} = $wiki_login;
+               $credential{password} = $wiki_passwd;
+               credential_run("fill", \%credential);
+               my $request = {lgname => $credential{username},
+                              lgpassword => $credential{password},
+                              lgdomain => $wiki_domain};
+               if ($mediawiki->login($request)) {
+                       credential_run("approve", \%credential);
+                       print STDERR "Logged in mediawiki user \"$credential{username}\".\n";
                } else {
-                       print STDERR "Logged in with user \"$wiki_login\".\n";
+                       print STDERR "Failed to log in mediawiki user \"$credential{username}\" on $url\n";
+                       print STDERR "  (error " .
+                               $mediawiki->{error}->{code} . ': ' .
+                               $mediawiki->{error}->{details} . ")\n";
+                       credential_run("reject", \%credential);
+                       exit 1;
+               }
+       }
+}
+
+## Functions for listing pages on the remote wiki
+sub get_mw_tracked_pages {
+       my $pages = shift;
+       get_mw_page_list(\@tracked_pages, $pages);
+}
+
+sub get_mw_page_list {
+       my $page_list = shift;
+       my $pages = shift;
+       my @some_pages = @$page_list;
+       while (@some_pages) {
+               my $last = 50;
+               if ($#some_pages < $last) {
+                       $last = $#some_pages;
                }
+               my @slice = @some_pages[0..$last];
+               get_mw_first_pages(\@slice, $pages);
+               @some_pages = @some_pages[51..$#some_pages];
        }
 }
 
+sub get_mw_tracked_categories {
+       my $pages = shift;
+       foreach my $category (@tracked_categories) {
+               if (index($category, ':') < 0) {
+                       # Mediawiki requires the Category
+                       # prefix, but let's not force the user
+                       # to specify it.
+                       $category = "Category:" . $category;
+               }
+               my $mw_pages = $mediawiki->list( {
+                       action => 'query',
+                       list => 'categorymembers',
+                       cmtitle => $category,
+                       cmlimit => 'max' } )
+                       || die $mediawiki->{error}->{code} . ': '
+                               . $mediawiki->{error}->{details};
+               foreach my $page (@{$mw_pages}) {
+                       $pages->{$page->{title}} = $page;
+               }
+       }
+}
+
+sub get_mw_all_pages {
+       my $pages = shift;
+       # No user-provided list, get the list of pages from the API.
+       my $mw_pages = $mediawiki->list({
+               action => 'query',
+               list => 'allpages',
+               aplimit => 'max'
+       });
+       if (!defined($mw_pages)) {
+               print STDERR "fatal: could not get the list of wiki pages.\n";
+               print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+               print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+               exit 1;
+       }
+       foreach my $page (@{$mw_pages}) {
+               $pages->{$page->{title}} = $page;
+       }
+}
+
+# queries the wiki for a set of pages. Meant to be used within a loop
+# querying the wiki for slices of page list.
 sub get_mw_first_pages {
        my $some_pages = shift;
        my @some_pages = @{$some_pages};
@@ -205,70 +343,45 @@ sub get_mw_first_pages {
        }
 }
 
+# Get the list of pages to be fetched according to configuration.
 sub get_mw_pages {
        mw_connect_maybe();
 
+       print STDERR "Listing pages on remote wiki...\n";
+
        my %pages; # hash on page titles to avoid duplicates
        my $user_defined;
        if (@tracked_pages) {
                $user_defined = 1;
                # The user provided a list of pages titles, but we
                # still need to query the API to get the page IDs.
-
-               my @some_pages = @tracked_pages;
-               while (@some_pages) {
-                       my $last = 50;
-                       if ($#some_pages < $last) {
-                               $last = $#some_pages;
-                       }
-                       my @slice = @some_pages[0..$last];
-                       get_mw_first_pages(\@slice, \%pages);
-                       @some_pages = @some_pages[51..$#some_pages];
-               }
+               get_mw_tracked_pages(\%pages);
        }
        if (@tracked_categories) {
                $user_defined = 1;
-               foreach my $category (@tracked_categories) {
-                       if (index($category, ':') < 0) {
-                               # Mediawiki requires the Category
-                               # prefix, but let's not force the user
-                               # to specify it.
-                               $category = "Category:" . $category;
-                       }
-                       my $mw_pages = $mediawiki->list( {
-                               action => 'query',
-                               list => 'categorymembers',
-                               cmtitle => $category,
-                               cmlimit => 'max' } )
-                           || die $mediawiki->{error}->{code} . ': ' . $mediawiki->{error}->{details};
-                       foreach my $page (@{$mw_pages}) {
-                               $pages{$page->{title}} = $page;
-                       }
-               }
+               get_mw_tracked_categories(\%pages);
        }
        if (!$user_defined) {
-               # No user-provided list, get the list of pages from
-               # the API.
-               my $mw_pages = $mediawiki->list({
-                       action => 'query',
-                       list => 'allpages',
-                       aplimit => 500,
-               });
-               if (!defined($mw_pages)) {
-                       print STDERR "fatal: could not get the list of wiki pages.\n";
-                       print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
-                       print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
-                       exit 1;
-               }
-               foreach my $page (@{$mw_pages}) {
-                       $pages{$page->{title}} = $page;
+               get_mw_all_pages(\%pages);
+       }
+       if ($import_media) {
+               print STDERR "Getting media files for selected pages...\n";
+               if ($user_defined) {
+                       get_linked_mediafiles(\%pages);
+               } else {
+                       get_all_mediafiles(\%pages);
                }
        }
-       return values(%pages);
+       print STDERR (scalar keys %pages) . " pages found.\n";
+       return %pages;
 }
 
+# usage: $out = run_git("command args");
+#        $out = run_git("command args", "raw"); # don't interpret output as UTF-8.
 sub run_git {
-       open(my $git, "-|:encoding(UTF-8)", "git " . $_[0]);
+       my $args = shift;
+       my $encoding = (shift || "encoding(UTF-8)");
+       open(my $git, "-|:$encoding", "git " . $args);
        my $res = do { local $/; <$git> };
        close($git);
 
@@ -276,6 +389,123 @@ sub run_git {
 }
 
 
+sub get_all_mediafiles {
+       my $pages = shift;
+       # Attach list of all pages for media files from the API,
+       # they are in a different namespace, only one namespace
+       # can be queried at the same moment
+       my $mw_pages = $mediawiki->list({
+               action => 'query',
+               list => 'allpages',
+               apnamespace => get_mw_namespace_id("File"),
+               aplimit => 'max'
+       });
+       if (!defined($mw_pages)) {
+               print STDERR "fatal: could not get the list of pages for media files.\n";
+               print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+               print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+               exit 1;
+       }
+       foreach my $page (@{$mw_pages}) {
+               $pages->{$page->{title}} = $page;
+       }
+}
+
+sub get_linked_mediafiles {
+       my $pages = shift;
+       my @titles = map $_->{title}, values(%{$pages});
+
+       # The query is split in small batches because of the MW API limit of
+       # the number of links to be returned (500 links max).
+       my $batch = 10;
+       while (@titles) {
+               if ($#titles < $batch) {
+                       $batch = $#titles;
+               }
+               my @slice = @titles[0..$batch];
+
+               # pattern 'page1|page2|...' required by the API
+               my $mw_titles = join('|', @slice);
+
+               # Media files could be included or linked from
+               # a page, get all related
+               my $query = {
+                       action => 'query',
+                       prop => 'links|images',
+                       titles => $mw_titles,
+                       plnamespace => get_mw_namespace_id("File"),
+                       pllimit => 'max'
+               };
+               my $result = $mediawiki->api($query);
+
+               while (my ($id, $page) = each(%{$result->{query}->{pages}})) {
+                       my @media_titles;
+                       if (defined($page->{links})) {
+                               my @link_titles = map $_->{title}, @{$page->{links}};
+                               push(@media_titles, @link_titles);
+                       }
+                       if (defined($page->{images})) {
+                               my @image_titles = map $_->{title}, @{$page->{images}};
+                               push(@media_titles, @image_titles);
+                       }
+                       if (@media_titles) {
+                               get_mw_page_list(\@media_titles, $pages);
+                       }
+               }
+
+               @titles = @titles[($batch+1)..$#titles];
+       }
+}
+
+sub get_mw_mediafile_for_page_revision {
+       # Name of the file on Wiki, with the prefix.
+       my $filename = shift;
+       my $timestamp = shift;
+       my %mediafile;
+
+       # Search if on a media file with given timestamp exists on
+       # MediaWiki. In that case download the file.
+       my $query = {
+               action => 'query',
+               prop => 'imageinfo',
+               titles => "File:" . $filename,
+               iistart => $timestamp,
+               iiend => $timestamp,
+               iiprop => 'timestamp|archivename|url',
+               iilimit => 1
+       };
+       my $result = $mediawiki->api($query);
+
+       my ($fileid, $file) = each( %{$result->{query}->{pages}} );
+       # If not defined it means there is no revision of the file for
+       # given timestamp.
+       if (defined($file->{imageinfo})) {
+               $mediafile{title} = $filename;
+
+               my $fileinfo = pop(@{$file->{imageinfo}});
+               $mediafile{timestamp} = $fileinfo->{timestamp};
+               # Mediawiki::API's download function doesn't support https URLs
+               # and can't download old versions of files.
+               print STDERR "\tDownloading file $mediafile{title}, version $mediafile{timestamp}\n";
+               $mediafile{content} = download_mw_mediafile($fileinfo->{url});
+       }
+       return %mediafile;
+}
+
+sub download_mw_mediafile {
+       my $url = shift;
+
+       my $response = $mediawiki->{ua}->get($url);
+       if ($response->code == 200) {
+               return $response->decoded_content;
+       } else {
+               print STDERR "Error downloading mediafile from :\n";
+               print STDERR "URL: $url\n";
+               print STDERR "Server response: " . $response->code . " " . $response->message . "\n";
+               exit 1;
+       }
+}
+
 sub get_last_local_revision {
        # Get note regarding last mediawiki revision
        my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null");
@@ -297,13 +527,36 @@ sub get_last_local_revision {
 # Remember the timestamp corresponding to a revision id.
 my %basetimestamps;
 
+# Get the last remote revision without taking in account which pages are
+# tracked or not. This function makes a single request to the wiki thus
+# avoid a loop onto all tracked pages. This is useful for the fetch-by-rev
+# option.
+sub get_last_global_remote_rev {
+       mw_connect_maybe();
+
+       my $query = {
+               action => 'query',
+               list => 'recentchanges',
+               prop => 'revisions',
+               rclimit => '1',
+               rcdir => 'older',
+       };
+       my $result = $mediawiki->api($query);
+       return $result->{query}->{recentchanges}[0]->{revid};
+}
+
+# Get the last remote revision concerning the tracked pages and the tracked
+# categories.
 sub get_last_remote_revision {
        mw_connect_maybe();
 
-       my @pages = get_mw_pages();
+       my %pages_hash = get_mw_pages();
+       my @pages = values(%pages_hash);
 
        my $max_rev_num = 0;
 
+       print STDERR "Getting last revision id on tracked pages...\n";
+
        foreach my $page (@pages) {
                my $id = $page->{pageid};
 
@@ -379,6 +632,16 @@ sub literal_data {
        print STDOUT "data ", bytes::length($content), "\n", $content;
 }
 
+sub literal_data_raw {
+       # Output possibly binary content.
+       my ($content) = @_;
+       # Avoid confusion between size in bytes and in characters
+       utf8::downgrade($content);
+       binmode STDOUT, ":raw";
+       print STDOUT "data ", bytes::length($content), "\n", $content;
+       binmode STDOUT, ":utf8";
+}
+
 sub mw_capabilities {
        # Revisions are imported to the private namespace
        # refs/mediawiki/$remotename/ by the helper and fetched into
@@ -466,6 +729,11 @@ sub import_file_revision {
        my %commit = %{$commit};
        my $full_import = shift;
        my $n = shift;
+       my $mediafile = shift;
+       my %mediafile;
+       if ($mediafile) {
+               %mediafile = %{$mediafile};
+       }
 
        my $title = $commit{title};
        my $comment = $commit{comment};
@@ -485,6 +753,10 @@ sub import_file_revision {
        if ($content ne DELETED_CONTENT) {
                print STDOUT "M 644 inline $title.mw\n";
                literal_data($content);
+               if (%mediafile) {
+                       print STDOUT "M 644 inline $mediafile{title}\n";
+                       literal_data_raw($mediafile{content});
+               }
                print STDOUT "\n\n";
        } else {
                print STDOUT "D $title.mw\n";
@@ -547,8 +819,6 @@ sub mw_import_ref {
 
        mw_connect_maybe();
 
-       my @pages = get_mw_pages();
-
        print STDERR "Searching revisions...\n";
        my $last_local = get_last_local_revision();
        my $fetch_from = $last_local + 1;
@@ -557,36 +827,111 @@ sub mw_import_ref {
        } else {
                print STDERR ", fetching from here.\n";
        }
+
+       my $n = 0;
+       if ($fetch_strategy eq "by_rev") {
+               print STDERR "Fetching & writing export data by revs...\n";
+               $n = mw_import_ref_by_revs($fetch_from);
+       } elsif ($fetch_strategy eq "by_page") {
+               print STDERR "Fetching & writing export data by pages...\n";
+               $n = mw_import_ref_by_pages($fetch_from);
+       } else {
+               print STDERR "fatal: invalid fetch strategy \"$fetch_strategy\".\n";
+               print STDERR "Check your configuration variables remote.$remotename.fetchStrategy and mediawiki.fetchStrategy\n";
+               exit 1;
+       }
+
+       if ($fetch_from == 1 && $n == 0) {
+               print STDERR "You appear to have cloned an empty MediaWiki.\n";
+               # Something has to be done remote-helper side. If nothing is done, an error is
+               # thrown saying that HEAD is refering to unknown object 0000000000000000000
+               # and the clone fails.
+       }
+}
+
+sub mw_import_ref_by_pages {
+
+       my $fetch_from = shift;
+       my %pages_hash = get_mw_pages();
+       my @pages = values(%pages_hash);
+
        my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from);
 
-       # Creation of the fast-import stream
-       print STDERR "Fetching & writing export data...\n";
+       @revisions = sort {$a->{revid} <=> $b->{revid}} @revisions;
+       my @revision_ids = map $_->{revid}, @revisions;
+
+       return mw_import_revids($fetch_from, \@revision_ids, \%pages_hash);
+}
+
+sub mw_import_ref_by_revs {
+
+       my $fetch_from = shift;
+       my %pages_hash = get_mw_pages();
+
+       my $last_remote = get_last_global_remote_rev();
+       my @revision_ids = $fetch_from..$last_remote;
+       return mw_import_revids($fetch_from, \@revision_ids, \%pages_hash);
+}
+
+# Import revisions given in second argument (array of integers).
+# Only pages appearing in the third argument (hash indexed by page titles)
+# will be imported.
+sub mw_import_revids {
+       my $fetch_from = shift;
+       my $revision_ids = shift;
+       my $pages = shift;
 
-       $n = 0;
+       my $n = 0;
+       my $n_actual = 0;
        my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
 
-       foreach my $pagerevid (sort {$a->{revid} <=> $b->{revid}} @revisions) {
+       foreach my $pagerevid (@$revision_ids) {
+               # Count page even if we skip it, since we display
+               # $n/$total and $total includes skipped pages.
+               $n++;
+
                # fetch the content of the pages
                my $query = {
                        action => 'query',
                        prop => 'revisions',
                        rvprop => 'content|timestamp|comment|user|ids',
-                       revids => $pagerevid->{revid},
+                       revids => $pagerevid,
                };
 
                my $result = $mediawiki->api($query);
 
-               my $rev = pop(@{$result->{query}->{pages}->{$pagerevid->{pageid}}->{revisions}});
+               if (!$result) {
+                       die "Failed to retrieve modified page for revision $pagerevid";
+               }
 
-               $n++;
+               if (defined($result->{query}->{badrevids}->{$pagerevid})) {
+                       # The revision id does not exist on the remote wiki.
+                       next;
+               }
+
+               if (!defined($result->{query}->{pages})) {
+                       die "Invalid revision $pagerevid.";
+               }
+
+               my @result_pages = values(%{$result->{query}->{pages}});
+               my $result_page = $result_pages[0];
+               my $rev = $result_pages[0]->{revisions}->[0];
+
+               my $page_title = $result_page->{title};
+
+               if (!exists($pages->{$page_title})) {
+                       print STDERR "$n/", scalar(@$revision_ids),
+                               ": Skipping revision #$rev->{revid} of $page_title\n";
+                       next;
+               }
+
+               $n_actual++;
 
                my %commit;
                $commit{author} = $rev->{user} || 'Anonymous';
-               $commit{comment} = $rev->{comment} || '*Empty MediaWiki Message*';
-               $commit{title} = mediawiki_smudge_filename(
-                       $result->{query}->{pages}->{$pagerevid->{pageid}}->{title}
-                   );
-               $commit{mw_revision} = $pagerevid->{revid};
+               $commit{comment} = $rev->{comment} || EMPTY_MESSAGE;
+               $commit{title} = mediawiki_smudge_filename($page_title);
+               $commit{mw_revision} = $rev->{revid};
                $commit{content} = mediawiki_smudge($rev->{'*'});
 
                if (!defined($rev->{timestamp})) {
@@ -596,17 +941,23 @@ sub mw_import_ref {
                }
                $commit{date} = DateTime::Format::ISO8601->parse_datetime($last_timestamp);
 
-               print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
-
-               import_file_revision(\%commit, ($fetch_from == 1), $n);
+               # Differentiates classic pages and media files.
+               my ($namespace, $filename) = $page_title =~ /^([^:]*):(.*)$/;
+               my %mediafile;
+               if ($namespace) {
+                       my $id = get_mw_namespace_id($namespace);
+                       if ($id && $id == get_mw_namespace_id("File")) {
+                               %mediafile = get_mw_mediafile_for_page_revision($filename, $rev->{timestamp});
+                       }
+               }
+               # If this is a revision of the media page for new version
+               # of a file do one common commit for both file and media page.
+               # Else do commit only for that page.
+               print STDERR "$n/", scalar(@$revision_ids), ": Revision #$rev->{revid} of $commit{title}\n";
+               import_file_revision(\%commit, ($fetch_from == 1), $n_actual, \%mediafile);
        }
 
-       if ($fetch_from == 1 && $n == 0) {
-               print STDERR "You appear to have cloned an empty MediaWiki.\n";
-               # Something has to be done remote-helper side. If nothing is done, an error is
-               # thrown saying that HEAD is refering to unknown object 0000000000000000000
-               # and the clone fails.
-       }
+       return $n_actual;
 }
 
 sub error_non_fast_forward {
@@ -624,6 +975,63 @@ sub error_non_fast_forward {
        return 0;
 }
 
+sub mw_upload_file {
+       my $complete_file_name = shift;
+       my $new_sha1 = shift;
+       my $extension = shift;
+       my $file_deleted = shift;
+       my $summary = shift;
+       my $newrevid;
+       my $path = "File:" . $complete_file_name;
+       my %hashFiles = get_allowed_file_extensions();
+       if (!exists($hashFiles{$extension})) {
+               print STDERR "$complete_file_name is not a permitted file on this wiki.\n";
+               print STDERR "Check the configuration of file uploads in your mediawiki.\n";
+               return $newrevid;
+       }
+       # Deleting and uploading a file requires a priviledged user
+       if ($file_deleted) {
+               mw_connect_maybe();
+               my $query = {
+                       action => 'delete',
+                       title => $path,
+                       reason => $summary
+               };
+               if (!$mediawiki->edit($query)) {
+                       print STDERR "Failed to delete file on remote wiki\n";
+                       print STDERR "Check your permissions on the remote site. Error code:\n";
+                       print STDERR $mediawiki->{error}->{code} . ':' . $mediawiki->{error}->{details};
+                       exit 1;
+               }
+       } else {
+               # Don't let perl try to interpret file content as UTF-8 => use "raw"
+               my $content = run_git("cat-file blob $new_sha1", "raw");
+               if ($content ne "") {
+                       mw_connect_maybe();
+                       $mediawiki->{config}->{upload_url} =
+                               "$url/index.php/Special:Upload";
+                       $mediawiki->edit({
+                               action => 'upload',
+                               filename => $complete_file_name,
+                               comment => $summary,
+                               file => [undef,
+                                        $complete_file_name,
+                                        Content => $content],
+                               ignorewarnings => 1,
+                       }, {
+                               skip_encoding => 1
+                       } ) || die $mediawiki->{error}->{code} . ':'
+                                . $mediawiki->{error}->{details};
+                       my $last_file_page = $mediawiki->get_page({title => $path});
+                       $newrevid = $last_file_page->{revid};
+                       print STDERR "Pushed file: $new_sha1 - $complete_file_name.\n";
+               } else {
+                       print STDERR "Empty file $complete_file_name not pushed.\n";
+               }
+       }
+       return $newrevid;
+}
+
 sub mw_push_file {
        my $diff_info = shift;
        # $diff_info contains a string in this format:
@@ -636,7 +1044,12 @@ sub mw_push_file {
        my $summary = shift;
        # MediaWiki revision number. Keep the previous one by default,
        # in case there's no edit to perform.
-       my $newrevid = shift;
+       my $oldrevid = shift;
+       my $newrevid;
+
+       if ($summary eq EMPTY_MESSAGE) {
+               $summary = '';
+       }
 
        my $new_sha1 = $diff_info_split[3];
        my $old_sha1 = $diff_info_split[2];
@@ -644,9 +1057,16 @@ sub mw_push_file {
        my $page_deleted = ($new_sha1 eq NULL_SHA1);
        $complete_file_name = mediawiki_clean_filename($complete_file_name);
 
-       if (substr($complete_file_name,-3) eq ".mw") {
-               my $title = substr($complete_file_name,0,-3);
-
+       my ($title, $extension) = $complete_file_name =~ /^(.*)\.([^\.]*)$/;
+       if (!defined($extension)) {
+               $extension = "";
+       }
+       if ($extension eq "mw") {
+               my $ns = get_mw_namespace_id_for_page($complete_file_name);
+               if ($ns && $ns == get_mw_namespace_id("File") && (!$export_media)) {
+                       print STDERR "Ignoring media file related page: $complete_file_name\n";
+                       return ($oldrevid, "ok");
+               }
                my $file_content;
                if ($page_deleted) {
                        # Deleting a page usually requires
@@ -664,7 +1084,7 @@ sub mw_push_file {
                        action => 'edit',
                        summary => $summary,
                        title => $title,
-                       basetimestamp => $basetimestamps{$newrevid},
+                       basetimestamp => $basetimestamps{$oldrevid},
                        text => mediawiki_clean($file_content, $page_created),
                                  }, {
                                          skip_encoding => 1 # Helps with names with accentuated characters
@@ -676,7 +1096,7 @@ sub mw_push_file {
                                    $mediawiki->{error}->{code} .
                                    ' from mediwiki: ' . $mediawiki->{error}->{details} .
                                    ".\n";
-                               return ($newrevid, "non-fast-forward");
+                               return ($oldrevid, "non-fast-forward");
                        } else {
                                # Other errors. Shouldn't happen => just die()
                                die 'Fatal: Error ' .
@@ -686,9 +1106,14 @@ sub mw_push_file {
                }
                $newrevid = $result->{edit}->{newrevid};
                print STDERR "Pushed file: $new_sha1 - $title\n";
+       } elsif ($export_media) {
+               $newrevid = mw_upload_file($complete_file_name, $new_sha1,
+                                          $extension, $page_deleted,
+                                          $summary);
        } else {
-               print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
+               print STDERR "Ignoring media file $title\n";
        }
+       $newrevid = ($newrevid or $oldrevid);
        return ($newrevid, "ok");
 }
 
@@ -760,16 +1185,26 @@ sub mw_push_revision {
        if ($last_local_revid > 0) {
                my $parsed_sha1 = $remoteorigin_sha1;
                # Find a path from last MediaWiki commit to pushed commit
+               print STDERR "Computing path from local to remote ...\n";
+               my @local_ancestry = split(/\n/, run_git("rev-list --boundary --parents $local ^$parsed_sha1"));
+               my %local_ancestry;
+               foreach my $line (@local_ancestry) {
+                       if (my ($child, $parents) = $line =~ m/^-?([a-f0-9]+) ([a-f0-9 ]+)/) {
+                               foreach my $parent (split(' ', $parents)) {
+                                       $local_ancestry{$parent} = $child;
+                               }
+                       } elsif (!$line =~ m/^([a-f0-9]+)/) {
+                               die "Unexpected output from git rev-list: $line";
+                       }
+               }
                while ($parsed_sha1 ne $HEAD_sha1) {
-                       my @commit_info =  grep(/^$parsed_sha1/, split(/\n/, run_git("rev-list --children $local")));
-                       if (!@commit_info) {
+                       my $child = $local_ancestry{$parsed_sha1};
+                       if (!$child) {
+                               printf STDERR "Cannot find a path in history from remote commit to last commit\n";
                                return error_non_fast_forward($remote);
                        }
-                       my @commit_info_split = split(/ |\n/, $commit_info[0]);
-                       # $commit_info_split[1] is the sha1 of the commit to export
-                       # $commit_info_split[0] is the sha1 of its direct child
-                       push(@commit_pairs, \@commit_info_split);
-                       $parsed_sha1 = $commit_info_split[1];
+                       push(@commit_pairs, [$parsed_sha1, $child]);
+                       $parsed_sha1 = $child;
                }
        } else {
                # No remote mediawiki revision. Export the whole
@@ -791,8 +1226,8 @@ sub mw_push_revision {
                # TODO: we could detect rename, and encode them with a #redirect on the wiki.
                # TODO: for now, it's just a delete+add
                my @diff_info_list = split(/\0/, $diff_infos);
-               # Keep the first line of the commit message as mediawiki comment for the revision
-               my $commit_msg = (split(/\n/, run_git("show --pretty=format:\"%s\" $sha1_commit")))[0];
+               # Keep the subject line of the commit message as mediawiki comment for the revision
+               my $commit_msg = run_git("log --no-walk --format=\"%s\" $sha1_commit");
                chomp($commit_msg);
                # Push every blob
                while (@diff_info_list) {
@@ -817,7 +1252,7 @@ sub mw_push_revision {
                        }
                }
                unless ($dumb_push) {
-                       run_git("notes --ref=$remotename/mediawiki add -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
+                       run_git("notes --ref=$remotename/mediawiki add -f -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
                        run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
                }
        }
@@ -825,3 +1260,104 @@ sub mw_push_revision {
        print STDOUT "ok $remote\n";
        return 1;
 }
+
+sub get_allowed_file_extensions {
+       mw_connect_maybe();
+
+       my $query = {
+               action => 'query',
+               meta => 'siteinfo',
+               siprop => 'fileextensions'
+               };
+       my $result = $mediawiki->api($query);
+       my @file_extensions= map $_->{ext},@{$result->{query}->{fileextensions}};
+       my %hashFile = map {$_ => 1}@file_extensions;
+
+       return %hashFile;
+}
+
+# In memory cache for MediaWiki namespace ids.
+my %namespace_id;
+
+# Namespaces whose id is cached in the configuration file
+# (to avoid duplicates)
+my %cached_mw_namespace_id;
+
+# Return MediaWiki id for a canonical namespace name.
+# Ex.: "File", "Project".
+sub get_mw_namespace_id {
+       mw_connect_maybe();
+       my $name = shift;
+
+       if (!exists $namespace_id{$name}) {
+               # Look at configuration file, if the record for that namespace is
+               # already cached. Namespaces are stored in form:
+               # "Name_of_namespace:Id_namespace", ex.: "File:6".
+               my @temp = split(/[\n]/, run_git("config --get-all remote."
+                                               . $remotename .".namespaceCache"));
+               chomp(@temp);
+               foreach my $ns (@temp) {
+                       my ($n, $id) = split(/:/, $ns);
+                       if ($id eq 'notANameSpace') {
+                               $namespace_id{$n} = {is_namespace => 0};
+                       } else {
+                               $namespace_id{$n} = {is_namespace => 1, id => $id};
+                       }
+                       $cached_mw_namespace_id{$n} = 1;
+               }
+       }
+
+       if (!exists $namespace_id{$name}) {
+               print STDERR "Namespace $name not found in cache, querying the wiki ...\n";
+               # NS not found => get namespace id from MW and store it in
+               # configuration file.
+               my $query = {
+                       action => 'query',
+                       meta => 'siteinfo',
+                       siprop => 'namespaces'
+               };
+               my $result = $mediawiki->api($query);
+
+               while (my ($id, $ns) = each(%{$result->{query}->{namespaces}})) {
+                       if (defined($ns->{id}) && defined($ns->{canonical})) {
+                               $namespace_id{$ns->{canonical}} = {is_namespace => 1, id => $ns->{id}};
+                               if ($ns->{'*'}) {
+                                       # alias (e.g. french Fichier: as alias for canonical File:)
+                                       $namespace_id{$ns->{'*'}} = {is_namespace => 1, id => $ns->{id}};
+                               }
+                       }
+               }
+       }
+
+       my $ns = $namespace_id{$name};
+       my $id;
+
+       unless (defined $ns) {
+               print STDERR "No such namespace $name on MediaWiki.\n";
+               $ns = {is_namespace => 0};
+               $namespace_id{$name} = $ns;
+       }
+
+       if ($ns->{is_namespace}) {
+               $id = $ns->{id};
+       }
+
+       # Store "notANameSpace" as special value for inexisting namespaces
+       my $store_id = ($id || 'notANameSpace');
+
+       # Store explicitely requested namespaces on disk
+       if (!exists $cached_mw_namespace_id{$name}) {
+               run_git("config --add remote.". $remotename
+                       .".namespaceCache \"". $name .":". $store_id ."\"");
+               $cached_mw_namespace_id{$name} = 1;
+       }
+       return $id;
+}
+
+sub get_mw_namespace_id_for_page {
+       if (my ($namespace) = $_[0] =~ /^([^:]*):/) {
+               return get_mw_namespace_id($namespace);
+       } else {
+               return;
+       }
+}
diff --git a/contrib/mw-to-git/t/.gitignore b/contrib/mw-to-git/t/.gitignore
new file mode 100644 (file)
index 0000000..a7a40b4
--- /dev/null
@@ -0,0 +1,4 @@
+WEB/
+wiki/
+trash directory.t*/
+test-results/
diff --git a/contrib/mw-to-git/t/Makefile b/contrib/mw-to-git/t/Makefile
new file mode 100644 (file)
index 0000000..f422203
--- /dev/null
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2012
+#     Charles Roussel <charles.roussel@ensimag.imag.fr>
+#     Simon Cathebras <simon.cathebras@ensimag.imag.fr>
+#     Julien Khayat <julien.khayat@ensimag.imag.fr>
+#     Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
+#     Simon Perrat <simon.perrat@ensimag.imag.fr>
+#
+## Test git-remote-mediawiki
+
+all: test
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+.PHONY: help test clean all
+
+help:
+       @echo 'Run "$(MAKE) test" to launch test scripts'
+       @echo 'Run "$(MAKE) clean" to remove trash folders'
+
+test:
+       @for t in $(T); do \
+               echo "$$t"; \
+               "./$$t" || exit 1; \
+       done
+
+clean:
+       $(RM) -r 'trash directory'.*
diff --git a/contrib/mw-to-git/t/README b/contrib/mw-to-git/t/README
new file mode 100644 (file)
index 0000000..96e9739
--- /dev/null
@@ -0,0 +1,124 @@
+Tests for Mediawiki-to-Git
+==========================
+
+Introduction
+------------
+This manual describes how to install the git-remote-mediawiki test
+environment on a machine with git installed on it.
+
+Prerequisite
+------------
+
+In order to run this test environment correctly, you will need to
+install the following packages (Debian/Ubuntu names, may need to be
+adapted for another distribution):
+
+* lighttpd
+* php5
+* php5-cgi
+* php5-cli
+* php5-curl
+* php5-sqlite
+
+Principles and Technical Choices
+--------------------------------
+
+The test environment makes it easy to install and manipulate one or
+several MediaWiki instances. To allow developers to run the testsuite
+easily, the environment does not require root priviledge (except to
+install the required packages if needed). It starts a webserver
+instance on the user's account (using lighttpd greatly helps for
+that), and does not need a separate database daemon (thanks to the use
+of sqlite).
+
+Run the test environment
+------------------------
+
+Install a new wiki
+~~~~~~~~~~~~~~~~~~
+
+Once you have all the prerequisite, you need to install a MediaWiki
+instance on your machine. If you already have one, it is still
+strongly recommended to install one with the script provided. Here's
+how to work it:
+
+a. change directory to contrib/mw-to-git/t/
+b. if needed, edit test.config to choose your installation parameters
+c. run `./install-wiki.sh install`
+d. check on your favourite web browser if your wiki is correctly
+   installed.
+
+Remove an existing wiki
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Edit the file test.config to fit the wiki you want to delete, and then
+execute the command `./install-wiki.sh delete` from the
+contrib/mw-to-git/t directory.
+
+Run the existing tests
+~~~~~~~~~~~~~~~~~~~~~~
+
+The provided tests are currently in the `contrib/mw-to-git/t` directory.
+The files are all the t936[0-9]-*.sh shell scripts.
+
+a. Run all tests:
+To do so, run "make test" from the contrib/mw-to-git/ directory.
+
+b. Run a specific test:
+To run a given test <test_name>, run ./<test_name> from the
+contrib/mw-to-git/t directory.
+
+How to create new tests
+-----------------------
+
+Available functions
+~~~~~~~~~~~~~~~~~~~
+
+The test environment of git-remote-mediawiki provides some functions
+useful to test its behaviour. for more details about the functions'
+parameters, please refer to the `test-gitmw-lib.sh` and
+`test-gitmw.pl` files.
+
+** `test_check_wiki_precond`:
+Check if the tests must be skipped or not. Please use this function
+at the beggining of each new test file.
+
+** `wiki_getpage`:
+Fetch a given page from the wiki and puts its content in the
+directory in parameter.
+
+** `wiki_delete_page`:
+Delete a given page from the wiki.
+
+** `wiki_edit_page`:
+Create or modify a given page in the wiki. You can specify several
+parameters like a summary for the page edition, or add the page to a
+given category.
+See test-gitmw.pl for more details.
+
+** `wiki_getallpage`:
+Fetch all pages from the wiki into a given directory. The directory
+is created if it does not exists.
+
+** `test_diff_directories`:
+Compare the content of two directories. The content must be the same.
+Use this function to compare the content of a git directory and a wiki
+one created by wiki_getallpage.
+
+** `test_contains_N_files`:
+Check if the given directory contains a given number of file.
+
+** `wiki_page_exists`:
+Tests if a given page exists on the wiki.
+
+** `wiki_reset`:
+Reset the wiki, i.e. flush the database. Use this function at the
+begining of each new test, except if the test re-uses the same wiki
+(and history) as the previous test.
+
+How to write a new test
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Please, follow the standards given by git. See git/t/README.
+New file should be named as t936[0-9]-*.sh.
+Be sure to reset your wiki regulary with the function `wiki_reset`.
diff --git a/contrib/mw-to-git/t/install-wiki.sh b/contrib/mw-to-git/t/install-wiki.sh
new file mode 100755 (executable)
index 0000000..c6d6fa3
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# This script installs or deletes a MediaWiki on your computer.
+# It requires a web server with PHP and SQLite running. In addition, if you
+# do not have MediaWiki sources on your computer, the option 'install'
+# downloads them for you.
+# Please set the CONFIGURATION VARIABLES in ./test-gitmw-lib.sh
+
+WIKI_TEST_DIR=$(cd "$(dirname "$0")" && pwd)
+
+if test -z "$WIKI_TEST_DIR"
+then
+       WIKI_TEST_DIR=.
+fi
+
+. "$WIKI_TEST_DIR"/test-gitmw-lib.sh
+usage () {
+       echo "Usage: "
+       echo "  ./install-wiki.sh <install | delete | --help>"
+       echo "          install | -i :  Install a wiki on your computer."
+       echo "          delete | -d : Delete the wiki and all its pages and "
+       echo "                  content."
+}
+
+
+# Argument: install, delete, --help | -h
+case "$1" in
+       "install" | "-i")
+               wiki_install
+               exit 0
+               ;;
+       "delete" | "-d")
+               wiki_delete
+               exit 0
+               ;;
+       "--help" | "-h")
+               usage
+               exit 0
+               ;;
+       *)
+               echo "Invalid argument: $1"
+               usage
+               exit 1
+               ;;
+esac
diff --git a/contrib/mw-to-git/t/install-wiki/.gitignore b/contrib/mw-to-git/t/install-wiki/.gitignore
new file mode 100644 (file)
index 0000000..b5a2a44
--- /dev/null
@@ -0,0 +1 @@
+wikidb.sqlite
diff --git a/contrib/mw-to-git/t/install-wiki/LocalSettings.php b/contrib/mw-to-git/t/install-wiki/LocalSettings.php
new file mode 100644 (file)
index 0000000..29f1251
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+# This file was automatically generated by the MediaWiki 1.19.0
+# installer. If you make manual changes, please keep track in case you
+# need to recreate them later.
+#
+# See includes/DefaultSettings.php for all configurable settings
+# and their default values, but don't forget to make changes in _this_
+# file, not there.
+#
+# Further documentation for configuration settings may be found at:
+# http://www.mediawiki.org/wiki/Manual:Configuration_settings
+
+# Protect against web entry
+if ( !defined( 'MEDIAWIKI' ) ) {
+       exit;
+}
+
+## Uncomment this to disable output compression
+# $wgDisableOutputCompression = true;
+
+$wgSitename      = "Git-MediaWiki-Test";
+$wgMetaNamespace = "Git-MediaWiki-Test";
+
+## The URL base path to the directory containing the wiki;
+## defaults for all runtime URL paths are based off of this.
+## For more information on customizing the URLs please see:
+## http://www.mediawiki.org/wiki/Manual:Short_URL
+$wgScriptPath       = "@WG_SCRIPT_PATH@";
+$wgScriptExtension  = ".php";
+
+## The protocol and server name to use in fully-qualified URLs
+$wgServer           = "@WG_SERVER@";
+
+## The relative URL path to the skins directory
+$wgStylePath        = "$wgScriptPath/skins";
+
+## The relative URL path to the logo.  Make sure you change this from the default,
+## or else you'll overwrite your logo when you upgrade!
+$wgLogo             = "$wgStylePath/common/images/wiki.png";
+
+## UPO means: this is also a user preference option
+
+$wgEnableEmail      = true;
+$wgEnableUserEmail  = true; # UPO
+
+$wgEmergencyContact = "apache@localhost";
+$wgPasswordSender   = "apache@localhost";
+
+$wgEnotifUserTalk      = false; # UPO
+$wgEnotifWatchlist     = false; # UPO
+$wgEmailAuthentication = true;
+
+## Database settings
+$wgDBtype           = "sqlite";
+$wgDBserver         = "";
+$wgDBname           = "@WG_SQLITE_DATAFILE@";
+$wgDBuser           = "";
+$wgDBpassword       = "";
+
+# SQLite-specific settings
+$wgSQLiteDataDir    = "@WG_SQLITE_DATADIR@";
+
+
+## Shared memory settings
+$wgMainCacheType    = CACHE_NONE;
+$wgMemCachedServers = array();
+
+## To enable image uploads, make sure the 'images' directory
+## is writable, then set this to true:
+$wgEnableUploads  = true;
+$wgUseImageMagick = true;
+$wgImageMagickConvertCommand ="@CONVERT@";
+$wgFileExtensions[] = 'txt';
+
+# InstantCommons allows wiki to use images from http://commons.wikimedia.org
+$wgUseInstantCommons  = false;
+
+## If you use ImageMagick (or any other shell command) on a
+## Linux server, this will need to be set to the name of an
+## available UTF-8 locale
+$wgShellLocale = "en_US.utf8";
+
+## If you want to use image uploads under safe mode,
+## create the directories images/archive, images/thumb and
+## images/temp, and make them all writable. Then uncomment
+## this, if it's not already uncommented:
+#$wgHashedUploadDirectory = false;
+
+## Set $wgCacheDirectory to a writable directory on the web server
+## to make your wiki go slightly faster. The directory should not
+## be publically accessible from the web.
+#$wgCacheDirectory = "$IP/cache";
+
+# Site language code, should be one of the list in ./languages/Names.php
+$wgLanguageCode = "en";
+
+$wgSecretKey = "1c912bfe3519fb70f5dc523ecc698111cd43d81a11c585b3eefb28f29c2699b7";
+#$wgSecretKey = "@SECRETKEY@";
+
+
+# Site upgrade key. Must be set to a string (default provided) to turn on the
+# web installer while LocalSettings.php is in place
+$wgUpgradeKey = "ddae7dc87cd0a645";
+
+## Default skin: you can change the default skin. Use the internal symbolic
+## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook', 'vector':
+$wgDefaultSkin = "vector";
+
+## For attaching licensing metadata to pages, and displaying an
+## appropriate copyright notice / icon. GNU Free Documentation
+## License and Creative Commons licenses are supported so far.
+$wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
+$wgRightsUrl  = "";
+$wgRightsText = "";
+$wgRightsIcon = "";
+
+# Path to the GNU diff3 utility. Used for conflict resolution.
+$wgDiff3 = "/usr/bin/diff3";
+
+# Query string length limit for ResourceLoader. You should only set this if
+# your web server has a query string length limit (then set it to that limit),
+# or if you have suhosin.get.max_value_length set in php.ini (then set it to
+# that value)
+$wgResourceLoaderMaxQueryLength = -1;
+
+
+
+# End of automatically generated settings.
+# Add more configuration options below.
diff --git a/contrib/mw-to-git/t/install-wiki/db_install.php b/contrib/mw-to-git/t/install-wiki/db_install.php
new file mode 100644 (file)
index 0000000..0f3f4e0
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+/**
+ * This script generates a SQLite database for a MediaWiki version 1.19.0
+ * You must specify the login of the admin (argument 1) and its
+ * password (argument 2) and the folder where the database file
+ * is located (absolute path in argument 3).
+ * It is used by the script install-wiki.sh in order to make easy the
+ * installation of a MediaWiki.
+ *
+ * In order to generate a SQLite database file, MediaWiki ask the user
+ * to submit some forms in its web browser. This script simulates this
+ * behavior though the functions <get> and <submit>
+ *
+ */
+$argc = $_SERVER['argc'];
+$argv = $_SERVER['argv'];
+
+$login = $argv[2];
+$pass = $argv[3];
+$tmp = $argv[4];
+$port = $argv[5];
+
+$url = 'http://localhost:'.$port.'/wiki/mw-config/index.php';
+$db_dir = urlencode($tmp);
+$tmp_cookie = tempnam($tmp, "COOKIE_");
+/*
+ * Fetchs a page with cURL.
+ */
+function get($page_name = "") {
+       $curl = curl_init();
+       $page_name_add = "";
+       if ($page_name != "") {
+               $page_name_add = '?page='.$page_name;
+       }
+       $url = $GLOBALS['url'].$page_name_add;
+       $tmp_cookie = $GLOBALS['tmp_cookie'];
+       curl_setopt($curl, CURLOPT_COOKIEJAR, $tmp_cookie);
+       curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+       curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+       curl_setopt($curl, CURLOPT_COOKIEFILE, $tmp_cookie);
+       curl_setopt($curl, CURLOPT_HEADER, true);
+       curl_setopt($curl, CURLOPT_URL, $url);
+
+       $page = curl_exec($curl);
+       if (!$page) {
+               die("Could not get page: $url\n");
+       }
+       curl_close($curl);
+       return $page;
+}
+
+/*
+ * Submits a form with cURL.
+ */
+function submit($page_name, $option = "") {
+       $curl = curl_init();
+       $datapost = 'submit-continue=Continue+%E2%86%92';
+       if ($option != "") {
+               $datapost = $option.'&'.$datapost;
+       }
+       $url = $GLOBALS['url'].'?page='.$page_name;
+       $tmp_cookie = $GLOBALS['tmp_cookie'];
+       curl_setopt($curl, CURLOPT_URL, $url);
+       curl_setopt($curl, CURLOPT_POST, true);
+       curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+       curl_setopt($curl, CURLOPT_POSTFIELDS, $datapost);
+       curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+       curl_setopt($curl, CURLOPT_COOKIEJAR, $tmp_cookie);
+       curl_setopt($curl, CURLOPT_COOKIEFILE, $tmp_cookie);
+
+       $page = curl_exec($curl);
+       if (!$page) {
+               die("Could not get page: $url\n");
+       }
+       curl_close($curl);
+       return "$page";
+}
+
+/*
+ * Here starts this script: simulates the behavior of the user
+ * submitting forms to generates the database file.
+ * Note this simulation was made for the MediaWiki version 1.19.0,
+ * we can't assume it works with other versions.
+ *
+ */
+
+$page = get();
+if (!preg_match('/input type="hidden" value="([0-9]+)" name="LanguageRequestTime"/',
+               $page, $matches)) {
+       echo "Unexpected content for page downloaded:\n";
+       echo "$page";
+       die;
+};
+$timestamp = $matches[1];
+$language = "LanguageRequestTime=$timestamp&uselang=en&ContLang=en";
+$page = submit('Language', $language);
+
+submit('Welcome');
+
+$db_config = 'DBType=sqlite';
+$db_config = $db_config.'&sqlite_wgSQLiteDataDir='.$db_dir;
+$db_config = $db_config.'&sqlite_wgDBname='.$argv[1];
+submit('DBConnect', $db_config);
+
+$wiki_config = 'config_wgSitename=TEST';
+$wiki_config = $wiki_config.'&config__NamespaceType=site-name';
+$wiki_config = $wiki_config.'&config_wgMetaNamespace=MyWiki';
+$wiki_config = $wiki_config.'&config__AdminName='.$login;
+
+$wiki_config = $wiki_config.'&config__AdminPassword='.$pass;
+$wiki_config = $wiki_config.'&config__AdminPassword2='.$pass;
+
+$wiki_config = $wiki_config.'&wiki__configEmail=email%40email.org';
+$wiki_config = $wiki_config.'&config__SkipOptional=skip';
+submit('Name', $wiki_config);
+submit('Install');
+submit('Install');
+
+unlink($tmp_cookie);
+?>
diff --git a/contrib/mw-to-git/t/push-pull-tests.sh b/contrib/mw-to-git/t/push-pull-tests.sh
new file mode 100644 (file)
index 0000000..9da2dc5
--- /dev/null
@@ -0,0 +1,144 @@
+test_push_pull () {
+
+       test_expect_success 'Git pull works after adding a new wiki page' '
+               wiki_reset &&
+
+               git clone mediawiki::'"$WIKI_URL"' mw_dir_1 &&
+               wiki_editpage Foo "page created after the git clone" false &&
+
+               (
+                       cd mw_dir_1 &&
+                       git pull
+               ) &&
+
+               wiki_getallpage ref_page_1 &&
+               test_diff_directories mw_dir_1 ref_page_1
+       '
+
+       test_expect_success 'Git pull works after editing a wiki page' '
+               wiki_reset &&
+
+               wiki_editpage Foo "page created before the git clone" false &&
+               git clone mediawiki::'"$WIKI_URL"' mw_dir_2 &&
+               wiki_editpage Foo "new line added on the wiki" true &&
+
+               (
+                       cd mw_dir_2 &&
+                       git pull
+               ) &&
+
+               wiki_getallpage ref_page_2 &&
+               test_diff_directories mw_dir_2 ref_page_2
+       '
+
+       test_expect_success 'git pull works on conflict handled by auto-merge' '
+               wiki_reset &&
+
+               wiki_editpage Foo "1 init
+3
+5
+       " false &&
+               git clone mediawiki::'"$WIKI_URL"' mw_dir_3 &&
+
+               wiki_editpage Foo "1 init
+2 content added on wiki after clone
+3
+5
+       " false &&
+
+               (
+                       cd mw_dir_3 &&
+               echo "1 init
+3
+4 content added on git after clone
+5
+" >Foo.mw &&
+                       git commit -am "conflicting change on foo" &&
+                       git pull &&
+                       git push
+               )
+       '
+
+       test_expect_success 'Git push works after adding a file .mw' '
+               wiki_reset &&
+               git clone mediawiki::'"$WIKI_URL"' mw_dir_4 &&
+               wiki_getallpage ref_page_4 &&
+               (
+                       cd mw_dir_4 &&
+                       test_path_is_missing Foo.mw &&
+                       touch Foo.mw &&
+                       echo "hello world" >>Foo.mw &&
+                       git add Foo.mw &&
+                       git commit -m "Foo" &&
+                       git push
+               ) &&
+               wiki_getallpage ref_page_4 &&
+               test_diff_directories mw_dir_4 ref_page_4
+       '
+
+       test_expect_success 'Git push works after editing a file .mw' '
+               wiki_reset &&
+               wiki_editpage "Foo" "page created before the git clone" false &&
+               git clone mediawiki::'"$WIKI_URL"' mw_dir_5 &&
+
+               (
+                       cd mw_dir_5 &&
+                       echo "new line added in the file Foo.mw" >>Foo.mw &&
+                       git commit -am "edit file Foo.mw" &&
+                       git push
+               ) &&
+
+               wiki_getallpage ref_page_5 &&
+               test_diff_directories mw_dir_5 ref_page_5
+       '
+
+       test_expect_failure 'Git push works after deleting a file' '
+               wiki_reset &&
+               wiki_editpage Foo "wiki page added before git clone" false &&
+               git clone mediawiki::'"$WIKI_URL"' mw_dir_6 &&
+
+               (
+                       cd mw_dir_6 &&
+                       git rm Foo.mw &&
+                       git commit -am "page Foo.mw deleted" &&
+                       git push
+               ) &&
+
+               test_must_fail wiki_page_exist Foo
+       '
+
+       test_expect_success 'Merge conflict expected and solving it' '
+               wiki_reset &&
+
+               git clone mediawiki::'"$WIKI_URL"' mw_dir_7 &&
+               wiki_editpage Foo "1 conflict
+3 wiki
+4" false &&
+
+               (
+                       cd mw_dir_7 &&
+               echo "1 conflict
+2 git
+4" >Foo.mw &&
+                       git add Foo.mw &&
+                       git commit -m "conflict created" &&
+                       test_must_fail git pull &&
+                       "$PERL_PATH" -pi -e "s/[<=>].*//g" Foo.mw &&
+                       git commit -am "merge conflict solved" &&
+                       git push
+               )
+       '
+
+       test_expect_failure 'git pull works after deleting a wiki page' '
+               wiki_reset &&
+               wiki_editpage Foo "wiki page added before the git clone" false &&
+               git clone mediawiki::'"$WIKI_URL"' mw_dir_8 &&
+
+               wiki_delete_page Foo &&
+               (
+                       cd mw_dir_8 &&
+                       git pull &&
+                       test_path_is_missing Foo.mw
+               )
+       '
+}
diff --git a/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh b/contrib/mw-to-git/t/t9360-mw-to-git-clone.sh
new file mode 100755 (executable)
index 0000000..811a90c
--- /dev/null
@@ -0,0 +1,257 @@
+#!/bin/sh
+#
+# Copyright (C) 2012
+#     Charles Roussel <charles.roussel@ensimag.imag.fr>
+#     Simon Cathebras <simon.cathebras@ensimag.imag.fr>
+#     Julien Khayat <julien.khayat@ensimag.imag.fr>
+#     Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
+#     Simon Perrat <simon.perrat@ensimag.imag.fr>
+#
+# License: GPL v2 or later
+
+
+test_description='Test the Git Mediawiki remote helper: git clone'
+
+. ./test-gitmw-lib.sh
+. $TEST_DIRECTORY/test-lib.sh
+
+
+test_check_precond
+
+
+test_expect_success 'Git clone creates the expected git log with one file' '
+       wiki_reset &&
+       wiki_editpage foo "this is not important" false -c cat -s "this must be the same" &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_1 &&
+       (
+               cd mw_dir_1 &&
+               git log --format=%s HEAD^..HEAD >log.tmp
+       ) &&
+       echo "this must be the same" >msg.tmp &&
+       diff -b mw_dir_1/log.tmp msg.tmp
+'
+
+
+test_expect_success 'Git clone creates the expected git log with multiple files' '
+       wiki_reset &&
+       wiki_editpage daddy "this is not important" false -s="this must be the same" &&
+       wiki_editpage daddy "neither is this" true -s="this must also be the same" &&
+       wiki_editpage daddy "neither is this" true -s="same same same" &&
+       wiki_editpage dj "dont care" false -s="identical" &&
+       wiki_editpage dj "dont care either" true -s="identical too" &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_2 &&
+       (
+               cd mw_dir_2 &&
+               git log --format=%s Daddy.mw  >logDaddy.tmp &&
+               git log --format=%s Dj.mw >logDj.tmp
+       ) &&
+       echo "same same same" >msgDaddy.tmp &&
+       echo "this must also be the same" >>msgDaddy.tmp &&
+       echo "this must be the same" >>msgDaddy.tmp &&
+       echo "identical too" >msgDj.tmp &&
+       echo "identical" >>msgDj.tmp &&
+       diff -b mw_dir_2/logDaddy.tmp msgDaddy.tmp &&
+       diff -b mw_dir_2/logDj.tmp msgDj.tmp
+'
+
+
+test_expect_success 'Git clone creates only Main_Page.mw with an empty wiki' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_3 &&
+       test_contains_N_files mw_dir_3 1 &&
+       test_path_is_file mw_dir_3/Main_Page.mw
+'
+
+test_expect_success 'Git clone does not fetch a deleted page' '
+       wiki_reset &&
+       wiki_editpage foo "this page must be deleted before the clone" false &&
+       wiki_delete_page foo &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_4 &&
+       test_contains_N_files mw_dir_4 1 &&
+       test_path_is_file mw_dir_4/Main_Page.mw &&
+       test_path_is_missing mw_dir_4/Foo.mw
+'
+
+test_expect_success 'Git clone works with page added' '
+       wiki_reset &&
+       wiki_editpage foo " I will be cloned" false &&
+       wiki_editpage bar "I will be cloned" false &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_5 &&
+       wiki_getallpage ref_page_5 &&
+       test_diff_directories mw_dir_5 ref_page_5 &&
+       wiki_delete_page foo &&
+       wiki_delete_page bar
+'
+
+test_expect_success 'Git clone works with an edited page ' '
+       wiki_reset &&
+       wiki_editpage foo "this page will be edited" \
+               false -s "first edition of page foo"&&
+       wiki_editpage foo "this page has been edited and must be on the clone " true &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_6 &&
+       test_path_is_file mw_dir_6/Foo.mw &&
+       test_path_is_file mw_dir_6/Main_Page.mw &&
+       wiki_getallpage mw_dir_6/page_ref_6 &&
+       test_diff_directories mw_dir_6 mw_dir_6/page_ref_6 &&
+       (
+               cd mw_dir_6 &&
+               git log --format=%s HEAD^ Foo.mw > ../Foo.log
+       ) &&
+       echo "first edition of page foo" > FooExpect.log &&
+       diff FooExpect.log Foo.log
+'
+
+
+test_expect_success 'Git clone works with several pages and some deleted ' '
+       wiki_reset &&
+       wiki_editpage foo "this page will not be deleted" false &&
+       wiki_editpage bar "I must not be erased" false &&
+       wiki_editpage namnam "I will not be there at the end" false &&
+       wiki_editpage nyancat "nyan nyan nyan delete me" false &&
+       wiki_delete_page namnam &&
+       wiki_delete_page nyancat &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_7 &&
+       test_path_is_file mw_dir_7/Foo.mw &&
+       test_path_is_file mw_dir_7/Bar.mw &&
+       test_path_is_missing mw_dir_7/Namnam.mw &&
+       test_path_is_missing mw_dir_7/Nyancat.mw &&
+       wiki_getallpage mw_dir_7/page_ref_7 &&
+       test_diff_directories mw_dir_7 mw_dir_7/page_ref_7
+'
+
+
+test_expect_success 'Git clone works with one specific page cloned ' '
+       wiki_reset &&
+       wiki_editpage foo "I will not be cloned" false &&
+       wiki_editpage bar "Do not clone me" false &&
+       wiki_editpage namnam "I will be cloned :)" false  -s="this log must stay" &&
+       wiki_editpage nyancat "nyan nyan nyan you cant clone me" false &&
+       git clone -c remote.origin.pages=namnam \
+               mediawiki::'"$WIKI_URL"' mw_dir_8 &&
+       test_contains_N_files mw_dir_8 1 &&
+       test_path_is_file mw_dir_8/Namnam.mw &&
+       test_path_is_missing mw_dir_8/Main_Page.mw &&
+       (
+               cd mw_dir_8 &&
+               echo "this log must stay" >msg.tmp &&
+               git log --format=%s >log.tmp &&
+               diff -b msg.tmp log.tmp
+       ) &&
+       wiki_check_content mw_dir_8/Namnam.mw Namnam
+'
+
+test_expect_success 'Git clone works with multiple specific page cloned ' '
+       wiki_reset &&
+       wiki_editpage foo "I will be there" false &&
+       wiki_editpage bar "I will not disapear" false &&
+       wiki_editpage namnam "I be erased" false &&
+       wiki_editpage nyancat "nyan nyan nyan you will not erase me" false &&
+       wiki_delete_page namnam &&
+       git clone -c remote.origin.pages="foo bar nyancat namnam" \
+               mediawiki::'"$WIKI_URL"' mw_dir_9 &&
+       test_contains_N_files mw_dir_9 3 &&
+       test_path_is_missing mw_dir_9/Namnam.mw &&
+       test_path_is_file mw_dir_9/Foo.mw &&
+       test_path_is_file mw_dir_9/Nyancat.mw &&
+       test_path_is_file mw_dir_9/Bar.mw &&
+       wiki_check_content mw_dir_9/Foo.mw Foo &&
+       wiki_check_content mw_dir_9/Bar.mw Bar &&
+       wiki_check_content mw_dir_9/Nyancat.mw Nyancat
+'
+
+test_expect_success 'Mediawiki-clone of several specific pages on wiki' '
+       wiki_reset &&
+       wiki_editpage foo "foo 1" false &&
+       wiki_editpage bar "bar 1" false &&
+       wiki_editpage dummy "dummy 1" false &&
+       wiki_editpage cloned_1 "cloned_1 1" false &&
+       wiki_editpage cloned_2 "cloned_2 2" false &&
+       wiki_editpage cloned_3 "cloned_3 3" false &&
+       mkdir -p ref_page_10 &&
+       wiki_getpage cloned_1 ref_page_10 &&
+       wiki_getpage cloned_2 ref_page_10 &&
+       wiki_getpage cloned_3 ref_page_10 &&
+       git clone -c remote.origin.pages="cloned_1 cloned_2 cloned_3" \
+               mediawiki::'"$WIKI_URL"' mw_dir_10 &&
+       test_diff_directories mw_dir_10 ref_page_10
+'
+
+test_expect_success 'Git clone works with the shallow option' '
+       wiki_reset &&
+       wiki_editpage foo "1st revision, should be cloned" false &&
+       wiki_editpage bar "1st revision, should be cloned" false &&
+       wiki_editpage nyan "1st revision, should not be cloned" false &&
+       wiki_editpage nyan "2nd revision, should be cloned" false &&
+       git -c remote.origin.shallow=true clone \
+               mediawiki::'"$WIKI_URL"' mw_dir_11 &&
+       test_contains_N_files mw_dir_11 4 &&
+       test_path_is_file mw_dir_11/Nyan.mw &&
+       test_path_is_file mw_dir_11/Foo.mw &&
+       test_path_is_file mw_dir_11/Bar.mw &&
+       test_path_is_file mw_dir_11/Main_Page.mw &&
+       (
+               cd mw_dir_11 &&
+               test `git log --oneline Nyan.mw | wc -l` -eq 1 &&
+               test `git log --oneline Foo.mw | wc -l` -eq 1 &&
+               test `git log --oneline Bar.mw | wc -l` -eq 1 &&
+               test `git log --oneline Main_Page.mw | wc -l ` -eq 1
+       ) &&
+       wiki_check_content mw_dir_11/Nyan.mw Nyan &&
+       wiki_check_content mw_dir_11/Foo.mw Foo &&
+       wiki_check_content mw_dir_11/Bar.mw Bar &&
+       wiki_check_content mw_dir_11/Main_Page.mw Main_Page
+'
+
+test_expect_success 'Git clone works with the shallow option with a delete page' '
+       wiki_reset &&
+       wiki_editpage foo "1st revision, will be deleted" false &&
+       wiki_editpage bar "1st revision, should be cloned" false &&
+       wiki_editpage nyan "1st revision, should not be cloned" false &&
+       wiki_editpage nyan "2nd revision, should be cloned" false &&
+       wiki_delete_page foo &&
+       git -c remote.origin.shallow=true clone \
+               mediawiki::'"$WIKI_URL"' mw_dir_12 &&
+       test_contains_N_files mw_dir_12 3 &&
+       test_path_is_file mw_dir_12/Nyan.mw &&
+       test_path_is_missing mw_dir_12/Foo.mw &&
+       test_path_is_file mw_dir_12/Bar.mw &&
+       test_path_is_file mw_dir_12/Main_Page.mw &&
+       (
+               cd mw_dir_12 &&
+               test `git log --oneline Nyan.mw | wc -l` -eq 1 &&
+               test `git log --oneline Bar.mw | wc -l` -eq 1 &&
+               test `git log --oneline Main_Page.mw | wc -l ` -eq 1
+       ) &&
+       wiki_check_content mw_dir_12/Nyan.mw Nyan &&
+       wiki_check_content mw_dir_12/Bar.mw Bar &&
+       wiki_check_content mw_dir_12/Main_Page.mw Main_Page
+'
+
+test_expect_success 'Test of fetching a category' '
+       wiki_reset &&
+       wiki_editpage Foo "I will be cloned" false -c=Category &&
+       wiki_editpage Bar "Meet me on the repository" false -c=Category &&
+       wiki_editpage Dummy "I will not come" false &&
+       wiki_editpage BarWrong "I will stay online only" false -c=NotCategory &&
+       git clone -c remote.origin.categories="Category" \
+               mediawiki::'"$WIKI_URL"' mw_dir_13 &&
+       wiki_getallpage ref_page_13 Category &&
+       test_diff_directories mw_dir_13 ref_page_13
+'
+
+test_expect_success 'Test of resistance to modification of category on wiki for clone' '
+       wiki_reset &&
+       wiki_editpage Tobedeleted "this page will be deleted" false -c=Catone &&
+       wiki_editpage Tobeedited "this page will be modified" false -c=Catone &&
+       wiki_editpage Normalone "this page wont be modified and will be on git" false -c=Catone &&
+       wiki_editpage Notconsidered "this page will not appear on local" false &&
+       wiki_editpage Othercategory "this page will not appear on local" false -c=Cattwo &&
+       wiki_editpage Tobeedited "this page have been modified" true -c=Catone &&
+       wiki_delete_page Tobedeleted
+       git clone -c remote.origin.categories="Catone" \
+               mediawiki::'"$WIKI_URL"' mw_dir_14 &&
+       wiki_getallpage ref_page_14 Catone &&
+       test_diff_directories mw_dir_14 ref_page_14
+'
+
+test_done
diff --git a/contrib/mw-to-git/t/t9361-mw-to-git-push-pull.sh b/contrib/mw-to-git/t/t9361-mw-to-git-push-pull.sh
new file mode 100755 (executable)
index 0000000..9ea2014
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# Copyright (C) 2012
+#     Charles Roussel <charles.roussel@ensimag.imag.fr>
+#     Simon Cathebras <simon.cathebras@ensimag.imag.fr>
+#     Julien Khayat <julien.khayat@ensimag.imag.fr>
+#     Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
+#     Simon Perrat <simon.perrat@ensimag.imag.fr>
+#
+# License: GPL v2 or later
+
+# tests for git-remote-mediawiki
+
+test_description='Test the Git Mediawiki remote helper: git push and git pull simple test cases'
+
+. ./test-gitmw-lib.sh
+. ./push-pull-tests.sh
+. $TEST_DIRECTORY/test-lib.sh
+
+test_check_precond
+
+test_push_pull
+
+test_done
diff --git a/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh b/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh
new file mode 100755 (executable)
index 0000000..246d47d
--- /dev/null
@@ -0,0 +1,321 @@
+#!/bin/sh
+#
+# Copyright (C) 2012
+#     Charles Roussel <charles.roussel@ensimag.imag.fr>
+#     Simon Cathebras <simon.cathebras@ensimag.imag.fr>
+#     Julien Khayat <julien.khayat@ensimag.imag.fr>
+#     Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
+#     Simon Perrat <simon.perrat@ensimag.imag.fr>
+#
+# License: GPL v2 or later
+
+# tests for git-remote-mediawiki
+
+test_description='Test git-mediawiki with special characters in filenames'
+
+. ./test-gitmw-lib.sh
+. $TEST_DIRECTORY/test-lib.sh
+
+
+test_check_precond
+
+
+test_expect_success 'Git clone works for a wiki with accents in the page names' '
+       wiki_reset &&
+       wiki_editpage féé "This page must be délétéd before clone" false &&
+       wiki_editpage kèè "This page must be deleted before clone" false &&
+       wiki_editpage hàà "This page must be deleted before clone" false &&
+       wiki_editpage kîî "This page must be deleted before clone" false &&
+       wiki_editpage foo "This page must be deleted before clone" false &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_1 &&
+       wiki_getallpage ref_page_1 &&
+       test_diff_directories mw_dir_1 ref_page_1
+'
+
+
+test_expect_success 'Git pull works with a wiki with accents in the pages names' '
+       wiki_reset &&
+       wiki_editpage kîî "this page must be cloned" false &&
+       wiki_editpage foo "this page must be cloned" false &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_2 &&
+       wiki_editpage éàîôû "This page must be pulled" false &&
+       (
+               cd mw_dir_2 &&
+               git pull
+       ) &&
+       wiki_getallpage ref_page_2 &&
+       test_diff_directories mw_dir_2 ref_page_2
+'
+
+
+test_expect_success 'Cloning a chosen page works with accents' '
+       wiki_reset &&
+       wiki_editpage kîî "this page must be cloned" false &&
+       git clone -c remote.origin.pages=kîî \
+               mediawiki::'"$WIKI_URL"' mw_dir_3 &&
+       wiki_check_content mw_dir_3/Kîî.mw Kîî &&
+       test_path_is_file mw_dir_3/Kîî.mw &&
+       rm -rf mw_dir_3
+'
+
+
+test_expect_success 'The shallow option works with accents' '
+       wiki_reset &&
+       wiki_editpage néoà "1st revision, should not be cloned" false &&
+       wiki_editpage néoà "2nd revision, should be cloned" false &&
+       git -c remote.origin.shallow=true clone \
+               mediawiki::'"$WIKI_URL"' mw_dir_4 &&
+       test_contains_N_files mw_dir_4 2 &&
+       test_path_is_file mw_dir_4/Néoà.mw &&
+       test_path_is_file mw_dir_4/Main_Page.mw &&
+       (
+               cd mw_dir_4 &&
+               test `git log --oneline Néoà.mw | wc -l` -eq 1 &&
+               test `git log --oneline Main_Page.mw | wc -l ` -eq 1
+       ) &&
+       wiki_check_content mw_dir_4/Néoà.mw Néoà &&
+       wiki_check_content mw_dir_4/Main_Page.mw Main_Page
+'
+
+
+test_expect_success 'Cloning works when page name first letter has an accent' '
+       wiki_reset &&
+       wiki_editpage îî "this page must be cloned" false &&
+       git clone -c remote.origin.pages=îî \
+               mediawiki::'"$WIKI_URL"' mw_dir_5 &&
+       test_path_is_file mw_dir_5/Îî.mw &&
+       wiki_check_content mw_dir_5/Îî.mw Îî
+'
+
+
+test_expect_success 'Git push works with a wiki with accents' '
+       wiki_reset &&
+       wiki_editpage féé "lots of accents : éèàÖ" false &&
+       wiki_editpage foo "this page must be cloned" false &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_6 &&
+       (
+               cd mw_dir_6 &&
+               echo "A wild Pîkächû appears on the wiki" >Pîkächû.mw &&
+               git add Pîkächû.mw &&
+               git commit -m "A new page appears" &&
+               git push
+       ) &&
+       wiki_getallpage ref_page_6 &&
+       test_diff_directories mw_dir_6 ref_page_6
+'
+
+test_expect_success 'Git clone works with accentsand spaces' '
+       wiki_reset &&
+       wiki_editpage "é à î" "this page must be délété before the clone" false &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_7 &&
+       wiki_getallpage ref_page_7 &&
+       test_diff_directories mw_dir_7 ref_page_7
+'
+
+test_expect_success 'character $ in page name (mw -> git)' '
+       wiki_reset &&
+       wiki_editpage file_\$_foo "expect to be called file_$_foo" false &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_8 &&
+       test_path_is_file mw_dir_8/File_\$_foo.mw &&
+       wiki_getallpage ref_page_8 &&
+       test_diff_directories mw_dir_8 ref_page_8
+'
+
+
+
+test_expect_success 'character $ in file name (git -> mw) ' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_9 &&
+       (
+               cd mw_dir_9 &&
+               echo "this file is called File_\$_foo.mw" >File_\$_foo.mw &&
+               git add . &&
+               git commit -am "file File_\$_foo.mw" &&
+               git pull &&
+               git push
+       ) &&
+       wiki_getallpage ref_page_9 &&
+       test_diff_directories mw_dir_9 ref_page_9
+'
+
+
+test_expect_failure 'capital at the begining of file names' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_10 &&
+       (
+               cd mw_dir_10 &&
+               echo "my new file foo" >foo.mw &&
+               echo "my new file Foo... Finger crossed" >Foo.mw &&
+               git add . &&
+               git commit -am "file foo.mw" &&
+               git pull &&
+               git push
+       ) &&
+       wiki_getallpage ref_page_10 &&
+       test_diff_directories mw_dir_10 ref_page_10
+'
+
+
+test_expect_failure 'special character at the begining of file name from mw to git' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_11 &&
+       wiki_editpage {char_1 "expect to be renamed {char_1" false &&
+       wiki_editpage [char_2 "expect to be renamed [char_2" false &&
+       (
+               cd mw_dir_11 &&
+               git pull
+       ) &&
+       test_path_is_file mw_dir_11/{char_1 &&
+       test_path_is_file mw_dir_11/[char_2
+'
+
+test_expect_success 'Pull page with title containing ":" other than namespace separator' '
+       wiki_editpage Foo:Bar content false &&
+       (
+               cd mw_dir_11 &&
+               git pull
+       ) &&
+       test_path_is_file mw_dir_11/Foo:Bar.mw
+'
+
+test_expect_success 'Push page with title containing ":" other than namespace separator' '
+       (
+               cd mw_dir_11 &&
+               echo content >NotANameSpace:Page.mw &&
+               git add NotANameSpace:Page.mw &&
+               git commit -m "add page with colon" &&
+               git push
+       ) &&
+       wiki_page_exist NotANameSpace:Page
+'
+
+test_expect_success 'test of correct formating for file name from mw to git' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_12 &&
+       wiki_editpage char_%_7b_1 "expect to be renamed char{_1" false &&
+       wiki_editpage char_%_5b_2 "expect to be renamed char{_2" false &&
+       (
+               cd mw_dir_12 &&
+               git pull
+       ) &&
+       test_path_is_file mw_dir_12/Char\{_1.mw &&
+       test_path_is_file mw_dir_12/Char\[_2.mw &&
+       wiki_getallpage ref_page_12 &&
+       mv ref_page_12/Char_%_7b_1.mw ref_page_12/Char\{_1.mw &&
+       mv ref_page_12/Char_%_5b_2.mw ref_page_12/Char\[_2.mw &&
+       test_diff_directories mw_dir_12 ref_page_12
+'
+
+
+test_expect_failure 'test of correct formating for file name begining with special character' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_13 &&
+       (
+               cd mw_dir_13 &&
+               echo "my new file {char_1" >\{char_1.mw &&
+               echo "my new file [char_2" >\[char_2.mw &&
+               git add . &&
+               git commit -am "commiting some exotic file name..." &&
+               git push &&
+               git pull
+       ) &&
+       wiki_getallpage ref_page_13 &&
+       test_path_is_file ref_page_13/{char_1.mw &&
+       test_path_is_file ref_page_13/[char_2.mw &&
+       test_diff_directories mw_dir_13 ref_page_13
+'
+
+
+test_expect_success 'test of correct formating for file name from git to mw' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_14 &&
+       (
+               cd mw_dir_14 &&
+               echo "my new file char{_1" >Char\{_1.mw &&
+               echo "my new file char[_2" >Char\[_2.mw &&
+               git add . &&
+               git commit -m "commiting some exotic file name..." &&
+               git push
+       ) &&
+       wiki_getallpage ref_page_14 &&
+       mv mw_dir_14/Char\{_1.mw mw_dir_14/Char_%_7b_1.mw &&
+       mv mw_dir_14/Char\[_2.mw mw_dir_14/Char_%_5b_2.mw &&
+       test_diff_directories mw_dir_14 ref_page_14
+'
+
+
+test_expect_success 'git clone with /' '
+       wiki_reset &&
+       wiki_editpage \/fo\/o "this is not important" false -c=Deleted &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_15 &&
+       test_path_is_file mw_dir_15/%2Ffo%2Fo.mw &&
+       wiki_check_content mw_dir_15/%2Ffo%2Fo.mw \/fo\/o
+'
+
+
+test_expect_success 'git push with /' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_16 &&
+       echo "I will be on the wiki" >mw_dir_16/%2Ffo%2Fo.mw &&
+       (
+               cd mw_dir_16 &&
+               git add %2Ffo%2Fo.mw &&
+               git commit -m " %2Ffo%2Fo added" &&
+               git push
+       ) &&
+       wiki_page_exist \/fo\/o &&
+       wiki_check_content mw_dir_16/%2Ffo%2Fo.mw \/fo\/o
+
+'
+
+
+test_expect_success 'git clone with \' '
+       wiki_reset &&
+       wiki_editpage \\ko\\o "this is not important" false -c=Deleted &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_17 &&
+       test_path_is_file mw_dir_17/\\ko\\o.mw &&
+       wiki_check_content mw_dir_17/\\ko\\o.mw \\ko\\o
+'
+
+
+test_expect_success 'git push with \' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_18 &&
+       echo "I will be on the wiki" >mw_dir_18/\\ko\\o.mw &&
+       (
+               cd mw_dir_18 &&
+               git add \\ko\\o.mw &&
+               git commit -m " \\ko\\o added" &&
+               git push
+       )&&
+       wiki_page_exist \\ko\\o &&
+       wiki_check_content mw_dir_18/\\ko\\o.mw \\ko\\o
+
+'
+
+test_expect_success 'git clone with \ in format control' '
+       wiki_reset &&
+       wiki_editpage \\no\\o "this is not important" false &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_19 &&
+       test_path_is_file mw_dir_19/\\no\\o.mw &&
+       wiki_check_content mw_dir_19/\\no\\o.mw \\no\\o
+'
+
+
+test_expect_success 'git push with \ in format control' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_20 &&
+       echo "I will be on the wiki" >mw_dir_20/\\fo\\o.mw &&
+       (
+               cd mw_dir_20 &&
+               git add \\fo\\o.mw &&
+               git commit -m " \\fo\\o added" &&
+               git push
+       )&&
+       wiki_page_exist \\fo\\o &&
+       wiki_check_content mw_dir_20/\\fo\\o.mw \\fo\\o
+
+'
+
+
+test_done
diff --git a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh
new file mode 100755 (executable)
index 0000000..5a03739
--- /dev/null
@@ -0,0 +1,198 @@
+#!/bin/sh
+#
+# Copyright (C) 2012
+#     Charles Roussel <charles.roussel@ensimag.imag.fr>
+#     Simon Cathebras <simon.cathebras@ensimag.imag.fr>
+#     Julien Khayat <julien.khayat@ensimag.imag.fr>
+#     Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
+#     Simon Perrat <simon.perrat@ensimag.imag.fr>
+#
+# License: GPL v2 or later
+
+# tests for git-remote-mediawiki
+
+test_description='Test the Git Mediawiki remote helper: git push and git pull simple test cases'
+
+. ./test-gitmw-lib.sh
+. $TEST_DIRECTORY/test-lib.sh
+
+
+test_check_precond
+
+
+test_git_reimport () {
+       git -c remote.origin.dumbPush=true push &&
+       git -c remote.origin.mediaImport=true pull --rebase
+}
+
+# Don't bother with permissions, be administrator by default
+test_expect_success 'setup config' '
+       git config --global remote.origin.mwLogin WikiAdmin &&
+       git config --global remote.origin.mwPassword AdminPass &&
+       test_might_fail git config --global --unset remote.origin.mediaImport
+'
+
+test_expect_success 'git push can upload media (File:) files' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir &&
+       (
+               cd mw_dir &&
+               echo "hello world" >Foo.txt &&
+               git add Foo.txt &&
+               git commit -m "add a text file" &&
+               git push &&
+               "$PERL_PATH" -e "print STDOUT \"binary content: \".chr(255);" >Foo.txt &&
+               git add Foo.txt &&
+               git commit -m "add a text file with binary content" &&
+               git push
+       )
+'
+
+test_expect_success 'git clone works on previously created wiki with media files' '
+       test_when_finished "rm -rf mw_dir mw_dir_clone" &&
+       git clone -c remote.origin.mediaimport=true \
+               mediawiki::'"$WIKI_URL"' mw_dir_clone &&
+       test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt &&
+       (cd mw_dir_clone && git checkout HEAD^) &&
+       (cd mw_dir && git checkout HEAD^) &&
+       test_cmp mw_dir_clone/Foo.txt mw_dir/Foo.txt
+'
+
+test_expect_success 'git push & pull work with locally renamed media files' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir &&
+       test_when_finished "rm -fr mw_dir" &&
+       (
+               cd mw_dir &&
+               echo "A File" >Foo.txt &&
+               git add Foo.txt &&
+               git commit -m "add a file" &&
+               git mv Foo.txt Bar.txt &&
+               git commit -m "Rename a file" &&
+               test_git_reimport &&
+               echo "A File" >expect &&
+               test_cmp expect Bar.txt &&
+               test_path_is_missing Foo.txt
+       )
+'
+
+test_expect_success 'git push can propagate local page deletion' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir &&
+       test_when_finished "rm -fr mw_dir" &&
+       (
+               cd mw_dir &&
+               test_path_is_missing Foo.mw &&
+               echo "hello world" >Foo.mw &&
+               git add Foo.mw &&
+               git commit -m "Add the page Foo" &&
+               git push &&
+               rm -f Foo.mw &&
+               git commit -am "Delete the page Foo" &&
+               test_git_reimport &&
+               test_path_is_missing Foo.mw
+       )
+'
+
+test_expect_success 'git push can propagate local media file deletion' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir &&
+       test_when_finished "rm -fr mw_dir" &&
+       (
+               cd mw_dir &&
+               echo "hello world" >Foo.txt &&
+               git add Foo.txt &&
+               git commit -m "Add the text file Foo" &&
+               git rm Foo.txt &&
+               git commit -m "Delete the file Foo" &&
+               test_git_reimport &&
+               test_path_is_missing Foo.txt
+       )
+'
+
+# test failure: the file is correctly uploaded, and then deleted but
+# as no page link to it, the import (which looks at page revisions)
+# doesn't notice the file deletion on the wiki. We fetch the list of
+# files from the wiki, but as the file is deleted, it doesn't appear.
+test_expect_failure 'git pull correctly imports media file deletion when no page link to it' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir &&
+       test_when_finished "rm -fr mw_dir" &&
+       (
+               cd mw_dir &&
+               echo "hello world" >Foo.txt &&
+               git add Foo.txt &&
+               git commit -m "Add the text file Foo" &&
+               git push &&
+               git rm Foo.txt &&
+               git commit -m "Delete the file Foo" &&
+               test_git_reimport &&
+               test_path_is_missing Foo.txt
+       )
+'
+
+test_expect_success 'git push properly warns about insufficient permissions' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir &&
+       test_when_finished "rm -fr mw_dir" &&
+       (
+               cd mw_dir &&
+               echo "A File" >foo.forbidden &&
+               git add foo.forbidden &&
+               git commit -m "add a file" &&
+               git push 2>actual &&
+               test_i18ngrep "foo.forbidden is not a permitted file" actual
+       )
+'
+
+test_expect_success 'setup a repository with media files' '
+       wiki_reset &&
+       wiki_editpage testpage "I am linking a file [[File:File.txt]]" false &&
+       echo "File content" >File.txt &&
+       wiki_upload_file File.txt &&
+       echo "Another file content" >AnotherFile.txt &&
+       wiki_upload_file AnotherFile.txt
+'
+
+test_expect_success 'git clone works with one specific page cloned and mediaimport=true' '
+       git clone -c remote.origin.pages=testpage \
+                 -c remote.origin.mediaimport=true \
+                       mediawiki::'"$WIKI_URL"' mw_dir_15 &&
+       test_when_finished "rm -rf mw_dir_15" &&
+       test_contains_N_files mw_dir_15 3 &&
+       test_path_is_file mw_dir_15/Testpage.mw &&
+       test_path_is_file mw_dir_15/File:File.txt.mw &&
+       test_path_is_file mw_dir_15/File.txt &&
+       test_path_is_missing mw_dir_15/Main_Page.mw &&
+       test_path_is_missing mw_dir_15/File:AnotherFile.txt.mw &&
+       test_path_is_missing mw_dir_15/AnothetFile.txt &&
+       wiki_check_content mw_dir_15/Testpage.mw Testpage &&
+       test_cmp mw_dir_15/File.txt File.txt
+'
+
+test_expect_success 'git clone works with one specific page cloned and mediaimport=false' '
+       test_when_finished "rm -rf mw_dir_16" &&
+       git clone -c remote.origin.pages=testpage \
+                       mediawiki::'"$WIKI_URL"' mw_dir_16 &&
+       test_contains_N_files mw_dir_16 1 &&
+       test_path_is_file mw_dir_16/Testpage.mw &&
+       test_path_is_missing mw_dir_16/File:File.txt.mw &&
+       test_path_is_missing mw_dir_16/File.txt &&
+       test_path_is_missing mw_dir_16/Main_Page.mw &&
+       wiki_check_content mw_dir_16/Testpage.mw Testpage
+'
+
+# should behave like mediaimport=false
+test_expect_success 'git clone works with one specific page cloned and mediaimport unset' '
+       test_when_finished "rm -fr mw_dir_17" &&
+       git clone -c remote.origin.pages=testpage \
+               mediawiki::'"$WIKI_URL"' mw_dir_17 &&
+       test_contains_N_files mw_dir_17 1 &&
+       test_path_is_file mw_dir_17/Testpage.mw &&
+       test_path_is_missing mw_dir_17/File:File.txt.mw &&
+       test_path_is_missing mw_dir_17/File.txt &&
+       test_path_is_missing mw_dir_17/Main_Page.mw &&
+       wiki_check_content mw_dir_17/Testpage.mw Testpage
+'
+
+test_done
diff --git a/contrib/mw-to-git/t/t9364-pull-by-rev.sh b/contrib/mw-to-git/t/t9364-pull-by-rev.sh
new file mode 100755 (executable)
index 0000000..5c22457
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test_description='Test the Git Mediawiki remote helper: git pull by revision'
+
+. ./test-gitmw-lib.sh
+. ./push-pull-tests.sh
+. $TEST_DIRECTORY/test-lib.sh
+
+test_check_precond
+
+test_expect_success 'configuration' '
+       git config --global mediawiki.fetchStrategy by_rev
+'
+
+test_push_pull
+
+test_done
diff --git a/contrib/mw-to-git/t/test-gitmw-lib.sh b/contrib/mw-to-git/t/test-gitmw-lib.sh
new file mode 100755 (executable)
index 0000000..3b2cfac
--- /dev/null
@@ -0,0 +1,435 @@
+# Copyright (C) 2012
+#     Charles Roussel <charles.roussel@ensimag.imag.fr>
+#     Simon Cathebras <simon.cathebras@ensimag.imag.fr>
+#     Julien Khayat <julien.khayat@ensimag.imag.fr>
+#     Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
+#     Simon Perrat <simon.perrat@ensimag.imag.fr>
+# License: GPL v2 or later
+
+#
+# CONFIGURATION VARIABLES
+# You might want to change these ones
+#
+
+. ./test.config
+
+WIKI_URL=http://"$SERVER_ADDR:$PORT/$WIKI_DIR_NAME"
+CURR_DIR=$(pwd)
+TEST_OUTPUT_DIRECTORY=$(pwd)
+TEST_DIRECTORY="$CURR_DIR"/../../../t
+
+export TEST_OUTPUT_DIRECTORY TEST_DIRECTORY CURR_DIR
+
+if test "$LIGHTTPD" = "false" ; then
+       PORT=80
+else
+       WIKI_DIR_INST="$CURR_DIR/$WEB_WWW"
+fi
+
+wiki_upload_file () {
+       "$CURR_DIR"/test-gitmw.pl upload_file "$@"
+}
+
+wiki_getpage () {
+       "$CURR_DIR"/test-gitmw.pl get_page "$@"
+}
+
+wiki_delete_page () {
+       "$CURR_DIR"/test-gitmw.pl delete_page "$@"
+}
+
+wiki_editpage () {
+       "$CURR_DIR"/test-gitmw.pl edit_page "$@"
+}
+
+die () {
+       die_with_status 1 "$@"
+}
+
+die_with_status () {
+       status=$1
+       shift
+       echo >&2 "$*"
+       exit "$status"
+}
+
+
+# Check the preconditions to run git-remote-mediawiki's tests
+test_check_precond () {
+       if ! test_have_prereq PERL
+       then
+               skip_all='skipping gateway git-mw tests, perl not available'
+               test_done
+       fi
+
+       if [ ! -f "$GIT_BUILD_DIR"/git-remote-mediawiki ];
+       then
+               echo "No remote mediawiki for git found. Copying it in git"
+               echo "cp $GIT_BUILD_DIR/contrib/mw-to-git/git-remote-mediawiki $GIT_BUILD_DIR/"
+               ln -s "$GIT_BUILD_DIR"/contrib/mw-to-git/git-remote-mediawiki "$GIT_BUILD_DIR"
+       fi
+
+       if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ];
+       then
+               skip_all='skipping gateway git-mw tests, no mediawiki found'
+               test_done
+       fi
+}
+
+# test_diff_directories <dir_git> <dir_wiki>
+#
+# Compare the contents of directories <dir_git> and <dir_wiki> with diff
+# and errors if they do not match. The program will
+# not look into .git in the process.
+# Warning: the first argument MUST be the directory containing the git data
+test_diff_directories () {
+       rm -rf "$1_tmp"
+       mkdir -p "$1_tmp"
+       cp "$1"/*.mw "$1_tmp"
+       diff -r -b "$1_tmp" "$2"
+}
+
+# $1=<dir>
+# $2=<N>
+#
+# Check that <dir> contains exactly <N> files
+test_contains_N_files () {
+       if test `ls -- "$1" | wc -l` -ne "$2"; then
+               echo "directory $1 sould contain $2 files"
+               echo "it contains these files:"
+               ls "$1"
+               false
+       fi
+}
+
+
+# wiki_check_content <file_name> <page_name>
+#
+# Compares the contents of the file <file_name> and the wiki page
+# <page_name> and exits with error 1 if they do not match.
+wiki_check_content () {
+       mkdir -p wiki_tmp
+       wiki_getpage "$2" wiki_tmp
+       # replacement of forbidden character in file name
+       page_name=$(printf "%s\n" "$2" | sed -e "s/\//%2F/g")
+
+       diff -b "$1" wiki_tmp/"$page_name".mw
+       if test $? -ne 0
+       then
+               rm -rf wiki_tmp
+               error "ERROR: file $2 not found on wiki"
+       fi
+       rm -rf wiki_tmp
+}
+
+# wiki_page_exist <page_name>
+#
+# Check the existence of the page <page_name> on the wiki and exits
+# with error if it is absent from it.
+wiki_page_exist () {
+       mkdir -p wiki_tmp
+       wiki_getpage "$1" wiki_tmp
+       page_name=$(printf "%s\n" "$1" | sed "s/\//%2F/g")
+       if test -f wiki_tmp/"$page_name".mw ; then
+               rm -rf wiki_tmp
+       else
+               rm -rf wiki_tmp
+               error "test failed: file $1 not found on wiki"
+       fi
+}
+
+# wiki_getallpagename
+#
+# Fetch the name of each page on the wiki.
+wiki_getallpagename () {
+       "$CURR_DIR"/test-gitmw.pl getallpagename
+}
+
+# wiki_getallpagecategory <category>
+#
+# Fetch the name of each page belonging to <category> on the wiki.
+wiki_getallpagecategory () {
+       "$CURR_DIR"/test-gitmw.pl getallpagename "$@"
+}
+
+# wiki_getallpage <dest_dir> [<category>]
+#
+# Fetch all the pages from the wiki and place them in the directory
+# <dest_dir>.
+# If <category> is define, then wiki_getallpage fetch the pages included
+# in <category>.
+wiki_getallpage () {
+       if test -z "$2";
+       then
+               wiki_getallpagename
+       else
+               wiki_getallpagecategory "$2"
+       fi
+       mkdir -p "$1"
+       while read -r line; do
+               wiki_getpage "$line" $1;
+       done < all.txt
+}
+
+# ================= Install part =================
+
+error () {
+       echo "$@" >&2
+       exit 1
+}
+
+# config_lighttpd
+#
+# Create the configuration files and the folders necessary to start lighttpd.
+# Overwrite any existing file.
+config_lighttpd () {
+       mkdir -p $WEB
+       mkdir -p $WEB_TMP
+       mkdir -p $WEB_WWW
+       cat > $WEB/lighttpd.conf <<EOF
+       server.document-root = "$CURR_DIR/$WEB_WWW"
+       server.port = $PORT
+       server.pid-file = "$CURR_DIR/$WEB_TMP/pid"
+
+       server.modules = (
+       "mod_rewrite",
+       "mod_redirect",
+       "mod_access",
+       "mod_accesslog",
+       "mod_fastcgi"
+       )
+
+       index-file.names = ("index.php" , "index.html")
+
+       mimetype.assign             = (
+       ".pdf"          =>      "application/pdf",
+       ".sig"          =>      "application/pgp-signature",
+       ".spl"          =>      "application/futuresplash",
+       ".class"        =>      "application/octet-stream",
+       ".ps"           =>      "application/postscript",
+       ".torrent"      =>      "application/x-bittorrent",
+       ".dvi"          =>      "application/x-dvi",
+       ".gz"           =>      "application/x-gzip",
+       ".pac"          =>      "application/x-ns-proxy-autoconfig",
+       ".swf"          =>      "application/x-shockwave-flash",
+       ".tar.gz"       =>      "application/x-tgz",
+       ".tgz"          =>      "application/x-tgz",
+       ".tar"          =>      "application/x-tar",
+       ".zip"          =>      "application/zip",
+       ".mp3"          =>      "audio/mpeg",
+       ".m3u"          =>      "audio/x-mpegurl",
+       ".wma"          =>      "audio/x-ms-wma",
+       ".wax"          =>      "audio/x-ms-wax",
+       ".ogg"          =>      "application/ogg",
+       ".wav"          =>      "audio/x-wav",
+       ".gif"          =>      "image/gif",
+       ".jpg"          =>      "image/jpeg",
+       ".jpeg"         =>      "image/jpeg",
+       ".png"          =>      "image/png",
+       ".xbm"          =>      "image/x-xbitmap",
+       ".xpm"          =>      "image/x-xpixmap",
+       ".xwd"          =>      "image/x-xwindowdump",
+       ".css"          =>      "text/css",
+       ".html"         =>      "text/html",
+       ".htm"          =>      "text/html",
+       ".js"           =>      "text/javascript",
+       ".asc"          =>      "text/plain",
+       ".c"            =>      "text/plain",
+       ".cpp"          =>      "text/plain",
+       ".log"          =>      "text/plain",
+       ".conf"         =>      "text/plain",
+       ".text"         =>      "text/plain",
+       ".txt"          =>      "text/plain",
+       ".dtd"          =>      "text/xml",
+       ".xml"          =>      "text/xml",
+       ".mpeg"         =>      "video/mpeg",
+       ".mpg"          =>      "video/mpeg",
+       ".mov"          =>      "video/quicktime",
+       ".qt"           =>      "video/quicktime",
+       ".avi"          =>      "video/x-msvideo",
+       ".asf"          =>      "video/x-ms-asf",
+       ".asx"          =>      "video/x-ms-asf",
+       ".wmv"          =>      "video/x-ms-wmv",
+       ".bz2"          =>      "application/x-bzip",
+       ".tbz"          =>      "application/x-bzip-compressed-tar",
+       ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
+       ""              =>      "text/plain"
+       )
+
+       fastcgi.server = ( ".php" =>
+       ("localhost" =>
+       ( "socket" => "$CURR_DIR/$WEB_TMP/php.socket",
+       "bin-path" => "$PHP_DIR/php-cgi -c $CURR_DIR/$WEB/php.ini"
+
+       )
+       )
+       )
+EOF
+
+       cat > $WEB/php.ini <<EOF
+       session.save_path ='$CURR_DIR/$WEB_TMP'
+EOF
+}
+
+# start_lighttpd
+#
+# Start or restart daemon lighttpd. If restart, rewrite configuration files.
+start_lighttpd () {
+       if test -f "$WEB_TMP/pid"; then
+               echo "Instance already running. Restarting..."
+               stop_lighttpd
+       fi
+       config_lighttpd
+       "$LIGHTTPD_DIR"/lighttpd -f "$WEB"/lighttpd.conf
+
+       if test $? -ne 0 ; then
+               echo "Could not execute http deamon lighttpd"
+               exit 1
+       fi
+}
+
+# stop_lighttpd
+#
+# Kill daemon lighttpd and removes files and folders associated.
+stop_lighttpd () {
+       test -f "$WEB_TMP/pid" && kill $(cat "$WEB_TMP/pid")
+       rm -rf "$WEB"
+}
+
+# Create the SQLite database of the MediaWiki. If the database file already
+# exists, it will be deleted.
+# This script should be runned from the directory where $FILES_FOLDER is
+# located.
+create_db () {
+       rm -f "$TMP/$DB_FILE"
+
+       echo "Generating the SQLite database file. It can take some time ..."
+       # Run the php script to generate the SQLite database file
+       # with cURL calls.
+       php "$FILES_FOLDER/$DB_INSTALL_SCRIPT" $(basename "$DB_FILE" .sqlite) \
+               "$WIKI_ADMIN" "$WIKI_PASSW" "$TMP" "$PORT"
+
+       if [ ! -f "$TMP/$DB_FILE" ] ; then
+               error "Can't create database file $TMP/$DB_FILE. Try to run ./install-wiki.sh delete first."
+       fi
+
+       # Copy the generated database file into the directory the
+       # user indicated.
+       cp "$TMP/$DB_FILE" "$FILES_FOLDER" ||
+               error "Unable to copy $TMP/$DB_FILE to $FILES_FOLDER"
+}
+
+# Install a wiki in your web server directory.
+wiki_install () {
+       if test $LIGHTTPD = "true" ; then
+               start_lighttpd
+       fi
+
+       SERVER_ADDR=$SERVER_ADDR:$PORT
+       # In this part, we change directory to $TMP in order to download,
+       # unpack and copy the files of MediaWiki
+       (
+       mkdir -p "$WIKI_DIR_INST/$WIKI_DIR_NAME"
+       if [ ! -d "$WIKI_DIR_INST/$WIKI_DIR_NAME" ] ; then
+               error "Folder $WIKI_DIR_INST/$WIKI_DIR_NAME doesn't exist.
+               Please create it and launch the script again."
+       fi
+
+       # Fetch MediaWiki's archive if not already present in the TMP directory
+       cd "$TMP"
+       if [ ! -f "$MW_VERSION.tar.gz" ] ; then
+               echo "Downloading $MW_VERSION sources ..."
+               wget "http://download.wikimedia.org/mediawiki/1.19/mediawiki-1.19.0.tar.gz" ||
+                       error "Unable to download "\
+                       "http://download.wikimedia.org/mediawiki/1.19/"\
+                       "mediawiki-1.19.0.tar.gz. "\
+                       "Please fix your connection and launch the script again."
+               echo "$MW_VERSION.tar.gz downloaded in `pwd`. "\
+                       "You can delete it later if you want."
+       else
+               echo "Reusing existing $MW_VERSION.tar.gz downloaded in `pwd`."
+       fi
+       archive_abs_path=$(pwd)/"$MW_VERSION.tar.gz"
+       cd "$WIKI_DIR_INST/$WIKI_DIR_NAME/" ||
+               error "can't cd to $WIKI_DIR_INST/$WIKI_DIR_NAME/"
+       tar xzf "$archive_abs_path" --strip-components=1 ||
+               error "Unable to extract WikiMedia's files from $archive_abs_path to "\
+                       "$WIKI_DIR_INST/$WIKI_DIR_NAME"
+       ) || exit 1
+
+       create_db
+
+       # Copy the generic LocalSettings.php in the web server's directory
+       # And modify parameters according to the ones set at the top
+       # of this script.
+       # Note that LocalSettings.php is never modified.
+       if [ ! -f "$FILES_FOLDER/LocalSettings.php" ] ; then
+               error "Can't find $FILES_FOLDER/LocalSettings.php " \
+                       "in the current folder. "\
+               "Please run the script inside its folder."
+       fi
+       cp "$FILES_FOLDER/LocalSettings.php" \
+               "$FILES_FOLDER/LocalSettings-tmp.php" ||
+               error "Unable to copy $FILES_FOLDER/LocalSettings.php " \
+               "to $FILES_FOLDER/LocalSettings-tmp.php"
+
+       # Parse and set the LocalSettings file of the user according to the
+       # CONFIGURATION VARIABLES section at the beginning of this script
+       file_swap="$FILES_FOLDER/LocalSettings-swap.php"
+       sed "s,@WG_SCRIPT_PATH@,/$WIKI_DIR_NAME," \
+               "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
+       mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
+       sed "s,@WG_SERVER@,http://$SERVER_ADDR," \
+               "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
+       mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
+       sed "s,@WG_SQLITE_DATADIR@,$TMP," \
+               "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
+       mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
+       sed "s,@WG_SQLITE_DATAFILE@,$( basename $DB_FILE .sqlite)," \
+               "$FILES_FOLDER/LocalSettings-tmp.php" > "$file_swap"
+       mv "$file_swap" "$FILES_FOLDER/LocalSettings-tmp.php"
+
+       mv "$FILES_FOLDER/LocalSettings-tmp.php" \
+               "$WIKI_DIR_INST/$WIKI_DIR_NAME/LocalSettings.php" ||
+               error "Unable to move $FILES_FOLDER/LocalSettings-tmp.php" \
+               "in $WIKI_DIR_INST/$WIKI_DIR_NAME"
+       echo "File $FILES_FOLDER/LocalSettings.php is set in" \
+               " $WIKI_DIR_INST/$WIKI_DIR_NAME"
+
+       echo "Your wiki has been installed. You can check it at
+               http://$SERVER_ADDR/$WIKI_DIR_NAME"
+}
+
+# Reset the database of the wiki and the password of the admin
+#
+# Warning: This function must be called only in a subdirectory of t/ directory
+wiki_reset () {
+       # Copy initial database of the wiki
+       if [ ! -f "../$FILES_FOLDER/$DB_FILE" ] ; then
+               error "Can't find ../$FILES_FOLDER/$DB_FILE in the current folder."
+       fi
+       cp "../$FILES_FOLDER/$DB_FILE" "$TMP" ||
+               error "Can't copy ../$FILES_FOLDER/$DB_FILE in $TMP"
+       echo "File $FILES_FOLDER/$DB_FILE is set in $TMP"
+}
+
+# Delete the wiki created in the web server's directory and all its content
+# saved in the database.
+wiki_delete () {
+       if test $LIGHTTPD = "true"; then
+               stop_lighttpd
+       else
+               # Delete the wiki's directory.
+               rm -rf "$WIKI_DIR_INST/$WIKI_DIR_NAME" ||
+                       error "Wiki's directory $WIKI_DIR_INST/" \
+                       "$WIKI_DIR_NAME could not be deleted"
+               # Delete the wiki's SQLite database.
+               rm -f "$TMP/$DB_FILE" ||
+                       error "Database $TMP/$DB_FILE could not be deleted."
+       fi
+
+       # Delete the wiki's SQLite database
+       rm -f "$TMP/$DB_FILE" || error "Database $TMP/$DB_FILE could not be deleted."
+       rm -f "$FILES_FOLDER/$DB_FILE"
+       rm -rf "$TMP/$MW_VERSION"
+}
diff --git a/contrib/mw-to-git/t/test-gitmw.pl b/contrib/mw-to-git/t/test-gitmw.pl
new file mode 100755 (executable)
index 0000000..0ff7625
--- /dev/null
@@ -0,0 +1,225 @@
+#!/usr/bin/perl -w -s
+# Copyright (C) 2012
+#     Charles Roussel <charles.roussel@ensimag.imag.fr>
+#     Simon Cathebras <simon.cathebras@ensimag.imag.fr>
+#     Julien Khayat <julien.khayat@ensimag.imag.fr>
+#     Guillaume Sasdy <guillaume.sasdy@ensimag.imag.fr>
+#     Simon Perrat <simon.perrat@ensimag.imag.fr>
+# License: GPL v2 or later
+
+# Usage:
+#       ./test-gitmw.pl <command> [argument]*
+# Execute in terminal using the name of the function to call as first
+# parameter, and the function's arguments as following parameters
+#
+# Example:
+#     ./test-gitmw.pl "get_page" foo .
+# will call <wiki_getpage> with arguments <foo> and <.>
+#
+# Available functions are:
+#     "get_page"
+#     "delete_page"
+#     "edit_page"
+#     "getallpagename"
+
+use MediaWiki::API;
+use Getopt::Long;
+use encoding 'utf8';
+use DateTime::Format::ISO8601;
+use open ':encoding(utf8)';
+use constant SLASH_REPLACEMENT => "%2F";
+
+#Parsing of the config file
+
+my $configfile = "$ENV{'CURR_DIR'}/test.config";
+my %config;
+open my $CONFIG, "<",  $configfile or die "can't open $configfile: $!";
+while (<$CONFIG>)
+{
+       chomp;
+       s/#.*//;
+       s/^\s+//;
+       s/\s+$//;
+       next unless length;
+       my ($key, $value) = split (/\s*=\s*/,$_, 2);
+       $config{$key} = $value;
+       last if ($key eq 'LIGHTTPD' and $value eq 'false');
+       last if ($key eq 'PORT');
+}
+close $CONFIG or die "can't close $configfile: $!";
+
+my $wiki_address = "http://$config{'SERVER_ADDR'}".":"."$config{'PORT'}";
+my $wiki_url = "$wiki_address/$config{'WIKI_DIR_NAME'}/api.php";
+my $wiki_admin = "$config{'WIKI_ADMIN'}";
+my $wiki_admin_pass = "$config{'WIKI_PASSW'}";
+my $mw = MediaWiki::API->new;
+$mw->{config}->{api_url} = $wiki_url;
+
+
+# wiki_login <name> <password>
+#
+# Logs the user with <name> and <password> in the global variable
+# of the mediawiki $mw
+sub wiki_login {
+       $mw->login( { lgname => "$_[0]",lgpassword => "$_[1]" } )
+       || die "getpage: login failed";
+}
+
+# wiki_getpage <wiki_page> <dest_path>
+#
+# fetch a page <wiki_page> from the wiki referenced in the global variable
+# $mw and copies its content in directory dest_path
+sub wiki_getpage {
+       my $pagename = $_[0];
+       my $destdir = $_[1];
+
+       my $page = $mw->get_page( { title => $pagename } );
+       if (!defined($page)) {
+               die "getpage: wiki does not exist";
+       }
+
+       my $content = $page->{'*'};
+       if (!defined($content)) {
+               die "getpage: page does not exist";
+       }
+
+       $pagename=$page->{'title'};
+       # Replace spaces by underscore in the page name
+       $pagename =~ s/ /_/g;
+       $pagename =~ s/\//%2F/g;
+       open(my $file, ">$destdir/$pagename.mw");
+       print $file "$content";
+       close ($file);
+
+}
+
+# wiki_delete_page <page_name>
+#
+# delete the page with name <page_name> from the wiki referenced
+# in the global variable $mw
+sub wiki_delete_page {
+       my $pagename = $_[0];
+
+       my $exist=$mw->get_page({title => $pagename});
+
+       if (defined($exist->{'*'})){
+               $mw->edit({ action => 'delete',
+                               title => $pagename})
+               || die $mw->{error}->{code} . ": " . $mw->{error}->{details};
+       } else {
+               die "no page with such name found: $pagename\n";
+       }
+}
+
+# wiki_editpage <wiki_page> <wiki_content> <wiki_append> [-c=<category>] [-s=<summary>]
+#
+# Edit a page named <wiki_page> with content <wiki_content> on the wiki
+# referenced with the global variable $mw
+# If <wiki_append> == true : append <wiki_content> at the end of the actual
+# content of the page <wiki_page>
+# If <wik_page> doesn't exist, that page is created with the <wiki_content>
+sub wiki_editpage {
+       my $wiki_page = $_[0];
+       my $wiki_content = $_[1];
+       my $wiki_append = $_[2];
+       my $summary = "";
+       my ($summ, $cat) = ();
+       GetOptions('s=s' => \$summ, 'c=s' => \$cat);
+
+       my $append = 0;
+       if (defined($wiki_append) && $wiki_append eq 'true') {
+               $append=1;
+       }
+
+       my $previous_text ="";
+
+       if ($append) {
+               my $ref = $mw->get_page( { title => $wiki_page } );
+               $previous_text = $ref->{'*'};
+       }
+
+       my $text = $wiki_content;
+       if (defined($previous_text)) {
+               $text="$previous_text$text";
+       }
+
+       # Eventually, add this page to a category.
+       if (defined($cat)) {
+               my $category_name="[[Category:$cat]]";
+               $text="$text\n $category_name";
+       }
+       if(defined($summ)){
+               $summary=$summ;
+       }
+
+       $mw->edit( { action => 'edit', title => $wiki_page, summary => $summary, text => "$text"} );
+}
+
+# wiki_getallpagename [<category>]
+#
+# Fetch all pages of the wiki referenced by the global variable $mw
+# and print the names of each one in the file all.txt with a new line
+# ("\n") between these.
+# If the argument <category> is defined, then this function get only the pages
+# belonging to <category>.
+sub wiki_getallpagename {
+       # fetch the pages of the wiki
+       if (defined($_[0])) {
+               my $mw_pages = $mw->list ( { action => 'query',
+                               list => 'categorymembers',
+                               cmtitle => "Category:$_[0]",
+                               cmnamespace => 0,
+                               cmlimit => 500 },
+               )
+               || die $mw->{error}->{code}.": ".$mw->{error}->{details};
+               open(my $file, ">all.txt");
+               foreach my $page (@{$mw_pages}) {
+                       print $file "$page->{title}\n";
+               }
+               close ($file);
+
+       } else {
+               my $mw_pages = $mw->list({
+                               action => 'query',
+                               list => 'allpages',
+                               aplimit => 500,
+                       })
+               || die $mw->{error}->{code}.": ".$mw->{error}->{details};
+               open(my $file, ">all.txt");
+               foreach my $page (@{$mw_pages}) {
+                       print $file "$page->{title}\n";
+               }
+               close ($file);
+       }
+}
+
+sub wiki_upload_file {
+       my $file_name = $_[0];
+       my $resultat = $mw->edit ( {
+               action => 'upload',
+               filename => $file_name,
+               comment => 'upload a file',
+               file => [ $file_name ],
+               ignorewarnings=>1,
+       }, {
+               skip_encoding => 1
+       } ) || die $mw->{error}->{code} . ' : ' . $mw->{error}->{details};
+}
+
+
+
+# Main part of this script: parse the command line arguments
+# and select which function to execute
+my $fct_to_call = shift;
+
+wiki_login($wiki_admin, $wiki_admin_pass);
+
+my %functions_to_call = qw(
+       upload_file    wiki_upload_file
+       get_page       wiki_getpage
+       delete_page    wiki_delete_page
+       edit_page      wiki_editpage
+       getallpagename wiki_getallpagename
+);
+die "$0 ERROR: wrong argument" unless exists $functions_to_call{$fct_to_call};
+&{$functions_to_call{$fct_to_call}}(@ARGV);
diff --git a/contrib/mw-to-git/t/test.config b/contrib/mw-to-git/t/test.config
new file mode 100644 (file)
index 0000000..958b37b
--- /dev/null
@@ -0,0 +1,35 @@
+# Name of the web server's directory dedicated to the wiki is WIKI_DIR_NAME
+WIKI_DIR_NAME=wiki
+
+# Login and password of the wiki's admin
+WIKI_ADMIN=WikiAdmin
+WIKI_PASSW=AdminPass
+
+# Address of the web server
+SERVER_ADDR=localhost
+
+# SQLite database of the wiki, named DB_FILE, is located in TMP
+TMP=/tmp
+DB_FILE=wikidb.sqlite
+
+# If LIGHTTPD is not set to true, the script will use the defaut
+# web server running in WIKI_DIR_INST.
+WIKI_DIR_INST=/var/www
+
+# If LIGHTTPD is set to true, the script will use Lighttpd to run
+# the wiki.
+LIGHTTPD=true
+
+# The variables below are useful only if LIGHTTPD is set to true.
+PORT=1234
+PHP_DIR=/usr/bin
+LIGHTTPD_DIR=/usr/sbin
+WEB=WEB
+WEB_TMP=$WEB/tmp
+WEB_WWW=$WEB/www
+
+# The variables below are used by the script to install a wiki.
+# You should not modify these unless you are modifying the script itself.
+MW_VERSION=mediawiki-1.19.0
+FILES_FOLDER=install-wiki
+DB_INSTALL_SCRIPT=db_install.php
index 62d1c56819e5351e5697dab7ff13d33ac806c398..e54753c75d1c2abf7916f1aa7075d4ada3cfc61f 100644 (file)
@@ -172,6 +172,8 @@ int credential_read(struct credential *c, FILE *fp)
                } else if (!strcmp(key, "path")) {
                        free(c->path);
                        c->path = xstrdup(value);
+               } else if (!strcmp(key, "url")) {
+                       credential_from_url(c, value);
                }
                /*
                 * Ignore other lines; we don't know what they mean, but
@@ -191,7 +193,7 @@ static void credential_write_item(FILE *fp, const char *key, const char *value)
        fprintf(fp, "%s=%s\n", key, value);
 }
 
-static void credential_write(const struct credential *c, FILE *fp)
+void credential_write(const struct credential *c, FILE *fp)
 {
        credential_write_item(fp, "protocol", c->protocol);
        credential_write_item(fp, "host", c->host);
index 96ea41bd1c99c05d63cb965a5fc57fcd838f3fdb..0c3e85e8e4232ad9298372efebf7696a77dc94b1 100644 (file)
@@ -26,6 +26,7 @@ void credential_approve(struct credential *);
 void credential_reject(struct credential *);
 
 int credential_read(struct credential *, FILE *);
+void credential_write(const struct credential *, FILE *);
 void credential_from_url(struct credential *, const char *url);
 int credential_match(const struct credential *have,
                     const struct credential *want);
diff --git a/date.c b/date.c
index 1fdcf7c6eae0386552c5d7e069f5242fca3c2e0e..57331ed406e2391e0a2bf327fbb86d3b62012324 100644 (file)
--- a/date.c
+++ b/date.c
@@ -624,7 +624,7 @@ static int match_object_header_date(const char *date, unsigned long *timestamp,
        unsigned long stamp;
        int ofs;
 
-       if (*date < '0' || '9' <= *date)
+       if (*date < '0' || '9' < *date)
                return -1;
        stamp = strtoul(date, &end, 10);
        if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
diff --git a/diff.c b/diff.c
index 208096fe461b483a0646bb583fd2158c3179823f..62cbe141efb411e831484fc36c0cba95a2b8846b 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -2992,9 +2992,8 @@ static void run_diff_cmd(const char *pgm,
        int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score;
        int must_show_header = 0;
 
-       if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
-               pgm = NULL;
-       else {
+
+       if (DIFF_OPT_TST(o, ALLOW_EXTERNAL)) {
                struct userdiff_driver *drv = userdiff_find_by_path(attr_path);
                if (drv && drv->external)
                        pgm = drv->external;
@@ -3074,6 +3073,9 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
 
+       if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
+               pgm = NULL;
+
        if (DIFF_PAIR_UNMERGED(p)) {
                run_diff_cmd(pgm, name, NULL, attr_path,
                             NULL, NULL, NULL, o, p);
diff --git a/dir.c b/dir.c
index 2c02b312b77f3363c234bdc66faaeafd5899586d..a772c6dc6c2bf4dc40f46f0bb5d0899312f3ffd4 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -288,9 +288,24 @@ int match_pathspec_depth(const struct pathspec *ps,
        return retval;
 }
 
+/*
+ * Return the length of the "simple" part of a path match limiter.
+ */
+static int simple_length(const char *match)
+{
+       int len = -1;
+
+       for (;;) {
+               unsigned char c = *match++;
+               len++;
+               if (c == '\0' || is_glob_special(c))
+                       return len;
+       }
+}
+
 static int no_wildcard(const char *string)
 {
-       return string[strcspn(string, "*?[{\\")] == '\0';
+       return string[simple_length(string)] == '\0';
 }
 
 void add_exclude(const char *string, const char *base,
@@ -326,8 +341,7 @@ void add_exclude(const char *string, const char *base,
        x->flags = flags;
        if (!strchr(string, '/'))
                x->flags |= EXC_FLAG_NODIR;
-       if (no_wildcard(string))
-               x->flags |= EXC_FLAG_NOWILDCARD;
+       x->nowildcardlen = simple_length(string);
        if (*string == '*' && no_wildcard(string+1))
                x->flags |= EXC_FLAG_ENDSWITH;
        ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
@@ -498,57 +512,69 @@ int excluded_from_list(const char *pathname,
 {
        int i;
 
-       if (el->nr) {
-               for (i = el->nr - 1; 0 <= i; i--) {
-                       struct exclude *x = el->excludes[i];
-                       const char *exclude = x->pattern;
-                       int to_exclude = x->to_exclude;
-
-                       if (x->flags & EXC_FLAG_MUSTBEDIR) {
-                               if (*dtype == DT_UNKNOWN)
-                                       *dtype = get_dtype(NULL, pathname, pathlen);
-                               if (*dtype != DT_DIR)
-                                       continue;
-                       }
+       if (!el->nr)
+               return -1;      /* undefined */
 
-                       if (x->flags & EXC_FLAG_NODIR) {
-                               /* match basename */
-                               if (x->flags & EXC_FLAG_NOWILDCARD) {
-                                       if (!strcmp_icase(exclude, basename))
-                                               return to_exclude;
-                               } else if (x->flags & EXC_FLAG_ENDSWITH) {
-                                       if (x->patternlen - 1 <= pathlen &&
-                                           !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
-                                               return to_exclude;
-                               } else {
-                                       if (fnmatch_icase(exclude, basename, 0) == 0)
-                                               return to_exclude;
-                               }
-                       }
-                       else {
-                               /* match with FNM_PATHNAME:
-                                * exclude has base (baselen long) implicitly
-                                * in front of it.
-                                */
-                               int baselen = x->baselen;
-                               if (*exclude == '/')
-                                       exclude++;
-
-                               if (pathlen < baselen ||
-                                   (baselen && pathname[baselen-1] != '/') ||
-                                   strncmp_icase(pathname, x->base, baselen))
-                                   continue;
-
-                               if (x->flags & EXC_FLAG_NOWILDCARD) {
-                                       if (!strcmp_icase(exclude, pathname + baselen))
-                                               return to_exclude;
-                               } else {
-                                       if (fnmatch_icase(exclude, pathname+baselen,
-                                                   FNM_PATHNAME) == 0)
-                                           return to_exclude;
-                               }
+       for (i = el->nr - 1; 0 <= i; i--) {
+               struct exclude *x = el->excludes[i];
+               const char *name, *exclude = x->pattern;
+               int to_exclude = x->to_exclude;
+               int namelen, prefix = x->nowildcardlen;
+
+               if (x->flags & EXC_FLAG_MUSTBEDIR) {
+                       if (*dtype == DT_UNKNOWN)
+                               *dtype = get_dtype(NULL, pathname, pathlen);
+                       if (*dtype != DT_DIR)
+                               continue;
+               }
+
+               if (x->flags & EXC_FLAG_NODIR) {
+                       /* match basename */
+                       if (prefix == x->patternlen) {
+                               if (!strcmp_icase(exclude, basename))
+                                       return to_exclude;
+                       } else if (x->flags & EXC_FLAG_ENDSWITH) {
+                               if (x->patternlen - 1 <= pathlen &&
+                                   !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
+                                       return to_exclude;
+                       } else {
+                               if (fnmatch_icase(exclude, basename, 0) == 0)
+                                       return to_exclude;
                        }
+                       continue;
+               }
+
+               /* match with FNM_PATHNAME:
+                * exclude has base (baselen long) implicitly in front of it.
+                */
+               if (*exclude == '/') {
+                       exclude++;
+                       prefix--;
                }
+
+               if (pathlen < x->baselen ||
+                   (x->baselen && pathname[x->baselen-1] != '/') ||
+                   strncmp_icase(pathname, x->base, x->baselen))
+                       continue;
+
+               namelen = x->baselen ? pathlen - x->baselen : pathlen;
+               name = pathname + pathlen  - namelen;
+
+               /* if the non-wildcard part is longer than the
+                  remaining pathname, surely it cannot match */
+               if (prefix > namelen)
+                       continue;
+
+               if (prefix) {
+                       if (strncmp_icase(exclude, name, prefix))
+                               continue;
+                       exclude += prefix;
+                       name    += prefix;
+                       namelen -= prefix;
+               }
+
+               if (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME))
+                       return to_exclude;
        }
        return -1; /* undecided */
 }
@@ -1055,21 +1081,6 @@ static int cmp_name(const void *p1, const void *p2)
                                  e2->name, e2->len);
 }
 
-/*
- * Return the length of the "simple" part of a path match limiter.
- */
-static int simple_length(const char *match)
-{
-       int len = -1;
-
-       for (;;) {
-               unsigned char c = *match++;
-               len++;
-               if (c == '\0' || is_glob_special(c))
-                       return len;
-       }
-}
-
 static struct path_simplify *create_simplify(const char **pathspec)
 {
        int nr, alloc = 0;
@@ -1292,12 +1303,17 @@ int remove_dir_recursively(struct strbuf *path, int flag)
 void setup_standard_excludes(struct dir_struct *dir)
 {
        const char *path;
+       char *xdg_path;
 
        dir->exclude_per_dir = ".gitignore";
        path = git_path("info/exclude");
+       if (!excludes_file) {
+               home_config_paths(NULL, &xdg_path, "ignore");
+               excludes_file = xdg_path;
+       }
        if (!access(path, R_OK))
                add_excludes_from_file(dir, path);
-       if (excludes_file && !access(excludes_file, R_OK))
+       if (!access(excludes_file, R_OK))
                add_excludes_from_file(dir, excludes_file);
 }
 
diff --git a/dir.h b/dir.h
index 6c73e4151de8374d35427358cde50b506a2d7de9..893465a1e89d17cf5f94d3e68be1371f03e6163d 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -9,7 +9,6 @@ struct dir_entry {
 };
 
 #define EXC_FLAG_NODIR 1
-#define EXC_FLAG_NOWILDCARD 2
 #define EXC_FLAG_ENDSWITH 4
 #define EXC_FLAG_MUSTBEDIR 8
 
@@ -19,6 +18,7 @@ struct exclude_list {
        struct exclude {
                const char *pattern;
                int patternlen;
+               int nowildcardlen;
                const char *base;
                int baselen;
                int to_exclude;
index 669e498f5a245909725494feda2531f0088f8160..85edd7f95adecd8316b49638d7bda2841839206d 100644 (file)
@@ -58,6 +58,7 @@ char *notes_ref_name;
 int grafts_replace_parents = 1;
 int core_apply_sparse_checkout;
 int merge_log_config = -1;
+int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
 struct startup_info *startup_info;
 unsigned long pack_size_limit_cfg;
 
index f8b7a0cb602d2d2425f68f8c27338cc003b70f6b..c02e62d2aa4f3bd2221c687effa3a4ec44918383 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -260,7 +260,7 @@ check_patch_format () {
 split_patches () {
        case "$patch_format" in
        mbox)
-               if test -n "$rebasing" || test t = "$keepcr"
+               if test t = "$keepcr"
                then
                    keep_cr=--keep-cr
                else
@@ -413,7 +413,7 @@ do
        --abort)
                abort=t ;;
        --rebasing)
-               rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;;
+               rebasing=t threeway=t ;;
        -d|--dotest)
                die "$(gettext "-d option is no longer supported.  Do not use.")"
                ;;
@@ -658,32 +658,34 @@ do
        # by the user, or the user can tell us to do so by --resolved flag.
        case "$resume" in
        '')
-               git mailinfo $keep $no_inbody_headers $scissors $utf8 "$dotest/msg" "$dotest/patch" \
-                       <"$dotest/$msgnum" >"$dotest/info" ||
-                       stop_here $this
-
-               # skip pine's internal folder data
-               sane_grep '^Author: Mail System Internal Data$' \
-                       <"$dotest"/info >/dev/null &&
-                       go_next && continue
-
-               test -s "$dotest/patch" || {
-                       eval_gettextln "Patch is empty.  Was it split wrong?
-If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
-To restore the original branch and stop patching run \"\$cmdline --abort\"."
-                       stop_here $this
-               }
-               rm -f "$dotest/original-commit" "$dotest/author-script"
-               if test -f "$dotest/rebasing" &&
+               if test -f "$dotest/rebasing"
+               then
                        commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
                                -e q "$dotest/$msgnum") &&
-                       test "$(git cat-file -t "$commit")" = commit
-               then
+                       test "$(git cat-file -t "$commit")" = commit ||
+                               stop_here $this
                        git cat-file commit "$commit" |
                        sed -e '1,/^$/d' >"$dotest/msg-clean"
-                       echo "$commit" > "$dotest/original-commit"
-                       get_author_ident_from_commit "$commit" > "$dotest/author-script"
+                       echo "$commit" >"$dotest/original-commit"
+                       get_author_ident_from_commit "$commit" >"$dotest/author-script"
+                       git diff-tree --root --binary "$commit" >"$dotest/patch"
                else
+                       git mailinfo $keep $no_inbody_headers $scissors $utf8 "$dotest/msg" "$dotest/patch" \
+                               <"$dotest/$msgnum" >"$dotest/info" ||
+                               stop_here $this
+
+                       # skip pine's internal folder data
+                       sane_grep '^Author: Mail System Internal Data$' \
+                               <"$dotest"/info >/dev/null &&
+                               go_next && continue
+
+                       test -s "$dotest/patch" || {
+                               eval_gettextln "Patch is empty.  Was it split wrong?
+If you would prefer to skip this patch, instead run \"\$cmdline --skip\".
+To restore the original branch and stop patching run \"\$cmdline --abort\"."
+                               stop_here $this
+                       }
+                       rm -f "$dotest/original-commit" "$dotest/author-script"
                        {
                                sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
                                echo
@@ -855,6 +857,11 @@ did you forget to use 'git add'?"
        if test $apply_status != 0
        then
                eval_gettextln 'Patch failed at $msgnum $FIRSTLINE'
+               if test "$(git config --bool advice.amworkdir)" != false
+               then
+                       eval_gettextln "The copy of the patch that failed is found in:
+   $dotest/patch"
+               fi
                stop_here_user_resolve $this
        fi
 
index 5bd9ad7d2a23773b1410ded9f4f241ebe4d4da00..35b095e8ae989ae02228f814528b43d5bd026aab 100644 (file)
 #endif
 #endif
 
+/* used on Mac OS X */
+#ifdef PRECOMPOSE_UNICODE
+#include "compat/precompose_utf8.h"
+#else
+#define precompose_str(in,i_nfd2nfc)
+#define precompose_argv(c,v)
+#define probe_utf8_pathname_composition(a,b)
+#endif
+
 #ifndef NO_LIBGEN_H
 #include <libgen.h>
 #else
index ae1e0525d89181f8adbe288b62e107b8dadb8ca1..c0798540adee04cb7cfb6fd04ef3541a1fcbb762 100755 (executable)
@@ -15,6 +15,7 @@
 use warnings;
 use File::Basename qw(dirname);
 use File::Copy;
+use File::Compare;
 use File::Find;
 use File::stat;
 use File::Path qw(mkpath);
@@ -336,8 +337,10 @@ sub write_to_file
        # 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 $!;
+               if (-e "$b/$file" && compare("$b/$file", "$workdir/$file")) {
+                       copy("$b/$file", "$workdir/$file") or die $!;
+                       chmod(stat("$b/$file")->mode, "$workdir/$file") or die $!;
+               }
        }
 } else {
        if (defined($prompt)) {
index add2c0247fa91e0f629428c295fc581f19cf85e1..178e45305d2b66bbe5a0c0902d989ea66888869b 100755 (executable)
@@ -84,7 +84,7 @@ set_ident () {
                        s/.*/GIT_'$uid'_EMAIL='\''&'\''; export GIT_'$uid'_EMAIL/p
 
                        g
-                       s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
+                       s/^'$lid' [^<]* <[^>]*> \(.*\)$/@\1/
                        s/'\''/'\''\'\'\''/g
                        s/.*/GIT_'$uid'_DATE='\''&'\''; export GIT_'$uid'_DATE/p
 
index f895a2412bd5723d72f3bcfdcddbce495070d5e6..e67d37d2f93361337df6e680d2b2ab961e31ad66 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -120,6 +120,15 @@ def p4_read_pipe_lines(c):
     real_cmd = p4_build_cmd(c)
     return read_pipe_lines(real_cmd)
 
+def p4_has_command(cmd):
+    """Ask p4 for help on this command.  If it returns an error, the
+       command does not exist in this version of p4."""
+    real_cmd = p4_build_cmd(["help", cmd])
+    p = subprocess.Popen(real_cmd, stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE)
+    p.communicate()
+    return p.returncode == 0
+
 def system(cmd):
     expand = isinstance(cmd,basestring)
     if verbose:
@@ -157,6 +166,9 @@ def p4_revert(f):
 def p4_reopen(type, f):
     p4_system(["reopen", "-t", type, wildcard_encode(f)])
 
+def p4_move(src, dest):
+    p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
+
 #
 # Canonicalize the p4 type and return a tuple of the
 # base type, plus any modifiers.  See "p4 help filetypes"
@@ -844,20 +856,45 @@ def __init__(self):
         ]
         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.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
         self.isWindows = (platform.system() == "Windows")
         self.exportLabels = False
+        self.p4HasMoveCommand = p4_has_command("move")
 
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
             die("You have files opened with perforce! Close them before starting the sync.")
 
-    # replaces everything between 'Description:' and the next P4 submit template field with the
-    # commit message
-    def prepareLogMessage(self, template, message):
+    def separate_jobs_from_description(self, message):
+        """Extract and return a possible Jobs field in the commit
+           message.  It goes into a separate section in the p4 change
+           specification.
+
+           A jobs line starts with "Jobs:" and looks like a new field
+           in a form.  Values are white-space separated on the same
+           line or on following lines that start with a tab.
+
+           This does not parse and extract the full git commit message
+           like a p4 form.  It just sees the Jobs: line as a marker
+           to pass everything from then on directly into the p4 form,
+           but outside the description section.
+
+           Return a tuple (stripped log message, jobs string)."""
+
+        m = re.search(r'^Jobs:', message, re.MULTILINE)
+        if m is None:
+            return (message, None)
+
+        jobtext = message[m.start():]
+        stripped_message = message[:m.start()].rstrip()
+        return (stripped_message, jobtext)
+
+    def prepareLogMessage(self, template, message, jobs):
+        """Edits the template returned from "p4 change -o" to insert
+           the message in the Description field, and the jobs text in
+           the Jobs field."""
         result = ""
 
         inDescriptionSection = False
@@ -870,6 +907,9 @@ def prepareLogMessage(self, template, message):
             if inDescriptionSection:
                 if line.startswith("Files:") or line.startswith("Jobs:"):
                     inDescriptionSection = False
+                    # insert Jobs section
+                    if jobs:
+                        result += jobs + "\n"
                 else:
                     continue
             else:
@@ -981,7 +1021,13 @@ def canChangeChangelists(self):
         return 0
 
     def prepareSubmitTemplate(self):
-        # remove lines in the Files section that show changes to files outside the depot path we're committing into
+        """Run "p4 change -o" to grab a change specification template.
+           This does not use "p4 -G", as it is nice to keep the submission
+           template in original order, since a human might edit it.
+
+           Remove lines in the Files section that show changes to files
+           outside the depot path we're committing into."""
+
         template = ""
         inFilesSection = False
         for line in p4_read_pipe_lines(['change', '-o']):
@@ -1046,27 +1092,7 @@ def applyCommit(self, id):
 
         (p4User, gitEmail) = self.p4UserForCommit(id)
 
-        if not self.detectRenames:
-            # If not explicitly set check the config variable
-            self.detectRenames = gitConfig("git-p4.detectRenames")
-
-        if self.detectRenames.lower() == "false" or self.detectRenames == "":
-            diffOpts = ""
-        elif self.detectRenames.lower() == "true":
-            diffOpts = "-M"
-        else:
-            diffOpts = "-M%s" % self.detectRenames
-
-        detectCopies = gitConfig("git-p4.detectCopies")
-        if detectCopies.lower() == "true":
-            diffOpts += " -C"
-        elif detectCopies != "" and detectCopies.lower() != "false":
-            diffOpts += " -C%s" % detectCopies
-
-        if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
-            diffOpts += " --find-copies-harder"
-
-        diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
+        diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (self.diffOpts, id, id))
         filesToAdd = set()
         filesToDelete = set()
         editedFiles = set()
@@ -1106,17 +1132,23 @@ def applyCommit(self, id):
                 editedFiles.add(dest)
             elif modifier == "R":
                 src, dest = diff['src'], diff['dst']
-                p4_integrate(src, dest)
-                if diff['src_sha1'] != diff['dst_sha1']:
-                    p4_edit(dest)
+                if self.p4HasMoveCommand:
+                    p4_edit(src)        # src must be open before move
+                    p4_move(src, dest)  # opens for (move/delete, move/add)
                 else:
-                    pureRenameCopy.add(dest)
+                    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)
+                    if not self.p4HasMoveCommand:
+                        p4_edit(dest)   # with move: already open, writable
                     filesToChangeExecBit[dest] = diff['dst_mode']
-                os.unlink(dest)
+                if not self.p4HasMoveCommand:
+                    os.unlink(dest)
+                    filesToDelete.add(src)
                 editedFiles.add(dest)
-                filesToDelete.add(src)
             else:
                 die("unknown modifier %s for %s" % (modifier, path))
 
@@ -1206,89 +1238,80 @@ def applyCommit(self, id):
 
         logMessage = extractLogMessageFromGitCommit(id)
         logMessage = logMessage.strip()
+        (logMessage, jobs) = self.separate_jobs_from_description(logMessage)
 
         template = self.prepareSubmitTemplate()
+        submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
 
-        if self.interactive:
-            submitTemplate = self.prepareLogMessage(template, logMessage)
-
-            if self.preserveUser:
-               submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
-
-            if os.environ.has_key("P4DIFF"):
-                del(os.environ["P4DIFF"])
-            diff = ""
-            for editedFile in editedFiles:
-                diff += p4_read_pipe(['diff', '-du',
-                                      wildcard_encode(editedFile)])
-
-            newdiff = ""
-            for newFile in filesToAdd:
-                newdiff += "==== new file ====\n"
-                newdiff += "--- /dev/null\n"
-                newdiff += "+++ %s\n" % newFile
-                f = open(newFile, "r")
-                for line in f.readlines():
-                    newdiff += "+" + line
-                f.close()
-
-            if self.checkAuthorship and not self.p4UserIsMe(p4User):
-                submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
-                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"
-
-            (handle, fileName) = tempfile.mkstemp()
-            tmpFile = os.fdopen(handle, "w+")
-            if self.isWindows:
-                submitTemplate = submitTemplate.replace("\n", "\r\n")
-                separatorLine = separatorLine.replace("\n", "\r\n")
-                newdiff = newdiff.replace("\n", "\r\n")
-            tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
+        if self.preserveUser:
+           submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
+
+        if os.environ.has_key("P4DIFF"):
+            del(os.environ["P4DIFF"])
+        diff = ""
+        for editedFile in editedFiles:
+            diff += p4_read_pipe(['diff', '-du',
+                                  wildcard_encode(editedFile)])
+
+        newdiff = ""
+        for newFile in filesToAdd:
+            newdiff += "==== new file ====\n"
+            newdiff += "--- /dev/null\n"
+            newdiff += "+++ %s\n" % newFile
+            f = open(newFile, "r")
+            for line in f.readlines():
+                newdiff += "+" + line
+            f.close()
+
+        if self.checkAuthorship and not self.p4UserIsMe(p4User):
+            submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
+            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"
+
+        (handle, fileName) = tempfile.mkstemp()
+        tmpFile = os.fdopen(handle, "w+")
+        if self.isWindows:
+            submitTemplate = submitTemplate.replace("\n", "\r\n")
+            separatorLine = separatorLine.replace("\n", "\r\n")
+            newdiff = newdiff.replace("\n", "\r\n")
+        tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
+        tmpFile.close()
+
+        if self.edit_template(fileName):
+            # read the edited message and submit
+            tmpFile = open(fileName, "rb")
+            message = tmpFile.read()
             tmpFile.close()
+            submitTemplate = message[:message.index(separatorLine)]
+            if self.isWindows:
+                submitTemplate = submitTemplate.replace("\r\n", "\n")
+            p4_write_pipe(['submit', '-i'], submitTemplate)
 
-            if self.edit_template(fileName):
-                # read the edited message and submit
-                tmpFile = open(fileName, "rb")
-                message = tmpFile.read()
-                tmpFile.close()
-                submitTemplate = message[:message.index(separatorLine)]
-                if self.isWindows:
-                    submitTemplate = submitTemplate.replace("\r\n", "\n")
-                p4_write_pipe(['submit', '-i'], submitTemplate)
-
-                if self.preserveUser:
-                    if p4User:
-                        # Get last changelist number. Cannot easily get it from
-                        # the submit command output as the output is
-                        # 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."
-                for f in editedFiles:
-                    p4_revert(f)
-                for f in filesToAdd:
-                    p4_revert(f)
-                    os.remove(f)
+            if self.preserveUser:
+                if p4User:
+                    # Get last changelist number. Cannot easily get it from
+                    # the submit command output as the output is
+                    # 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")
 
-            os.remove(fileName)
         else:
-            fileName = "submit.txt"
-            file = open(fileName, "w+")
-            file.write(self.prepareLogMessage(template, logMessage))
-            file.close()
-            print ("Perforce submit template written as %s. "
-                   + "Please review/edit and then use p4 submit -i < %s to submit directly!"
-                   % (fileName, fileName))
+            # skip this patch
+            print "Submission cancelled, undoing p4 changes."
+            for f in editedFiles:
+                p4_revert(f)
+            for f in filesToAdd:
+                p4_revert(f)
+                os.remove(f)
+
+        os.remove(fileName)
 
     # Export git tags as p4 labels. Create a p4 label and then tag
     # with that.
@@ -1433,12 +1456,41 @@ def run(self, args):
         if self.preserveUser:
             self.checkValidP4Users(commits)
 
+        #
+        # Build up a set of options to be passed to diff when
+        # submitting each commit to p4.
+        #
+        if self.detectRenames:
+            # command-line -M arg
+            self.diffOpts = "-M"
+        else:
+            # If not explicitly set check the config variable
+            detectRenames = gitConfig("git-p4.detectRenames")
+
+            if detectRenames.lower() == "false" or detectRenames == "":
+                self.diffOpts = ""
+            elif detectRenames.lower() == "true":
+                self.diffOpts = "-M"
+            else:
+                self.diffOpts = "-M%s" % detectRenames
+
+        # no command-line arg for -C or --find-copies-harder, just
+        # config variables
+        detectCopies = gitConfig("git-p4.detectCopies")
+        if detectCopies.lower() == "false" or detectCopies == "":
+            pass
+        elif detectCopies.lower() == "true":
+            self.diffOpts += " -C"
+        else:
+            self.diffOpts += " -C%s" % detectCopies
+
+        if gitConfig("git-p4.detectCopiesHarder", "--bool") == "true":
+            self.diffOpts += " --find-copies-harder"
+
         while len(commits) > 0:
             commit = commits[0]
             commits = commits[1:]
             self.applyCommit(commit)
-            if not self.interactive:
-                break
 
         if len(commits) == 0:
             print "All changes applied!"
index 04d89415fe8ca1634fa362c66ea9569794dcadd5..392ebc9790c88f9857fc4e943d1fe6351e953c7d 100644 (file)
@@ -3,8 +3,6 @@
 # Copyright (c) 2010 Junio C Hamano.
 #
 
-. git-sh-setup
-
 case "$action" in
 continue)
        git am --resolved --resolvemsg="$resolvemsg" &&
index 0c19b7c7539192851d5bd43df686baa97dc0df73..bef7bc0444bab7b7e511c6e29417ce6db44b77ef 100644 (file)
@@ -9,9 +9,7 @@
 #
 # The original idea comes from Eric W. Biederman, in
 # http://article.gmane.org/gmane.comp.version-control.git/22407
-
-. git-sh-setup
-
+#
 # The file containing rebase commands, comments, and empty lines.
 # This file is created by "git rebase -i" then edited by the user.  As
 # the lines are processed, they are removed from the front of this
@@ -417,6 +415,29 @@ record_in_rewritten() {
        esac
 }
 
+do_pick () {
+       if test "$(git rev-parse HEAD)" = "$squash_onto"
+       then
+               # Set the correct commit message and author info on the
+               # sentinel root before cherry-picking the original changes
+               # without committing (-n).  Finally, update the sentinel again
+               # to include these changes.  If the cherry-pick results in a
+               # conflict, this means our behaviour is similar to a standard
+               # failed cherry-pick during rebase, with a dirty index to
+               # resolve before manually running git commit --amend then git
+               # rebase --continue.
+               git commit --allow-empty --allow-empty-message --amend \
+                          --no-post-rewrite -n -q -C $1 &&
+                       pick_one -n $1 &&
+                       git commit --allow-empty --allow-empty-message \
+                                  --amend --no-post-rewrite -n -q -C $1 ||
+                       die_with_patch $1 "Could not apply $1... $2"
+       else
+               pick_one $1 ||
+                       die_with_patch $1 "Could not apply $1... $2"
+       fi
+}
+
 do_next () {
        rm -f "$msg" "$author_script" "$amend" || exit
        read -r command sha1 rest < "$todo"
@@ -428,16 +449,14 @@ do_next () {
                comment_for_reflog pick
 
                mark_action_done
-               pick_one $sha1 ||
-                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               do_pick $sha1 "$rest"
                record_in_rewritten $sha1
                ;;
        reword|r)
                comment_for_reflog reword
 
                mark_action_done
-               pick_one $sha1 ||
-                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               do_pick $sha1 "$rest"
                git commit --amend --no-post-rewrite || {
                        warn "Could not amend commit after successfully picking $sha1... $rest"
                        warn "This is most likely due to an empty commit message, or the pre-commit hook"
@@ -451,8 +470,7 @@ do_next () {
                comment_for_reflog edit
 
                mark_action_done
-               pick_one $sha1 ||
-                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               do_pick $sha1 "$rest"
                warn "Stopped at $sha1... $rest"
                exit_with_patch $sha1 0
                ;;
@@ -684,6 +702,27 @@ rearrange_squash () {
        rm -f "$1.sq" "$1.rearranged"
 }
 
+# Add commands after a pick or after a squash/fixup serie
+# in the todo list.
+add_exec_commands () {
+       {
+               first=t
+               while read -r insn rest
+               do
+                       case $insn in
+                       pick)
+                               test -n "$first" ||
+                               printf "%s" "$cmd"
+                               ;;
+                       esac
+                       printf "%s %s\n" "$insn" "$rest"
+                       first=
+               done
+               printf "%s" "$cmd"
+       } <"$1" >"$1.new" &&
+       mv "$1.new" "$1"
+}
+
 case "$action" in
 continue)
        # do we have anything to commit?
@@ -857,6 +896,8 @@ fi
 
 test -s "$todo" || echo noop >> "$todo"
 test -n "$autosquash" && rearrange_squash "$todo"
+test -n "$cmd" && add_exec_commands "$todo"
+
 cat >> "$todo" << EOF
 
 # Rebase $shortrevisions onto $shortonto
index dc599077f0e55472ae814ed4e15f5b3a1b729caf..b10f2cf21b904bbbc3fafc27d183d8ad7917b479 100644 (file)
@@ -3,8 +3,6 @@
 # Copyright (c) 2010 Junio C Hamano.
 #
 
-. git-sh-setup
-
 prec=4
 
 read_state () {
index e6167374445dfbb5e0b6bedc9a5f33a01cb5a7df..1cd0633b80a8daf00dbc50657ccba228bd578a73 100755 (executable)
@@ -3,7 +3,8 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
+USAGE='[--interactive | -i] [--exec | -x <cmd>] [-v] [--force-rebase | -f]
+       [--no-ff] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -30,8 +31,8 @@ Example:       git-rebase master~1 topic
 SUBDIRECTORY_OK=Yes
 OPTIONS_KEEPDASHDASH=
 OPTIONS_SPEC="\
-git rebase [-i] [options] [--onto <newbase>] [<upstream>] [<branch>]
-git rebase [-i] [options] --onto <newbase> --root [<branch>]
+git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>]
+git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
 git-rebase [-i] --continue | --abort | --skip
 --
  Available options are
@@ -43,6 +44,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
+x,exec=!           add exec lines after each commit of the editable list
 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
@@ -76,6 +78,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\".
 To check out the original branch and stop rebasing run \"git rebase --abort\".
 "
 unset onto
+cmd=
 strategy=
 strategy_opts=
 do_merge=
@@ -220,6 +223,11 @@ do
                onto="$2"
                shift
                ;;
+       -x)
+               test 2 -le "$#" || usage
+               cmd="${cmd}exec $2${LF}"
+               shift
+               ;;
        -i)
                interactive_rebase=explicit
                ;;
@@ -305,6 +313,12 @@ do
 done
 test $# -gt 2 && usage
 
+if test -n "$cmd" &&
+   test "$interactive_rebase" != explicit
+then
+       die "--exec option must be used with --interactive option"
+fi
+
 if test -n "$action"
 then
        test -z "$in_progress" && die "No rebase in progress?"
@@ -364,6 +378,11 @@ and run me again.  I am stopping in case you still have something
 valuable there.'
 fi
 
+if test -n "$rebase_root" && test -z "$onto"
+then
+       test -z "$interactive_rebase" && interactive_rebase=implied
+fi
+
 if test -n "$interactive_rebase"
 then
        type=interactive
@@ -397,9 +416,15 @@ then
        die "invalid upstream $upstream_name"
        upstream_arg="$upstream_name"
 else
-       test -z "$onto" && die "You must specify --onto when using --root"
+       if test -z "$onto"
+       then
+               empty_tree=`git hash-object -t tree /dev/null`
+               onto=`git commit-tree $empty_tree </dev/null`
+               squash_onto="$onto"
+       fi
        unset upstream_name
        unset upstream
+       test $# -gt 1 && usage
        upstream_arg=--root
 fi
 
@@ -450,7 +475,7 @@ case "$#" in
                die "fatal: no such branch: $1"
        fi
        ;;
-*)
+0)
        # Do not need to switch branches, we are already on it.
        if branch_name=`git symbolic-ref -q HEAD`
        then
@@ -462,6 +487,9 @@ case "$#" in
        fi
        orig_head=$(git rev-parse --verify "${branch_name}^0") || exit
        ;;
+*)
+       die "BUG: unexpected number of arguments left to parse"
+       ;;
 esac
 
 require_clean_work_tree "rebase" "Please commit or stash them."
index fbf2fafaaf49ec65090ec4eadb33d1ccedc60436..dba4d39e1fd3e971336e0d85f24c114c29c4d1fd 100755 (executable)
@@ -30,7 +30,22 @@ nofetch=
 update=
 prefix=
 
-# Resolve relative url by appending to parent's url
+# The function takes at most 2 arguments. The first argument is the
+# URL that navigates to the submodule origin repo. When relative, this URL
+# is relative to the superproject origin URL repo. The second up_path
+# argument, if specified, is the relative path that navigates
+# from the submodule working tree to the superproject working tree.
+#
+# The output of the function is the origin URL of the submodule.
+#
+# The output will either be an absolute URL or filesystem path (if the
+# superproject origin URL is an absolute URL or filesystem path,
+# respectively) or a relative file system path (if the superproject
+# origin URL is a relative file system path).
+#
+# When the output is a relative file system path, the path is either
+# relative to the submodule working tree, if up_path is specified, or to
+# the superproject working tree otherwise.
 resolve_relative_url ()
 {
        remote=$(get_default_remote)
@@ -39,6 +54,21 @@ resolve_relative_url ()
        url="$1"
        remoteurl=${remoteurl%/}
        sep=/
+       up_path="$2"
+
+       case "$remoteurl" in
+       *:*|/*)
+               is_relative=
+               ;;
+       ./*|../*)
+               is_relative=t
+               ;;
+       *)
+               is_relative=t
+               remoteurl="./$remoteurl"
+               ;;
+       esac
+
        while test -n "$url"
        do
                case "$url" in
@@ -53,7 +83,12 @@ resolve_relative_url ()
                                sep=:
                                ;;
                        *)
-                               die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
+                               if test -z "$is_relative" || test "." = "$remoteurl"
+                               then
+                                       die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")"
+                               else
+                                       remoteurl=.
+                               fi
                                ;;
                        esac
                        ;;
@@ -64,7 +99,8 @@ resolve_relative_url ()
                        break;;
                esac
        done
-       echo "$remoteurl$sep${url%/}"
+       remoteurl="$remoteurl$sep${url%/}"
+       echo "${is_relative:+${up_path}}${remoteurl#./}"
 }
 
 #
@@ -150,8 +186,10 @@ module_clone()
                die "$(eval_gettext "Clone of '\$url' into submodule path '\$sm_path' failed")"
        fi
 
-       a=$(cd "$gitdir" && pwd)/
-       b=$(cd "$sm_path" && pwd)/
+       # We already are at the root of the work tree but cd_to_toplevel will
+       # resolve any symlinks that might be present in $PWD
+       a=$(cd_to_toplevel && cd "$gitdir" && pwd)/
+       b=$(cd_to_toplevel && cd "$sm_path" && pwd)/
        # 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
@@ -965,14 +1003,26 @@ cmd_sync()
                # Possibly a url relative to parent
                case "$url" in
                ./*|../*)
-                       url=$(resolve_relative_url "$url") || exit
+                       # rewrite foo/bar as ../.. to find path from
+                       # submodule work tree to superproject work tree
+                       up_path="$(echo "$sm_path" | sed "s/[^/][^/]*/../g")" &&
+                       # guarantee a trailing /
+                       up_path=${up_path%/}/ &&
+                       # path from submodule work tree to submodule origin repo
+                       sub_origin_url=$(resolve_relative_url "$url" "$up_path") &&
+                       # path from superproject work tree to submodule origin repo
+                       super_config_url=$(resolve_relative_url "$url") || exit
+                       ;;
+               *)
+                       sub_origin_url="$url"
+                       super_config_url="$url"
                        ;;
                esac
 
                if git config "submodule.$name.url" >/dev/null 2>/dev/null
                then
                        say "$(eval_gettext "Synchronizing submodule url for '\$name'")"
-                       git config submodule."$name".url "$url"
+                       git config submodule."$name".url "$super_config_url"
 
                        if test -e "$sm_path"/.git
                        then
@@ -980,7 +1030,7 @@ cmd_sync()
                                clear_local_git_env
                                cd "$sm_path"
                                remote=$(get_default_remote)
-                               git config remote."$remote".url "$url"
+                               git config remote."$remote".url "$sub_origin_url"
                        )
                        fi
                fi
index 0b074c4c63ebb6e1334cf05371cd22087fb79f63..6673d21f841c7f5a4806bc7785c5ae7755f9297b 100755 (executable)
@@ -367,9 +367,9 @@ package main;
 eval {
        Git::SVN::verify_remotes_sanity();
        $cmd{$cmd}->[0]->(@ARGV);
+       post_fetch_checkout();
 };
 fatal $@ if $@;
-post_fetch_checkout();
 exit 0;
 
 ####################### primary functions ######################
@@ -1598,8 +1598,8 @@ sub rebase_cmd {
 
 sub post_fetch_checkout {
        return if $_no_checkout;
+       return if verify_ref('HEAD^0');
        my $gs = $Git::SVN::_head or return;
-       return if verify_ref('refs/heads/master^0');
 
        # look for "trunk" ref if it exists
        my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}};
@@ -1612,9 +1612,8 @@ sub post_fetch_checkout {
                }
        }
 
-       my $valid_head = verify_ref('HEAD^0');
-       command_noisy(qw(update-ref refs/heads/master), $gs->refname);
-       return if ($valid_head || !verify_ref('HEAD^0'));
+       command_noisy(qw(update-ref HEAD), $gs->refname);
+       return unless verify_ref('HEAD^0');
 
        return if $ENV{GIT_DIR} !~ m#^(?:.*/)?\.git$#;
        my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
diff --git a/git.c b/git.c
index d232de92e496bd5750015edce22d3b67f236b605..8788b32ccd891eb4db25fd6f1232935635d5a153 100644 (file)
--- a/git.c
+++ b/git.c
@@ -256,8 +256,6 @@ static int handle_alias(int *argcp, const char ***argv)
        return ret;
 }
 
-const char git_version_string[] = GIT_VERSION;
-
 #define RUN_SETUP              (1<<0)
 #define RUN_SETUP_GENTLY       (1<<1)
 #define USE_PAGER              (1<<2)
@@ -353,6 +351,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config, RUN_SETUP_GENTLY },
                { "count-objects", cmd_count_objects, RUN_SETUP },
+               { "credential", cmd_credential, RUN_SETUP_GENTLY },
                { "describe", cmd_describe, RUN_SETUP },
                { "diff", cmd_diff },
                { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
index 55e0e9ea38b3080e32467b6faf56f40d45386b96..3d6a7053881ec7646dc28a2eba991a15ff80c634 100755 (executable)
@@ -4484,30 +4484,33 @@ sub git_print_log {
        }
 
        # print log
-       my $signoff = 0;
-       my $empty = 0;
+       my $skip_blank_line = 0;
        foreach my $line (@$log) {
-               if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
-                       $signoff = 1;
-                       $empty = 0;
+               if ($line =~ m/^\s*([A-Z][-A-Za-z]*-[Bb]y|C[Cc]): /) {
                        if (! $opts{'-remove_signoff'}) {
                                print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
-                               next;
-                       } else {
-                               # remove signoff lines
-                               next;
+                               $skip_blank_line = 1;
                        }
-               } else {
-                       $signoff = 0;
+                       next;
+               }
+
+               if ($line =~ m,\s*([a-z]*link): (https?://\S+),i) {
+                       if (! $opts{'-remove_signoff'}) {
+                               print "<span class=\"signoff\">" . esc_html($1) . ": " .
+                                       "<a href=\"" . esc_html($2) . "\">" . esc_html($2) . "</a>" .
+                                       "</span><br/>\n";
+                               $skip_blank_line = 1;
+                       }
+                       next;
                }
 
                # print only one empty line
                # do not print empty line after signoff
                if ($line eq "") {
-                       next if ($empty || $signoff);
-                       $empty = 1;
+                       next if ($skip_blank_line);
+                       $skip_blank_line = 1;
                } else {
-                       $empty = 0;
+                       $skip_blank_line = 0;
                }
 
                print format_log_line_html($line) . "<br/>\n";
@@ -4515,7 +4518,7 @@ sub git_print_log {
 
        if ($opts{'-final_empty_line'}) {
                # end with single empty line
-               print "<br/>\n" unless $empty;
+               print "<br/>\n" unless $skip_blank_line;
        }
 }
 
diff --git a/help.c b/help.c
index 6012c07b735a541659cd75e2334563aeb933bfcb..662349dd56fd31d2ea2635657887640f43ce947c 100644 (file)
--- a/help.c
+++ b/help.c
@@ -6,6 +6,7 @@
 #include "common-cmds.h"
 #include "string-list.h"
 #include "column.h"
+#include "version.h"
 
 void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
diff --git a/http.c b/http.c
index 5cb87f16f25fe3d32e2594c8c6325d562eddc9ca..b61ac85d4b19a0b9121c5b19f2b9c731bbb238d4 100644 (file)
--- a/http.c
+++ b/http.c
@@ -4,6 +4,7 @@
 #include "run-command.h"
 #include "url.h"
 #include "credential.h"
+#include "version.h"
 
 int active_requests;
 int http_is_verbose;
@@ -299,7 +300,7 @@ static CURL *get_curl_handle(void)
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
 
        curl_easy_setopt(result, CURLOPT_USERAGENT,
-               user_agent ? user_agent : GIT_HTTP_USER_AGENT);
+               user_agent ? user_agent : git_user_agent());
 
        if (curl_ftp_no_epsv)
                curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
index 74aa77ce4be2bf23387a32e42cbdc72154c529c2..29c6411fc63a01460f95b25bf62115c350ef2191 100644 (file)
@@ -524,8 +524,10 @@ static int merge_from_diffs(struct notes_merge_options *o,
        free(changes);
 
        if (o->verbosity >= 4)
-               printf("Merge result: %i unmerged notes and a %s notes tree\n",
-                       conflicts, t->dirty ? "dirty" : "clean");
+               printf(t->dirty ?
+                      "Merge result: %i unmerged notes and a dirty notes tree\n" :
+                      "Merge result: %i unmerged notes and a clean notes tree\n",
+                      conflicts);
 
        return conflicts ? -1 : 1;
 }
index ab70c29c49b28eac1de3d7dd32814fa1e8544059..c1c66bd408c50685c06fe7ff1e1c6a78c26be1b1 100644 (file)
@@ -476,6 +476,7 @@ int parse_options(int argc, const char **argv, const char *prefix,
                usage_with_options(usagestr, options);
        }
 
+       precompose_argv(argc, argv);
        return parse_options_end(&ctx);
 }
 
diff --git a/path.c b/path.c
index 6f2aa699ad63c2f7c65632761efbaebb7f461868..66acd24fa8821861d032fc216f6ea6fdd711a969 100644 (file)
--- a/path.c
+++ b/path.c
@@ -87,6 +87,21 @@ char *git_pathdup(const char *fmt, ...)
        return xstrdup(path);
 }
 
+char *mkpathdup(const char *fmt, ...)
+{
+       char *path;
+       struct strbuf sb = STRBUF_INIT;
+       va_list args;
+
+       va_start(args, fmt);
+       strbuf_vaddf(&sb, fmt, args);
+       va_end(args);
+       path = xstrdup(cleanup_path(sb.buf));
+
+       strbuf_release(&sb);
+       return path;
+}
+
 char *mkpath(const char *fmt, ...)
 {
        va_list args;
@@ -122,6 +137,32 @@ char *git_path(const char *fmt, ...)
        return cleanup_path(pathname);
 }
 
+void home_config_paths(char **global, char **xdg, char *file)
+{
+       char *xdg_home = getenv("XDG_CONFIG_HOME");
+       char *home = getenv("HOME");
+       char *to_free = NULL;
+
+       if (!home) {
+               if (global)
+                       *global = NULL;
+       } else {
+               if (!xdg_home) {
+                       to_free = mkpathdup("%s/.config", home);
+                       xdg_home = to_free;
+               }
+               if (global)
+                       *global = mkpathdup("%s/.gitconfig", home);
+       }
+
+       if (!xdg_home)
+               *xdg = NULL;
+       else
+               *xdg = mkpathdup("%s/git/%s", xdg_home, file);
+
+       free(to_free);
+}
+
 char *git_path_submodule(const char *path, const char *fmt, ...)
 {
        char *pathname = get_pathname();
index fe7a48646412b872d8db09bdef70de751399cad6..6ca7d472ebad5c93c014ddaac556e6a4f2284990 100644 (file)
@@ -34,22 +34,34 @@ modules += Git/SVN/Ra
 
 $(makfile): ../GIT-CFLAGS Makefile
        echo all: private-Error.pm Git.pm Git/I18N.pm > $@
-       echo '  mkdir -p blib/lib/Git/SVN/Memoize' >> $@
        set -e; \
        for i in $(modules); \
        do \
+               if test $$i = $${i%/*}; \
+               then \
+                       subdir=; \
+               else \
+                       subdir=/$${i%/*}; \
+               fi; \
                echo '  $(RM) blib/lib/'$$i'.pm' >> $@; \
+               echo '  mkdir -p blib/lib'$$subdir >> $@; \
                echo '  cp '$$i'.pm blib/lib/'$$i'.pm' >> $@; \
        done
        echo '  $(RM) blib/lib/Error.pm' >> $@
        '$(PERL_PATH_SQ)' -MError -e 'exit($$Error::VERSION < 0.15009)' || \
        echo '  cp private-Error.pm blib/lib/Error.pm' >> $@
        echo install: >> $@
-       echo '  mkdir -p "$$(DESTDIR)$(instdir_SQ)/Git/SVN/Memoize"' >> $@
        set -e; \
        for i in $(modules); \
        do \
+               if test $$i = $${i%/*}; \
+               then \
+                       subdir=; \
+               else \
+                       subdir=/$${i%/*}; \
+               fi; \
                echo '  $(RM) "$$(DESTDIR)$(instdir_SQ)/'$$i'.pm"' >> $@; \
+               echo '  mkdir -p "$$(DESTDIR)$(instdir_SQ)'$$subdir'"' >> $@; \
                echo '  cp '$$i'.pm "$$(DESTDIR)$(instdir_SQ)/'$$i'.pm"' >> $@; \
        done
        echo '  $(RM) "$$(DESTDIR)$(instdir_SQ)/Error.pm"' >> $@
index 5a04984ea31744528de30269add2754ed2dcd105..eaba15f124b5d8247d39bd0844967cb5a9101ba8 100644 (file)
@@ -135,13 +135,19 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
        strbuf_add(buf, buffer, n);
 }
 
-static void safe_read(int fd, void *buffer, unsigned size)
+static int safe_read(int fd, void *buffer, unsigned size, int return_line_fail)
 {
        ssize_t ret = read_in_full(fd, buffer, size);
        if (ret < 0)
                die_errno("read error");
-       else if (ret < size)
+       else if (ret < size) {
+               if (return_line_fail)
+                       return -1;
+
                die("The remote end hung up unexpectedly");
+       }
+
+       return ret;
 }
 
 static int packet_length(const char *linelen)
@@ -169,12 +175,14 @@ static int packet_length(const char *linelen)
        return len;
 }
 
-int packet_read_line(int fd, char *buffer, unsigned size)
+static int packet_read_internal(int fd, char *buffer, unsigned size, int return_line_fail)
 {
-       int len;
+       int len, ret;
        char linelen[4];
 
-       safe_read(fd, linelen, 4);
+       ret = safe_read(fd, linelen, 4, return_line_fail);
+       if (return_line_fail && ret < 0)
+               return ret;
        len = packet_length(linelen);
        if (len < 0)
                die("protocol error: bad line length character: %.4s", linelen);
@@ -185,12 +193,24 @@ int packet_read_line(int fd, char *buffer, unsigned size)
        len -= 4;
        if (len >= size)
                die("protocol error: bad line length %d", len);
-       safe_read(fd, buffer, len);
+       ret = safe_read(fd, buffer, len, return_line_fail);
+       if (return_line_fail && ret < 0)
+               return ret;
        buffer[len] = 0;
        packet_trace(buffer, len, 0);
        return len;
 }
 
+int packet_read(int fd, char *buffer, unsigned size)
+{
+       return packet_read_internal(fd, buffer, size, 1);
+}
+
+int packet_read_line(int fd, char *buffer, unsigned size)
+{
+       return packet_read_internal(fd, buffer, size, 0);
+}
+
 int packet_get_line(struct strbuf *out,
        char **src_buf, size_t *src_len)
 {
index 1e5dcfe87c568eaa7746091b1eb059834f4913bb..8cfeb0c31c8661bf11ce78d3679a21cd030ffb75 100644 (file)
@@ -13,6 +13,7 @@ void packet_buf_flush(struct strbuf *buf);
 void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
 
 int packet_read_line(int fd, char *buffer, unsigned size);
+int packet_read(int fd, char *buffer, unsigned size);
 int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len);
 ssize_t safe_write(int, const void *, ssize_t);
 
index 70d8418f73485ebeb3ea692f67f05f0ad2338d16..0cbcd64a7e1d25d677e03edb8b4499ea9d6f008c 100644 (file)
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git 1.7.11\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2012-06-08 10:20+0800\n"
+"POT-Creation-Date: 2012-07-03 10:23+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"
@@ -57,8 +57,8 @@ msgstr "Konnte '%s' nicht öffnen"
 msgid "Repository lacks these prerequisite commits:"
 msgstr "Dem Projektarchiv fehlen folgende vorrausgesetzte Versionen:"
 
-#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
-#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:290
+#: builtin/log.c:721 builtin/log.c:1310 builtin/log.c:1529 builtin/merge.c:347
 #: builtin/shortlog.c:181
 msgid "revision walk setup failed"
 msgstr "Einrichtung des Revisionsgangs fehlgeschlagen"
@@ -71,44 +71,48 @@ msgstr[0] "Das Paket enthält %d Referenz"
 msgstr[1] "Das Paket enthält %d Referenzen"
 
 #: bundle.c:192
+msgid "The bundle records a complete history."
+msgstr "Das Paket speichert eine komplette Historie."
+
+#: bundle.c:195
 #, c-format
 msgid "The bundle requires this ref"
 msgid_plural "The bundle requires these %d refs"
 msgstr[0] "Das Paket benötigt diese Referenz"
 msgstr[1] "Das Paket benötigt diese %d Referenzen"
 
-#: bundle.c:290
+#: bundle.c:294
 msgid "rev-list died"
 msgstr "\"rev-list\" abgebrochen"
 
-#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#: bundle.c:300 builtin/log.c:1206 builtin/shortlog.c:284
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr "nicht erkanntes Argument: %s"
 
-#: bundle.c:331
+#: bundle.c:335
 #, c-format
 msgid "ref '%s' is excluded by the rev-list options"
 msgstr "Referenz '%s' wird durch \"rev-list\" Optionen ausgeschlossen"
 
-#: bundle.c:376
+#: bundle.c:380
 msgid "Refusing to create empty bundle."
 msgstr "Erstellung eines leeren Pakets zurückgewiesen."
 
-#: bundle.c:394
+#: bundle.c:398
 msgid "Could not spawn pack-objects"
 msgstr "Konnte Paketobjekte nicht erstellen"
 
-#: bundle.c:412
+#: bundle.c:416
 msgid "pack-objects died"
 msgstr "Erstellung der Paketobjekte abgebrochen"
 
-#: bundle.c:415
+#: bundle.c:419
 #, c-format
 msgid "cannot create '%s'"
 msgstr "kann '%s' nicht erstellen"
 
-#: bundle.c:437
+#: bundle.c:441
 msgid "index-pack died"
 msgstr "Erstellung der Paketindexdatei abgebrochen"
 
@@ -288,16 +292,16 @@ msgstr "'%s': %s"
 msgid "'%s': short read %s"
 msgstr "'%s': read() zu kurz %s"
 
-#: help.c:207
+#: help.c:208
 #, c-format
 msgid "available git commands in '%s'"
 msgstr "Vorhandene Git-Kommandos in '%s'"
 
-#: help.c:214
+#: help.c:215
 msgid "git commands available from elsewhere on your $PATH"
 msgstr "Vorhandene Git-Kommandos irgendwo in deinem $PATH"
 
-#: help.c:270
+#: help.c:271
 #, c-format
 msgid ""
 "'%s' appears to be a git command, but we were not\n"
@@ -306,11 +310,11 @@ msgstr ""
 "'%s' scheint ein git-Kommando zu sein, konnte aber\n"
 "nicht ausgeführt werden. Vielleicht ist git-%s fehlerhaft?"
 
-#: help.c:327
+#: help.c:328
 msgid "Uh oh. Your system reports no Git commands at all."
 msgstr "Uh oh. Keine Git-Kommandos auf deinem System vorhanden."
 
-#: help.c:349
+#: help.c:350
 #, c-format
 msgid ""
 "WARNING: You called a Git command named '%s', which does not exist.\n"
@@ -319,17 +323,17 @@ msgstr ""
 "Warnung: Du hast das nicht existierende Git-Kommando '%s' ausgeführt.\n"
 "Setze fort unter der Annahme das du '%s' gemeint hast"
 
-#: help.c:354
+#: help.c:355
 #, c-format
 msgid "in %0.1f seconds automatically..."
 msgstr "automatisch in %0.1f Sekunden..."
 
-#: help.c:361
+#: help.c:362
 #, c-format
 msgid "git: '%s' is not a git command. See 'git --help'."
 msgstr "git: '%s' ist kein Git-Kommando. Siehe 'git --help'."
 
-#: help.c:365
+#: help.c:366
 msgid ""
 "\n"
 "Did you mean this?"
@@ -673,243 +677,351 @@ msgstr "konnte aktuellen Benutzer nicht in Passwort-Datei finden: %s"
 msgid "no such user"
 msgstr "kein solcher Benutzer"
 
-#: wt-status.c:135
+#: wt-status.c:141
 msgid "Unmerged paths:"
 msgstr "Nicht zusammengeführte Pfade:"
 
-#: wt-status.c:141 wt-status.c:158
+#: wt-status.c:168 wt-status.c:195
 #, c-format
 msgid "  (use \"git reset %s <file>...\" to unstage)"
 msgstr ""
 "  (benutze \"git reset %s <Datei>...\" zum Herausnehmen aus der "
 "Bereitstellung)"
 
-#: wt-status.c:143 wt-status.c:160
+#: wt-status.c:170 wt-status.c:197
 msgid "  (use \"git rm --cached <file>...\" to unstage)"
 msgstr ""
 "  (benutze \"git rm --cached <Datei>...\" zum Herausnehmen aus der "
 "Bereitstellung)"
 
-#: wt-status.c:144
+#: wt-status.c:174
+msgid "  (use \"git add <file>...\" to mark resolution)"
+msgstr "  (benutze \"git add/rm <Datei>...\" um die Auflösung zu markieren)"
+
+#: wt-status.c:176 wt-status.c:180
 msgid "  (use \"git add/rm <file>...\" as appropriate to mark resolution)"
 msgstr ""
 "  (benutze \"git add/rm <Datei>...\" um die Auflösung entsprechend zu "
 "markieren)"
 
-#: wt-status.c:152
+#: wt-status.c:178
+msgid "  (use \"git rm <file>...\" to mark resolution)"
+msgstr "  (benutze \"git add/rm <Datei>...\" um die Auflösung zu markieren)"
+
+#: wt-status.c:189
 msgid "Changes to be committed:"
 msgstr "zum Eintragen bereitgestellte Änderungen:"
 
-#: wt-status.c:170
+#: wt-status.c:207
 msgid "Changes not staged for commit:"
 msgstr "Änderungen, die nicht zum Eintragen bereitgestellt sind:"
 
-#: wt-status.c:174
+#: wt-status.c:211
 msgid "  (use \"git add <file>...\" to update what will be committed)"
 msgstr "  (benutze \"git add <Datei>...\" zum Bereitstellen)"
 
-#: wt-status.c:176
+#: wt-status.c:213
 msgid "  (use \"git add/rm <file>...\" to update what will be committed)"
 msgstr "  (benutze \"git add/rm <Datei>...\" zum Bereitstellen)"
 
-#: wt-status.c:177
+#: wt-status.c:214
 msgid ""
 "  (use \"git checkout -- <file>...\" to discard changes in working directory)"
 msgstr ""
 "  (benutze \"git checkout -- <Datei>...\" um die Änderungen im "
 "Arbeitsverzeichnis zu verwerfen)"
 
-#: wt-status.c:179
+#: wt-status.c:216
 msgid "  (commit or discard the untracked or modified content in submodules)"
 msgstr ""
 "  (trage ein oder verwerfe den unbeobachteten oder geänderten Inhalt in den "
 "Unterprojekten)"
 
-#: wt-status.c:188
+#: wt-status.c:225
 #, c-format
 msgid "%s files:"
 msgstr "%s Dateien:"
 
-#: wt-status.c:191
+#: wt-status.c:228
 #, c-format
 msgid "  (use \"git %s <file>...\" to include in what will be committed)"
 msgstr "  (benutze \"git %s <Datei>...\" zum Einfügen in die Eintragung)"
 
-#: wt-status.c:208
+#: wt-status.c:245
 msgid "bug"
 msgstr "Fehler"
 
-#: wt-status.c:213
+#: wt-status.c:250
 msgid "both deleted:"
 msgstr "beide gelöscht:"
 
-#: wt-status.c:214
+#: wt-status.c:251
 msgid "added by us:"
 msgstr "von uns hinzugefügt:"
 
-#: wt-status.c:215
+#: wt-status.c:252
 msgid "deleted by them:"
 msgstr "von denen gelöscht:"
 
-#: wt-status.c:216
+#: wt-status.c:253
 msgid "added by them:"
 msgstr "von denen hinzugefügt:"
 
-#: wt-status.c:217
+#: wt-status.c:254
 msgid "deleted by us:"
 msgstr "von uns gelöscht:"
 
-#: wt-status.c:218
+#: wt-status.c:255
 msgid "both added:"
 msgstr "von beiden hinzugefügt:"
 
-#: wt-status.c:219
+#: wt-status.c:256
 msgid "both modified:"
 msgstr "von beiden geändert:"
 
-#: wt-status.c:249
+#: wt-status.c:286
 msgid "new commits, "
 msgstr "neue Versionen, "
 
-#: wt-status.c:251
+#: wt-status.c:288
 msgid "modified content, "
 msgstr "geänderter Inhalt, "
 
-#: wt-status.c:253
+#: wt-status.c:290
 msgid "untracked content, "
 msgstr "unbeobachteter Inhalt, "
 
-#: wt-status.c:267
+#: wt-status.c:304
 #, c-format
 msgid "new file:   %s"
 msgstr "neue Datei:   %s"
 
-#: wt-status.c:270
+#: wt-status.c:307
 #, c-format
 msgid "copied:     %s -> %s"
 msgstr "kopiert:     %s -> %s"
 
-#: wt-status.c:273
+#: wt-status.c:310
 #, c-format
 msgid "deleted:    %s"
 msgstr "gelöscht:    %s"
 
-#: wt-status.c:276
+#: wt-status.c:313
 #, c-format
 msgid "modified:   %s"
 msgstr "geändert:   %s"
 
-#: wt-status.c:279
+#: wt-status.c:316
 #, c-format
 msgid "renamed:    %s -> %s"
 msgstr "umbenannt:    %s -> %s"
 
-#: wt-status.c:282
+#: wt-status.c:319
 #, c-format
 msgid "typechange: %s"
 msgstr "Typänderung: %s"
 
-#: wt-status.c:285
+#: wt-status.c:322
 #, c-format
 msgid "unknown:    %s"
 msgstr "unbekannt:    %s"
 
-#: wt-status.c:288
+#: wt-status.c:325
 #, c-format
 msgid "unmerged:   %s"
 msgstr "nicht zusammengeführt:   %s"
 
-#: wt-status.c:291
+#: wt-status.c:328
 #, c-format
 msgid "bug: unhandled diff status %c"
 msgstr "Fehler: unbehandelter Differenz-Status %c"
 
-#: wt-status.c:737
+#: wt-status.c:786
+msgid "You have unmerged paths."
+msgstr "Du hast nicht zusammengeführte Pfade."
+
+#: wt-status.c:789 wt-status.c:913
+msgid "  (fix conflicts and run \"git commit\")"
+msgstr " (behebe die Konflikte und führe \"git commit\" aus)"
+
+#: wt-status.c:792
+msgid "All conflicts fixed but you are still merging."
+msgstr "Alle Konflikte sind behoben, aber du bist immer noch beim "
+"Zusammenführen."
+
+#: wt-status.c:795
+msgid "  (use \"git commit\" to conclude merge)"
+msgstr "  (benutze \"git commit\" um die Zusammenführung abzuschließen)"
+
+#: wt-status.c:805
+msgid "You are in the middle of an am session."
+msgstr "Eine \"am\"-Sitzung ist im Gange."
+
+#: wt-status.c:808
+msgid "The current patch is empty."
+msgstr "Der aktuelle Patch ist leer."
+
+#: wt-status.c:812
+msgid "  (fix conflicts and then run \"git am --resolved\")"
+msgstr "  (behebe die Konflikte und führe dann \"git am --resolved\" aus)"
+
+#: wt-status.c:814
+msgid "  (use \"git am --skip\" to skip this patch)"
+msgstr " (benutze \"git am --skip\" um diesen Patch auszulassen)"
+
+#: wt-status.c:816
+msgid "  (use \"git am --abort\" to restore the original branch)"
+msgstr "  (benutze \"git am --abort\" um den ursprünglichen Zweig "
+"wiederherzustellen)"
+
+#: wt-status.c:874 wt-status.c:884
+msgid "You are currently rebasing."
+msgstr "Du bist gerade beim Neuaufbau."
+
+#: wt-status.c:877
+msgid "  (fix conflicts and then run \"git rebase --continue\")"
+msgstr "  (behebe die Konflikte und führe dann \"git rebase --continue\" aus)"
+
+#: wt-status.c:879
+msgid "  (use \"git rebase --skip\" to skip this patch)"
+msgstr "  (benutze \"git rebase --skip\" um diesen Patch auszulassen)"
+
+#: wt-status.c:881
+msgid "  (use \"git rebase --abort\" to check out the original branch)"
+msgstr "  (benutze \"git rebase --abort\" um den ursprünglichen Zweig "
+"auszuchecken)"
+
+#: wt-status.c:887
+msgid "  (all conflicts fixed: run \"git rebase --continue\")"
+msgstr "  (alle Konflikte behoben: führe \"git rebase --continue\" aus)"
+
+#: wt-status.c:889
+msgid "You are currently splitting a commit during a rebase."
+msgstr "Du teilst gerade eine Version während eines Neuaufbaus auf."
+
+#: wt-status.c:892
+msgid "  (Once your working directory is clean, run \"git rebase --continue\")"
+msgstr "  (Sobald dein Arbeitsverzeichnis sauber ist, führe "
+"\"git rebase --continue\" aus)"
+
+#: wt-status.c:894
+msgid "You are currently editing a commit during a rebase."
+msgstr "Du editierst gerade eine Version während eines Neuaufbaus."
+
+#: wt-status.c:897
+msgid "  (use \"git commit --amend\" to amend the current commit)"
+msgstr "  (benutze \"git commit --amend\" um die aktuelle Version "
+"nachzubessern)"
+
+#: wt-status.c:899
+msgid ""
+"  (use \"git rebase --continue\" once you are satisfied with your changes)"
+msgstr "  (benutze \"git rebase --continue\" sobald deine Änderungen "
+"abgeschlossen sind)"
+
+#: wt-status.c:909
+msgid "You are currently cherry-picking."
+msgstr "Du führst gerade \"cherry-pick\" aus."
+
+#: wt-status.c:916
+msgid "  (all conflicts fixed: run \"git commit\")"
+msgstr "  (alle Konflikte behoben: führe \"git commit\" aus)"
+
+#: wt-status.c:925
+msgid "You are currently bisecting."
+msgstr "Du bist gerade beim Halbieren."
+
+#: wt-status.c:928
+msgid "  (use \"git bisect reset\" to get back to the original branch)"
+msgstr "  (benutze \"git bisect reset\" um zum ursprünglichen Zweig "
+"zurückzukehren)"
+
+#: wt-status.c:979
 msgid "On branch "
 msgstr "Auf Zweig "
 
-#: wt-status.c:744
+#: wt-status.c:986
 msgid "Not currently on any branch."
 msgstr "Im Moment auf keinem Zweig."
 
-#: wt-status.c:755
+#: wt-status.c:998
 msgid "Initial commit"
 msgstr "Initiale Version"
 
-#: wt-status.c:769
+#: wt-status.c:1012
 msgid "Untracked"
 msgstr "Unbeobachtete"
 
-#: wt-status.c:771
+#: wt-status.c:1014
 msgid "Ignored"
 msgstr "Ignorierte"
 
-#: wt-status.c:773
+#: wt-status.c:1016
 #, c-format
 msgid "Untracked files not listed%s"
 msgstr "Unbeobachtete Dateien nicht aufgelistet%s"
 
-#: wt-status.c:775
+#: wt-status.c:1018
 msgid " (use -u option to show untracked files)"
 msgstr " (benutze die Option -u um unbeobachteten Dateien anzuzeigen)"
 
-#: wt-status.c:781
+#: wt-status.c:1024
 msgid "No changes"
 msgstr "Keine Änderungen"
 
-#: wt-status.c:785
+#: wt-status.c:1028
 #, c-format
 msgid "no changes added to commit%s\n"
 msgstr "keine Änderungen zum Eintragen hinzugefügt%s\n"
 
-#: wt-status.c:787
+#: wt-status.c:1030
 msgid " (use \"git add\" and/or \"git commit -a\")"
 msgstr " (benutze \"git add\" und/oder \"git commit -a\")"
 
-#: wt-status.c:789
+#: wt-status.c:1032
 #, c-format
 msgid "nothing added to commit but untracked files present%s\n"
 msgstr ""
 "nichts zum Eintragen hinzugefügt, aber es gibt unbeobachtete Dateien%s\n"
 
-#: wt-status.c:791
+#: wt-status.c:1034
 msgid " (use \"git add\" to track)"
 msgstr " (benutze \"git add\" zum Beobachten)"
 
-#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#: wt-status.c:1036 wt-status.c:1039 wt-status.c:1042
 #, c-format
 msgid "nothing to commit%s\n"
 msgstr "nichts zum Eintragen%s\n"
 
-#: wt-status.c:794
+#: wt-status.c:1037
 msgid " (create/copy files and use \"git add\" to track)"
 msgstr " (Erstelle/Kopiere Dateien und benutze \"git add\" zum Beobachten)"
 
-#: wt-status.c:797
+#: wt-status.c:1040
 msgid " (use -u to show untracked files)"
 msgstr " (benutze die Option -u um unbeobachtete Dateien anzuzeigen)"
 
-#: wt-status.c:800
+#: wt-status.c:1043
 msgid " (working directory clean)"
 msgstr " (Arbeitsverzeichnis sauber)"
 
-#: wt-status.c:908
+#: wt-status.c:1151
 msgid "HEAD (no branch)"
 msgstr "HEAD (kein Zweig)"
 
-#: wt-status.c:914
+#: wt-status.c:1157
 msgid "Initial commit on "
 msgstr "Initiale Version auf "
 
-#: wt-status.c:929
+#: wt-status.c:1172
 msgid "behind "
 msgstr "hinterher "
 
-#: wt-status.c:932 wt-status.c:935
+#: wt-status.c:1175 wt-status.c:1178
 msgid "ahead "
 msgstr "voraus "
 
-#: wt-status.c:937
+#: wt-status.c:1180
 msgid ", behind "
 msgstr ", hinterher "
 
@@ -937,7 +1049,7 @@ msgid "Unstaged changes after refreshing the index:"
 msgstr ""
 "Nicht bereitgestellte Änderungen nach Aktualisierung der Bereitstellung:"
 
-#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#: builtin/add.c:195 builtin/add.c:459 builtin/rm.c:186
 #, c-format
 msgid "pathspec '%s' did not match any files"
 msgstr "Pfadspezifikation '%s' stimmt mit keinen Dateien überein"
@@ -1017,7 +1129,7 @@ msgstr "Wolltest du vielleicht 'git add .' sagen?\n"
 msgid "index file corrupt"
 msgstr "Bereitstellungsdatei beschädigt"
 
-#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+#: builtin/add.c:480 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
 msgid "Unable to write new index file"
 msgstr "Konnte neue Bereitstellungsdatei nicht schreiben."
 
@@ -2608,22 +2720,22 @@ msgstr "Ungültige Option: %s"
 msgid "Not a git repository"
 msgstr "Kein Git-Projektarchiv"
 
-#: builtin/diff.c:347
+#: builtin/diff.c:341
 #, c-format
 msgid "invalid object '%s' given."
 msgstr "Objekt '%s' ist ungültig."
 
-#: builtin/diff.c:352
+#: builtin/diff.c:346
 #, c-format
 msgid "more than %d trees given: '%s'"
 msgstr "Mehr als %d Zweige angegeben: '%s'"
 
-#: builtin/diff.c:362
+#: builtin/diff.c:356
 #, c-format
 msgid "more than two blobs given: '%s'"
 msgstr "Mehr als zwei Blobs angegeben: '%s'"
 
-#: builtin/diff.c:370
+#: builtin/diff.c:364
 #, c-format
 msgid "unhandled object '%s' given."
 msgstr "unbehandeltes Objekt '%s' angegeben"
@@ -2882,30 +2994,30 @@ msgstr ""
 msgid "both --cached and trees are given."
 msgstr "sowohl --cached als auch Zweige gegeben"
 
-#: builtin/help.c:59
+#: builtin/help.c:63
 #, c-format
 msgid "unrecognized help format '%s'"
 msgstr "nicht erkanntes Hilfeformat: %s"
 
-#: builtin/help.c:87
+#: builtin/help.c:91
 msgid "Failed to start emacsclient."
 msgstr "Konnte emacsclient nicht starten."
 
-#: builtin/help.c:100
+#: builtin/help.c:104
 msgid "Failed to parse emacsclient version."
 msgstr "Konnte Version des emacsclient nicht parsen."
 
-#: builtin/help.c:108
+#: builtin/help.c:112
 #, c-format
 msgid "emacsclient version '%d' too old (< 22)."
 msgstr "Version des emacsclient '%d' ist zu alt (< 22)."
 
-#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#: builtin/help.c:130 builtin/help.c:158 builtin/help.c:167 builtin/help.c:175
 #, c-format
 msgid "failed to exec '%s': %s"
 msgstr "Fehler beim Ausführen von '%s': %s"
 
-#: builtin/help.c:211
+#: builtin/help.c:215
 #, c-format
 msgid ""
 "'%s': path for unsupported man viewer.\n"
@@ -2914,7 +3026,7 @@ msgstr ""
 "'%s': Pfad für nicht unterstützten Handbuchbetrachter.\n"
 "Du könntest stattdessen 'man.<Werkzeug>.cmd' benutzen."
 
-#: builtin/help.c:223
+#: builtin/help.c:227
 #, c-format
 msgid ""
 "'%s': cmd for supported man viewer.\n"
@@ -2923,267 +3035,278 @@ msgstr ""
 "'%s': Kommando für unterstützten Handbuchbetrachter.\n"
 "Du könntest stattdessen 'man.<Werkzeug>.path' benutzen."
 
-#: builtin/help.c:287
+#: builtin/help.c:291
 msgid "The most commonly used git commands are:"
 msgstr "Die allgemein verwendeten Git-Kommandos sind:"
 
-#: builtin/help.c:355
+#: builtin/help.c:359
 #, c-format
 msgid "'%s': unknown man viewer."
 msgstr "'%s': unbekannter Handbuch-Betrachter."
 
-#: builtin/help.c:372
+#: builtin/help.c:376
 msgid "no man viewer handled the request"
 msgstr "kein Handbuch-Betrachter konnte mit dieser Anfrage umgehen"
 
-#: builtin/help.c:380
+#: builtin/help.c:384
 msgid "no info viewer handled the request"
 msgstr "kein Informations-Betrachter konnte mit dieser Anfrage umgehen"
 
-#: builtin/help.c:391
+#: builtin/help.c:395
 #, c-format
 msgid "'%s': not a documentation directory."
 msgstr "'%s' ist kein Dokumentationsverzeichnis"
 
-#: builtin/help.c:432 builtin/help.c:439
+#: builtin/help.c:436 builtin/help.c:443
 #, c-format
 msgid "usage: %s%s"
 msgstr "Verwendung: %s%s"
 
-#: builtin/help.c:453
+#: builtin/help.c:459
 #, c-format
 msgid "`git %s' is aliased to `%s'"
 msgstr "für `git %s' wurde der Alias `%s' angelegt"
 
-#: builtin/index-pack.c:169
+#: builtin/index-pack.c:170
 #, c-format
 msgid "object type mismatch at %s"
 msgstr "Objekt-Typen passen bei %s nicht zusammen"
 
-#: builtin/index-pack.c:189
+#: builtin/index-pack.c:190
 msgid "object of unexpected type"
 msgstr "Objekt hat unerwarteten Typ"
 
-#: builtin/index-pack.c:226
+#: builtin/index-pack.c:227
 #, c-format
 msgid "cannot fill %d byte"
 msgid_plural "cannot fill %d bytes"
 msgstr[0] "kann %d Byte nicht lesen"
 msgstr[1] "kann %d Bytes nicht lesen"
 
-#: builtin/index-pack.c:236
+#: builtin/index-pack.c:237
 msgid "early EOF"
 msgstr "zu frühes Dateiende"
 
-#: builtin/index-pack.c:237
+#: builtin/index-pack.c:238
 msgid "read error on input"
 msgstr "Fehler beim Lesen der Eingabe"
 
-#: builtin/index-pack.c:249
+#: builtin/index-pack.c:250
 msgid "used more bytes than were available"
 msgstr "verwendete mehr Bytes als verfügbar waren"
 
-#: builtin/index-pack.c:256
+#: builtin/index-pack.c:257
 msgid "pack too large for current definition of off_t"
 msgstr "Paket ist zu groß für die aktuelle Definition von off_t"
 
-#: builtin/index-pack.c:272
+#: builtin/index-pack.c:273
 #, c-format
 msgid "unable to create '%s'"
 msgstr "konnte '%s' nicht erstellen"
 
-#: builtin/index-pack.c:277
+#: builtin/index-pack.c:278
 #, c-format
 msgid "cannot open packfile '%s'"
 msgstr "Kann Paketdatei '%s' nicht öffnen"
 
-#: builtin/index-pack.c:291
+#: builtin/index-pack.c:292
 msgid "pack signature mismatch"
 msgstr "Paketsignatur stimmt nicht überein"
 
-#: builtin/index-pack.c:311
+#: builtin/index-pack.c:312
 #, c-format
 msgid "pack has bad object at offset %lu: %s"
 msgstr "Paket hat ein ungültiges Objekt bei Versatz %lu: %s"
 
-#: builtin/index-pack.c:405
+#: builtin/index-pack.c:434
 #, c-format
 msgid "inflate returned %d"
 msgstr "Dekomprimierung gab %d zurück"
 
-#: builtin/index-pack.c:450
+#: builtin/index-pack.c:483
 msgid "offset value overflow for delta base object"
 msgstr "Wert für Versatz bei Differenzobjekt übergelaufen"
 
-#: builtin/index-pack.c:458
+#: builtin/index-pack.c:491
 msgid "delta base offset is out of bound"
 msgstr ""
 "Wert für Versatz bei Differenzobjekt liegt außerhalb des gültigen Bereichs"
 
-#: builtin/index-pack.c:466
+#: builtin/index-pack.c:499
 #, c-format
 msgid "unknown object type %d"
 msgstr "Unbekannter Objekt-Typ %d"
 
-#: builtin/index-pack.c:495
+#: builtin/index-pack.c:531
 msgid "cannot pread pack file"
 msgstr "Kann Paketdatei %s nicht lesen"
 
-#: builtin/index-pack.c:497
+#: builtin/index-pack.c:533
 #, c-format
 msgid "premature end of pack file, %lu byte missing"
 msgid_plural "premature end of pack file, %lu bytes missing"
 msgstr[0] "frühzeitiges Ende der Paketdatei, vermisse %lu Byte"
 msgstr[1] "frühzeitiges Ende der Paketdatei, vermisse %lu Bytes"
 
-#: builtin/index-pack.c:510
+#: builtin/index-pack.c:555
 msgid "serious inflate inconsistency"
 msgstr "ernsthafte Inkonsistenz nach Dekomprimierung"
 
-#: builtin/index-pack.c:583
-#, c-format
-msgid "cannot read existing object %s"
-msgstr "Kann existierendes Objekt %s nicht lesen."
-
-#: builtin/index-pack.c:586
+#: builtin/index-pack.c:646 builtin/index-pack.c:652 builtin/index-pack.c:675
+#: builtin/index-pack.c:709 builtin/index-pack.c:718
 #, c-format
 msgid "SHA1 COLLISION FOUND WITH %s !"
 msgstr "SHA1 KOLLISION MIT %s GEFUNDEN !"
 
-#: builtin/index-pack.c:598
+#: builtin/index-pack.c:649 builtin/pack-objects.c:170
+#: builtin/pack-objects.c:262
+#, c-format
+msgid "unable to read %s"
+msgstr "kann %s nicht lesen"
+
+#: builtin/index-pack.c:715
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "Kann existierendes Objekt %s nicht lesen."
+
+#: builtin/index-pack.c:729
 #, c-format
 msgid "invalid blob object %s"
 msgstr "ungültiges Blob-Objekt %s"
 
-#: builtin/index-pack.c:610
+#: builtin/index-pack.c:744
 #, c-format
 msgid "invalid %s"
 msgstr "Ungültiger Objekt-Typ %s"
 
-#: builtin/index-pack.c:612
+#: builtin/index-pack.c:746
 msgid "Error in object"
 msgstr "Fehler in Objekt"
 
-#: builtin/index-pack.c:614
+#: builtin/index-pack.c:748
 #, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "Nicht alle Kind-Objekte von %s sind erreichbar"
 
-#: builtin/index-pack.c:687 builtin/index-pack.c:713
+#: builtin/index-pack.c:818 builtin/index-pack.c:844
 msgid "failed to apply delta"
 msgstr "Konnte Dateiunterschied nicht anwenden"
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Receiving objects"
 msgstr "Empfange Objekte"
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Indexing objects"
 msgstr "Indiziere Objekte"
 
-#: builtin/index-pack.c:872
+#: builtin/index-pack.c:1009
 msgid "pack is corrupted (SHA1 mismatch)"
 msgstr "Paket ist beschädigt (SHA1 unterschiedlich)"
 
-#: builtin/index-pack.c:877
+#: builtin/index-pack.c:1014
 msgid "cannot fstat packfile"
 msgstr "kann Paketdatei nicht lesen"
 
-#: builtin/index-pack.c:880
+#: builtin/index-pack.c:1017
 msgid "pack has junk at the end"
 msgstr "Paketende enthält nicht verwendbaren Inhalt"
 
-#: builtin/index-pack.c:903
+#: builtin/index-pack.c:1028
+msgid "confusion beyond insanity in parse_pack_objects()"
+msgstr "Fehler beim Ausführen von \"parse_pack_objects()\""
+
+#: builtin/index-pack.c:1051
 msgid "Resolving deltas"
 msgstr "Löse Unterschiede auf"
 
-#: builtin/index-pack.c:954
+#: builtin/index-pack.c:1102
 msgid "confusion beyond insanity"
 msgstr "Fehler beim Auflösen der Unterschiede"
 
-#: builtin/index-pack.c:973
+#: builtin/index-pack.c:1121
 #, c-format
 msgid "pack has %d unresolved delta"
 msgid_plural "pack has %d unresolved deltas"
 msgstr[0] "Paket hat %d unaufgelöste Unterschied"
 msgstr[1] "Paket hat %d unaufgelöste Unterschiede"
 
-#: builtin/index-pack.c:998
+#: builtin/index-pack.c:1146
 #, c-format
 msgid "unable to deflate appended object (%d)"
 msgstr "Konnte angehängtes Objekt (%d) nicht komprimieren"
 
-#: builtin/index-pack.c:1077
+#: builtin/index-pack.c:1225
 #, c-format
 msgid "local object %s is corrupt"
 msgstr "lokales Objekt %s ist beschädigt"
 
-#: builtin/index-pack.c:1101
+#: builtin/index-pack.c:1249
 msgid "error while closing pack file"
 msgstr "Fehler beim Schließen der Paketdatei"
 
-#: builtin/index-pack.c:1114
+#: builtin/index-pack.c:1262
 #, c-format
 msgid "cannot write keep file '%s'"
 msgstr "Kann Paketbeschreibungsdatei '%s' nicht schreiben"
 
-#: builtin/index-pack.c:1122
+#: builtin/index-pack.c:1270
 #, c-format
 msgid "cannot close written keep file '%s'"
 msgstr "Kann eben erstellte Paketbeschreibungsdatei '%s' nicht schließen"
 
-#: builtin/index-pack.c:1135
+#: builtin/index-pack.c:1283
 msgid "cannot store pack file"
 msgstr "Kann Paketdatei nicht speichern"
 
-#: builtin/index-pack.c:1146
+#: builtin/index-pack.c:1294
 msgid "cannot store index file"
 msgstr "Kann Indexdatei nicht speichern"
 
-#: builtin/index-pack.c:1247
+#: builtin/index-pack.c:1395
 #, c-format
 msgid "Cannot open existing pack file '%s'"
 msgstr "Kann existierende Paketdatei '%s' nicht öffnen"
 
-#: builtin/index-pack.c:1249
+#: builtin/index-pack.c:1397
 #, c-format
 msgid "Cannot open existing pack idx file for '%s'"
 msgstr "Kann existierende Indexdatei für Paket '%s' nicht öffnen"
 
-#: builtin/index-pack.c:1296
+#: builtin/index-pack.c:1444
 #, c-format
 msgid "non delta: %d object"
 msgid_plural "non delta: %d objects"
 msgstr[0] "kein Unterschied: %d Objekt"
 msgstr[1] "kein Unterschied: %d Objekte"
 
-#: builtin/index-pack.c:1303
+#: builtin/index-pack.c:1451
 #, c-format
 msgid "chain length = %d: %lu object"
 msgid_plural "chain length = %d: %lu objects"
 msgstr[0] "Länge der Objekt-Liste = %d: %lu Objekt"
 msgstr[1] "Länge der Objekt-Liste = %d: %lu Objekte"
 
-#: builtin/index-pack.c:1330
+#: builtin/index-pack.c:1478
 msgid "Cannot come back to cwd"
 msgstr "Kann nicht zurück zu Arbeitsverzeichnis wechseln"
 
-#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
-#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#: builtin/index-pack.c:1522 builtin/index-pack.c:1525
+#: builtin/index-pack.c:1537 builtin/index-pack.c:1541
 #, c-format
 msgid "bad %s"
 msgstr "%s ist ungültig"
 
-#: builtin/index-pack.c:1407
+#: builtin/index-pack.c:1555
 msgid "--fix-thin cannot be used without --stdin"
 msgstr "--fix-thin kann nicht ohne --stdin benutzt werden"
 
-#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#: builtin/index-pack.c:1559 builtin/index-pack.c:1569
 #, c-format
 msgid "packfile name '%s' does not end with '.pack'"
 msgstr "Name der Paketdatei '%s' endet nicht mit '.pack'"
 
-#: builtin/index-pack.c:1430
+#: builtin/index-pack.c:1578
 msgid "--verify with no packfile name given"
 msgstr "--verify ohne Name der Paketdatei angegeben"
 
@@ -3331,94 +3454,94 @@ msgstr "Kann nicht auf aktuelles Arbeitsverzeichnis zugreifen."
 msgid "Cannot access work tree '%s'"
 msgstr "Kann nicht auf Arbeitsbaum '%s' zugreifen."
 
-#: builtin/log.c:188
+#: builtin/log.c:189
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr "letzte Ausgabe: %d %s\n"
 
-#: builtin/log.c:401 builtin/log.c:489
+#: builtin/log.c:402 builtin/log.c:490
 #, c-format
 msgid "Could not read object %s"
 msgstr "Kann Objekt %s nicht lesen."
 
-#: builtin/log.c:513
+#: builtin/log.c:514
 #, c-format
 msgid "Unknown type: %d"
 msgstr "Unbekannter Typ: %d"
 
-#: builtin/log.c:602
+#: builtin/log.c:603
 msgid "format.headers without value"
 msgstr "format.headers ohne Wert"
 
-#: builtin/log.c:676
+#: builtin/log.c:677
 msgid "name of output directory is too long"
 msgstr "Name des Ausgabeverzeichnisses ist zu lang."
 
-#: builtin/log.c:687
+#: builtin/log.c:688
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr "Kann Patch-Datei %s nicht öffnen"
 
-#: builtin/log.c:701
+#: builtin/log.c:702
 msgid "Need exactly one range."
 msgstr "Brauche genau einen Versionsbereich."
 
-#: builtin/log.c:709
+#: builtin/log.c:710
 msgid "Not a range."
 msgstr "Kein Versionsbereich."
 
-#: builtin/log.c:786
+#: builtin/log.c:787
 msgid "Cover letter needs email format"
 msgstr "Anschreiben benötigt E-Mail-Format"
 
-#: builtin/log.c:859
+#: builtin/log.c:860
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr "ungültiges in-reply-to: %s"
 
-#: builtin/log.c:932
+#: builtin/log.c:933
 msgid "Two output directories?"
 msgstr "Zwei Ausgabeverzeichnisse?"
 
-#: builtin/log.c:1153
+#: builtin/log.c:1154
 #, c-format
 msgid "bogus committer info %s"
 msgstr "unechte Einreicher-Informationen %s"
 
-#: builtin/log.c:1198
+#: builtin/log.c:1199
 msgid "-n and -k are mutually exclusive."
 msgstr "-n und -k schliessen sich gegenseitig aus"
 
-#: builtin/log.c:1200
+#: builtin/log.c:1201
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr "--subject-prefix und -k schliessen sich gegenseitig aus"
 
-#: builtin/log.c:1208
+#: builtin/log.c:1209
 msgid "--name-only does not make sense"
 msgstr "--name-only macht keinen Sinn"
 
-#: builtin/log.c:1210
+#: builtin/log.c:1211
 msgid "--name-status does not make sense"
 msgstr "--name-status macht keinen Sinn"
 
-#: builtin/log.c:1212
+#: builtin/log.c:1213
 msgid "--check does not make sense"
 msgstr "--check macht keinen Sinn"
 
-#: builtin/log.c:1235
+#: builtin/log.c:1236
 msgid "standard output, or directory, which one?"
 msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?"
 
-#: builtin/log.c:1237
+#: builtin/log.c:1238
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "Konnte Verzeichnis '%s' nicht erstellen."
 
-#: builtin/log.c:1390
+#: builtin/log.c:1391
 msgid "Failed to create output files"
 msgstr "Fehler beim Erstellen der Ausgabedateien."
 
-#: builtin/log.c:1494
+#: builtin/log.c:1495
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> manually.\n"
@@ -3426,7 +3549,7 @@ msgstr ""
 "Konnte gefolgten, externen Zweig nicht finden, bitte gebe <upstream> manuell "
 "an.\n"
 
-#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#: builtin/log.c:1511 builtin/log.c:1513 builtin/log.c:1525
 #, c-format
 msgid "Unknown commit %s"
 msgstr "Unbekannte Version %s"
@@ -3926,22 +4049,27 @@ msgstr "Objekt %s hat keine Notiz\n"
 msgid "Unknown subcommand: %s"
 msgstr "Unbekanntes Unterkommando: %s"
 
-#: builtin/pack-objects.c:2337
+#: builtin/pack-objects.c:183 builtin/pack-objects.c:186
+#, c-format
+msgid "deflate error (%d)"
+msgstr "Fehler beim Komprimieren (%d)"
+
+#: builtin/pack-objects.c:2398
 #, c-format
 msgid "unsupported index version %s"
 msgstr "Nicht unterstützte Bereitstellungsversion %s"
 
-#: builtin/pack-objects.c:2341
+#: builtin/pack-objects.c:2402
 #, c-format
 msgid "bad index version '%s'"
 msgstr "Ungültige Bereitstellungsversion '%s'"
 
-#: builtin/pack-objects.c:2364
+#: builtin/pack-objects.c:2425
 #, c-format
 msgid "option %s does not accept negative form"
 msgstr "Option %s akzeptiert keine negative Form"
 
-#: builtin/pack-objects.c:2368
+#: builtin/pack-objects.c:2429
 #, c-format
 msgid "unable to parse value '%s' for option %s"
 msgstr "konnte Wert '%s' für Option %s nicht parsen"
@@ -4946,9 +5074,10 @@ msgid ""
 "To restore the original branch and stop patching run \"$cmdline --abort\"."
 msgstr ""
 "Wenn du das Problem aufgelöst hast, führe \"$cmdline --resolved\" aus.\n"
-"Falls du diesen Patch auslassen möchtest, führe stattdessen "
-"\"$cmdline --skip\" aus.\n"
-"Um den ursprünglichen Zweig wiederherzustellen und die Anwendung der Patches\n"
+"Falls du diesen Patch auslassen möchtest, führe stattdessen \"$cmdline --skip"
+"\" aus.\n"
+"Um den ursprünglichen Zweig wiederherzustellen und die Anwendung der "
+"Patches\n"
 "abzubrechen, führe \"$cmdline --abort\" aus."
 
 #: git-am.sh:121
@@ -5019,9 +5148,10 @@ msgid ""
 "To restore the original branch and stop patching run \"$cmdline --abort\"."
 msgstr ""
 "Patch ist leer. Wurde er falsch aufgeteilt?\n"
-"Wenn du diesen Patch auslassen möchtest, führe stattdessen "
-"\"$cmdline --skip\" aus.\n"
-"Um den ursprünglichen Zweig wiederherzustellen und die Anwendung der Patches\n"
+"Wenn du diesen Patch auslassen möchtest, führe stattdessen \"$cmdline --skip"
+"\" aus.\n"
+"Um den ursprünglichen Zweig wiederherzustellen und die Anwendung der "
+"Patches\n"
 "abzubrechen, führe \"$cmdline --abort\" aus."
 
 #: git-am.sh:708
@@ -5414,109 +5544,108 @@ msgstr "Kein Zweigname spezifiziert"
 msgid "(To restore them type \"git stash apply\")"
 msgstr "(Zur Wiederherstellung gebe \"git stash apply\" ein)"
 
-#: git-submodule.sh:56
+#: git-submodule.sh:88
 #, sh-format
 msgid "cannot strip one component off url '$remoteurl'"
 msgstr "Kann eine Komponente von URL '$remoteurl' nicht extrahieren"
 
-#: git-submodule.sh:109
+#: git-submodule.sh:145
 #, sh-format
 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:150
+#: git-submodule.sh:186
 #, sh-format
 msgid "Clone of '$url' into submodule path '$sm_path' failed"
 msgstr "Klonen von '$url' in Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
 
-#: git-submodule.sh:160
+#: git-submodule.sh:196
 #, 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"
 
-#: git-submodule.sh:249
+#: git-submodule.sh:285
 #, 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:266
+#: git-submodule.sh:302
 #, sh-format
 msgid "'$sm_path' already exists in the index"
 msgstr "'$sm_path' existiert bereits in der Bereitstellung"
 
-#: git-submodule.sh:270
+#: git-submodule.sh:306
 #, sh-format
 msgid ""
 "The following path is ignored by one of your .gitignore files:\n"
 "$sm_path\n"
 "Use -f if you really want to add it."
 msgstr ""
-"Der folgende Pfad wird durch eine deiner \".gitignore\" Dateien "
-"ignoriert:\n"
+"Der folgende Pfad wird durch eine deiner \".gitignore\" Dateien ignoriert:\n"
 "$sm_path\n"
 "Benutze -f wenn du diesen wirklich hinzufügen möchtest."
 
-#: git-submodule.sh:281
+#: git-submodule.sh:317
 #, sh-format
 msgid "Adding existing repo at '$sm_path' to the index"
-msgstr "Füge existierendes Projektarchiv in '$sm_path' der Bereitstellung "
-"hinzu."
+msgstr ""
+"Füge existierendes Projektarchiv in '$sm_path' der Bereitstellung hinzu."
 
-#: git-submodule.sh:283
+#: git-submodule.sh:319
 #, sh-format
 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:297
+#: git-submodule.sh:333
 #, sh-format
 msgid "Unable to checkout submodule '$sm_path'"
 msgstr "Unfähig Unterprojekt '$sm_path' auszuchecken"
 
-#: git-submodule.sh:302
+#: git-submodule.sh:338
 #, sh-format
 msgid "Failed to add submodule '$sm_path'"
 msgstr "Hinzufügen von Unterprojekt '$sm_path' fehlgeschlagen"
 
-#: git-submodule.sh:307
+#: git-submodule.sh:343
 #, sh-format
 msgid "Failed to register submodule '$sm_path'"
 msgstr "Registierung von Unterprojekt '$sm_path' fehlgeschlagen"
 
-#: git-submodule.sh:349
+#: git-submodule.sh:385
 #, sh-format
 msgid "Entering '$prefix$sm_path'"
 msgstr "Betrete '$prefix$sm_path'"
 
-#: git-submodule.sh:363
+#: git-submodule.sh:399
 #, sh-format
 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:406
+#: git-submodule.sh:442
 #, sh-format
 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:415
+#: git-submodule.sh:451
 #, sh-format
 msgid "Failed to register url for submodule path '$sm_path'"
 msgstr "Registrierung der URL für Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
 
-#: git-submodule.sh:417
+#: git-submodule.sh:453
 #, sh-format
 msgid "Submodule '$name' ($url) registered for path '$sm_path'"
 msgstr "Unterprojekt '$name' ($url) ist für Pfad '$sm_path' registriert"
 
-#: git-submodule.sh:425
+#: git-submodule.sh:461
 #, sh-format
 msgid "Failed to register update mode for submodule path '$sm_path'"
 msgstr ""
 "Registrierung des Aktualisierungsmodus für Unterprojekt-Pfad '$sm_path' "
 "fehlgeschlagen"
 
-#: git-submodule.sh:524
+#: git-submodule.sh:560
 #, sh-format
 msgid ""
 "Submodule path '$sm_path' not initialized\n"
@@ -5525,95 +5654,95 @@ msgstr ""
 "Unterprojekt-Pfad '$sm_path' ist nicht initialisiert\n"
 "Vielleicht möchtest du 'update --init' benutzen?"
 
-#: git-submodule.sh:537
+#: git-submodule.sh:573
 #, sh-format
 msgid "Unable to find current revision in submodule path '$sm_path'"
 msgstr "Konnte aktuelle Version in Unterprojekt-Pfad '$sm_path' nicht finden"
 
-#: git-submodule.sh:556
+#: git-submodule.sh:592
 #, sh-format
 msgid "Unable to fetch in submodule path '$sm_path'"
 msgstr "Konnte in Unterprojekt-Pfad '$sm_path' nicht anfordern"
 
-#: git-submodule.sh:570
+#: git-submodule.sh:606
 #, sh-format
 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:571
+#: git-submodule.sh:607
 #, sh-format
 msgid "Submodule path '$sm_path': rebased into '$sha1'"
 msgstr "Unterprojekt-Pfad '$sm_path': neu aufgebaut in '$sha1'"
 
-#: git-submodule.sh:576
+#: git-submodule.sh:612
 #, sh-format
 msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
 msgstr ""
 "Zusammenführung von '$sha1' in Unterprojekt-Pfad '$sm_path' fehlgeschlagen"
 
-#: git-submodule.sh:577
+#: git-submodule.sh:613
 #, sh-format
 msgid "Submodule path '$sm_path': merged in '$sha1'"
 msgstr "Unterprojekt-Pfad '$sm_path': zusammengeführt in '$sha1'"
 
-#: git-submodule.sh:582
+#: git-submodule.sh:618
 #, sh-format
 msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
 msgstr "Konnte '$sha1' in Unterprojekt-Pfad '$sm_path' nicht auschecken."
 
-#: git-submodule.sh:583
+#: git-submodule.sh:619
 #, sh-format
 msgid "Submodule path '$sm_path': checked out '$sha1'"
 msgstr "Unterprojekt-Pfad: '$sm_path': '$sha1' ausgecheckt"
 
-#: git-submodule.sh:605 git-submodule.sh:928
+#: git-submodule.sh:641 git-submodule.sh:964
 #, sh-format
 msgid "Failed to recurse into submodule path '$sm_path'"
 msgstr "Fehler bei Rekursion in Unterprojekt-Pfad '$sm_path'"
 
-#: git-submodule.sh:713
+#: git-submodule.sh:749
 msgid "--cached cannot be used with --files"
 msgstr "--cached kann nicht mit --files benutzt werden"
 
 #. unexpected type
-#: git-submodule.sh:753
+#: git-submodule.sh:789
 #, sh-format
 msgid "unexpected mode $mod_dst"
 msgstr "unerwarteter Modus $mod_dst"
 
-#: git-submodule.sh:771
+#: git-submodule.sh:807
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_src"
 msgstr "  Warnung: $name beinhaltet nicht Version $sha1_src"
 
-#: git-submodule.sh:774
+#: git-submodule.sh:810
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_dst"
 msgstr "  Warnung: $name beinhaltet nicht Version $sha1_dst"
 
-#: git-submodule.sh:777
+#: git-submodule.sh:813
 #, sh-format
 msgid "  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 msgstr ""
 "  Warnung: $name beinhaltet nicht die Versionen $sha1_src und $sha1_dst"
 
-#: git-submodule.sh:802
+#: git-submodule.sh:838
 msgid "blob"
 msgstr "Blob"
 
-#: git-submodule.sh:803
+#: git-submodule.sh:839
 msgid "submodule"
 msgstr "Unterprojekt"
 
-#: git-submodule.sh:840
+#: git-submodule.sh:876
 msgid "# Submodules changed but not updated:"
 msgstr "# Unterprojekte geändert, aber nicht aktualisiert:"
 
-#: git-submodule.sh:842
+#: git-submodule.sh:878
 msgid "# Submodule changes to be committed:"
 msgstr "# Änderungen in Unterprojekt zum Eintragen:"
 
-#: git-submodule.sh:974
+#: git-submodule.sh:1022
 #, sh-format
 msgid "Synchronizing submodule url for '$name'"
 msgstr "Synchronisiere Unterprojekt-URL für '$name'"
index b6665060de23353efecb6307830d4ae11689dcca..3d9ae759ea3a512a5a638adfc70bda5139b25209 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-06-08 10:20+0800\n"
+"POT-Creation-Date: 2012-07-03 10:23+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"
@@ -54,8 +54,8 @@ msgstr ""
 msgid "Repository lacks these prerequisite commits:"
 msgstr ""
 
-#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
-#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:290
+#: builtin/log.c:721 builtin/log.c:1310 builtin/log.c:1529 builtin/merge.c:347
 #: builtin/shortlog.c:181
 msgid "revision walk setup failed"
 msgstr ""
@@ -68,44 +68,48 @@ msgstr[0] ""
 msgstr[1] ""
 
 #: bundle.c:192
+msgid "The bundle records a complete history."
+msgstr ""
+
+#: bundle.c:195
 #, c-format
 msgid "The bundle requires this ref"
 msgid_plural "The bundle requires these %d refs"
 msgstr[0] ""
 msgstr[1] ""
 
-#: bundle.c:290
+#: bundle.c:294
 msgid "rev-list died"
 msgstr ""
 
-#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#: bundle.c:300 builtin/log.c:1206 builtin/shortlog.c:284
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr ""
 
-#: bundle.c:331
+#: bundle.c:335
 #, c-format
 msgid "ref '%s' is excluded by the rev-list options"
 msgstr ""
 
-#: bundle.c:376
+#: bundle.c:380
 msgid "Refusing to create empty bundle."
 msgstr ""
 
-#: bundle.c:394
+#: bundle.c:398
 msgid "Could not spawn pack-objects"
 msgstr ""
 
-#: bundle.c:412
+#: bundle.c:416
 msgid "pack-objects died"
 msgstr ""
 
-#: bundle.c:415
+#: bundle.c:419
 #, c-format
 msgid "cannot create '%s'"
 msgstr ""
 
-#: bundle.c:437
+#: bundle.c:441
 msgid "index-pack died"
 msgstr ""
 
@@ -280,44 +284,44 @@ msgstr ""
 msgid "'%s': short read %s"
 msgstr ""
 
-#: help.c:207
+#: help.c:208
 #, c-format
 msgid "available git commands in '%s'"
 msgstr ""
 
-#: help.c:214
+#: help.c:215
 msgid "git commands available from elsewhere on your $PATH"
 msgstr ""
 
-#: help.c:270
+#: help.c:271
 #, c-format
 msgid ""
 "'%s' appears to be a git command, but we were not\n"
 "able to execute it. Maybe git-%s is broken?"
 msgstr ""
 
-#: help.c:327
+#: help.c:328
 msgid "Uh oh. Your system reports no Git commands at all."
 msgstr ""
 
-#: help.c:349
+#: help.c:350
 #, c-format
 msgid ""
 "WARNING: You called a Git command named '%s', which does not exist.\n"
 "Continuing under the assumption that you meant '%s'"
 msgstr ""
 
-#: help.c:354
+#: help.c:355
 #, c-format
 msgid "in %0.1f seconds automatically..."
 msgstr ""
 
-#: help.c:361
+#: help.c:362
 #, c-format
 msgid "git: '%s' is not a git command. See 'git --help'."
 msgstr ""
 
-#: help.c:365
+#: help.c:366
 msgid ""
 "\n"
 "Did you mean this?"
@@ -638,232 +642,333 @@ msgstr ""
 msgid "no such user"
 msgstr ""
 
-#: wt-status.c:135
+#: wt-status.c:141
 msgid "Unmerged paths:"
 msgstr ""
 
-#: wt-status.c:141 wt-status.c:158
+#: wt-status.c:168 wt-status.c:195
 #, c-format
 msgid "  (use \"git reset %s <file>...\" to unstage)"
 msgstr ""
 
-#: wt-status.c:143 wt-status.c:160
+#: wt-status.c:170 wt-status.c:197
 msgid "  (use \"git rm --cached <file>...\" to unstage)"
 msgstr ""
 
-#: wt-status.c:144
+#: wt-status.c:174
+msgid "  (use \"git add <file>...\" to mark resolution)"
+msgstr ""
+
+#: wt-status.c:176 wt-status.c:180
 msgid "  (use \"git add/rm <file>...\" as appropriate to mark resolution)"
 msgstr ""
 
-#: wt-status.c:152
+#: wt-status.c:178
+msgid "  (use \"git rm <file>...\" to mark resolution)"
+msgstr ""
+
+#: wt-status.c:189
 msgid "Changes to be committed:"
 msgstr ""
 
-#: wt-status.c:170
+#: wt-status.c:207
 msgid "Changes not staged for commit:"
 msgstr ""
 
-#: wt-status.c:174
+#: wt-status.c:211
 msgid "  (use \"git add <file>...\" to update what will be committed)"
 msgstr ""
 
-#: wt-status.c:176
+#: wt-status.c:213
 msgid "  (use \"git add/rm <file>...\" to update what will be committed)"
 msgstr ""
 
-#: wt-status.c:177
+#: wt-status.c:214
 msgid ""
 "  (use \"git checkout -- <file>...\" to discard changes in working directory)"
 msgstr ""
 
-#: wt-status.c:179
+#: wt-status.c:216
 msgid "  (commit or discard the untracked or modified content in submodules)"
 msgstr ""
 
-#: wt-status.c:188
+#: wt-status.c:225
 #, c-format
 msgid "%s files:"
 msgstr ""
 
-#: wt-status.c:191
+#: wt-status.c:228
 #, c-format
 msgid "  (use \"git %s <file>...\" to include in what will be committed)"
 msgstr ""
 
-#: wt-status.c:208
+#: wt-status.c:245
 msgid "bug"
 msgstr ""
 
-#: wt-status.c:213
+#: wt-status.c:250
 msgid "both deleted:"
 msgstr ""
 
-#: wt-status.c:214
+#: wt-status.c:251
 msgid "added by us:"
 msgstr ""
 
-#: wt-status.c:215
+#: wt-status.c:252
 msgid "deleted by them:"
 msgstr ""
 
-#: wt-status.c:216
+#: wt-status.c:253
 msgid "added by them:"
 msgstr ""
 
-#: wt-status.c:217
+#: wt-status.c:254
 msgid "deleted by us:"
 msgstr ""
 
-#: wt-status.c:218
+#: wt-status.c:255
 msgid "both added:"
 msgstr ""
 
-#: wt-status.c:219
+#: wt-status.c:256
 msgid "both modified:"
 msgstr ""
 
-#: wt-status.c:249
+#: wt-status.c:286
 msgid "new commits, "
 msgstr ""
 
-#: wt-status.c:251
+#: wt-status.c:288
 msgid "modified content, "
 msgstr ""
 
-#: wt-status.c:253
+#: wt-status.c:290
 msgid "untracked content, "
 msgstr ""
 
-#: wt-status.c:267
+#: wt-status.c:304
 #, c-format
 msgid "new file:   %s"
 msgstr ""
 
-#: wt-status.c:270
+#: wt-status.c:307
 #, c-format
 msgid "copied:     %s -> %s"
 msgstr ""
 
-#: wt-status.c:273
+#: wt-status.c:310
 #, c-format
 msgid "deleted:    %s"
 msgstr ""
 
-#: wt-status.c:276
+#: wt-status.c:313
 #, c-format
 msgid "modified:   %s"
 msgstr ""
 
-#: wt-status.c:279
+#: wt-status.c:316
 #, c-format
 msgid "renamed:    %s -> %s"
 msgstr ""
 
-#: wt-status.c:282
+#: wt-status.c:319
 #, c-format
 msgid "typechange: %s"
 msgstr ""
 
-#: wt-status.c:285
+#: wt-status.c:322
 #, c-format
 msgid "unknown:    %s"
 msgstr ""
 
-#: wt-status.c:288
+#: wt-status.c:325
 #, c-format
 msgid "unmerged:   %s"
 msgstr ""
 
-#: wt-status.c:291
+#: wt-status.c:328
 #, c-format
 msgid "bug: unhandled diff status %c"
 msgstr ""
 
-#: wt-status.c:737
+#: wt-status.c:786
+msgid "You have unmerged paths."
+msgstr ""
+
+#: wt-status.c:789 wt-status.c:913
+msgid "  (fix conflicts and run \"git commit\")"
+msgstr ""
+
+#: wt-status.c:792
+msgid "All conflicts fixed but you are still merging."
+msgstr ""
+
+#: wt-status.c:795
+msgid "  (use \"git commit\" to conclude merge)"
+msgstr ""
+
+#: wt-status.c:805
+msgid "You are in the middle of an am session."
+msgstr ""
+
+#: wt-status.c:808
+msgid "The current patch is empty."
+msgstr ""
+
+#: wt-status.c:812
+msgid "  (fix conflicts and then run \"git am --resolved\")"
+msgstr ""
+
+#: wt-status.c:814
+msgid "  (use \"git am --skip\" to skip this patch)"
+msgstr ""
+
+#: wt-status.c:816
+msgid "  (use \"git am --abort\" to restore the original branch)"
+msgstr ""
+
+#: wt-status.c:874 wt-status.c:884
+msgid "You are currently rebasing."
+msgstr ""
+
+#: wt-status.c:877
+msgid "  (fix conflicts and then run \"git rebase --continue\")"
+msgstr ""
+
+#: wt-status.c:879
+msgid "  (use \"git rebase --skip\" to skip this patch)"
+msgstr ""
+
+#: wt-status.c:881
+msgid "  (use \"git rebase --abort\" to check out the original branch)"
+msgstr ""
+
+#: wt-status.c:887
+msgid "  (all conflicts fixed: run \"git rebase --continue\")"
+msgstr ""
+
+#: wt-status.c:889
+msgid "You are currently splitting a commit during a rebase."
+msgstr ""
+
+#: wt-status.c:892
+msgid "  (Once your working directory is clean, run \"git rebase --continue\")"
+msgstr ""
+
+#: wt-status.c:894
+msgid "You are currently editing a commit during a rebase."
+msgstr ""
+
+#: wt-status.c:897
+msgid "  (use \"git commit --amend\" to amend the current commit)"
+msgstr ""
+
+#: wt-status.c:899
+msgid ""
+"  (use \"git rebase --continue\" once you are satisfied with your changes)"
+msgstr ""
+
+#: wt-status.c:909
+msgid "You are currently cherry-picking."
+msgstr ""
+
+#: wt-status.c:916
+msgid "  (all conflicts fixed: run \"git commit\")"
+msgstr ""
+
+#: wt-status.c:925
+msgid "You are currently bisecting."
+msgstr ""
+
+#: wt-status.c:928
+msgid "  (use \"git bisect reset\" to get back to the original branch)"
+msgstr ""
+
+#: wt-status.c:979
 msgid "On branch "
 msgstr ""
 
-#: wt-status.c:744
+#: wt-status.c:986
 msgid "Not currently on any branch."
 msgstr ""
 
-#: wt-status.c:755
+#: wt-status.c:998
 msgid "Initial commit"
 msgstr ""
 
-#: wt-status.c:769
+#: wt-status.c:1012
 msgid "Untracked"
 msgstr ""
 
-#: wt-status.c:771
+#: wt-status.c:1014
 msgid "Ignored"
 msgstr ""
 
-#: wt-status.c:773
+#: wt-status.c:1016
 #, c-format
 msgid "Untracked files not listed%s"
 msgstr ""
 
-#: wt-status.c:775
+#: wt-status.c:1018
 msgid " (use -u option to show untracked files)"
 msgstr ""
 
-#: wt-status.c:781
+#: wt-status.c:1024
 msgid "No changes"
 msgstr ""
 
-#: wt-status.c:785
+#: wt-status.c:1028
 #, c-format
 msgid "no changes added to commit%s\n"
 msgstr ""
 
-#: wt-status.c:787
+#: wt-status.c:1030
 msgid " (use \"git add\" and/or \"git commit -a\")"
 msgstr ""
 
-#: wt-status.c:789
+#: wt-status.c:1032
 #, c-format
 msgid "nothing added to commit but untracked files present%s\n"
 msgstr ""
 
-#: wt-status.c:791
+#: wt-status.c:1034
 msgid " (use \"git add\" to track)"
 msgstr ""
 
-#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#: wt-status.c:1036 wt-status.c:1039 wt-status.c:1042
 #, c-format
 msgid "nothing to commit%s\n"
 msgstr ""
 
-#: wt-status.c:794
+#: wt-status.c:1037
 msgid " (create/copy files and use \"git add\" to track)"
 msgstr ""
 
-#: wt-status.c:797
+#: wt-status.c:1040
 msgid " (use -u to show untracked files)"
 msgstr ""
 
-#: wt-status.c:800
+#: wt-status.c:1043
 msgid " (working directory clean)"
 msgstr ""
 
-#: wt-status.c:908
+#: wt-status.c:1151
 msgid "HEAD (no branch)"
 msgstr ""
 
-#: wt-status.c:914
+#: wt-status.c:1157
 msgid "Initial commit on "
 msgstr ""
 
-#: wt-status.c:929
+#: wt-status.c:1172
 msgid "behind "
 msgstr ""
 
-#: wt-status.c:932 wt-status.c:935
+#: wt-status.c:1175 wt-status.c:1178
 msgid "ahead "
 msgstr ""
 
-#: wt-status.c:937
+#: wt-status.c:1180
 msgid ", behind "
 msgstr ""
 
@@ -890,7 +995,7 @@ msgstr ""
 msgid "Unstaged changes after refreshing the index:"
 msgstr ""
 
-#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#: builtin/add.c:195 builtin/add.c:459 builtin/rm.c:186
 #, c-format
 msgid "pathspec '%s' did not match any files"
 msgstr ""
@@ -967,7 +1072,7 @@ msgstr ""
 msgid "index file corrupt"
 msgstr ""
 
-#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+#: builtin/add.c:480 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
 msgid "Unable to write new index file"
 msgstr ""
 
@@ -2432,22 +2537,22 @@ msgstr ""
 msgid "Not a git repository"
 msgstr ""
 
-#: builtin/diff.c:347
+#: builtin/diff.c:341
 #, c-format
 msgid "invalid object '%s' given."
 msgstr ""
 
-#: builtin/diff.c:352
+#: builtin/diff.c:346
 #, c-format
 msgid "more than %d trees given: '%s'"
 msgstr ""
 
-#: builtin/diff.c:362
+#: builtin/diff.c:356
 #, c-format
 msgid "more than two blobs given: '%s'"
 msgstr ""
 
-#: builtin/diff.c:370
+#: builtin/diff.c:364
 #, c-format
 msgid "unhandled object '%s' given."
 msgstr ""
@@ -2689,303 +2794,314 @@ msgstr ""
 msgid "both --cached and trees are given."
 msgstr ""
 
-#: builtin/help.c:59
+#: builtin/help.c:63
 #, c-format
 msgid "unrecognized help format '%s'"
 msgstr ""
 
-#: builtin/help.c:87
+#: builtin/help.c:91
 msgid "Failed to start emacsclient."
 msgstr ""
 
-#: builtin/help.c:100
+#: builtin/help.c:104
 msgid "Failed to parse emacsclient version."
 msgstr ""
 
-#: builtin/help.c:108
+#: builtin/help.c:112
 #, c-format
 msgid "emacsclient version '%d' too old (< 22)."
 msgstr ""
 
-#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#: builtin/help.c:130 builtin/help.c:158 builtin/help.c:167 builtin/help.c:175
 #, c-format
 msgid "failed to exec '%s': %s"
 msgstr ""
 
-#: builtin/help.c:211
+#: builtin/help.c:215
 #, c-format
 msgid ""
 "'%s': path for unsupported man viewer.\n"
 "Please consider using 'man.<tool>.cmd' instead."
 msgstr ""
 
-#: builtin/help.c:223
+#: builtin/help.c:227
 #, c-format
 msgid ""
 "'%s': cmd for supported man viewer.\n"
 "Please consider using 'man.<tool>.path' instead."
 msgstr ""
 
-#: builtin/help.c:287
+#: builtin/help.c:291
 msgid "The most commonly used git commands are:"
 msgstr ""
 
-#: builtin/help.c:355
+#: builtin/help.c:359
 #, c-format
 msgid "'%s': unknown man viewer."
 msgstr ""
 
-#: builtin/help.c:372
+#: builtin/help.c:376
 msgid "no man viewer handled the request"
 msgstr ""
 
-#: builtin/help.c:380
+#: builtin/help.c:384
 msgid "no info viewer handled the request"
 msgstr ""
 
-#: builtin/help.c:391
+#: builtin/help.c:395
 #, c-format
 msgid "'%s': not a documentation directory."
 msgstr ""
 
-#: builtin/help.c:432 builtin/help.c:439
+#: builtin/help.c:436 builtin/help.c:443
 #, c-format
 msgid "usage: %s%s"
 msgstr ""
 
-#: builtin/help.c:453
+#: builtin/help.c:459
 #, c-format
 msgid "`git %s' is aliased to `%s'"
 msgstr ""
 
-#: builtin/index-pack.c:169
+#: builtin/index-pack.c:170
 #, c-format
 msgid "object type mismatch at %s"
 msgstr ""
 
-#: builtin/index-pack.c:189
+#: builtin/index-pack.c:190
 msgid "object of unexpected type"
 msgstr ""
 
-#: builtin/index-pack.c:226
+#: builtin/index-pack.c:227
 #, c-format
 msgid "cannot fill %d byte"
 msgid_plural "cannot fill %d bytes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: builtin/index-pack.c:236
+#: builtin/index-pack.c:237
 msgid "early EOF"
 msgstr ""
 
-#: builtin/index-pack.c:237
+#: builtin/index-pack.c:238
 msgid "read error on input"
 msgstr ""
 
-#: builtin/index-pack.c:249
+#: builtin/index-pack.c:250
 msgid "used more bytes than were available"
 msgstr ""
 
-#: builtin/index-pack.c:256
+#: builtin/index-pack.c:257
 msgid "pack too large for current definition of off_t"
 msgstr ""
 
-#: builtin/index-pack.c:272
+#: builtin/index-pack.c:273
 #, c-format
 msgid "unable to create '%s'"
 msgstr ""
 
-#: builtin/index-pack.c:277
+#: builtin/index-pack.c:278
 #, c-format
 msgid "cannot open packfile '%s'"
 msgstr ""
 
-#: builtin/index-pack.c:291
+#: builtin/index-pack.c:292
 msgid "pack signature mismatch"
 msgstr ""
 
-#: builtin/index-pack.c:311
+#: builtin/index-pack.c:312
 #, c-format
 msgid "pack has bad object at offset %lu: %s"
 msgstr ""
 
-#: builtin/index-pack.c:405
+#: builtin/index-pack.c:434
 #, c-format
 msgid "inflate returned %d"
 msgstr ""
 
-#: builtin/index-pack.c:450
+#: builtin/index-pack.c:483
 msgid "offset value overflow for delta base object"
 msgstr ""
 
-#: builtin/index-pack.c:458
+#: builtin/index-pack.c:491
 msgid "delta base offset is out of bound"
 msgstr ""
 
-#: builtin/index-pack.c:466
+#: builtin/index-pack.c:499
 #, c-format
 msgid "unknown object type %d"
 msgstr ""
 
-#: builtin/index-pack.c:495
+#: builtin/index-pack.c:531
 msgid "cannot pread pack file"
 msgstr ""
 
-#: builtin/index-pack.c:497
+#: builtin/index-pack.c:533
 #, c-format
 msgid "premature end of pack file, %lu byte missing"
 msgid_plural "premature end of pack file, %lu bytes missing"
 msgstr[0] ""
 msgstr[1] ""
 
-#: builtin/index-pack.c:510
+#: builtin/index-pack.c:555
 msgid "serious inflate inconsistency"
 msgstr ""
 
-#: builtin/index-pack.c:583
+#: builtin/index-pack.c:646 builtin/index-pack.c:652 builtin/index-pack.c:675
+#: builtin/index-pack.c:709 builtin/index-pack.c:718
 #, c-format
-msgid "cannot read existing object %s"
+msgid "SHA1 COLLISION FOUND WITH %s !"
 msgstr ""
 
-#: builtin/index-pack.c:586
+#: builtin/index-pack.c:649 builtin/pack-objects.c:170
+#: builtin/pack-objects.c:262
 #, c-format
-msgid "SHA1 COLLISION FOUND WITH %s !"
+msgid "unable to read %s"
 msgstr ""
 
-#: builtin/index-pack.c:598
+#: builtin/index-pack.c:715
+#, c-format
+msgid "cannot read existing object %s"
+msgstr ""
+
+#: builtin/index-pack.c:729
 #, c-format
 msgid "invalid blob object %s"
 msgstr ""
 
-#: builtin/index-pack.c:610
+#: builtin/index-pack.c:744
 #, c-format
 msgid "invalid %s"
 msgstr ""
 
-#: builtin/index-pack.c:612
+#: builtin/index-pack.c:746
 msgid "Error in object"
 msgstr ""
 
-#: builtin/index-pack.c:614
+#: builtin/index-pack.c:748
 #, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr ""
 
-#: builtin/index-pack.c:687 builtin/index-pack.c:713
+#: builtin/index-pack.c:818 builtin/index-pack.c:844
 msgid "failed to apply delta"
 msgstr ""
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Receiving objects"
 msgstr ""
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Indexing objects"
 msgstr ""
 
-#: builtin/index-pack.c:872
+#: builtin/index-pack.c:1009
 msgid "pack is corrupted (SHA1 mismatch)"
 msgstr ""
 
-#: builtin/index-pack.c:877
+#: builtin/index-pack.c:1014
 msgid "cannot fstat packfile"
 msgstr ""
 
-#: builtin/index-pack.c:880
+#: builtin/index-pack.c:1017
 msgid "pack has junk at the end"
 msgstr ""
 
-#: builtin/index-pack.c:903
+#: builtin/index-pack.c:1028
+msgid "confusion beyond insanity in parse_pack_objects()"
+msgstr ""
+
+#: builtin/index-pack.c:1051
 msgid "Resolving deltas"
 msgstr ""
 
-#: builtin/index-pack.c:954
+#: builtin/index-pack.c:1102
 msgid "confusion beyond insanity"
 msgstr ""
 
-#: builtin/index-pack.c:973
+#: builtin/index-pack.c:1121
 #, c-format
 msgid "pack has %d unresolved delta"
 msgid_plural "pack has %d unresolved deltas"
 msgstr[0] ""
 msgstr[1] ""
 
-#: builtin/index-pack.c:998
+#: builtin/index-pack.c:1146
 #, c-format
 msgid "unable to deflate appended object (%d)"
 msgstr ""
 
-#: builtin/index-pack.c:1077
+#: builtin/index-pack.c:1225
 #, c-format
 msgid "local object %s is corrupt"
 msgstr ""
 
-#: builtin/index-pack.c:1101
+#: builtin/index-pack.c:1249
 msgid "error while closing pack file"
 msgstr ""
 
-#: builtin/index-pack.c:1114
+#: builtin/index-pack.c:1262
 #, c-format
 msgid "cannot write keep file '%s'"
 msgstr ""
 
-#: builtin/index-pack.c:1122
+#: builtin/index-pack.c:1270
 #, c-format
 msgid "cannot close written keep file '%s'"
 msgstr ""
 
-#: builtin/index-pack.c:1135
+#: builtin/index-pack.c:1283
 msgid "cannot store pack file"
 msgstr ""
 
-#: builtin/index-pack.c:1146
+#: builtin/index-pack.c:1294
 msgid "cannot store index file"
 msgstr ""
 
-#: builtin/index-pack.c:1247
+#: builtin/index-pack.c:1395
 #, c-format
 msgid "Cannot open existing pack file '%s'"
 msgstr ""
 
-#: builtin/index-pack.c:1249
+#: builtin/index-pack.c:1397
 #, c-format
 msgid "Cannot open existing pack idx file for '%s'"
 msgstr ""
 
-#: builtin/index-pack.c:1296
+#: builtin/index-pack.c:1444
 #, c-format
 msgid "non delta: %d object"
 msgid_plural "non delta: %d objects"
 msgstr[0] ""
 msgstr[1] ""
 
-#: builtin/index-pack.c:1303
+#: builtin/index-pack.c:1451
 #, c-format
 msgid "chain length = %d: %lu object"
 msgid_plural "chain length = %d: %lu objects"
 msgstr[0] ""
 msgstr[1] ""
 
-#: builtin/index-pack.c:1330
+#: builtin/index-pack.c:1478
 msgid "Cannot come back to cwd"
 msgstr ""
 
-#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
-#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#: builtin/index-pack.c:1522 builtin/index-pack.c:1525
+#: builtin/index-pack.c:1537 builtin/index-pack.c:1541
 #, c-format
 msgid "bad %s"
 msgstr ""
 
-#: builtin/index-pack.c:1407
+#: builtin/index-pack.c:1555
 msgid "--fix-thin cannot be used without --stdin"
 msgstr ""
 
-#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#: builtin/index-pack.c:1559 builtin/index-pack.c:1569
 #, c-format
 msgid "packfile name '%s' does not end with '.pack'"
 msgstr ""
 
-#: builtin/index-pack.c:1430
+#: builtin/index-pack.c:1578
 msgid "--verify with no packfile name given"
 msgstr ""
 
@@ -3131,100 +3247,100 @@ msgstr ""
 msgid "Cannot access work tree '%s'"
 msgstr ""
 
-#: builtin/log.c:188
+#: builtin/log.c:189
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr ""
 
-#: builtin/log.c:401 builtin/log.c:489
+#: builtin/log.c:402 builtin/log.c:490
 #, c-format
 msgid "Could not read object %s"
 msgstr ""
 
-#: builtin/log.c:513
+#: builtin/log.c:514
 #, c-format
 msgid "Unknown type: %d"
 msgstr ""
 
-#: builtin/log.c:602
+#: builtin/log.c:603
 msgid "format.headers without value"
 msgstr ""
 
-#: builtin/log.c:676
+#: builtin/log.c:677
 msgid "name of output directory is too long"
 msgstr ""
 
-#: builtin/log.c:687
+#: builtin/log.c:688
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr ""
 
-#: builtin/log.c:701
+#: builtin/log.c:702
 msgid "Need exactly one range."
 msgstr ""
 
-#: builtin/log.c:709
+#: builtin/log.c:710
 msgid "Not a range."
 msgstr ""
 
-#: builtin/log.c:786
+#: builtin/log.c:787
 msgid "Cover letter needs email format"
 msgstr ""
 
-#: builtin/log.c:859
+#: builtin/log.c:860
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr ""
 
-#: builtin/log.c:932
+#: builtin/log.c:933
 msgid "Two output directories?"
 msgstr ""
 
-#: builtin/log.c:1153
+#: builtin/log.c:1154
 #, c-format
 msgid "bogus committer info %s"
 msgstr ""
 
-#: builtin/log.c:1198
+#: builtin/log.c:1199
 msgid "-n and -k are mutually exclusive."
 msgstr ""
 
-#: builtin/log.c:1200
+#: builtin/log.c:1201
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr ""
 
-#: builtin/log.c:1208
+#: builtin/log.c:1209
 msgid "--name-only does not make sense"
 msgstr ""
 
-#: builtin/log.c:1210
+#: builtin/log.c:1211
 msgid "--name-status does not make sense"
 msgstr ""
 
-#: builtin/log.c:1212
+#: builtin/log.c:1213
 msgid "--check does not make sense"
 msgstr ""
 
-#: builtin/log.c:1235
+#: builtin/log.c:1236
 msgid "standard output, or directory, which one?"
 msgstr ""
 
-#: builtin/log.c:1237
+#: builtin/log.c:1238
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr ""
 
-#: builtin/log.c:1390
+#: builtin/log.c:1391
 msgid "Failed to create output files"
 msgstr ""
 
-#: builtin/log.c:1494
+#: builtin/log.c:1495
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> manually.\n"
 msgstr ""
 
-#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#: builtin/log.c:1511 builtin/log.c:1513 builtin/log.c:1525
 #, c-format
 msgid "Unknown commit %s"
 msgstr ""
@@ -3694,22 +3810,27 @@ msgstr ""
 msgid "Unknown subcommand: %s"
 msgstr ""
 
-#: builtin/pack-objects.c:2337
+#: builtin/pack-objects.c:183 builtin/pack-objects.c:186
+#, c-format
+msgid "deflate error (%d)"
+msgstr ""
+
+#: builtin/pack-objects.c:2398
 #, c-format
 msgid "unsupported index version %s"
 msgstr ""
 
-#: builtin/pack-objects.c:2341
+#: builtin/pack-objects.c:2402
 #, c-format
 msgid "bad index version '%s'"
 msgstr ""
 
-#: builtin/pack-objects.c:2364
+#: builtin/pack-objects.c:2425
 #, c-format
 msgid "option %s does not accept negative form"
 msgstr ""
 
-#: builtin/pack-objects.c:2368
+#: builtin/pack-objects.c:2429
 #, c-format
 msgid "unable to parse value '%s' for option %s"
 msgstr ""
@@ -5021,37 +5142,37 @@ msgstr ""
 msgid "(To restore them type \"git stash apply\")"
 msgstr ""
 
-#: git-submodule.sh:56
+#: git-submodule.sh:88
 #, sh-format
 msgid "cannot strip one component off url '$remoteurl'"
 msgstr ""
 
-#: git-submodule.sh:109
+#: git-submodule.sh:145
 #, sh-format
 msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:150
+#: git-submodule.sh:186
 #, sh-format
 msgid "Clone of '$url' into submodule path '$sm_path' failed"
 msgstr ""
 
-#: git-submodule.sh:160
+#: git-submodule.sh:196
 #, sh-format
 msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
 msgstr ""
 
-#: git-submodule.sh:249
+#: git-submodule.sh:285
 #, sh-format
 msgid "repo URL: '$repo' must be absolute or begin with ./|../"
 msgstr ""
 
-#: git-submodule.sh:266
+#: git-submodule.sh:302
 #, sh-format
 msgid "'$sm_path' already exists in the index"
 msgstr ""
 
-#: git-submodule.sh:270
+#: git-submodule.sh:306
 #, sh-format
 msgid ""
 "The following path is ignored by one of your .gitignore files:\n"
@@ -5059,155 +5180,155 @@ msgid ""
 "Use -f if you really want to add it."
 msgstr ""
 
-#: git-submodule.sh:281
+#: git-submodule.sh:317
 #, sh-format
 msgid "Adding existing repo at '$sm_path' to the index"
 msgstr ""
 
-#: git-submodule.sh:283
+#: git-submodule.sh:319
 #, sh-format
 msgid "'$sm_path' already exists and is not a valid git repo"
 msgstr ""
 
-#: git-submodule.sh:297
+#: git-submodule.sh:333
 #, sh-format
 msgid "Unable to checkout submodule '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:302
+#: git-submodule.sh:338
 #, sh-format
 msgid "Failed to add submodule '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:307
+#: git-submodule.sh:343
 #, sh-format
 msgid "Failed to register submodule '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:349
+#: git-submodule.sh:385
 #, sh-format
 msgid "Entering '$prefix$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:363
+#: git-submodule.sh:399
 #, sh-format
 msgid "Stopping at '$sm_path'; script returned non-zero status."
 msgstr ""
 
-#: git-submodule.sh:406
+#: git-submodule.sh:442
 #, sh-format
 msgid "No url found for submodule path '$sm_path' in .gitmodules"
 msgstr ""
 
-#: git-submodule.sh:415
+#: git-submodule.sh:451
 #, sh-format
 msgid "Failed to register url for submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:417
+#: git-submodule.sh:453
 #, sh-format
 msgid "Submodule '$name' ($url) registered for path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:425
+#: git-submodule.sh:461
 #, sh-format
 msgid "Failed to register update mode for submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:524
+#: git-submodule.sh:560
 #, sh-format
 msgid ""
 "Submodule path '$sm_path' not initialized\n"
 "Maybe you want to use 'update --init'?"
 msgstr ""
 
-#: git-submodule.sh:537
+#: git-submodule.sh:573
 #, sh-format
 msgid "Unable to find current revision in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:556
+#: git-submodule.sh:592
 #, sh-format
 msgid "Unable to fetch in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:570
+#: git-submodule.sh:606
 #, sh-format
 msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:571
+#: git-submodule.sh:607
 #, sh-format
 msgid "Submodule path '$sm_path': rebased into '$sha1'"
 msgstr ""
 
-#: git-submodule.sh:576
+#: git-submodule.sh:612
 #, sh-format
 msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:577
+#: git-submodule.sh:613
 #, sh-format
 msgid "Submodule path '$sm_path': merged in '$sha1'"
 msgstr ""
 
-#: git-submodule.sh:582
+#: git-submodule.sh:618
 #, sh-format
 msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:583
+#: git-submodule.sh:619
 #, sh-format
 msgid "Submodule path '$sm_path': checked out '$sha1'"
 msgstr ""
 
-#: git-submodule.sh:605 git-submodule.sh:928
+#: git-submodule.sh:641 git-submodule.sh:964
 #, sh-format
 msgid "Failed to recurse into submodule path '$sm_path'"
 msgstr ""
 
-#: git-submodule.sh:713
+#: git-submodule.sh:749
 msgid "--cached cannot be used with --files"
 msgstr ""
 
 #. unexpected type
-#: git-submodule.sh:753
+#: git-submodule.sh:789
 #, sh-format
 msgid "unexpected mode $mod_dst"
 msgstr ""
 
-#: git-submodule.sh:771
+#: git-submodule.sh:807
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_src"
 msgstr ""
 
-#: git-submodule.sh:774
+#: git-submodule.sh:810
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_dst"
 msgstr ""
 
-#: git-submodule.sh:777
+#: git-submodule.sh:813
 #, sh-format
 msgid "  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 msgstr ""
 
-#: git-submodule.sh:802
+#: git-submodule.sh:838
 msgid "blob"
 msgstr ""
 
-#: git-submodule.sh:803
+#: git-submodule.sh:839
 msgid "submodule"
 msgstr ""
 
-#: git-submodule.sh:840
+#: git-submodule.sh:876
 msgid "# Submodules changed but not updated:"
 msgstr ""
 
-#: git-submodule.sh:842
+#: git-submodule.sh:878
 msgid "# Submodule changes to be committed:"
 msgstr ""
 
-#: git-submodule.sh:974
+#: git-submodule.sh:1022
 #, sh-format
 msgid "Synchronizing submodule url for '$name'"
 msgstr ""
index b0ff6f99a76f25e07517d337356691f662302f3b..2635c2cf7b0516eac3df9855058f6c57a086f596 100644 (file)
--- a/po/sv.po
+++ b/po/sv.po
@@ -7,8 +7,8 @@ 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-06-08 10:20+0800\n"
-"PO-Revision-Date: 2012-07-01 22:59+0100\n"
+"POT-Creation-Date: 2012-07-03 10:23+0800\n"
+"PO-Revision-Date: 2012-07-04 19:33+0100\n"
 "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n"
 "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
 "Language: sv\n"
@@ -57,8 +57,8 @@ msgstr "kunde inte öppna \"%s\""
 msgid "Repository lacks these prerequisite commits:"
 msgstr "Arkivet saknar dessa nödvändiga incheckningar:"
 
-#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
-#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:290
+#: builtin/log.c:721 builtin/log.c:1310 builtin/log.c:1529 builtin/merge.c:347
 #: builtin/shortlog.c:181
 msgid "revision walk setup failed"
 msgstr "misslyckades skapa revisionstraversering"
@@ -71,44 +71,48 @@ msgstr[0] "Paketet (bundlen) innehåller %d referens"
 msgstr[1] "Paketet (bundlen) innehåller %d referenser"
 
 #: bundle.c:192
+msgid "The bundle records a complete history."
+msgstr "Paketet (bundlen) beskriver en komplett historik."
+
+#: bundle.c:195
 #, c-format
 msgid "The bundle requires this ref"
 msgid_plural "The bundle requires these %d refs"
 msgstr[0] "Paketet (bundlen) kräver denna referens"
 msgstr[1] "Paketet (bundlen) kräver dessa %d referenser"
 
-#: bundle.c:290
+#: bundle.c:294
 msgid "rev-list died"
 msgstr "rev-list dog"
 
-#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#: bundle.c:300 builtin/log.c:1206 builtin/shortlog.c:284
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr "okänt argument: %s"
 
-#: bundle.c:331
+#: bundle.c:335
 #, c-format
 msgid "ref '%s' is excluded by the rev-list options"
 msgstr "referensen \"%s\" exkluderas av argumenten till rev-list"
 
-#: bundle.c:376
+#: bundle.c:380
 msgid "Refusing to create empty bundle."
 msgstr "Vägrar skapa ett tomt paket (bundle)."
 
-#: bundle.c:394
+#: bundle.c:398
 msgid "Could not spawn pack-objects"
 msgstr "Kunde inte starta pack-objects"
 
-#: bundle.c:412
+#: bundle.c:416
 msgid "pack-objects died"
 msgstr "pack-objects misslyckades"
 
-#: bundle.c:415
+#: bundle.c:419
 #, c-format
 msgid "cannot create '%s'"
 msgstr "kan inte skapa \"%s\""
 
-#: bundle.c:437
+#: bundle.c:441
 msgid "index-pack died"
 msgstr "index-pack dog"
 
@@ -287,16 +291,16 @@ msgstr "\"%s\": %s"
 msgid "'%s': short read %s"
 msgstr "\"%s\": kort läsning %s"
 
-#: help.c:207
+#: help.c:208
 #, c-format
 msgid "available git commands in '%s'"
 msgstr "git-kommandon tillgängliga i \"%s\""
 
-#: help.c:214
+#: help.c:215
 msgid "git commands available from elsewhere on your $PATH"
 msgstr "git-kommandon från andra platser i din $PATH"
 
-#: help.c:270
+#: help.c:271
 #, c-format
 msgid ""
 "'%s' appears to be a git command, but we were not\n"
@@ -305,11 +309,11 @@ msgstr ""
 "\"%s\" verkar vara ett git-kommando, men vi kan inte\n"
 "köra det. Kanske git-%s är trasigt?"
 
-#: help.c:327
+#: help.c:328
 msgid "Uh oh. Your system reports no Git commands at all."
 msgstr "Oj då. Ditt system rapporterar inga Git-kommandon alls."
 
-#: help.c:349
+#: help.c:350
 #, c-format
 msgid ""
 "WARNING: You called a Git command named '%s', which does not exist.\n"
@@ -318,17 +322,17 @@ msgstr ""
 "VARNING: Du anropade ett Git-kommando vid namn \"%s\", som inte finns.\n"
 "Fortsätter under förutsättningen att du menade \"%s\""
 
-#: help.c:354
+#: help.c:355
 #, c-format
 msgid "in %0.1f seconds automatically..."
 msgstr "automatiskt om %0.1f sekunder..."
 
-#: help.c:361
+#: help.c:362
 #, c-format
 msgid "git: '%s' is not a git command. See 'git --help'."
 msgstr "git: \"%s\" är inte ett git-kommando. Se \"git --help\"."
 
-#: help.c:365
+#: help.c:366
 msgid ""
 "\n"
 "Did you mean this?"
@@ -664,240 +668,343 @@ msgstr "kan inte slå upp aktuell användare i passwd-filen: %s"
 msgid "no such user"
 msgstr "okänd användare"
 
-#: wt-status.c:135
+#: wt-status.c:141
 msgid "Unmerged paths:"
 msgstr "Ej sammanslagna sökvägar:"
 
-#: wt-status.c:141 wt-status.c:158
+#: wt-status.c:168 wt-status.c:195
 #, c-format
 msgid "  (use \"git reset %s <file>...\" to unstage)"
 msgstr "  (använd \"git reset %s <fil>...\" för att ta bort från kö)"
 
-#: wt-status.c:143 wt-status.c:160
+#: wt-status.c:170 wt-status.c:197
 msgid "  (use \"git rm --cached <file>...\" to unstage)"
 msgstr "  (använd \"git rm --cached <fil>...\" för att ta bort från kö)"
 
-#: wt-status.c:144
+#: wt-status.c:174
+msgid "  (use \"git add <file>...\" to mark resolution)"
+msgstr "  (använd \"git add <fil>...\" för att ange lösning)"
+
+#: wt-status.c:176 wt-status.c:180
 msgid "  (use \"git add/rm <file>...\" as appropriate to mark resolution)"
 msgstr "  (använd \"git add/rm <fil>...\" som lämpligt för att ange lösning)"
 
-#: wt-status.c:152
+#: wt-status.c:178
+msgid "  (use \"git rm <file>...\" to mark resolution)"
+msgstr "  (använd \"git rm <fil>...\" för att ange lösning)"
+
+#: wt-status.c:189
 msgid "Changes to be committed:"
 msgstr "Ändringar att checka in:"
 
-#: wt-status.c:170
+#: wt-status.c:207
 msgid "Changes not staged for commit:"
 msgstr "Ändringar ej i incheckningskön:"
 
-#: wt-status.c:174
+#: wt-status.c:211
 msgid "  (use \"git add <file>...\" to update what will be committed)"
 msgstr ""
 "  (använd \"git add <fil>...\" för att uppdatera vad som skall checkas in)"
 
-#: wt-status.c:176
+#: wt-status.c:213
 msgid "  (use \"git add/rm <file>...\" to update what will be committed)"
 msgstr ""
 "  (använd \"git add/rm <fil>...\" för att uppdatera vad som skall checkas in)"
 
-#: wt-status.c:177
+#: wt-status.c:214
 msgid ""
 "  (use \"git checkout -- <file>...\" to discard changes in working directory)"
 msgstr ""
 "  (använd \"git checkout -- <fil>...\" för att förkasta ändringar i "
 "arbetskatalogen)"
 
-#: wt-status.c:179
+#: wt-status.c:216
 msgid "  (commit or discard the untracked or modified content in submodules)"
 msgstr ""
 "  (checka in eller förkasta ospårat eller ändrat innehåll i undermoduler)"
 
 # %s är ett verb ("Untracked"/"Ignored"); lägg till ett -e.
-#: wt-status.c:188
+#: wt-status.c:225
 #, c-format
 msgid "%s files:"
 msgstr "%se filer:"
 
-#: wt-status.c:191
+#: wt-status.c:228
 #, c-format
 msgid "  (use \"git %s <file>...\" to include in what will be committed)"
 msgstr ""
 "  (använd \"git %s <fil>...\" för att ta med i vad som skall checkas in)"
 
-#: wt-status.c:208
+#: wt-status.c:245
 msgid "bug"
 msgstr "programfel"
 
-#: wt-status.c:213
+#: wt-status.c:250
 msgid "both deleted:"
 msgstr "borttaget av bägge:"
 
-#: wt-status.c:214
+#: wt-status.c:251
 msgid "added by us:"
 msgstr "tillagt av oss:"
 
-#: wt-status.c:215
+#: wt-status.c:252
 msgid "deleted by them:"
 msgstr "borttaget av dem:"
 
-#: wt-status.c:216
+#: wt-status.c:253
 msgid "added by them:"
 msgstr "tillagt av dem:"
 
-#: wt-status.c:217
+#: wt-status.c:254
 msgid "deleted by us:"
 msgstr "borttaget av oss:"
 
-#: wt-status.c:218
+#: wt-status.c:255
 msgid "both added:"
 msgstr "tillagt av bägge:"
 
-#: wt-status.c:219
+#: wt-status.c:256
 msgid "both modified:"
 msgstr "ändrat av bägge:"
 
-#: wt-status.c:249
+#: wt-status.c:286
 msgid "new commits, "
 msgstr "nya incheckningar, "
 
-#: wt-status.c:251
+#: wt-status.c:288
 msgid "modified content, "
 msgstr "ändrat innehåll, "
 
-#: wt-status.c:253
+#: wt-status.c:290
 msgid "untracked content, "
 msgstr "ospårat innehåll, "
 
-#: wt-status.c:267
+#: wt-status.c:304
 #, c-format
 msgid "new file:   %s"
 msgstr "ny fil:     %s"
 
-#: wt-status.c:270
+#: wt-status.c:307
 #, c-format
 msgid "copied:     %s -> %s"
 msgstr "kopierad:   %s -> %s"
 
-#: wt-status.c:273
+#: wt-status.c:310
 #, c-format
 msgid "deleted:    %s"
 msgstr "borttagen:  %s"
 
-#: wt-status.c:276
+#: wt-status.c:313
 #, c-format
 msgid "modified:   %s"
 msgstr "ändrad:     %s"
 
-#: wt-status.c:279
+#: wt-status.c:316
 #, c-format
 msgid "renamed:    %s -> %s"
 msgstr "namnbyte:   %s -> %s"
 
-#: wt-status.c:282
+#: wt-status.c:319
 #, c-format
 msgid "typechange: %s"
 msgstr "typbyte:    %s"
 
-#: wt-status.c:285
+#: wt-status.c:322
 #, c-format
 msgid "unknown:    %s"
 msgstr "okänd:      %s"
 
-#: wt-status.c:288
+#: wt-status.c:325
 #, c-format
 msgid "unmerged:   %s"
 msgstr "osammansl.: %s"
 
-#: wt-status.c:291
+#: wt-status.c:328
 #, c-format
 msgid "bug: unhandled diff status %c"
 msgstr "programfel: diff-status %c ej hanterad"
 
-#: wt-status.c:737
+#: wt-status.c:786
+msgid "You have unmerged paths."
+msgstr "Du har ej sammanslagna sökvägar."
+
+#: wt-status.c:789 wt-status.c:913
+msgid "  (fix conflicts and run \"git commit\")"
+msgstr "  (rätta konflikter och kör \"git commit\")"
+
+#: wt-status.c:792
+msgid "All conflicts fixed but you are still merging."
+msgstr "Alla konflikter har rättats men du är fortfarande i en sammanslagning."
+
+#: wt-status.c:795
+msgid "  (use \"git commit\" to conclude merge)"
+msgstr "  (använd \"git commit\" för att slutföra sammanslagningen)"
+
+#: wt-status.c:805
+msgid "You are in the middle of an am session."
+msgstr "Du är i mitten av en körning av \"git am\"."
+
+#: wt-status.c:808
+msgid "The current patch is empty."
+msgstr "Aktuell patch är tom."
+
+#: wt-status.c:812
+msgid "  (fix conflicts and then run \"git am --resolved\")"
+msgstr "  (rätta konflikter och kör sedan \"git am --resolved\")"
+
+#: wt-status.c:814
+msgid "  (use \"git am --skip\" to skip this patch)"
+msgstr "  (använd \"git am --skip\" för att hoppa över patchen)"
+
+#: wt-status.c:816
+msgid "  (use \"git am --abort\" to restore the original branch)"
+msgstr "  (använd \"git am --abort\" för att återställa ursprungsgrenen)"
+
+#: wt-status.c:874 wt-status.c:884
+msgid "You are currently rebasing."
+msgstr "Du håller på med en ombasering."
+
+#: wt-status.c:877
+msgid "  (fix conflicts and then run \"git rebase --continue\")"
+msgstr "  (rätta konflikter och kör sedan \"git rebase --continue\")"
+
+#: wt-status.c:879
+msgid "  (use \"git rebase --skip\" to skip this patch)"
+msgstr "  (använd \"git rebase --skip\" för att hoppa över patchen)"
+
+#: wt-status.c:881
+msgid "  (use \"git rebase --abort\" to check out the original branch)"
+msgstr "  (använd \"git rebase --abort\" för att checka ut ursprungsgrenen)"
+
+#: wt-status.c:887
+msgid "  (all conflicts fixed: run \"git rebase --continue\")"
+msgstr "  (alla konflikter rättade: kör \"git rebase --continue\")"
+
+#: wt-status.c:889
+msgid "You are currently splitting a commit during a rebase."
+msgstr "Du håller på att dela upp en incheckning i en ombasering."
+
+#: wt-status.c:892
+msgid "  (Once your working directory is clean, run \"git rebase --continue\")"
+msgstr "  (Så fort din arbetskatalog är ren, kör \"git rebase --continue\")"
+
+#: wt-status.c:894
+msgid "You are currently editing a commit during a rebase."
+msgstr "Du håller på att redigera en incheckning under en ombasering."
+
+#: wt-status.c:897
+msgid "  (use \"git commit --amend\" to amend the current commit)"
+msgstr ""
+"  (använd \"git commit --amend\" för att lägga till på aktuell incheckning)"
+
+#: wt-status.c:899
+msgid ""
+"  (use \"git rebase --continue\" once you are satisfied with your changes)"
+msgstr "  (använd \"git rebase --continue\" när du är nöjd med dina ändringar)"
+
+#: wt-status.c:909
+msgid "You are currently cherry-picking."
+msgstr "Du håller på med en \"cherry-pick\"."
+
+#: wt-status.c:916
+msgid "  (all conflicts fixed: run \"git commit\")"
+msgstr "  (alla konflikter har rättats: kör \"git commit\")"
+
+#: wt-status.c:925
+msgid "You are currently bisecting."
+msgstr "Du håller på med en \"bisect\"."
+
+#: wt-status.c:928
+msgid "  (use \"git bisect reset\" to get back to the original branch)"
+msgstr ""
+"  (använd \"git bisect reset\" för att komma tillbaka till ursprungsgrenen)"
+
+#: wt-status.c:979
 msgid "On branch "
 msgstr "På grenen "
 
-#: wt-status.c:744
+#: wt-status.c:986
 msgid "Not currently on any branch."
 msgstr "Inte på någon gren för närvarande."
 
-#: wt-status.c:755
+#: wt-status.c:998
 msgid "Initial commit"
 msgstr "Första incheckning"
 
-#: wt-status.c:769
+#: wt-status.c:1012
 msgid "Untracked"
 msgstr "Ospårad"
 
-#: wt-status.c:771
+#: wt-status.c:1014
 msgid "Ignored"
 msgstr "Ignorerad"
 
 # %s är nästa sträng eller tom.
-#: wt-status.c:773
+#: wt-status.c:1016
 #, c-format
 msgid "Untracked files not listed%s"
 msgstr "Ospårade filer visas ej%s"
 
-#: wt-status.c:775
+#: wt-status.c:1018
 msgid " (use -u option to show untracked files)"
 msgstr " (använd flaggan -u för att visa ospårade filer)"
 
-#: wt-status.c:781
+#: wt-status.c:1024
 msgid "No changes"
 msgstr "Inga ändringar"
 
-#: wt-status.c:785
+#: wt-status.c:1028
 #, c-format
 msgid "no changes added to commit%s\n"
 msgstr "inga ändringar att checka in%s\n"
 
-#: wt-status.c:787
+#: wt-status.c:1030
 msgid " (use \"git add\" and/or \"git commit -a\")"
 msgstr " (använd \"git add\" och/eller \"git commit -a\")"
 
-#: wt-status.c:789
+#: wt-status.c:1032
 #, c-format
 msgid "nothing added to commit but untracked files present%s\n"
 msgstr "inget köat för incheckning, men ospårade filer finns%s\n"
 
-#: wt-status.c:791
+#: wt-status.c:1034
 msgid " (use \"git add\" to track)"
 msgstr " (använd \"git add\" för att spåra)"
 
-#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#: wt-status.c:1036 wt-status.c:1039 wt-status.c:1042
 #, c-format
 msgid "nothing to commit%s\n"
 msgstr "inget att checka in%s\n"
 
-#: wt-status.c:794
+#: wt-status.c:1037
 msgid " (create/copy files and use \"git add\" to track)"
 msgstr " (skapa/kopiera filer och använd \"git add\" för att spåra)"
 
-#: wt-status.c:797
+#: wt-status.c:1040
 msgid " (use -u to show untracked files)"
 msgstr " (använd -u för att visa ospårade filer)"
 
-#: wt-status.c:800
+#: wt-status.c:1043
 msgid " (working directory clean)"
 msgstr " (arbetskatalogen ren)"
 
-#: wt-status.c:908
+#: wt-status.c:1151
 msgid "HEAD (no branch)"
 msgstr "HEAD (ingen gren)"
 
-#: wt-status.c:914
+#: wt-status.c:1157
 msgid "Initial commit on "
 msgstr "Första incheckning på "
 
-#: wt-status.c:929
+#: wt-status.c:1172
 msgid "behind "
 msgstr "efter "
 
-#: wt-status.c:932 wt-status.c:935
+#: wt-status.c:1175 wt-status.c:1178
 msgid "ahead "
 msgstr "före "
 
-#: wt-status.c:937
+#: wt-status.c:1180
 msgid ", behind "
 msgstr ", efter "
 
@@ -924,7 +1031,7 @@ msgstr "Sökvägen \"%s\" är i undermodulen \"%.*s\""
 msgid "Unstaged changes after refreshing the index:"
 msgstr "Ospårade ändringar efter att ha uppdaterat indexet:"
 
-#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#: builtin/add.c:195 builtin/add.c:459 builtin/rm.c:186
 #, c-format
 msgid "pathspec '%s' did not match any files"
 msgstr "sökvägsangivelsen \"%s\" motsvarade inte några filer"
@@ -1001,7 +1108,7 @@ msgstr "Kanske menade du att skriva \"git add .\"?\n"
 msgid "index file corrupt"
 msgstr "indexfilen trasig"
 
-#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+#: builtin/add.c:480 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
 msgid "Unable to write new index file"
 msgstr "Kunde inte skriva ny indexfil"
 
@@ -2550,22 +2657,22 @@ msgstr "ogiltig flagga: %s"
 msgid "Not a git repository"
 msgstr "Inte ett git-arkiv"
 
-#: builtin/diff.c:347
+#: builtin/diff.c:341
 #, c-format
 msgid "invalid object '%s' given."
 msgstr "objektet \"%s\" som angavs är felaktigt."
 
-#: builtin/diff.c:352
+#: builtin/diff.c:346
 #, c-format
 msgid "more than %d trees given: '%s'"
 msgstr "mer än %d träd angavs: \"%s\""
 
-#: builtin/diff.c:362
+#: builtin/diff.c:356
 #, c-format
 msgid "more than two blobs given: '%s'"
 msgstr "mer än två blobbar angavs: \"%s\""
 
-#: builtin/diff.c:370
+#: builtin/diff.c:364
 #, c-format
 msgid "unhandled object '%s' given."
 msgstr "ej hanterat objekt \"%s\" angavs."
@@ -2815,30 +2922,30 @@ msgstr "--[no-]exclude-standard kan inte användas för spårat innehåll."
 msgid "both --cached and trees are given."
 msgstr "både --cached och träd angavs."
 
-#: builtin/help.c:59
+#: builtin/help.c:63
 #, c-format
 msgid "unrecognized help format '%s'"
 msgstr "okänt hjälpformat: %s"
 
-#: builtin/help.c:87
+#: builtin/help.c:91
 msgid "Failed to start emacsclient."
 msgstr "Misslyckades starta emacsclient."
 
-#: builtin/help.c:100
+#: builtin/help.c:104
 msgid "Failed to parse emacsclient version."
 msgstr "Kunde inte tolka emacsclient-version."
 
-#: builtin/help.c:108
+#: builtin/help.c:112
 #, c-format
 msgid "emacsclient version '%d' too old (< 22)."
 msgstr "emacsclient version \"%d\" för gammal (< 22)."
 
-#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#: builtin/help.c:130 builtin/help.c:158 builtin/help.c:167 builtin/help.c:175
 #, c-format
 msgid "failed to exec '%s': %s"
 msgstr "exec misslyckades för \"%s\": %s"
 
-#: builtin/help.c:211
+#: builtin/help.c:215
 #, c-format
 msgid ""
 "'%s': path for unsupported man viewer.\n"
@@ -2847,7 +2954,7 @@ msgstr ""
 "\"%s\": sökväg för man-visare som ej stöds.\n"
 "Använd \"man.<verktyg>.cmd\" istället."
 
-#: builtin/help.c:223
+#: builtin/help.c:227
 #, c-format
 msgid ""
 "'%s': cmd for supported man viewer.\n"
@@ -2856,266 +2963,277 @@ msgstr ""
 "\"%s\": kommando för man-visare som stöds.\n"
 "Använd \"man.<verktyg>.path\" istället."
 
-#: builtin/help.c:287
+#: builtin/help.c:291
 msgid "The most commonly used git commands are:"
 msgstr "De mest använda git-kommandona är:"
 
-#: builtin/help.c:355
+#: builtin/help.c:359
 #, c-format
 msgid "'%s': unknown man viewer."
 msgstr "\"%s\": okänd man-visare."
 
-#: builtin/help.c:372
+#: builtin/help.c:376
 msgid "no man viewer handled the request"
 msgstr "ingen man-visare hanterade förfrågan"
 
-#: builtin/help.c:380
+#: builtin/help.c:384
 msgid "no info viewer handled the request"
 msgstr "ingen info-visare hanterade förfrågan"
 
-#: builtin/help.c:391
+#: builtin/help.c:395
 #, c-format
 msgid "'%s': not a documentation directory."
 msgstr "\"%s\": inte en dokumentationskatalog."
 
-#: builtin/help.c:432 builtin/help.c:439
+#: builtin/help.c:436 builtin/help.c:443
 #, c-format
 msgid "usage: %s%s"
 msgstr "användning: %s%s"
 
-#: builtin/help.c:453
+#: builtin/help.c:459
 #, c-format
 msgid "`git %s' is aliased to `%s'"
 msgstr "\"git %s\" är ett alias för \"%s\""
 
-#: builtin/index-pack.c:169
+#: builtin/index-pack.c:170
 #, c-format
 msgid "object type mismatch at %s"
 msgstr "objekttyp stämmer inte överens vid %s"
 
-#: builtin/index-pack.c:189
+#: builtin/index-pack.c:190
 msgid "object of unexpected type"
 msgstr "objekt av oväntad typ"
 
-#: builtin/index-pack.c:226
+#: builtin/index-pack.c:227
 #, c-format
 msgid "cannot fill %d byte"
 msgid_plural "cannot fill %d bytes"
 msgstr[0] "kan inte fylla %d byte"
 msgstr[1] "kan inte fylla %d byte"
 
-#: builtin/index-pack.c:236
+#: builtin/index-pack.c:237
 msgid "early EOF"
 msgstr "tidigt filslut"
 
-#: builtin/index-pack.c:237
+#: builtin/index-pack.c:238
 msgid "read error on input"
 msgstr "indataläsfel"
 
-#: builtin/index-pack.c:249
+#: builtin/index-pack.c:250
 msgid "used more bytes than were available"
 msgstr "använde fler byte än tillgängligt"
 
-#: builtin/index-pack.c:256
+#: builtin/index-pack.c:257
 msgid "pack too large for current definition of off_t"
 msgstr "paket för stort för nuvarande definition av off_t"
 
-#: builtin/index-pack.c:272
+#: builtin/index-pack.c:273
 #, c-format
 msgid "unable to create '%s'"
 msgstr "kunde inte skapa \"%s\""
 
-#: builtin/index-pack.c:277
+#: builtin/index-pack.c:278
 #, c-format
 msgid "cannot open packfile '%s'"
 msgstr "kan inte öppna paketfilen \"%s\""
 
-#: builtin/index-pack.c:291
+#: builtin/index-pack.c:292
 msgid "pack signature mismatch"
 msgstr "paketsignatur stämmer inte överens"
 
-#: builtin/index-pack.c:311
+#: builtin/index-pack.c:312
 #, c-format
 msgid "pack has bad object at offset %lu: %s"
 msgstr "paketet har felaktigt objekt vid index %lu: %s"
 
-#: builtin/index-pack.c:405
+#: builtin/index-pack.c:434
 #, c-format
 msgid "inflate returned %d"
 msgstr "inflate returnerade %d"
 
-#: builtin/index-pack.c:450
+#: builtin/index-pack.c:483
 msgid "offset value overflow for delta base object"
 msgstr "indexvärdespill för deltabasobjekt"
 
-#: builtin/index-pack.c:458
+#: builtin/index-pack.c:491
 msgid "delta base offset is out of bound"
 msgstr "deltabasindex utanför gränsen"
 
-#: builtin/index-pack.c:466
+#: builtin/index-pack.c:499
 #, c-format
 msgid "unknown object type %d"
 msgstr "okänd objekttyp %d"
 
-#: builtin/index-pack.c:495
+#: builtin/index-pack.c:531
 msgid "cannot pread pack file"
 msgstr "kan inte utföra \"pread\" på paketfil"
 
-#: builtin/index-pack.c:497
+#: builtin/index-pack.c:533
 #, c-format
 msgid "premature end of pack file, %lu byte missing"
 msgid_plural "premature end of pack file, %lu bytes missing"
 msgstr[0] "för tidigt slut på paketfilen, %lu byte saknas"
 msgstr[1] "för tidigt slut på paketfilen, %lu byte saknas"
 
-#: builtin/index-pack.c:510
+#: builtin/index-pack.c:555
 msgid "serious inflate inconsistency"
 msgstr "allvarlig inflate-inkonsekvens"
 
-#: builtin/index-pack.c:583
-#, c-format
-msgid "cannot read existing object %s"
-msgstr "kan inte läsa befintligt objekt %s"
-
-#: builtin/index-pack.c:586
+#: builtin/index-pack.c:646 builtin/index-pack.c:652 builtin/index-pack.c:675
+#: builtin/index-pack.c:709 builtin/index-pack.c:718
 #, c-format
 msgid "SHA1 COLLISION FOUND WITH %s !"
 msgstr "SHA1-KOLLISION UPPTÄCKT VID %s !"
 
-#: builtin/index-pack.c:598
+#: builtin/index-pack.c:649 builtin/pack-objects.c:170
+#: builtin/pack-objects.c:262
+#, c-format
+msgid "unable to read %s"
+msgstr "kunde inte läsa %s"
+
+#: builtin/index-pack.c:715
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "kan inte läsa befintligt objekt %s"
+
+#: builtin/index-pack.c:729
 #, c-format
 msgid "invalid blob object %s"
 msgstr "ogiltigt blob-objekt %s"
 
-#: builtin/index-pack.c:610
+#: builtin/index-pack.c:744
 #, c-format
 msgid "invalid %s"
 msgstr "ogiltigt %s"
 
-#: builtin/index-pack.c:612
+#: builtin/index-pack.c:746
 msgid "Error in object"
 msgstr "Fel i objekt"
 
-#: builtin/index-pack.c:614
+#: builtin/index-pack.c:748
 #, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "Inte alla barnobjekt för %s kan nås"
 
-#: builtin/index-pack.c:687 builtin/index-pack.c:713
+#: builtin/index-pack.c:818 builtin/index-pack.c:844
 msgid "failed to apply delta"
 msgstr "misslyckades tillämpa delta"
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Receiving objects"
 msgstr "Tar bort objeckt"
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Indexing objects"
 msgstr "Skapar index för objekt"
 
-#: builtin/index-pack.c:872
+#: builtin/index-pack.c:1009
 msgid "pack is corrupted (SHA1 mismatch)"
 msgstr "paketet är trasigt (SHA1 stämmer inte)"
 
-#: builtin/index-pack.c:877
+#: builtin/index-pack.c:1014
 msgid "cannot fstat packfile"
 msgstr "kan inte utföra \"fstat\" på paketfil"
 
-#: builtin/index-pack.c:880
+#: builtin/index-pack.c:1017
 msgid "pack has junk at the end"
 msgstr "paket har skräp i slutet"
 
-#: builtin/index-pack.c:903
+#: builtin/index-pack.c:1028
+msgid "confusion beyond insanity in parse_pack_objects()"
+msgstr "förvirrad bortom vanvett i parse_pack_objects()"
+
+#: builtin/index-pack.c:1051
 msgid "Resolving deltas"
 msgstr "Analyserar delta"
 
-#: builtin/index-pack.c:954
+#: builtin/index-pack.c:1102
 msgid "confusion beyond insanity"
 msgstr "förvirrad bortom vanvett"
 
-#: builtin/index-pack.c:973
+#: builtin/index-pack.c:1121
 #, c-format
 msgid "pack has %d unresolved delta"
 msgid_plural "pack has %d unresolved deltas"
 msgstr[0] "paketet har %d oanalyserat delta"
 msgstr[1] "paketet har %d oanalyserade delta"
 
-#: builtin/index-pack.c:998
+#: builtin/index-pack.c:1146
 #, c-format
 msgid "unable to deflate appended object (%d)"
 msgstr "kunde inte utföra \"deflate\" på tillagt objekt (%d)"
 
-#: builtin/index-pack.c:1077
+#: builtin/index-pack.c:1225
 #, c-format
 msgid "local object %s is corrupt"
 msgstr "lokalt objekt %s är trasigt"
 
-#: builtin/index-pack.c:1101
+#: builtin/index-pack.c:1249
 msgid "error while closing pack file"
 msgstr "fel vid stängning av paketfil"
 
-#: builtin/index-pack.c:1114
+#: builtin/index-pack.c:1262
 #, c-format
 msgid "cannot write keep file '%s'"
 msgstr "kan inte ta skriva \"keep\"-fil \"%s\""
 
-#: builtin/index-pack.c:1122
+#: builtin/index-pack.c:1270
 #, c-format
 msgid "cannot close written keep file '%s'"
 msgstr "akn inte stänga skriven \"keep\"-fil \"%s\""
 
-#: builtin/index-pack.c:1135
+#: builtin/index-pack.c:1283
 msgid "cannot store pack file"
 msgstr "kan inte spara paketfil"
 
-#: builtin/index-pack.c:1146
+#: builtin/index-pack.c:1294
 msgid "cannot store index file"
 msgstr "kan inte spara indexfil"
 
-#: builtin/index-pack.c:1247
+#: builtin/index-pack.c:1395
 #, c-format
 msgid "Cannot open existing pack file '%s'"
 msgstr "Kan inte öppna befintlig paketfil \"%s\""
 
-#: builtin/index-pack.c:1249
+#: builtin/index-pack.c:1397
 #, c-format
 msgid "Cannot open existing pack idx file for '%s'"
 msgstr "Kan inte öppna befintligt paket-idx-fil för \"%s\""
 
-#: builtin/index-pack.c:1296
+#: builtin/index-pack.c:1444
 #, c-format
 msgid "non delta: %d object"
 msgid_plural "non delta: %d objects"
 msgstr[0] "icke-delta: %d objekt"
 msgstr[1] "icke-delta: %d objekt"
 
-#: builtin/index-pack.c:1303
+#: builtin/index-pack.c:1451
 #, c-format
 msgid "chain length = %d: %lu object"
 msgid_plural "chain length = %d: %lu objects"
 msgstr[0] "kedjelängd = %d: %lu objekt"
 msgstr[1] "kedjelängd = %d: %lu objekt"
 
-#: builtin/index-pack.c:1330
+#: builtin/index-pack.c:1478
 msgid "Cannot come back to cwd"
 msgstr "Kan inte gå tillbaka till arbetskatalogen (cwd)"
 
-#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
-#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#: builtin/index-pack.c:1522 builtin/index-pack.c:1525
+#: builtin/index-pack.c:1537 builtin/index-pack.c:1541
 #, c-format
 msgid "bad %s"
 msgstr "felaktig %s"
 
-#: builtin/index-pack.c:1407
+#: builtin/index-pack.c:1555
 msgid "--fix-thin cannot be used without --stdin"
 msgstr "--fix-thin kan inte användas med --stdin"
 
-#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#: builtin/index-pack.c:1559 builtin/index-pack.c:1569
 #, c-format
 msgid "packfile name '%s' does not end with '.pack'"
 msgstr "paketfilnamnet \"%s\" slutar inte med \".pack\""
 
-#: builtin/index-pack.c:1430
+#: builtin/index-pack.c:1578
 msgid "--verify with no packfile name given"
 msgstr "--verify angavs utan paketfilnamn"
 
@@ -3263,100 +3381,100 @@ msgstr "Kan inte komma åt aktuell arbetskatalog"
 msgid "Cannot access work tree '%s'"
 msgstr "Kan inte komma åt arbetskatalogen \"%s\""
 
-#: builtin/log.c:188
+#: builtin/log.c:189
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr "Slututdata: %d %s\n"
 
-#: builtin/log.c:401 builtin/log.c:489
+#: builtin/log.c:402 builtin/log.c:490
 #, c-format
 msgid "Could not read object %s"
 msgstr "Kunde inte läsa objektet %s"
 
-#: builtin/log.c:513
+#: builtin/log.c:514
 #, c-format
 msgid "Unknown type: %d"
 msgstr "Okänd typ: %d"
 
-#: builtin/log.c:602
+#: builtin/log.c:603
 msgid "format.headers without value"
 msgstr "format.headers utan värde"
 
-#: builtin/log.c:676
+#: builtin/log.c:677
 msgid "name of output directory is too long"
 msgstr "namnet på utdatakatalogen är för långt"
 
-#: builtin/log.c:687
+#: builtin/log.c:688
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr "Kan inte öppna patchfilen %s"
 
-#: builtin/log.c:701
+#: builtin/log.c:702
 msgid "Need exactly one range."
 msgstr "Behöver precis ett intervall."
 
-#: builtin/log.c:709
+#: builtin/log.c:710
 msgid "Not a range."
 msgstr "Inte ett intervall."
 
-#: builtin/log.c:786
+#: builtin/log.c:787
 msgid "Cover letter needs email format"
 msgstr "Omslagsbrevet behöver e-postformat"
 
-#: builtin/log.c:859
+#: builtin/log.c:860
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr "tokigt in-reply-to: %s"
 
-#: builtin/log.c:932
+#: builtin/log.c:933
 msgid "Two output directories?"
 msgstr "Två utdatakataloger?"
 
-#: builtin/log.c:1153
+#: builtin/log.c:1154
 #, c-format
 msgid "bogus committer info %s"
 msgstr "felaktig incheckarinformation %s"
 
-#: builtin/log.c:1198
+#: builtin/log.c:1199
 msgid "-n and -k are mutually exclusive."
 msgstr "-n och -k kan inte användas samtidigt."
 
-#: builtin/log.c:1200
+#: builtin/log.c:1201
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr "--subject-prefix och -k kan inte användas samtidigt."
 
-#: builtin/log.c:1208
+#: builtin/log.c:1209
 msgid "--name-only does not make sense"
 msgstr "kan inte använda --name-only"
 
-#: builtin/log.c:1210
+#: builtin/log.c:1211
 msgid "--name-status does not make sense"
 msgstr "kan inte använda --name-status"
 
-#: builtin/log.c:1212
+#: builtin/log.c:1213
 msgid "--check does not make sense"
 msgstr "kan inte använda --check"
 
-#: builtin/log.c:1235
+#: builtin/log.c:1236
 msgid "standard output, or directory, which one?"
 msgstr "standard ut, eller katalog, vilken skall det vara?"
 
-#: builtin/log.c:1237
+#: builtin/log.c:1238
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "Kunde inte skapa katalogen \"%s\""
 
-#: builtin/log.c:1390
+#: builtin/log.c:1391
 msgid "Failed to create output files"
 msgstr "Misslyckades skapa utdatafiler"
 
-#: builtin/log.c:1494
+#: builtin/log.c:1495
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> manually.\n"
 msgstr "Kunde inte hitta en spårad fjärrgren, ange <uppström> manuellt.\n"
 
-#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#: builtin/log.c:1511 builtin/log.c:1513 builtin/log.c:1525
 #, c-format
 msgid "Unknown commit %s"
 msgstr "Okänd incheckning %s"
@@ -3846,22 +3964,27 @@ msgstr "Objektet %s har ingen anteckning\n"
 msgid "Unknown subcommand: %s"
 msgstr "Okänt underkommando: %s"
 
-#: builtin/pack-objects.c:2337
+#: builtin/pack-objects.c:183 builtin/pack-objects.c:186
+#, c-format
+msgid "deflate error (%d)"
+msgstr "fel i deflate (%d)"
+
+#: builtin/pack-objects.c:2398
 #, c-format
 msgid "unsupported index version %s"
 msgstr "indexversionen %s stöds ej"
 
-#: builtin/pack-objects.c:2341
+#: builtin/pack-objects.c:2402
 #, c-format
 msgid "bad index version '%s'"
 msgstr "felaktig indexversion \"%s\""
 
-#: builtin/pack-objects.c:2364
+#: builtin/pack-objects.c:2425
 #, c-format
 msgid "option %s does not accept negative form"
 msgstr "flaggan %s godtar inte negativ form"
 
-#: builtin/pack-objects.c:2368
+#: builtin/pack-objects.c:2429
 #, c-format
 msgid "unable to parse value '%s' for option %s"
 msgstr "kunde inte tolka värdet \"%s\" för flaggan %s"
@@ -5291,38 +5414,38 @@ msgstr "Inget grennamn angavs"
 msgid "(To restore them type \"git stash apply\")"
 msgstr "(För att återställa dem, skriv \"git stash apply\")"
 
-#: git-submodule.sh:56
+#: git-submodule.sh:88
 #, sh-format
 msgid "cannot strip one component off url '$remoteurl'"
 msgstr "kan inte ta bort en komponent från url:en \"$remoteurl\""
 
-#: git-submodule.sh:109
+#: git-submodule.sh:145
 #, sh-format
 msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
 msgstr ""
 "Hittade ingen undermodulmappning i .gitmodules för sökvägen \"$sm_path\""
 
-#: git-submodule.sh:150
+#: git-submodule.sh:186
 #, sh-format
 msgid "Clone of '$url' into submodule path '$sm_path' failed"
 msgstr "Misslyckades klona \"$url\" till undermodulsökvägen \"$sm_path\""
 
-#: git-submodule.sh:160
+#: git-submodule.sh:196
 #, sh-format
 msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
 msgstr "Gitkatalog \"$a\" ingår i underkatalogsökvägen \"$b\" eller omvänt"
 
-#: git-submodule.sh:249
+#: git-submodule.sh:285
 #, sh-format
 msgid "repo URL: '$repo' must be absolute or begin with ./|../"
 msgstr "arkiv-URL: \"$repo\" måste vara absolut eller börja med ./|../"
 
-#: git-submodule.sh:266
+#: git-submodule.sh:302
 #, sh-format
 msgid "'$sm_path' already exists in the index"
 msgstr "\"$sm_path\" finns redan i indexet"
 
-#: git-submodule.sh:270
+#: git-submodule.sh:306
 #, sh-format
 msgid ""
 "The following path is ignored by one of your .gitignore files:\n"
@@ -5333,64 +5456,64 @@ msgstr ""
 "$sm_path\n"
 "Använd -f om du verkligen vill lägga till den"
 
-#: git-submodule.sh:281
+#: git-submodule.sh:317
 #, sh-format
 msgid "Adding existing repo at '$sm_path' to the index"
 msgstr "Lägger till befintligt arkiv i \"$sm_path\" i indexet"
 
-#: git-submodule.sh:283
+#: git-submodule.sh:319
 #, sh-format
 msgid "'$sm_path' already exists and is not a valid git repo"
 msgstr "\"$sm_path\" finns redan och är inte ett giltigt git-arkiv"
 
-#: git-submodule.sh:297
+#: git-submodule.sh:333
 #, sh-format
 msgid "Unable to checkout submodule '$sm_path'"
 msgstr "Kan inte checka ut undermodulen \"$sm_path\""
 
-#: git-submodule.sh:302
+#: git-submodule.sh:338
 #, sh-format
 msgid "Failed to add submodule '$sm_path'"
 msgstr "Misslyckades lägga till undermodulen \"$sm_path\""
 
-#: git-submodule.sh:307
+#: git-submodule.sh:343
 #, sh-format
 msgid "Failed to register submodule '$sm_path'"
 msgstr "Misslyckades registrera undermodulen \"$sm_path\""
 
-#: git-submodule.sh:349
+#: git-submodule.sh:385
 #, sh-format
 msgid "Entering '$prefix$sm_path'"
 msgstr "Går in i \"$prefix$sm_path\""
 
-#: git-submodule.sh:363
+#: git-submodule.sh:399
 #, sh-format
 msgid "Stopping at '$sm_path'; script returned non-zero status."
 msgstr ""
 "Stoppar på \"$sm_path\"; skriptet returnerade en status skild från noll."
 
-#: git-submodule.sh:406
+#: git-submodule.sh:442
 #, sh-format
 msgid "No url found for submodule path '$sm_path' in .gitmodules"
 msgstr "Hittade ingen url för undermodulsökvägen \"$sm_path\" i .gitmodules"
 
-#: git-submodule.sh:415
+#: git-submodule.sh:451
 #, sh-format
 msgid "Failed to register url for submodule path '$sm_path'"
 msgstr "Misslyckades registrera url för underkatalogsökväg \"$sm_path\""
 
-#: git-submodule.sh:417
+#: git-submodule.sh:453
 #, sh-format
 msgid "Submodule '$name' ($url) registered for path '$sm_path'"
 msgstr "Undermodulen \"$name\" ($url) registrerad för sökvägen \"$sm_path\""
 
-#: git-submodule.sh:425
+#: git-submodule.sh:461
 #, sh-format
 msgid "Failed to register update mode for submodule path '$sm_path'"
 msgstr ""
 "Misslyckades registrera uppdateringsläge för undermodulsökväg \"$sm_path\""
 
-#: git-submodule.sh:524
+#: git-submodule.sh:560
 #, sh-format
 msgid ""
 "Submodule path '$sm_path' not initialized\n"
@@ -5399,93 +5522,93 @@ msgstr ""
 "Undermodulen \"$sm_path\" har inte initierats\n"
 "Kanske du vill köra \"update --init\"?"
 
-#: git-submodule.sh:537
+#: git-submodule.sh:573
 #, sh-format
 msgid "Unable to find current revision in submodule path '$sm_path'"
 msgstr "Kan inte hitta aktuell revision i undermodulsökväg \"$sm_path\""
 
-#: git-submodule.sh:556
+#: git-submodule.sh:592
 #, sh-format
 msgid "Unable to fetch in submodule path '$sm_path'"
 msgstr "Kan inte hämta i undermodulsökväg \"$sm_path\""
 
-#: git-submodule.sh:570
+#: git-submodule.sh:606
 #, sh-format
 msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
 msgstr "Kan inte ombasera \"$sha1\" i undermodulsökväg \"$sm_path\""
 
-#: git-submodule.sh:571
+#: git-submodule.sh:607
 #, sh-format
 msgid "Submodule path '$sm_path': rebased into '$sha1'"
 msgstr "Undermodulsökvägen \"$sm_path\": ombaserade in i \"$sha1\""
 
-#: git-submodule.sh:576
+#: git-submodule.sh:612
 #, sh-format
 msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
 msgstr "Kan inte slå ihop \"$sha1\" i undermodulsökvägen \"$sm_path\""
 
-#: git-submodule.sh:577
+#: git-submodule.sh:613
 #, sh-format
 msgid "Submodule path '$sm_path': merged in '$sha1'"
 msgstr "Undermodulsökvägen \"$sm_path\": sammanslagen i \"$sha1\""
 
-#: git-submodule.sh:582
+#: git-submodule.sh:618
 #, sh-format
 msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
 msgstr "Kan inte checka ut \"$sha1\" i undermodulsökvägen \"$sm_path\""
 
-#: git-submodule.sh:583
+#: git-submodule.sh:619
 #, sh-format
 msgid "Submodule path '$sm_path': checked out '$sha1'"
 msgstr "Undermodulsökvägen \"$sm_path\": checkade ut \"$sha1\""
 
-#: git-submodule.sh:605 git-submodule.sh:928
+#: git-submodule.sh:641 git-submodule.sh:964
 #, sh-format
 msgid "Failed to recurse into submodule path '$sm_path'"
 msgstr "Misslyckades rekursera in i undermodulsökvägen \"$sm_path\""
 
-#: git-submodule.sh:713
+#: git-submodule.sh:749
 msgid "--cached cannot be used with --files"
 msgstr "--cached kan inte användas med --files"
 
 #. unexpected type
-#: git-submodule.sh:753
+#: git-submodule.sh:789
 #, sh-format
 msgid "unexpected mode $mod_dst"
 msgstr "oväntat läge $mod_dst"
 
-#: git-submodule.sh:771
+#: git-submodule.sh:807
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_src"
 msgstr "  Varning: $name innehåller inte incheckning $sha1_src"
 
-#: git-submodule.sh:774
+#: git-submodule.sh:810
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_dst"
 msgstr "  Varning: $name innehåller inte incheckning $sha1_dst"
 
-#: git-submodule.sh:777
+#: git-submodule.sh:813
 #, sh-format
 msgid "  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 msgstr "  Varning: $name innehåller inte incheckningar $sha1_src och $sha1_dst"
 
-#: git-submodule.sh:802
+#: git-submodule.sh:838
 msgid "blob"
 msgstr "blob"
 
-#: git-submodule.sh:803
+#: git-submodule.sh:839
 msgid "submodule"
 msgstr "undermodul"
 
-#: git-submodule.sh:840
+#: git-submodule.sh:876
 msgid "# Submodules changed but not updated:"
 msgstr "# Undermoduler ändrade men inte uppdaterade:"
 
-#: git-submodule.sh:842
+#: git-submodule.sh:878
 msgid "# Submodule changes to be committed:"
 msgstr "# Undermodulers ändringar att checka in:"
 
-#: git-submodule.sh:974
+#: git-submodule.sh:1022
 #, sh-format
 msgid "Synchronizing submodule url for '$name'"
 msgstr "Synkroniserar undermodul-url för \"$name\""
index f0529f407a15669156777455395a480165ee5162..9c8c6fe174e0240806e11b011b9121c7ce713460 100644 (file)
--- a/po/vi.po
+++ b/po/vi.po
@@ -5,10 +5,10 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: git-1.7.11.rc2.2.gb694fbb\n"
+"Project-Id-Version: git-1.7.11.1-107-g72601\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2012-06-08 10:20+0800\n"
-"PO-Revision-Date: 2012-06-09 14:08+0700\n"
+"POT-Creation-Date: 2012-07-03 10:23+0800\n"
+"PO-Revision-Date: 2012-07-03 14:21+0700\n"
 "Last-Translator: Trần Ngọc Quân <vnwildman@gmail.com>\n"
 "Language-Team: Vietnamese <translation-team-vi@lists.sourceforge.net>\n"
 "MIME-Version: 1.0\n"
@@ -65,10 +65,10 @@ msgstr "Khó chứa thiếu những lần chuyển giao (commit) cần trước
 #: bundle.c:164
 #: sequencer.c:550
 #: sequencer.c:982
-#: builtin/log.c:289
-#: builtin/log.c:720
-#: builtin/log.c:1309
-#: builtin/log.c:1528
+#: builtin/log.c:290
+#: builtin/log.c:721
+#: builtin/log.c:1310
+#: builtin/log.c:1529
 #: builtin/merge.c:347
 #: builtin/shortlog.c:181
 msgid "revision walk setup failed"
@@ -82,46 +82,50 @@ msgstr[0] "Bundle chứa %d tham chiếu (refs)"
 msgstr[1] "Bundle chứa %d tham chiếu (refs)"
 
 #: bundle.c:192
+msgid "The bundle records a complete history."
+msgstr "Lệnh bundle ghi lại toàn bộ lịch sử."
+
+#: bundle.c:195
 #, c-format
 msgid "The bundle requires this ref"
 msgid_plural "The bundle requires these %d refs"
 msgstr[0] "Lệnh bundle yêu cầu tham chiếu (refs) này"
 msgstr[1] "Lệnh bundle yêu cầu %d tham chiếu (refs) này"
 
-#: bundle.c:290
+#: bundle.c:294
 msgid "rev-list died"
 msgstr "rev-list bị chết"
 
-#: bundle.c:296
-#: builtin/log.c:1205
+#: bundle.c:300
+#: builtin/log.c:1206
 #: builtin/shortlog.c:284
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr "đối số không được thừa nhận: %s"
 
-#: bundle.c:331
+#: bundle.c:335
 #, c-format
 msgid "ref '%s' is excluded by the rev-list options"
 msgstr "tham chiếu '%s' bị loại trừ bởi các tùy chọn rev-list"
 
-#: bundle.c:376
+#: bundle.c:380
 msgid "Refusing to create empty bundle."
 msgstr "Từ chối tạo một bundle trống rỗng."
 
-#: bundle.c:394
+#: bundle.c:398
 msgid "Could not spawn pack-objects"
 msgstr "Không thể sản sinh pack-objects"
 
-#: bundle.c:412
+#: bundle.c:416
 msgid "pack-objects died"
 msgstr "pack-objects đã chết"
 
-#: bundle.c:415
+#: bundle.c:419
 #, c-format
 msgid "cannot create '%s'"
 msgstr "không thể tạo '%s'"
 
-#: bundle.c:437
+#: bundle.c:441
 msgid "index-pack died"
 msgstr "index-pack đã chết"
 
@@ -302,16 +306,16 @@ msgstr "'%s': %s"
 msgid "'%s': short read %s"
 msgstr "'%s': đọc ngắn %s"
 
-#: help.c:207
+#: help.c:208
 #, c-format
 msgid "available git commands in '%s'"
 msgstr "các lệnh git sẵn sàng để dùng trong '%s'"
 
-#: help.c:214
+#: help.c:215
 msgid "git commands available from elsewhere on your $PATH"
 msgstr "các lệnh git sẵn sàng để dùng từ một nơi khác trong $PATH của bạn"
 
-#: help.c:270
+#: help.c:271
 #, c-format
 msgid ""
 "'%s' appears to be a git command, but we were not\n"
@@ -320,11 +324,11 @@ msgstr ""
 "'%s' trông như là một lệnh git, nhưng chúng tôi không\n"
 "thể thực thi nó. Có lẽ là lệnh git-%s đã bị hỏng?"
 
-#: help.c:327
+#: help.c:328
 msgid "Uh oh. Your system reports no Git commands at all."
 msgstr "Ối chà. Hệ thống của bạn báo rằng chẳng có lệnh Git nào cả."
 
-#: help.c:349
+#: help.c:350
 #, c-format
 msgid ""
 "WARNING: You called a Git command named '%s', which does not exist.\n"
@@ -333,17 +337,17 @@ msgstr ""
 "CẢNH BÁO: Bạn đã gọi lệnh Git có tên '%s', mà nó lại không sẵn có.\n"
 "Giả định rằng ý bạn là '%s'"
 
-#: help.c:354
+#: help.c:355
 #, c-format
 msgid "in %0.1f seconds automatically..."
 msgstr "trong %0.1f giây một cách tự động..."
 
-#: help.c:361
+#: help.c:362
 #, c-format
 msgid "git: '%s' is not a git command. See 'git --help'."
 msgstr "git: '%s' không phải là một lệnh của git. Xem thêm 'git --help'."
 
-#: help.c:365
+#: help.c:366
 msgid ""
 "\n"
 "Did you mean this?"
@@ -688,236 +692,339 @@ msgstr "không tìm thấy người dùng hiện tại trong tập tin passwd: %
 msgid "no such user"
 msgstr "không có người dùng như vậy"
 
-#: wt-status.c:135
+#: wt-status.c:141
 msgid "Unmerged paths:"
 msgstr "Những đường dẫn chưa được hòa trộn:"
 
-#: wt-status.c:141
-#: wt-status.c:158
+#: wt-status.c:168
+#: wt-status.c:195
 #, c-format
 msgid "  (use \"git reset %s <file>...\" to unstage)"
 msgstr "  (sử dụng \"git reset %s <tập-tin>...\" để bỏ một stage (trạng thái))"
 
-#: wt-status.c:143
-#: wt-status.c:160
+#: wt-status.c:170
+#: wt-status.c:197
 msgid "  (use \"git rm --cached <file>...\" to unstage)"
 msgstr "  (sử dụng \"git rm --cached <tập-tin>...\" để bỏ trạng thái (stage))"
 
-#: wt-status.c:144
+#: wt-status.c:174
+msgid "  (use \"git add <file>...\" to mark resolution)"
+msgstr "  (sử dụng \"git add <tập-tin>...\" để đánh dấu là cần giải quyết)"
+
+#: wt-status.c:176
+#: wt-status.c:180
 msgid "  (use \"git add/rm <file>...\" as appropriate to mark resolution)"
 msgstr "  (sử dụng \"git add/rm <tập-tin>...\" như là một cách  thích hợp để đánh dấu là cần được giải quyết)"
 
-#: wt-status.c:152
+#: wt-status.c:178
+msgid "  (use \"git rm <file>...\" to mark resolution)"
+msgstr "  (sử dụng \"git rm <tập-tin>...\" để đánh dấu là cần giải quyết)"
+
+#: wt-status.c:189
 msgid "Changes to be committed:"
 msgstr "Những thay đổi sẽ được chuyển giao:"
 
-#: wt-status.c:170
+#: wt-status.c:207
 msgid "Changes not staged for commit:"
 msgstr "Các thay đổi không được đặt trạng thái (stage) cho lần chuyển giao (commit):"
 
-#: wt-status.c:174
+#: wt-status.c:211
 msgid "  (use \"git add <file>...\" to update what will be committed)"
 msgstr "  (sử dụng \"git add <tập-tin>...\" để cập nhật những gì cần chuyển giao (commit))"
 
-#: wt-status.c:176
+#: wt-status.c:213
 msgid "  (use \"git add/rm <file>...\" to update what will be committed)"
 msgstr "  (sử dụng \"git add/rm <tập_tin>...\" để cập nhật những gì sẽ được chuyển giao)"
 
-#: wt-status.c:177
+#: wt-status.c:214
 msgid "  (use \"git checkout -- <file>...\" to discard changes in working directory)"
 msgstr "  (sử dụng \"git checkout -- <tập_tin>...\" để loại bỏ những thay đổi trong thư mục làm việc)"
 
-#: wt-status.c:179
+#: wt-status.c:216
 msgid "  (commit or discard the untracked or modified content in submodules)"
 msgstr "  (chuyển giao (commit) hoặc là loại bỏ các nội dung không-bị-theo-vết hay đã bị chỉnh sửa trong mô-đun-con)"
 
-#: wt-status.c:188
+#: wt-status.c:225
 #, c-format
 msgid "%s files:"
 msgstr "%s tệp tin:"
 
-#: wt-status.c:191
+#: wt-status.c:228
 #, c-format
 msgid "  (use \"git %s <file>...\" to include in what will be committed)"
 msgstr "  (sử dụng \"git %s <tập-tin>...\" để bao gồm thêm vào những gì cần chuyển giao (commit))"
 
-#: wt-status.c:208
+#: wt-status.c:245
 msgid "bug"
 msgstr "lỗi"
 
-#: wt-status.c:213
+#: wt-status.c:250
 msgid "both deleted:"
 msgstr "bị xóa bởi cả hai:"
 
-#: wt-status.c:214
+#: wt-status.c:251
 msgid "added by us:"
 msgstr "được thêm vào bởi chúng tôi:"
 
-#: wt-status.c:215
+#: wt-status.c:252
 msgid "deleted by them:"
 msgstr "bị xóa đi bởi họ:"
 
-#: wt-status.c:216
+#: wt-status.c:253
 msgid "added by them:"
 msgstr "được thêm vào bởi họ:"
 
-#: wt-status.c:217
+#: wt-status.c:254
 msgid "deleted by us:"
 msgstr "bị xóa bởi chúng tôi:"
 
-#: wt-status.c:218
+#: wt-status.c:255
 msgid "both added:"
 msgstr "được thêm vào bởi cả hai:"
 
-#: wt-status.c:219
+#: wt-status.c:256
 msgid "both modified:"
 msgstr "bị sửa bởi cả hai:"
 
-#: wt-status.c:249
+#: wt-status.c:286
 msgid "new commits, "
 msgstr " lần chuyển giao (commit) mới, "
 
-#: wt-status.c:251
+#: wt-status.c:288
 msgid "modified content, "
 msgstr "nội dung được sửa đổi,"
 
-#: wt-status.c:253
+#: wt-status.c:290
 msgid "untracked content, "
 msgstr "nội dung chưa được theo dõi"
 
-#: wt-status.c:267
+#: wt-status.c:304
 #, c-format
 msgid "new file:   %s"
 msgstr "tập tin mới:   %s"
 
-#: wt-status.c:270
+#: wt-status.c:307
 #, c-format
 msgid "copied:     %s -> %s"
 msgstr "đã sao chép:     %s -> %s"
 
-#: wt-status.c:273
+#: wt-status.c:310
 #, c-format
 msgid "deleted:    %s"
 msgstr "bị xóa:    %s"
 
-#: wt-status.c:276
+#: wt-status.c:313
 #, c-format
 msgid "modified:   %s"
 msgstr "bị sửa đổi:   %s"
 
-#: wt-status.c:279
+#: wt-status.c:316
 #, c-format
 msgid "renamed:    %s -> %s"
 msgstr "đã đổi tên:    %s -> %s"
 
-#: wt-status.c:282
+#: wt-status.c:319
 #, c-format
 msgid "typechange: %s"
 msgstr "đổi-kiểu: %s"
 
-#: wt-status.c:285
+#: wt-status.c:322
 #, c-format
 msgid "unknown:    %s"
 msgstr "không rõ:    %s"
 
-#: wt-status.c:288
+#: wt-status.c:325
 #, c-format
 msgid "unmerged:   %s"
 msgstr "chưa hòa trộn:   %s"
 
-#: wt-status.c:291
+#: wt-status.c:328
 #, c-format
 msgid "bug: unhandled diff status %c"
 msgstr "lỗi: không lấy được trạng thái lệnh diff %c"
 
-#: wt-status.c:737
+#: wt-status.c:786
+msgid "You have unmerged paths."
+msgstr "Bạn có những đường dẫn chưa được hòa trộn."
+
+#: wt-status.c:789
+#: wt-status.c:913
+msgid "  (fix conflicts and run \"git commit\")"
+msgstr "  (sửa các xung đột sau đó chạy \"git commit\")"
+
+#: wt-status.c:792
+msgid "All conflicts fixed but you are still merging."
+msgstr "Tất cả các xung đột đã được giải quyết nhưng bạn vẫn đang hòa trộn."
+
+#: wt-status.c:795
+msgid "  (use \"git commit\" to conclude merge)"
+msgstr "  (sử dụng \"git commit\" để hoàn tất việc hòa trộn)"
+
+#: wt-status.c:805
+msgid "You are in the middle of an am session."
+msgstr "Bạn đang ở giữa của một phiên 'am'."
+
+#: wt-status.c:808
+msgid "The current patch is empty."
+msgstr "Miếng vá hiện tại bị trống rỗng."
+
+#: wt-status.c:812
+msgid "  (fix conflicts and then run \"git am --resolved\")"
+msgstr "  (sửa các xung đột và sau đó chạy lệnh \"git am --resolved\")"
+
+#: wt-status.c:814
+msgid "  (use \"git am --skip\" to skip this patch)"
+msgstr "  (sử dụng \"git am --skip\" để bỏ qua lần vá này)"
+
+#: wt-status.c:816
+msgid "  (use \"git am --abort\" to restore the original branch)"
+msgstr "  (sử dụng \"git am --abort\" để phục hồi lại nhánh nguyên thủy)"
+
+#: wt-status.c:874
+#: wt-status.c:884
+msgid "You are currently rebasing."
+msgstr "Bạn hiện nay đang thực hiện việc rebase (tái cấu trúc)."
+
+#: wt-status.c:877
+msgid "  (fix conflicts and then run \"git rebase --continue\")"
+msgstr "  (sửa các xung đột và sau đó chạy lệnh \"git rebase --continue\")"
+
+#: wt-status.c:879
+msgid "  (use \"git rebase --skip\" to skip this patch)"
+msgstr "  (sử dụng \"git rebase --skip\" để bỏ qua lần vá này)"
+
+#: wt-status.c:881
+msgid "  (use \"git rebase --abort\" to check out the original branch)"
+msgstr "  (sử dụng \"git rebase --abort\" để check-out nhánh nguyên thủy)"
+
+#: wt-status.c:887
+msgid "  (all conflicts fixed: run \"git rebase --continue\")"
+msgstr "  (khi tất cả các xung đột đã sửa xong: chạy lệnh \"git rebase --continue\")"
+
+#: wt-status.c:889
+msgid "You are currently splitting a commit during a rebase."
+msgstr "Bạn hiện tại đang cắt đôi một lần chuyển giao trong khi đang thực hiện việc rebase."
+
+#: wt-status.c:892
+msgid "  (Once your working directory is clean, run \"git rebase --continue\")"
+msgstr "  (Một khi thư mục làm việc của bạn đã gọn gàng, chạy \"git rebase --continue\")"
+
+#: wt-status.c:894
+msgid "You are currently editing a commit during a rebase."
+msgstr "Bạn hiện đang sửa một lần chuyển giao trong khi bạn thực hiện rebase."
+
+#: wt-status.c:897
+msgid "  (use \"git commit --amend\" to amend the current commit)"
+msgstr "  (sử dụng \"git commit --amend\" để tu bổ lần chuyển giao (commit) hiện tại)"
+
+#: wt-status.c:899
+msgid "  (use \"git rebase --continue\" once you are satisfied with your changes)"
+msgstr "  (sử dụng \"git rebase --continue\" một khi bạn cảm thấy hài lòng về những thay đổi của mình)"
+
+#: wt-status.c:909
+msgid "You are currently cherry-picking."
+msgstr "Bạn hiện nay đang thực hiện việc cherry-pick."
+
+#: wt-status.c:916
+msgid "  (all conflicts fixed: run \"git commit\")"
+msgstr "  (khi tất cả các xung đột đã sửa xong: chạy lệnh \"git commit\")"
+
+#: wt-status.c:925
+msgid "You are currently bisecting."
+msgstr "Bạn hiện tại đang thực hiện việc bisect (chia đôi)."
+
+#: wt-status.c:928
+msgid "  (use \"git bisect reset\" to get back to the original branch)"
+msgstr "  (sử dụng \"git bisect reset\" để quay trở lại nhánh nguyên thủy)"
+
+#: wt-status.c:979
 msgid "On branch "
 msgstr "Trên nhánh"
 
-#: wt-status.c:744
+#: wt-status.c:986
 msgid "Not currently on any branch."
 msgstr "Hiện tại chẳng ở nhánh nào cả."
 
-#: wt-status.c:755
+#: wt-status.c:998
 msgid "Initial commit"
 msgstr "Lần chuyển giao (commit) khởi đầu"
 
-#: wt-status.c:769
+#: wt-status.c:1012
 msgid "Untracked"
 msgstr "Không được theo vết"
 
-#: wt-status.c:771
+#: wt-status.c:1014
 msgid "Ignored"
 msgstr "Bị bỏ qua"
 
-#: wt-status.c:773
+#: wt-status.c:1016
 #, c-format
 msgid "Untracked files not listed%s"
 msgstr "Những tập tin không bị theo vết không được liệt kê ra %s"
 
-#: wt-status.c:775
+#: wt-status.c:1018
 msgid " (use -u option to show untracked files)"
 msgstr " (sử dụng tùy chọn -u để hiển thị các tập tin chưa được theo dõi)"
 
-#: wt-status.c:781
+#: wt-status.c:1024
 msgid "No changes"
 msgstr "Không có thay đổi nào"
 
-#: wt-status.c:785
+#: wt-status.c:1028
 #, c-format
 msgid "no changes added to commit%s\n"
 msgstr "không có thay đổi nào được thêm vào lần chuyển giao (commit)%s\n"
 
-#: wt-status.c:787
+#: wt-status.c:1030
 msgid " (use \"git add\" and/or \"git commit -a\")"
 msgstr " (sử dụng \"git add\" và/hoặc \"git commit -a\")"
 
-#: wt-status.c:789
+#: wt-status.c:1032
 #, c-format
 msgid "nothing added to commit but untracked files present%s\n"
 msgstr "không có gì được thêm vào lần chuyển giao (commit) nhưng có những tập tin không được theo dấu vết hiện diện%s\n"
 
-#: wt-status.c:791
+#: wt-status.c:1034
 msgid " (use \"git add\" to track)"
 msgstr " (sử dụng \"git add\" để theo dõi dấu vết)"
 
-#: wt-status.c:793
-#: wt-status.c:796
-#: wt-status.c:799
+#: wt-status.c:1036
+#: wt-status.c:1039
+#: wt-status.c:1042
 #, c-format
 msgid "nothing to commit%s\n"
 msgstr "không có gì để chuyển giao (commit) %s\n"
 
-#: wt-status.c:794
+#: wt-status.c:1037
 msgid " (create/copy files and use \"git add\" to track)"
 msgstr " (tạo/sao-chép các tập tin và sử dụng \"git add\" để theo dõi dấu vết)"
 
-#: wt-status.c:797
+#: wt-status.c:1040
 msgid " (use -u to show untracked files)"
 msgstr " (sử dụng tùy chọn -u để hiển thị các tập tin chưa được theo dõi)"
 
-#: wt-status.c:800
+#: wt-status.c:1043
 msgid " (working directory clean)"
 msgstr " (thư mục làm việc sạch sẽ)"
 
-#: wt-status.c:908
+#: wt-status.c:1151
 msgid "HEAD (no branch)"
 msgstr "HEAD (chưa có nhánh nào)"
 
-#: wt-status.c:914
+#: wt-status.c:1157
 msgid "Initial commit on "
 msgstr "Lần chuyển giao (commit)  khởi tạo trên"
 
-#: wt-status.c:929
+#: wt-status.c:1172
 msgid "behind "
 msgstr "đằng sau"
 
-#: wt-status.c:932
-#: wt-status.c:935
+#: wt-status.c:1175
+#: wt-status.c:1178
 msgid "ahead "
 msgstr "phía trước"
 
-#: wt-status.c:937
+#: wt-status.c:1180
 msgid ", behind "
 msgstr ", đằng sau"
 
@@ -946,7 +1053,7 @@ msgid "Unstaged changes after refreshing the index:"
 msgstr "Các thay đổi không được lưu trạng thái sau khi làm tươi mới lại bảng mục lục:"
 
 #: builtin/add.c:195
-#: builtin/add.c:456
+#: builtin/add.c:459
 #: builtin/rm.c:186
 #, c-format
 msgid "pathspec '%s' did not match any files"
@@ -1027,7 +1134,7 @@ msgstr "Có lẽ bạn muốn nói là 'git add .' phải không?\n"
 msgid "index file corrupt"
 msgstr "tập tin ghi bảng mục lục bị hỏng"
 
-#: builtin/add.c:476
+#: builtin/add.c:480
 #: builtin/apply.c:4108
 #: builtin/mv.c:229
 #: builtin/rm.c:260
@@ -2590,22 +2697,22 @@ msgstr "tùy chọn sai: %s"
 msgid "Not a git repository"
 msgstr "Không phải là kho git"
 
-#: builtin/diff.c:347
+#: builtin/diff.c:341
 #, c-format
 msgid "invalid object '%s' given."
 msgstr "đối tượng đã cho '%s' không hợp lệ."
 
-#: builtin/diff.c:352
+#: builtin/diff.c:346
 #, c-format
 msgid "more than %d trees given: '%s'"
 msgstr "đã chỉ ra nhiều hơn %d cây (tree): '%s'"
 
-#: builtin/diff.c:362
+#: builtin/diff.c:356
 #, c-format
 msgid "more than two blobs given: '%s'"
 msgstr "đã cho nhiều hơn hai đối tượng blob: '%s'"
 
-#: builtin/diff.c:370
+#: builtin/diff.c:364
 #, c-format
 msgid "unhandled object '%s' given."
 msgstr "đã cho đối tượng không thể nắm giữ '%s'."
@@ -2859,33 +2966,33 @@ msgstr "--[no-]exclude-standard không thể sử dụng cho nội dung lưu d
 msgid "both --cached and trees are given."
 msgstr "cả hai --cached và các cây phải được chỉ ra."
 
-#: builtin/help.c:59
+#: builtin/help.c:63
 #, c-format
 msgid "unrecognized help format '%s'"
 msgstr "không nhận ra định dạng trợ giúp '%s'"
 
-#: builtin/help.c:87
+#: builtin/help.c:91
 msgid "Failed to start emacsclient."
 msgstr "Lỗi khởi chạy emacsclient."
 
-#: builtin/help.c:100
+#: builtin/help.c:104
 msgid "Failed to parse emacsclient version."
 msgstr "Gặp lỗi khi phân tích phiên bản emacsclient."
 
-#: builtin/help.c:108
+#: builtin/help.c:112
 #, c-format
 msgid "emacsclient version '%d' too old (< 22)."
 msgstr "phiên bản của emacsclient '%d' quá cũ (< 22)."
 
-#: builtin/help.c:126
-#: builtin/help.c:154
-#: builtin/help.c:163
-#: builtin/help.c:171
+#: builtin/help.c:130
+#: builtin/help.c:158
+#: builtin/help.c:167
+#: builtin/help.c:175
 #, c-format
 msgid "failed to exec '%s': %s"
 msgstr "gặp lỗi khi thực thi '%s': %s"
 
-#: builtin/help.c:211
+#: builtin/help.c:215
 #, c-format
 msgid ""
 "'%s': path for unsupported man viewer.\n"
@@ -2894,7 +3001,7 @@ msgstr ""
 "'%s': đường dẫn không hỗ trợ bộ trình chiếu man.\n"
 "Hãy cân nhắc đến việc sử dụng 'man.<tool>.cmd' để thay thế."
 
-#: builtin/help.c:223
+#: builtin/help.c:227
 #, c-format
 msgid ""
 "'%s': cmd for supported man viewer.\n"
@@ -2903,271 +3010,286 @@ msgstr ""
 "'%s': cmd (lệnh) hỗ trợ bộ trình chiếu man.\n"
 "Hãy cân nhắc đến việc sử dụng 'man.<tool>.path' để thay thế."
 
-#: builtin/help.c:287
+#: builtin/help.c:291
 msgid "The most commonly used git commands are:"
 msgstr "Những lệnh git hay được sử dụng nhất là:"
 
-#: builtin/help.c:355
+#: builtin/help.c:359
 #, c-format
 msgid "'%s': unknown man viewer."
 msgstr "'%s': không rõ chương trình xem man."
 
-#: builtin/help.c:372
+#: builtin/help.c:376
 msgid "no man viewer handled the request"
 msgstr "không có trình xem trợ giúp dạng manpage tiếp hợp với yêu cầu"
 
-#: builtin/help.c:380
+#: builtin/help.c:384
 msgid "no info viewer handled the request"
 msgstr "không có trình xem trợ giúp dạng info tiếp hợp với yêu cầu"
 
-#: builtin/help.c:391
+#: builtin/help.c:395
 #, c-format
 msgid "'%s': not a documentation directory."
 msgstr "'%s': không phải là một thư mục tài liệu."
 
-#: builtin/help.c:432
-#: builtin/help.c:439
+#: builtin/help.c:436
+#: builtin/help.c:443
 #, c-format
 msgid "usage: %s%s"
 msgstr "cách sử dụng: %s%s"
 
-#: builtin/help.c:453
+#: builtin/help.c:459
 #, c-format
 msgid "`git %s' is aliased to `%s'"
 msgstr "`git %s' được đặt bí danh thành `%s'"
 
-#: builtin/index-pack.c:169
+#: builtin/index-pack.c:170
 #, c-format
 msgid "object type mismatch at %s"
 msgstr "kiểu đối tượng không khớp tại %s"
 
-#: builtin/index-pack.c:189
+#: builtin/index-pack.c:190
 msgid "object of unexpected type"
 msgstr "đối tượng của kiểu không mong đợi"
 
-#: builtin/index-pack.c:226
+#: builtin/index-pack.c:227
 #, c-format
 msgid "cannot fill %d byte"
 msgid_plural "cannot fill %d bytes"
 msgstr[0] "không thể điền vào %d byte"
 msgstr[1] "không thể điền vào %d byte"
 
-#: builtin/index-pack.c:236
+#: builtin/index-pack.c:237
 msgid "early EOF"
 msgstr "vừa đúng lúc EOF"
 
-#: builtin/index-pack.c:237
+#: builtin/index-pack.c:238
 msgid "read error on input"
 msgstr "lỗi đọc ở đầu vào"
 
-#: builtin/index-pack.c:249
+#: builtin/index-pack.c:250
 msgid "used more bytes than were available"
 msgstr "sử dụng nhiều hơn số lượng byte mà nó sẵn có"
 
-#: builtin/index-pack.c:256
+#: builtin/index-pack.c:257
 msgid "pack too large for current definition of off_t"
 msgstr "pack quá lớn so với định nghĩa hiện tại của kiểu off_t"
 
-#: builtin/index-pack.c:272
+#: builtin/index-pack.c:273
 #, c-format
 msgid "unable to create '%s'"
 msgstr "không thể tạo '%s'"
 
-#: builtin/index-pack.c:277
+#: builtin/index-pack.c:278
 #, c-format
 msgid "cannot open packfile '%s'"
 msgstr "không thể mở packfile '%s'"
 
-#: builtin/index-pack.c:291
+#: builtin/index-pack.c:292
 msgid "pack signature mismatch"
 msgstr "chữ ký cho pack không khớp"
 
-#: builtin/index-pack.c:311
+#: builtin/index-pack.c:312
 #, c-format
 msgid "pack has bad object at offset %lu: %s"
 msgstr "pack có đối tượng sai khoảng bù (offset) %lu: %s"
 
-#: builtin/index-pack.c:405
+#: builtin/index-pack.c:434
 #, c-format
 msgid "inflate returned %d"
 msgstr "xả nén trả về %d"
 
-#: builtin/index-pack.c:450
+#: builtin/index-pack.c:483
 msgid "offset value overflow for delta base object"
 msgstr "tràn giá trị khoảng bù cho đối tượng delta cơ sở"
 
-#: builtin/index-pack.c:458
+#: builtin/index-pack.c:491
 msgid "delta base offset is out of bound"
 msgstr "khoảng bù cơ sở cho delta nằm ngoài phạm vi"
 
-#: builtin/index-pack.c:466
+#: builtin/index-pack.c:499
 #, c-format
 msgid "unknown object type %d"
 msgstr "không hiểu kiểu đối tượng %d"
 
-#: builtin/index-pack.c:495
+#: builtin/index-pack.c:531
 msgid "cannot pread pack file"
 msgstr "không thể chạy hàm pread cho tập tin pack"
 
-#: builtin/index-pack.c:497
+#: builtin/index-pack.c:533
 #, c-format
 msgid "premature end of pack file, %lu byte missing"
 msgid_plural "premature end of pack file, %lu bytes missing"
 msgstr[0] "tập tin pack bị kết thúc sớm, %lu byte bị thiếu"
 msgstr[1] "tập tin pack bị kết thúc sớm, %lu byte bị thiếu"
 
-#: builtin/index-pack.c:510
+#: builtin/index-pack.c:555
 msgid "serious inflate inconsistency"
 msgstr "sự mâu thuẫn xả nén nghiêm trọng"
 
-#: builtin/index-pack.c:583
-#, c-format
-msgid "cannot read existing object %s"
-msgstr "không thể đọc đối tượng đã tồn tại %s"
-
-#: builtin/index-pack.c:586
+#: builtin/index-pack.c:646
+#: builtin/index-pack.c:652
+#: builtin/index-pack.c:675
+#: builtin/index-pack.c:709
+#: builtin/index-pack.c:718
 #, c-format
 msgid "SHA1 COLLISION FOUND WITH %s !"
 msgstr "SỰ VA CHẠM SHA1 ĐàXẢY RA VỚI %s!"
 
-#: builtin/index-pack.c:598
+#: builtin/index-pack.c:649
+#: builtin/pack-objects.c:170
+#: builtin/pack-objects.c:262
+#, c-format
+msgid "unable to read %s"
+msgstr "không thể đọc %s"
+
+#: builtin/index-pack.c:715
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "không thể đọc đối tượng đã tồn tại %s"
+
+#: builtin/index-pack.c:729
 #, c-format
 msgid "invalid blob object %s"
 msgstr "đối tượng blob không hợp lệ %s"
 
-#: builtin/index-pack.c:610
+#: builtin/index-pack.c:744
 #, c-format
 msgid "invalid %s"
 msgstr "%s không hợp lệ"
 
-#: builtin/index-pack.c:612
+#: builtin/index-pack.c:746
 msgid "Error in object"
 msgstr "Lỗi trong đối tượng"
 
-#: builtin/index-pack.c:614
+#: builtin/index-pack.c:748
 #, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "Không phải tất cả các đối tượng con của %s là có thể với tới được"
 
-#: builtin/index-pack.c:687
-#: builtin/index-pack.c:713
+#: builtin/index-pack.c:818
+#: builtin/index-pack.c:844
 msgid "failed to apply delta"
 msgstr "gặp lỗi khi áp dụng delta"
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Receiving objects"
 msgstr "Đang nhận về các đối tượng"
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Indexing objects"
 msgstr "Các đối tượng bảng mục lục"
 
-#: builtin/index-pack.c:872
+#: builtin/index-pack.c:1009
 msgid "pack is corrupted (SHA1 mismatch)"
 msgstr "pack bị sai hỏng (SHA1 không khớp)"
 
-#: builtin/index-pack.c:877
+#: builtin/index-pack.c:1014
 msgid "cannot fstat packfile"
 msgstr "không thể fstat packfile"
 
-#: builtin/index-pack.c:880
+#: builtin/index-pack.c:1017
 msgid "pack has junk at the end"
 msgstr "pack có phần thừa ở cuối"
 
-#: builtin/index-pack.c:903
+#: builtin/index-pack.c:1028
+msgid "confusion beyond insanity in parse_pack_objects()"
+msgstr "lộn xộn hơn cả điên rồ khi chạy hàm parse_pack_objects()"
+
+#: builtin/index-pack.c:1051
 msgid "Resolving deltas"
 msgstr "Đang phân giải các delta"
 
-#: builtin/index-pack.c:954
+#: builtin/index-pack.c:1102
 msgid "confusion beyond insanity"
 msgstr "lộn xộn hơn cả điên rồ"
 
-#: builtin/index-pack.c:973
+#: builtin/index-pack.c:1121
 #, c-format
 msgid "pack has %d unresolved delta"
 msgid_plural "pack has %d unresolved deltas"
 msgstr[0] "pack có %d delta chưa được giải quyết"
 msgstr[1] "pack có %d delta chưa được giải quyết"
 
-#: builtin/index-pack.c:998
+#: builtin/index-pack.c:1146
 #, c-format
 msgid "unable to deflate appended object (%d)"
 msgstr "không thể xả đối tượng nối thêm (%d)"
 
-#: builtin/index-pack.c:1077
+#: builtin/index-pack.c:1225
 #, c-format
 msgid "local object %s is corrupt"
 msgstr "đối tượng nội bộ %s bị hỏng"
 
-#: builtin/index-pack.c:1101
+#: builtin/index-pack.c:1249
 msgid "error while closing pack file"
 msgstr "gặp lỗi trong khi đóng tập tin pack"
 
-#: builtin/index-pack.c:1114
+#: builtin/index-pack.c:1262
 #, c-format
 msgid "cannot write keep file '%s'"
 msgstr "không thể ghi tập tin giữ lại '%s'"
 
-#: builtin/index-pack.c:1122
+#: builtin/index-pack.c:1270
 #, c-format
 msgid "cannot close written keep file '%s'"
 msgstr "không thể đóng tập tin giữ lại đã được ghi '%s'"
 
-#: builtin/index-pack.c:1135
+#: builtin/index-pack.c:1283
 msgid "cannot store pack file"
 msgstr "không thể lưu tập tin pack"
 
-#: builtin/index-pack.c:1146
+#: builtin/index-pack.c:1294
 msgid "cannot store index file"
 msgstr "không thể lưu trữ tập tin ghi mục lục"
 
-#: builtin/index-pack.c:1247
+#: builtin/index-pack.c:1395
 #, c-format
 msgid "Cannot open existing pack file '%s'"
 msgstr "Không thể mở tập tin pack đã sẵn có '%s' "
 
-#: builtin/index-pack.c:1249
+#: builtin/index-pack.c:1397
 #, c-format
 msgid "Cannot open existing pack idx file for '%s'"
 msgstr "Không thể mở tập tin 'pack idx' cho '%s'"
 
-#: builtin/index-pack.c:1296
+#: builtin/index-pack.c:1444
 #, c-format
 msgid "non delta: %d object"
 msgid_plural "non delta: %d objects"
 msgstr[0] "không delta: %d đối tượng"
 msgstr[1] "không delta: %d đối tượng"
 
-#: builtin/index-pack.c:1303
+#: builtin/index-pack.c:1451
 #, c-format
 msgid "chain length = %d: %lu object"
 msgid_plural "chain length = %d: %lu objects"
 msgstr[0] "chiều dài xích = %d: %lu đối tượng"
 msgstr[1] "chiều dài xích = %d: %lu đối tượng"
 
-#: builtin/index-pack.c:1330
+#: builtin/index-pack.c:1478
 msgid "Cannot come back to cwd"
 msgstr "Không thể quay lại cwd"
 
-#: builtin/index-pack.c:1374
-#: builtin/index-pack.c:1377
-#: builtin/index-pack.c:1389
-#: builtin/index-pack.c:1393
+#: builtin/index-pack.c:1522
+#: builtin/index-pack.c:1525
+#: builtin/index-pack.c:1537
+#: builtin/index-pack.c:1541
 #, c-format
 msgid "bad %s"
 msgstr "%s sai"
 
-#: builtin/index-pack.c:1407
+#: builtin/index-pack.c:1555
 msgid "--fix-thin cannot be used without --stdin"
 msgstr "--fix-thin không thể được dùng mà không có --stdin"
 
-#: builtin/index-pack.c:1411
-#: builtin/index-pack.c:1421
+#: builtin/index-pack.c:1559
+#: builtin/index-pack.c:1569
 #, c-format
 msgid "packfile name '%s' does not end with '.pack'"
 msgstr "tên tập tin packfile '%s' không được kết thúc bằng đuôi '.pack'"
 
-#: builtin/index-pack.c:1430
+#: builtin/index-pack.c:1578
 msgid "--verify with no packfile name given"
 msgstr "dùng tùy chọn --verify mà không đưa ra tên packfile"
 
@@ -3313,102 +3435,102 @@ msgstr "Không thể truy cập thư mục làm việc hiện hành"
 msgid "Cannot access work tree '%s'"
 msgstr "không thể truy cập cây (tree) làm việc '%s'"
 
-#: builtin/log.c:188
+#: builtin/log.c:189
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr "Kết xuất cuối cùng: %d %s\n"
 
-#: builtin/log.c:401
-#: builtin/log.c:489
+#: builtin/log.c:402
+#: builtin/log.c:490
 #, c-format
 msgid "Could not read object %s"
 msgstr "Không thể đọc đối tượng %s"
 
-#: builtin/log.c:513
+#: builtin/log.c:514
 #, c-format
 msgid "Unknown type: %d"
 msgstr "Không nhận ra kiểu: %d"
 
-#: builtin/log.c:602
+#: builtin/log.c:603
 msgid "format.headers without value"
 msgstr "format.headers không có giá trị cụ thể"
 
-#: builtin/log.c:676
+#: builtin/log.c:677
 msgid "name of output directory is too long"
 msgstr "tên của thư mục kết xuất quá dài"
 
-#: builtin/log.c:687
+#: builtin/log.c:688
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr "Không thể mở tập tin miếng vá: %s"
 
-#: builtin/log.c:701
+#: builtin/log.c:702
 msgid "Need exactly one range."
 msgstr "Cần chính xác một vùng."
 
-#: builtin/log.c:709
+#: builtin/log.c:710
 msgid "Not a range."
 msgstr "Không phải là một vùng."
 
-#: builtin/log.c:786
+#: builtin/log.c:787
 msgid "Cover letter needs email format"
 msgstr "'Cover letter' cần cho định dạng thư"
 
-#: builtin/log.c:859
+#: builtin/log.c:860
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr "in-reply-to điên rồ: %s"
 
-#: builtin/log.c:932
+#: builtin/log.c:933
 msgid "Two output directories?"
 msgstr "Hai thư mục kết xuất?"
 
-#: builtin/log.c:1153
+#: builtin/log.c:1154
 #, c-format
 msgid "bogus committer info %s"
 msgstr "thông tin người chuyển giao không có thực %s"
 
-#: builtin/log.c:1198
+#: builtin/log.c:1199
 msgid "-n and -k are mutually exclusive."
 msgstr "-n và  -k loại từ lẫn nhau."
 
-#: builtin/log.c:1200
+#: builtin/log.c:1201
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr "--subject-prefix và -k xung khắc nhau."
 
-#: builtin/log.c:1208
+#: builtin/log.c:1209
 msgid "--name-only does not make sense"
 msgstr "--name-only không hợp lý"
 
-#: builtin/log.c:1210
+#: builtin/log.c:1211
 msgid "--name-status does not make sense"
 msgstr "--name-status không hợp lý"
 
-#: builtin/log.c:1212
+#: builtin/log.c:1213
 msgid "--check does not make sense"
 msgstr "--check không hợp lý"
 
-#: builtin/log.c:1235
+#: builtin/log.c:1236
 msgid "standard output, or directory, which one?"
 msgstr "đầu ra chuẩn, hay thư mục, chọn cái nào?"
 
-#: builtin/log.c:1237
+#: builtin/log.c:1238
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "Không thể tạo thư mục '%s'"
 
-#: builtin/log.c:1390
+#: builtin/log.c:1391
 msgid "Failed to create output files"
 msgstr "Gặp lỗi khi tạo các tập tin kết xuất"
 
-#: builtin/log.c:1494
+#: builtin/log.c:1495
 #, c-format
 msgid "Could not find a tracked remote branch, please specify <upstream> manually.\n"
 msgstr "Không tìm thấy nhánh mạng bị theo vết, hãy chỉ định <dòng-ngược> một cách thủ công.\n"
 
-#: builtin/log.c:1510
-#: builtin/log.c:1512
-#: builtin/log.c:1524
+#: builtin/log.c:1511
+#: builtin/log.c:1513
+#: builtin/log.c:1525
 #, c-format
 msgid "Unknown commit %s"
 msgstr "Không hiểu lần chuyển giao (commit) %s"
@@ -3910,22 +4032,28 @@ msgstr "Đối tượng %s không có ghi chú (note)\n"
 msgid "Unknown subcommand: %s"
 msgstr "Không hiểu câu lệnh con: %s"
 
-#: builtin/pack-objects.c:2337
+#: builtin/pack-objects.c:183
+#: builtin/pack-objects.c:186
+#, c-format
+msgid "deflate error (%d)"
+msgstr "lỗi giải nén (%d)"
+
+#: builtin/pack-objects.c:2398
 #, c-format
 msgid "unsupported index version %s"
 msgstr "phiên bản mục lục không được hỗ trợ %s"
 
-#: builtin/pack-objects.c:2341
+#: builtin/pack-objects.c:2402
 #, c-format
 msgid "bad index version '%s'"
 msgstr "phiên bản mục lục sai '%s'"
 
-#: builtin/pack-objects.c:2364
+#: builtin/pack-objects.c:2425
 #, c-format
 msgid "option %s does not accept negative form"
 msgstr "tùy chọn %s không chấp nhận dạng thức âm"
 
-#: builtin/pack-objects.c:2368
+#: builtin/pack-objects.c:2429
 #, c-format
 msgid "unable to parse value '%s' for option %s"
 msgstr "không thể phân tích giá trị '%s' cho tùy chọn %s"
@@ -4904,7 +5032,7 @@ msgid ""
 msgstr ""
 "Khi bạn cần giải quyết vấn đề này hãy chạy lệnh \"$cmdline --resolved\".\n"
 "Nếu bạn có ý định bỏ qua miếng vá, thay vào đó bạn chạy \"$cmdline --skip\".\n"
-"Để phục hồi lại thành nhánh nguyên bản và dừng việc vá lại thì chạy \"$cmdline --abort\"."
+"Để phục hồi lại thành nhánh nguyên thủy và dừng việc vá lại thì chạy \"$cmdline --abort\"."
 
 #: git-am.sh:121
 msgid "Cannot fall back to three-way merge."
@@ -4970,7 +5098,7 @@ msgid ""
 msgstr ""
 "Miếng vá trống rỗng.  Nó đã bị chia cắt sai phải không?\n"
 "Nếu bạn thích bỏ qua miếng vá này, hãy chạy lệnh sau để thay thế \"$cmdline --skip\".\n"
-"Để phục hồi lại nhánh nguyên bản và dừng vá lại hãy chạy lệnh \"$cmdline --abort\"."
+"Để phục hồi lại nhánh nguyên thủy và dừng vá lại hãy chạy lệnh \"$cmdline --abort\"."
 
 #: git-am.sh:708
 msgid "Patch does not have a valid e-mail address."
@@ -5135,8 +5263,8 @@ msgid ""
 "Could not check out original HEAD '$branch'.\n"
 "Try 'git bisect reset <commit>'."
 msgstr ""
-"Không thể check out original HEAD '$branch'.\n"
-"Hãy thử 'git bisect reset <commit>'."
+"Không thể check-out HEAD nguyên thủy của '$branch'.\n"
+"Hãy thử 'git bisect reset <lần-chuyển-giao>'."
 
 #: git-bisect.sh:390
 msgid "No logfile given"
@@ -5353,37 +5481,37 @@ msgstr "Chưa chỉ ra tên của nhánh"
 msgid "(To restore them type \"git stash apply\")"
 msgstr "(Để phục hồi lại chúng hãy gõ \"git stash apply\")"
 
-#: git-submodule.sh:56
+#: git-submodule.sh:88
 #, sh-format
 msgid "cannot strip one component off url '$remoteurl'"
 msgstr "không thể tháo bỏ một thành phần ra khỏi url '$remoteurl'"
 
-#: git-submodule.sh:109
+#: git-submodule.sh:145
 #, sh-format
 msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
 msgstr "Không tìm thấy ánh xạ (mapping) mô-đun-con trong .gitmodules cho đường dẫn '$sm_path'"
 
-#: git-submodule.sh:150
+#: git-submodule.sh:186
 #, sh-format
 msgid "Clone of '$url' into submodule path '$sm_path' failed"
 msgstr "Nhân bản '$url' vào đường dẫn mô-đun-con '$sm_path' gặp lỗi"
 
-#: git-submodule.sh:160
+#: git-submodule.sh:196
 #, sh-format
 msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
 msgstr "Gitdir '$a' là bộ phận của đường dẫn mô-đun-con '$b' hoặc \"vice versa\""
 
-#: git-submodule.sh:249
+#: git-submodule.sh:285
 #, sh-format
 msgid "repo URL: '$repo' must be absolute or begin with ./|../"
 msgstr "repo URL: '$repo' phải là đường dẫn tuyệt đối hoặc là bắt đầu bằng ./|../"
 
-#: git-submodule.sh:266
+#: git-submodule.sh:302
 #, sh-format
 msgid "'$sm_path' already exists in the index"
 msgstr "'$sm_path' thực sự đã tồn tại ở bảng mục lục rồi"
 
-#: git-submodule.sh:270
+#: git-submodule.sh:306
 #, sh-format
 msgid ""
 "The following path is ignored by one of your .gitignore files:\n"
@@ -5394,62 +5522,62 @@ msgstr ""
 "$sm_path\n"
 "Sử dụng -f nếu bạn thực sự muốn thêm nó vào."
 
-#: git-submodule.sh:281
+#: git-submodule.sh:317
 #, sh-format
 msgid "Adding existing repo at '$sm_path' to the index"
 msgstr "Đang thêm repo có sẵn tại '$sm_path' vào bảng mục lục"
 
-#: git-submodule.sh:283
+#: git-submodule.sh:319
 #, sh-format
 msgid "'$sm_path' already exists and is not a valid git repo"
 msgstr "'$sm_path' đã tồn tại từ trước và không phải là một kho git hợp lệ"
 
-#: git-submodule.sh:297
+#: git-submodule.sh:333
 #, sh-format
 msgid "Unable to checkout submodule '$sm_path'"
 msgstr "Không thể checkout mô-đun con '$sm_path'"
 
-#: git-submodule.sh:302
+#: git-submodule.sh:338
 #, sh-format
 msgid "Failed to add submodule '$sm_path'"
 msgstr "Gặp lỗi khi thêm mô-đun con '$sm_path'"
 
-#: git-submodule.sh:307
+#: git-submodule.sh:343
 #, sh-format
 msgid "Failed to register submodule '$sm_path'"
 msgstr "Gặp lỗi khi đăng ký với hệ thống mô-đun con '$sm_path'"
 
-#: git-submodule.sh:349
+#: git-submodule.sh:385
 #, sh-format
 msgid "Entering '$prefix$sm_path'"
 msgstr "Đang nhập '$prefix$sm_path'"
 
-#: git-submodule.sh:363
+#: git-submodule.sh:399
 #, sh-format
 msgid "Stopping at '$sm_path'; script returned non-zero status."
 msgstr "Dừng lại tại '$sm_path'; script trả về trạng thái khác không."
 
-#: git-submodule.sh:406
+#: git-submodule.sh:442
 #, sh-format
 msgid "No url found for submodule path '$sm_path' in .gitmodules"
 msgstr "Không tìm thấy url cho đường dẫn mô-đun-con '$sm_path' trong .gitmodules"
 
-#: git-submodule.sh:415
+#: git-submodule.sh:451
 #, sh-format
 msgid "Failed to register url for submodule path '$sm_path'"
 msgstr "Gặp lỗi khi đăng ký url cho đường dẫn mô-đun-con '$sm_path'"
 
-#: git-submodule.sh:417
+#: git-submodule.sh:453
 #, sh-format
 msgid "Submodule '$name' ($url) registered for path '$sm_path'"
 msgstr "Mô-đun-con '$name' ($url) được đăng ký cho đường dẫn '$sm_path'"
 
-#: git-submodule.sh:425
+#: git-submodule.sh:461
 #, sh-format
 msgid "Failed to register update mode for submodule path '$sm_path'"
 msgstr "Gặp lỗi khi đăng ký chế độ cập nhật cho đường dẫn mô-đun-con '$sm_path'"
 
-#: git-submodule.sh:524
+#: git-submodule.sh:560
 #, sh-format
 msgid ""
 "Submodule path '$sm_path' not initialized\n"
@@ -5458,94 +5586,94 @@ msgstr ""
 "Đường dẫn mô-đun-con '$sm_path' chưa được khởi tạo\n"
 "Có lẽ bạn muốn sử dụng lệnh 'update --init'?"
 
-#: git-submodule.sh:537
+#: git-submodule.sh:573
 #, sh-format
 msgid "Unable to find current revision in submodule path '$sm_path'"
 msgstr "Không tìm thấy điểm xét lại hiện hành trong đường dẫn mô-đun-con '$sm_path'"
 
-#: git-submodule.sh:556
+#: git-submodule.sh:592
 #, sh-format
 msgid "Unable to fetch in submodule path '$sm_path'"
 msgstr "Không thể lấy về (fetch) trong đường dẫn mô-đun-con '$sm_path'"
 
-#: git-submodule.sh:570
+#: git-submodule.sh:606
 #, sh-format
 msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
 msgstr "Không thể rebase '$sha1' trong đường dẫn mô-đun-con '$sm_path'"
 
-#: git-submodule.sh:571
+#: git-submodule.sh:607
 #, sh-format
 msgid "Submodule path '$sm_path': rebased into '$sha1'"
 msgstr "Đường dẫn mô-đun-con '$sm_path': được rebase vào trong '$sha1'"
 
-#: git-submodule.sh:576
+#: git-submodule.sh:612
 #, sh-format
 msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
 msgstr "Không thể hòa trộn (merge) '$sha1' trong đường dẫn mô-đun-con '$sm_path'"
 
-#: git-submodule.sh:577
+#: git-submodule.sh:613
 #, sh-format
 msgid "Submodule path '$sm_path': merged in '$sha1'"
 msgstr "Đường dẫn mô-đun-con '$sm_path': được hòa trộn vào '$sha1'"
 
-#: git-submodule.sh:582
+#: git-submodule.sh:618
 #, sh-format
 msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
 msgstr "Không thể checkout '$sha1' trong đường dẫn mô-đun-con '$sm_path'"
 
-#: git-submodule.sh:583
+#: git-submodule.sh:619
 #, sh-format
 msgid "Submodule path '$sm_path': checked out '$sha1'"
 msgstr "Đường dẫn mô-đun-con '$sm_path': được checkout '$sha1'"
 
-#: git-submodule.sh:605
-#: git-submodule.sh:928
+#: git-submodule.sh:641
+#: git-submodule.sh:964
 #, sh-format
 msgid "Failed to recurse into submodule path '$sm_path'"
 msgstr "Gặp lỗi khi đệ quy vào trong đường dẫn mô-đun-con '$sm_path'"
 
-#: git-submodule.sh:713
+#: git-submodule.sh:749
 msgid "--cached cannot be used with --files"
 msgstr "--cached không thể được sử dụng cùng với --files"
 
 #. unexpected type
-#: git-submodule.sh:753
+#: git-submodule.sh:789
 #, sh-format
 msgid "unexpected mode $mod_dst"
 msgstr "chế độ không như mong chờ $mod_dst"
 
-#: git-submodule.sh:771
+#: git-submodule.sh:807
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_src"
 msgstr "  Cảnh báo: $name không chứa lần chuyển giao (commit) $sha1_src"
 
-#: git-submodule.sh:774
+#: git-submodule.sh:810
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_dst"
 msgstr "  Cảnh báo: $name không chứa lần chuyển giao (commit) $sha1_dst"
 
-#: git-submodule.sh:777
+#: git-submodule.sh:813
 #, sh-format
 msgid "  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 msgstr "  Cảnh báo: $name không chứa những lần chuyển giao (commit) $sha1_src và $sha1_dst"
 
-#: git-submodule.sh:802
+#: git-submodule.sh:838
 msgid "blob"
 msgstr "blob"
 
-#: git-submodule.sh:803
+#: git-submodule.sh:839
 msgid "submodule"
 msgstr "mô-đun con"
 
-#: git-submodule.sh:840
+#: git-submodule.sh:876
 msgid "# Submodules changed but not updated:"
 msgstr "# Những mô-đun-con đã bị thay đổi nhưng chưa được cập nhật:"
 
-#: git-submodule.sh:842
+#: git-submodule.sh:878
 msgid "# Submodule changes to be committed:"
 msgstr "# Những thay đổi mô-đun-con được chuyển giao (commit):"
 
-#: git-submodule.sh:974
+#: git-submodule.sh:1022
 #, sh-format
 msgid "Synchronizing submodule url for '$name'"
 msgstr "Đang đồng bộ hóa url mô-đun-con cho '$name'"
index b46b53e6d6aaafff640c528bcaf86e74676fdee7..b3e1a54c8efeba2571278400d57bbb01ec45f595 100644 (file)
@@ -12,8 +12,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Git\n"
 "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
-"POT-Creation-Date: 2012-06-08 10:20+0800\n"
-"PO-Revision-Date: 2012-06-08 12:24+0800\n"
+"POT-Creation-Date: 2012-07-03 10:23+0800\n"
+"PO-Revision-Date: 2012-07-04 22:38+0800\n"
 "Last-Translator: Jiang Xin <worldhello.net@gmail.com>\n"
 "Language-Team: GitHub <https://github.com/gotgit/git/>\n"
 "Language: zh_CN\n"
@@ -61,8 +61,8 @@ msgstr "不能打开 '%s'"
 msgid "Repository lacks these prerequisite commits:"
 msgstr "版本库缺少这些必备的提交:"
 
-#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:289
-#: builtin/log.c:720 builtin/log.c:1309 builtin/log.c:1528 builtin/merge.c:347
+#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:290
+#: builtin/log.c:721 builtin/log.c:1310 builtin/log.c:1529 builtin/merge.c:347
 #: builtin/shortlog.c:181
 msgid "revision walk setup failed"
 msgstr "版本遍历设置失败"
@@ -75,44 +75,48 @@ msgstr[0] "这个包中含有 %d 个引用"
 msgstr[1] "这个包中含有 %d 个引用"
 
 #: bundle.c:192
+msgid "The bundle records a complete history."
+msgstr "这个包记录一个完整历史。"
+
+#: bundle.c:195
 #, c-format
 msgid "The bundle requires this ref"
 msgid_plural "The bundle requires these %d refs"
 msgstr[0] "这个包需要这个引用"
 msgstr[1] "这个包需要 %d 个这些引用"
 
-#: bundle.c:290
+#: bundle.c:294
 msgid "rev-list died"
 msgstr "rev-list 终止"
 
-#: bundle.c:296 builtin/log.c:1205 builtin/shortlog.c:284
+#: bundle.c:300 builtin/log.c:1206 builtin/shortlog.c:284
 #, c-format
 msgid "unrecognized argument: %s"
 msgstr "未能识别的参数:%s"
 
-#: bundle.c:331
+#: bundle.c:335
 #, c-format
 msgid "ref '%s' is excluded by the rev-list options"
 msgstr "引用 '%s' 被 rev-list 选项排除"
 
-#: bundle.c:376
+#: bundle.c:380
 msgid "Refusing to create empty bundle."
 msgstr "不能创建空包。"
 
-#: bundle.c:394
+#: bundle.c:398
 msgid "Could not spawn pack-objects"
 msgstr "不能生成 pack-objects 进程"
 
-#: bundle.c:412
+#: bundle.c:416
 msgid "pack-objects died"
 msgstr "pack-objects 终止"
 
-#: bundle.c:415
+#: bundle.c:419
 #, c-format
 msgid "cannot create '%s'"
 msgstr "不能创建 '%s'"
 
-#: bundle.c:437
+#: bundle.c:441
 msgid "index-pack died"
 msgstr "index-pack 终止"
 
@@ -293,16 +297,16 @@ msgstr "'%s':%s"
 msgid "'%s': short read %s"
 msgstr "'%s':读取不完整 %s"
 
-#: help.c:207
+#: help.c:208
 #, c-format
 msgid "available git commands in '%s'"
 msgstr "在 '%s' 下可用的 git 命令"
 
-#: help.c:214
+#: help.c:215
 msgid "git commands available from elsewhere on your $PATH"
 msgstr "在 $PATH 路径中的其他地方可用的 git 命令"
 
-#: help.c:270
+#: help.c:271
 #, c-format
 msgid ""
 "'%s' appears to be a git command, but we were not\n"
@@ -311,11 +315,11 @@ msgstr ""
 "'%s' 像是一个 git 命令,但却无法运行。\n"
 "可能是 git-%s 受损?"
 
-#: help.c:327
+#: help.c:328
 msgid "Uh oh. Your system reports no Git commands at all."
 msgstr "唉呀,您的系统中未发现 Git 命令。"
 
-#: help.c:349
+#: help.c:350
 #, c-format
 msgid ""
 "WARNING: You called a Git command named '%s', which does not exist.\n"
@@ -324,17 +328,17 @@ msgstr ""
 "警告:您运行一个不存在的 Git 命令 '%s'。继续执行假定您要要运行的\n"
 "是 '%s'"
 
-#: help.c:354
+#: help.c:355
 #, c-format
 msgid "in %0.1f seconds automatically..."
 msgstr "在 %0.1f 秒钟后自动运行..."
 
-#: help.c:361
+#: help.c:362
 #, c-format
 msgid "git: '%s' is not a git command. See 'git --help'."
 msgstr "git:'%s' 不是一个 git 命令。参见 'git --help'。"
 
-#: help.c:365
+#: help.c:366
 msgid ""
 "\n"
 "Did you mean this?"
@@ -668,261 +672,379 @@ msgstr "无法在 passwd 文件中查询到当前用户:%s"
 msgid "no such user"
 msgstr "无此用户"
 
-#: wt-status.c:135
+#: wt-status.c:141
 msgid "Unmerged paths:"
 msgstr "未合并的路径:"
 
 #  译者:注意保持前导空格
-#: wt-status.c:141 wt-status.c:158
+#: wt-status.c:168 wt-status.c:195
 #, c-format
 msgid "  (use \"git reset %s <file>...\" to unstage)"
 msgstr "  (使用 \"git reset %s <file>...\" 撤出暂存区)"
 
 #  译者:注意保持前导空格
-#: wt-status.c:143 wt-status.c:160
+#: wt-status.c:170 wt-status.c:197
 msgid "  (use \"git rm --cached <file>...\" to unstage)"
 msgstr "  (使用 \"git rm --cached <file>...\" 撤出暂存区)"
 
 #  译者:注意保持前导空格
-#: wt-status.c:144
+#: wt-status.c:174
+msgid "  (use \"git add <file>...\" to mark resolution)"
+msgstr "  (使用 \"git add <file>...\" 标记解决方案)"
+
+#  译者:注意保持前导空格
+#: wt-status.c:176 wt-status.c:180
 msgid "  (use \"git add/rm <file>...\" as appropriate to mark resolution)"
 msgstr "  (酌情使用 \"git add/rm <file>...\" 标记解决方案)"
 
-#: wt-status.c:152
+#  译者:注意保持前导空格
+#: wt-status.c:178
+msgid "  (use \"git rm <file>...\" to mark resolution)"
+msgstr "  (使用 \"git rm <file>...\" 标记解决方案)"
+
+#: wt-status.c:189
 msgid "Changes to be committed:"
 msgstr "要提交的变更:"
 
-#: wt-status.c:170
+#: wt-status.c:207
 msgid "Changes not staged for commit:"
 msgstr "尚未暂存以备提交的变更:"
 
 #  译者:注意保持前导空格
-#: wt-status.c:174
+#: wt-status.c:211
 msgid "  (use \"git add <file>...\" to update what will be committed)"
 msgstr "  (使用 \"git add <file>...\" 更新要提交的内容)"
 
 #  译者:注意保持前导空格
-#: wt-status.c:176
+#: wt-status.c:213
 msgid "  (use \"git add/rm <file>...\" to update what will be committed)"
 msgstr "  (使用 \"git add/rm <file>...\" 更新要提交的内容)"
 
 #  译者:注意保持前导空格
-#: wt-status.c:177
+#: wt-status.c:214
 msgid ""
 "  (use \"git checkout -- <file>...\" to discard changes in working directory)"
 msgstr "  (使用 \"git checkout -- <file>...\" 丢弃工作区的改动)"
 
 #  译者:注意保持前导空格
-#: wt-status.c:179
+#: wt-status.c:216
 msgid "  (commit or discard the untracked or modified content in submodules)"
 msgstr "  (提交或丢弃子模组中未跟踪或修改的内容)"
 
-#: wt-status.c:188
+#: wt-status.c:225
 #, c-format
 msgid "%s files:"
 msgstr "%s文件:"
 
 #  译者:注意保持前导空格
-#: wt-status.c:191
+#: wt-status.c:228
 #, c-format
 msgid "  (use \"git %s <file>...\" to include in what will be committed)"
 msgstr "  (使用 \"git %s <file>...\" 以包含要提交的内容)"
 
-#: wt-status.c:208
+#: wt-status.c:245
 msgid "bug"
 msgstr "bug"
 
-#: wt-status.c:213
+#: wt-status.c:250
 msgid "both deleted:"
 msgstr "双方删除:"
 
-#: wt-status.c:214
+#: wt-status.c:251
 msgid "added by us:"
 msgstr "由我们添加:"
 
-#: wt-status.c:215
+#: wt-status.c:252
 msgid "deleted by them:"
 msgstr "由他们删除:"
 
-#: wt-status.c:216
+#: wt-status.c:253
 msgid "added by them:"
 msgstr "由他们添加:"
 
-#: wt-status.c:217
+#: wt-status.c:254
 msgid "deleted by us:"
 msgstr "由我们删除:"
 
-#: wt-status.c:218
+#: wt-status.c:255
 msgid "both added:"
 msgstr "双方添加:"
 
-#: wt-status.c:219
+#: wt-status.c:256
 msgid "both modified:"
 msgstr "双方修改:"
 
 #  译者:末尾两个字节可能被删减,如果翻译为中文标点会出现半个汉字
-#: wt-status.c:249
+#: wt-status.c:286
 msgid "new commits, "
 msgstr "新提交, "
 
 #  译者:末尾两个字节可能被删减,如果翻译为中文标点会出现半个汉字
-#: wt-status.c:251
+#: wt-status.c:288
 msgid "modified content, "
 msgstr "修改的内容, "
 
 #  译者:末尾两个字节可能被删减,如果翻译为中文标点会出现半个汉字
-#: wt-status.c:253
+#: wt-status.c:290
 msgid "untracked content, "
 msgstr "未跟踪的内容, "
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
-#: wt-status.c:267
+#: wt-status.c:304
 #, c-format
 msgid "new file:   %s"
 msgstr "新文件:    %s"
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
-#: wt-status.c:270
+#: wt-status.c:307
 #, c-format
 msgid "copied:     %s -> %s"
 msgstr "拷贝:      %s -> %s"
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
-#: wt-status.c:273
+#: wt-status.c:310
 #, c-format
 msgid "deleted:    %s"
 msgstr "删除:      %s"
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
-#: wt-status.c:276
+#: wt-status.c:313
 #, c-format
 msgid "modified:   %s"
 msgstr "修改:      %s"
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
-#: wt-status.c:279
+#: wt-status.c:316
 #, c-format
 msgid "renamed:    %s -> %s"
 msgstr "重命名:    %s -> %s"
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
-#: wt-status.c:282
+#: wt-status.c:319
 #, c-format
 msgid "typechange: %s"
 msgstr "类型变更:  %s"
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
-#: wt-status.c:285
+#: wt-status.c:322
 #, c-format
 msgid "unknown:    %s"
 msgstr "未知:      %s"
 
 #  译者:为保证在输出中对齐,注意调整句中空格!
-#: wt-status.c:288
+#: wt-status.c:325
 #, c-format
 msgid "unmerged:   %s"
 msgstr "未合并:    %s"
 
-#: wt-status.c:291
+#: wt-status.c:328
 #, c-format
 msgid "bug: unhandled diff status %c"
 msgstr "bug:未处理的差异状态 %c"
 
-#: wt-status.c:737
+#: wt-status.c:786
+msgid "You have unmerged paths."
+msgstr "您有路径尚未合并。"
+
+#  译者:注意保持前导空格
+#: wt-status.c:789 wt-status.c:913
+msgid "  (fix conflicts and run \"git commit\")"
+msgstr "  (解决冲突并运行 \"git commit\")"
+
+#: wt-status.c:792
+msgid "All conflicts fixed but you are still merging."
+msgstr "所有冲突已解决但您仍处于合并中。"
+
+#  译者:注意保持前导空格
+#: wt-status.c:795
+msgid "  (use \"git commit\" to conclude merge)"
+msgstr "  (使用 \"git commit\" 结束合并)"
+
+#: wt-status.c:805
+msgid "You are in the middle of an am session."
+msgstr "您正处于一个 am 过程中。"
+
+#: wt-status.c:808
+msgid "The current patch is empty."
+msgstr "当前的补丁为空。"
+
+#  译者:注意保持前导空格
+#: wt-status.c:812
+msgid "  (fix conflicts and then run \"git am --resolved\")"
+msgstr "  (解决冲突,然后运行 \"git am --resolved\")"
+
+#  译者:注意保持前导空格
+#: wt-status.c:814
+msgid "  (use \"git am --skip\" to skip this patch)"
+msgstr "  (使用 \"git am --skip\" 跳过此补丁)"
+
+#  译者:注意保持前导空格
+#: wt-status.c:816
+msgid "  (use \"git am --abort\" to restore the original branch)"
+msgstr "  (使用 \"git am --abort\" 恢复原有分支)"
+
+#: wt-status.c:874 wt-status.c:884
+msgid "You are currently rebasing."
+msgstr "您正在变基。"
+
+#  译者:注意保持前导空格
+#: wt-status.c:877
+msgid "  (fix conflicts and then run \"git rebase --continue\")"
+msgstr "  (解决冲突,然后运行 \"git rebase --continue\")"
+
+#  译者:注意保持前导空格
+#: wt-status.c:879
+msgid "  (use \"git rebase --skip\" to skip this patch)"
+msgstr "  (使用 \"git rebase --skip\" 跳过此补丁)"
+
+#  译者:注意保持前导空格
+#: wt-status.c:881
+msgid "  (use \"git rebase --abort\" to check out the original branch)"
+msgstr "  (使用 \"git rebase --abort\" 以检出原有分支)"
+
+#  译者:注意保持前导空格
+#: wt-status.c:887
+msgid "  (all conflicts fixed: run \"git rebase --continue\")"
+msgstr "  (所有冲突已解决:运行 \"git rebase --continue\")"
+
+#: wt-status.c:889
+msgid "You are currently splitting a commit during a rebase."
+msgstr "您正在变基过程中拆分一个提交。"
+
+#  译者:注意保持前导空格
+#: wt-status.c:892
+msgid "  (Once your working directory is clean, run \"git rebase --continue\")"
+msgstr "  (一旦您工作目录提交干净后,运行 \"git rebase --continue\")"
+
+#: wt-status.c:894
+msgid "You are currently editing a commit during a rebase."
+msgstr "您正在变基过程中编辑一个提交。"
+
+#  译者:注意保持前导空格
+#: wt-status.c:897
+msgid "  (use \"git commit --amend\" to amend the current commit)"
+msgstr "  (使用 \"git commit --amend\" 修补当前提交)"
+
+#  译者:注意保持前导空格
+#: wt-status.c:899
+msgid ""
+"  (use \"git rebase --continue\" once you are satisfied with your changes)"
+msgstr ""
+"  (执行 \"git rebase --continue\" 一旦您满意您的修改)"
+
+#: wt-status.c:909
+msgid "You are currently cherry-picking."
+msgstr "您正在做拣选操作。"
+
+#  译者:注意保持前导空格
+#: wt-status.c:916
+msgid "  (all conflicts fixed: run \"git commit\")"
+msgstr "  (解决所有冲突后,执行 \"git commit\")"
+
+#: wt-status.c:925
+msgid "You are currently bisecting."
+msgstr "您正在做二分查找。"
+
+#  译者:注意保持前导空格
+#: wt-status.c:928
+msgid "  (use \"git bisect reset\" to get back to the original branch)"
+msgstr "  (使用 \"git bisect reset\" 以回到原有分支)"
+
+#: wt-status.c:979
 msgid "On branch "
 msgstr "位于分支 "
 
-#: wt-status.c:744
+#: wt-status.c:986
 msgid "Not currently on any branch."
 msgstr "当前不在任何分支上。"
 
-#: wt-status.c:755
+#: wt-status.c:998
 msgid "Initial commit"
 msgstr "初始提交"
 
-#: wt-status.c:769
+#: wt-status.c:1012
 msgid "Untracked"
 msgstr "未跟踪的"
 
-#: wt-status.c:771
+#: wt-status.c:1014
 msgid "Ignored"
 msgstr "忽略的"
 
-#: wt-status.c:773
+#: wt-status.c:1016
 #, c-format
 msgid "Untracked files not listed%s"
 msgstr "未跟踪的文件没有列出%s"
 
 #  译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:775
+#: wt-status.c:1018
 msgid " (use -u option to show untracked files)"
 msgstr "(使用 -u 参数显示未跟踪的文件)"
 
-#: wt-status.c:781
+#: wt-status.c:1024
 msgid "No changes"
 msgstr "没有修改"
 
-#: wt-status.c:785
+#: wt-status.c:1028
 #, c-format
 msgid "no changes added to commit%s\n"
 msgstr "修改尚未加入提交%s\n"
 
 #  译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:787
+#: wt-status.c:1030
 msgid " (use \"git add\" and/or \"git commit -a\")"
 msgstr "(使用 \"git add\" 和/或 \"git commit -a\")"
 
-#: wt-status.c:789
+#: wt-status.c:1032
 #, c-format
 msgid "nothing added to commit but untracked files present%s\n"
 msgstr "空提交但存在未跟踪文件%s\n"
 
 #  译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:791
+#: wt-status.c:1034
 msgid " (use \"git add\" to track)"
 msgstr "(使用 \"git add\" 建立跟踪)"
 
-#: wt-status.c:793 wt-status.c:796 wt-status.c:799
+#: wt-status.c:1036 wt-status.c:1039 wt-status.c:1042
 #, c-format
 msgid "nothing to commit%s\n"
 msgstr "无须提交%s\n"
 
 #  译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:794
+#: wt-status.c:1037
 msgid " (create/copy files and use \"git add\" to track)"
 msgstr "(新建/拷贝的文件使用 \"git add\" 建立跟踪)"
 
 #  译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:797
+#: wt-status.c:1040
 msgid " (use -u to show untracked files)"
 msgstr "(使用 -u 显示未跟踪文件)"
 
 #  译者:中文字符串拼接,可删除前导空格
-#: wt-status.c:800
+#: wt-status.c:1043
 msgid " (working directory clean)"
 msgstr "(干净的工作区)"
 
-#: wt-status.c:908
+#: wt-status.c:1151
 msgid "HEAD (no branch)"
 msgstr "HEAD(非分支)"
 
 #  译者:注意保持句尾空格
-#: wt-status.c:914
+#: wt-status.c:1157
 msgid "Initial commit on "
 msgstr "初始提交于 "
 
 #  译者:注意保持句尾空格
-#: wt-status.c:929
+#: wt-status.c:1172
 msgid "behind "
 msgstr "落后 "
 
 #  译者:注意保持句尾空格
-#: wt-status.c:932 wt-status.c:935
+#: wt-status.c:1175 wt-status.c:1178
 msgid "ahead "
 msgstr "领先 "
 
 #  译者:注意保持句尾空格
-#: wt-status.c:937
+#: wt-status.c:1180
 msgid ", behind "
 msgstr ",落后 "
 
@@ -949,7 +1071,7 @@ msgstr "路径 '%s' 属于模组 '%.*s'"
 msgid "Unstaged changes after refreshing the index:"
 msgstr "刷新索引之后尚未被暂存的变更:"
 
-#: builtin/add.c:195 builtin/add.c:456 builtin/rm.c:186
+#: builtin/add.c:195 builtin/add.c:459 builtin/rm.c:186
 #, c-format
 msgid "pathspec '%s' did not match any files"
 msgstr "路径 '%s' 未匹配任何文件"
@@ -1026,7 +1148,7 @@ msgstr "也许您想要执行 'git add .'?\n"
 msgid "index file corrupt"
 msgstr "索引文件损坏"
 
-#: builtin/add.c:476 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
+#: builtin/add.c:480 builtin/apply.c:4108 builtin/mv.c:229 builtin/rm.c:260
 msgid "Unable to write new index file"
 msgstr "无法写入新索引文件"
 
@@ -2558,22 +2680,22 @@ msgstr "无效选项:%s"
 msgid "Not a git repository"
 msgstr "不是一个 git 版本库"
 
-#: builtin/diff.c:347
+#: builtin/diff.c:341
 #, c-format
 msgid "invalid object '%s' given."
 msgstr "提供了无效对象 '%s'。"
 
-#: builtin/diff.c:352
+#: builtin/diff.c:346
 #, c-format
 msgid "more than %d trees given: '%s'"
 msgstr "提供了超过 %d 个树对象:'%s'"
 
-#: builtin/diff.c:362
+#: builtin/diff.c:356
 #, c-format
 msgid "more than two blobs given: '%s'"
 msgstr "提供了超过两个 blob 对象:'%s'"
 
-#: builtin/diff.c:370
+#: builtin/diff.c:364
 #, c-format
 msgid "unhandled object '%s' given."
 msgstr "提供了无法处理的对象 '%s'。"
@@ -2822,30 +2944,30 @@ msgstr "--[no-]exclude-standard 不能用于已跟踪内容。"
 msgid "both --cached and trees are given."
 msgstr "同时给出了 --cached 和树对象。"
 
-#: builtin/help.c:59
+#: builtin/help.c:63
 #, c-format
 msgid "unrecognized help format '%s'"
 msgstr "未能识别的帮助格式 '%s'"
 
-#: builtin/help.c:87
+#: builtin/help.c:91
 msgid "Failed to start emacsclient."
 msgstr "无法启动 emacsclient。"
 
-#: builtin/help.c:100
+#: builtin/help.c:104
 msgid "Failed to parse emacsclient version."
 msgstr "无法解析 emacsclient 版本。"
 
-#: builtin/help.c:108
+#: builtin/help.c:112
 #, c-format
 msgid "emacsclient version '%d' too old (< 22)."
 msgstr "emacsclient 版本 '%d' 太老 (< 22)。"
 
-#: builtin/help.c:126 builtin/help.c:154 builtin/help.c:163 builtin/help.c:171
+#: builtin/help.c:130 builtin/help.c:158 builtin/help.c:167 builtin/help.c:175
 #, c-format
 msgid "failed to exec '%s': %s"
 msgstr "无法执行 '%s':%s"
 
-#: builtin/help.c:211
+#: builtin/help.c:215
 #, c-format
 msgid ""
 "'%s': path for unsupported man viewer.\n"
@@ -2854,7 +2976,7 @@ msgstr ""
 "'%s':不支持的 man 手册查看器的路径。\n"
 "请使用 'man.<tool>.cmd'。"
 
-#: builtin/help.c:223
+#: builtin/help.c:227
 #, c-format
 msgid ""
 "'%s': cmd for supported man viewer.\n"
@@ -2863,266 +2985,277 @@ msgstr ""
 "'%s': 支持的 man 手册查看器命令。\n"
 "请使用 'man.<tool>.path'。"
 
-#: builtin/help.c:287
+#: builtin/help.c:291
 msgid "The most commonly used git commands are:"
 msgstr "最常用的 git 命令有:"
 
-#: builtin/help.c:355
+#: builtin/help.c:359
 #, c-format
 msgid "'%s': unknown man viewer."
 msgstr "'%s':未知的 man 查看器。"
 
-#: builtin/help.c:372
+#: builtin/help.c:376
 msgid "no man viewer handled the request"
 msgstr "没有 man 查看器处理此请求"
 
-#: builtin/help.c:380
+#: builtin/help.c:384
 msgid "no info viewer handled the request"
 msgstr "没有 info 查看器处理此请求"
 
-#: builtin/help.c:391
+#: builtin/help.c:395
 #, c-format
 msgid "'%s': not a documentation directory."
 msgstr "'%s':不是一个文档目录。"
 
-#: builtin/help.c:432 builtin/help.c:439
+#: builtin/help.c:436 builtin/help.c:443
 #, c-format
 msgid "usage: %s%s"
 msgstr "用法:%s%s"
 
-#: builtin/help.c:453
+#: builtin/help.c:459
 #, c-format
 msgid "`git %s' is aliased to `%s'"
 msgstr "`git %s' 是 `%s' 的别名"
 
-#: builtin/index-pack.c:169
+#: builtin/index-pack.c:170
 #, c-format
 msgid "object type mismatch at %s"
 msgstr "%s 的对象类型不匹配"
 
-#: builtin/index-pack.c:189
+#: builtin/index-pack.c:190
 msgid "object of unexpected type"
 msgstr "意外的类型的对象"
 
-#: builtin/index-pack.c:226
+#: builtin/index-pack.c:227
 #, c-format
 msgid "cannot fill %d byte"
 msgid_plural "cannot fill %d bytes"
 msgstr[0] "无法填充 %d 字节"
 msgstr[1] "无法填充 %d 字节"
 
-#: builtin/index-pack.c:236
+#: builtin/index-pack.c:237
 msgid "early EOF"
 msgstr "过早的文件结束符(EOF)"
 
-#: builtin/index-pack.c:237
+#: builtin/index-pack.c:238
 msgid "read error on input"
 msgstr "输入上的读错误"
 
-#: builtin/index-pack.c:249
+#: builtin/index-pack.c:250
 msgid "used more bytes than were available"
 msgstr "用掉了超过可用的字节"
 
-#: builtin/index-pack.c:256
+#: builtin/index-pack.c:257
 msgid "pack too large for current definition of off_t"
 msgstr "包太大超过了当前 off_t 的定义"
 
-#: builtin/index-pack.c:272
+#: builtin/index-pack.c:273
 #, c-format
 msgid "unable to create '%s'"
 msgstr "不能创建 '%s'"
 
-#: builtin/index-pack.c:277
+#: builtin/index-pack.c:278
 #, c-format
 msgid "cannot open packfile '%s'"
 msgstr "无法打开包文件 '%s'"
 
-#: builtin/index-pack.c:291
+#: builtin/index-pack.c:292
 msgid "pack signature mismatch"
 msgstr "包签名不匹配"
 
-#: builtin/index-pack.c:311
+#: builtin/index-pack.c:312
 #, c-format
 msgid "pack has bad object at offset %lu: %s"
 msgstr "包中有错误的对象位于 %lu:%s"
 
-#: builtin/index-pack.c:405
+#: builtin/index-pack.c:434
 #, c-format
 msgid "inflate returned %d"
 msgstr "解压缩返回 %d"
 
-#: builtin/index-pack.c:450
+#: builtin/index-pack.c:483
 msgid "offset value overflow for delta base object"
 msgstr "偏移值覆盖了 delta 基准对象"
 
-#: builtin/index-pack.c:458
+#: builtin/index-pack.c:491
 msgid "delta base offset is out of bound"
 msgstr "delta 基准偏移越界"
 
-#: builtin/index-pack.c:466
+#: builtin/index-pack.c:499
 #, c-format
 msgid "unknown object type %d"
 msgstr "未知对象类型 %d"
 
-#: builtin/index-pack.c:495
+#: builtin/index-pack.c:531
 msgid "cannot pread pack file"
 msgstr "无法读取包文件"
 
-#: builtin/index-pack.c:497
+#: builtin/index-pack.c:533
 #, c-format
 msgid "premature end of pack file, %lu byte missing"
 msgid_plural "premature end of pack file, %lu bytes missing"
 msgstr[0] "包文件过早结束,缺少 %lu 字节"
 msgstr[1] "包文件过早结束,缺少 %lu 字节"
 
-#: builtin/index-pack.c:510
+#: builtin/index-pack.c:555
 msgid "serious inflate inconsistency"
 msgstr "解压缩严重的不一致"
 
-#: builtin/index-pack.c:583
-#, c-format
-msgid "cannot read existing object %s"
-msgstr "不能读取现存对象 %s"
-
-#: builtin/index-pack.c:586
+#: builtin/index-pack.c:646 builtin/index-pack.c:652 builtin/index-pack.c:675
+#: builtin/index-pack.c:709 builtin/index-pack.c:718
 #, c-format
 msgid "SHA1 COLLISION FOUND WITH %s !"
 msgstr "发现 %s 出现 SHA1 冲突!"
 
-#: builtin/index-pack.c:598
+#: builtin/index-pack.c:649 builtin/pack-objects.c:170
+#: builtin/pack-objects.c:262
+#, c-format
+msgid "unable to read %s"
+msgstr "不能读 %s"
+
+#: builtin/index-pack.c:715
+#, c-format
+msgid "cannot read existing object %s"
+msgstr "不能读取现存对象 %s"
+
+#: builtin/index-pack.c:729
 #, c-format
 msgid "invalid blob object %s"
 msgstr "无效的 blob 对象 %s"
 
-#: builtin/index-pack.c:610
+#: builtin/index-pack.c:744
 #, c-format
 msgid "invalid %s"
 msgstr "无效的 %s"
 
-#: builtin/index-pack.c:612
+#: builtin/index-pack.c:746
 msgid "Error in object"
 msgstr "对象中出错"
 
-#: builtin/index-pack.c:614
+#: builtin/index-pack.c:748
 #, c-format
 msgid "Not all child objects of %s are reachable"
 msgstr "%s 的所有子对象并非都可达"
 
-#: builtin/index-pack.c:687 builtin/index-pack.c:713
+#: builtin/index-pack.c:818 builtin/index-pack.c:844
 msgid "failed to apply delta"
 msgstr "无法应用 delta"
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Receiving objects"
 msgstr "接收对象中"
 
-#: builtin/index-pack.c:850
+#: builtin/index-pack.c:983
 msgid "Indexing objects"
 msgstr "索引对象中"
 
-#: builtin/index-pack.c:872
+#: builtin/index-pack.c:1009
 msgid "pack is corrupted (SHA1 mismatch)"
 msgstr "包冲突(SHA1 不匹配)"
 
-#: builtin/index-pack.c:877
+#: builtin/index-pack.c:1014
 msgid "cannot fstat packfile"
 msgstr "不能枚举包文件状态"
 
-#: builtin/index-pack.c:880
+#: builtin/index-pack.c:1017
 msgid "pack has junk at the end"
 msgstr "包的结尾有垃圾数据"
 
-#: builtin/index-pack.c:903
+#: builtin/index-pack.c:1028
+msgid "confusion beyond insanity in parse_pack_objects()"
+msgstr "parse_pack_objects() 中遇到不可理喻的问题"
+
+#: builtin/index-pack.c:1051
 msgid "Resolving deltas"
 msgstr "处理 delta 中"
 
-#: builtin/index-pack.c:954
+#: builtin/index-pack.c:1102
 msgid "confusion beyond insanity"
 msgstr "不可理喻"
 
-#: builtin/index-pack.c:973
+#: builtin/index-pack.c:1121
 #, c-format
 msgid "pack has %d unresolved delta"
 msgid_plural "pack has %d unresolved deltas"
 msgstr[0] "包有 %d 个未解决的 delta"
 msgstr[1] "包有 %d 个未解决的 delta"
 
-#: builtin/index-pack.c:998
+#: builtin/index-pack.c:1146
 #, c-format
 msgid "unable to deflate appended object (%d)"
 msgstr "不能缩小附加对象(%d)"
 
-#: builtin/index-pack.c:1077
+#: builtin/index-pack.c:1225
 #, c-format
 msgid "local object %s is corrupt"
 msgstr "本地对象 %s 已损坏"
 
-#: builtin/index-pack.c:1101
+#: builtin/index-pack.c:1249
 msgid "error while closing pack file"
 msgstr "关闭包文件时出错"
 
-#: builtin/index-pack.c:1114
+#: builtin/index-pack.c:1262
 #, c-format
 msgid "cannot write keep file '%s'"
 msgstr "无法写保留文件 '%s'"
 
-#: builtin/index-pack.c:1122
+#: builtin/index-pack.c:1270
 #, c-format
 msgid "cannot close written keep file '%s'"
 msgstr "无法关闭保留文件 '%s'"
 
-#: builtin/index-pack.c:1135
+#: builtin/index-pack.c:1283
 msgid "cannot store pack file"
 msgstr "无法存储包文件"
 
-#: builtin/index-pack.c:1146
+#: builtin/index-pack.c:1294
 msgid "cannot store index file"
 msgstr "无法存储索引文件"
 
-#: builtin/index-pack.c:1247
+#: builtin/index-pack.c:1395
 #, c-format
 msgid "Cannot open existing pack file '%s'"
 msgstr "无法打开现存包文件 '%s'"
 
-#: builtin/index-pack.c:1249
+#: builtin/index-pack.c:1397
 #, c-format
 msgid "Cannot open existing pack idx file for '%s'"
 msgstr "无法为 %s 打开包索引文件"
 
-#: builtin/index-pack.c:1296
+#: builtin/index-pack.c:1444
 #, c-format
 msgid "non delta: %d object"
 msgid_plural "non delta: %d objects"
 msgstr[0] "非 delta:%d 个对象"
 msgstr[1] "非 delta:%d 个对象"
 
-#: builtin/index-pack.c:1303
+#: builtin/index-pack.c:1451
 #, c-format
 msgid "chain length = %d: %lu object"
 msgid_plural "chain length = %d: %lu objects"
 msgstr[0] "链长 = %d: %lu 对象"
 msgstr[1] "链长 = %d: %lu 对象"
 
-#: builtin/index-pack.c:1330
+#: builtin/index-pack.c:1478
 msgid "Cannot come back to cwd"
 msgstr "无法返回当前工作目录"
 
-#: builtin/index-pack.c:1374 builtin/index-pack.c:1377
-#: builtin/index-pack.c:1389 builtin/index-pack.c:1393
+#: builtin/index-pack.c:1522 builtin/index-pack.c:1525
+#: builtin/index-pack.c:1537 builtin/index-pack.c:1541
 #, c-format
 msgid "bad %s"
 msgstr "错误选项 %s"
 
-#: builtin/index-pack.c:1407
+#: builtin/index-pack.c:1555
 msgid "--fix-thin cannot be used without --stdin"
 msgstr "--fix-thin 不能和 --stdin 共用"
 
-#: builtin/index-pack.c:1411 builtin/index-pack.c:1421
+#: builtin/index-pack.c:1559 builtin/index-pack.c:1569
 #, c-format
 msgid "packfile name '%s' does not end with '.pack'"
 msgstr "包名 '%s' 没有以 '.pack' 结尾"
 
-#: builtin/index-pack.c:1430
+#: builtin/index-pack.c:1578
 msgid "--verify with no packfile name given"
 msgstr "--verify 没有提供包名参数"
 
@@ -3271,100 +3404,100 @@ msgstr "不能访问当前工作目录"
 msgid "Cannot access work tree '%s'"
 msgstr "不能访问工作区 '%s'"
 
-#: builtin/log.c:188
+#: builtin/log.c:189
 #, c-format
 msgid "Final output: %d %s\n"
 msgstr "最终输出:%d %s\n"
 
-#: builtin/log.c:401 builtin/log.c:489
+#: builtin/log.c:402 builtin/log.c:490
 #, c-format
 msgid "Could not read object %s"
 msgstr "不能读取对象 %s"
 
-#: builtin/log.c:513
+#: builtin/log.c:514
 #, c-format
 msgid "Unknown type: %d"
 msgstr "未知类型:%d"
 
-#: builtin/log.c:602
+#: builtin/log.c:603
 msgid "format.headers without value"
 msgstr "format.headers 没有值"
 
-#: builtin/log.c:676
+#: builtin/log.c:677
 msgid "name of output directory is too long"
 msgstr "输出目录名太长"
 
-#: builtin/log.c:687
+#: builtin/log.c:688
 #, c-format
 msgid "Cannot open patch file %s"
 msgstr "无法打开补丁文件 %s"
 
-#: builtin/log.c:701
+#: builtin/log.c:702
 msgid "Need exactly one range."
 msgstr "只需要一个范围。"
 
-#: builtin/log.c:709
+#: builtin/log.c:710
 msgid "Not a range."
 msgstr "不是一个范围。"
 
-#: builtin/log.c:786
+#: builtin/log.c:787
 msgid "Cover letter needs email format"
 msgstr "信封需要邮件地址格式"
 
-#: builtin/log.c:859
+#: builtin/log.c:860
 #, c-format
 msgid "insane in-reply-to: %s"
 msgstr "不正常的 in-reply-to:%s"
 
-#: builtin/log.c:932
+#: builtin/log.c:933
 msgid "Two output directories?"
 msgstr "两个输出目录?"
 
-#: builtin/log.c:1153
+#: builtin/log.c:1154
 #, c-format
 msgid "bogus committer info %s"
 msgstr "虚假的提交者信息 %s"
 
-#: builtin/log.c:1198
+#: builtin/log.c:1199
 msgid "-n and -k are mutually exclusive."
 msgstr "-n 和 -k 互斥。"
 
-#: builtin/log.c:1200
+#: builtin/log.c:1201
 msgid "--subject-prefix and -k are mutually exclusive."
 msgstr "--subject-prefix 和 -k 互斥。"
 
-#: builtin/log.c:1208
+#: builtin/log.c:1209
 msgid "--name-only does not make sense"
 msgstr "--name-only 无意义"
 
-#: builtin/log.c:1210
+#: builtin/log.c:1211
 msgid "--name-status does not make sense"
 msgstr "--name-status 无意义"
 
-#: builtin/log.c:1212
+#: builtin/log.c:1213
 msgid "--check does not make sense"
 msgstr "--check 无意义"
 
-#: builtin/log.c:1235
+#: builtin/log.c:1236
 msgid "standard output, or directory, which one?"
 msgstr "标准输出或目录,哪一个?"
 
-#: builtin/log.c:1237
+#: builtin/log.c:1238
 #, c-format
 msgid "Could not create directory '%s'"
 msgstr "不能创建目录 '%s'"
 
-#: builtin/log.c:1390
+#: builtin/log.c:1391
 msgid "Failed to create output files"
 msgstr "无法创建输出文件"
 
-#: builtin/log.c:1494
+#: builtin/log.c:1495
 #, c-format
 msgid ""
 "Could not find a tracked remote branch, please specify <upstream> manually.\n"
 msgstr "不能找到跟踪的远程分支,请手工指定 <upstream>。\n"
 
-#: builtin/log.c:1510 builtin/log.c:1512 builtin/log.c:1524
+#: builtin/log.c:1511 builtin/log.c:1513 builtin/log.c:1525
 #, c-format
 msgid "Unknown commit %s"
 msgstr "未知提交 %s"
@@ -3845,22 +3978,27 @@ msgstr "对象 %s 没有注解\n"
 msgid "Unknown subcommand: %s"
 msgstr "未知子命令:%s"
 
-#: builtin/pack-objects.c:2337
+#: builtin/pack-objects.c:183 builtin/pack-objects.c:186
+#, c-format
+msgid "deflate error (%d)"
+msgstr "压缩错误 (%d)"
+
+#: builtin/pack-objects.c:2398
 #, c-format
 msgid "unsupported index version %s"
 msgstr "不支持的索引版本 %s"
 
-#: builtin/pack-objects.c:2341
+#: builtin/pack-objects.c:2402
 #, c-format
 msgid "bad index version '%s'"
 msgstr "坏的索引版本 '%s'"
 
-#: builtin/pack-objects.c:2364
+#: builtin/pack-objects.c:2425
 #, c-format
 msgid "option %s does not accept negative form"
 msgstr "选项 %s 不接受否定格式"
 
-#: builtin/pack-objects.c:2368
+#: builtin/pack-objects.c:2429
 #, c-format
 msgid "unable to parse value '%s' for option %s"
 msgstr "不能解析选项 %1$s 的值 '%2$s'"
@@ -4810,8 +4948,7 @@ msgstr "您需要先设置你的提交者信息"
 msgid ""
 "You seem to have moved HEAD since the last 'am' failure.\n"
 "Not rewinding to ORIG_HEAD"
-msgstr ""
-"您好像在上一次 'am' 失败后移动了 HEAD。未回退至 ORIG_HEAD"
+msgstr "您好像在上一次 'am' 失败后移动了 HEAD。未回退至 ORIG_HEAD"
 
 #: git-am.sh:105
 #, sh-format
@@ -4929,8 +5066,7 @@ msgstr ""
 msgid ""
 "You still have unmerged paths in your index\n"
 "did you forget to use 'git add'?"
-msgstr ""
-"您的索引中仍有未合并的路径。您是否忘了执行 'git add'?"
+msgstr "您的索引中仍有未合并的路径。您是否忘了执行 'git add'?"
 
 #: git-am.sh:847
 msgid "No changes -- Patch already applied."
@@ -5271,37 +5407,37 @@ msgstr "未指定分支名"
 msgid "(To restore them type \"git stash apply\")"
 msgstr "(为恢复数据输入 \"git stash apply\")"
 
-#: git-submodule.sh:56
+#: git-submodule.sh:88
 #, sh-format
 msgid "cannot strip one component off url '$remoteurl'"
 msgstr "无法从 url '$remoteurl' 剥离一个组件"
 
-#: git-submodule.sh:109
+#: git-submodule.sh:145
 #, sh-format
 msgid "No submodule mapping found in .gitmodules for path '$sm_path'"
 msgstr "未在 .gitmodules 中发现路径 '$sm_path' 的子模组映射"
 
-#: git-submodule.sh:150
+#: git-submodule.sh:186
 #, sh-format
 msgid "Clone of '$url' into submodule path '$sm_path' failed"
 msgstr "无法克隆 '$url' 到子模组路径 '$sm_path'"
 
-#: git-submodule.sh:160
+#: git-submodule.sh:196
 #, sh-format
 msgid "Gitdir '$a' is part of the submodule path '$b' or vice versa"
 msgstr "Gitdir '$a' 在子模组路径 '$b' 之下或者相反"
 
-#: git-submodule.sh:249
+#: git-submodule.sh:285
 #, sh-format
 msgid "repo URL: '$repo' must be absolute or begin with ./|../"
 msgstr "版本库URL:'$repo' 必须是绝对路径或以 ./|../ 起始"
 
-#: git-submodule.sh:266
+#: git-submodule.sh:302
 #, sh-format
 msgid "'$sm_path' already exists in the index"
 msgstr "'$sm_path' 已经存在于索引中"
 
-#: git-submodule.sh:270
+#: git-submodule.sh:306
 #, sh-format
 msgid ""
 "The following path is ignored by one of your .gitignore files:\n"
@@ -5312,62 +5448,62 @@ msgstr ""
 "$sm_path\n"
 "如果您确实想添加它,使用 -f 参数。"
 
-#: git-submodule.sh:281
+#: git-submodule.sh:317
 #, sh-format
 msgid "Adding existing repo at '$sm_path' to the index"
 msgstr "添加位于 '$sm_path' 的现存版本库到索引"
 
-#: git-submodule.sh:283
+#: git-submodule.sh:319
 #, sh-format
 msgid "'$sm_path' already exists and is not a valid git repo"
 msgstr "'$sm_path' 已存在且不是一个有效的 git 版本库"
 
-#: git-submodule.sh:297
+#: git-submodule.sh:333
 #, sh-format
 msgid "Unable to checkout submodule '$sm_path'"
 msgstr "不能检出子模组 '$sm_path'"
 
-#: git-submodule.sh:302
+#: git-submodule.sh:338
 #, sh-format
 msgid "Failed to add submodule '$sm_path'"
 msgstr "无法添加子模组 '$sm_path'"
 
-#: git-submodule.sh:307
+#: git-submodule.sh:343
 #, sh-format
 msgid "Failed to register submodule '$sm_path'"
 msgstr "无法注册子模组 '$sm_path'"
 
-#: git-submodule.sh:349
+#: git-submodule.sh:385
 #, sh-format
 msgid "Entering '$prefix$sm_path'"
 msgstr "正在进入 '$prefix$sm_path'"
 
-#: git-submodule.sh:363
+#: git-submodule.sh:399
 #, sh-format
 msgid "Stopping at '$sm_path'; script returned non-zero status."
 msgstr "停止于 '$sm_path',脚本返回非零值。"
 
-#: git-submodule.sh:406
+#: git-submodule.sh:442
 #, sh-format
 msgid "No url found for submodule path '$sm_path' in .gitmodules"
 msgstr "在 .gitmodules 中未找到子模组路径 '$sm_path' 的 url"
 
-#: git-submodule.sh:415
+#: git-submodule.sh:451
 #, sh-format
 msgid "Failed to register url for submodule path '$sm_path'"
 msgstr "无法为子模组路径 '$sm_path' 注册 url"
 
-#: git-submodule.sh:417
+#: git-submodule.sh:453
 #, sh-format
 msgid "Submodule '$name' ($url) registered for path '$sm_path'"
 msgstr "子模组 '$name' ($url) 已为路径 '$sm_path' 注册"
 
-#: git-submodule.sh:425
+#: git-submodule.sh:461
 #, sh-format
 msgid "Failed to register update mode for submodule path '$sm_path'"
 msgstr "无法为子模组路径 '$sm_path' 注册更新模式"
 
-#: git-submodule.sh:524
+#: git-submodule.sh:560
 #, sh-format
 msgid ""
 "Submodule path '$sm_path' not initialized\n"
@@ -5376,96 +5512,96 @@ msgstr ""
 "子模组路径 '$sm_path' 没有初始化\n"
 "也许您想用 'update --init'?"
 
-#: git-submodule.sh:537
+#: git-submodule.sh:573
 #, sh-format
 msgid "Unable to find current revision in submodule path '$sm_path'"
 msgstr "无法在子模组路径 '$sm_path' 中找到当前版本"
 
-#: git-submodule.sh:556
+#: git-submodule.sh:592
 #, sh-format
 msgid "Unable to fetch in submodule path '$sm_path'"
 msgstr "无法在子模组路径 '$sm_path' 中获取"
 
-#: git-submodule.sh:570
+#: git-submodule.sh:606
 #, sh-format
 msgid "Unable to rebase '$sha1' in submodule path '$sm_path'"
 msgstr "无法在子模组路径 '$sm_path' 中变基 '$sha1'"
 
-#: git-submodule.sh:571
+#: git-submodule.sh:607
 #, sh-format
 msgid "Submodule path '$sm_path': rebased into '$sha1'"
 msgstr "子模组路径 '$sm_path':变基至 '$sha1'"
 
-#: git-submodule.sh:576
+#: git-submodule.sh:612
 #, sh-format
 msgid "Unable to merge '$sha1' in submodule path '$sm_path'"
 msgstr "无法合并 '$sha1' 到子模组路径 '$sm_path' 中"
 
-#: git-submodule.sh:577
+#: git-submodule.sh:613
 #, sh-format
 msgid "Submodule path '$sm_path': merged in '$sha1'"
 msgstr "子模组路径 '$sm_path':已合并入 '$sha1'"
 
-#: git-submodule.sh:582
+#: git-submodule.sh:618
 #, sh-format
 msgid "Unable to checkout '$sha1' in submodule path '$sm_path'"
 msgstr "无法在子模组路径 '$sm_path' 中检出 '$sha1'"
 
-#: git-submodule.sh:583
+#: git-submodule.sh:619
 #, sh-format
 msgid "Submodule path '$sm_path': checked out '$sha1'"
 msgstr "子模组路径 '$sm_path':检出 '$sha1'"
 
-#: git-submodule.sh:605 git-submodule.sh:928
+#: git-submodule.sh:641 git-submodule.sh:964
 #, sh-format
 msgid "Failed to recurse into submodule path '$sm_path'"
 msgstr "无法递归进子模组路径 '$sm_path'"
 
-#: git-submodule.sh:713
+#: git-submodule.sh:749
 msgid "--cached cannot be used with --files"
 msgstr "--cached 不能和 --files 共用"
 
 #. unexpected type
-#: git-submodule.sh:753
+#: git-submodule.sh:789
 #, sh-format
 msgid "unexpected mode $mod_dst"
 msgstr "意外的模式 $mod_dst"
 
 #  译者:注意保持前导空格
-#: git-submodule.sh:771
+#: git-submodule.sh:807
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_src"
 msgstr "  警告:$name 未包含提交 $sha1_src"
 
 #  译者:注意保持前导空格
-#: git-submodule.sh:774
+#: git-submodule.sh:810
 #, sh-format
 msgid "  Warn: $name doesn't contain commit $sha1_dst"
 msgstr "  警告:$name 未包含提交 $sha1_dst"
 
 #  译者:注意保持前导空格
-#: git-submodule.sh:777
+#: git-submodule.sh:813
 #, sh-format
 msgid "  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
 msgstr "  警告:$name 未包含提交 $sha1_src 和 $sha1_dst"
 
-#: git-submodule.sh:802
+#: git-submodule.sh:838
 msgid "blob"
 msgstr "blob"
 
-#: git-submodule.sh:803
+#: git-submodule.sh:839
 msgid "submodule"
 msgstr "子模组"
 
-#: git-submodule.sh:840
+#: git-submodule.sh:876
 msgid "# Submodules changed but not updated:"
 msgstr "# 子模组已修改但尚未更新:"
 
-#: git-submodule.sh:842
+#: git-submodule.sh:878
 msgid "# Submodule changes to be committed:"
 msgstr "要提交的子模组变更:"
 
-#: git-submodule.sh:974
+#: git-submodule.sh:1022
 #, sh-format
 msgid "Synchronizing submodule url for '$name'"
 msgstr "为 '$name' 同步子模组 url"
index b645827c06a268ee4721fda5fc8cdcd84775c9ec..2f8159fb165f853aafb5e0cee61f35aa854271ec 100644 (file)
 
 static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 
+/* Mask for the name length in ce_flags in the on-disk index */
+
+#define CE_NAMEMASK  (0x0fff)
+
 /* Index extensions.
  *
  * The first letter should be 'A'..'Z' for extensions that are not
@@ -54,8 +58,8 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
 
        new = xmalloc(cache_entry_size(namelen));
        copy_cache_entry(new, old);
-       new->ce_flags &= ~(CE_STATE_MASK | CE_NAMEMASK);
-       new->ce_flags |= (namelen >= CE_NAMEMASK ? CE_NAMEMASK : namelen);
+       new->ce_flags &= ~CE_STATE_MASK;
+       new->ce_namelen = namelen;
        memcpy(new->name, new_name, namelen + 1);
 
        cache_tree_invalidate_path(istate->cache_tree, old->name);
@@ -395,17 +399,10 @@ int df_name_compare(const char *name1, int len1, int mode1,
        return c1 - c2;
 }
 
-int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2)
+int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2)
 {
-       int len1, len2, len, cmp;
-
-       len1 = flags1 & CE_NAMEMASK;
-       if (CE_NAMEMASK <= len1)
-               len1 = strlen(name1 + CE_NAMEMASK) + CE_NAMEMASK;
-       len2 = flags2 & CE_NAMEMASK;
-       if (CE_NAMEMASK <= len2)
-               len2 = strlen(name2 + CE_NAMEMASK) + CE_NAMEMASK;
-       len = len1 < len2 ? len1 : len2;
+       int len = len1 < len2 ? len1 : len2;
+       int cmp;
 
        cmp = memcmp(name1, name2, len);
        if (cmp)
@@ -415,18 +412,19 @@ int cache_name_compare(const char *name1, int flags1, const char *name2, int fla
        if (len1 > len2)
                return 1;
 
-       /* Compare stages  */
-       flags1 &= CE_STAGEMASK;
-       flags2 &= CE_STAGEMASK;
-
-       if (flags1 < flags2)
+       if (stage1 < stage2)
                return -1;
-       if (flags1 > flags2)
+       if (stage1 > stage2)
                return 1;
        return 0;
 }
 
-int index_name_pos(const struct index_state *istate, const char *name, int namelen)
+int cache_name_compare(const char *name1, int len1, const char *name2, int len2)
+{
+       return cache_name_stage_compare(name1, len1, 0, name2, len2, 0);
+}
+
+int index_name_stage_pos(const struct index_state *istate, const char *name, int namelen, int stage)
 {
        int first, last;
 
@@ -435,7 +433,7 @@ int index_name_pos(const struct index_state *istate, const char *name, int namel
        while (last > first) {
                int next = (last + first) >> 1;
                struct cache_entry *ce = istate->cache[next];
-               int cmp = cache_name_compare(name, namelen, ce->name, ce->ce_flags);
+               int cmp = cache_name_stage_compare(name, namelen, stage, ce->name, ce_namelen(ce), ce_stage(ce));
                if (!cmp)
                        return next;
                if (cmp < 0) {
@@ -447,6 +445,11 @@ int index_name_pos(const struct index_state *istate, const char *name, int namel
        return -first-1;
 }
 
+int index_name_pos(const struct index_state *istate, const char *name, int namelen)
+{
+       return index_name_stage_pos(istate, name, namelen, 0);
+}
+
 /* Remove entry, return true if there are more entries to go.. */
 int remove_index_entry_at(struct index_state *istate, int pos)
 {
@@ -586,7 +589,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        size = cache_entry_size(namelen);
        ce = xcalloc(1, size);
        memcpy(ce->name, path, namelen);
-       ce->ce_flags = namelen;
+       ce->ce_namelen = namelen;
        if (!intent_only)
                fill_stat_cache_info(ce, st);
        else
@@ -688,7 +691,8 @@ struct cache_entry *make_cache_entry(unsigned int mode,
 
        hashcpy(ce->sha1, sha1);
        memcpy(ce->name, path, len);
-       ce->ce_flags = create_ce_flags(len, stage);
+       ce->ce_flags = create_ce_flags(stage);
+       ce->ce_namelen = len;
        ce->ce_mode = create_ce_mode(mode);
 
        if (refresh)
@@ -825,7 +829,7 @@ static int has_dir_name(struct index_state *istate,
                }
                len = slash - name;
 
-               pos = index_name_pos(istate, name, create_ce_flags(len, stage));
+               pos = index_name_stage_pos(istate, name, len, stage);
                if (pos >= 0) {
                        /*
                         * Found one, but not so fast.  This could
@@ -915,7 +919,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
        int new_only = option & ADD_CACHE_NEW_ONLY;
 
        cache_tree_invalidate_path(istate->cache_tree, ce->name);
-       pos = index_name_pos(istate, ce->name, ce->ce_flags);
+       pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
 
        /* existing match? Just replace it. */
        if (pos >= 0) {
@@ -947,7 +951,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
                if (!ok_to_replace)
                        return error("'%s' appears as both a file and as a directory",
                                     ce->name);
-               pos = index_name_pos(istate, ce->name, ce->ce_flags);
+               pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
                pos = -pos-1;
        }
        return pos + 1;
@@ -1124,7 +1128,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
                        continue;
 
                if (pathspec &&
-                   !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+                   !match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
                        filtered = 1;
 
                if (ce_stage(ce)) {
@@ -1324,7 +1328,8 @@ static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *on
        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;
+       ce->ce_flags = flags & ~CE_NAMEMASK;
+       ce->ce_namelen = len;
        hashcpy(ce->sha1, ondisk->sha1);
        memcpy(ce->name, name, len);
        ce->name[len] = '\0';
@@ -1651,6 +1656,8 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
 static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
                                       struct cache_entry *ce)
 {
+       short flags;
+
        ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
        ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
        ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
@@ -1662,7 +1669,10 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
        ondisk->gid  = htonl(ce->ce_gid);
        ondisk->size = htonl(ce->ce_size);
        hashcpy(ondisk->sha1, ce->sha1);
-       ondisk->flags = htons(ce->ce_flags);
+
+       flags = ce->ce_flags;
+       flags |= (ce_namelen(ce) >= CE_NAMEMASK ? CE_NAMEMASK : ce_namelen(ce));
+       ondisk->flags = htons(flags);
        if (ce->ce_flags & CE_EXTENDED) {
                struct ondisk_cache_entry_extended *ondisk2;
                ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
@@ -1846,11 +1856,12 @@ int read_index_unmerged(struct index_state *istate)
                if (!ce_stage(ce))
                        continue;
                unmerged = 1;
-               len = strlen(ce->name);
+               len = ce_namelen(ce);
                size = cache_entry_size(len);
                new_ce = xcalloc(1, size);
                memcpy(new_ce->name, ce->name, len);
-               new_ce->ce_flags = create_ce_flags(len, 0) | CE_CONFLICTED;
+               new_ce->ce_flags = create_ce_flags(0) | CE_CONFLICTED;
+               new_ce->ce_namelen = len;
                new_ce->ce_mode = ce->ce_mode;
                if (add_index_entry(istate, new_ce, 0))
                        return error("%s: cannot drop to stage #0",
index 651c5def92e3f864a99ed7e97d008aa04cce3782..b57a34daa718a5ecf9750d3068a8ecee4f3e261f 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -544,13 +544,13 @@ static int do_plain_rerere(struct string_list *rr, int fd)
 
                if (has_rerere_resolution(name)) {
                        if (!merge(name, path)) {
-                               if (rerere_autoupdate)
+                               const char *msg;
+                               if (rerere_autoupdate) {
                                        string_list_insert(&update, path);
-                               fprintf(stderr,
-                                       "%s '%s' using previous resolution.\n",
-                                       rerere_autoupdate
-                                       ? "Staged" : "Resolved",
-                                       path);
+                                       msg = "Staged '%s' using previous resolution.\n";
+                               } else
+                                       msg = "Resolved '%s' using previous resolution.\n";
+                               fprintf(stderr, msg, path);
                                goto mark_resolved;
                        }
                }
index 5b81a92e3ac65ab0295f25198511fc83b4bbf1c9..9e8f47a25d8def94cbf2a8389039e4d3d670cd06 100644 (file)
@@ -1000,7 +1000,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags)
                flags ^= UNINTERESTING;
                arg++;
        }
-       if (get_sha1(arg, sha1))
+       if (get_sha1_committish(arg, sha1))
                return 0;
        while (1) {
                it = get_reference(revs, arg, sha1, 0);
@@ -1114,16 +1114,16 @@ static void prepare_show_merge(struct rev_info *revs)
        revs->limited = 1;
 }
 
-int handle_revision_arg(const char *arg_, struct rev_info *revs,
-                       int flags,
-                       int cant_be_filename)
+int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
 {
-       unsigned mode;
+       struct object_context oc;
        char *dotdot;
        struct object *object;
        unsigned char sha1[20];
        int local_flags;
        const char *arg = arg_;
+       int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
+       unsigned get_sha1_flags = 0;
 
        dotdot = strstr(arg, "..");
        if (dotdot) {
@@ -1141,8 +1141,8 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs,
                        next = "HEAD";
                if (dotdot == arg)
                        this = "HEAD";
-               if (!get_sha1(this, from_sha1) &&
-                   !get_sha1(next, sha1)) {
+               if (!get_sha1_committish(this, from_sha1) &&
+                   !get_sha1_committish(next, sha1)) {
                        struct commit *a, *b;
                        struct commit_list *exclude;
 
@@ -1201,13 +1201,17 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs,
                local_flags = UNINTERESTING;
                arg++;
        }
-       if (get_sha1_with_mode(arg, sha1, &mode))
+
+       if (revarg_opt & REVARG_COMMITTISH)
+               get_sha1_flags = GET_SHA1_COMMITTISH;
+
+       if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc))
                return revs->ignore_missing ? 0 : -1;
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, sha1, flags ^ local_flags);
        add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
-       add_pending_object_with_mode(revs, object, arg, mode);
+       add_pending_object_with_mode(revs, object, arg, oc.mode);
        return 0;
 }
 
@@ -1257,7 +1261,7 @@ static void read_revisions_from_stdin(struct rev_info *revs,
                        }
                        die("options not supported in --stdin mode");
                }
-               if (handle_revision_arg(sb.buf, revs, 0, 1))
+               if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
        if (seen_dashdash)
@@ -1708,7 +1712,7 @@ static int handle_revision_pseudo_opt(const char *submodule,
  */
 int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
 {
-       int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;
+       int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt;
        struct cmdline_pathspec prune_data;
        const char *submodule = NULL;
 
@@ -1736,6 +1740,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 
        /* Second, deal with arguments and options */
        flags = 0;
+       revarg_opt = opt ? opt->revarg_opt : 0;
+       if (seen_dashdash)
+               revarg_opt |= REVARG_CANNOT_BE_FILENAME;
        read_from_stdin = 0;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -1771,7 +1778,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
                        continue;
                }
 
-               if (handle_revision_arg(arg, revs, flags, seen_dashdash)) {
+
+               if (handle_revision_arg(arg, revs, flags, revarg_opt)) {
                        int j;
                        if (seen_dashdash || *arg == '^')
                                die("bad revision '%s'", arg);
@@ -1822,11 +1830,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
        if (revs->def && !revs->pending.nr && !got_rev_arg) {
                unsigned char sha1[20];
                struct object *object;
-               unsigned mode;
-               if (get_sha1_with_mode(revs->def, sha1, &mode))
+               struct object_context oc;
+               if (get_sha1_with_context(revs->def, 0, sha1, &oc))
                        die("bad default revision '%s'", revs->def);
                object = get_reference(revs, revs->def, sha1, 0);
-               add_pending_object_with_mode(revs, object, revs->def, mode);
+               add_pending_object_with_mode(revs, object, revs->def, oc.mode);
        }
 
        /* Did the user ask for any diff output? Run the diff! */
@@ -2361,29 +2369,28 @@ static struct commit *get_revision_internal(struct rev_info *revs)
        }
 
        /*
-        * Now pick up what they want to give us
+        * If our max_count counter has reached zero, then we are done. We
+        * don't simply return NULL because we still might need to show
+        * boundary commits. But we want to avoid calling get_revision_1, which
+        * might do a considerable amount of work finding the next commit only
+        * for us to throw it away.
+        *
+        * If it is non-zero, then either we don't have a max_count at all
+        * (-1), or it is still counting, in which case we decrement.
         */
-       c = get_revision_1(revs);
-       if (c) {
-               while (0 < revs->skip_count) {
-                       revs->skip_count--;
-                       c = get_revision_1(revs);
-                       if (!c)
-                               break;
+       if (revs->max_count) {
+               c = get_revision_1(revs);
+               if (c) {
+                       while (0 < revs->skip_count) {
+                               revs->skip_count--;
+                               c = get_revision_1(revs);
+                               if (!c)
+                                       break;
+                       }
                }
-       }
 
-       /*
-        * Check the max_count.
-        */
-       switch (revs->max_count) {
-       case -1:
-               break;
-       case 0:
-               c = NULL;
-               break;
-       default:
-               revs->max_count--;
+               if (revs->max_count > 0)
+                       revs->max_count--;
        }
 
        if (c)
index 863f4f64543bd9f9e830e7c9567def23e906c7d9..cb5ab3513bc9982095e292119b9119d333556256 100644 (file)
@@ -184,6 +184,7 @@ struct setup_revision_opt {
        void (*tweak)(struct rev_info *, struct setup_revision_opt *);
        const char *submodule;
        int assume_dashdash;
+       unsigned revarg_opt;
 };
 
 extern void init_revisions(struct rev_info *revs, const char *prefix);
@@ -191,7 +192,9 @@ extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, s
 extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx,
                                 const struct option *options,
                                 const char * const usagestr[]);
-extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
+#define REVARG_CANNOT_BE_FILENAME 01
+#define REVARG_COMMITTISH 02
+extern int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt);
 
 extern void reset_revision_walk(void);
 extern int prepare_revision_walk(struct rev_info *revs);
diff --git a/setup.c b/setup.c
index e11497720e01dd14a66e152883e675669fc3b548..9139beefc75c4fe92cf8c7dc54a9fae972995aca 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -77,9 +77,6 @@ static void NORETURN die_verify_filename(const char *prefix,
                                         const char *arg,
                                         int diagnose_misspelt_rev)
 {
-       unsigned char sha1[20];
-       unsigned mode;
-
        if (!diagnose_misspelt_rev)
                die("%s: no such path in the working tree.\n"
                    "Use '-- <path>...' to specify paths that do not exist locally.",
@@ -88,11 +85,10 @@ static void NORETURN die_verify_filename(const char *prefix,
         * Saying "'(icase)foo' does not exist in the index" when the
         * user gave us ":(icase)foo" is just stupid.  A magic pathspec
         * begins with a colon and is followed by a non-alnum; do not
-        * let get_sha1_with_mode_1(only_to_die=1) to even trigger.
+        * let maybe_die_on_misspelt_object_name() even trigger.
         */
        if (!(arg[0] == ':' && !isalnum(arg[1])))
-               /* try a detailed diagnostic ... */
-               get_sha1_with_mode_1(arg, sha1, &mode, 1, prefix);
+               maybe_die_on_misspelt_object_name(arg, prefix);
 
        /* ... or fall back the most general message. */
        die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
index 5d81ea0564c8d90b417eb8ec5ed8c32baa2c3ea3..95003c77ea9a0ee240d60bc7958bb48245b14ad4 100644 (file)
@@ -9,14 +9,82 @@
 
 static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
 
-static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
+typedef int (*disambiguate_hint_fn)(const unsigned char *, void *);
+
+struct disambiguate_state {
+       disambiguate_hint_fn fn;
+       void *cb_data;
+       unsigned char candidate[20];
+       unsigned candidate_exists:1;
+       unsigned candidate_checked:1;
+       unsigned candidate_ok:1;
+       unsigned disambiguate_fn_used:1;
+       unsigned ambiguous:1;
+       unsigned always_call_fn:1;
+};
+
+static void update_candidates(struct disambiguate_state *ds, const unsigned char *current)
+{
+       if (ds->always_call_fn) {
+               ds->ambiguous = ds->fn(current, ds->cb_data) ? 1 : 0;
+               return;
+       }
+       if (!ds->candidate_exists) {
+               /* this is the first candidate */
+               hashcpy(ds->candidate, current);
+               ds->candidate_exists = 1;
+               return;
+       } else if (!hashcmp(ds->candidate, current)) {
+               /* the same as what we already have seen */
+               return;
+       }
+
+       if (!ds->fn) {
+               /* cannot disambiguate between ds->candidate and current */
+               ds->ambiguous = 1;
+               return;
+       }
+
+       if (!ds->candidate_checked) {
+               ds->candidate_ok = ds->fn(ds->candidate, ds->cb_data);
+               ds->disambiguate_fn_used = 1;
+               ds->candidate_checked = 1;
+       }
+
+       if (!ds->candidate_ok) {
+               /* discard the candidate; we know it does not satisify fn */
+               hashcpy(ds->candidate, current);
+               ds->candidate_checked = 0;
+               return;
+       }
+
+       /* if we reach this point, we know ds->candidate satisfies fn */
+       if (ds->fn(current, ds->cb_data)) {
+               /*
+                * if both current and candidate satisfy fn, we cannot
+                * disambiguate.
+                */
+               ds->candidate_ok = 0;
+               ds->ambiguous = 1;
+       }
+
+       /* otherwise, current can be discarded and candidate is still good */
+}
+
+static void find_short_object_filename(int len, const char *hex_pfx, struct disambiguate_state *ds)
 {
        struct alternate_object_database *alt;
        char hex[40];
-       int found = 0;
        static struct alternate_object_database *fakeent;
 
        if (!fakeent) {
+               /*
+                * Create a "fake" alternate object database that
+                * points to our own object database, to make it
+                * easier to get a temporary working space in
+                * alt->name/alt->base while iterating over the
+                * object databases including our own.
+                */
                const char *objdir = get_object_directory();
                int objdir_len = strlen(objdir);
                int entlen = objdir_len + 43;
@@ -27,33 +95,28 @@ static int find_short_object_filename(int len, const char *name, unsigned char *
        }
        fakeent->next = alt_odb_list;
 
-       sprintf(hex, "%.2s", name);
-       for (alt = fakeent; alt && found < 2; alt = alt->next) {
+       sprintf(hex, "%.2s", hex_pfx);
+       for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) {
                struct dirent *de;
                DIR *dir;
-               sprintf(alt->name, "%.2s/", name);
+               sprintf(alt->name, "%.2s/", hex_pfx);
                dir = opendir(alt->base);
                if (!dir)
                        continue;
-               while ((de = readdir(dir)) != NULL) {
+
+               while (!ds->ambiguous && (de = readdir(dir)) != NULL) {
+                       unsigned char sha1[20];
+
                        if (strlen(de->d_name) != 38)
                                continue;
-                       if (memcmp(de->d_name, name + 2, len - 2))
+                       if (memcmp(de->d_name, hex_pfx + 2, len - 2))
                                continue;
-                       if (!found) {
-                               memcpy(hex + 2, de->d_name, 38);
-                               found++;
-                       }
-                       else if (memcmp(hex + 2, de->d_name, 38)) {
-                               found = 2;
-                               break;
-                       }
+                       memcpy(hex + 2, de->d_name, 38);
+                       if (!get_sha1_hex(hex, sha1))
+                               update_candidates(ds, sha1);
                }
                closedir(dir);
        }
-       if (found == 1)
-               return get_sha1_hex(hex, sha1) == 0;
-       return found;
 }
 
 static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
@@ -71,103 +134,157 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char *
        return 1;
 }
 
-static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
+static void unique_in_pack(int len,
+                         const unsigned char *bin_pfx,
+                          struct packed_git *p,
+                          struct disambiguate_state *ds)
 {
-       struct packed_git *p;
-       const unsigned char *found_sha1 = NULL;
-       int found = 0;
-
-       prepare_packed_git();
-       for (p = packed_git; p && found < 2; p = p->next) {
-               uint32_t num, last;
-               uint32_t first = 0;
-               open_pack_index(p);
-               num = p->num_objects;
-               last = num;
-               while (first < last) {
-                       uint32_t mid = (first + last) / 2;
-                       const unsigned char *now;
-                       int cmp;
-
-                       now = nth_packed_object_sha1(p, mid);
-                       cmp = hashcmp(match, now);
-                       if (!cmp) {
-                               first = mid;
-                               break;
-                       }
-                       if (cmp > 0) {
-                               first = mid+1;
-                               continue;
-                       }
-                       last = mid;
+       uint32_t num, last, i, first = 0;
+       const unsigned char *current = NULL;
+
+       open_pack_index(p);
+       num = p->num_objects;
+       last = num;
+       while (first < last) {
+               uint32_t mid = (first + last) / 2;
+               const unsigned char *current;
+               int cmp;
+
+               current = nth_packed_object_sha1(p, mid);
+               cmp = hashcmp(bin_pfx, current);
+               if (!cmp) {
+                       first = mid;
+                       break;
                }
-               if (first < num) {
-                       const unsigned char *now, *next;
-                      now = nth_packed_object_sha1(p, first);
-                       if (match_sha(len, match, now)) {
-                               next = nth_packed_object_sha1(p, first+1);
-                              if (!next|| !match_sha(len, match, next)) {
-                                       /* unique within this pack */
-                                       if (!found) {
-                                               found_sha1 = now;
-                                               found++;
-                                       }
-                                       else if (hashcmp(found_sha1, now)) {
-                                               found = 2;
-                                               break;
-                                       }
-                               }
-                               else {
-                                       /* not even unique within this pack */
-                                       found = 2;
-                                       break;
-                               }
-                       }
+               if (cmp > 0) {
+                       first = mid+1;
+                       continue;
                }
+               last = mid;
+       }
+
+       /*
+        * At this point, "first" is the location of the lowest object
+        * with an object name that could match "bin_pfx".  See if we have
+        * 0, 1 or more objects that actually match(es).
+        */
+       for (i = first; i < num && !ds->ambiguous; i++) {
+               current = nth_packed_object_sha1(p, i);
+               if (!match_sha(len, bin_pfx, current))
+                       break;
+               update_candidates(ds, current);
        }
-       if (found == 1)
-               hashcpy(sha1, found_sha1);
-       return found;
+}
+
+static void find_short_packed_object(int len, const unsigned char *bin_pfx,
+                                    struct disambiguate_state *ds)
+{
+       struct packed_git *p;
+
+       prepare_packed_git();
+       for (p = packed_git; p && !ds->ambiguous; p = p->next)
+               unique_in_pack(len, bin_pfx, p, ds);
 }
 
 #define SHORT_NAME_NOT_FOUND (-1)
 #define SHORT_NAME_AMBIGUOUS (-2)
 
-static int find_unique_short_object(int len, char *canonical,
-                                   unsigned char *res, unsigned char *sha1)
+static int finish_object_disambiguation(struct disambiguate_state *ds,
+                                       unsigned char *sha1)
 {
-       int has_unpacked, has_packed;
-       unsigned char unpacked_sha1[20], packed_sha1[20];
+       if (ds->ambiguous)
+               return SHORT_NAME_AMBIGUOUS;
 
-       prepare_alt_odb();
-       has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1);
-       has_packed = find_short_packed_object(len, res, packed_sha1);
-       if (!has_unpacked && !has_packed)
+       if (!ds->candidate_exists)
                return SHORT_NAME_NOT_FOUND;
-       if (1 < has_unpacked || 1 < has_packed)
+
+       if (!ds->candidate_checked)
+               /*
+                * If this is the only candidate, there is no point
+                * calling the disambiguation hint callback.
+                *
+                * On the other hand, if the current candidate
+                * replaced an earlier candidate that did _not_ pass
+                * the disambiguation hint callback, then we do have
+                * more than one objects that match the short name
+                * given, so we should make sure this one matches;
+                * otherwise, if we discovered this one and the one
+                * that we previously discarded in the reverse order,
+                * we would end up showing different results in the
+                * same repository!
+                */
+               ds->candidate_ok = (!ds->disambiguate_fn_used ||
+                                   ds->fn(ds->candidate, ds->cb_data));
+
+       if (!ds->candidate_ok)
                return SHORT_NAME_AMBIGUOUS;
-       if (has_unpacked != has_packed) {
-               hashcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1));
+
+       hashcpy(sha1, ds->candidate);
+       return 0;
+}
+
+static int disambiguate_commit_only(const unsigned char *sha1, void *cb_data_unused)
+{
+       int kind = sha1_object_info(sha1, NULL);
+       return kind == OBJ_COMMIT;
+}
+
+static int disambiguate_committish_only(const unsigned char *sha1, void *cb_data_unused)
+{
+       struct object *obj;
+       int kind;
+
+       kind = sha1_object_info(sha1, NULL);
+       if (kind == OBJ_COMMIT)
+               return 1;
+       if (kind != OBJ_TAG)
                return 0;
-       }
-       /* Both have unique ones -- do they match? */
-       if (hashcmp(packed_sha1, unpacked_sha1))
-               return SHORT_NAME_AMBIGUOUS;
-       hashcpy(sha1, packed_sha1);
+
+       /* We need to do this the hard way... */
+       obj = deref_tag(lookup_object(sha1), NULL, 0);
+       if (obj && obj->type == OBJ_COMMIT)
+               return 1;
        return 0;
 }
 
-static int get_short_sha1(const char *name, int len, unsigned char *sha1,
-                         int quietly)
+static int disambiguate_tree_only(const unsigned char *sha1, void *cb_data_unused)
 {
-       int i, status;
-       char canonical[40];
-       unsigned char res[20];
+       int kind = sha1_object_info(sha1, NULL);
+       return kind == OBJ_TREE;
+}
 
-       if (len < MINIMUM_ABBREV || len > 40)
-               return -1;
-       hashclr(res);
-       memset(canonical, 'x', 40);
+static int disambiguate_treeish_only(const unsigned char *sha1, void *cb_data_unused)
+{
+       struct object *obj;
+       int kind;
+
+       kind = sha1_object_info(sha1, NULL);
+       if (kind == OBJ_TREE || kind == OBJ_COMMIT)
+               return 1;
+       if (kind != OBJ_TAG)
+               return 0;
+
+       /* We need to do this the hard way... */
+       obj = deref_tag(lookup_object(sha1), NULL, 0);
+       if (obj && (obj->type == OBJ_TREE || obj->type == OBJ_COMMIT))
+               return 1;
+       return 0;
+}
+
+static int disambiguate_blob_only(const unsigned char *sha1, void *cb_data_unused)
+{
+       int kind = sha1_object_info(sha1, NULL);
+       return kind == OBJ_BLOB;
+}
+
+static int prepare_prefixes(const char *name, int len,
+                           unsigned char *bin_pfx,
+                           char *hex_pfx)
+{
+       int i;
+
+       hashclr(bin_pfx);
+       memset(hex_pfx, 'x', 40);
        for (i = 0; i < len ;i++) {
                unsigned char c = name[i];
                unsigned char val;
@@ -181,18 +298,76 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1,
                }
                else
                        return -1;
-               canonical[i] = c;
+               hex_pfx[i] = c;
                if (!(i & 1))
                        val <<= 4;
-               res[i >> 1] |= val;
+               bin_pfx[i >> 1] |= val;
        }
+       return 0;
+}
+
+static int get_short_sha1(const char *name, int len, unsigned char *sha1,
+                         unsigned flags)
+{
+       int status;
+       char hex_pfx[40];
+       unsigned char bin_pfx[20];
+       struct disambiguate_state ds;
+       int quietly = !!(flags & GET_SHA1_QUIETLY);
+
+       if (len < MINIMUM_ABBREV || len > 40)
+               return -1;
+       if (prepare_prefixes(name, len, bin_pfx, hex_pfx) < 0)
+               return -1;
+
+       prepare_alt_odb();
+
+       memset(&ds, 0, sizeof(ds));
+       if (flags & GET_SHA1_COMMIT)
+               ds.fn = disambiguate_commit_only;
+       else if (flags & GET_SHA1_COMMITTISH)
+               ds.fn = disambiguate_committish_only;
+       else if (flags & GET_SHA1_TREE)
+               ds.fn = disambiguate_tree_only;
+       else if (flags & GET_SHA1_TREEISH)
+               ds.fn = disambiguate_treeish_only;
+       else if (flags & GET_SHA1_BLOB)
+               ds.fn = disambiguate_blob_only;
+
+       find_short_object_filename(len, hex_pfx, &ds);
+       find_short_packed_object(len, bin_pfx, &ds);
+       status = finish_object_disambiguation(&ds, sha1);
 
-       status = find_unique_short_object(i, canonical, res, sha1);
        if (!quietly && (status == SHORT_NAME_AMBIGUOUS))
-               return error("short SHA1 %.*s is ambiguous.", len, canonical);
+               return error("short SHA1 %.*s is ambiguous.", len, hex_pfx);
        return status;
 }
 
+
+int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data)
+{
+       char hex_pfx[40];
+       unsigned char bin_pfx[20];
+       struct disambiguate_state ds;
+       int len = strlen(prefix);
+
+       if (len < MINIMUM_ABBREV || len > 40)
+               return -1;
+       if (prepare_prefixes(prefix, len, bin_pfx, hex_pfx) < 0)
+               return -1;
+
+       prepare_alt_odb();
+
+       memset(&ds, 0, sizeof(ds));
+       ds.always_call_fn = 1;
+       ds.cb_data = cb_data;
+       ds.fn = fn;
+
+       find_short_object_filename(len, hex_pfx, &ds);
+       find_short_packed_object(len, bin_pfx, &ds);
+       return ds.ambiguous;
+}
+
 const char *find_unique_abbrev(const unsigned char *sha1, int len)
 {
        int status, exists;
@@ -204,7 +379,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len)
                return hex;
        while (len < 40) {
                unsigned char sha1_ret[20];
-               status = get_short_sha1(hex, len, sha1_ret, 1);
+               status = get_short_sha1(hex, len, sha1_ret, GET_SHA1_QUIETLY);
                if (exists
                    ? !status
                    : status == SHORT_NAME_NOT_FOUND) {
@@ -255,7 +430,7 @@ static inline int upstream_mark(const char *string, int len)
        return 0;
 }
 
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
 
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
@@ -292,7 +467,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                ret = interpret_branch_name(str+at, &buf);
                if (ret > 0) {
                        /* substitute this branch name and restart */
-                       return get_sha1_1(buf.buf, buf.len, sha1);
+                       return get_sha1_1(buf.buf, buf.len, sha1, 0);
                } else if (ret == 0) {
                        return -1;
                }
@@ -362,7 +537,7 @@ static int get_parent(const char *name, int len,
                      unsigned char *result, int idx)
 {
        unsigned char sha1[20];
-       int ret = get_sha1_1(name, len, sha1);
+       int ret = get_sha1_1(name, len, sha1, GET_SHA1_COMMITTISH);
        struct commit *commit;
        struct commit_list *p;
 
@@ -395,7 +570,7 @@ static int get_nth_ancestor(const char *name, int len,
        struct commit *commit;
        int ret;
 
-       ret = get_sha1_1(name, len, sha1);
+       ret = get_sha1_1(name, len, sha1, GET_SHA1_COMMITTISH);
        if (ret)
                return ret;
        commit = lookup_commit_reference(sha1);
@@ -441,6 +616,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
        unsigned char outer[20];
        const char *sp;
        unsigned int expected_type = 0;
+       unsigned lookup_flags = 0;
        struct object *o;
 
        /*
@@ -476,7 +652,10 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
        else
                return -1;
 
-       if (get_sha1_1(name, sp - name - 2, outer))
+       if (expected_type == OBJ_COMMIT)
+               lookup_flags = GET_SHA1_COMMITTISH;
+
+       if (get_sha1_1(name, sp - name - 2, outer, lookup_flags))
                return -1;
 
        o = parse_object(outer);
@@ -525,6 +704,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
 static int get_describe_name(const char *name, int len, unsigned char *sha1)
 {
        const char *cp;
+       unsigned flags = GET_SHA1_QUIETLY | GET_SHA1_COMMIT;
 
        for (cp = name + len - 1; name + 2 <= cp; cp--) {
                char ch = *cp;
@@ -535,14 +715,14 @@ static int get_describe_name(const char *name, int len, unsigned char *sha1)
                        if (ch == 'g' && cp[-1] == '-') {
                                cp++;
                                len -= cp - name;
-                               return get_short_sha1(cp, len, sha1, 1);
+                               return get_short_sha1(cp, len, sha1, flags);
                        }
                }
        }
        return -1;
 }
 
-static int get_sha1_1(const char *name, int len, unsigned char *sha1)
+static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags)
 {
        int ret, has_suffix;
        const char *cp;
@@ -587,7 +767,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
        if (!ret)
                return 0;
 
-       return get_short_sha1(name, len, sha1, 0);
+       return get_short_sha1(name, len, sha1, lookup_flags);
 }
 
 /*
@@ -769,7 +949,7 @@ int get_sha1_mb(const char *name, unsigned char *sha1)
                struct strbuf sb;
                strbuf_init(&sb, dots - name);
                strbuf_add(&sb, name, dots - name);
-               st = get_sha1(sb.buf, sha1_tmp);
+               st = get_sha1_committish(sb.buf, sha1_tmp);
                strbuf_release(&sb);
        }
        if (st)
@@ -778,7 +958,7 @@ int get_sha1_mb(const char *name, unsigned char *sha1)
        if (!one)
                return -1;
 
-       if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
+       if (get_sha1_committish(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
                return -1;
        two = lookup_commit_reference_gently(sha1_tmp, 0);
        if (!two)
@@ -905,7 +1085,52 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
 int get_sha1(const char *name, unsigned char *sha1)
 {
        struct object_context unused;
-       return get_sha1_with_context(name, sha1, &unused);
+       return get_sha1_with_context(name, 0, sha1, &unused);
+}
+
+/*
+ * Many callers know that the user meant to name a committish by
+ * syntactical positions where the object name appears.  Calling this
+ * function allows the machinery to disambiguate shorter-than-unique
+ * abbreviated object names between committish and others.
+ *
+ * Note that this does NOT error out when the named object is not a
+ * committish. It is merely to give a hint to the disambiguation
+ * machinery.
+ */
+int get_sha1_committish(const char *name, unsigned char *sha1)
+{
+       struct object_context unused;
+       return get_sha1_with_context(name, GET_SHA1_COMMITTISH,
+                                    sha1, &unused);
+}
+
+int get_sha1_treeish(const char *name, unsigned char *sha1)
+{
+       struct object_context unused;
+       return get_sha1_with_context(name, GET_SHA1_TREEISH,
+                                    sha1, &unused);
+}
+
+int get_sha1_commit(const char *name, unsigned char *sha1)
+{
+       struct object_context unused;
+       return get_sha1_with_context(name, GET_SHA1_COMMIT,
+                                    sha1, &unused);
+}
+
+int get_sha1_tree(const char *name, unsigned char *sha1)
+{
+       struct object_context unused;
+       return get_sha1_with_context(name, GET_SHA1_TREE,
+                                    sha1, &unused);
+}
+
+int get_sha1_blob(const char *name, unsigned char *sha1)
+{
+       struct object_context unused;
+       return get_sha1_with_context(name, GET_SHA1_BLOB,
+                                    sha1, &unused);
 }
 
 /* Must be called only when object_name:filename doesn't exist. */
@@ -1004,16 +1229,6 @@ static void diagnose_invalid_index_path(int stage,
 }
 
 
-int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode,
-                        int only_to_die, const char *prefix)
-{
-       struct object_context oc;
-       int ret;
-       ret = get_sha1_with_context_1(name, sha1, &oc, only_to_die, prefix);
-       *mode = oc.mode;
-       return ret;
-}
-
 static char *resolve_relative_path(const char *rel)
 {
        if (prefixcmp(rel, "./") && prefixcmp(rel, "../"))
@@ -1031,20 +1246,24 @@ static char *resolve_relative_path(const char *rel)
                           rel);
 }
 
-int get_sha1_with_context_1(const char *name, unsigned char *sha1,
-                           struct object_context *oc,
-                           int only_to_die, const char *prefix)
+static int get_sha1_with_context_1(const char *name,
+                                  unsigned flags,
+                                  const char *prefix,
+                                  unsigned char *sha1,
+                                  struct object_context *oc)
 {
        int ret, bracket_depth;
        int namelen = strlen(name);
        const char *cp;
+       int only_to_die = flags & GET_SHA1_ONLY_TO_DIE;
 
        memset(oc, 0, sizeof(*oc));
        oc->mode = S_IFINVALID;
-       ret = get_sha1_1(name, namelen, sha1);
+       ret = get_sha1_1(name, namelen, sha1, flags);
        if (!ret)
                return ret;
-       /* sha1:path --> object name of path in ent sha1
+       /*
+        * sha1:path --> object name of path in ent sha1
         * :path -> object name of absolute path in index
         * :./path -> object name of path relative to cwd in index
         * :[0-3]:path -> object name of path in index at stage
@@ -1119,7 +1338,7 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
                        strncpy(object_name, name, cp-name);
                        object_name[cp-name] = '\0';
                }
-               if (!get_sha1_1(name, cp-name, tree_sha1)) {
+               if (!get_sha1_1(name, cp-name, tree_sha1, GET_SHA1_TREEISH)) {
                        const char *filename = cp+1;
                        char *new_filename = NULL;
 
@@ -1146,3 +1365,22 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
        }
        return ret;
 }
+
+/*
+ * Call this function when you know "name" given by the end user must
+ * name an object but it doesn't; the function _may_ die with a better
+ * diagnostic message than "no such object 'name'", e.g. "Path 'doc' does not
+ * exist in 'HEAD'" when given "HEAD:doc", or it may return in which case
+ * you have a chance to diagnose the error further.
+ */
+void maybe_die_on_misspelt_object_name(const char *name, const char *prefix)
+{
+       struct object_context oc;
+       unsigned char sha1[20];
+       get_sha1_with_context_1(name, GET_SHA1_ONLY_TO_DIE, prefix, sha1, &oc);
+}
+
+int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
+{
+       return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
+}
diff --git a/t/lib-bash.sh b/t/lib-bash.sh
new file mode 100644 (file)
index 0000000..11397f7
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Ensures that tests are run under Bash; primarily intended for running tests
+# of the completion script.
+
+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-lib.sh
index 4a37cd79e585e86572b5bc6f127bbee33af998b3..957ae936e8b785f8b1afbffd9a5918a3260d213f 100755 (executable)
@@ -4,10 +4,20 @@
 # stdout and stderr should be provided on stdin,
 # separated by "--".
 check() {
+       credential_opts=
+       credential_cmd=$1
+       shift
+       for arg in "$@"; do
+               credential_opts="$credential_opts -c credential.helper='$arg'"
+       done
        read_chunk >stdin &&
        read_chunk >expect-stdout &&
        read_chunk >expect-stderr &&
-       test-credential "$@" <stdin >stdout 2>stderr &&
+       if ! eval "git $credential_opts credential $credential_cmd <stdin >stdout 2>stderr"; then
+               echo "git credential failed with code $?" &&
+               cat stderr &&
+               false
+       fi &&
        test_cmp expect-stdout stdout &&
        test_cmp expect-stderr stderr
 }
@@ -41,7 +51,7 @@ reject() {
                echo protocol=$2
                echo host=$3
                echo username=$4
-       ) | test-credential reject $1
+       ) | git -c credential.helper=$1 credential reject
 }
 
 helper_test() {
@@ -52,6 +62,8 @@ helper_test() {
                protocol=https
                host=example.com
                --
+               protocol=https
+               host=example.com
                username=askpass-username
                password=askpass-password
                --
@@ -74,6 +86,8 @@ helper_test() {
                protocol=https
                host=example.com
                --
+               protocol=https
+               host=example.com
                username=store-user
                password=store-pass
                --
@@ -85,6 +99,8 @@ helper_test() {
                protocol=http
                host=example.com
                --
+               protocol=http
+               host=example.com
                username=askpass-username
                password=askpass-password
                --
@@ -98,6 +114,8 @@ helper_test() {
                protocol=https
                host=other.tld
                --
+               protocol=https
+               host=other.tld
                username=askpass-username
                password=askpass-password
                --
@@ -112,6 +130,8 @@ helper_test() {
                host=example.com
                username=other
                --
+               protocol=https
+               host=example.com
                username=other
                password=askpass-password
                --
@@ -133,6 +153,9 @@ helper_test() {
                host=path.tld
                path=bar.git
                --
+               protocol=http
+               host=path.tld
+               path=bar.git
                username=askpass-username
                password=askpass-password
                --
@@ -150,6 +173,8 @@ helper_test() {
                protocol=https
                host=example.com
                --
+               protocol=https
+               host=example.com
                username=askpass-username
                password=askpass-password
                --
@@ -176,6 +201,8 @@ helper_test() {
                host=example.com
                username=user1
                --
+               protocol=https
+               host=example.com
                username=user1
                password=pass1
                EOF
@@ -184,6 +211,8 @@ helper_test() {
                host=example.com
                username=user2
                --
+               protocol=https
+               host=example.com
                username=user2
                password=pass2
                EOF
@@ -200,6 +229,8 @@ helper_test() {
                host=example.com
                username=user1
                --
+               protocol=https
+               host=example.com
                username=user1
                password=askpass-password
                --
@@ -213,6 +244,8 @@ helper_test() {
                host=example.com
                username=user2
                --
+               protocol=https
+               host=example.com
                username=user2
                password=pass2
                EOF
@@ -234,6 +267,8 @@ helper_test_timeout() {
                protocol=https
                host=timeout.tld
                --
+               protocol=https
+               host=timeout.tld
                username=askpass-username
                password=askpass-password
                --
index 121e38002bc73807649ebcc5df2799f6f35a9c67..2d753ab7e118e0905e63860cc4ee6ae4932f16c4 100644 (file)
@@ -2,6 +2,10 @@
 # Library code for git p4 tests
 #
 
+# p4 tests never use the top-level repo; always build/clone into
+# a subdirectory called "$git"
+TEST_NO_CREATE_REPO=NoThanks
+
 . ./test-lib.sh
 
 if ! test_have_prereq PYTHON; then
@@ -27,23 +31,48 @@ export P4CLIENT=client
 export P4EDITOR=:
 
 db="$TRASH_DIRECTORY/db"
-cli="$TRASH_DIRECTORY/cli"
+cli=$(test-path-utils real_path "$TRASH_DIRECTORY/cli")
 git="$TRASH_DIRECTORY/git"
 pidfile="$TRASH_DIRECTORY/p4d.pid"
 
 start_p4d() {
        mkdir -p "$db" "$cli" "$git" &&
+       rm -f "$pidfile" &&
        (
                p4d -q -r "$db" -p $P4DPORT &
                echo $! >"$pidfile"
        ) &&
-       for i in 1 2 3 4 5 ; do
-               p4 info >/dev/null 2>&1 && break || true &&
-               echo waiting for p4d to start &&
+
+       # This gives p4d a long time to start up, as it can be
+       # quite slow depending on the machine.  Set this environment
+       # variable to something smaller to fail faster in, say,
+       # an automated test setup.  If the p4d process dies, that
+       # will be caught with the "kill -0" check below.
+       i=${P4D_START_PATIENCE:-300}
+       pid=$(cat "$pidfile")
+       ready=
+       while test $i -gt 0
+       do
+               # succeed when p4 client commands start to work
+               if p4 info >/dev/null 2>&1
+               then
+                       ready=true
+                       break
+               fi
+               # fail if p4d died
+               kill -0 $pid 2>/dev/null || break
+               echo waiting for p4d to start
                sleep 1
-       done &&
-       # complain if it never started
-       p4 info >/dev/null &&
+               i=$(( $i - 1 ))
+       done
+
+       if test -z "$ready"
+       then
+               # p4d failed to start
+               return 1
+       fi
+
+       # build a client
        (
                cd "$cli" &&
                p4 client -i <<-EOF
@@ -53,6 +82,7 @@ start_p4d() {
                View: //depot/... //client/...
                EOF
        )
+       return 0
 }
 
 kill_p4d() {
@@ -69,5 +99,19 @@ kill_p4d() {
 }
 
 cleanup_git() {
-       rm -rf "$git"
+       rm -rf "$git" &&
+       mkdir "$git"
+}
+
+marshal_dump() {
+       what=$1 &&
+       line=${2:-1} &&
+       cat >"$TRASH_DIRECTORY/marshal-dump.py" <<-EOF &&
+       import marshal
+       import sys
+       for i in range($line):
+           d = marshal.load(sys.stdin)
+       print d['$what']
+       EOF
+       "$PYTHON_PATH" "$TRASH_DIRECTORY/marshal-dump.py"
 }
index 094d49089389c9e8dbf8c561ccc133065552fc81..d773542680d9f3c3d77e52871877560a9e545a26 100644 (file)
@@ -43,6 +43,10 @@ TEST_PATH="$TEST_DIRECTORY"/lib-httpd
 HTTPD_ROOT_PATH="$PWD"/httpd
 HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www
 
+# hack to suppress apache PassEnv warnings
+GIT_VALGRIND=$GIT_VALGRIND; export GIT_VALGRIND
+GIT_VALGRIND_OPTIONS=$GIT_VALGRIND_OPTIONS; export GIT_VALGRIND_OPTIONS
+
 if ! test -x "$LIB_HTTPD_PATH"
 then
        skip_all="skipping test, no web server found at '$LIB_HTTPD_PATH'"
index de3762e24762692733f7a42f58b139e279b29f3d..36b1596a10c04b5c5c095e96e8e247463eb3f022 100644 (file)
@@ -42,6 +42,9 @@ ErrorLog error.log
 </IfModule>
 </IfVersion>
 
+PassEnv GIT_VALGRIND
+PassEnv GIT_VALGRIND_OPTIONS
+
 Alias /dumb/ www/
 Alias /auth/ www/auth/
 
@@ -62,7 +65,7 @@ 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
+       Options FollowSymlinks
 </Directory>
 <Files ${GIT_EXEC_PATH}/git-http-backend>
        Options ExecCGI
index 20e28e34e7dc63aaf9ee9c6bdabcf311e0240615..538ea5fb1c27bf5e4f9c4a0268f219f5d6d6e9c5 100755 (executable)
@@ -82,6 +82,9 @@ test_expect_success 'credential_fill passes along metadata' '
        host=example.com
        path=foo.git
        --
+       protocol=ftp
+       host=example.com
+       path=foo.git
        username=one
        password=two
        --
@@ -213,6 +216,8 @@ test_expect_success 'match configured credential' '
        host=example.com
        path=repo.git
        --
+       protocol=https
+       host=example.com
        username=foo
        password=bar
        --
@@ -225,6 +230,8 @@ test_expect_success 'do not match configured credential' '
        protocol=https
        host=bar
        --
+       protocol=https
+       host=bar
        username=askpass-username
        password=askpass-password
        --
@@ -239,6 +246,8 @@ test_expect_success 'pull username from config' '
        protocol=https
        host=example.com
        --
+       protocol=https
+       host=example.com
        username=foo
        password=askpass-password
        --
@@ -252,6 +261,8 @@ test_expect_success 'http paths can be part of context' '
        host=example.com
        path=foo.git
        --
+       protocol=https
+       host=example.com
        username=foo
        password=bar
        --
@@ -265,6 +276,9 @@ test_expect_success 'http paths can be part of context' '
        host=example.com
        path=foo.git
        --
+       protocol=https
+       host=example.com
+       path=foo.git
        username=foo
        password=bar
        --
index 55ed955ceffee9184b5822054697f58e7d0ef6a4..fd105280092b7f655f5c48f09b2e8f5e0ee6d351 100755 (executable)
@@ -130,10 +130,27 @@ test_expect_success 'git-show a large file' '
 
 '
 
+test_expect_success 'index-pack' '
+       git clone file://"`pwd`"/.git foo &&
+       GIT_DIR=non-existent git index-pack --strict --verify foo/.git/objects/pack/*.pack
+'
+
 test_expect_success 'repack' '
        git repack -ad
 '
 
+test_expect_success 'pack-objects with large loose object' '
+       SHA1=`git hash-object huge` &&
+       test_create_repo loose &&
+       echo $SHA1 | git pack-objects --stdout |
+               GIT_ALLOC_LIMIT=0 GIT_DIR=loose/.git git unpack-objects &&
+       echo $SHA1 | GIT_DIR=loose/.git git pack-objects pack &&
+       test_create_repo packed &&
+       mv pack-* packed/.git/objects/pack &&
+       GIT_DIR=packed/.git git cat-file blob $SHA1 >actual &&
+       cmp huge actual
+'
+
 test_expect_success 'tar achiving' '
        git archive --format=tar HEAD >/dev/null
 '
index c4414ff576fc03b3234c532bfdcd0f2c8eca9c09..a3b77239f48ade053c3514c9ba65cd72286d0783 100755 (executable)
@@ -7,6 +7,9 @@ test_description='git commit-tree options test
 
 This test checks that git commit-tree can create a specific commit
 object by defining all environment variables that it understands.
+
+Also make sure that command line parser understands the normal
+"flags first and then non flag arguments" command line.
 '
 
 . ./test-lib.sh
@@ -42,4 +45,18 @@ test_expect_success \
     'compare commit' \
     'test_cmp expected commit'
 
+
+test_expect_success 'flags and then non flags' '
+       echo comment text |
+       git commit-tree $(cat treeid) >commitid &&
+       echo comment text |
+       git commit-tree $(cat treeid) -p $(cat commitid) >childid-1 &&
+       echo comment text |
+       git commit-tree -p $(cat commitid) $(cat treeid) >childid-2 &&
+       test_cmp childid-1 childid-2 &&
+       git commit-tree $(cat treeid) -m foo >childid-3 &&
+       git commit-tree -m foo $(cat treeid) >childid-4 &&
+       test_cmp childid-3 childid-4
+'
+
 test_done
index 2b962cfda70d998e9c2799cf8fc720b330cd118a..79045abb5171691246167a42eec6bf18fbeb0b76 100755 (executable)
@@ -14,16 +14,15 @@ umask 077
 # We need an arbitrary other user give permission to using ACLs. root
 # is a good candidate: exists on all unices, and it has permission
 # anyway, so we don't create a security hole running the testsuite.
-
-setfacl_out="$(setfacl -m u:root:rwx . 2>&1)"
-setfacl_ret=$?
-
-if test $setfacl_ret != 0
-then
-       say "Unable to use setfacl (output: '$setfacl_out'; return code: '$setfacl_ret')"
-else
-       test_set_prereq SETFACL
-fi
+test_expect_success 'checking for a working acl setup' '
+       if setfacl -m d:m:rwx -m u:root:rwx . &&
+          getfacl . | grep user:root:rwx &&
+          touch should-have-readable-acl &&
+          getfacl should-have-readable-acl | egrep "mask::?rw-"
+       then
+               test_set_prereq SETFACL
+       fi
+'
 
 if test -z "$LOGNAME"
 then
diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh
new file mode 100755 (executable)
index 0000000..3c75c3f
--- /dev/null
@@ -0,0 +1,158 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Valentin Duperray, Lucien Kong, Franck Jonas,
+#                   Thomas Nguy, Khoi Nguyen
+#                   Grenoble INP Ensimag
+#
+
+test_description='Compatibility with $XDG_CONFIG_HOME/git/ files'
+
+. ./test-lib.sh
+
+test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' '
+       mkdir -p .config/git &&
+       echo "[alias]" >.config/git/config &&
+       echo "  myalias = !echo in_config" >>.config/git/config &&
+       echo in_config >expected &&
+       git myalias >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'read config: xdg file exists and ~/.gitconfig exists' '
+       >.gitconfig &&
+       echo "[alias]" >.gitconfig &&
+       echo "  myalias = !echo in_gitconfig" >>.gitconfig &&
+       echo in_gitconfig >expected &&
+       git myalias >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'read with --get: xdg file exists and ~/.gitconfig doesn'\''t' '
+       rm .gitconfig &&
+       echo "[user]" >.config/git/config &&
+       echo "  name = read_config" >>.config/git/config &&
+       echo read_config >expected &&
+       git config --get user.name >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'read with --get: xdg file exists and ~/.gitconfig exists' '
+       >.gitconfig &&
+       echo "[user]" >.gitconfig &&
+       echo "  name = read_gitconfig" >>.gitconfig &&
+       echo read_gitconfig >expected &&
+       git config --get user.name >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'read with --list: xdg file exists and ~/.gitconfig doesn'\''t' '
+       rm .gitconfig &&
+       echo user.name=read_config >expected &&
+       git config --global --list >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists' '
+       >.gitconfig &&
+       echo "[user]" >.gitconfig &&
+       echo "  name = read_gitconfig" >>.gitconfig &&
+       echo user.name=read_gitconfig >expected &&
+       git config --global --list >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'Setup' '
+       git init git &&
+       cd git &&
+       echo foo >to_be_excluded
+'
+
+
+test_expect_success 'Exclusion of a file in the XDG ignore file' '
+       mkdir -p "$HOME"/.config/git/ &&
+       echo to_be_excluded >"$HOME"/.config/git/ignore &&
+       test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Exclusion in both XDG and local ignore files' '
+       echo to_be_excluded >.gitignore &&
+       test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Exclusion in a non-XDG global ignore file' '
+       rm .gitignore &&
+       echo >"$HOME"/.config/git/ignore &&
+       echo to_be_excluded >"$HOME"/my_gitignore &&
+       git config core.excludesfile "$HOME"/my_gitignore &&
+       test_must_fail git add to_be_excluded
+'
+
+
+test_expect_success 'Checking attributes in the XDG attributes file' '
+       echo foo >f &&
+       git check-attr -a f >actual &&
+       test_line_count -eq 0 actual &&
+       echo "f attr_f" >"$HOME"/.config/git/attributes &&
+       echo "f: attr_f: set" >expected &&
+       git check-attr -a f >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'Checking attributes in both XDG and local attributes files' '
+       echo "f -attr_f" >.gitattributes &&
+       echo "f: attr_f: unset" >expected &&
+       git check-attr -a f >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'Checking attributes in a non-XDG global attributes file' '
+       test_might_fail rm .gitattributes &&
+       echo "f attr_f=test" >"$HOME"/my_gitattributes &&
+       git config core.attributesfile "$HOME"/my_gitattributes &&
+       echo "f: attr_f: test" >expected &&
+       git check-attr -a f >actual &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'write: xdg file exists and ~/.gitconfig doesn'\''t' '
+       mkdir -p "$HOME"/.config/git &&
+       >"$HOME"/.config/git/config &&
+       test_might_fail rm "$HOME"/.gitconfig &&
+       git config --global user.name "write_config" &&
+       echo "[user]" >expected &&
+       echo "  name = write_config" >>expected &&
+       test_cmp expected "$HOME"/.config/git/config
+'
+
+
+test_expect_success 'write: xdg file exists and ~/.gitconfig exists' '
+       >"$HOME"/.gitconfig &&
+       git config --global user.name "write_gitconfig" &&
+       echo "[user]" >expected &&
+       echo "  name = write_gitconfig" >>expected &&
+       test_cmp expected "$HOME"/.gitconfig
+'
+
+
+test_expect_success 'write: ~/.config/git/ exists and config file doesn'\''t' '
+       test_might_fail rm "$HOME"/.gitconfig &&
+       test_might_fail rm "$HOME"/.config/git/config &&
+       git config --global user.name "write_gitconfig" &&
+       echo "[user]" >expected &&
+       echo "  name = write_gitconfig" >>expected &&
+       test_cmp expected "$HOME"/.gitconfig
+'
+
+
+test_done
diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh
new file mode 100755 (executable)
index 0000000..6b3d797
--- /dev/null
@@ -0,0 +1,264 @@
+#!/bin/sh
+
+test_description='object name disambiguation
+
+Create blobs, trees, commits and a tag that all share the same
+prefix, and make sure "git rev-parse" can take advantage of
+type information to disambiguate short object names that are
+not necessarily unique.
+
+The final history used in the test has five commits, with the bottom
+one tagged as v1.0.0.  They all have one regular file each.
+
+  +-------------------------------------------+
+  |                                           |
+  |           .-------b3wettvi---- ad2uee     |
+  |          /                   /            |
+  |  a2onsxbvj---czy8f73t--ioiley5o           |
+  |                                           |
+  +-------------------------------------------+
+
+'
+
+. ./test-lib.sh
+
+test_expect_success 'blob and tree' '
+       test_tick &&
+       (
+               for i in 0 1 2 3 4 5 6 7 8 9
+               do
+                       echo $i
+               done
+               echo
+               echo b1rwzyc3
+       ) >a0blgqsjc &&
+
+       # create one blob 0000000000b36
+       git add a0blgqsjc &&
+
+       # create one tree 0000000000cdc
+       git write-tree
+'
+
+test_expect_success 'warn ambiguity when no candidate matches type hint' '
+       test_must_fail git rev-parse --verify 000000000^{commit} 2>actual &&
+       grep "short SHA1 000000000 is ambiguous" actual
+'
+
+test_expect_success 'disambiguate tree-ish' '
+       # feed tree-ish in an unambiguous way
+       git rev-parse --verify 0000000000cdc:a0blgqsjc &&
+
+       # ambiguous at the object name level, but there is only one
+       # such tree-ish (the other is a blob)
+       git rev-parse --verify 000000000:a0blgqsjc
+'
+
+test_expect_success 'disambiguate blob' '
+       sed -e "s/|$//" >patch <<-EOF &&
+       diff --git a/frotz b/frotz
+       index 000000000..ffffff 100644
+       --- a/frotz
+       +++ b/frotz
+       @@ -10,3 +10,4 @@
+        9
+        |
+        b1rwzyc3
+       +irwry
+       EOF
+       (
+               GIT_INDEX_FILE=frotz &&
+               export GIT_INDEX_FILE &&
+               git apply --build-fake-ancestor frotz patch &&
+               git cat-file blob :frotz >actual
+       ) &&
+       test_cmp a0blgqsjc actual
+'
+
+test_expect_success 'disambiguate tree' '
+       commit=$(echo "d7xm" | git commit-tree 000000000) &&
+       test $(git rev-parse $commit^{tree}) = $(git rev-parse 0000000000cdc)
+'
+
+test_expect_success 'first commit' '
+       # create one commit 0000000000e4f
+       git commit -m a2onsxbvj
+'
+
+test_expect_success 'disambiguate commit-ish' '
+       # feed commit-ish in an unambiguous way
+       git rev-parse --verify 0000000000e4f^{commit} &&
+
+       # ambiguous at the object name level, but there is only one
+       # such commit (the others are tree and blob)
+       git rev-parse --verify 000000000^{commit} &&
+
+       # likewise
+       git rev-parse --verify 000000000^0
+'
+
+test_expect_success 'disambiguate commit' '
+       commit=$(echo "hoaxj" | git commit-tree 0000000000cdc -p 000000000) &&
+       test $(git rev-parse $commit^) = $(git rev-parse 0000000000e4f)
+'
+
+test_expect_success 'log name1..name2 takes only commit-ishes on both ends' '
+       git log 000000000..000000000 &&
+       git log ..000000000 &&
+       git log 000000000.. &&
+       git log 000000000...000000000 &&
+       git log ...000000000 &&
+       git log 000000000...
+'
+
+test_expect_success 'rev-parse name1..name2 takes only commit-ishes on both ends' '
+       git rev-parse 000000000..000000000 &&
+       git rev-parse ..000000000 &&
+       git rev-parse 000000000..
+'
+
+test_expect_success 'git log takes only commit-ish' '
+       git log 000000000
+'
+
+test_expect_success 'git reset takes only commit-ish' '
+       git reset 000000000
+'
+
+test_expect_success 'first tag' '
+       # create one tag 0000000000f8f
+       git tag -a -m j7cp83um v1.0.0
+'
+
+test_expect_failure 'two semi-ambiguous commit-ish' '
+       # Once the parser becomes ultra-smart, it could notice that
+       # 110282 before ^{commit} name many different objects, but
+       # that only two (HEAD and v1.0.0 tag) can be peeled to commit,
+       # and that peeling them down to commit yield the same commit
+       # without ambiguity.
+       git rev-parse --verify 110282^{commit} &&
+
+       # likewise
+       git log 000000000..000000000 &&
+       git log ..000000000 &&
+       git log 000000000.. &&
+       git log 000000000...000000000 &&
+       git log ...000000000 &&
+       git log 000000000...
+'
+
+test_expect_failure 'three semi-ambiguous tree-ish' '
+       # Likewise for tree-ish.  HEAD, v1.0.0 and HEAD^{tree} share
+       # the prefix but peeling them to tree yields the same thing
+       git rev-parse --verify 000000000^{tree}
+'
+
+test_expect_success 'parse describe name' '
+       # feed an unambiguous describe name
+       git rev-parse --verify v1.0.0-0-g0000000000e4f &&
+
+       # ambiguous at the object name level, but there is only one
+       # such commit (others are blob, tree and tag)
+       git rev-parse --verify v1.0.0-0-g000000000
+'
+
+test_expect_success 'more history' '
+       # commit 0000000000043
+       git mv a0blgqsjc d12cr3h8t &&
+       echo h62xsjeu >>d12cr3h8t &&
+       git add d12cr3h8t &&
+
+       test_tick &&
+       git commit -m czy8f73t &&
+
+       # commit 00000000008ec
+       git mv d12cr3h8t j000jmpzn &&
+       echo j08bekfvt >>j000jmpzn &&
+       git add j000jmpzn &&
+
+       test_tick &&
+       git commit -m ioiley5o &&
+
+       # commit 0000000005b0
+       git checkout v1.0.0^0 &&
+       git mv a0blgqsjc f5518nwu &&
+
+       for i in h62xsjeu j08bekfvt kg7xflhm
+       do
+               echo $i
+       done >>f5518nwu &&
+       git add f5518nwu &&
+
+       test_tick &&
+       git commit -m b3wettvi &&
+       side=$(git rev-parse HEAD) &&
+
+       # commit 000000000066
+       git checkout master &&
+
+       # If you use recursive, merge will fail and you will need to
+       # clean up a0blgqsjc as well.  If you use resolve, merge will
+       # succeed.
+       test_might_fail git merge --no-commit -s recursive $side &&
+       git rm -f f5518nwu j000jmpzn &&
+
+       test_might_fail git rm -f a0blgqsjc &&
+       (
+               git cat-file blob $side:f5518nwu
+               echo j3l0i9s6
+       ) >ab2gs879 &&
+       git add ab2gs879 &&
+
+       test_tick &&
+       git commit -m ad2uee
+
+'
+
+test_expect_failure 'parse describe name taking advantage of generation' '
+       # ambiguous at the object name level, but there is only one
+       # such commit at generation 0
+       git rev-parse --verify v1.0.0-0-g000000000 &&
+
+       # likewise for generation 2 and 4
+       git rev-parse --verify v1.0.0-2-g000000000 &&
+       git rev-parse --verify v1.0.0-4-g000000000
+'
+
+# Note: because rev-parse does not even try to disambiguate based on
+# the generation number, this test currently succeeds for a wrong
+# reason.  When it learns to use the generation number, the previous
+# test should succeed, and also this test should fail because the
+# describe name used in the test with generation number can name two
+# commits.  Make sure that such a future enhancement does not randomly
+# pick one.
+test_expect_success 'parse describe name not ignoring ambiguity' '
+       # ambiguous at the object name level, and there are two such
+       # commits at generation 1
+       test_must_fail git rev-parse --verify v1.0.0-1-g000000000
+'
+
+test_expect_success 'ambiguous commit-ish' '
+       # Now there are many commits that begin with the
+       # common prefix, none of these should pick one at
+       # random.  They all should result in ambiguity errors.
+       test_must_fail git rev-parse --verify 110282^{commit} &&
+
+       # likewise
+       test_must_fail git log 000000000..000000000 &&
+       test_must_fail git log ..000000000 &&
+       test_must_fail git log 000000000.. &&
+       test_must_fail git log 000000000...000000000 &&
+       test_must_fail git log ...000000000 &&
+       test_must_fail git log 000000000...
+'
+
+test_expect_success 'rev-parse --disambiguate' '
+       # The test creates 16 objects that share the prefix and two
+       # commits created by commit-tree in earlier tests share a
+       # different prefix.
+       git rev-parse --disambiguate=000000000 >actual &&
+       test $(wc -l <actual) = 16 &&
+       test "$(sed -e "s/^\(.........\).*/\1/" actual | sort -u)" = 000000000
+'
+
+test_done
index 025c1c610efee9562343b34a9c815f34f51ed9ac..8078db6856ba4428e9d6b13f5e02076da7963b09 100755 (executable)
@@ -755,4 +755,152 @@ test_expect_success 'rebase-i history with funny messages' '
        test_cmp expect actual
 '
 
+
+test_expect_success 'prepare for rebase -i --exec' '
+       git checkout master &&
+       git checkout -b execute &&
+       test_commit one_exec main.txt one_exec &&
+       test_commit two_exec main.txt two_exec &&
+       test_commit three_exec main.txt three_exec
+'
+
+
+test_expect_success 'running "git rebase -i --exec git show HEAD"' '
+       git rebase -i --exec "git show HEAD" HEAD~2 >actual &&
+       (
+               FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
+               export FAKE_LINES &&
+               git rebase -i HEAD~2 >expect
+       ) &&
+       sed -e "1,9d" expect >expected &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'running "git rebase --exec git show HEAD -i"' '
+       git reset --hard execute &&
+       git rebase --exec "git show HEAD" -i HEAD~2 >actual &&
+       (
+               FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
+               export FAKE_LINES &&
+               git rebase -i HEAD~2 >expect
+       ) &&
+       sed -e "1,9d" expect >expected &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'running "git rebase -ix git show HEAD"' '
+       git reset --hard execute &&
+       git rebase -ix "git show HEAD" HEAD~2 >actual &&
+       (
+               FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
+               export FAKE_LINES &&
+               git rebase -i HEAD~2 >expect
+       ) &&
+       sed -e "1,9d" expect >expected &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'rebase -ix with several <CMD>' '
+       git reset --hard execute &&
+       git rebase -ix "git show HEAD; pwd" HEAD~2 >actual &&
+       (
+               FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" &&
+               export FAKE_LINES &&
+               git rebase -i HEAD~2 >expect
+       ) &&
+       sed -e "1,9d" expect >expected &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'rebase -ix with several instances of --exec' '
+       git reset --hard execute &&
+       git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual &&
+       (
+               FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2
+                               exec_git_show_HEAD exec_pwd" &&
+               export FAKE_LINES &&
+               git rebase -i HEAD~2 >expect
+       ) &&
+       sed -e "1,11d" expect >expected &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'rebase -ix with --autosquash' '
+       git reset --hard execute &&
+       git checkout -b autosquash &&
+       echo second >second.txt &&
+       git add second.txt &&
+       git commit -m "fixup! two_exec" &&
+       echo bis >bis.txt &&
+       git add bis.txt &&
+       git commit -m "fixup! two_exec" &&
+       (
+               git checkout -b autosquash_actual &&
+               git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual
+       ) &&
+       git checkout autosquash &&
+       (
+               git checkout -b autosquash_expected &&
+               FAKE_LINES="1 fixup 3 fixup 4 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
+               export FAKE_LINES &&
+               git rebase -i HEAD~4 >expect
+       ) &&
+       sed -e "1,13d" expect >expected &&
+       test_cmp expected actual
+'
+
+
+test_expect_success 'rebase --exec without -i shows error message' '
+       git reset --hard execute &&
+       test_must_fail git rebase --exec "git show HEAD" HEAD~2 2>actual &&
+       echo "--exec option must be used with --interactive option" >expected &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'rebase -i --exec without <CMD>' '
+       git reset --hard execute &&
+       test_must_fail git rebase -i --exec 2>tmp &&
+       sed -e "1d" tmp >actual &&
+       test_must_fail git rebase -h >expected &&
+       test_cmp expected actual &&
+       git checkout master
+'
+
+test_expect_success 'rebase -i --root re-order and drop commits' '
+       git checkout E &&
+       FAKE_LINES="3 1 2 5" git rebase -i --root &&
+       test E = $(git cat-file commit HEAD | sed -ne \$p) &&
+       test B = $(git cat-file commit HEAD^ | sed -ne \$p) &&
+       test A = $(git cat-file commit HEAD^^ | sed -ne \$p) &&
+       test C = $(git cat-file commit HEAD^^^ | sed -ne \$p) &&
+       test 0 = $(git cat-file commit HEAD^^^ | grep -c ^parent\ )
+'
+
+test_expect_success 'rebase -i --root retain root commit author and message' '
+       git checkout A &&
+       echo B >file7 &&
+       git add file7 &&
+       GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
+       FAKE_LINES="2" git rebase -i --root &&
+       git cat-file commit HEAD | grep -q "^author Twerp Snog" &&
+       git cat-file commit HEAD | grep -q "^different author$"
+'
+
+test_expect_success 'rebase -i --root temporary sentinel commit' '
+       git checkout B &&
+       (
+               FAKE_LINES="2" &&
+               export FAKE_LINES &&
+               test_must_fail git rebase -i --root
+       ) &&
+       git cat-file commit HEAD | grep "^tree 4b825dc642cb" &&
+       git rebase --abort
+'
+
 test_done
index e5ad67c643ffee9b79fce813673732faa950714f..19eddadcf734951b22246c6dd0a16d517f111b5b 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='rebase should not insist on git message convention'
+test_description='rebase should handle arbitrary git message'
 
 . ./test-lib.sh
 
@@ -12,6 +12,11 @@ It has two paragraphs, but its first paragraph is not friendly
 to oneline summary format.
 EOF
 
+cat >G <<\EOF
+commit log message containing a diff
+EOF
+
+
 test_expect_success setup '
 
        >file1 &&
@@ -19,8 +24,9 @@ test_expect_success setup '
        git add file1 file2 &&
        test_tick &&
        git commit -m "Initial commit" &&
+       git branch diff-in-message
 
-       git checkout -b side &&
+       git checkout -b multi-line-subject &&
        cat F >file2 &&
        git add file2 &&
        test_tick &&
@@ -28,6 +34,17 @@ test_expect_success setup '
 
        git cat-file commit HEAD | sed -e "1,/^\$/d" >F0 &&
 
+       git checkout diff-in-message &&
+       echo "commit log message containing a diff" >G &&
+       echo "" >>G
+       cat G >file2 &&
+       git add file2 &&
+       git diff --cached >>G &&
+       test_tick &&
+       git commit -F G &&
+
+       git cat-file commit HEAD | sed -e "1,/^\$/d" >G0 &&
+
        git checkout master &&
 
        echo One >file1 &&
@@ -36,13 +53,20 @@ test_expect_success setup '
        git commit -m "Second commit"
 '
 
-test_expect_success rebase '
+test_expect_success 'rebase commit with multi-line subject' '
 
-       git rebase master side &&
+       git rebase master multi-line-subject &&
        git cat-file commit HEAD | sed -e "1,/^\$/d" >F1 &&
 
        test_cmp F0 F1 &&
        test_cmp F F0
 '
 
+test_expect_success 'rebase commit with diff in message' '
+       git rebase master diff-in-message &&
+       git cat-file commit HEAD | sed -e "1,/^$/d" >G1 &&
+       test_cmp G0 G1 &&
+       test_cmp G G0
+'
+
 test_done
index 086c91c7b47aa2fa7b593f4f9b2f84f6b7ba3724..0b521057283bf106da8ce55f25f61c7ec7e3ad35 100755 (executable)
@@ -22,8 +22,9 @@ test_expect_success 'prepare repository' '
        test_commit 4 B
 '
 
-test_expect_success 'rebase --root expects --onto' '
-       test_must_fail git rebase --root
+test_expect_success 'rebase --root fails with too many args' '
+       git checkout -B fail other &&
+       test_must_fail git rebase --onto master --root fail fail
 '
 
 test_expect_success 'setup pre-rebase hook' '
@@ -42,7 +43,7 @@ cat > expect <<EOF
 EOF
 
 test_expect_success 'rebase --root --onto <newbase>' '
-       git checkout -b work &&
+       git checkout -b work other &&
        git rebase --root --onto master &&
        git log --pretty=tformat:"%s" > rebased &&
        test_cmp expect rebased
diff --git a/t/t3910-mac-os-precompose.sh b/t/t3910-mac-os-precompose.sh
new file mode 100755 (executable)
index 0000000..88b7a20
--- /dev/null
@@ -0,0 +1,164 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Torsten Bögershausen
+#
+
+test_description='utf-8 decomposed (nfd) converted to precomposed (nfc)'
+
+. ./test-lib.sh
+
+Adiarnfc=`printf '\303\204'`
+Adiarnfd=`printf 'A\314\210'`
+
+# check if the feature is compiled in
+mkdir junk &&
+>junk/"$Adiarnfc" &&
+case "$(cd junk && echo *)" in
+       "$Adiarnfd")
+       test_nfd=1
+       ;;
+       *)      ;;
+esac
+rm -rf junk
+
+
+if test "$test_nfd"
+then
+       # create more utf-8 variables
+       Odiarnfc=`printf '\303\226'`
+       Odiarnfd=`printf 'O\314\210'`
+       AEligatu=`printf '\303\206'`
+       Invalidu=`printf '\303\377'`
+
+
+       #Create a string with 255 bytes (decomposed)
+       Alongd=$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd$Adiarnfd #21 Byte
+       Alongd=$Alongd$Alongd$Alongd                                           #63 Byte
+       Alongd=$Alongd$Alongd$Alongd$Alongd$Adiarnfd                           #255 Byte
+
+       #Create a string with 254 bytes (precomposed)
+       Alongc=$AEligatu$AEligatu$AEligatu$AEligatu$AEligatu #10 Byte
+       Alongc=$Alongc$Alongc$Alongc$Alongc$Alongc           #50 Byte
+       Alongc=$Alongc$Alongc$Alongc$Alongc$Alongc           #250 Byte
+       Alongc=$Alongc$AEligatu$AEligatu                     #254 Byte
+
+       test_expect_success "detect if nfd needed" '
+               precomposeunicode=`git config core.precomposeunicode` &&
+               test "$precomposeunicode" = false &&
+               git config core.precomposeunicode true
+       '
+       test_expect_success "setup" '
+               >x &&
+               git add x &&
+               git commit -m "1st commit" &&
+               git rm x &&
+               git commit -m "rm x"
+       '
+       test_expect_success "setup case mac" '
+               git checkout -b mac_os
+       '
+       # This will test nfd2nfc in readdir()
+       test_expect_success "add file Adiarnfc" '
+               echo f.Adiarnfc >f.$Adiarnfc &&
+               git add f.$Adiarnfc &&
+               git commit -m "add f.$Adiarnfc"
+       '
+       # This will test nfd2nfc in git stage()
+       test_expect_success "stage file d.Adiarnfd/f.Adiarnfd" '
+               mkdir d.$Adiarnfd &&
+               echo d.$Adiarnfd/f.$Adiarnfd >d.$Adiarnfd/f.$Adiarnfd &&
+               git stage d.$Adiarnfd/f.$Adiarnfd &&
+               git commit -m "add d.$Adiarnfd/f.$Adiarnfd"
+       '
+       test_expect_success "add link Adiarnfc" '
+               ln -s d.$Adiarnfd/f.$Adiarnfd l.$Adiarnfc &&
+               git add l.$Adiarnfc &&
+               git commit -m "add l.Adiarnfc"
+       '
+       # This will test git log
+       test_expect_success "git log f.Adiar" '
+               git log f.$Adiarnfc > f.Adiarnfc.log &&
+               git log f.$Adiarnfd > f.Adiarnfd.log &&
+               test -s f.Adiarnfc.log &&
+               test -s f.Adiarnfd.log &&
+               test_cmp f.Adiarnfc.log f.Adiarnfd.log &&
+               rm f.Adiarnfc.log f.Adiarnfd.log
+       '
+       # This will test git ls-files
+       test_expect_success "git lsfiles f.Adiar" '
+               git ls-files f.$Adiarnfc > f.Adiarnfc.log &&
+               git ls-files f.$Adiarnfd > f.Adiarnfd.log &&
+               test -s f.Adiarnfc.log &&
+               test -s f.Adiarnfd.log &&
+               test_cmp f.Adiarnfc.log f.Adiarnfd.log &&
+               rm f.Adiarnfc.log f.Adiarnfd.log
+       '
+       # This will test git mv
+       test_expect_success "git mv" '
+               git mv f.$Adiarnfd f.$Odiarnfc &&
+               git mv d.$Adiarnfd d.$Odiarnfc &&
+               git mv l.$Adiarnfd l.$Odiarnfc &&
+               git commit -m "mv Adiarnfd Odiarnfc"
+       '
+       # Files can be checked out as nfc
+       # And the link has been corrected from nfd to nfc
+       test_expect_success "git checkout nfc" '
+               rm f.$Odiarnfc &&
+               git checkout f.$Odiarnfc
+       '
+       # Make it possible to checkout files with their NFD names
+       test_expect_success "git checkout file nfd" '
+               rm -f f.* &&
+               git checkout f.$Odiarnfd
+       '
+       # Make it possible to checkout links with their NFD names
+       test_expect_success "git checkout link nfd" '
+               rm l.* &&
+               git checkout l.$Odiarnfd
+       '
+       test_expect_success "setup case mac2" '
+               git checkout master &&
+               git reset --hard &&
+               git checkout -b mac_os_2
+       '
+       # This will test nfd2nfc in git commit
+       test_expect_success "commit file d2.Adiarnfd/f.Adiarnfd" '
+               mkdir d2.$Adiarnfd &&
+               echo d2.$Adiarnfd/f.$Adiarnfd >d2.$Adiarnfd/f.$Adiarnfd &&
+               git add d2.$Adiarnfd/f.$Adiarnfd &&
+               git commit -m "add d2.$Adiarnfd/f.$Adiarnfd" -- d2.$Adiarnfd/f.$Adiarnfd
+       '
+       test_expect_success "setup for long decomposed filename" '
+               git checkout master &&
+               git reset --hard &&
+               git checkout -b mac_os_long_nfd_fn
+       '
+       test_expect_success "Add long decomposed filename" '
+               echo longd >$Alongd &&
+               git add * &&
+               git commit -m "Long filename"
+       '
+       test_expect_success "setup for long precomposed filename" '
+               git checkout master &&
+               git reset --hard &&
+               git checkout -b mac_os_long_nfc_fn
+       '
+       test_expect_success "Add long precomposed filename" '
+               echo longc >$Alongc &&
+               git add * &&
+               git commit -m "Long filename"
+       '
+       # Test if the global core.precomposeunicode stops autosensing
+       # Must be the last test case
+       test_expect_success "respect git config --global core.precomposeunicode" '
+               git config --global core.precomposeunicode true &&
+               rm -rf .git &&
+               git init &&
+               precomposeunicode=`git config core.precomposeunicode` &&
+               test "$precomposeunicode" = "true"
+       '
+else
+        say "Skipping nfc/nfd tests"
+fi
+
+test_done
index 6cebb3951bc7f4e9b772efed052c485d82d1a68d..ec4deea1923919a88c6f1f9d35e11cb7737e323d 100755 (executable)
@@ -15,13 +15,14 @@ cat >expect.binary-numstat <<\EOF
 -      -       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 &&
-        echo git >a &&
-        cat "$TEST_DIRECTORY"/test-binary-1.png >b &&
-        echo git >c &&
-        cat b b >d'
+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 &&
+       echo git >a &&
+       cat "$TEST_DIRECTORY"/test-binary-1.png >b &&
+       echo git >c &&
+       cat b b >d
+'
 
 cat > expected <<\EOF
  a |    2 +-
@@ -30,16 +31,16 @@ cat > expected <<\EOF
  d |  Bin
  4 files changed, 2 insertions(+), 2 deletions(-)
 EOF
-test_expect_success '"apply --stat" output for binary file change' '
+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 'diff --shortstat output for binary file change' '
-       echo " 4 files changed, 2 insertions(+), 2 deletions(-)" >expected &&
+       tail -n 1 expected >expect &&
        git diff --shortstat >current &&
-       test_i18ncmp expected current
+       test_i18ncmp expect current
 '
 
 test_expect_success 'diff --shortstat output for binary file change only' '
@@ -62,49 +63,42 @@ test_expect_success 'apply --numstat understands diff --binary format' '
 
 # apply needs to be able to skip the binary material correctly
 # in order to report the line number of a corrupt patch.
-test_expect_success 'apply detecting corrupt patch correctly' \
-       'git diff | sed -e 's/-CIT/xCIT/' >broken &&
-        if git apply --stat --summary broken 2>detected
-        then
-               echo unhappy - should have detected an error
-               (exit 1)
-        else
-               echo happy
-        fi &&
-        detected=`cat detected` &&
-        detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
-        detected=`sed -ne "${detected}p" broken` &&
-        test "$detected" = xCIT'
-
-test_expect_success 'apply detecting corrupt patch correctly' \
-       'git diff --binary | sed -e 's/-CIT/xCIT/' >broken &&
-        if git apply --stat --summary broken 2>detected
-        then
-               echo unhappy - should have detected an error
-               (exit 1)
-        else
-               echo happy
-        fi &&
-        detected=`cat detected` &&
-        detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
-        detected=`sed -ne "${detected}p" broken` &&
-        test "$detected" = xCIT'
+test_expect_success 'apply detecting corrupt patch correctly' '
+       git diff >output &&
+       sed -e "s/-CIT/xCIT/" <output >broken &&
+       test_must_fail git apply --stat --summary broken 2>detected &&
+       detected=`cat detected` &&
+       detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
+       detected=`sed -ne "${detected}p" broken` &&
+       test "$detected" = xCIT
+'
+
+test_expect_success 'apply detecting corrupt patch correctly' '
+       git diff --binary | sed -e "s/-CIT/xCIT/" >broken &&
+       test_must_fail git apply --stat --summary broken 2>detected &&
+       detected=`cat detected` &&
+       detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
+       detected=`sed -ne "${detected}p" broken` &&
+       test "$detected" = xCIT
+'
 
 test_expect_success 'initial commit' 'git commit -a -m initial'
 
 # Try removal (b), modification (d), and creation (e).
-test_expect_success 'diff-index with --binary' \
-       'echo AIT >a && mv b e && echo CIT >c && cat e >d &&
-        git update-index --add --remove a b c d e &&
-        tree0=`git write-tree` &&
-        git diff --cached --binary >current &&
-        git apply --stat --summary current'
-
-test_expect_success 'apply binary patch' \
-       'git reset --hard &&
-        git apply --binary --index <current &&
-        tree1=`git write-tree` &&
-        test "$tree1" = "$tree0"'
+test_expect_success 'diff-index with --binary' '
+       echo AIT >a && mv b e && echo CIT >c && cat e >d &&
+       git update-index --add --remove a b c d e &&
+       tree0=`git write-tree` &&
+       git diff --cached --binary >current &&
+       git apply --stat --summary current
+'
+
+test_expect_success 'apply binary patch' '
+       git reset --hard &&
+       git apply --binary --index <current &&
+       tree1=`git write-tree` &&
+       test "$tree1" = "$tree0"
+'
 
 test_expect_success 'diff --no-index with binary creation' '
        echo Q | q_to_nul >binary &&
@@ -125,7 +119,7 @@ cat >expect <<EOF
 EOF
 
 test_expect_success 'diff --stat with binary files and big change count' '
-       echo X | dd of=binfile bs=1k seek=1 &&
+       printf "\01\00%1024d" 1 >binfile &&
        git add binfile &&
        i=0 &&
        while test $i -lt 10000; do
index 533afc1185114e8ea7e7e5e9315ceaf7816e32c2..2e7d73f0906e6a2808706236328470b2d855e9f9 100755 (executable)
@@ -48,7 +48,53 @@ test_expect_success 'GIT_EXTERNAL_DIFF environment and --no-ext-diff' '
 
 '
 
+test_expect_success SYMLINKS 'typechange diff' '
+       rm -f file &&
+       ln -s elif file &&
+       GIT_EXTERNAL_DIFF=echo git diff  | {
+               read path oldfile oldhex oldmode newfile newhex newmode &&
+               test "z$path" = zfile &&
+               test "z$oldmode" = z100644 &&
+               test "z$newhex" = "z$_z40" &&
+               test "z$newmode" = z120000 &&
+               oh=$(git rev-parse --verify HEAD:file) &&
+               test "z$oh" = "z$oldhex"
+       } &&
+       GIT_EXTERNAL_DIFF=echo git diff --no-ext-diff >actual &&
+       git diff >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'diff.external' '
+       git reset --hard &&
+       echo third >file &&
+       test_config diff.external echo &&
+       git diff | {
+               read path oldfile oldhex oldmode newfile newhex newmode &&
+               test "z$path" = zfile &&
+               test "z$oldmode" = z100644 &&
+               test "z$newhex" = "z$_z40" &&
+               test "z$newmode" = z100644 &&
+               oh=$(git rev-parse --verify HEAD:file) &&
+               test "z$oh" = "z$oldhex"
+       }
+'
+
+test_expect_success 'diff.external should apply only to diff' '
+       test_config diff.external echo &&
+       git log -p -1 HEAD |
+       grep "^diff --git a/file b/file"
+'
+
+test_expect_success 'diff.external and --no-ext-diff' '
+       test_config diff.external echo &&
+       git diff --no-ext-diff |
+       grep "^diff --git a/file b/file"
+'
+
 test_expect_success 'diff attribute' '
+       git reset --hard &&
+       echo third >file &&
 
        git config diff.parrot.command echo &&
 
@@ -113,6 +159,19 @@ test_expect_success 'diff attribute and --no-ext-diff' '
 
 '
 
+test_expect_success 'GIT_EXTERNAL_DIFF trumps diff.external' '
+       >.gitattributes &&
+       test_config diff.external "echo ext-global" &&
+       GIT_EXTERNAL_DIFF="echo ext-env" git diff | grep ext-env
+'
+
+test_expect_success 'attributes trump GIT_EXTERNAL_DIFF and diff.external' '
+       test_config diff.foo.command "echo ext-attribute" &&
+       test_config diff.external "echo ext-global" &&
+       echo "file diff=foo" >.gitattributes &&
+       GIT_EXTERNAL_DIFF="echo ext-env" git diff | grep ext-attribute
+'
+
 test_expect_success 'no diff with -diff' '
        echo >.gitattributes "file -diff" &&
        git diff | grep Binary
diff --git a/t/t4108-apply-threeway.sh b/t/t4108-apply-threeway.sh
new file mode 100755 (executable)
index 0000000..fa5d4ef
--- /dev/null
@@ -0,0 +1,157 @@
+#!/bin/sh
+
+test_description='git apply --3way'
+
+. ./test-lib.sh
+
+create_file () {
+       for i
+       do
+               echo "$i"
+       done
+}
+
+sanitize_conflicted_diff () {
+       sed -e '
+               /^index /d
+               s/^\(+[<>][<>][<>][<>]*\) .*/\1/
+       '
+}
+
+test_expect_success setup '
+       test_tick &&
+       create_file >one 1 2 3 4 5 6 7 &&
+       cat one >two &&
+       git add one two &&
+       git commit -m initial &&
+
+       git branch side &&
+
+       test_tick &&
+       create_file >one 1 two 3 4 5 six 7 &&
+       create_file >two 1 two 3 4 5 6 7 &&
+       git commit -a -m master &&
+
+       git checkout side &&
+       create_file >one 1 2 3 4 five 6 7 &&
+       create_file >two 1 2 3 4 five 6 7 &&
+       git commit -a -m side &&
+
+       git checkout master
+'
+
+test_expect_success 'apply without --3way' '
+       git diff side^ side >P.diff &&
+
+       # should fail to apply
+       git reset --hard &&
+       git checkout master^0 &&
+       test_must_fail git apply --index P.diff &&
+       # should leave things intact
+       git diff-files --exit-code &&
+       git diff-index --exit-code --cached HEAD
+'
+
+test_expect_success 'apply with --3way' '
+       # Merging side should be similar to applying this patch
+       git diff ...side >P.diff &&
+
+       # The corresponding conflicted merge
+       git reset --hard &&
+       git checkout master^0 &&
+       test_must_fail git merge --no-commit side &&
+       git ls-files -s >expect.ls &&
+       git diff HEAD | sanitize_conflicted_diff >expect.diff &&
+
+       # should fail to apply
+       git reset --hard &&
+       git checkout master^0 &&
+       test_must_fail git apply --index --3way P.diff &&
+       git ls-files -s >actual.ls &&
+       git diff HEAD | sanitize_conflicted_diff >actual.diff &&
+
+       # The result should resemble the corresponding merge
+       test_cmp expect.ls actual.ls &&
+       test_cmp expect.diff actual.diff
+'
+
+test_expect_success 'apply with --3way with rerere enabled' '
+       git config rerere.enabled true &&
+
+       # Merging side should be similar to applying this patch
+       git diff ...side >P.diff &&
+
+       # The corresponding conflicted merge
+       git reset --hard &&
+       git checkout master^0 &&
+       test_must_fail git merge --no-commit side &&
+
+       # Manually resolve and record the resolution
+       create_file 1 two 3 4 five six 7 >one &&
+       git rerere &&
+       cat one >expect &&
+
+       # should fail to apply
+       git reset --hard &&
+       git checkout master^0 &&
+       test_must_fail git apply --index --3way P.diff &&
+
+       # but rerere should have replayed the recorded resolution
+       test_cmp expect one
+'
+
+test_expect_success 'apply -3 with add/add conflict setup' '
+       git reset --hard &&
+
+       git checkout -b adder &&
+       create_file 1 2 3 4 5 6 7 >three &&
+       create_file 1 2 3 4 5 6 7 >four &&
+       git add three four &&
+       git commit -m "add three and four" &&
+
+       git checkout -b another adder^ &&
+       create_file 1 2 3 4 5 6 7 >three &&
+       create_file 1 2 3 four 5 6 7 >four &&
+       git add three four &&
+       git commit -m "add three and four" &&
+
+       # Merging another should be similar to applying this patch
+       git diff adder...another >P.diff &&
+
+       git checkout adder^0 &&
+       test_must_fail git merge --no-commit another &&
+       git ls-files -s >expect.ls &&
+       git diff HEAD | sanitize_conflicted_diff >expect.diff
+'
+
+test_expect_success 'apply -3 with add/add conflict' '
+       # should fail to apply ...
+       git reset --hard &&
+       git checkout adder^0 &&
+       test_must_fail git apply --index --3way P.diff &&
+       # ... and leave conflicts in the index and in the working tree
+       git ls-files -s >actual.ls &&
+       git diff HEAD | sanitize_conflicted_diff >actual.diff &&
+
+       # The result should resemble the corresponding merge
+       test_cmp expect.ls actual.ls &&
+       test_cmp expect.diff actual.diff
+'
+
+test_expect_success 'apply -3 with add/add conflict (dirty working tree)' '
+       # should fail to apply ...
+       git reset --hard &&
+       git checkout adder^0 &&
+       echo >>four &&
+       cat four >four.save &&
+       cat three >three.save &&
+       git ls-files -s >expect.ls &&
+       test_must_fail git apply --index --3way P.diff &&
+       # ... and should not touch anything
+       git ls-files -s >actual.ls &&
+       test_cmp expect.ls actual.ls &&
+       test_cmp four.save four &&
+       test_cmp three.save three
+'
+
+test_done
index e9ccd161ee96a5bdbb4bf77de406ea51dacfb5de..8e15ecbdfdb497fcaa077fd1e6b3f882c9a46cb4 100755 (executable)
@@ -46,6 +46,14 @@ test_expect_success setup '
        cat file1 >saved.file1
 '
 
+test_expect_success 'apply --reject is incompatible with --3way' '
+       test_when_finished "cat saved.file1 >file1" &&
+       git diff >patch.0 &&
+       git checkout file1 &&
+       test_must_fail git apply --reject --3way patch.0 &&
+       git diff --exit-code
+'
+
 test_expect_success 'apply without --reject should fail' '
 
        if git apply patch.1
index 4fd69a19ebab07cc0ca784b63a7f02ddd613f306..2e52f8b83894faa0aeb324d8c43193b2cc9ea0cc 100755 (executable)
@@ -418,4 +418,9 @@ test_expect_success \
     'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg &&
      grep "SHA1 COLLISION FOUND" msg'
 
+test_expect_success \
+    'make sure index-pack detects the SHA1 collision (large blobs)' \
+    'test_must_fail git -c core.bigfilethreshold=1 index-pack -o bad.idx test-3.pack 2>msg &&
+     grep "SHA1 COLLISION FOUND" msg'
+
 test_done
index 6764d511ce08cf62da2c257bf5fe50cabaf2f03c..d16e5d384a8966bc04e9fde6e92bd41818526aab 100755 (executable)
@@ -87,17 +87,15 @@ test_expect_success 'use branch.<name>.remote if possible' '
 test_expect_success 'confuses pattern as remote when no remote specified' '
        cat >exp <<-\EOF &&
        fatal: '\''refs*master'\'' does not appear to be a git repository
-       fatal: The remote end hung up unexpectedly
+       fatal: Could not read from remote repository.
+
+       Please make sure you have the correct access rights
+       and the repository exists.
        EOF
        #
-       # Do not expect "git ls-remote <pattern>" to work; ls-remote, correctly,
-       # confuses <pattern> for <remote>. Although ugly, this behaviour is akin
-       # to the confusion of refspecs for remotes by git-fetch and git-push,
-       # eg:
-       #
-       #   $ git fetch branch
-       #
-
+       # Do not expect "git ls-remote <pattern>" to work; ls-remote needs
+       # <remote> if you want to feed <pattern>, just like you cannot say
+       # fetch <branch>.
        # We could just as easily have used "master"; the "*" emphasizes its
        # role as a pattern.
        test_must_fail git ls-remote refs*master >actual 2>&1 &&
index c6feca44e3677c416bfe4841fda5205b2133f7bc..7ff6e0e16cbeb014c1ea551392a63beca2dbc386 100755 (executable)
@@ -124,4 +124,14 @@ test_expect_success 'cloning non-git directory fails' '
        test_must_fail git clone not-a-git-repo not-a-git-repo-clone
 '
 
+test_expect_success 'cloning file:// does not hardlink' '
+       git clone --bare file://"$(pwd)"/a non-local &&
+       ! repo_is_hardlinked non-local
+'
+
+test_expect_success 'cloning a local path with --no-local does not hardlink' '
+       git clone --bare --no-local a force-nonlocal &&
+       ! repo_is_hardlinked force-nonlocal
+'
+
 test_done
index e0227730deb88413301b37501fdb165a2c90460e..4d13e10de1f2594ab304b3a2b7b5905d75d6ff84 100755 (executable)
@@ -5,7 +5,8 @@ test_description='git filter-branch'
 
 test_expect_success 'setup' '
        test_commit A &&
-       test_commit B &&
+       GIT_COMMITTER_DATE="@0 +0000" GIT_AUTHOR_DATE="@0 +0000" &&
+       test_commit --notick B &&
        git checkout -b branch B &&
        test_commit D &&
        mkdir dir &&
index b8cb4906aa7de022543b594399b6fa9a83ffc847..f4f38a5e7387694e16ff6f4a54020843630c2ac7 100755 (executable)
@@ -30,6 +30,9 @@ test_expect_success 'Report new path with conflict' '
 
 cat >expect <<EOF
 # On branch side
+# You have unmerged paths.
+#   (fix conflicts and run "git commit")
+#
 # Unmerged paths:
 #   (use "git add/rm <file>..." as appropriate to mark resolution)
 #
@@ -118,4 +121,97 @@ test_expect_success 'git diff-index --cached -C shows 2 copies + 1 unmerged' '
        test_cmp expected actual
 '
 
+
+test_expect_success 'status when conflicts with add and rm advice (deleted by them)' '
+       git reset --hard &&
+       git checkout master &&
+       test_commit init main.txt init &&
+       git checkout -b second_branch &&
+       git rm main.txt &&
+       git commit -m "main.txt deleted on second_branch" &&
+       test_commit second conflict.txt second &&
+       git checkout master &&
+       test_commit on_second main.txt on_second &&
+       test_commit master conflict.txt master &&
+       test_must_fail git merge second_branch &&
+       cat >expected <<-\EOF &&
+       # On branch master
+       # You have unmerged paths.
+       #   (fix conflicts and run "git commit")
+       #
+       # Unmerged paths:
+       #   (use "git add/rm <file>..." as appropriate to mark resolution)
+       #
+       #       both added:         conflict.txt
+       #       deleted by them:    main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for conflicts' '
+       git reset --hard &&
+       git checkout -b conflict &&
+       test_commit one main.txt one &&
+       git branch conflict_second &&
+       git mv main.txt sub_master.txt &&
+       git commit -m "main.txt renamed in sub_master.txt" &&
+       git checkout conflict_second &&
+       git mv main.txt sub_second.txt &&
+       git commit -m "main.txt renamed in sub_second.txt"
+'
+
+
+test_expect_success 'status when conflicts with add and rm advice (both deleted)' '
+       test_must_fail git merge conflict &&
+       cat >expected <<-\EOF &&
+       # On branch conflict_second
+       # You have unmerged paths.
+       #   (fix conflicts and run "git commit")
+       #
+       # Unmerged paths:
+       #   (use "git add/rm <file>..." as appropriate to mark resolution)
+       #
+       #       both deleted:       main.txt
+       #       added by them:      sub_master.txt
+       #       added by us:        sub_second.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when conflicts with only rm advice (both deleted)' '
+       git reset --hard conflict_second &&
+       test_must_fail git merge conflict &&
+       git add sub_master.txt &&
+       git add sub_second.txt &&
+       cat >expected <<-\EOF &&
+       # On branch conflict_second
+       # You have unmerged paths.
+       #   (fix conflicts and run "git commit")
+       #
+       # Changes to be committed:
+       #
+       #       new file:   sub_master.txt
+       #
+       # Unmerged paths:
+       #   (use "git rm <file>..." to mark resolution)
+       #
+       #       both deleted:       main.txt
+       #
+       # Untracked files not listed (use -u option to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual &&
+       git reset --hard &&
+       git checkout master
+'
+
+
 test_done
index 81827e696f21f598357313d6dad94400d8562718..c73bec9551eb27dab25aad69c83ae7ce9f9b11a8 100755 (executable)
@@ -483,21 +483,72 @@ test_expect_success 'set up for relative path tests' '
                git add sub &&
                git config -f .gitmodules submodule.sub.path sub &&
                git config -f .gitmodules submodule.sub.url ../subrepo &&
-               cp .git/config pristine-.git-config
+               cp .git/config pristine-.git-config &&
+               cp .gitmodules pristine-.gitmodules
        )
 '
 
-test_expect_success 'relative path works with URL' '
+test_expect_success '../subrepo works with URL - ssh://hostname/repo' '
        (
                cd reltest &&
                cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
                git config remote.origin.url ssh://hostname/repo &&
                git submodule init &&
                test "$(git config submodule.sub.url)" = ssh://hostname/subrepo
        )
 '
 
-test_expect_success 'relative path works with user@host:path' '
+test_expect_success '../subrepo works with port-qualified URL - ssh://hostname:22/repo' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url ssh://hostname:22/repo &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = ssh://hostname:22/subrepo
+       )
+'
+
+# About the choice of the path in the next test:
+# - double-slash side-steps path mangling issues on Windows
+# - it is still an absolute local path
+# - there cannot be a server with a blank in its name just in case the
+#   path is used erroneously to access a //server/share style path
+test_expect_success '../subrepo path works with local path - //somewhere else/repo' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url "//somewhere else/repo" &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = "//somewhere else/subrepo"
+       )
+'
+
+test_expect_success '../subrepo works with file URL - file:///tmp/repo' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url file:///tmp/repo &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = file:///tmp/subrepo
+       )
+'
+
+test_expect_success '../subrepo works with helper URL- helper:://hostname/repo' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url helper:://hostname/repo &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = helper:://hostname/subrepo
+       )
+'
+
+test_expect_success '../subrepo works with scp-style URL - user@host:repo' '
        (
                cd reltest &&
                cp pristine-.git-config .git/config &&
@@ -507,6 +558,98 @@ test_expect_success 'relative path works with user@host:path' '
        )
 '
 
+test_expect_success '../subrepo works with scp-style URL - user@host:path/to/repo' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url user@host:path/to/repo &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = user@host:path/to/subrepo
+       )
+'
+
+test_expect_success '../subrepo works with relative local path - foo' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url foo &&
+               # actual: fails with an error
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = subrepo
+       )
+'
+
+test_expect_success '../subrepo works with relative local path - foo/bar' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url foo/bar &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = foo/subrepo
+       )
+'
+
+test_expect_success '../subrepo works with relative local path - ./foo' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url ./foo &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = subrepo
+       )
+'
+
+test_expect_success '../subrepo works with relative local path - ./foo/bar' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url ./foo/bar &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = foo/subrepo
+       )
+'
+
+test_expect_success '../subrepo works with relative local path - ../foo' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url ../foo &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = ../subrepo
+       )
+'
+
+test_expect_success '../subrepo works with relative local path - ../foo/bar' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               git config remote.origin.url ../foo/bar &&
+               git submodule init &&
+               test "$(git config submodule.sub.url)" = ../foo/subrepo
+       )
+'
+
+test_expect_success '../bar/a/b/c works with relative local path - ../foo/bar.git' '
+       (
+               cd reltest &&
+               cp pristine-.git-config .git/config &&
+               cp pristine-.gitmodules .gitmodules &&
+               mkdir -p a/b/c &&
+               (cd a/b/c; git init) &&
+               git config remote.origin.url ../foo/bar.git &&
+               git submodule add ../bar/a/b/c ./a/b/c &&
+               git submodule init &&
+               test "$(git config submodule.a/b/c.url)" = ../foo/bar/a/b/c
+       )
+'
+
 test_expect_success 'moving the superproject does not break submodules' '
        (
                cd addtest &&
index 3620215c1f84b8e184b470c92b9a01c15fd75dc2..524d5c1b21a507c5ed16edc52fb9353792b83f28 100755 (executable)
@@ -26,7 +26,9 @@ test_expect_success setup '
        (cd super-clone && git submodule update --init) &&
        git clone super empty-clone &&
        (cd empty-clone && git submodule init) &&
-       git clone super top-only-clone
+       git clone super top-only-clone &&
+       git clone super relative-clone &&
+       (cd relative-clone && git submodule update --init)
 '
 
 test_expect_success 'change submodule' '
@@ -86,4 +88,90 @@ test_expect_success '"git submodule sync" should not vivify uninteresting submod
        )
 '
 
+test_expect_success '"git submodule sync" handles origin URL of the form foo' '
+       (cd relative-clone &&
+        git remote set-url origin foo &&
+        git submodule sync &&
+       (cd submodule &&
+        #actual fails with: "cannot strip off url foo
+        test "$(git config remote.origin.url)" = "../submodule"
+       )
+       )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form foo/bar' '
+       (cd relative-clone &&
+        git remote set-url origin foo/bar &&
+        git submodule sync &&
+       (cd submodule &&
+        #actual foo/submodule
+        test "$(git config remote.origin.url)" = "../foo/submodule"
+       )
+       )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ./foo' '
+       (cd relative-clone &&
+        git remote set-url origin ./foo &&
+        git submodule sync &&
+       (cd submodule &&
+        #actual ./submodule
+        test "$(git config remote.origin.url)" = "../submodule"
+       )
+       )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ./foo/bar' '
+       (cd relative-clone &&
+        git remote set-url origin ./foo/bar &&
+        git submodule sync &&
+       (cd submodule &&
+        #actual ./foo/submodule
+        test "$(git config remote.origin.url)" = "../foo/submodule"
+       )
+       )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ../foo' '
+       (cd relative-clone &&
+        git remote set-url origin ../foo &&
+        git submodule sync &&
+       (cd submodule &&
+        #actual ../submodule
+        test "$(git config remote.origin.url)" = "../../submodule"
+       )
+       )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar' '
+       (cd relative-clone &&
+        git remote set-url origin ../foo/bar &&
+        git submodule sync &&
+       (cd submodule &&
+        #actual ../foo/submodule
+        test "$(git config remote.origin.url)" = "../../foo/submodule"
+       )
+       )
+'
+
+test_expect_success '"git submodule sync" handles origin URL of the form ../foo/bar with deeply nested submodule' '
+       (cd relative-clone &&
+        git remote set-url origin ../foo/bar &&
+        mkdir -p a/b/c &&
+        ( cd a/b/c &&
+          git init &&
+          :> .gitignore &&
+          git add .gitignore &&
+          test_tick &&
+          git commit -m "initial commit" ) &&
+        git submodule add ../bar/a/b/c ./a/b/c &&
+        git submodule sync &&
+       (cd a/b/c &&
+        #actual ../foo/bar/a/b/c
+        test "$(git config remote.origin.url)" = "../../../../foo/bar/a/b/c"
+       )
+       )
+'
+
+
 test_done
index dcb195b4cf67af3cf5ce94d0f5ca75bf94664b9a..ce61d4c0fa3d916757e09224ed79bc191fb0cb3d 100755 (executable)
@@ -636,4 +636,17 @@ test_expect_success 'submodule update properly revives a moved submodule' '
        )
 '
 
+test_expect_success SYMLINKS 'submodule update can handle symbolic links in pwd' '
+       mkdir -p linked/dir &&
+       ln -s linked/dir linkto &&
+       (
+               cd linkto &&
+               git clone "$TRASH_DIRECTORY"/super_update_r2 super &&
+               (
+                       cd super &&
+                       git submodule update --init --recursive
+               )
+       )
+'
+
 test_done
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
new file mode 100755 (executable)
index 0000000..b3f6eb9
--- /dev/null
@@ -0,0 +1,649 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Valentin Duperray, Lucien Kong, Franck Jonas,
+#                   Thomas Nguy, Khoi Nguyen
+#                   Grenoble INP Ensimag
+#
+
+test_description='git status advices'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'prepare for conflicts' '
+       test_commit init main.txt init &&
+       git branch conflicts &&
+       test_commit on_master main.txt on_master &&
+       git checkout conflicts &&
+       test_commit on_conflicts main.txt on_conflicts
+'
+
+
+test_expect_success 'status when conflicts unresolved' '
+       test_must_fail git merge master &&
+       cat >expected <<-\EOF &&
+       # On branch conflicts
+       # You have unmerged paths.
+       #   (fix conflicts and run "git commit")
+       #
+       # Unmerged paths:
+       #   (use "git add <file>..." to mark resolution)
+       #
+       #       both modified:      main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when conflicts resolved before commit' '
+       git reset --hard conflicts &&
+       test_must_fail git merge master &&
+       echo one >main.txt &&
+       git add main.txt &&
+       cat >expected <<-\EOF &&
+       # On branch conflicts
+       # All conflicts fixed but you are still merging.
+       #   (use "git commit" to conclude merge)
+       #
+       # Changes to be committed:
+       #
+       #       modified:   main.txt
+       #
+       # Untracked files not listed (use -u option to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for rebase conflicts' '
+       git reset --hard master &&
+       git checkout -b rebase_conflicts &&
+       test_commit one_rebase main.txt one &&
+       test_commit two_rebase main.txt two &&
+       test_commit three_rebase main.txt three
+'
+
+
+test_expect_success 'status when rebase in progress before resolving conflicts' '
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently rebasing.
+       #   (fix conflicts and then run "git rebase --continue")
+       #   (use "git rebase --skip" to skip this patch)
+       #   (use "git rebase --abort" to check out the original branch)
+       #
+       # Unmerged paths:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #   (use "git add <file>..." to mark resolution)
+       #
+       #       both modified:      main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when rebase in progress before rebase --continue' '
+       git reset --hard rebase_conflicts &&
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+       echo three >main.txt &&
+       git add main.txt &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently rebasing.
+       #   (all conflicts fixed: run "git rebase --continue")
+       #
+       # Changes to be committed:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #
+       #       modified:   main.txt
+       #
+       # Untracked files not listed (use -u option to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for rebase_i_conflicts' '
+       git reset --hard master &&
+       git checkout -b rebase_i_conflicts &&
+       test_commit one_unmerge main.txt one_unmerge &&
+       git branch rebase_i_conflicts_second &&
+       test_commit one_master main.txt one_master &&
+       git checkout rebase_i_conflicts_second &&
+       test_commit one_second main.txt one_second
+'
+
+
+test_expect_success 'status during rebase -i when conflicts unresolved' '
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase -i rebase_i_conflicts &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently rebasing.
+       #   (fix conflicts and then run "git rebase --continue")
+       #   (use "git rebase --skip" to skip this patch)
+       #   (use "git rebase --abort" to check out the original branch)
+       #
+       # Unmerged paths:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #   (use "git add <file>..." to mark resolution)
+       #
+       #       both modified:      main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status during rebase -i after resolving conflicts' '
+       git reset --hard rebase_i_conflicts_second &&
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase -i rebase_i_conflicts &&
+       git add main.txt &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently rebasing.
+       #   (all conflicts fixed: run "git rebase --continue")
+       #
+       # Changes to be committed:
+       #   (use "git reset HEAD <file>..." to unstage)
+       #
+       #       modified:   main.txt
+       #
+       # Untracked files not listed (use -u option to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when rebasing -i in edit mode' '
+       git reset --hard master &&
+       git checkout -b rebase_i_edit &&
+       test_commit one_rebase_i main.txt one &&
+       test_commit two_rebase_i main.txt two &&
+       test_commit three_rebase_i main.txt three &&
+       FAKE_LINES="1 edit 2" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~2 &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently editing a commit during a rebase.
+       #   (use "git commit --amend" to amend the current commit)
+       #   (use "git rebase --continue" once you are satisfied with your changes)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when splitting a commit' '
+       git reset --hard master &&
+       git checkout -b split_commit &&
+       test_commit one_split main.txt one &&
+       test_commit two_split main.txt two &&
+       test_commit three_split main.txt three &&
+       test_commit four_split main.txt four &&
+       FAKE_LINES="1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git reset HEAD^ &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently splitting a commit during a rebase.
+       #   (Once your working directory is clean, run "git rebase --continue")
+       #
+       # 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:   main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status after editing the last commit with --amend during a rebase -i' '
+       git reset --hard master &&
+       git checkout -b amend_last &&
+       test_commit one_amend main.txt one &&
+       test_commit two_amend main.txt two &&
+       test_commit three_amend main.txt three &&
+       test_commit four_amend main.txt four &&
+       FAKE_LINES="1 2 edit 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git commit --amend -m "foo" &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently editing a commit during a rebase.
+       #   (use "git commit --amend" to amend the current commit)
+       #   (use "git rebase --continue" once you are satisfied with your changes)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for several edits' '
+       git reset --hard master &&
+       git checkout -b several_edits &&
+       test_commit one_edits main.txt one &&
+       test_commit two_edits main.txt two &&
+       test_commit three_edits main.txt three &&
+       test_commit four_edits main.txt four
+'
+
+
+test_expect_success 'status: (continue first edit) second edit' '
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git rebase --continue &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently editing a commit during a rebase.
+       #   (use "git commit --amend" to amend the current commit)
+       #   (use "git rebase --continue" once you are satisfied with your changes)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (continue first edit) second edit and split' '
+       git reset --hard several_edits &&
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git rebase --continue &&
+       git reset HEAD^ &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently splitting a commit during a rebase.
+       #   (Once your working directory is clean, run "git rebase --continue")
+       #
+       # 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:   main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (continue first edit) second edit and amend' '
+       git reset --hard several_edits &&
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git rebase --continue &&
+       git commit --amend -m "foo" &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently editing a commit during a rebase.
+       #   (use "git commit --amend" to amend the current commit)
+       #   (use "git rebase --continue" once you are satisfied with your changes)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (amend first edit) second edit' '
+       git reset --hard several_edits &&
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git commit --amend -m "a" &&
+       git rebase --continue &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently editing a commit during a rebase.
+       #   (use "git commit --amend" to amend the current commit)
+       #   (use "git rebase --continue" once you are satisfied with your changes)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (amend first edit) second edit and split' '
+       git reset --hard several_edits &&
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git commit --amend -m "b" &&
+       git rebase --continue &&
+       git reset HEAD^ &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently splitting a commit during a rebase.
+       #   (Once your working directory is clean, run "git rebase --continue")
+       #
+       # 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:   main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (amend first edit) second edit and amend' '
+       git reset --hard several_edits &&
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git commit --amend -m "c" &&
+       git rebase --continue &&
+       git commit --amend -m "d" &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently editing a commit during a rebase.
+       #   (use "git commit --amend" to amend the current commit)
+       #   (use "git rebase --continue" once you are satisfied with your changes)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (split first edit) second edit' '
+       git reset --hard several_edits &&
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git reset HEAD^ &&
+       git add main.txt &&
+       git commit -m "e" &&
+       git rebase --continue &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently editing a commit during a rebase.
+       #   (use "git commit --amend" to amend the current commit)
+       #   (use "git rebase --continue" once you are satisfied with your changes)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (split first edit) second edit and split' '
+       git reset --hard several_edits &&
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git reset HEAD^ &&
+       git add main.txt &&
+       git commit --amend -m "f" &&
+       git rebase --continue &&
+       git reset HEAD^ &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently splitting a commit during a rebase.
+       #   (Once your working directory is clean, run "git rebase --continue")
+       #
+       # 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:   main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status: (split first edit) second edit and amend' '
+       git reset --hard several_edits &&
+       FAKE_LINES="edit 1 edit 2 3" &&
+       export FAKE_LINES &&
+       test_when_finished "git rebase --abort" &&
+       git rebase -i HEAD~3 &&
+       git reset HEAD^ &&
+       git add main.txt &&
+       git commit --amend -m "g" &&
+       git rebase --continue &&
+       git commit --amend -m "h" &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently editing a commit during a rebase.
+       #   (use "git commit --amend" to amend the current commit)
+       #   (use "git rebase --continue" once you are satisfied with your changes)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare am_session' '
+       git reset --hard master &&
+       git checkout -b am_session &&
+       test_commit one_am one.txt "one" &&
+       test_commit two_am two.txt "two" &&
+       test_commit three_am three.txt "three"
+'
+
+
+test_expect_success 'status in an am session: file already exists' '
+       git checkout -b am_already_exists &&
+       test_when_finished "rm Maildir/* && git am --abort" &&
+       git format-patch -1 -oMaildir &&
+       test_must_fail git am Maildir/*.patch &&
+       cat >expected <<-\EOF &&
+       # On branch am_already_exists
+       # You are in the middle of an am session.
+       #   (fix conflicts and then run "git am --resolved")
+       #   (use "git am --skip" to skip this patch)
+       #   (use "git am --abort" to restore the original branch)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status in an am session: file does not exist' '
+       git reset --hard am_session &&
+       git checkout -b am_not_exists &&
+       git rm three.txt &&
+       git commit -m "delete three.txt" &&
+       test_when_finished "rm Maildir/* && git am --abort" &&
+       git format-patch -1 -oMaildir &&
+       test_must_fail git am Maildir/*.patch &&
+       cat >expected <<-\EOF &&
+       # On branch am_not_exists
+       # You are in the middle of an am session.
+       #   (fix conflicts and then run "git am --resolved")
+       #   (use "git am --skip" to skip this patch)
+       #   (use "git am --abort" to restore the original branch)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status in an am session: empty patch' '
+       git reset --hard am_session &&
+       git checkout -b am_empty &&
+       test_when_finished "rm Maildir/* && git am --abort" &&
+       git format-patch -3 -oMaildir &&
+       git rm one.txt two.txt three.txt &&
+       git commit -m "delete all am_empty" &&
+       echo error >Maildir/0002-two_am.patch &&
+       test_must_fail git am Maildir/*.patch &&
+       cat >expected <<-\EOF &&
+       # On branch am_empty
+       # You are in the middle of an am session.
+       # The current patch is empty.
+       #   (use "git am --skip" to skip this patch)
+       #   (use "git am --abort" to restore the original branch)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when bisecting' '
+       git reset --hard master &&
+       git checkout -b bisect &&
+       test_commit one_bisect main.txt one &&
+       test_commit two_bisect main.txt two &&
+       test_commit three_bisect main.txt three &&
+       test_when_finished "git bisect reset" &&
+       git bisect start &&
+       git bisect bad &&
+       git bisect good one_bisect &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently bisecting.
+       #   (use "git bisect reset" to get back to the original branch)
+       #
+       nothing to commit (use -u to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when rebase conflicts with statushints disabled' '
+       git reset --hard master &&
+       git checkout -b statushints_disabled &&
+       test_when_finished "git config --local advice.statushints true" &&
+       git config --local advice.statushints false &&
+       test_commit one_statushints main.txt one &&
+       test_commit two_statushints main.txt two &&
+       test_commit three_statushints main.txt three &&
+       test_when_finished "git rebase --abort" &&
+       test_must_fail git rebase HEAD^ --onto HEAD^^ &&
+       cat >expected <<-\EOF &&
+       # Not currently on any branch.
+       # You are currently rebasing.
+       #
+       # Unmerged paths:
+       #       both modified:      main.txt
+       #
+       no changes added to commit
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'prepare for cherry-pick conflicts' '
+       git reset --hard master &&
+       git checkout -b cherry_branch &&
+       test_commit one_cherry main.txt one &&
+       test_commit two_cherries main.txt two &&
+       git checkout -b cherry_branch_second &&
+       test_commit second_cherry main.txt second &&
+       git checkout cherry_branch &&
+       test_commit three_cherries main.txt three
+'
+
+
+test_expect_success 'status when cherry-picking before resolving conflicts' '
+       test_when_finished "git cherry-pick --abort" &&
+       test_must_fail git cherry-pick cherry_branch_second &&
+       cat >expected <<-\EOF &&
+       # On branch cherry_branch
+       # You are currently cherry-picking.
+       #   (fix conflicts and run "git commit")
+       #
+       # Unmerged paths:
+       #   (use "git add <file>..." to mark resolution)
+       #
+       #       both modified:      main.txt
+       #
+       no changes added to commit (use "git add" and/or "git commit -a")
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_expect_success 'status when cherry-picking after resolving conflicts' '
+       git reset --hard cherry_branch &&
+       test_when_finished "git cherry-pick --abort" &&
+       test_must_fail git cherry-pick cherry_branch_second &&
+       echo end >main.txt &&
+       git add main.txt &&
+       cat >expected <<-\EOF &&
+       # On branch cherry_branch
+       # You are currently cherry-picking.
+       #   (all conflicts fixed: run "git commit")
+       #
+       # Changes to be committed:
+       #
+       #       modified:   main.txt
+       #
+       # Untracked files not listed (use -u option to show untracked files)
+       EOF
+       git status --untracked-files=no >actual &&
+       test_i18ncmp expected actual
+'
+
+
+test_done
index 0f410c45f7005e7de1237e556396db5726a22e50..b7ad716b09facbc068a23c48cdbfe68a87f52d4c 100755 (executable)
@@ -45,7 +45,8 @@ test_expect_success 'git p4 sync uninitialized repo' '
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
-               test_must_fail git p4 sync
+               test_must_fail git p4 sync 2>errs &&
+               test_i18ngrep "Perhaps you never did" errs
        )
 '
 
@@ -126,150 +127,24 @@ test_expect_success 'clone two dirs, @all, conflicting files' '
 '
 
 test_expect_success 'exit when p4 fails to produce marshaled output' '
-       badp4dir="$TRASH_DIRECTORY/badp4dir" &&
-       mkdir "$badp4dir" &&
-       test_when_finished "rm \"$badp4dir/p4\" && rmdir \"$badp4dir\"" &&
-       cat >"$badp4dir"/p4 <<-EOF &&
+       mkdir badp4dir &&
+       test_when_finished "rm badp4dir/p4 && rmdir badp4dir" &&
+       cat >badp4dir/p4 <<-EOF &&
        #!$SHELL_PATH
        exit 1
        EOF
-       chmod 755 "$badp4dir"/p4 &&
-       PATH="$badp4dir:$PATH" git p4 clone --dest="$git" //depot >errs 2>&1 ; retval=$? &&
-       test $retval -eq 1 &&
-       test_must_fail grep -q Traceback errs
-'
-
-test_expect_success 'add p4 files with wildcards in the names' '
-       (
-               cd "$cli" &&
-               echo file-wild-hash >file-wild#hash &&
-               echo file-wild-star >file-wild\*star &&
-               echo file-wild-at >file-wild@at &&
-               echo file-wild-percent >file-wild%percent &&
-               p4 add -f file-wild* &&
-               p4 submit -d "file wildcards"
-       )
-'
-
-test_expect_success 'wildcard files git p4 clone' '
-       git p4 clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       (
-               cd "$git" &&
-               test -f file-wild#hash &&
-               test -f file-wild\*star &&
-               test -f file-wild@at &&
-               test -f file-wild%percent
-       )
-'
-
-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 &&
+       chmod 755 badp4dir/p4 &&
        (
-               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
+               PATH="$TRASH_DIRECTORY/badp4dir:$PATH" &&
+               export PATH &&
+               test_expect_code 1 git p4 clone --dest="$git" //depot >errs 2>&1
        ) &&
-       (
-               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
-       )
+       cat errs &&
+       ! test_i18ngrep Traceback errs
 '
 
 test_expect_success 'clone bare' '
+       rm -rf "$git" &&
        git p4 clone --dest="$git" --bare //depot &&
        test_when_finished cleanup_git &&
        (
@@ -280,138 +155,6 @@ test_expect_success 'clone bare' '
        )
 '
 
-p4_add_user() {
-       name=$1 fullname=$2 &&
-       p4 user -f -i <<-EOF &&
-       User: $name
-       Email: $name@localhost
-       FullName: $fullname
-       EOF
-       p4 passwd -P secret $name
-}
-
-p4_grant_admin() {
-       name=$1 &&
-       {
-               p4 protect -o &&
-               echo "    admin user $name * //depot/..."
-       } | p4 protect -i
-}
-
-p4_check_commit_author() {
-       file=$1 user=$2 &&
-       p4 changes -m 1 //depot/$file | grep -q $user
-}
-
-make_change_by_user() {
-       file=$1 name=$2 email=$3 &&
-       echo "username: a change by $name" >>"$file" &&
-       git add "$file" &&
-       git commit --author "$name <$email>" -m "a change by $name"
-}
-
-# Test username support, submitting as user 'alice'
-test_expect_success 'preserve users' '
-       p4_add_user alice Alice &&
-       p4_add_user bob Bob &&
-       p4_grant_admin alice &&
-       git p4 clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       (
-               cd "$git" &&
-               echo "username: a change by alice" >>file1 &&
-               echo "username: a change by bob" >>file2 &&
-               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 git p4 commit --preserve-user &&
-               p4_check_commit_author file1 alice &&
-               p4_check_commit_author file2 bob
-       )
-'
-
-# 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' '
-       git p4 clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       (
-               cd "$git" &&
-               git config git-p4.skipSubmitEditCheck true &&
-               echo "username-noperms: a change by alice" >>file1 &&
-               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 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' '
-       git p4 clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       (
-               cd "$git" &&
-               git config git-p4.skipSubmitEditCheck true &&
-               echo "username-bob: a change by bob" >>file1 &&
-               git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
-               echo "username-unknown: a change by charlie" >>file1 &&
-               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 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 &&
-               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
-# 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' '
-       git p4 clone --dest="$git" //depot &&
-       test_when_finished cleanup_git &&
-       (
-               cd "$git" &&
-               git config git-p4.skipSubmitEditCheck true &&
-               p4_add_user derek Derek &&
-
-               make_change_by_user usernamefile3 Derek derek@localhost &&
-               P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
-               export P4EDITOR P4USER P4PASSWD &&
-               git p4 commit |\
-               grep "git author derek@localhost does not match" &&
-
-               make_change_by_user usernamefile3 Charlie charlie@localhost &&
-               git p4 commit |\
-               grep "git author charlie@localhost does not match" &&
-
-               make_change_by_user usernamefile3 alice alice@localhost &&
-               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 &&
-               git p4 commit |\
-               test_must_fail grep "git author.*does not match" &&
-
-               p4_check_commit_author usernamefile3 alice
-       )
-'
-
-marshal_dump() {
-       what=$1
-       "$PYTHON_PATH" -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]'
-}
-
 # Sleep a bit so that the top-most p4 change did not happen "now".  Then
 # import the repo and make sure that the initial import has the same time
 # as the top-most change.
@@ -429,146 +172,6 @@ test_expect_success 'initial import time from top change time' '
        )
 '
 
-# Rename a file and confirm that rename is not detected in P4.
-# Rename the new file again with detectRenames option enabled and confirm that
-# this is detected in P4.
-# Rename the new file again adding an extra line, configure a big threshold in
-# detectRenames and confirm that rename is not detected in P4.
-# Repeat, this time with a smaller threshold and confirm that the rename is
-# detected in P4.
-test_expect_success 'detect renames' '
-       git p4 clone --dest="$git" //depot@all &&
-       test_when_finished cleanup_git &&
-       (
-               cd "$git" &&
-               git config git-p4.skipSubmitEdit true &&
-
-               git mv file1 file4 &&
-               git commit -a -m "Rename file1 to file4" &&
-               git diff-tree -r -M HEAD &&
-               git p4 submit &&
-               p4 filelog //depot/file4 &&
-               p4 filelog //depot/file4 | test_must_fail grep -q "branch from" &&
-
-               git mv file4 file5 &&
-               git commit -a -m "Rename file4 to file5" &&
-               git diff-tree -r -M HEAD &&
-               git config git-p4.detectRenames true &&
-               git p4 submit &&
-               p4 filelog //depot/file5 &&
-               p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
-
-               git mv file5 file6 &&
-               echo update >>file6 &&
-               git add file6 &&
-               git commit -a -m "Rename file5 to file6 with changes" &&
-               git diff-tree -r -M HEAD &&
-               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)) &&
-               git p4 submit &&
-               p4 filelog //depot/file6 &&
-               p4 filelog //depot/file6 | test_must_fail grep -q "branch from" &&
-
-               git mv file6 file7 &&
-               echo update >>file7 &&
-               git add file7 &&
-               git commit -a -m "Rename file6 to file7 with changes" &&
-               git diff-tree -r -M HEAD &&
-               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)) &&
-               git p4 submit &&
-               p4 filelog //depot/file7 &&
-               p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
-       )
-'
-
-# Copy a file and confirm that copy is not detected in P4.
-# Copy a file with detectCopies option enabled and confirm that copy is not
-# detected in P4.
-# Modify and copy a file with detectCopies option enabled and confirm that copy
-# is detected in P4.
-# Copy a file with detectCopies and detectCopiesHarder options enabled and
-# confirm that copy is detected in P4.
-# Modify and copy a file, configure a bigger threshold in detectCopies and
-# confirm that copy is not detected in P4.
-# Modify and copy a file, configure a smaller threshold in detectCopies and
-# confirm that copy is detected in P4.
-test_expect_success 'detect copies' '
-       git p4 clone --dest="$git" //depot@all &&
-       test_when_finished cleanup_git &&
-       (
-               cd "$git" &&
-               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 &&
-               git p4 submit &&
-               p4 filelog //depot/file8 &&
-               p4 filelog //depot/file8 | test_must_fail grep -q "branch from" &&
-
-               cp file2 file9 &&
-               git add file9 &&
-               git commit -a -m "Copy file2 to file9" &&
-               git diff-tree -r -C HEAD &&
-               git config git-p4.detectCopies true &&
-               git p4 submit &&
-               p4 filelog //depot/file9 &&
-               p4 filelog //depot/file9 | test_must_fail grep -q "branch from" &&
-
-               echo "file2" >>file2 &&
-               cp file2 file10 &&
-               git add file2 file10 &&
-               git commit -a -m "Modify and copy file2 to file10" &&
-               git diff-tree -r -C HEAD &&
-               git p4 submit &&
-               p4 filelog //depot/file10 &&
-               p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
-
-               cp file2 file11 &&
-               git add file11 &&
-               git commit -a -m "Copy file2 to file11" &&
-               git diff-tree -r -C --find-copies-harder HEAD &&
-               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-               test "$src" = file10 &&
-               git config git-p4.detectCopiesHarder true &&
-               git p4 submit &&
-               p4 filelog //depot/file11 &&
-               p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
-
-               cp file2 file12 &&
-               echo "some text" >>file12 &&
-               git add file12 &&
-               git commit -a -m "Copy file2 to file12 with changes" &&
-               git diff-tree -r -C --find-copies-harder HEAD &&
-               level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
-               test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
-               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-               test "$src" = file10 &&
-               git config git-p4.detectCopies $(($level + 2)) &&
-               git p4 submit &&
-               p4 filelog //depot/file12 &&
-               p4 filelog //depot/file12 | test_must_fail grep -q "branch from" &&
-
-               cp file2 file13 &&
-               echo "different text" >>file13 &&
-               git add file13 &&
-               git commit -a -m "Copy file2 to file13 with changes" &&
-               git diff-tree -r -C --find-copies-harder HEAD &&
-               level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
-               test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
-               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-               test "$src" = file10 &&
-               git config git-p4.detectCopies $(($level - 2)) &&
-               git p4 submit &&
-               p4 filelog //depot/file13 &&
-               p4 filelog //depot/file13 | grep -q "branch from //depot/file"
-       )
-'
-
 test_expect_success 'kill p4d' '
        kill_p4d
 '
index 353dcfbe09fec2c2a85427e3e5cb378b9a8318d8..fb3c8ec12c7a3724c3b4f719c91357cc4afed078 100755 (executable)
@@ -78,20 +78,19 @@ test_expect_success 'skipSubmitEditCheck' '
 test_expect_success 'no config, edited' '
        git p4 clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
-       ed="$TRASH_DIRECTORY/ed.sh" &&
-       test_when_finished "rm \"$ed\"" &&
-       cat >"$ed" <<-EOF &&
+       test_when_finished "rm ed.sh" &&
+       cat >ed.sh <<-EOF &&
                #!$SHELL_PATH
                sleep 1
                touch "\$1"
                exit 0
        EOF
-       chmod 755 "$ed" &&
+       chmod 755 ed.sh &&
        (
                cd "$git" &&
                echo line >>file1 &&
                git commit -a -m "change 5" &&
-               P4EDITOR="" EDITOR="\"$ed\"" git p4 submit &&
+               P4EDITOR="" EDITOR="\"$TRASH_DIRECTORY/ed.sh\"" git p4 submit &&
                p4 changes //depot/... >wc &&
                test_line_count = 5 wc
        )
index 2892367830c90353698ef0554b84c1e66d36a0a2..fa40cc8bb59a4e785681013e1dc676709d808410 100755 (executable)
@@ -39,10 +39,9 @@ test_expect_success 'clone --branch' '
 '
 
 test_expect_success 'clone --changesfile' '
-       cf="$TRASH_DIRECTORY/cf" &&
-       test_when_finished "rm \"$cf\"" &&
-       printf "1\n3\n" >"$cf" &&
-       git p4 clone --changesfile="$cf" --dest="$git" //depot &&
+       test_when_finished "rm cf" &&
+       printf "1\n3\n" >cf &&
+       git p4 clone --changesfile="$TRASH_DIRECTORY/cf" --dest="$git" //depot &&
        test_when_finished cleanup_git &&
        (
                cd "$git" &&
@@ -55,10 +54,9 @@ test_expect_success 'clone --changesfile' '
 '
 
 test_expect_success 'clone --changesfile, @all' '
-       cf="$TRASH_DIRECTORY/cf" &&
-       test_when_finished "rm \"$cf\"" &&
-       printf "1\n3\n" >"$cf" &&
-       test_must_fail git p4 clone --changesfile="$cf" --dest="$git" //depot@all
+       test_when_finished "rm cf" &&
+       printf "1\n3\n" >cf &&
+       test_must_fail git p4 clone --changesfile="$TRASH_DIRECTORY/cf" --dest="$git" //depot@all
 '
 
 # imports both master and p4/master in refs/heads
@@ -128,7 +126,7 @@ test_expect_success 'clone --use-client-spec' '
                exec >/dev/null &&
                test_must_fail git p4 clone --dest="$git" --use-client-spec
        ) &&
-       cli2="$TRASH_DIRECTORY/cli2" &&
+       cli2=$(test-path-utils real_path "$TRASH_DIRECTORY/cli2") &&
        mkdir -p "$cli2" &&
        test_when_finished "rmdir \"$cli2\"" &&
        (
@@ -151,7 +149,6 @@ test_expect_success 'clone --use-client-spec' '
        cleanup_git &&
 
        # same thing again, this time with variable instead of option
-       mkdir "$git" &&
        (
                cd "$git" &&
                git init &&
index f23b4c3620592704cac35839a010ce55de139da7..9394fd4e9b5bd7eedc1f3d0552aa71d429b64308 100755 (executable)
@@ -182,6 +182,161 @@ test_expect_success 'submit rename' '
        )
 '
 
+#
+# Converting git commit message to p4 change description, including
+# parsing out the optional Jobs: line.
+#
+test_expect_success 'simple one-line description' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo desc2 >desc2 &&
+               git add desc2 &&
+               cat >msg <<-EOF &&
+               One-line description line for desc2.
+               EOF
+               git commit -F - <msg &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit &&
+               change=$(p4 -G changes -m 1 //depot/... | \
+                        marshal_dump change) &&
+               # marshal_dump always adds a newline
+               p4 -G describe $change | marshal_dump desc | sed \$d >pmsg &&
+               test_cmp msg pmsg
+       )
+'
+
+test_expect_success 'description with odd formatting' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo desc3 >desc3 &&
+               git add desc3 &&
+               (
+                       printf "subject line\n\n\tExtra tab\nline.\n\n" &&
+                       printf "Description:\n\tBogus description marker\n\n" &&
+                       # git commit eats trailing newlines; only use one
+                       printf "Files:\n\tBogus descs marker\n"
+               ) >msg &&
+               git commit -F - <msg &&
+               git config git-p4.skipSubmitEdit true &&
+               git p4 submit &&
+               change=$(p4 -G changes -m 1 //depot/... | \
+                        marshal_dump change) &&
+               # marshal_dump always adds a newline
+               p4 -G describe $change | marshal_dump desc | sed \$d >pmsg &&
+               test_cmp msg pmsg
+       )
+'
+
+make_job() {
+       name="$1" &&
+       tab="$(printf \\t)" &&
+       p4 job -o | \
+       sed -e "/^Job:/s/.*/Job: $name/" \
+           -e "/^Description/{ n; s/.*/$tab job text/; }" | \
+       p4 job -i
+}
+
+test_expect_success 'description with Jobs section at end' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo desc4 >desc4 &&
+               git add desc4 &&
+               echo 6060842 >jobname &&
+               (
+                       printf "subject line\n\n\tExtra tab\nline.\n\n" &&
+                       printf "Files:\n\tBogus files marker\n" &&
+                       printf "Junk: 3164175\n" &&
+                       printf "Jobs: $(cat jobname)\n"
+               ) >msg &&
+               git commit -F - <msg &&
+               git config git-p4.skipSubmitEdit true &&
+               # build a job
+               make_job $(cat jobname) &&
+               git p4 submit &&
+               change=$(p4 -G changes -m 1 //depot/... | \
+                        marshal_dump change) &&
+               # marshal_dump always adds a newline
+               p4 -G describe $change | marshal_dump desc | sed \$d >pmsg &&
+               # make sure Jobs line and all following is gone
+               sed "/^Jobs:/,\$d" msg >jmsg &&
+               test_cmp jmsg pmsg &&
+               # make sure p4 knows about job
+               p4 -G describe $change | marshal_dump job0 >job0 &&
+               test_cmp jobname job0
+       )
+'
+
+test_expect_success 'description with Jobs and values on separate lines' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo desc5 >desc5 &&
+               git add desc5 &&
+               echo PROJ-6060842 >jobname1 &&
+               echo PROJ-6060847 >jobname2 &&
+               (
+                       printf "subject line\n\n\tExtra tab\nline.\n\n" &&
+                       printf "Files:\n\tBogus files marker\n" &&
+                       printf "Junk: 3164175\n" &&
+                       printf "Jobs:\n" &&
+                       printf "\t$(cat jobname1)\n" &&
+                       printf "\t$(cat jobname2)\n"
+               ) >msg &&
+               git commit -F - <msg &&
+               git config git-p4.skipSubmitEdit true &&
+               # build two jobs
+               make_job $(cat jobname1) &&
+               make_job $(cat jobname2) &&
+               git p4 submit &&
+               change=$(p4 -G changes -m 1 //depot/... | \
+                        marshal_dump change) &&
+               # marshal_dump always adds a newline
+               p4 -G describe $change | marshal_dump desc | sed \$d >pmsg &&
+               # make sure Jobs line and all following is gone
+               sed "/^Jobs:/,\$d" msg >jmsg &&
+               test_cmp jmsg pmsg &&
+               # make sure p4 knows about the two jobs
+               p4 -G describe $change >change &&
+               (
+                       marshal_dump job0 <change &&
+                       marshal_dump job1 <change
+               ) | sort >jobs &&
+               cat jobname1 jobname2 | sort >expected &&
+               test_cmp expected jobs
+       )
+'
+
+test_expect_success 'description with Jobs section and bogus following text' '
+       test_when_finished cleanup_git &&
+       git p4 clone --dest="$git" //depot &&
+       (
+               cd "$git" &&
+               echo desc6 >desc6 &&
+               git add desc6 &&
+               echo 6060843 >jobname &&
+               (
+                       printf "subject line\n\n\tExtra tab\nline.\n\n" &&
+                       printf "Files:\n\tBogus files marker\n" &&
+                       printf "Junk: 3164175\n" &&
+                       printf "Jobs: $(cat jobname)\n" &&
+                       printf "MoreJunk: 3711\n"
+               ) >msg &&
+               git commit -F - <msg &&
+               git config git-p4.skipSubmitEdit true &&
+               # build a job
+               make_job $(cat jobname) &&
+               test_must_fail git p4 submit 2>err &&
+               test_i18ngrep "Unknown field name" err
+       )
+'
+
 test_expect_success 'kill p4d' '
        kill_p4d
 '
index 2f8014a60ef07d84430b9a696d46cd63c4822524..dc92e60cd6f09fa5e2610d491cf1c07d43c45dfd 100755 (executable)
@@ -21,7 +21,7 @@ test_expect_success 'init depot' '
 # environment variable is set
 test_expect_success 'P4CONFIG and absolute dir clone' '
        printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config &&
-       test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" &&
+       test_when_finished "rm p4config" &&
        test_when_finished cleanup_git &&
        (
                P4CONFIG=p4config && export P4CONFIG &&
@@ -33,7 +33,7 @@ test_expect_success 'P4CONFIG and absolute dir clone' '
 # same thing, but with relative directory name, note missing $ on --dest
 test_expect_success 'P4CONFIG and relative dir clone' '
        printf "P4PORT=$P4PORT\nP4CLIENT=$P4CLIENT\n" >p4config &&
-       test_when_finished "rm \"$TRASH_DIRECTORY/p4config\"" &&
+       test_when_finished "rm p4config" &&
        test_when_finished cleanup_git &&
        (
                P4CONFIG=p4config && export P4CONFIG &&
index b00ad09d23d8e859d3b52c73579e84ad4620b36c..e9daa9c4f6bab493ad5351d4ec9c7c00c6152e68 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-p4 rcs keywords'
+test_description='git p4 rcs keywords'
 
 . ./lib-git-p4.sh
 
@@ -147,7 +147,7 @@ test_expect_success 'scrub ko files differently' '
        )
 '
 
-# hack; git-p4 submit should do it on its own
+# hack; git p4 submit should do it on its own
 test_expect_success 'cleanup after failure' '
        (
                cd "$cli" &&
@@ -193,7 +193,7 @@ test_expect_success 'do not scrub plain text' '
        )
 '
 
-# hack; git-p4 submit should do it on its own
+# hack; git p4 submit should do it on its own
 test_expect_success 'cleanup after failure 2' '
        (
                cd "$cli" &&
@@ -244,7 +244,7 @@ test_expect_success 'cope with rcs keyword expansion damage' '
                cd "$git" &&
                git config git-p4.skipSubmitEdit true &&
                git config git-p4.attemptRCSCleanup true &&
-               (cd ../cli && p4_append_to_file kwfile1.c) &&
+               (cd "$cli" && p4_append_to_file kwfile1.c) &&
                old_lines=$(wc -l <kwfile1.c) &&
                "$PERL_PATH" -n -i -e "print unless m/Revision:/" kwfile1.c &&
                new_lines=$(wc -l <kwfile1.c) &&
diff --git a/t/t9812-git-p4-wildcards.sh b/t/t9812-git-p4-wildcards.sh
new file mode 100755 (executable)
index 0000000..143d413
--- /dev/null
@@ -0,0 +1,147 @@
+#!/bin/sh
+
+test_description='git p4 wildcards'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'add p4 files with wildcards in the names' '
+       (
+               cd "$cli" &&
+               printf "file2\nhas\nsome\nrandom\ntext\n" >file2 &&
+               p4 add file2 &&
+               echo file-wild-hash >file-wild#hash &&
+               echo file-wild-star >file-wild\*star &&
+               echo file-wild-at >file-wild@at &&
+               echo file-wild-percent >file-wild%percent &&
+               p4 add -f file-wild* &&
+               p4 submit -d "file wildcards"
+       )
+'
+
+test_expect_success 'wildcard files git p4 clone' '
+       git p4 clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               test -f file-wild#hash &&
+               test -f file-wild\*star &&
+               test -f file-wild@at &&
+               test -f file-wild%percent
+       )
+'
+
+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 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9813-git-p4-preserve-users.sh b/t/t9813-git-p4-preserve-users.sh
new file mode 100755 (executable)
index 0000000..f2e85e5
--- /dev/null
@@ -0,0 +1,153 @@
+#!/bin/sh
+
+test_description='git p4 preserve users'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'create files' '
+       (
+               cd "$cli" &&
+               p4 client -o | sed "/LineEnd/s/:.*/:unix/" | p4 client -i &&
+               echo file1 >file1 &&
+               echo file2 >file2 &&
+               p4 add file1 file2 &&
+               p4 submit -d "add files"
+       )
+'
+
+p4_add_user() {
+       name=$1 fullname=$2 &&
+       p4 user -f -i <<-EOF &&
+       User: $name
+       Email: $name@localhost
+       FullName: $fullname
+       EOF
+       p4 passwd -P secret $name
+}
+
+p4_grant_admin() {
+       name=$1 &&
+       {
+               p4 protect -o &&
+               echo "    admin user $name * //depot/..."
+       } | p4 protect -i
+}
+
+p4_check_commit_author() {
+       file=$1 user=$2 &&
+       p4 changes -m 1 //depot/$file | grep -q $user
+}
+
+make_change_by_user() {
+       file=$1 name=$2 email=$3 &&
+       echo "username: a change by $name" >>"$file" &&
+       git add "$file" &&
+       git commit --author "$name <$email>" -m "a change by $name"
+}
+
+# Test username support, submitting as user 'alice'
+test_expect_success 'preserve users' '
+       p4_add_user alice Alice &&
+       p4_add_user bob Bob &&
+       p4_grant_admin alice &&
+       git p4 clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               echo "username: a change by alice" >>file1 &&
+               echo "username: a change by bob" >>file2 &&
+               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 git p4 commit --preserve-user &&
+               p4_check_commit_author file1 alice &&
+               p4_check_commit_author file2 bob
+       )
+'
+
+# 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' '
+       git p4 clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo "username-noperms: a change by alice" >>file1 &&
+               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 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' '
+       git p4 clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo "username-bob: a change by bob" >>file1 &&
+               git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
+               echo "username-unknown: a change by charlie" >>file1 &&
+               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 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 &&
+               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
+# 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' '
+       git p4 clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               p4_add_user derek Derek &&
+
+               make_change_by_user usernamefile3 Derek derek@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret &&
+               export P4EDITOR P4USER P4PASSWD &&
+               git p4 commit |\
+               grep "git author derek@localhost does not match" &&
+
+               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               git p4 commit |\
+               grep "git author charlie@localhost does not match" &&
+
+               make_change_by_user usernamefile3 alice alice@localhost &&
+               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 &&
+               git p4 commit |\
+               test_must_fail grep "git author.*does not match" &&
+
+               p4_check_commit_author usernamefile3 alice
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh
new file mode 100755 (executable)
index 0000000..3bf1224
--- /dev/null
@@ -0,0 +1,206 @@
+#!/bin/sh
+
+test_description='git p4 rename'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+# We rely on this behavior to detect for p4 move availability.
+test_expect_success 'p4 help unknown returns 1' '
+       (
+               cd "$cli" &&
+               (
+                       p4 help client >errs 2>&1
+                       echo $? >retval
+               )
+               echo 0 >expected &&
+               test_cmp expected retval &&
+               rm retval &&
+               (
+                       p4 help nosuchcommand >errs 2>&1
+                       echo $? >retval
+               )
+               echo 1 >expected &&
+               test_cmp expected retval &&
+               rm retval
+       )
+'
+
+test_expect_success 'create files' '
+       (
+               cd "$cli" &&
+               p4 client -o | sed "/LineEnd/s/:.*/:unix/" | p4 client -i &&
+               cat >file1 <<-EOF &&
+               A large block of text
+               in file1 that will generate
+               enough context so that rename
+               and copy detection will find
+               something interesting to do.
+               EOF
+               cat >file2 <<-EOF &&
+               /*
+                * This blob looks a bit
+                * different.
+                */
+               int main(int argc, char **argv)
+               {
+                       char text[200];
+
+                       strcpy(text, "copy/rename this");
+                       printf("text is %s\n", text);
+                       return 0;
+               }
+               EOF
+               p4 add file1 file2 &&
+               p4 submit -d "add files"
+       )
+'
+
+# Rename a file and confirm that rename is not detected in P4.
+# Rename the new file again with detectRenames option enabled and confirm that
+# this is detected in P4.
+# Rename the new file again adding an extra line, configure a big threshold in
+# detectRenames and confirm that rename is not detected in P4.
+# Repeat, this time with a smaller threshold and confirm that the rename is
+# detected in P4.
+test_expect_success 'detect renames' '
+       git p4 clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+
+               git mv file1 file4 &&
+               git commit -a -m "Rename file1 to file4" &&
+               git diff-tree -r -M HEAD &&
+               git p4 submit &&
+               p4 filelog //depot/file4 >filelog &&
+               ! grep " from //depot" filelog &&
+
+               git mv file4 file5 &&
+               git commit -a -m "Rename file4 to file5" &&
+               git diff-tree -r -M HEAD &&
+               git config git-p4.detectRenames true &&
+               git p4 submit &&
+               p4 filelog //depot/file5 >filelog &&
+               grep " from //depot/file4" filelog &&
+
+               git mv file5 file6 &&
+               echo update >>file6 &&
+               git add file6 &&
+               git commit -a -m "Rename file5 to file6 with changes" &&
+               git diff-tree -r -M HEAD &&
+               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)) &&
+               git p4 submit &&
+               p4 filelog //depot/file6 >filelog &&
+               ! grep " from //depot" filelog &&
+
+               git mv file6 file7 &&
+               echo update >>file7 &&
+               git add file7 &&
+               git commit -a -m "Rename file6 to file7 with changes" &&
+               git diff-tree -r -M HEAD &&
+               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)) &&
+               git p4 submit &&
+               p4 filelog //depot/file7 >filelog &&
+               grep " from //depot/file6" filelog
+       )
+'
+
+# Copy a file and confirm that copy is not detected in P4.
+# Copy a file with detectCopies option enabled and confirm that copy is not
+# detected in P4.
+# Modify and copy a file with detectCopies option enabled and confirm that copy
+# is detected in P4.
+# Copy a file with detectCopies and detectCopiesHarder options enabled and
+# confirm that copy is detected in P4.
+# Modify and copy a file, configure a bigger threshold in detectCopies and
+# confirm that copy is not detected in P4.
+# Modify and copy a file, configure a smaller threshold in detectCopies and
+# confirm that copy is detected in P4.
+test_expect_success 'detect copies' '
+       git p4 clone --dest="$git" //depot@all &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               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 &&
+               git p4 submit &&
+               p4 filelog //depot/file8 &&
+               p4 filelog //depot/file8 | test_must_fail grep -q "branch from" &&
+
+               cp file2 file9 &&
+               git add file9 &&
+               git commit -a -m "Copy file2 to file9" &&
+               git diff-tree -r -C HEAD &&
+               git config git-p4.detectCopies true &&
+               git p4 submit &&
+               p4 filelog //depot/file9 &&
+               p4 filelog //depot/file9 | test_must_fail grep -q "branch from" &&
+
+               echo "file2" >>file2 &&
+               cp file2 file10 &&
+               git add file2 file10 &&
+               git commit -a -m "Modify and copy file2 to file10" &&
+               git diff-tree -r -C HEAD &&
+               git p4 submit &&
+               p4 filelog //depot/file10 &&
+               p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
+
+               cp file2 file11 &&
+               git add file11 &&
+               git commit -a -m "Copy file2 to file11" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 &&
+               git config git-p4.detectCopiesHarder true &&
+               git p4 submit &&
+               p4 filelog //depot/file11 &&
+               p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
+
+               cp file2 file12 &&
+               echo "some text" >>file12 &&
+               git add file12 &&
+               git commit -a -m "Copy file2 to file12 with changes" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+               test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 -o "$src" = file11 &&
+               git config git-p4.detectCopies $(($level + 2)) &&
+               git p4 submit &&
+               p4 filelog //depot/file12 &&
+               p4 filelog //depot/file12 | test_must_fail grep -q "branch from" &&
+
+               cp file2 file13 &&
+               echo "different text" >>file13 &&
+               git add file13 &&
+               git commit -a -m "Copy file2 to file13 with changes" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+               test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 -o "$src" = file11 -o "$src" = file12 &&
+               git config git-p4.detectCopies $(($level - 2)) &&
+               git p4 submit &&
+               p4 filelog //depot/file13 &&
+               p4 filelog //depot/file13 | grep -q "branch from //depot/file"
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
index 256e6a0b3f3d001482cf0a21605f6a860d935d58..92d7eb47c2a412a300a9faca04d41777e0a29b00 100755 (executable)
@@ -3,21 +3,9 @@
 # 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
+. ./lib-bash.sh
 
 complete ()
 {
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
new file mode 100755 (executable)
index 0000000..f17c1f8
--- /dev/null
@@ -0,0 +1,456 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 SZEDER Gábor
+#
+
+test_description='test git-specific bash prompt functions'
+
+. ./lib-bash.sh
+
+. "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh"
+
+actual="$TRASH_DIRECTORY/actual"
+
+test_expect_success 'setup for prompt tests' '
+       mkdir -p subdir/subsubdir &&
+       git init otherrepo &&
+       echo 1 > file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       git tag -a -m msg1 t1 &&
+       git checkout -b b1 &&
+       echo 2 > file &&
+       git commit -m "second b1" file &&
+       echo 3 > file &&
+       git commit -m "third b1" file &&
+       git tag -a -m msg2 t2 &&
+       git checkout -b b2 master &&
+       echo 0 > file &&
+       git commit -m "second b2" file &&
+       git checkout master
+'
+
+test_expect_success 'gitdir - from command line (through $__git_dir)' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+       (
+               __git_dir="$TRASH_DIRECTORY/otherrepo/.git" &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - repo as argument' '
+       echo "otherrepo/.git" > expected &&
+       __gitdir "otherrepo" > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - remote as argument' '
+       echo "remote" > expected &&
+       __gitdir "remote" > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - .git directory in cwd' '
+       echo ".git" > expected &&
+       __gitdir > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - .git directory in parent' '
+       echo "$TRASH_DIRECTORY/.git" > expected &&
+       (
+               cd subdir/subsubdir &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - cwd is a .git directory' '
+       echo "." > expected &&
+       (
+               cd .git &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - parent is a .git directory' '
+       echo "$TRASH_DIRECTORY/.git" > expected &&
+       (
+               cd .git/refs/heads &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - $GIT_DIR set while .git directory in cwd' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+       (
+               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+               export GIT_DIR &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - $GIT_DIR set while .git directory in parent' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+       (
+               GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" &&
+               export GIT_DIR &&
+               cd subdir &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - non-existing $GIT_DIR' '
+       (
+               GIT_DIR="$TRASH_DIRECTORY/non-existing" &&
+               export GIT_DIR &&
+               test_must_fail __gitdir
+       )
+'
+
+test_expect_success 'gitdir - gitfile in cwd' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
+       test_when_finished "rm -f subdir/.git" &&
+       (
+               cd subdir &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - gitfile in parent' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+       echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
+       test_when_finished "rm -f subdir/.git" &&
+       (
+               cd subdir/subsubdir &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' '
+       echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+       mkdir otherrepo/dir &&
+       test_when_finished "rm -rf otherrepo/dir" &&
+       ln -s otherrepo/dir link &&
+       test_when_finished "rm -f link" &&
+       (
+               cd link &&
+               __gitdir > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'gitdir - not a git repository' '
+       (
+               cd subdir/subsubdir &&
+               GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" &&
+               export GIT_CEILING_DIRECTORIES &&
+               test_must_fail __gitdir
+       )
+'
+
+test_expect_success 'prompt - branch name' '
+       printf " (master)" > expected &&
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - detached head' '
+       printf " ((%s...))" $(git log -1 --format="%h" b1^) > expected &&
+       git checkout b1^ &&
+       test_when_finished "git checkout master" &&
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - describe detached head - contains' '
+       printf " ((t2~1))" > expected &&
+       git checkout b1^ &&
+       test_when_finished "git checkout master" &&
+       (
+               GIT_PS1_DESCRIBE_STYLE=contains &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - describe detached head - branch' '
+       printf " ((b1~1))" > expected &&
+       git checkout b1^ &&
+       test_when_finished "git checkout master" &&
+       (
+               GIT_PS1_DESCRIBE_STYLE=branch &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - describe detached head - describe' '
+       printf " ((t1-1-g%s))" $(git log -1 --format="%h" b1^) > expected &&
+       git checkout b1^ &&
+       test_when_finished "git checkout master" &&
+       (
+               GIT_PS1_DESCRIBE_STYLE=describe &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - describe detached head - default' '
+       printf " ((t2))" > expected &&
+       git checkout --detach b1 &&
+       test_when_finished "git checkout master" &&
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - inside .git directory' '
+       printf " (GIT_DIR!)" > expected &&
+       (
+               cd .git &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - deep inside .git directory' '
+       printf " (GIT_DIR!)" > expected &&
+       (
+               cd .git/refs/heads &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - inside bare repository' '
+       printf " (BARE:master)" > expected &&
+       git init --bare bare.git &&
+       test_when_finished "rm -rf bare.git" &&
+       (
+               cd bare.git &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - interactive rebase' '
+       printf " (b1|REBASE-i)" > expected
+       echo "#!$SHELL_PATH" >fake_editor.sh &&
+       cat >>fake_editor.sh <<\EOF &&
+echo "edit $(git log -1 --format="%h")" > "$1"
+EOF
+       test_when_finished "rm -f fake_editor.sh" &&
+       chmod a+x fake_editor.sh &&
+       test_set_editor "$TRASH_DIRECTORY/fake_editor.sh" &&
+       git checkout b1 &&
+       test_when_finished "git checkout master" &&
+       git rebase -i HEAD^ &&
+       test_when_finished "git rebase --abort"
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - rebase merge' '
+       printf " (b2|REBASE-m)" > expected &&
+       git checkout b2 &&
+       test_when_finished "git checkout master" &&
+       test_must_fail git rebase --merge b1 b2 &&
+       test_when_finished "git rebase --abort" &&
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - rebase' '
+       printf " ((t2)|REBASE)" > expected &&
+       git checkout b2 &&
+       test_when_finished "git checkout master" &&
+       test_must_fail git rebase b1 b2 &&
+       test_when_finished "git rebase --abort" &&
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - merge' '
+       printf " (b1|MERGING)" > expected &&
+       git checkout b1 &&
+       test_when_finished "git checkout master" &&
+       test_must_fail git merge b2 &&
+       test_when_finished "git reset --hard" &&
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - cherry-pick' '
+       printf " (master|CHERRY-PICKING)" > expected &&
+       test_must_fail git cherry-pick b1 &&
+       test_when_finished "git reset --hard" &&
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - bisect' '
+       printf " (master|BISECTING)" > expected &&
+       git bisect start &&
+       test_when_finished "git bisect reset" &&
+       __git_ps1 > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - clean' '
+       printf " (master)" > expected &&
+       (
+               GIT_PS1_SHOWDIRTYSTATE=y &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - dirty worktree' '
+       printf " (master *)" > expected &&
+       echo "dirty" > file &&
+       test_when_finished "git reset --hard" &&
+       (
+               GIT_PS1_SHOWDIRTYSTATE=y &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - dirty index' '
+       printf " (master +)" > expected &&
+       echo "dirty" > file &&
+       test_when_finished "git reset --hard" &&
+       git add -u &&
+       (
+               GIT_PS1_SHOWDIRTYSTATE=y &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - dirty index and worktree' '
+       printf " (master *+)" > expected &&
+       echo "dirty index" > file &&
+       test_when_finished "git reset --hard" &&
+       git add -u &&
+       echo "dirty worktree" > file &&
+       (
+               GIT_PS1_SHOWDIRTYSTATE=y &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - before root commit' '
+       printf " (master #)" > expected &&
+       (
+               GIT_PS1_SHOWDIRTYSTATE=y &&
+               cd otherrepo &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - disabled by config' '
+       printf " (master)" > expected &&
+       echo "dirty" > file &&
+       test_when_finished "git reset --hard" &&
+       test_config bash.showDirtyState false &&
+       (
+               GIT_PS1_SHOWDIRTYSTATE=y &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - dirty status indicator - not shown inside .git directory' '
+       printf " (GIT_DIR!)" > expected &&
+       echo "dirty" > file &&
+       test_when_finished "git reset --hard" &&
+       (
+               GIT_PS1_SHOWDIRTYSTATE=y &&
+               cd .git &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - stash status indicator - no stash' '
+       printf " (master)" > expected &&
+       (
+               GIT_PS1_SHOWSTASHSTATE=y &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - stash status indicator - stash' '
+       printf " (master $)" > expected &&
+       echo 2 >file &&
+       git stash &&
+       test_when_finished "git stash drop" &&
+       (
+               GIT_PS1_SHOWSTASHSTATE=y &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - stash status indicator - not shown inside .git directory' '
+       printf " (GIT_DIR!)" > expected &&
+       echo 2 >file &&
+       git stash &&
+       test_when_finished "git stash drop" &&
+       (
+               GIT_PS1_SHOWSTASHSTATE=y &&
+               cd .git &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - untracked files status indicator - no untracked files' '
+       printf " (master)" > expected &&
+       (
+               GIT_PS1_SHOWUNTRACKEDFILES=y &&
+               cd otherrepo &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - untracked files status indicator - untracked files' '
+       printf " (master %%)" > expected &&
+       (
+               GIT_PS1_SHOWUNTRACKEDFILES=y &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - untracked files status indicator - not shown inside .git directory' '
+       printf " (GIT_DIR!)" > expected &&
+       (
+               GIT_PS1_SHOWUNTRACKEDFILES=y &&
+               cd .git &&
+               __git_ps1 > "$actual"
+       ) &&
+       test_cmp expected "$actual"
+'
+
+test_expect_success 'prompt - format string starting with dash' '
+       printf -- "-master" > expected &&
+       __git_ps1 "-%s" > "$actual" &&
+       test_cmp expected "$actual"
+'
+
+test_done
index 16397691d951864f760b832ee066a63cd83f64fc..80daaca7806cbe1a6de0ddeba40400c8a811328e 100644 (file)
@@ -143,10 +143,19 @@ test_pause () {
 # Both <file> and <contents> default to <message>.
 
 test_commit () {
-       file=${2:-"$1.t"}
+       notick= &&
+       if test "z$1" = "z--notick"
+       then
+               notick=yes
+               shift
+       fi &&
+       file=${2:-"$1.t"} &&
        echo "${3-$1}" > "$file" &&
        git add "$file" &&
-       test_tick &&
+       if test -z "$notick"
+       then
+               test_tick
+       fi &&
        git commit -m "$1" &&
        git tag "$1"
 }
diff --git a/test-credential.c b/test-credential.c
deleted file mode 100644 (file)
index dee200e..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-#include "cache.h"
-#include "credential.h"
-#include "string-list.h"
-
-static const char usage_msg[] =
-"test-credential <fill|approve|reject> [helper...]";
-
-int main(int argc, const char **argv)
-{
-       const char *op;
-       struct credential c = CREDENTIAL_INIT;
-       int i;
-
-       op = argv[1];
-       if (!op)
-               usage(usage_msg);
-       for (i = 2; i < argc; i++)
-               string_list_append(&c.helpers, argv[i]);
-
-       if (credential_read(&c, stdin) < 0)
-               die("unable to read credential from stdin");
-
-       if (!strcmp(op, "fill")) {
-               credential_fill(&c);
-               if (c.username)
-                       printf("username=%s\n", c.username);
-               if (c.password)
-                       printf("password=%s\n", c.password);
-       }
-       else if (!strcmp(op, "approve"))
-               credential_approve(&c);
-       else if (!strcmp(op, "reject"))
-               credential_reject(&c);
-       else
-               usage(usage_msg);
-
-       return 0;
-}
index 7ec9b13c9b2ceb84fb6dd0dd229e9f6cb8c1a635..ef1d7bae1441e07b6cb74e83a23d07b4720a8ebf 100644 (file)
@@ -87,6 +87,5 @@ int main(int argc, char *argv[])
                die("input error");
        if (ferror(stdout))
                die("output error");
-       buffer_reset(&stdin_buf);
        return 0;
 }
index 332a5f711df8f3e3fea3305eb5ecb10de5581033..83633a21e586fd94015bd983b683ffc10b2c1963 100644 (file)
@@ -31,9 +31,7 @@ static int apply_delta(int argc, char *argv[])
                die_errno("cannot close preimage");
        if (buffer_deinit(&delta))
                die_errno("cannot close delta");
-       buffer_reset(&preimage);
        strbuf_release(&preimage_view.buf);
-       buffer_reset(&delta);
        return 0;
 }
 
diff --git a/tree.c b/tree.c
index 676e9f710ca8d5a568e0c6ea2fa88132da81b48c..62fed632d8a1ade389dd645b5887155c21ae25ba 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -22,7 +22,8 @@ static int read_one_entry_opt(const unsigned char *sha1, const char *base, int b
        ce = xcalloc(1, size);
 
        ce->ce_mode = create_ce_mode(mode);
-       ce->ce_flags = create_ce_flags(baselen + len, stage);
+       ce->ce_flags = create_ce_flags(stage);
+       ce->ce_namelen = baselen + len;
        memcpy(ce->name, base, baselen);
        memcpy(ce->name + baselen, pathname, len+1);
        hashcpy(ce->sha1, sha1);
@@ -133,8 +134,8 @@ static int cmp_cache_name_compare(const void *a_, const void *b_)
 
        ce1 = *((const struct cache_entry **)a_);
        ce2 = *((const struct cache_entry **)b_);
-       return cache_name_compare(ce1->name, ce1->ce_flags,
-                                 ce2->name, ce2->ce_flags);
+       return cache_name_stage_compare(ce1->name, ce1->ce_namelen, ce_stage(ce1),
+                                 ce2->name, ce2->ce_namelen, ce_stage(ce2));
 }
 
 int read_tree(struct tree *tree, int stage, struct pathspec *match)
index 33a581924e11167dc546bdf97c8d49460b43674e..6d9636623a2cbb5d5a38eb58d625ef0164d953c0 100644 (file)
@@ -539,7 +539,8 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, con
        struct cache_entry *ce = xcalloc(1, cache_entry_size(len));
 
        ce->ce_mode = create_ce_mode(n->mode);
-       ce->ce_flags = create_ce_flags(len, stage);
+       ce->ce_flags = create_ce_flags(stage);
+       ce->ce_namelen = len;
        hashcpy(ce->sha1, n->sha1);
        make_traverse_path(ce->name, info, n);
 
@@ -1296,7 +1297,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce,
         * First let's make sure we do not have a local modification
         * in that directory.
         */
-       namelen = strlen(ce->name);
+       namelen = ce_namelen(ce);
        for (i = locate_in_src_index(ce, o);
             i < o->src_index->cache_nr;
             i++) {
diff --git a/utf8.c b/utf8.c
index 8acbc660d31a3552a4451749353139e0dcd371bd..a544f15456656df642253533eaa28885ce3496a6 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -433,19 +433,12 @@ int is_encoding_utf8(const char *name)
 #else
        typedef char * iconv_ibp;
 #endif
-char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding)
+char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv)
 {
-       iconv_t conv;
-       size_t insz, outsz, outalloc;
+       size_t outsz, outalloc;
        char *out, *outpos;
        iconv_ibp cp;
 
-       if (!in_encoding)
-               return NULL;
-       conv = iconv_open(out_encoding, in_encoding);
-       if (conv == (iconv_t) -1)
-               return NULL;
-       insz = strlen(in);
        outsz = insz;
        outalloc = outsz + 1; /* for terminating NUL */
        out = xmalloc(outalloc);
@@ -459,7 +452,6 @@ char *reencode_string(const char *in, const char *out_encoding, const char *in_e
                        size_t sofar;
                        if (errno != E2BIG) {
                                free(out);
-                               iconv_close(conv);
                                return NULL;
                        }
                        /* insz has remaining number of bytes.
@@ -478,6 +470,20 @@ char *reencode_string(const char *in, const char *out_encoding, const char *in_e
                        break;
                }
        }
+       return out;
+}
+
+char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding)
+{
+       iconv_t conv;
+       char *out;
+
+       if (!in_encoding)
+               return NULL;
+       conv = iconv_open(out_encoding, in_encoding);
+       if (conv == (iconv_t) -1)
+               return NULL;
+       out = reencode_string_iconv(in, strlen(in), conv);
        iconv_close(conv);
        return out;
 }
diff --git a/utf8.h b/utf8.h
index 81f2c82fabcf63e3bb02c15beb4a0409afd9ab7b..3c0ae7624e027a802c38c4afb9fe5b0a590e093d 100644 (file)
--- a/utf8.h
+++ b/utf8.h
@@ -14,6 +14,7 @@ int strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len,
                             int indent, int indent2, int width);
 
 #ifndef NO_ICONV
+char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv);
 char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
 #else
 #define reencode_string(a,b,c) NULL
index b823b8519c6dc76aa534e056c44eea7210e716f5..1f04697866bd481e24f17e0833c1c2d29c8e12e2 100644 (file)
@@ -42,11 +42,6 @@ void fast_export_deinit(void)
                die_errno("error closing fast-import feedback stream");
 }
 
-void fast_export_reset(void)
-{
-       buffer_reset(&report_buffer);
-}
-
 void fast_export_delete(const char *path)
 {
        putchar('D');
@@ -163,7 +158,7 @@ static int parse_cat_response_line(const char *header, off_t *len)
 
        if (ends_with(header, headerlen, " missing"))
                return error("cat-blob reports missing blob: %s", header);
-       type = memmem(header, headerlen, " blob ", strlen(" blob "));
+       type = strstr(header, " blob ");
        if (!type)
                return error("cat-blob header has wrong object type: %s", header);
        n = strtoumax(type + strlen(" blob "), (char **) &end, 10);
@@ -259,7 +254,7 @@ static int parse_ls_response(const char *response, uint32_t *mode,
        }
 
        /* Mode. */
-       if (response_end - response < strlen("100644") ||
+       if (response_end - response < (signed) strlen("100644") ||
            response[strlen("100644")] != ' ')
                die("invalid ls response: missing mode: %s", response);
        *mode = 0;
@@ -272,7 +267,7 @@ static int parse_ls_response(const char *response, uint32_t *mode,
        }
 
        /* ' blob ' or ' tree ' */
-       if (response_end - response < strlen(" blob ") ||
+       if (response_end - response < (signed) strlen(" blob ") ||
            (response[1] != 'b' && response[1] != 't'))
                die("unexpected ls response: not a tree or blob: %s", response);
        response += strlen(" blob ");
index aa629f54ff5b49075eb6dafd1d480077fa8c16f2..8823aca15c9592ce732e741e879aa182f2273202 100644 (file)
@@ -6,7 +6,6 @@ struct line_buffer;
 
 void fast_export_init(int fd);
 void fast_export_deinit(void);
-void fast_export_reset(void);
 
 void fast_export_delete(const char *path);
 void fast_export_modify(const char *path, uint32_t mode, const char *dataref);
index 01fcb842f1dcc27517109f0d317a239d3493dc4a..57cc1cec033f638e6d408370039b46b653289e84 100644 (file)
@@ -124,7 +124,3 @@ off_t buffer_skip_bytes(struct line_buffer *buf, off_t nbytes)
        }
        return done;
 }
-
-void buffer_reset(struct line_buffer *buf)
-{
-}
index 8901f214bafce3917025721137a85d827cd1c1a6..ee23b4f490258548a7c50fa7c9a50213b70f5359 100644 (file)
@@ -14,7 +14,6 @@ struct line_buffer {
 int buffer_init(struct line_buffer *buf, const char *filename);
 int buffer_fdinit(struct line_buffer *buf, int fd);
 int buffer_deinit(struct line_buffer *buf);
-void buffer_reset(struct line_buffer *buf);
 
 int buffer_tmpfile_init(struct line_buffer *buf);
 FILE *buffer_tmpfile_rewind(struct line_buffer *buf);  /* prepare to write. */
index ec2707c9c421424a8b34f456aa7472c4c0f45559..f11d490995ee7e3367c1d18e1058f2e322cfa177 100644 (file)
@@ -54,7 +54,7 @@ int move_window(struct sliding_view *view, off_t off, size_t width)
                return -1;
        if (off < view->off || off + width < view->off + view->width)
                return error("invalid delta: window slides left");
-       if (view->max_off >= 0 && view->max_off < off + width)
+       if (view->max_off >= 0 && view->max_off < off + (off_t) width)
                return error("delta preimage ends early");
 
        file_offset = view->off + view->buf.len;
index 1647c1a780c5a6153455fdc73b6e422b474dab8e..74c97c4543d1fa707961aab1453edfb69b2679d2 100644 (file)
@@ -77,8 +77,9 @@ static int error_short_read(struct line_buffer *input)
 static int read_chunk(struct line_buffer *delta, off_t *delta_len,
                      struct strbuf *buf, size_t len)
 {
+       assert(*delta_len >= 0);
        strbuf_reset(buf);
-       if (len > *delta_len ||
+       if (len > (uintmax_t) *delta_len ||
            buffer_read_binary(delta, buf, len) != len)
                return error_short_read(delta);
        *delta_len -= buf->len;
@@ -258,6 +259,7 @@ static int apply_window_in_core(struct window *ctx)
 static int apply_one_window(struct line_buffer *delta, off_t *delta_len,
                            struct sliding_view *preimage, FILE *out)
 {
+       int rv = -1;
        struct window ctx = WINDOW_INIT(preimage);
        size_t out_len;
        size_t instructions_len;
@@ -275,27 +277,26 @@ static int apply_one_window(struct line_buffer *delta, off_t *delta_len,
        if (apply_window_in_core(&ctx))
                goto error_out;
        if (ctx.out.len != out_len) {
-               error("invalid delta: incorrect postimage length");
+               rv = error("invalid delta: incorrect postimage length");
                goto error_out;
        }
        if (write_strbuf(&ctx.out, out))
                goto error_out;
-       window_release(&ctx);
-       return 0;
+       rv = 0;
 error_out:
        window_release(&ctx);
-       return -1;
+       return rv;
 }
 
 int svndiff0_apply(struct line_buffer *delta, off_t delta_len,
                        struct sliding_view *preimage, FILE *postimage)
 {
-       assert(delta && preimage && postimage);
+       assert(delta && preimage && postimage && delta_len >= 0);
 
        if (read_magic(delta, &delta_len))
                return -1;
        while (delta_len) {     /* For each window: */
-               off_t pre_off = pre_off; /* stupid GCC... */
+               off_t pre_off = -1;
                size_t pre_len;
 
                if (read_offset(delta, &pre_off, &delta_len) ||
index 0899790a3331d82b72ba3e58fd7ff6ff83b38466..2b168aee75ddc299f22c738b26f07e12c285f9ae 100644 (file)
 #define NODE_CTX 2     /* node metadata */
 #define INTERNODE_CTX 3        /* between nodes */
 
-#define LENGTH_UNKNOWN (~0)
 #define DATE_RFC2822_LEN 31
 
 static struct line_buffer input = LINE_BUFFER_INIT;
 
 static struct {
-       uint32_t action, propLength, srcRev, type;
-       off_t text_length;
+       uint32_t action, srcRev, type;
+       off_t prop_length, text_length;
        struct strbuf src, dst;
        uint32_t text_delta, prop_delta;
 } node_ctx;
@@ -61,7 +60,7 @@ static void reset_node_ctx(char *fname)
 {
        node_ctx.type = 0;
        node_ctx.action = NODEACT_UNKNOWN;
-       node_ctx.propLength = LENGTH_UNKNOWN;
+       node_ctx.prop_length = -1;
        node_ctx.text_length = -1;
        strbuf_reset(&node_ctx.src);
        node_ctx.srcRev = 0;
@@ -209,7 +208,7 @@ static void read_props(void)
 static void handle_node(void)
 {
        const uint32_t type = node_ctx.type;
-       const int have_props = node_ctx.propLength != LENGTH_UNKNOWN;
+       const int have_props = node_ctx.prop_length != -1;
        const int have_text = node_ctx.text_length != -1;
        /*
         * Old text for this node:
@@ -273,7 +272,7 @@ static void handle_node(void)
        if (have_props) {
                if (!node_ctx.prop_delta)
                        node_ctx.type = type;
-               if (node_ctx.propLength)
+               if (node_ctx.prop_length)
                        read_props();
        }
 
@@ -361,7 +360,7 @@ void svndump_read(const char *url)
                        reset_rev_ctx(atoi(val));
                        break;
                case sizeof("Node-path"):
-                       if (prefixcmp(t, "Node-"))
+                       if (constcmp(t, "Node-"))
                                continue;
                        if (!constcmp(t + strlen("Node-"), "path")) {
                                if (active_ctx == NODE_CTX)
@@ -409,22 +408,26 @@ void svndump_read(const char *url)
                        node_ctx.srcRev = atoi(val);
                        break;
                case sizeof("Text-content-length"):
-                       if (!constcmp(t, "Text-content-length")) {
+                       if (constcmp(t, "Text") && constcmp(t, "Prop"))
+                               continue;
+                       if (constcmp(t + 4, "-content-length"))
+                               continue;
+                       {
                                char *end;
-                               uintmax_t textlen;
+                               uintmax_t len;
 
-                               textlen = strtoumax(val, &end, 10);
+                               len = strtoumax(val, &end, 10);
                                if (!isdigit(*val) || *end)
                                        die("invalid dump: non-numeric length %s", val);
-                               if (textlen > maximum_signed_value_of_type(off_t))
+                               if (len > maximum_signed_value_of_type(off_t))
                                        die("unrepresentable length in dump: %s", val);
-                               node_ctx.text_length = (off_t) textlen;
+
+                               if (*t == 'T')
+                                       node_ctx.text_length = (off_t) len;
+                               else
+                                       node_ctx.prop_length = (off_t) len;
                                break;
                        }
-                       if (constcmp(t, "Prop-content-length"))
-                               continue;
-                       node_ctx.propLength = atoi(val);
-                       break;
                case sizeof("Text-delta"):
                        if (!constcmp(t, "Text-delta")) {
                                node_ctx.text_delta = !strcmp(val, "true");
@@ -499,8 +502,6 @@ void svndump_deinit(void)
 
 void svndump_reset(void)
 {
-       fast_export_reset();
-       buffer_reset(&input);
        strbuf_release(&dump_ctx.uuid);
        strbuf_release(&dump_ctx.url);
        strbuf_release(&rev_ctx.log);
diff --git a/version.c b/version.c
new file mode 100644 (file)
index 0000000..f98d5a6
--- /dev/null
+++ b/version.c
@@ -0,0 +1,17 @@
+#include "git-compat-util.h"
+#include "version.h"
+
+const char git_version_string[] = GIT_VERSION;
+
+const char *git_user_agent(void)
+{
+       static const char *agent = NULL;
+
+       if (!agent) {
+               agent = getenv("GIT_USER_AGENT");
+               if (!agent)
+                       agent = GIT_USER_AGENT;
+       }
+
+       return agent;
+}
diff --git a/version.h b/version.h
new file mode 100644 (file)
index 0000000..fd9cdd6
--- /dev/null
+++ b/version.h
@@ -0,0 +1,8 @@
+#ifndef VERSION_H
+#define VERSION_H
+
+extern const char git_version_string[];
+
+const char *git_user_agent(void);
+
+#endif /* VERSION_H */
index dd6d8c41068e6664cff1c07100e482e7ca9f0959..c110cbc1250b08fe491d60244e1c8f27c0201532 100644 (file)
@@ -12,6 +12,7 @@
 #include "refs.h"
 #include "submodule.h"
 #include "column.h"
+#include "strbuf.h"
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -130,9 +131,34 @@ void wt_status_prepare(struct wt_status *s)
 
 static void wt_status_print_unmerged_header(struct wt_status *s)
 {
+       int i;
+       int del_mod_conflict = 0;
+       int both_deleted = 0;
+       int not_deleted = 0;
        const char *c = color(WT_STATUS_HEADER, s);
 
        status_printf_ln(s, c, _("Unmerged paths:"));
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct string_list_item *it = &(s->change.items[i]);
+               struct wt_status_change_data *d = it->util;
+
+               switch (d->stagemask) {
+               case 0:
+                       break;
+               case 1:
+                       both_deleted = 1;
+                       break;
+               case 3:
+               case 5:
+                       del_mod_conflict = 1;
+                       break;
+               default:
+                       not_deleted = 1;
+                       break;
+               }
+       }
+
        if (!advice_status_hints)
                return;
        if (s->whence != FROM_COMMIT)
@@ -141,7 +167,17 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
                status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
        else
                status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
-       status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+
+       if (!both_deleted) {
+               if (!del_mod_conflict)
+                       status_printf_ln(s, c, _("  (use \"git add <file>...\" to mark resolution)"));
+               else
+                       status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+       } else if (!del_mod_conflict && !not_deleted) {
+               status_printf_ln(s, c, _("  (use \"git rm <file>...\" to mark resolution)"));
+       } else {
+               status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
+       }
        status_printf_ln(s, c, "");
 }
 
@@ -728,6 +764,211 @@ static void wt_status_print_tracking(struct wt_status *s)
        color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 }
 
+static int has_unmerged(struct wt_status *s)
+{
+       int i;
+
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               d = s->change.items[i].util;
+               if (d->stagemask)
+                       return 1;
+       }
+       return 0;
+}
+
+static void show_merge_in_progress(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       if (has_unmerged(s)) {
+               status_printf_ln(s, color, _("You have unmerged paths."));
+               if (advice_status_hints)
+                       status_printf_ln(s, color,
+                               _("  (fix conflicts and run \"git commit\")"));
+       } else {
+               status_printf_ln(s, color,
+                       _("All conflicts fixed but you are still merging."));
+               if (advice_status_hints)
+                       status_printf_ln(s, color,
+                               _("  (use \"git commit\" to conclude merge)"));
+       }
+       wt_status_print_trailer(s);
+}
+
+static void show_am_in_progress(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       status_printf_ln(s, color,
+               _("You are in the middle of an am session."));
+       if (state->am_empty_patch)
+               status_printf_ln(s, color,
+                       _("The current patch is empty."));
+       if (advice_status_hints) {
+               if (!state->am_empty_patch)
+                       status_printf_ln(s, color,
+                               _("  (fix conflicts and then run \"git am --resolved\")"));
+               status_printf_ln(s, color,
+                       _("  (use \"git am --skip\" to skip this patch)"));
+               status_printf_ln(s, color,
+                       _("  (use \"git am --abort\" to restore the original branch)"));
+       }
+       wt_status_print_trailer(s);
+}
+
+static char *read_line_from_git_path(const char *filename)
+{
+       struct strbuf buf = STRBUF_INIT;
+       FILE *fp = fopen(git_path("%s", filename), "r");
+       if (!fp) {
+               strbuf_release(&buf);
+               return NULL;
+       }
+       strbuf_getline(&buf, fp, '\n');
+       if (!fclose(fp)) {
+               return strbuf_detach(&buf, NULL);
+       } else {
+               strbuf_release(&buf);
+               return NULL;
+       }
+}
+
+static int split_commit_in_progress(struct wt_status *s)
+{
+       int split_in_progress = 0;
+       char *head = read_line_from_git_path("HEAD");
+       char *orig_head = read_line_from_git_path("ORIG_HEAD");
+       char *rebase_amend = read_line_from_git_path("rebase-merge/amend");
+       char *rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head");
+
+       if (!head || !orig_head || !rebase_amend || !rebase_orig_head ||
+           !s->branch || strcmp(s->branch, "HEAD"))
+               return split_in_progress;
+
+       if (!strcmp(rebase_amend, rebase_orig_head)) {
+               if (strcmp(head, rebase_amend))
+                       split_in_progress = 1;
+       } else if (strcmp(orig_head, rebase_orig_head)) {
+               split_in_progress = 1;
+       }
+
+       if (!s->amend && !s->nowarn && !s->workdir_dirty)
+               split_in_progress = 0;
+
+       free(head);
+       free(orig_head);
+       free(rebase_amend);
+       free(rebase_orig_head);
+       return split_in_progress;
+}
+
+static void show_rebase_in_progress(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       struct stat st;
+
+       if (has_unmerged(s)) {
+               status_printf_ln(s, color, _("You are currently rebasing."));
+               if (advice_status_hints) {
+                       status_printf_ln(s, color,
+                               _("  (fix conflicts and then run \"git rebase --continue\")"));
+                       status_printf_ln(s, color,
+                               _("  (use \"git rebase --skip\" to skip this patch)"));
+                       status_printf_ln(s, color,
+                               _("  (use \"git rebase --abort\" to check out the original branch)"));
+               }
+       } else if (state->rebase_in_progress || !stat(git_path("MERGE_MSG"), &st)) {
+               status_printf_ln(s, color, _("You are currently rebasing."));
+               if (advice_status_hints)
+                       status_printf_ln(s, color,
+                               _("  (all conflicts fixed: run \"git rebase --continue\")"));
+       } else if (split_commit_in_progress(s)) {
+               status_printf_ln(s, color, _("You are currently splitting a commit during a rebase."));
+               if (advice_status_hints)
+                       status_printf_ln(s, color,
+                               _("  (Once your working directory is clean, run \"git rebase --continue\")"));
+       } else {
+               status_printf_ln(s, color, _("You are currently editing a commit during a rebase."));
+               if (advice_status_hints && !s->amend) {
+                       status_printf_ln(s, color,
+                               _("  (use \"git commit --amend\" to amend the current commit)"));
+                       status_printf_ln(s, color,
+                               _("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
+               }
+       }
+       wt_status_print_trailer(s);
+}
+
+static void show_cherry_pick_in_progress(struct wt_status *s,
+                                       struct wt_status_state *state,
+                                       const char *color)
+{
+       status_printf_ln(s, color, _("You are currently cherry-picking."));
+       if (advice_status_hints) {
+               if (has_unmerged(s))
+                       status_printf_ln(s, color,
+                               _("  (fix conflicts and run \"git commit\")"));
+               else
+                       status_printf_ln(s, color,
+                               _("  (all conflicts fixed: run \"git commit\")"));
+       }
+       wt_status_print_trailer(s);
+}
+
+static void show_bisect_in_progress(struct wt_status *s,
+                               struct wt_status_state *state,
+                               const char *color)
+{
+       status_printf_ln(s, color, _("You are currently bisecting."));
+       if (advice_status_hints)
+               status_printf_ln(s, color,
+                       _("  (use \"git bisect reset\" to get back to the original branch)"));
+       wt_status_print_trailer(s);
+}
+
+static void wt_status_print_state(struct wt_status *s)
+{
+       const char *state_color = color(WT_STATUS_HEADER, s);
+       struct wt_status_state state;
+       struct stat st;
+
+       memset(&state, 0, sizeof(state));
+
+       if (!stat(git_path("MERGE_HEAD"), &st)) {
+               state.merge_in_progress = 1;
+       } else if (!stat(git_path("rebase-apply"), &st)) {
+               if (!stat(git_path("rebase-apply/applying"), &st)) {
+                       state.am_in_progress = 1;
+                       if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
+                               state.am_empty_patch = 1;
+               } else {
+                       state.rebase_in_progress = 1;
+               }
+       } else if (!stat(git_path("rebase-merge"), &st)) {
+               if (!stat(git_path("rebase-merge/interactive"), &st))
+                       state.rebase_interactive_in_progress = 1;
+               else
+                       state.rebase_in_progress = 1;
+       } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) {
+               state.cherry_pick_in_progress = 1;
+       }
+       if (!stat(git_path("BISECT_LOG"), &st))
+               state.bisect_in_progress = 1;
+
+       if (state.merge_in_progress)
+               show_merge_in_progress(s, &state, state_color);
+       else if (state.am_in_progress)
+               show_am_in_progress(s, &state, state_color);
+       else if (state.rebase_in_progress || state.rebase_interactive_in_progress)
+               show_rebase_in_progress(s, &state, state_color);
+       else if (state.cherry_pick_in_progress)
+               show_cherry_pick_in_progress(s, &state, state_color);
+       if (state.bisect_in_progress)
+               show_bisect_in_progress(s, &state, state_color);
+}
+
 void wt_status_print(struct wt_status *s)
 {
        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
@@ -750,6 +991,7 @@ void wt_status_print(struct wt_status *s)
                        wt_status_print_tracking(s);
        }
 
+       wt_status_print_state(s);
        if (s->is_initial) {
                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
                status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
index 14aa9f7e13342f1210ead3aca658a6ed60961649..f8fc58cc0ac8ec73d81fba441b1899b934eaadb4 100644 (file)
@@ -71,6 +71,16 @@ struct wt_status {
        struct string_list ignored;
 };
 
+struct wt_status_state {
+       int merge_in_progress;
+       int am_in_progress;
+       int am_empty_patch;
+       int rebase_in_progress;
+       int rebase_interactive_in_progress;
+       int cherry_pick_in_progress;
+       int bisect_in_progress;
+};
+
 void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);