Code

Merge branch 'pb/configure'
authorJunio C Hamano <junkio@cox.net>
Wed, 26 Jul 2006 20:35:35 +0000 (13:35 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 26 Jul 2006 20:35:35 +0000 (13:35 -0700)
* pb/configure:
  Rename man1 and man7 variables to man1dir and man7dir
  Allow INSTALL, bindir, mandir to be set in main Makefile

298 files changed:
.gitignore
Documentation/Makefile
Documentation/SubmittingPatches
Documentation/asciidoc.conf
Documentation/config.txt
Documentation/cvs-migration.txt
Documentation/diff-options.txt
Documentation/git-commit.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-diff-files.txt
Documentation/git-diff.txt
Documentation/git-format-patch.txt
Documentation/git-init-db.txt
Documentation/git-instaweb.txt [new file with mode: 0644]
Documentation/git-ls-files.txt
Documentation/git-mailsplit.txt
Documentation/git-merge.txt
Documentation/git-name-rev.txt
Documentation/git-p4import.txt
Documentation/git-pack-redundant.txt
Documentation/git-push.txt
Documentation/git-repo-config.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-show-branch.txt
Documentation/git-svn.txt [new file with mode: 0644]
Documentation/git-tar-tree.txt
Documentation/git-tools.txt
Documentation/git-upload-tar.txt
Documentation/git.txt
Documentation/glossary.txt
Documentation/howto/isolate-bugs-with-bisect.txt
Documentation/howto/rebase-from-internal-branch.txt
Documentation/repository-layout.txt
Documentation/technical/pack-heuristics.txt
Documentation/urls.txt
INSTALL
Makefile
blame.c
blob.c
builtin-add.c
builtin-apply.c
builtin-diff-files.c
builtin-diff-index.c
builtin-diff-stages.c
builtin-diff-tree.c
builtin-diff.c
builtin-fmt-merge-msg.c [new file with mode: 0644]
builtin-grep.c
builtin-help.c
builtin-log.c
builtin-mailinfo.c
builtin-prune.c [new file with mode: 0644]
builtin-push.c
builtin-read-tree.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-rm.c
builtin-show-branch.c
builtin-tar-tree.c
builtin-update-index.c
builtin-update-ref.c
builtin-write-tree.c
builtin.h
cache.h
checkout-index.c
combine-diff.c
commit.c
commit.h
compat/subprocess.py
config.c
connect.c
contrib/colordiff/colordiff.perl
contrib/emacs/git.el
contrib/emacs/vc-git.el
contrib/git-svn/.gitignore [deleted file]
contrib/git-svn/Makefile [deleted file]
contrib/git-svn/git-svn.perl [deleted file]
contrib/git-svn/git-svn.txt [deleted file]
contrib/git-svn/t/lib-git-svn.sh [deleted file]
contrib/git-svn/t/t0000-contrib-git-svn.sh [deleted file]
contrib/git-svn/t/t0001-contrib-git-svn-props.sh [deleted file]
contrib/git-svn/t/t0002-deep-rmdir.sh [deleted file]
contrib/git-svn/t/t0003-graft-branches.sh [deleted file]
contrib/git-svn/t/t0004-follow-parent.sh [deleted file]
contrib/git-svn/t/t0005-commit-diff.sh [deleted file]
convert-objects.c
csum-file.c
daemon.c
describe.c
diff-delta.c
diff.c
diff.h
diffcore-rename.c
dir.c
environment.c
exec_cmd.c
fetch-clone.c
fetch-pack.c
fetch.c
fsck-objects.c
git-am.sh
git-annotate.perl
git-applypatch.sh
git-archimport.perl
git-bisect.sh
git-clone.sh
git-commit.sh
git-cvsexportcommit.perl
git-cvsserver.perl
git-fetch.sh
git-fmt-merge-msg.perl [deleted file]
git-instaweb.sh [new file with mode: 0755]
git-merge-one-file.sh
git-merge-recursive.py
git-merge.sh
git-prune.sh [deleted file]
git-pull.sh
git-push.sh
git-quiltimport.sh
git-rebase.sh
git-repack.sh
git-reset.sh
git-resolve.sh
git-revert.sh
git-send-email.perl
git-svn.perl [new file with mode: 0755]
git-svnimport.perl
git.c
gitweb/gitweb.cgi
gitweb/gitweb.css
http-fetch.c
http-push.c
log-tree.c
log-tree.h
merge-base.c
merge-file.c [new file with mode: 0644]
merge-tree.c
mktag.c
name-rev.c
object.c
object.h
pack-objects.c
pack.h
pager.c
peek-remote.c
ppc/sha1ppc.S
quote.c
quote.h
read-cache.c
refs.c
revision.c
revision.h
send-pack.c
server-info.c
sha1_file.c
sha1_name.c
ssh-fetch.c
t/Makefile
t/annotate-tests.sh
t/lib-git-svn.sh [new file with mode: 0644]
t/t1400-update-ref.sh
t/t4013-diff-various.sh [new file with mode: 0755]
t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master [new file with mode: 0644]
t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side [new file with mode: 0644]
t/t4013/diff.diff-tree_--cc_--patch-with-stat_master [new file with mode: 0644]
t/t4013/diff.diff-tree_--cc_--stat_--summary_master [new file with mode: 0644]
t/t4013/diff.diff-tree_--cc_--stat_--summary_side [new file with mode: 0644]
t/t4013/diff.diff-tree_--cc_--stat_master [new file with mode: 0644]
t/t4013/diff.diff-tree_--cc_master [new file with mode: 0644]
t/t4013/diff.diff-tree_--patch-with-raw_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--patch-with-stat_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty=oneline_--patch-with-raw_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty=oneline_--patch-with-stat_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-raw_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty=oneline_--root_-p_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty=oneline_--root_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty=oneline_-p_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty=oneline_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--patch-with-raw_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--patch-with-stat_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--root_--patch-with-raw_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--root_--stat_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--root_--summary_-r_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--root_--summary_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--root_-p_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--root_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--stat_--summary_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--stat_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_--summary_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_-p_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_-p_side [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--pretty_side [new file with mode: 0644]
t/t4013/diff.diff-tree_--root_--abbrev_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--root_--patch-with-raw_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--root_--patch-with-stat_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--root_-p_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--root_-r_--abbrev=4_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--root_-r_--abbrev_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--root_-r_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_--root_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_-c_--abbrev_master [new file with mode: 0644]
t/t4013/diff.diff-tree_-c_--stat_--summary_master [new file with mode: 0644]
t/t4013/diff.diff-tree_-c_--stat_--summary_side [new file with mode: 0644]
t/t4013/diff.diff-tree_-c_--stat_master [new file with mode: 0644]
t/t4013/diff.diff-tree_-c_master [new file with mode: 0644]
t/t4013/diff.diff-tree_-p_-m_master [new file with mode: 0644]
t/t4013/diff.diff-tree_-p_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_-p_master [new file with mode: 0644]
t/t4013/diff.diff-tree_-r_--abbrev=4_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_-r_--abbrev_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_-r_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_initial [new file with mode: 0644]
t/t4013/diff.diff-tree_master [new file with mode: 0644]
t/t4013/diff.diff_--abbrev_initial..side [new file with mode: 0644]
t/t4013/diff.diff_--patch-with-raw_-r_initial..side [new file with mode: 0644]
t/t4013/diff.diff_--patch-with-raw_initial..side [new file with mode: 0644]
t/t4013/diff.diff_--patch-with-stat_-r_initial..side [new file with mode: 0644]
t/t4013/diff.diff_--patch-with-stat_initial..side [new file with mode: 0644]
t/t4013/diff.diff_--stat_initial..side [new file with mode: 0644]
t/t4013/diff.diff_-r_--stat_initial..side [new file with mode: 0644]
t/t4013/diff.diff_-r_initial..side [new file with mode: 0644]
t/t4013/diff.diff_initial..side [new file with mode: 0644]
t/t4013/diff.format-patch_--attach_--stdout_initial..master [new file with mode: 0644]
t/t4013/diff.format-patch_--attach_--stdout_initial..master^ [new file with mode: 0644]
t/t4013/diff.format-patch_--attach_--stdout_initial..side [new file with mode: 0644]
t/t4013/diff.format-patch_--stdout_initial..master [new file with mode: 0644]
t/t4013/diff.format-patch_--stdout_initial..master^ [new file with mode: 0644]
t/t4013/diff.format-patch_--stdout_initial..side [new file with mode: 0644]
t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ [new file with mode: 0644]
t/t4013/diff.log_--patch-with-stat_master [new file with mode: 0644]
t/t4013/diff.log_--patch-with-stat_master_--_dir_ [new file with mode: 0644]
t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master [new file with mode: 0644]
t/t4013/diff.log_--root_--patch-with-stat_--summary_master [new file with mode: 0644]
t/t4013/diff.log_--root_--patch-with-stat_master [new file with mode: 0644]
t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master [new file with mode: 0644]
t/t4013/diff.log_--root_-p_master [new file with mode: 0644]
t/t4013/diff.log_--root_master [new file with mode: 0644]
t/t4013/diff.log_-SF_-p_master [new file with mode: 0644]
t/t4013/diff.log_-SF_master [new file with mode: 0644]
t/t4013/diff.log_-p_master [new file with mode: 0644]
t/t4013/diff.log_master [new file with mode: 0644]
t/t4013/diff.show_--patch-with-raw_side [new file with mode: 0644]
t/t4013/diff.show_--patch-with-stat_--summary_side [new file with mode: 0644]
t/t4013/diff.show_--patch-with-stat_side [new file with mode: 0644]
t/t4013/diff.show_--root_initial [new file with mode: 0644]
t/t4013/diff.show_--stat_--summary_side [new file with mode: 0644]
t/t4013/diff.show_--stat_side [new file with mode: 0644]
t/t4013/diff.show_initial [new file with mode: 0644]
t/t4013/diff.show_master [new file with mode: 0644]
t/t4013/diff.show_side [new file with mode: 0644]
t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_ [new file with mode: 0644]
t/t4013/diff.whatchanged_--patch-with-stat_master [new file with mode: 0644]
t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_ [new file with mode: 0644]
t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master [new file with mode: 0644]
t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master [new file with mode: 0644]
t/t4013/diff.whatchanged_--root_--patch-with-stat_master [new file with mode: 0644]
t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master [new file with mode: 0644]
t/t4013/diff.whatchanged_--root_-p_master [new file with mode: 0644]
t/t4013/diff.whatchanged_--root_master [new file with mode: 0644]
t/t4013/diff.whatchanged_-SF_-p_master [new file with mode: 0644]
t/t4013/diff.whatchanged_-SF_master [new file with mode: 0644]
t/t4013/diff.whatchanged_-p_master [new file with mode: 0644]
t/t4013/diff.whatchanged_master [new file with mode: 0644]
t/t4112-apply-renames.sh
t/t4114-apply-typechange.sh [new file with mode: 0755]
t/t5700-clone-reference.sh
t/t5710-info-alternate.sh
t/t6002-rev-list-bisect.sh
t/t6004-rev-list-path-optim.sh [new file with mode: 0755]
t/t6010-merge-base.sh
t/t6021-merge-criss-cross.sh
t/t6200-fmt-merge-msg.sh [new file with mode: 0755]
t/t7001-mv.sh
t/t8001-annotate.sh
t/t9001-send-email.sh
t/t9100-git-svn-basic.sh [new file with mode: 0755]
t/t9101-git-svn-props.sh [new file with mode: 0755]
t/t9102-git-svn-deep-rmdir.sh [new file with mode: 0755]
t/t9103-git-svn-graft-branches.sh [new file with mode: 0755]
t/t9104-git-svn-follow-parent.sh [new file with mode: 0755]
t/t9105-git-svn-commit-diff.sh [new file with mode: 0755]
t/test-lib.sh
tag.c
templates/hooks--update
test-sha1.c [new file with mode: 0644]
test-sha1.sh [new file with mode: 0755]
tree.c
unpack-objects.c
upload-pack.c
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xemit.c

index 7b954d587ec48314dee6c7d179e57607f958e875..52d61f31939d4fbbd55521cff85939fd1cf97e28 100644 (file)
@@ -46,6 +46,7 @@ git-http-push
 git-imap-send
 git-index-pack
 git-init-db
+git-instaweb
 git-local-fetch
 git-log
 git-lost-found
@@ -106,6 +107,7 @@ git-ssh-push
 git-ssh-upload
 git-status
 git-stripspace
+git-svn
 git-svnimport
 git-symbolic-ref
 git-tag
index cc83610588c24e2da863449facd656fb8c07907f..0d9ffb4ad9475de730ab2ee240d8b528b44017e8 100644 (file)
@@ -46,6 +46,7 @@ all: html man
 
 html: $(DOC_HTML)
 
+$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN7): asciidoc.conf
 
 man: man1 man7
 man1: $(DOC_MAN1)
index 8601949e80991823145a6d4c61cb5da918f98275..90722c21fad453791f53d1e8b62df23450622eae 100644 (file)
@@ -49,7 +49,7 @@ People on the git mailing list need to be able to read and
 comment on the changes you are submitting.  It is important for
 a developer to be able to "quote" your changes, using standard
 e-mail tools, so that they may comment on specific portions of
-your code.  For this reason, all patches should be submited
+your code.  For this reason, all patches should be submitted
 "inline".  WARNING: Be wary of your MUAs word-wrap
 corrupting your patch.  Do not cut-n-paste your patch; you can
 lose tabs that way if you are not careful.
index 7ce71510de52805c2bfff59f4c13d8267b9e3f25..8196d787ab13475200f7c0e739b21f525ba9e4ae 100644 (file)
@@ -9,6 +9,8 @@
 
 [attributes]
 caret=^
+startsb=&#91;
+endsb=&#93;
 
 ifdef::backend-docbook[]
 [gitlink-inlinemacro]
index a04c5adf8e522e65fae58ec32db07c46aeebe070..465eb13e76dca5cf4daad9d5ea773f4743452c75 100644 (file)
@@ -91,6 +91,18 @@ core.warnAmbiguousRefs::
        If true, git will warn you if the ref name you passed it is ambiguous
        and might match multiple refs in the .git/refs/ tree. True by default.
 
+core.compression::
+       An integer -1..9, indicating the compression level for objects that
+       are not in a pack file. -1 is the zlib and git default. 0 means no
+       compression, and 1..9 are various speed/size tradeoffs, 9 being
+       slowest.
+
+core.legacyheaders::
+       A boolean which enables the legacy object header format in case
+       you want to interoperate with old clients accessing the object
+       database directly (where the "http://" and "rsync://" protocols
+       count as direct access).
+
 alias.*::
        Command aliases for the gitlink:git[1] command wrapper - e.g.
        after defining "alias.last = cat-file commit HEAD", the invocation
@@ -104,10 +116,31 @@ apply.whitespace::
        Tells `git-apply` how to handle whitespaces, in the same way
        as the '--whitespace' option. See gitlink:git-apply[1].
 
+diff.color::
+       When true (or `always`), always use colors in patch.
+       When false (or `never`), never.  When set to `auto`, use
+       colors only when the output is to the terminal.
+
+diff.color.<slot>::
+       Use customized color for diff colorization.  `<slot>`
+       specifies which part of the patch to use the specified
+       color, and is one of `plain` (context text), `meta`
+       (metainformation), `frag` (hunk header), `old` (removed
+       lines), or `new` (added lines).  The value for these
+       configuration variables can be one of: `normal`, `bold`,
+       `dim`, `ul`, `blink`, `reverse`, `reset`, `black`,
+       `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, or
+       `white`.
+
 diff.renameLimit::
        The number of files to consider when performing the copy/rename
        detection; equivalent to the git diff option '-l'.
 
+diff.renames::
+       Tells git to detect renames.  If set to any boolean value, it
+       will enable basic rename detection.  If set to "copies" or
+       "copy", it will detect copies, as well.
+
 format.headers::
        Additional email headers to include in a patch to be submitted
        by mail.  See gitlink:git-format-patch[1].
@@ -166,6 +199,10 @@ merge.summary::
        Whether to include summaries of merged commits in newly created
        merge commit messages. False by default.
 
+pack.window::
+       The size of the window used by gitlink:git-pack-objects[1] when no
+       window size is given on the command line. Defaults to 10.
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@ -181,6 +218,17 @@ showbranch.default::
        The default set of branches for gitlink:git-show-branch[1].
        See gitlink:git-show-branch[1].
 
+tar.umask::
+       By default, git-link:git-tar-tree[1] sets file and directories modes
+       to 0666 or 0777. While this is both useful and acceptable for projects
+       such as the Linux Kernel, it might be excessive for other projects.
+       With this variable, it becomes possible to tell
+       git-link:git-tar-tree[1] to apply a specific umask to the modes above.
+       The special value "user" indicates that the user's current umask will
+       be used. This should be enough for most projects, as it will lead to
+       the same permissions as git-link:git-checkout[1] would use. The default
+       value remains 0, which means world read-write.
+
 user.email::
        Your email address to be recorded in any newly created commits.
        Can be overridden by the 'GIT_AUTHOR_EMAIL' and 'GIT_COMMITTER_EMAIL'
index 1fbca83141e20599e4d6edd880b1cabbf0cd9913..d2b0bd38de7fb39ccff5687013784ea8ceb22f1c 100644 (file)
@@ -93,7 +93,7 @@ machine where the repository is hosted.  If you don't want to give them a
 full shell on the machine, there is a restricted shell which only allows
 users to do git pushes and pulls; see gitlink:git-shell[1].
 
-Put all the committers should in the same group, and make the repository
+Put all the committers in the same group, and make the repository
 writable by that group:
 
 ------------------------------------------------
index f523ec2fbea9a129294b960461c82382b41ac92d..47ba9a403ae9379b87c54f0103297da9157237cb 100644 (file)
@@ -4,18 +4,21 @@
 -u::
        Synonym for "-p".
 
+--raw::
+       Generate the raw format.
+
 --patch-with-raw::
-       Generate patch but keep also the default raw diff output.
+       Synonym for "-p --raw".
 
 --stat::
-       Generate a diffstat instead of a patch.
+       Generate a diffstat.
 
 --summary::
        Output a condensed summary of extended header information
        such as creations, renames and mode changes.
 
 --patch-with-stat::
-       Generate patch and prepend its diffstat.
+       Synonym for "-p --stat".
 
 -z::
        \0 line termination on output
 --name-status::
        Show only names and status of changed files.
 
+--color::
+       Show colored diff.
+
+--no-color::
+       Turn off colored diff, even when the configuration file
+       gives the default to color output.
+
+--no-renames::
+       Turn off rename detection, even when the configuration
+       file gives the default to do so.
+
 --full-index::
        Instead of the first handful characters, show full
        object name of pre- and post-image blob on the "index"
-       line when generating a patch format output.     
+       line when generating a patch format output.
+
+--binary::
+       In addition to --full-index, output "binary diff" that
+       can be applied with "git apply".
 
 --abbrev[=<n>]::
        Instead of showing the full 40-byte hexadecimal object
        Swap two inputs; that is, show differences from index or
        on-disk file to tree contents.
 
+--text::
+       Treat all files as text.
+
+-a::
+       Shorthand for "--text".
+
 For more detailed explanation on these common options, see also
 link:diffcore.html[diffcore documentation].
index 0fe66f2d0c84003d285fed426181998063e86b96..517a86b238a91a1c853b5fbe331a5cc984e95878 100644 (file)
@@ -15,9 +15,9 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Updates the index file for given paths, or all modified files if
-'-a' is specified, and makes a commit object.  The command
-VISUAL and EDITOR environment variables to edit the commit log
-message.
+'-a' is specified, and makes a commit object.  The command specified
+by either the VISUAL or EDITOR environment variables are used to edit
+the commit log message.
 
 Several environment variable are used during commits.  They are
 documented in gitlink:git-commit-tree[1].
index 56bd3e517d80e6542a3aa118984aee0fefec2ab7..092d0d6730294a5ce701522575bc1f8d91755a1d 100644 (file)
@@ -8,7 +8,7 @@ git-cvsexportcommit - Export a commit to a CVS checkout
 
 SYNOPSIS
 --------
-'git-cvsexportcommmit' [-h] [-v] [-c] [-p] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-v] [-c] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
 
 
 DESCRIPTION
@@ -36,9 +36,13 @@ OPTIONS
        commit if any hunks fail to apply or there were other problems.
 
 -p::
-       Be pedantic (paranoid) when applying patches. Invokes patch with 
+       Be pedantic (paranoid) when applying patches. Invokes patch with
        --fuzz=0
 
+-a::
+       Add authorship information. Adds Author line, and Committer (if
+       different from Author) to the message.
+
 -f::
        Force the merge even if the files are not up to date.
 
index 481b8b3aa09539ff71a3f48b0b50b75e9ccce7a7..7248b35d952c3fe97496895059af35300694eb98 100644 (file)
@@ -37,7 +37,7 @@ omit diff output for unmerged entries and just show "Unmerged".
        commit with these flags.
 
 -q::
-       Remain silent even on nonexisting files
+       Remain silent even on nonexistent files
 
 Output format
 -------------
index 7ab20803761b0d5ac69e3b9acc31ed4e9d59ef4c..228c4d95bd41d40bbb32cfcb6b9a7dc648280fa5 100644 (file)
@@ -8,24 +8,24 @@ git-diff - Show changes between commits, commit and working tree, etc
 
 SYNOPSIS
 --------
-'git-diff' [ --diff-options ] <ent>{0,2} [<path>...]
+'git-diff' [ --diff-options ] <tree-ish>{0,2} [<path>...]
 
 DESCRIPTION
 -----------
-Show changes between two ents, an ent and the working tree, an
-ent and the index file, or the index file and the working tree.
+Show changes between two trees, a tree and the working tree, a
+tree and the index file, or the index file and the working tree.
 The combination of what is compared with what is determined by
-the number of ents given to the command.
+the number of trees given to the command.
 
-* When no <ent> is given, the working tree and the index
-  file is compared, using `git-diff-files`.
+* When no <tree-ish> is given, the working tree and the index
+  file are compared, using `git-diff-files`.
 
-* When one <ent> is given, the working tree and the named
-  tree is compared, using `git-diff-index`.  The option
+* When one <tree-ish> is given, the working tree and the named
+  tree are compared, using `git-diff-index`.  The option
   `--cached` can be given to compare the index file and
   the named tree.
 
-* When two <ent>s are given, these two trees are compared
+* When two <tree-ish>s are given, these two trees are compared
   using `git-diff-tree`.
 
 OPTIONS
index 4ca0014dac64729bee11b6a37bd730b538b2e5ee..67425dc0359ee9774200e1a2e12913d570117e7c 100644 (file)
@@ -9,8 +9,9 @@ git-format-patch - Prepare patches for e-mail submission
 SYNOPSIS
 --------
 [verse]
-'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--attach]
+'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--attach] [--thread]
                   [-s | --signoff] [--diff-options] [--start-number <n>]
+                  [--in-reply-to=Message-Id]
                   <since>[..<until>]
 
 DESCRIPTION
@@ -35,6 +36,10 @@ they are created in the current working directory.
 If -n is specified, instead of "[PATCH] Subject", the first line
 is formatted as "[PATCH n/m] Subject".
 
+If given --thread, git-format-patch will generate In-Reply-To and
+References headers to make the second and subsequent patch mails appear
+as replies to the first mail; this also generates a Message-Id header to
+reference.
 
 OPTIONS
 -------
@@ -63,6 +68,15 @@ OPTIONS
 --attach::
        Create attachments instead of inlining patches.
 
+--thread::
+       Add In-Reply-To and References headers to make the second and
+       subsequent mails appear as replies to the first.  Also generates
+       the Message-Id header to reference.
+
+--in-reply-to=Message-Id::
+       Make the first mail (or all the mails with --no-thread) appear as a
+       reply to the given Message-Id, which avoids breaking threads to
+       provide a new patch series.
 
 CONFIGURATION
 -------------
index 8a150d861f5b4c3dc468d46fa09dbfce5be020fc..0a4fc14b97fe690f9546c5d6734dfaaa9d0d656f 100644 (file)
@@ -25,7 +25,7 @@ DESCRIPTION
 -----------
 This command creates an empty git repository - basically a `.git` directory
 with subdirectories for `objects`, `refs/heads`, `refs/tags`, and
-templated files.
+template files.
 An initial `HEAD` file that references the HEAD of the master branch
 is also created.
 
diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt
new file mode 100644 (file)
index 0000000..7dd393b
--- /dev/null
@@ -0,0 +1,84 @@
+git-instaweb(1)
+===============
+
+NAME
+----
+git-instaweb - instantly browse your working repository in gitweb
+
+SYNOPSIS
+--------
+'git-instaweb' [--local] [--httpd=<httpd>] [--port=<port>] [--browser=<browser>]
+
+'git-instaweb' [--start] [--stop] [--restart]
+
+DESCRIPTION
+-----------
+A simple script to setup gitweb and a web server for browsing the local
+repository.
+
+OPTIONS
+-------
+
+-l|--local::
+       Only bind the web server to the local IP (127.0.0.1).
+
+-d|--httpd::
+       The HTTP daemon command-line that will be executed.
+       Command-line options may be specified here, and the
+       configuration file will be added at the end of the command-line.
+       Currently, lighttpd and apache2 are the only supported servers.
+       (Default: lighttpd)
+
+-m|--module-path::
+       The module path (only needed if httpd is Apache).
+       (Default: /usr/lib/apache2/modules)
+
+-p|--port::
+       The port number to bind the httpd to.  (Default: 1234)
+
+-b|--browser::
+
+       The web browser command-line to execute to view the gitweb page.
+       If blank, the URL of the gitweb instance will be printed to
+       stdout.  (Default: 'firefox')
+
+--start::
+       Start the httpd instance and exit.  This does not generate
+       any of the configuration files for spawning a new instance.
+
+--stop::
+       Stop the httpd instance and exit.  This does not generate
+       any of the configuration files for spawning a new instance,
+       nor does it close the browser.
+
+--restart::
+       Restart the httpd instance and exit.  This does not generate
+       any of the configuration files for spawning a new instance.
+
+CONFIGURATION
+-------------
+
+You may specify configuration in your .git/config
+
+-----------------------------------------------------------------------
+[instaweb]
+       local = true
+       httpd = apache2 -f
+       port = 4321
+       browser = konqueror
+       modulepath = /usr/lib/apache2/modules
+
+-----------------------------------------------------------------------
+
+Author
+------
+Written by Eric Wong <normalperson@yhbt.net>
+
+Documentation
+--------------
+Documentation by Eric Wong <normalperson@yhbt.net>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 4d8a2ad2d753426a697d9e82ff76e6ad278572db..8520b971111e8b015ac3d08b207a3ab37505ccca 100644 (file)
@@ -207,7 +207,7 @@ An exclude pattern is of the following format:
 An example:
 
 --------------------------------------------------------------
-    $ cat .git/ignore
+    $ cat .git/info/exclude
     # ignore objects and archives, anywhere in the tree.
     *.[oa]
     $ cat Documentation/.gitignore
@@ -217,7 +217,7 @@ An example:
     !foo.html
     $ git-ls-files --ignored \
         --exclude='Documentation/*.[0-9]' \
-        --exclude-from=.git/ignore \
+        --exclude-from=.git/info/exclude \
         --exclude-per-directory=.gitignore
 --------------------------------------------------------------
 
index 209e36bacb03c166e1c0c61e8c6e14447cd44f6a..5a17801f6a8d4d307f55e62745a400ad498ebdb9 100644 (file)
@@ -25,7 +25,7 @@ OPTIONS
 
 -b::
        If any file doesn't begin with a From line, assume it is a
-       single mail message instead of signalling error.
+       single mail message instead of signaling error.
 
 -d<prec>::
        Instead of the default 4 digits with leading zeros,
index 4ce799b520b501b1ea6dddd5643bf3aec2aca344..bebf30ad3db951c0a0bad01ebee619821781eb39 100644 (file)
@@ -83,7 +83,7 @@ your local modifications interfere with the merge, again, it
 stops before touching anything.
 
 So in the above two "failed merge" case, you do not have to
-worry about lossage of data --- you simply were not ready to do
+worry about loss of data --- you simply were not ready to do
 a merge, so no merge happened at all.  You may want to finish
 whatever you were in the middle of doing, and retry the same
 pull after you are done and ready.
index 39a1434a0e6be3c07852dc0919536bc63551a3dd..37fbf66efb91ff74bf57d1b33b5ab95a248713cd 100644 (file)
@@ -26,14 +26,14 @@ OPTIONS
        List all commits reachable from all refs
 
 --stdin::
-       Read from stdin, append "(<rev_name>)" to all sha1's of name'able
+       Read from stdin, append "(<rev_name>)" to all sha1's of nameable
        commits, and pass to stdout
 
 EXAMPLE
 -------
 
 Given a commit, find out where it is relative to the local refs. Say somebody
-wrote you about that phantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
+wrote you about that fantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
 Of course, you look into the commit, but that only tells you what happened, but
 not the context.
 
index 0858e5efbe01a8e47ddcfa9b1c7e1c6169f9b7e1..ee9e8fa9090f88d1eb45ddbdb73866e0dc1fd53d 100644 (file)
@@ -128,7 +128,7 @@ Tags
 A git tag of the form p4/xx is created for every change imported from
 the Perforce repository where xx is the Perforce changeset number.
 Therefore after the import you can use git to access any commit by its
-Perforce number, eg. git show p4/327.
+Perforce number, e.g. git show p4/327.
 
 The tag associated with the HEAD commit is also how `git-p4import`
 determines if there are new changes to incrementally import from the
@@ -143,7 +143,7 @@ may delete the tags.
 
 Notes
 -----
-You can interrupt the import (eg. ctrl-c) at any time and restart it
+You can interrupt the import (e.g. ctrl-c) at any time and restart it
 without worry.
 
 Author information is automatically determined by querying the
index 8fb065943862e55c6cc0b3c7408506d06ad510f4..7d54b17e37b1065444b9ce100013e4a505af218d 100644 (file)
@@ -29,7 +29,7 @@ OPTIONS
 
 
 --all::
-       Processes all packs. Any filenames on the commandline are ignored.
+       Processes all packs. Any filenames on the command line are ignored.
 
 --alt-odb::
        Don't require objects present in packs from alternate object
index d5b5ca167ccf3fcfaeb818a305ca79b37dcd7bf8..56afd64f42784457d73463ab9bea196264a3296b 100644 (file)
@@ -67,7 +67,7 @@ Some short-cut notations are also supported.
 
 -f, \--force::
        Usually, the command refuses to update a remote ref that is
-       not a descendent of the local ref used to overwrite it.
+       not a descendant of the local ref used to overwrite it.
        This flag disables the check.  This can cause the
        remote repository to lose commits; use it with care.
 
index 803c0d5cae6db0a953380adc6ee3980a0afa72e0..b03d66f61ca4b234e721d671cc2521182a8e12b1 100644 (file)
@@ -119,8 +119,8 @@ you can set the filemode to true with
 % git repo-config core.filemode true
 ------------
 
-The hypothetic proxy command entries actually have a postfix to discern
-to what URL they apply. Here is how to change the entry for kernel.org
+The hypothetical proxy command entries actually have a postfix to discern
+what URL they apply to. Here is how to change the entry for kernel.org
 to "ssh".
 
 ------------
index ad6d14c55aab2144bdac3c313305ba408fe5f0db..f60eacd93e96a080689569b1afd6736c05554ab7 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
             [ \--sparse ]
             [ \--no-merges ]
             [ \--remove-empty ]
+            [ \--not ]
             [ \--all ]
             [ \--topo-order ]
             [ \--parents ]
@@ -37,6 +38,14 @@ not in 'baz'".
 A special notation <commit1>..<commit2> can be used as a
 short-hand for {caret}<commit1> <commit2>.
 
+Another special notation is <commit1>...<commit2> which is useful for
+merges.  The resulting set of commits is the symmetric difference
+between the two operands.  The following two commands are equivalent:
+
+------------
+$ git-rev-list A B --not $(git-merge-base --all A B)
+$ git-rev-list A...B
+------------
 
 OPTIONS
 -------
@@ -47,6 +56,9 @@ OPTIONS
        Print the contents of the commit in raw-format; each
        record is separated with a NUL character.
 
+--parents::
+       Print the parents of the commit.
+
 --objects::
        Print the object IDs of any object referenced by the listed commits.
        'git-rev-list --objects foo ^bar' thus means "send me all object IDs
@@ -55,7 +67,7 @@ OPTIONS
 
 --objects-edge::
        Similar to `--objects`, but also print the IDs of
-       excluded commits refixed with a `-` character.  This is
+       excluded commits prefixed with a `-` character.  This is
        used by `git-pack-objects` to build 'thin' pack, which
        records objects in deltified form based on objects
        contained in these excluded commits to reduce network
@@ -93,6 +105,14 @@ OPTIONS
 --remove-empty::
        Stop when a given path disappears from the tree.
 
+--no-merges::
+       Do not print commits with more than one parent.
+
+--not::
+       Reverses the meaning of the '{caret}' prefix (or lack
+       thereof) for all following revision specifiers, up to
+       the next `--not`.
+
 --all::
        Pretend as if all the refs in `$GIT_DIR/refs/` are
        listed on the command line as <commit>.
index 627cde8520830c76378ffff0f2cc5cdc92fe2483..b761b4b9656eebf5b9caafc9d702c02e3f243fff 100644 (file)
@@ -156,11 +156,6 @@ syntax.
   and dereference the tag recursively until a non-tag object is
   found.
 
-'git-rev-parse' also accepts a prefix '{caret}' to revision parameter,
-which is passed to 'git-rev-list'.  Two revision parameters
-concatenated with '..' is a short-hand for writing a range
-between them.  I.e. 'r1..r2' is equivalent to saying '{caret}r1 r2'
-
 Here is an illustration, by Jon Loeliger.  Both node B and C are
 a commit parents of commit node A.  Parent commits are ordered
 left-to-right.
@@ -168,9 +163,9 @@ left-to-right.
     G   H   I   J
      \ /     \ /
       D   E   F
-       \  |  /
-        \ | /
-         \|/
+       \  |  / \
+        \ | /   |
+         \|/    |
           B     C
            \   /
             \ /
@@ -188,6 +183,40 @@ left-to-right.
     J = F^2  = B^3^2   = A^^3^2
 
 
+SPECIFYING RANGES
+-----------------
+
+History traversing commands such as `git-log` operate on a set
+of commits, not just a single commit.  To these commands,
+specifying a single revision with the notation described in the
+previous section means the set of commits reachable from that
+commit, following the commit ancestry chain.
+
+To exclude commits reachable from a commit, a prefix `{caret}`
+notation is used.  E.g. "`{caret}r1 r2`" means commits reachable
+from `r2` but exclude the ones reachable from `r1`.
+
+This set operation appears so often that there is a shorthand
+for it.  "`r1..r2`" is equivalent to "`{caret}r1 r2`".  It is
+the difference of two sets (subtract the set of commits
+reachable from `r1` from the set of commits reachable from
+`r2`).
+
+A similar notation "`r1\...r2`" is called symmetric difference
+of `r1` and `r2` and is defined as
+"`r1 r2 --not $(git-merge-base --all r1 r2)`".
+It it the set of commits that are reachable from either one of
+`r1` or `r2` but not from both.
+
+Here are a few examples:
+
+   D                A B D
+   D F              A B C D F
+   ^A G                    B D
+   ^A F             B C F
+   G...I            C D F G I
+   ^B G I          C D F G I
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org> and
index f115b45ef61567b18f6686d12dd168e68f83cb02..a2445a48fc98ddd4865bab8d3033880a854fca36 100644 (file)
@@ -52,6 +52,11 @@ OPTIONS
         appear in topological order (i.e., descendant commits
         are shown before their parents).
 
+--sparse::
+       By default, the output omits merges that are reachable
+       from only one tip being shown.  This option makes them
+       visible.
+
 --more=<n>::
        Usually the command stops output upon showing the commit
        that is the common ancestor of all the branches.  This
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
new file mode 100644 (file)
index 0000000..7d86809
--- /dev/null
@@ -0,0 +1,319 @@
+git-svn(1)
+==========
+
+NAME
+----
+git-svn - bidirectional operation between a single Subversion branch and git
+
+SYNOPSIS
+--------
+'git-svn' <command> [options] [arguments]
+
+DESCRIPTION
+-----------
+git-svn is a simple conduit for changesets between a single Subversion
+branch and git.
+
+git-svn is not to be confused with git-svnimport.  The were designed
+with very different goals in mind.
+
+git-svn is designed for an individual developer who wants a
+bidirectional flow of changesets between a single branch in Subversion
+and an arbitrary number of branches in git.  git-svnimport is designed
+for read-only operation on repositories that match a particular layout
+(albeit the recommended one by SVN developers).
+
+For importing svn, git-svnimport is potentially more powerful when
+operating on repositories organized under the recommended
+trunk/branch/tags structure, and should be faster, too.
+
+git-svn mostly ignores the very limited view of branching that
+Subversion has.  This allows git-svn to be much easier to use,
+especially on repositories that are not organized in a manner that
+git-svnimport is designed for.
+
+COMMANDS
+--------
+init::
+       Creates an empty git repository with additional metadata
+       directories for git-svn.  The Subversion URL must be specified
+       as a command-line argument.
+
+fetch::
+       Fetch unfetched revisions from the Subversion URL we are
+       tracking.  refs/remotes/git-svn will be updated to the
+       latest revision.
+
+       Note: You should never attempt to modify the remotes/git-svn
+       branch outside of git-svn.  Instead, create a branch from
+       remotes/git-svn and work on that branch.  Use the 'commit'
+       command (see below) to write git commits back to
+       remotes/git-svn.
+
+       See 'Additional Fetch Arguments' if you are interested in
+       manually joining branches on commit.
+
+commit::
+       Commit specified commit or tree objects to SVN.  This relies on
+       your imported fetch data being up-to-date.  This makes
+       absolutely no attempts to do patching when committing to SVN, it
+       simply overwrites files with those specified in the tree or
+       commit.  All merging is assumed to have taken place
+       independently of git-svn functions.
+
+rebuild::
+       Not a part of daily usage, but this is a useful command if
+       you've just cloned a repository (using git-clone) that was
+       tracked with git-svn.  Unfortunately, git-clone does not clone
+       git-svn metadata and the svn working tree that git-svn uses for
+       its operations.  This rebuilds the metadata so git-svn can
+       resume fetch operations.  A Subversion URL may be optionally
+       specified at the command-line if the directory/repository you're
+       tracking has moved or changed protocols.
+
+show-ignore::
+       Recursively finds and lists the svn:ignore property on
+       directories.  The output is suitable for appending to
+       the $GIT_DIR/info/exclude file.
+
+OPTIONS
+-------
+-r <ARG>::
+--revision <ARG>::
+       Only used with the 'fetch' command.
+
+       Takes any valid -r<argument> svn would accept and passes it
+       directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax
+       is also supported.  This is passed directly to svn, see svn
+       documentation for more details.
+
+       This can allow you to make partial mirrors when running fetch.
+
+-::
+--stdin::
+       Only used with the 'commit' command.
+
+       Read a list of commits from stdin and commit them in reverse
+       order.  Only the leading sha1 is read from each line, so
+       git-rev-list --pretty=oneline output can be used.
+
+--rmdir::
+       Only used with the 'commit' command.
+
+       Remove directories from the SVN tree if there are no files left
+       behind.  SVN can version empty directories, and they are not
+       removed by default if there are no files left in them.  git
+       cannot version empty directories.  Enabling this flag will make
+       the commit to SVN act like git.
+
+       repo-config key: svn.rmdir
+
+-e::
+--edit::
+       Only used with the 'commit' command.
+
+       Edit the commit message before committing to SVN.  This is off by
+       default for objects that are commits, and forced on when committing
+       tree objects.
+
+       repo-config key: svn.edit
+
+-l<num>::
+--find-copies-harder::
+       Both of these are only used with the 'commit' command.
+
+       They are both passed directly to git-diff-tree see
+       git-diff-tree(1) for more information.
+
+       repo-config key: svn.l
+       repo-config key: svn.findcopiesharder
+
+-A<filename>::
+--authors-file=<filename>::
+
+       Syntax is compatible with the files used by git-svnimport and
+       git-cvsimport:
+
+------------------------------------------------------------------------
+loginname = Joe User <user@example.com>
+------------------------------------------------------------------------
+
+       If this option is specified and git-svn encounters an SVN
+       committer name that does not exist in the authors-file, git-svn
+       will abort operation. The user will then have to add the
+       appropriate entry.  Re-running the previous git-svn command
+       after the authors-file is modified should continue operation.
+
+       repo-config key: svn.authors-file
+
+ADVANCED OPTIONS
+----------------
+-b<refname>::
+--branch <refname>::
+       Used with 'fetch' or 'commit'.
+
+       This can be used to join arbitrary git branches to remotes/git-svn
+       on new commits where the tree object is equivalent.
+
+       When used with different GIT_SVN_ID values, tags and branches in
+       SVN can be tracked this way, as can some merges where the heads
+       end up having completely equivalent content.  This can even be
+       used to track branches across multiple SVN _repositories_.
+
+       This option may be specified multiple times, once for each
+       branch.
+
+       repo-config key: svn.branch
+
+-i<GIT_SVN_ID>::
+--id <GIT_SVN_ID>::
+       This sets GIT_SVN_ID (instead of using the environment).  See
+       the section on "Tracking Multiple Repositories or Branches" for
+       more information on using GIT_SVN_ID.
+
+COMPATIBILITY OPTIONS
+---------------------
+--upgrade::
+       Only used with the 'rebuild' command.
+
+       Run this if you used an old version of git-svn that used
+       "git-svn-HEAD" instead of "remotes/git-svn" as the branch
+       for tracking the remote.
+
+--no-ignore-externals::
+       Only used with the 'fetch' and 'rebuild' command.
+
+       By default, git-svn passes --ignore-externals to svn to avoid
+       fetching svn:external trees into git.  Pass this flag to enable
+       externals tracking directly via git.
+
+       Versions of svn that do not support --ignore-externals are
+       automatically detected and this flag will be automatically
+       enabled for them.
+
+       Otherwise, do not enable this flag unless you know what you're
+       doing.
+
+       repo-config key: svn.noignoreexternals
+
+Basic Examples
+~~~~~~~~~~~~~~
+
+Tracking and contributing to an Subversion managed-project:
+
+------------------------------------------------------------------------
+# Initialize a tree (like git init-db):
+       git-svn init http://svn.foo.org/project/trunk
+# Fetch remote revisions:
+       git-svn fetch
+# Create your own branch to hack on:
+       git checkout -b my-branch remotes/git-svn
+# Commit only the git commits you want to SVN:
+       git-svn commit <tree-ish> [<tree-ish_2> ...]
+# Commit all the git commits from my-branch that don't exist in SVN:
+       git-svn commit remotes/git-svn..my-branch
+# Something is committed to SVN, pull the latest into your branch:
+       git-svn fetch && git pull . remotes/git-svn
+# Append svn:ignore settings to the default git exclude file:
+       git-svn show-ignore >> .git/info/exclude
+------------------------------------------------------------------------
+
+DESIGN PHILOSOPHY
+-----------------
+Merge tracking in Subversion is lacking and doing branched development
+with Subversion is cumbersome as a result.  git-svn completely forgoes
+any automated merge/branch tracking on the Subversion side and leaves it
+entirely up to the user on the git side.  It's simply not worth it to do
+a useful translation when the original signal is weak.
+
+TRACKING MULTIPLE REPOSITORIES OR BRANCHES
+------------------------------------------
+This is for advanced users, most users should ignore this section.
+
+Because git-svn does not care about relationships between different
+branches or directories in a Subversion repository, git-svn has a simple
+hack to allow it to track an arbitrary number of related _or_ unrelated
+SVN repositories via one git repository.  Simply set the GIT_SVN_ID
+environment variable to a name other other than "git-svn" (the default)
+and git-svn will ignore the contents of the $GIT_DIR/git-svn directory
+and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that
+invocation.  The interface branch will be remotes/$GIT_SVN_ID, instead of
+remotes/git-svn.  Any remotes/$GIT_SVN_ID branch should never be modified
+by the user outside of git-svn commands.
+
+ADDITIONAL FETCH ARGUMENTS
+--------------------------
+This is for advanced users, most users should ignore this section.
+
+Unfetched SVN revisions may be imported as children of existing commits
+by specifying additional arguments to 'fetch'.  Additional parents may
+optionally be specified in the form of sha1 hex sums at the
+command-line.  Unfetched SVN revisions may also be tied to particular
+git commits with the following syntax:
+
+       svn_revision_number=git_commit_sha1
+
+This allows you to tie unfetched SVN revision 375 to your current HEAD::
+
+       `git-svn fetch 375=$(git-rev-parse HEAD)`
+
+Advanced Example: Tracking a Reorganized Repository
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you're tracking a directory that has moved, or otherwise been
+branched or tagged off of another directory in the repository and you
+care about the full history of the project, then you can read this
+section.
+
+This is how Yann Dirson tracked the trunk of the ufoai directory when
+the /trunk directory of his repository was moved to /ufoai/trunk and
+he needed to continue tracking /ufoai/trunk where /trunk left off.
+
+------------------------------------------------------------------------
+       # This log message shows when the repository was reorganized:
+       r166 | ydirson | 2006-03-02 01:36:55 +0100 (Thu, 02 Mar 2006) | 1 line
+       Changed paths:
+          D /trunk
+          A /ufoai/trunk (from /trunk:165)
+
+       # First we start tracking the old revisions:
+       GIT_SVN_ID=git-oldsvn git-svn init \
+                       https://svn.sourceforge.net/svnroot/ufoai/trunk
+       GIT_SVN_ID=git-oldsvn git-svn fetch -r1:165
+
+       # And now, we continue tracking the new revisions:
+       GIT_SVN_ID=git-newsvn git-svn init \
+             https://svn.sourceforge.net/svnroot/ufoai/ufoai/trunk
+       GIT_SVN_ID=git-newsvn git-svn fetch \
+             166=`git-rev-parse refs/remotes/git-oldsvn`
+------------------------------------------------------------------------
+
+BUGS
+----
+If somebody commits a conflicting changeset to SVN at a bad moment
+(right before you commit) causing a conflict and your commit to fail,
+your svn working tree ($GIT_DIR/git-svn/tree) may be dirtied.  The
+easiest thing to do is probably just to rm -rf $GIT_DIR/git-svn/tree and
+run 'rebuild'.
+
+We ignore all SVN properties except svn:executable.  Too difficult to
+map them since we rely heavily on git write-tree being _exactly_ the
+same on both the SVN and git working trees and I prefer not to clutter
+working trees with metadata files.
+
+svn:keywords can't be ignored in Subversion (at least I don't know of
+a way to ignore them).
+
+Renamed and copied directories are not detected by git and hence not
+tracked when committing to SVN.  I do not plan on adding support for
+this as it's quite difficult and time-consuming to get working for all
+the possible corner cases (git doesn't do it, either).  Renamed and
+copied files are fully supported if they're similar enough for git to
+detect them.
+
+Author
+------
+Written by Eric Wong <normalperson@yhbt.net>.
+
+Documentation
+-------------
+Written by Eric Wong <normalperson@yhbt.net>.
index f2675c41933fc4f824f871cc9185a6a1bf010d2b..7a99acf2ec92f9bbab84b664d10bb442482c4911 100644 (file)
@@ -37,7 +37,20 @@ OPTIONS
        Instead of making a tar archive from local repository,
        retrieve a tar archive from a remote repository.
 
-Examples
+CONFIGURATION
+-------------
+By default, file and directories modes are set to 0666 or 0777. It is
+possible to change this by setting the "umask" variable in the
+repository configuration as follows :
+
+[tar]
+        umask = 002    ;# group friendly
+
+The special umask value "user" indicates that the user's current umask
+will be used instead. The default value remains 0, which means world
+readable/writable files and directories.
+
+EXAMPLES
 --------
 git tar-tree HEAD junk | (cd /var/tmp/ && tar xf -)::
 
index d79523f56d063aa4a3ee3181fa224694c7970613..0914cbb0ba528653aa50c6a2eb27815bd69d3508 100644 (file)
@@ -42,7 +42,7 @@ History Viewers
 
    - *gitk* (shipped with git-core)
 
-   gitk is a simple TK GUI for browsing history of GIT repositories easily.
+   gitk is a simple Tk GUI for browsing history of GIT repositories easily.
 
 
    - *gitview*  (contrib/)
index a1019a0231fdc36ca5f70b712415114b6cfa6bdb..394af62015590551f313378edcc0d03bfa13802c 100644 (file)
@@ -17,7 +17,7 @@ to the other end over the git protocol.
 
 This command is usually not invoked directly by the end user.
 The UI for the protocol is on the 'git-tar-tree' side, and the
-program pair is meant to be used to get a tar achive from a
+program pair is meant to be used to get a tar archive from a
 remote repository.
 
 
index 51f20c6e67acb8c7eb526fac544992eaec321623..ce3058182fead833c45daf02a2806f151b15f61f 100644 (file)
@@ -478,7 +478,7 @@ Configuration Mechanism
 
 Starting from 0.99.9 (actually mid 0.99.8.GIT), `.git/config` file
 is used to hold per-repository configuration options.  It is a
-simple text file modelled after `.ini` format familiar to some
+simple text file modeled after `.ini` format familiar to some
 people.  Here is an example:
 
 ------------
@@ -615,6 +615,13 @@ git Diffs
        gitlink:git-diff-files[1];
        gitlink:git-diff-tree[1]
 
+other
+~~~~~
+'GIT_TRACE'::
+       If this variable is set git will print `trace:` messages on
+       stderr telling about alias expansion, built-in command
+       execution and external command execution.
+
 Discussion[[Discussion]]
 ------------------------
 include::README[]
index 116ddb7fbfd7654bb7330e8f56dd03a9be029cd1..14449ca8baeb7c2c979d0fd3f61ce683fd8e08ce 100644 (file)
@@ -86,7 +86,7 @@ directory::
 ent::
        Favorite synonym to "tree-ish" by some total geeks. See
        `http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
-       explanation.
+       explanation.  Avoid this term, not to confuse people.
 
 fast forward::
        A fast-forward is a special type of merge where you have
index edbcd4c6618d6f9c123c49d4835dfc59b74e07f5..926bbdc3cb1dcab81e0d90de3a0b9b87153e55e6 100644 (file)
@@ -28,7 +28,7 @@ Then do
 
 and at this point "git bisect" will churn for a while, and tell you what 
 the mid-point between those two commits are, and check that state out as 
-the head of the bew "bisect" branch.
+the head of the new "bisect" branch.
 
 Compile and reboot.
 
index c2d4a91c7cf0a631ffd3caadea8a6aa25aa4a24c..fcd64e9b9b6d53b46d31e0c797016e9881651604 100644 (file)
@@ -124,7 +124,7 @@ up your changes, along with other changes.
 
 The two commits #2' and #3' in the above picture record the same
 changes your e-mail submission for #2 and #3 contained, but
-probably with the new sign-off line added by the upsteam
+probably with the new sign-off line added by the upstream
 maintainer and definitely with different committer and ancestry
 information, they are different objects from #2 and #3 commits.
 
index b52dfdc3081537c68f2fb12635fc986b0221dd4a..275d18bb545f3b14ad1f7de50d4224f184a507f8 100644 (file)
@@ -120,9 +120,11 @@ info/grafts::
 
 info/exclude::
        This file, by convention among Porcelains, stores the
-       exclude pattern list.  `git status` looks at it, but
-       otherwise it is not looked at by any of the core git
-       commands.
+       exclude pattern list. `.gitignore` is the per-directory
+       ignore file.  `git status`, `git add`, `git rm` and `git
+       clean` look at it but the core git commands do not look
+       at it.  See also: gitlink:git-ls-files[1] `--exclude-from`
+       and `--exclude-per-directory`.
 
 remotes::
        Stores shorthands to be used to give URL and default
index 9aadd5cee5204b1f11e8ac0511609bc86ab5de81..103eb5d989349c8e7e0147920b2e218caba9daf9 100644 (file)
@@ -73,7 +73,7 @@ The traditional insight:
 
     <pasky> yes
 
-And Bable-like confusion flowed.
+And Babel-like confusion flowed.
 
     <njs`> oh, hmm, and I'm not sure what this sliding window means either
 
@@ -257,7 +257,7 @@ proclaim it a non-issue.  Good style too!
         (type, basename, size)).
 
         Then we walk through this list, and calculate a delta of
-        each object against the last n (tunable paramater) objects,
+        each object against the last n (tunable parameter) objects,
         and pick the smallest of these deltas.
 
 Vastly simplified, but the essence is there!
@@ -395,7 +395,7 @@ used as setup for a later optimization, which is a real word:
         do "object name->location in packfile" translation.
 
     <njs`> I'm assuming the real win for delta-ing large->small is
-        more homogenous statistics for gzip to run over?
+        more homogeneous statistics for gzip to run over?
 
         (You have to put the bytes in one place or another, but
         putting them in a larger blob wins on compression)
@@ -448,7 +448,7 @@ design options, etc.
 
         Bugs happen, but they are "simple" bugs. And bugs that
         actually get some object store detail wrong are almost always
-        so obious that they never go anywhere.
+        so obvious that they never go anywhere.
 
     <njs`> Yeah.
 
index 74774134e3939cf1f5447d3aaec319994504ce1b..26ecba53fbfad4af4da46680330f998bf92f1b3f 100644 (file)
@@ -10,20 +10,21 @@ to name the remote repository:
 - https://host.xz/path/to/repo.git/
 - git://host.xz/path/to/repo.git/
 - git://host.xz/~user/path/to/repo.git/
-- ssh://host.xz/path/to/repo.git/
-- ssh://host.xz/~user/path/to/repo.git/
-- ssh://host.xz/~/path/to/repo.git
+- ssh://{startsb}user@{endsb}host.xz/path/to/repo.git/
+- ssh://{startsb}user@{endsb}host.xz/~user/path/to/repo.git/
+- ssh://{startsb}user@{endsb}host.xz/~/path/to/repo.git
 ===============================================================
 
-SSH Is the default transport protocol and also supports an
-scp-like syntax.  Both syntaxes support username expansion,
+SSH is the default transport protocol.  You can optionally specify
+which user to log-in as, and an alternate, scp-like syntax is also
+supported.  Both syntaxes support username expansion,
 as does the native git protocol. The following three are
 identical to the last three above, respectively:
 
 ===============================================================
-- host.xz:/path/to/repo.git/
-- host.xz:~user/path/to/repo.git/
-- host.xz:path/to/repo.git
+- {startsb}user@{endsb}host.xz:/path/to/repo.git/
+- {startsb}user@{endsb}host.xz:~user/path/to/repo.git/
+- {startsb}user@{endsb}host.xz:path/to/repo.git
 ===============================================================
 
 To sync with a local directory, use:
@@ -47,7 +48,7 @@ Then such a short-hand is specified in place of
 <repository> without <refspec> parameters on the command
 line, <refspec> specified on `Push:` lines or `Pull:`
 lines are used for `git-push` and `git-fetch`/`git-pull`,
-respectively.  Multiple `Push:` and and `Pull:` lines may
+respectively.  Multiple `Push:` and `Pull:` lines may
 be specified for additional branch mappings.
 
 The name of a file in `$GIT_DIR/branches` directory can be
diff --git a/INSTALL b/INSTALL
index f8337e2a4d154157ed805eeb10f52d01513f7adb..7da2c8982978e9ff069eb1cc2338330a45669100 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -44,7 +44,7 @@ Issues of note:
 
        - "libcurl" and "curl" executable.  git-http-fetch and
          git-fetch use them.  If you do not use http
-         transfer, you are probabaly OK if you do not have
+         transfer, you are probably OK if you do not have
          them.
 
        - expat library; git-http-push uses it for remote lock
@@ -69,7 +69,7 @@ Issues of note:
          git, and if you only use git to track other peoples work you'll
          never notice the lack of it. 
 
-        - "wish", the TCL/Tk windowing shell is used in gitk to show the
+        - "wish", the Tcl/Tk windowing shell is used in gitk to show the
           history graphically
 
        - "ssh" is used to push and pull over the net
index ccd7c62e5760ec140b5860b30fd557ec3900fa3e..49eaa10b3be884a2aebdcea51ca50d0018e869f5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -33,6 +33,22 @@ all:
 # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
 # Enable it on Windows.  By default, symrefs are still used.
 #
+# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability
+# tests.  These tests take up a significant amount of the total test time
+# but are not needed unless you plan to talk to SVN repos.
+#
+# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink
+# installed in /sw, but don't want GIT to link against any libraries
+# installed there.  If defined you may specify your own (or Fink's)
+# include directories and library directories by defining CFLAGS
+# and LDFLAGS appropriately.
+#
+# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X,
+# have DarwinPorts installed in /opt/local, but don't want GIT to
+# link against any libraries installed there.  If defined you may
+# specify your own (or DarwinPort's) include directories and
+# library directories by defining CFLAGS and LDFLAGS appropriately.
+#
 # Define PPC_SHA1 environment variable when running make to make use of
 # a bundled SHA1 routine optimized for PowerPC.
 #
@@ -60,7 +76,7 @@ all:
 # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
 # a missing newline at the end of the file.
 #
-# Define NO_PYTHON if you want to loose all benefits of the recursive merge.
+# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
 #
 # Define COLLISION_CHECK below if you believe that SHA1's
 # 1461501637330902918203684832716283019655932542976 hashes do not give you
@@ -122,7 +138,7 @@ SCRIPT_SH = \
        git-fetch.sh \
        git-ls-remote.sh \
        git-merge-one-file.sh git-parse-remote.sh \
-       git-prune.sh git-pull.sh git-rebase.sh \
+       git-pull.sh git-rebase.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-resolve.sh git-revert.sh git-sh-setup.sh \
        git-tag.sh git-verify-tag.sh \
@@ -133,10 +149,10 @@ SCRIPT_SH = \
 
 SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \
+       git-shortlog.perl git-rerere.perl \
        git-annotate.perl git-cvsserver.perl \
        git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \
-       git-send-email.perl
+       git-send-email.perl git-svn.perl
 
 SCRIPT_PYTHON = \
        git-merge-recursive.py
@@ -144,7 +160,7 @@ SCRIPT_PYTHON = \
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
          $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
-         git-cherry-pick git-status
+         git-cherry-pick git-status git-instaweb
 
 # The ones that do not have to link with lcrypto, lz nor xdiff.
 SIMPLE_PROGRAMS = \
@@ -175,7 +191,8 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \
        git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \
        git-read-tree$X git-commit-tree$X git-write-tree$X \
        git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \
-       git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X
+       git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \
+       git-fmt-merge-msg$X git-prune$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -219,7 +236,7 @@ LIB_OBJS = \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
-       alloc.o $(DIFF_OBJS)
+       alloc.o merge-file.o $(DIFF_OBJS)
 
 BUILTIN_OBJS = \
        builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
@@ -231,7 +248,7 @@ BUILTIN_OBJS = \
        builtin-apply.o builtin-show-branch.o builtin-diff-files.o \
        builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \
        builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \
-       builtin-update-ref.o
+       builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
@@ -251,15 +268,17 @@ ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
        NO_STRLCPY = YesPlease
-       ## fink
-       ifeq ($(shell test -d /sw/lib && echo y),y)
-               ALL_CFLAGS += -I/sw/include
-               ALL_LDFLAGS += -L/sw/lib
+       ifndef NO_FINK
+               ifeq ($(shell test -d /sw/lib && echo y),y)
+                       ALL_CFLAGS += -I/sw/include
+                       ALL_LDFLAGS += -L/sw/lib
+               endif
        endif
-       ## darwinports
-       ifeq ($(shell test -d /opt/local/lib && echo y),y)
-               ALL_CFLAGS += -I/opt/local/include
-               ALL_LDFLAGS += -L/opt/local/lib
+       ifndef NO_DARWIN_PORTS
+               ifeq ($(shell test -d /opt/local/lib && echo y),y)
+                       ALL_CFLAGS += -I/opt/local/include
+                       ALL_LDFLAGS += -L/opt/local/lib
+               endif
        endif
 endif
 ifeq ($(uname_S),SunOS)
@@ -470,7 +489,7 @@ ifdef NO_ACCURATE_DIFF
        ALL_CFLAGS += -DNO_ACCURATE_DIFF
 endif
 
-# Shell quote (do not use $(call) to accomodate ancient setups);
+# Shell quote (do not use $(call) to accommodate ancient setups);
 
 SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
 
@@ -515,6 +534,7 @@ common-cmds.h: Documentation/git-*.txt
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
        rm -f $@ $@+
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+           -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
            -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
@@ -547,6 +567,20 @@ git-status: git-commit
        cp $< $@+
        mv $@+ $@
 
+git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
+       rm -f $@ $@+
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
+           -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
+           -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \
+           -e '/@@GITWEB_CGI@@/d' \
+           -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
+           -e '/@@GITWEB_CSS@@/d' \
+           $@.sh | sed "s|/usr/bin/git|$(bindir)/git|" > $@+
+       chmod +x $@+
+       mv $@+ $@
+
 # These can record GIT_VERSION
 git$X git.spec \
        $(patsubst %.sh,%,$(SCRIPT_SH)) \
@@ -589,11 +623,11 @@ git-ssh-push$X: rsh.o
 git-imap-send$X: imap-send.o $(LIB_FILE)
 
 http.o http-fetch.o http-push.o: http.h
-git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE)
+git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
-git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)
+git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
@@ -639,6 +673,7 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 # with that.
 
 export NO_PYTHON
+export NO_SVN_TESTS
 
 test: all
        $(MAKE) -C t/ all
@@ -652,6 +687,12 @@ test-delta$X: test-delta.c diff-delta.o patch-delta.o
 test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
+test-sha1$X: test-sha1.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
+check-sha1:: test-sha1$X
+       ./test-sha1.sh
+
 check:
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
 
diff --git a/blame.c b/blame.c
index c86e2fd4b31fc75adf1c315fda166aabc38f0f43..b04b8f58aaf718208ab13e5cbfd8cd2a4444cbd7 100644 (file)
--- a/blame.c
+++ b/blame.c
 #define DEBUG 0
 
 static const char blame_usage[] = "[-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n"
-       "  -c, --compability Use the same output mode as git-annotate (Default: off)\n"
-       "  -l, --long        Show long commit SHA1 (Default: off)\n"
-       "  -t, --time        Show raw timestamp (Default: off)\n"
-       "  -S, --revs-file   Use revisions from revs-file instead of calling git-rev-list\n"
-       "  -h, --help        This message";
+       "  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+       "  -l, --long          Show long commit SHA1 (Default: off)\n"
+       "  -t, --time          Show raw timestamp (Default: off)\n"
+       "  -S, --revs-file     Use revisions from revs-file instead of calling git-rev-list\n"
+       "  -h, --help          This message";
 
 static struct commit **blame_lines;
 static int num_blame_lines;
@@ -44,8 +44,8 @@ struct util_info {
 };
 
 struct chunk {
-       int off1, len1; // ---
-       int off2, len2; // +++
+       int off1, len1; /* --- */
+       int off2, len2; /* +++ */
 };
 
 struct patch {
@@ -255,7 +255,7 @@ static void print_map(struct commit *cmit, struct commit *other)
 }
 #endif
 
-// p is a patch from commit to other.
+/* p is a patch from commit to other. */
 static void fill_line_map(struct commit *commit, struct commit *other,
                          struct patch *p)
 {
@@ -747,7 +747,7 @@ int main(int argc, const char **argv)
        const char *filename = NULL, *commit = NULL;
        char filename_buf[256];
        int sha1_len = 8;
-       int compability = 0;
+       int compatibility = 0;
        int show_raw_time = 0;
        int options = 1;
        struct commit* start_commit;
@@ -774,8 +774,8 @@ int main(int argc, const char **argv)
                                sha1_len = 40;
                                continue;
                        } else if(!strcmp(argv[i], "-c") ||
-                                 !strcmp(argv[i], "--compability")) {
-                               compability = 1;
+                                 !strcmp(argv[i], "--compatibility")) {
+                               compatibility = 1;
                                continue;
                        } else if(!strcmp(argv[i], "-t") ||
                                  !strcmp(argv[i], "--time")) {
@@ -784,7 +784,7 @@ int main(int argc, const char **argv)
                        } else if(!strcmp(argv[i], "-S")) {
                                if (i + 1 < argc &&
                                    !read_ancestry(argv[i + 1], &sha1_p)) {
-                                       compability = 1;
+                                       compatibility = 1;
                                        i++;
                                        continue;
                                }
@@ -884,7 +884,7 @@ int main(int argc, const char **argv)
                u = c->util;
                get_commit_info(c, &ci);
                fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
-               if(compability) {
+               if(compatibility) {
                        printf("\t(%10s\t%10s\t%d)", ci.author,
                               format_time(ci.author_time, ci.author_tz,
                                           show_raw_time),
diff --git a/blob.c b/blob.c
index 496f270043a617242efa9d27285853feb2597484..d1af2e62f14965fab491a3d9dde712f02b718adc 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -10,12 +10,12 @@ struct blob *lookup_blob(const unsigned char *sha1)
        if (!obj) {
                struct blob *ret = alloc_blob_node();
                created_object(sha1, &ret->object);
-               ret->object.type = TYPE_BLOB;
+               ret->object.type = OBJ_BLOB;
                return ret;
        }
        if (!obj->type)
-               obj->type = TYPE_BLOB;
-       if (obj->type != TYPE_BLOB) {
+               obj->type = OBJ_BLOB;
+       if (obj->type != OBJ_BLOB) {
                error("Object %s is a %s, not a blob",
                      sha1_to_hex(sha1), typename(obj->type));
                return NULL;
index bfbbb1bf52e10667f94dd574ab32fe070c98c4cb..3a73a173f729e0ca75e650779773316e8ae3fe98 100644 (file)
@@ -21,8 +21,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
 
        for (specs = 0; pathspec[specs];  specs++)
                /* nothing */;
-       seen = xmalloc(specs);
-       memset(seen, 0, specs);
+       seen = xcalloc(specs, 1);
 
        src = dst = dir->entries;
        i = dir->nr;
@@ -181,7 +180,7 @@ int cmd_add(int argc, const char **argv, char **envp)
 
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_lock_file(&lock_file))
+                   close(newfd) || commit_lock_file(&lock_file))
                        die("Unable to write new index file");
        }
 
index e9ead002d38368f6de6ba75f25542fe61258cfb9..d924ac3d0ab663c2ceb05c2cb9943f6caa23c0ef 100644 (file)
 #include "delta.h"
 #include "builtin.h"
 
-//  --check turns on checking that the working tree matches the
-//    files that are being modified, but doesn't apply the patch
-//  --stat does just a diffstat, and doesn't actually apply
-//  --numstat does numeric diffstat, and doesn't actually apply
-//  --index-info shows the old and new index info for paths if available.
-//  --index updates the cache as well.
-//  --cached updates only the cache without ever touching the working tree.
-//
+/*
+ *  --check turns on checking that the working tree matches the
+ *    files that are being modified, but doesn't apply the patch
+ *  --stat does just a diffstat, and doesn't actually apply
+ *  --numstat does numeric diffstat, and doesn't actually apply
+ *  --index-info shows the old and new index info for paths if available.
+ *  --index updates the cache as well.
+ *  --cached updates only the cache without ever touching the working tree.
+ */
 static const char *prefix;
 static int prefix_length = -1;
 static int newfd = -1;
@@ -284,8 +285,8 @@ static void parse_traditional_patch(const char *first, const char *second, struc
 {
        char *name;
 
-       first += 4;     // skip "--- "
-       second += 4;    // skip "+++ "
+       first += 4;     /* skip "--- " */
+       second += 4;    /* skip "+++ " */
        if (is_dev_null(first)) {
                patch->is_new = 1;
                patch->is_delete = 0;
@@ -765,7 +766,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                        continue;
 
                /*
-                * Make sure we don't find any unconnected patch fragmants.
+                * Make sure we don't find any unconnected patch fragments.
                 * That's a sign that we didn't find a header, and that a
                 * patch has become corrupted/broken up.
                 */
@@ -990,7 +991,7 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
         * so one line can fit up to 13 groups that would decode
         * to 52 bytes max.  The length byte 'A'-'Z' corresponds
         * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes.
-        * The end of binary is signalled with an empty line.
+        * The end of binary is signaled with an empty line.
         */
        int llen, used;
        struct fragment *fragment;
@@ -1663,13 +1664,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
        return 0;
 }
 
-static int check_patch(struct patch *patch)
+static int check_patch(struct patch *patch, struct patch *prev_patch)
 {
        struct stat st;
        const char *old_name = patch->old_name;
        const char *new_name = patch->new_name;
        const char *name = old_name ? old_name : new_name;
        struct cache_entry *ce = NULL;
+       int ok_if_exists;
 
        if (old_name) {
                int changed = 0;
@@ -1727,13 +1729,33 @@ static int check_patch(struct patch *patch)
                                old_name, st_mode, patch->old_mode);
        }
 
+       if (new_name && prev_patch && prev_patch->is_delete &&
+           !strcmp(prev_patch->old_name, new_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.
+                */
+               ok_if_exists = 1;
+       else
+               ok_if_exists = 0;
+
        if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
-               if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0)
+               if (check_index &&
+                   cache_name_pos(new_name, strlen(new_name)) >= 0 &&
+                   !ok_if_exists)
                        return error("%s: already exists in index", new_name);
                if (!cached) {
-                       if (!lstat(new_name, &st))
-                               return error("%s: already exists in working directory", new_name);
-                       if (errno != ENOENT)
+                       struct stat nst;
+                       if (!lstat(new_name, &nst)) {
+                               if (S_ISDIR(nst.st_mode) || ok_if_exists)
+                                       ; /* ok */
+                               else
+                                       return error("%s: already exists in working directory", new_name);
+                       }
+                       else if ((errno != ENOENT) && (errno != ENOTDIR))
                                return error("%s: %s", new_name, strerror(errno));
                }
                if (!patch->new_mode) {
@@ -1761,10 +1783,13 @@ static int check_patch(struct patch *patch)
 
 static int check_patch_list(struct patch *patch)
 {
+       struct patch *prev_patch = NULL;
        int error = 0;
 
-       for (;patch ; patch = patch->next)
-               error |= check_patch(patch);
+       for (prev_patch = NULL; patch ; patch = patch->next) {
+               error |= check_patch(patch, prev_patch);
+               prev_patch = patch;
+       }
        return error;
 }
 
@@ -2009,6 +2034,16 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
                        return;
        }
 
+       if (errno == EEXIST || errno == EACCES) {
+               /* We may be trying to create a file where a directory
+                * used to be.
+                */
+               struct stat st;
+               errno = 0;
+               if (!lstat(path, &st) && S_ISDIR(st.st_mode) && !rmdir(path))
+                       errno = EEXIST;
+       }
+
        if (errno == EEXIST) {
                unsigned int nr = getpid();
 
@@ -2043,32 +2078,42 @@ static void create_file(struct patch *patch)
        cache_tree_invalidate_path(active_cache_tree, path);
 }
 
-static void write_out_one_result(struct patch *patch)
+/* phase zero is to remove, phase one is to create */
+static void write_out_one_result(struct patch *patch, int phase)
 {
        if (patch->is_delete > 0) {
-               remove_file(patch);
+               if (phase == 0)
+                       remove_file(patch);
                return;
        }
        if (patch->is_new > 0 || patch->is_copy) {
-               create_file(patch);
+               if (phase == 1)
+                       create_file(patch);
                return;
        }
        /*
         * Rename or modification boils down to the same
         * thing: remove the old, write the new
         */
-       remove_file(patch);
+       if (phase == 0)
+               remove_file(patch);
+       if (phase == 1)
        create_file(patch);
 }
 
 static void write_out_results(struct patch *list, int skipped_patch)
 {
+       int phase;
+
        if (!list && !skipped_patch)
                die("No changes");
 
-       while (list) {
-               write_out_one_result(list);
-               list = list->next;
+       for (phase = 0; phase < 2; phase++) {
+               struct patch *l = list;
+               while (l) {
+                       write_out_one_result(l, phase);
+                       l = l->next;
+               }
        }
 }
 
@@ -2323,7 +2368,7 @@ int cmd_apply(int argc, const char **argv, char **envp)
 
        if (write_index) {
                if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_lock_file(&lock_file))
+                   close(newfd) || commit_lock_file(&lock_file))
                        die("Unable to write new index file");
        }
 
index 5afc1d7208f8a13b7363be2127cad3b43baa95dd..81ac2fe64aea23f9db605da181da098dcfca757a 100644 (file)
@@ -18,7 +18,7 @@ int cmd_diff_files(int argc, const char **argv, char **envp)
        struct rev_info rev;
        int silent = 0;
 
-       git_config(git_diff_config);
+       git_config(git_default_config); /* no "diff" UI options */
        init_revisions(&rev);
        rev.abbrev = 0;
 
@@ -36,6 +36,9 @@ int cmd_diff_files(int argc, const char **argv, char **envp)
                        usage(diff_files_usage);
                argv++; argc--;
        }
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+
        /*
         * Make sure there are NO revision (i.e. pending object) parameter,
         * rev.max_count is reasonable (0 <= n <= 3),
index c42ef9a7a76e7d8f6bc25d593865b9343d4e74b5..a1fa1b85cf741cd6b5f06afd02abbe618e86f5d1 100644 (file)
@@ -15,7 +15,7 @@ int cmd_diff_index(int argc, const char **argv, char **envp)
        int cached = 0;
        int i;
 
-       git_config(git_diff_config);
+       git_config(git_default_config); /* no "diff" UI options */
        init_revisions(&rev);
        rev.abbrev = 0;
 
@@ -28,6 +28,9 @@ int cmd_diff_index(int argc, const char **argv, char **envp)
                else
                        usage(diff_cache_usage);
        }
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+
        /*
         * Make sure there is one revision (i.e. pending object),
         * and there is no revision filtering parameters.
index 7c157ca889eaf85b38be2764fca83814bb83a3f4..9c62702941569511a149eba89a8be2b478f04b70 100644 (file)
@@ -61,7 +61,7 @@ int cmd_diff_stages(int ac, const char **av, char **envp)
        const char *prefix = setup_git_directory();
        const char **pathspec = NULL;
 
-       git_config(git_diff_config);
+       git_config(git_default_config); /* no "diff" UI options */
        read_cache();
        diff_setup(&diff_options);
        while (1 < ac && av[1][0] == '-') {
@@ -85,6 +85,9 @@ int cmd_diff_stages(int ac, const char **av, char **envp)
                ac--; av++;
        }
 
+       if (!diff_options.output_format)
+               diff_options.output_format = DIFF_FORMAT_RAW;
+
        if (ac < 3 ||
            sscanf(av[1], "%d", &stage1) != 1 ||
            ! (0 <= stage1 && stage1 <= 3) ||
index 3409a39a9f73f9ed67547f5cc79619c1e402f47b..b6106685942e3cb3a5db2b3a67123b5bd554d856 100644 (file)
@@ -67,7 +67,7 @@ int cmd_diff_tree(int argc, const char **argv, char **envp)
        static struct rev_info *opt = &log_tree_opt;
        int read_stdin = 0;
 
-       git_config(git_diff_config);
+       git_config(git_default_config); /* no "diff" UI options */
        nr_sha1 = 0;
        init_revisions(opt);
        opt->abbrev = 0;
@@ -84,6 +84,9 @@ int cmd_diff_tree(int argc, const char **argv, char **envp)
                usage(diff_tree_usage);
        }
 
+       if (!opt->diffopt.output_format)
+               opt->diffopt.output_format = DIFF_FORMAT_RAW;
+
        /*
         * NOTE! We expect "a ^b" to be equal to "a..b", so we
         * reverse the order of the objects if the second one
index 99a2f766055d6c8c3231e38bd369169d3c742782..cb38f445611735cff5bc9fbafb3189e6ccc6c2fd 100644 (file)
@@ -39,8 +39,6 @@ static int builtin_diff_files(struct rev_info *revs,
                        revs->max_count = 3;
                else if (!strcmp(arg, "-q"))
                        silent = 1;
-               else if (!strcmp(arg, "--raw"))
-                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
                else
                        usage(builtin_diff_usage);
                argv++; argc--;
@@ -56,7 +54,7 @@ static int builtin_diff_files(struct rev_info *revs,
            3 < revs->max_count)
                usage(builtin_diff_usage);
        if (revs->max_count < 0 &&
-           (revs->diffopt.output_format == DIFF_FORMAT_PATCH))
+           (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
                revs->combine_merges = revs->dense_combined_merges = 1;
        /*
         * Backward compatibility wart - "diff-files -s" used to
@@ -107,14 +105,9 @@ static int builtin_diff_b_f(struct rev_info *revs,
        /* Blob vs file in the working tree*/
        struct stat st;
 
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (!strcmp(arg, "--raw"))
-                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
-               else
-                       usage(builtin_diff_usage);
-               argv++; argc--;
-       }
+       if (argc > 1)
+               usage(builtin_diff_usage);
+
        if (lstat(path, &st))
                die("'%s': %s", path, strerror(errno));
        if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
@@ -137,14 +130,9 @@ static int builtin_diff_blobs(struct rev_info *revs,
         */
        unsigned mode = canon_mode(S_IFREG | 0644);
 
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (!strcmp(arg, "--raw"))
-                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
-               else
-                       usage(builtin_diff_usage);
-               argv++; argc--;
-       }
+       if (argc > 1)
+               usage(builtin_diff_usage);
+
        stuff_change(&revs->diffopt,
                     mode, mode,
                     blob[1].sha1, blob[0].sha1,
@@ -162,8 +150,6 @@ static int builtin_diff_index(struct rev_info *revs,
                const char *arg = argv[1];
                if (!strcmp(arg, "--cached"))
                        cached = 1;
-               else if (!strcmp(arg, "--raw"))
-                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
                else
                        usage(builtin_diff_usage);
                argv++; argc--;
@@ -185,17 +171,12 @@ static int builtin_diff_tree(struct rev_info *revs,
 {
        const unsigned char *(sha1[2]);
        int swap = 0;
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (!strcmp(arg, "--raw"))
-                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
-               else
-                       usage(builtin_diff_usage);
-               argv++; argc--;
-       }
+
+       if (argc > 1)
+               usage(builtin_diff_usage);
 
        /* We saw two trees, ent[0] and ent[1].
-        * if ent[1] is unintesting, they are swapped
+        * if ent[1] is uninteresting, they are swapped
         */
        if (ent[1].item->flags & UNINTERESTING)
                swap = 1;
@@ -214,14 +195,9 @@ static int builtin_diff_combined(struct rev_info *revs,
        const unsigned char (*parent)[20];
        int i;
 
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (!strcmp(arg, "--raw"))
-                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
-               else
-                       usage(builtin_diff_usage);
-               argv++; argc--;
-       }
+       if (argc > 1)
+               usage(builtin_diff_usage);
+
        if (!revs->dense_combined_merges && !revs->combine_merges)
                revs->dense_combined_merges = revs->combine_merges = 1;
        parent = xmalloc(ents * sizeof(*parent));
@@ -274,11 +250,15 @@ int cmd_diff(int argc, const char **argv, char **envp)
         * Other cases are errors.
         */
 
-       git_config(git_diff_config);
+       git_config(git_diff_ui_config);
        init_revisions(&rev);
-       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
 
        argc = setup_revisions(argc, argv, &rev, NULL);
+       if (!rev.diffopt.output_format) {
+               rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+               diff_setup_done(&rev.diffopt);
+       }
+
        /* Do we have --cached and not have a pending object, then
         * default to HEAD by hand.  Eek.
         */
@@ -305,9 +285,9 @@ int cmd_diff(int argc, const char **argv, char **envp)
                obj = deref_tag(obj, NULL, 0);
                if (!obj)
                        die("invalid object '%s' given.", name);
-               if (obj->type == TYPE_COMMIT)
+               if (obj->type == OBJ_COMMIT)
                        obj = &((struct commit *)obj)->tree->object;
-               if (obj->type == TYPE_TREE) {
+               if (obj->type == OBJ_TREE) {
                        if (ARRAY_SIZE(ent) <= ents)
                                die("more than %d trees given: '%s'",
                                    (int) ARRAY_SIZE(ent), name);
@@ -317,7 +297,7 @@ int cmd_diff(int argc, const char **argv, char **envp)
                        ents++;
                        continue;
                }
-               if (obj->type == TYPE_BLOB) {
+               if (obj->type == OBJ_BLOB) {
                        if (2 <= blobs)
                                die("more than two blobs given: '%s'", name);
                        memcpy(blob[blobs].sha1, obj->sha1, 20);
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
new file mode 100644 (file)
index 0000000..f20b27b
--- /dev/null
@@ -0,0 +1,359 @@
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "tag.h"
+
+static const char *fmt_merge_msg_usage =
+       "git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]";
+
+static int merge_summary = 0;
+
+static int fmt_merge_msg_config(const char *key, const char *value)
+{
+       if (!strcmp("merge.summary", key))
+               merge_summary = git_config_bool(key, value);
+       return 0;
+}
+
+struct list {
+       char **list;
+       void **payload;
+       unsigned nr, alloc;
+};
+
+static void append_to_list(struct list *list, char *value, void *payload)
+{
+       if (list->nr == list->alloc) {
+               list->alloc += 32;
+               list->list = realloc(list->list, sizeof(char *) * list->alloc);
+               list->payload = realloc(list->payload,
+                               sizeof(char *) * list->alloc);
+       }
+       list->payload[list->nr] = payload;
+       list->list[list->nr++] = value;
+}
+
+static int find_in_list(struct list *list, char *value)
+{
+       int i;
+
+       for (i = 0; i < list->nr; i++)
+               if (!strcmp(list->list[i], value))
+                       return i;
+
+       return -1;
+}
+
+static void free_list(struct list *list)
+{
+       int i;
+
+       if (list->alloc == 0)
+               return;
+
+       for (i = 0; i < list->nr; i++) {
+               free(list->list[i]);
+               if (list->payload[i])
+                       free(list->payload[i]);
+       }
+       free(list->list);
+       free(list->payload);
+       list->nr = list->alloc = 0;
+}
+
+struct src_data {
+       struct list branch, tag, r_branch, generic;
+       int head_status;
+};
+
+static struct list srcs = { NULL, NULL, 0, 0};
+static struct list origins = { NULL, NULL, 0, 0};
+
+static int handle_line(char *line)
+{
+       int i, len = strlen(line);
+       unsigned char *sha1;
+       char *src, *origin;
+       struct src_data *src_data;
+       int pulling_head = 0;
+
+       if (len < 43 || line[40] != '\t')
+               return 1;
+
+       if (!strncmp(line + 41, "not-for-merge", 13))
+               return 0;
+
+       if (line[41] != '\t')
+               return 2;
+
+       line[40] = 0;
+       sha1 = xmalloc(20);
+       i = get_sha1(line, sha1);
+       line[40] = '\t';
+       if (i)
+               return 3;
+
+       if (line[len - 1] == '\n')
+               line[len - 1] = 0;
+       line += 42;
+
+       src = strstr(line, " of ");
+       if (src) {
+               *src = 0;
+               src += 4;
+               pulling_head = 0;
+       } else {
+               src = line;
+               pulling_head = 1;
+       }
+
+       i = find_in_list(&srcs, src);
+       if (i < 0) {
+               i = srcs.nr;
+               append_to_list(&srcs, strdup(src),
+                               xcalloc(1, sizeof(struct src_data)));
+       }
+       src_data = srcs.payload[i];
+
+       if (pulling_head) {
+               origin = strdup(src);
+               src_data->head_status |= 1;
+       } else if (!strncmp(line, "branch ", 7)) {
+               origin = strdup(line + 7);
+               append_to_list(&src_data->branch, origin, NULL);
+               src_data->head_status |= 2;
+       } else if (!strncmp(line, "tag ", 4)) {
+               origin = line;
+               append_to_list(&src_data->tag, strdup(origin + 4), NULL);
+               src_data->head_status |= 2;
+       } else if (!strncmp(line, "remote branch ", 14)) {
+               origin = strdup(line + 14);
+               append_to_list(&src_data->r_branch, origin, NULL);
+               src_data->head_status |= 2;
+       } else {
+               origin = strdup(src);
+               append_to_list(&src_data->generic, strdup(line), NULL);
+               src_data->head_status |= 2;
+       }
+
+       if (!strcmp(".", src) || !strcmp(src, origin)) {
+               int len = strlen(origin);
+               if (origin[0] == '\'' && origin[len - 1] == '\'') {
+                       char *new_origin = malloc(len - 1);
+                       memcpy(new_origin, origin + 1, len - 2);
+                       new_origin[len - 1] = 0;
+                       origin = new_origin;
+               } else
+                       origin = strdup(origin);
+       } else {
+               char *new_origin = malloc(strlen(origin) + strlen(src) + 5);
+               sprintf(new_origin, "%s of %s", origin, src);
+               origin = new_origin;
+       }
+       append_to_list(&origins, origin, sha1);
+       return 0;
+}
+
+static void print_joined(const char *singular, const char *plural,
+               struct list *list)
+{
+       if (list->nr == 0)
+               return;
+       if (list->nr == 1) {
+               printf("%s%s", singular, list->list[0]);
+       } else {
+               int i;
+               printf("%s", plural);
+               for (i = 0; i < list->nr - 1; i++)
+                       printf("%s%s", i > 0 ? ", " : "", list->list[i]);
+               printf(" and %s", list->list[list->nr - 1]);
+       }
+}
+
+static void shortlog(const char *name, unsigned char *sha1,
+               struct commit *head, struct rev_info *rev, int limit)
+{
+       int i, count = 0;
+       struct commit *commit;
+       struct object *branch;
+       struct list subjects = { NULL, NULL, 0, 0 };
+       int flags = UNINTERESTING | TREECHANGE | SEEN | SHOWN | ADDED;
+
+       branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
+       if (!branch || branch->type != OBJ_COMMIT)
+               return;
+
+       setup_revisions(0, NULL, rev, NULL);
+       rev->ignore_merges = 1;
+       add_pending_object(rev, branch, name);
+       add_pending_object(rev, &head->object, "^HEAD");
+       head->object.flags |= UNINTERESTING;
+       prepare_revision_walk(rev);
+       while ((commit = get_revision(rev)) != NULL) {
+               char *oneline, *bol, *eol;
+
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+
+               count++;
+               if (subjects.nr > limit)
+                       continue;
+
+               bol = strstr(commit->buffer, "\n\n");
+               if (!bol) {
+                       append_to_list(&subjects, strdup(sha1_to_hex(
+                                                       commit->object.sha1)),
+                                       NULL);
+                       continue;
+               }
+
+               bol += 2;
+               eol = strchr(bol, '\n');
+
+               if (eol) {
+                       int len = eol - bol;
+                       oneline = malloc(len + 1);
+                       memcpy(oneline, bol, len);
+                       oneline[len] = 0;
+               } else
+                       oneline = strdup(bol);
+               append_to_list(&subjects, oneline, NULL);
+       }
+
+       if (count > limit)
+               printf("\n* %s: (%d commits)\n", name, count);
+       else
+               printf("\n* %s:\n", name);
+
+       for (i = 0; i < subjects.nr; i++)
+               if (i >= limit)
+                       printf("  ...\n");
+               else
+                       printf("  %s\n", subjects.list[i]);
+
+       clear_commit_marks((struct commit *)branch, flags);
+       clear_commit_marks(head, flags);
+       free_commit_list(rev->commits);
+       rev->commits = NULL;
+       rev->pending.nr = 0;
+
+       free_list(&subjects);
+}
+
+int cmd_fmt_merge_msg(int argc, char **argv, char **envp)
+{
+       int limit = 20, i = 0;
+       char line[1024];
+       FILE *in = stdin;
+       const char *sep = "";
+       unsigned char head_sha1[20];
+       const char *head, *current_branch;
+
+       git_config(fmt_merge_msg_config);
+
+       while (argc > 1) {
+               if (!strcmp(argv[1], "--summary"))
+                       merge_summary = 1;
+               else if (!strcmp(argv[1], "--no-summary"))
+                       merge_summary = 0;
+               else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
+                       if (argc < 2)
+                               die ("Which file?");
+                       if (!strcmp(argv[2], "-"))
+                               in = stdin;
+                       else {
+                               fclose(in);
+                               in = fopen(argv[2], "r");
+                       }
+                       argc--; argv++;
+               } else
+                       break;
+               argc--; argv++;
+       }
+
+       if (argc > 1)
+               usage(fmt_merge_msg_usage);
+
+       /* get current branch */
+       head = strdup(git_path("HEAD"));
+       current_branch = resolve_ref(head, head_sha1, 1);
+       current_branch += strlen(head) - 4;
+       free((char *)head);
+       if (!strncmp(current_branch, "refs/heads/", 11))
+               current_branch += 11;
+
+       while (fgets(line, sizeof(line), in)) {
+               i++;
+               if (line[0] == 0)
+                       continue;
+               if (handle_line(line))
+                       die ("Error in line %d: %s", i, line);
+       }
+
+       printf("Merge ");
+       for (i = 0; i < srcs.nr; i++) {
+               struct src_data *src_data = srcs.payload[i];
+               const char *subsep = "";
+
+               printf(sep);
+               sep = "; ";
+
+               if (src_data->head_status == 1) {
+                       printf(srcs.list[i]);
+                       continue;
+               }
+               if (src_data->head_status == 3) {
+                       subsep = ", ";
+                       printf("HEAD");
+               }
+               if (src_data->branch.nr) {
+                       printf(subsep);
+                       subsep = ", ";
+                       print_joined("branch ", "branches ", &src_data->branch);
+               }
+               if (src_data->r_branch.nr) {
+                       printf(subsep);
+                       subsep = ", ";
+                       print_joined("remote branch ", "remote branches ",
+                                       &src_data->r_branch);
+               }
+               if (src_data->tag.nr) {
+                       printf(subsep);
+                       subsep = ", ";
+                       print_joined("tag ", "tags ", &src_data->tag);
+               }
+               if (src_data->generic.nr) {
+                       printf(subsep);
+                       print_joined("commit ", "commits ", &src_data->generic);
+               }
+               if (strcmp(".", srcs.list[i]))
+                       printf(" of %s", srcs.list[i]);
+       }
+
+       if (!strcmp("master", current_branch))
+               putchar('\n');
+       else
+               printf(" into %s\n", current_branch);
+
+       if (merge_summary) {
+               struct commit *head;
+               struct rev_info rev;
+
+               head = lookup_commit(head_sha1);
+               init_revisions(&rev);
+               rev.commit_format = CMIT_FMT_ONELINE;
+               rev.ignore_merges = 1;
+               rev.limited = 1;
+
+               for (i = 0; i < origins.nr; i++)
+                       shortlog(origins.list[i], origins.payload[i],
+                                       head, &rev, limit);
+       }
+
+       /* No cleanup yet; is standalone anyway */
+
+       return 0;
+}
+
index 2e7986cecefc964f665b10d363569951c1f40293..a79bac305aea880d6decb787836fe86295ead32e 100644 (file)
@@ -82,17 +82,47 @@ static int pathspec_matches(const char **paths, const char *name)
        return 0;
 }
 
+enum grep_pat_token {
+       GREP_PATTERN,
+       GREP_AND,
+       GREP_OPEN_PAREN,
+       GREP_CLOSE_PAREN,
+       GREP_NOT,
+       GREP_OR,
+};
+
 struct grep_pat {
        struct grep_pat *next;
        const char *origin;
        int no;
+       enum grep_pat_token token;
        const char *pattern;
        regex_t regexp;
 };
 
+enum grep_expr_node {
+       GREP_NODE_ATOM,
+       GREP_NODE_NOT,
+       GREP_NODE_AND,
+       GREP_NODE_OR,
+};
+
+struct grep_expr {
+       enum grep_expr_node node;
+       union {
+               struct grep_pat *atom;
+               struct grep_expr *unary;
+               struct {
+                       struct grep_expr *left;
+                       struct grep_expr *right;
+               } binary;
+       } u;
+};
+
 struct grep_opt {
        struct grep_pat *pattern_list;
        struct grep_pat **pattern_tail;
+       struct grep_expr *pattern_expression;
        regex_t regexp;
        unsigned linenum:1;
        unsigned invert:1;
@@ -105,43 +135,224 @@ struct grep_opt {
 #define GREP_BINARY_NOMATCH    1
 #define GREP_BINARY_TEXT       2
        unsigned binary:2;
+       unsigned extended:1;
        int regflags;
        unsigned pre_context;
        unsigned post_context;
 };
 
 static void add_pattern(struct grep_opt *opt, const char *pat,
-                       const char *origin, int no)
+                       const char *origin, int no, enum grep_pat_token t)
 {
        struct grep_pat *p = xcalloc(1, sizeof(*p));
        p->pattern = pat;
        p->origin = origin;
        p->no = no;
+       p->token = t;
        *opt->pattern_tail = p;
        opt->pattern_tail = &p->next;
        p->next = NULL;
 }
 
+static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
+{
+       int err = regcomp(&p->regexp, p->pattern, opt->regflags);
+       if (err) {
+               char errbuf[1024];
+               char where[1024];
+               if (p->no)
+                       sprintf(where, "In '%s' at %d, ",
+                               p->origin, p->no);
+               else if (p->origin)
+                       sprintf(where, "%s, ", p->origin);
+               else
+                       where[0] = 0;
+               regerror(err, &p->regexp, errbuf, 1024);
+               regfree(&p->regexp);
+               die("%s'%s': %s", where, p->pattern, errbuf);
+       }
+}
+
+#if DEBUG
+static inline void indent(int in)
+{
+       int i;
+       for (i = 0; i < in; i++) putchar(' ');
+}
+
+static void dump_pattern_exp(struct grep_expr *x, int in)
+{
+       switch (x->node) {
+       case GREP_NODE_ATOM:
+               indent(in);
+               puts(x->u.atom->pattern);
+               break;
+       case GREP_NODE_NOT:
+               indent(in);
+               puts("--not");
+               dump_pattern_exp(x->u.unary, in+1);
+               break;
+       case GREP_NODE_AND:
+               dump_pattern_exp(x->u.binary.left, in+1);
+               indent(in);
+               puts("--and");
+               dump_pattern_exp(x->u.binary.right, in+1);
+               break;
+       case GREP_NODE_OR:
+               dump_pattern_exp(x->u.binary.left, in+1);
+               indent(in);
+               puts("--or");
+               dump_pattern_exp(x->u.binary.right, in+1);
+               break;
+       }
+}
+
+static void looking_at(const char *msg, struct grep_pat **list)
+{
+       struct grep_pat *p = *list;
+       fprintf(stderr, "%s: looking at ", msg);
+       if (!p)
+               fprintf(stderr, "empty\n");
+       else
+               fprintf(stderr, "<%s>\n", p->pattern);
+}
+#else
+#define looking_at(a,b) do {} while(0)
+#endif
+
+static struct grep_expr *compile_pattern_expr(struct grep_pat **);
+static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
+{
+       struct grep_pat *p;
+       struct grep_expr *x;
+
+       looking_at("atom", list);
+
+       p = *list;
+       switch (p->token) {
+       case GREP_PATTERN: /* atom */
+               x = xcalloc(1, sizeof (struct grep_expr));
+               x->node = GREP_NODE_ATOM;
+               x->u.atom = p;
+               *list = p->next;
+               return x;
+       case GREP_OPEN_PAREN:
+               *list = p->next;
+               x = compile_pattern_expr(list);
+               if (!x)
+                       return NULL;
+               if (!*list || (*list)->token != GREP_CLOSE_PAREN)
+                       die("unmatched parenthesis");
+               *list = (*list)->next;
+               return x;
+       default:
+               return NULL;
+       }
+}
+
+static struct grep_expr *compile_pattern_not(struct grep_pat **list)
+{
+       struct grep_pat *p;
+       struct grep_expr *x;
+
+       looking_at("not", list);
+
+       p = *list;
+       switch (p->token) {
+       case GREP_NOT:
+               if (!p->next)
+                       die("--not not followed by pattern expression");
+               *list = p->next;
+               x = xcalloc(1, sizeof (struct grep_expr));
+               x->node = GREP_NODE_NOT;
+               x->u.unary = compile_pattern_not(list);
+               if (!x->u.unary)
+                       die("--not followed by non pattern expression");
+               return x;
+       default:
+               return compile_pattern_atom(list);
+       }
+}
+
+static struct grep_expr *compile_pattern_and(struct grep_pat **list)
+{
+       struct grep_pat *p;
+       struct grep_expr *x, *y, *z;
+
+       looking_at("and", list);
+
+       x = compile_pattern_not(list);
+       p = *list;
+       if (p && p->token == GREP_AND) {
+               if (!p->next)
+                       die("--and not followed by pattern expression");
+               *list = p->next;
+               y = compile_pattern_and(list);
+               if (!y)
+                       die("--and not followed by pattern expression");
+               z = xcalloc(1, sizeof (struct grep_expr));
+               z->node = GREP_NODE_AND;
+               z->u.binary.left = x;
+               z->u.binary.right = y;
+               return z;
+       }
+       return x;
+}
+
+static struct grep_expr *compile_pattern_or(struct grep_pat **list)
+{
+       struct grep_pat *p;
+       struct grep_expr *x, *y, *z;
+
+       looking_at("or", list);
+
+       x = compile_pattern_and(list);
+       p = *list;
+       if (x && p && p->token != GREP_CLOSE_PAREN) {
+               y = compile_pattern_or(list);
+               if (!y)
+                       die("not a pattern expression %s", p->pattern);
+               z = xcalloc(1, sizeof (struct grep_expr));
+               z->node = GREP_NODE_OR;
+               z->u.binary.left = x;
+               z->u.binary.right = y;
+               return z;
+       }
+       return x;
+}
+
+static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
+{
+       looking_at("expr", list);
+
+       return compile_pattern_or(list);
+}
+
 static void compile_patterns(struct grep_opt *opt)
 {
        struct grep_pat *p;
+
+       /* First compile regexps */
        for (p = opt->pattern_list; p; p = p->next) {
-               int err = regcomp(&p->regexp, p->pattern, opt->regflags);
-               if (err) {
-                       char errbuf[1024];
-                       char where[1024];
-                       if (p->no)
-                               sprintf(where, "In '%s' at %d, ",
-                                       p->origin, p->no);
-                       else if (p->origin)
-                               sprintf(where, "%s, ", p->origin);
-                       else
-                               where[0] = 0;
-                       regerror(err, &p->regexp, errbuf, 1024);
-                       regfree(&p->regexp);
-                       die("%s'%s': %s", where, p->pattern, errbuf);
-               }
+               if (p->token == GREP_PATTERN)
+                       compile_regexp(p, opt);
+               else
+                       opt->extended = 1;
        }
+
+       if (!opt->extended)
+               return;
+
+       /* Then bundle them up in an expression.
+        * A classic recursive descent parser would do.
+        */
+       p = opt->pattern_list;
+       opt->pattern_expression = compile_pattern_expr(&p);
+#if DEBUG
+       dump_pattern_exp(opt->pattern_expression, 0);
+#endif
+       if (p)
+               die("incomplete pattern expression: %s", p->pattern);
 }
 
 static char *end_of_line(char *cp, unsigned long *left)
@@ -196,6 +407,79 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match)
        }
 }
 
+static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol)
+{
+       int hit = 0;
+       regmatch_t pmatch[10];
+
+       if (!opt->fixed) {
+               regex_t *exp = &p->regexp;
+               hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
+                              pmatch, 0);
+       }
+       else {
+               hit = !fixmatch(p->pattern, bol, pmatch);
+       }
+
+       if (hit && opt->word_regexp) {
+               /* Match beginning must be either
+                * beginning of the line, or at word
+                * boundary (i.e. the last char must
+                * not be alnum or underscore).
+                */
+               if ((pmatch[0].rm_so < 0) ||
+                   (eol - bol) <= pmatch[0].rm_so ||
+                   (pmatch[0].rm_eo < 0) ||
+                   (eol - bol) < pmatch[0].rm_eo)
+                       die("regexp returned nonsense");
+               if (pmatch[0].rm_so != 0 &&
+                   word_char(bol[pmatch[0].rm_so-1]))
+                       hit = 0;
+               if (pmatch[0].rm_eo != (eol-bol) &&
+                   word_char(bol[pmatch[0].rm_eo]))
+                       hit = 0;
+       }
+       return hit;
+}
+
+static int match_expr_eval(struct grep_opt *opt,
+                          struct grep_expr *x,
+                          char *bol, char *eol)
+{
+       switch (x->node) {
+       case GREP_NODE_ATOM:
+               return match_one_pattern(opt, x->u.atom, bol, eol);
+               break;
+       case GREP_NODE_NOT:
+               return !match_expr_eval(opt, x->u.unary, bol, eol);
+       case GREP_NODE_AND:
+               return (match_expr_eval(opt, x->u.binary.left, bol, eol) &&
+                       match_expr_eval(opt, x->u.binary.right, bol, eol));
+       case GREP_NODE_OR:
+               return (match_expr_eval(opt, x->u.binary.left, bol, eol) ||
+                       match_expr_eval(opt, x->u.binary.right, bol, eol));
+       }
+       die("Unexpected node type (internal error) %d\n", x->node);
+}
+
+static int match_expr(struct grep_opt *opt, char *bol, char *eol)
+{
+       struct grep_expr *x = opt->pattern_expression;
+       return match_expr_eval(opt, x, bol, eol);
+}
+
+static int match_line(struct grep_opt *opt, char *bol, char *eol)
+{
+       struct grep_pat *p;
+       if (opt->extended)
+               return match_expr(opt, bol, eol);
+       for (p = opt->pattern_list; p; p = p->next) {
+               if (match_one_pattern(opt, p, bol, eol))
+                       return 1;
+       }
+       return 0;
+}
+
 static int grep_buffer(struct grep_opt *opt, const char *name,
                       char *buf, unsigned long size)
 {
@@ -231,46 +515,15 @@ static int grep_buffer(struct grep_opt *opt, const char *name,
                hunk_mark = "--\n";
 
        while (left) {
-               regmatch_t pmatch[10];
                char *eol, ch;
                int hit = 0;
-               struct grep_pat *p;
 
                eol = end_of_line(bol, &left);
                ch = *eol;
                *eol = 0;
 
-               for (p = opt->pattern_list; p; p = p->next) {
-                       if (!opt->fixed) {
-                               regex_t *exp = &p->regexp;
-                               hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
-                                              pmatch, 0);
-                       }
-                       else {
-                               hit = !fixmatch(p->pattern, bol, pmatch);
-                       }
+               hit = match_line(opt, bol, eol);
 
-                       if (hit && opt->word_regexp) {
-                               /* Match beginning must be either
-                                * beginning of the line, or at word
-                                * boundary (i.e. the last char must
-                                * not be alnum or underscore).
-                                */
-                               if ((pmatch[0].rm_so < 0) ||
-                                   (eol - bol) <= pmatch[0].rm_so ||
-                                   (pmatch[0].rm_eo < 0) ||
-                                   (eol - bol) < pmatch[0].rm_eo)
-                                       die("regexp returned nonsense");
-                               if (pmatch[0].rm_so != 0 &&
-                                   word_char(bol[pmatch[0].rm_so-1]))
-                                       hit = 0;
-                               if (pmatch[0].rm_eo != (eol-bol) &&
-                                   word_char(bol[pmatch[0].rm_eo]))
-                                       hit = 0;
-                       }
-                       if (hit)
-                               break;
-               }
                /* "grep -v -e foo -e bla" should list lines
                 * that do not have either, so inversion should
                 * be done outside.
@@ -446,12 +699,14 @@ static int exec_grep(int argc, const char **argv)
 
 static int external_grep(struct grep_opt *opt, const char **paths, int cached)
 {
-       int i, nr, argc, hit, len;
+       int i, nr, argc, hit, len, status;
        const char *argv[MAXARGS+1];
        char randarg[ARGBUF];
        char *argptr = randarg;
        struct grep_pat *p;
 
+       if (opt->extended)
+               return -1;
        len = nr = 0;
        push_arg("grep");
        if (opt->fixed)
@@ -536,12 +791,17 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                argv[argc++] = name;
                if (argc < MAXARGS)
                        continue;
-               hit += exec_grep(argc, argv);
+               status = exec_grep(argc, argv);
+               if (0 < status)
+                       hit = 1;
                argc = nr;
        }
-       if (argc > nr)
-               hit += exec_grep(argc, argv);
-       return 0;
+       if (argc > nr) {
+               status = exec_grep(argc, argv);
+               if (0 < status)
+                       hit = 1;
+       }
+       return hit;
 }
 
 static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
@@ -631,9 +891,9 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
 static int grep_object(struct grep_opt *opt, const char **paths,
                       struct object *obj, const char *name)
 {
-       if (obj->type == TYPE_BLOB)
+       if (obj->type == OBJ_BLOB)
                return grep_sha1(opt, obj->sha1, name);
-       if (obj->type == TYPE_COMMIT || obj->type == TYPE_TREE) {
+       if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
                struct tree_desc tree;
                void *data;
                int hit;
@@ -652,6 +912,13 @@ static int grep_object(struct grep_opt *opt, const char **paths,
 static const char builtin_grep_usage[] =
 "git-grep <option>* <rev>* [-e] <pattern> [<path>...]";
 
+static const char emsg_invalid_context_len[] =
+"%s: invalid context length argument";
+static const char emsg_missing_context_len[] =
+"missing context length argument";
+static const char emsg_missing_argument[] =
+"option requires an argument -%s";
+
 int cmd_grep(int argc, const char **argv, char **envp)
 {
        int hit = 0;
@@ -674,7 +941,7 @@ int cmd_grep(int argc, const char **argv, char **envp)
         * pattern, but then what follows it must be zero or more
         * valid refs up to the -- (if exists), and then existing
         * paths.  If there is an explicit pattern, then the first
-        * unrecocnized non option is the beginning of the refs list
+        * unrecognized non option is the beginning of the refs list
         * that continues up to the -- (if exists), and then paths.
         */
 
@@ -759,7 +1026,7 @@ int cmd_grep(int argc, const char **argv, char **envp)
                        case 'A': case 'B': case 'C':
                                if (!arg[2]) {
                                        if (argc <= 1)
-                                               usage(builtin_grep_usage);
+                                               die(emsg_missing_context_len);
                                        scan = *++argv;
                                        argc--;
                                }
@@ -771,7 +1038,7 @@ int cmd_grep(int argc, const char **argv, char **envp)
                                break;
                        }
                        if (sscanf(scan, "%u", &num) != 1)
-                               usage(builtin_grep_usage);
+                               die(emsg_invalid_context_len, scan);
                        switch (arg[1]) {
                        case 'A':
                                opt.post_context = num;
@@ -790,7 +1057,7 @@ int cmd_grep(int argc, const char **argv, char **envp)
                        int lno = 0;
                        char buf[1024];
                        if (argc <= 1)
-                               usage(builtin_grep_usage);
+                               die(emsg_missing_argument, arg);
                        patterns = fopen(argv[1], "r");
                        if (!patterns)
                                die("'%s': %s", argv[1], strerror(errno));
@@ -801,30 +1068,55 @@ int cmd_grep(int argc, const char **argv, char **envp)
                                /* ignore empty line like grep does */
                                if (!buf[0])
                                        continue;
-                               add_pattern(&opt, strdup(buf), argv[1], ++lno);
+                               add_pattern(&opt, strdup(buf), argv[1], ++lno,
+                                           GREP_PATTERN);
                        }
                        fclose(patterns);
                        argv++;
                        argc--;
                        continue;
                }
+               if (!strcmp("--not", arg)) {
+                       add_pattern(&opt, arg, "command line", 0, GREP_NOT);
+                       continue;
+               }
+               if (!strcmp("--and", arg)) {
+                       add_pattern(&opt, arg, "command line", 0, GREP_AND);
+                       continue;
+               }
+               if (!strcmp("--or", arg))
+                       continue; /* no-op */
+               if (!strcmp("(", arg)) {
+                       add_pattern(&opt, arg, "command line", 0, GREP_OPEN_PAREN);
+                       continue;
+               }
+               if (!strcmp(")", arg)) {
+                       add_pattern(&opt, arg, "command line", 0, GREP_CLOSE_PAREN);
+                       continue;
+               }
                if (!strcmp("-e", arg)) {
                        if (1 < argc) {
-                               add_pattern(&opt, argv[1], "-e option", 0);
+                               add_pattern(&opt, argv[1], "-e option", 0,
+                                           GREP_PATTERN);
                                argv++;
                                argc--;
                                continue;
                        }
-                       usage(builtin_grep_usage);
+                       die(emsg_missing_argument, arg);
                }
-               if (!strcmp("--", arg))
+               if (!strcmp("--", arg)) {
+                       /* later processing wants to have this at argv[1] */
+                       argv--;
+                       argc++;
                        break;
+               }
                if (*arg == '-')
                        usage(builtin_grep_usage);
 
                /* First unrecognized non-option token */
                if (!opt.pattern_list) {
-                       add_pattern(&opt, arg, "command line", 0);
+                       add_pattern(&opt, arg, "command line", 0,
+                                   GREP_PATTERN);
                        break;
                }
                else {
index 7470faa56692e17608d202000b78a91565360b72..335fe5fedcc6523a3b49a3e00686fe381098bcda 100644 (file)
@@ -12,7 +12,7 @@
 static const char git_usage[] =
        "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
 
-/* most gui terms set COLUMNS (although some don't export it) */
+/* most GUI terminals set COLUMNS (although some don't export it) */
 static int term_columns(void)
 {
        char *col_string = getenv("COLUMNS");
index f9515a8a4ac8a53e87b6fb9f73e1964350481bfb..4052cc75bdb2e3915ff17af4d1dc59a3f84f99dd 100644 (file)
 #include "revision.h"
 #include "log-tree.h"
 #include "builtin.h"
+#include <time.h>
+#include <sys/time.h>
 
 /* this is in builtin-diff.c */
 void add_head(struct rev_info *revs);
 
-static int cmd_log_wc(int argc, const char **argv, char **envp,
+static void cmd_log_init(int argc, const char **argv, char **envp,
                      struct rev_info *rev)
 {
-       struct commit *commit;
-
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
        rev->verbose_header = 1;
        argc = setup_revisions(argc, argv, rev, "HEAD");
-       if (rev->always_show_header) {
-               if (rev->diffopt.pickaxe || rev->diffopt.filter) {
-                       rev->always_show_header = 0;
-                       if (rev->diffopt.output_format == DIFF_FORMAT_RAW)
-                               rev->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
-               }
-       }
-
+       if (rev->diffopt.pickaxe || rev->diffopt.filter)
+               rev->always_show_header = 0;
        if (argc > 1)
                die("unrecognized argument: %s", argv[1]);
+}
+
+static int cmd_log_walk(struct rev_info *rev)
+{
+       struct commit *commit;
 
        prepare_revision_walk(rev);
        setup_pager();
@@ -50,17 +49,22 @@ int cmd_whatchanged(int argc, const char **argv, char **envp)
 {
        struct rev_info rev;
 
+       git_config(git_diff_ui_config);
        init_revisions(&rev);
        rev.diff = 1;
        rev.diffopt.recursive = 1;
        rev.simplify_history = 0;
-       return cmd_log_wc(argc, argv, envp, &rev);
+       cmd_log_init(argc, argv, envp, &rev);
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+       return cmd_log_walk(&rev);
 }
 
 int cmd_show(int argc, const char **argv, char **envp)
 {
        struct rev_info rev;
 
+       git_config(git_diff_ui_config);
        init_revisions(&rev);
        rev.diff = 1;
        rev.diffopt.recursive = 1;
@@ -69,17 +73,19 @@ int cmd_show(int argc, const char **argv, char **envp)
        rev.always_show_header = 1;
        rev.ignore_merges = 0;
        rev.no_walk = 1;
-       return cmd_log_wc(argc, argv, envp, &rev);
+       cmd_log_init(argc, argv, envp, &rev);
+       return cmd_log_walk(&rev);
 }
 
 int cmd_log(int argc, const char **argv, char **envp)
 {
        struct rev_info rev;
 
+       git_config(git_diff_ui_config);
        init_revisions(&rev);
        rev.always_show_header = 1;
-       rev.diffopt.recursive = 1;
-       return cmd_log_wc(argc, argv, envp, &rev);
+       cmd_log_init(argc, argv, envp, &rev);
+       return cmd_log_walk(&rev);
 }
 
 static int istitlechar(char c)
@@ -101,7 +107,10 @@ static int git_format_config(const char *var, const char *value)
                strcat(extra_headers, value);
                return 0;
        }
-       return git_default_config(var, value);
+       if (!strcmp(var, "diff.color")) {
+               return 0;
+       }
+       return git_diff_ui_config(var, value);
 }
 
 
@@ -219,6 +228,18 @@ static void get_patch_ids(struct rev_info *rev, struct diff_options *options)
        o2->flags = flags2;
 }
 
+static void gen_message_id(char *dest, unsigned int length, char *base)
+{
+       const char *committer = git_committer_info(1);
+       const char *email_start = strrchr(committer, '<');
+       const char *email_end = strrchr(committer, '>');
+       if(!email_start || !email_end || email_start > email_end - 1)
+               die("Could not extract email from committer identity.");
+       snprintf(dest, length, "%s.%lu.git.%.*s", base,
+                (unsigned long) time(NULL),
+                (int)(email_end - email_start - 1), email_start + 1);
+}
+
 int cmd_format_patch(int argc, const char **argv, char **envp)
 {
        struct commit *commit;
@@ -230,20 +251,23 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
        int start_number = -1;
        int keep_subject = 0;
        int ignore_if_in_upstream = 0;
+       int thread = 0;
+       const char *in_reply_to = NULL;
        struct diff_options patch_id_opts;
        char *add_signoff = NULL;
+       char message_id[1024];
+       char ref_message_id[1024];
 
+       git_config(git_format_config);
        init_revisions(&rev);
        rev.commit_format = CMIT_FMT_EMAIL;
        rev.verbose_header = 1;
        rev.diff = 1;
-       rev.diffopt.with_raw = 0;
-       rev.diffopt.with_stat = 1;
        rev.combine_merges = 0;
        rev.ignore_merges = 1;
-       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+       rev.diffopt.msg_sep = "";
+       rev.diffopt.recursive = 1;
 
-       git_config(git_format_config);
        rev.extra_headers = extra_headers;
 
        /*
@@ -298,6 +322,16 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
                        rev.mime_boundary = argv[i] + 9;
                else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
                        ignore_if_in_upstream = 1;
+               else if (!strcmp(argv[i], "--thread"))
+                       thread = 1;
+               else if (!strncmp(argv[i], "--in-reply-to=", 14))
+                       in_reply_to = argv[i] + 14;
+               else if (!strcmp(argv[i], "--in-reply-to")) {
+                       i++;
+                       if (i == argc)
+                               die("Need a Message-Id for --in-reply-to");
+                       in_reply_to = argv[i];
+               }
                else
                        argv[j++] = argv[i];
        }
@@ -312,6 +346,9 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
        if (argc > 1)
                die ("unrecognized argument: %s", argv[1]);
 
+       if (!rev.diffopt.output_format)
+               rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
+
        if (output_directory) {
                if (use_stdout)
                        die("standard output, or directory, which one?");
@@ -352,10 +389,23 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
        if (numbered)
                rev.total = total + start_number - 1;
        rev.add_signoff = add_signoff;
+       rev.ref_message_id = in_reply_to;
        while (0 <= --nr) {
                int shown;
                commit = list[nr];
                rev.nr = total - nr + (start_number - 1);
+               /* Make the second and subsequent mails replies to the first */
+               if (thread) {
+                       if (nr == (total - 2)) {
+                               strncpy(ref_message_id, message_id,
+                                       sizeof(ref_message_id));
+                               ref_message_id[sizeof(ref_message_id)-1]='\0';
+                               rev.ref_message_id = ref_message_id;
+                       }
+                       gen_message_id(message_id, sizeof(message_id),
+                                      sha1_to_hex(commit->object.sha1));
+                       rev.message_id = message_id;
+               }
                if (!use_stdout)
                        reopen_stdout(commit, rev.nr, keep_subject);
                shown = log_tree_commit(&rev, commit);
index 3e40747cf57ed4a8f7f20d83510ae08bcfcbd33a..ac53f76f689fdc0a44b6e4921dc08b880e1349b0 100644 (file)
@@ -348,7 +348,7 @@ static void cleanup_space(char *buf)
        }
 }
 
-static void decode_header_bq(char *it);
+static void decode_header(char *it);
 typedef int (*header_fn_t)(char *);
 struct header_def {
        const char *name;
@@ -371,7 +371,7 @@ static void check_header(char *line, struct header_def *header)
                        /* Unwrap inline B and Q encoding, and optionally
                         * normalize the meta information to utf8.
                         */
-                       decode_header_bq(line + len + 2);
+                       decode_header(line + len + 2);
                        header[i].func(line + len + 2);
                        break;
                }
@@ -566,16 +566,19 @@ static void convert_to_utf8(char *line, char *charset)
 #endif
 }
 
-static void decode_header_bq(char *it)
+static int decode_header_bq(char *it)
 {
        char *in, *out, *ep, *cp, *sp;
        char outbuf[1000];
+       int rfc2047 = 0;
 
        in = it;
        out = outbuf;
        while ((ep = strstr(in, "=?")) != NULL) {
                int sz, encoding;
                char charset_q[256], piecebuf[256];
+               rfc2047 = 1;
+
                if (in != ep) {
                        sz = ep - in;
                        memcpy(out, in, sz);
@@ -589,19 +592,19 @@ static void decode_header_bq(char *it)
                ep += 2;
                cp = strchr(ep, '?');
                if (!cp)
-                       return; /* no munging */
+                       return rfc2047; /* no munging */
                for (sp = ep; sp < cp; sp++)
                        charset_q[sp - ep] = tolower(*sp);
                charset_q[cp - ep] = 0;
                encoding = cp[1];
                if (!encoding || cp[2] != '?')
-                       return; /* no munging */
+                       return rfc2047; /* no munging */
                ep = strstr(cp + 3, "?=");
                if (!ep)
-                       return; /* no munging */
+                       return rfc2047; /* no munging */
                switch (tolower(encoding)) {
                default:
-                       return; /* no munging */
+                       return rfc2047; /* no munging */
                case 'b':
                        sz = decode_b_segment(cp + 3, piecebuf, ep);
                        break;
@@ -610,7 +613,7 @@ static void decode_header_bq(char *it)
                        break;
                }
                if (sz < 0)
-                       return;
+                       return rfc2047;
                if (metainfo_charset)
                        convert_to_utf8(piecebuf, charset_q);
                strcpy(out, piecebuf);
@@ -619,6 +622,19 @@ static void decode_header_bq(char *it)
        }
        strcpy(out, in);
        strcpy(it, outbuf);
+       return rfc2047;
+}
+
+static void decode_header(char *it)
+{
+
+       if (decode_header_bq(it))
+               return;
+       /* otherwise "it" is a straight copy of the input.
+        * This can be binary guck but there is no charset specified.
+        */
+       if (metainfo_charset)
+               convert_to_utf8(it, "");
 }
 
 static void decode_transfer_encoding(char *line)
diff --git a/builtin-prune.c b/builtin-prune.c
new file mode 100644 (file)
index 0000000..d196c41
--- /dev/null
@@ -0,0 +1,259 @@
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "builtin.h"
+#include "cache-tree.h"
+
+static const char prune_usage[] = "git prune [-n]";
+static int show_only = 0;
+static struct rev_info revs;
+
+static int prune_object(char *path, const char *filename, const unsigned char *sha1)
+{
+       if (show_only) {
+               printf("would prune %s/%s\n", path, filename);
+               return 0;
+       }
+       unlink(mkpath("%s/%s", path, filename));
+       rmdir(path);
+       return 0;
+}
+
+static int prune_dir(int i, char *path)
+{
+       DIR *dir = opendir(path);
+       struct dirent *de;
+
+       if (!dir)
+               return 0;
+
+       while ((de = readdir(dir)) != NULL) {
+               char name[100];
+               unsigned char sha1[20];
+               int len = strlen(de->d_name);
+
+               switch (len) {
+               case 2:
+                       if (de->d_name[1] != '.')
+                               break;
+               case 1:
+                       if (de->d_name[0] != '.')
+                               break;
+                       continue;
+               case 38:
+                       sprintf(name, "%02x", i);
+                       memcpy(name+2, de->d_name, len+1);
+                       if (get_sha1_hex(name, sha1) < 0)
+                               break;
+
+                       /*
+                        * Do we know about this object?
+                        * It must have been reachable
+                        */
+                       if (lookup_object(sha1))
+                               continue;
+
+                       prune_object(path, de->d_name, sha1);
+                       continue;
+               }
+               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+       }
+       closedir(dir);
+       return 0;
+}
+
+static void prune_object_dir(const char *path)
+{
+       int i;
+       for (i = 0; i < 256; i++) {
+               static char dir[4096];
+               sprintf(dir, "%s/%02x", path, i);
+               prune_dir(i, dir);
+       }
+}
+
+static void process_blob(struct blob *blob,
+                        struct object_array *p,
+                        struct name_path *path,
+                        const char *name)
+{
+       struct object *obj = &blob->object;
+
+       if (obj->flags & SEEN)
+               return;
+       obj->flags |= SEEN;
+       /* Nothing to do, really .. The blob lookup was the important part */
+}
+
+static void process_tree(struct tree *tree,
+                        struct object_array *p,
+                        struct name_path *path,
+                        const char *name)
+{
+       struct object *obj = &tree->object;
+       struct tree_desc desc;
+       struct name_entry entry;
+       struct name_path me;
+
+       if (obj->flags & SEEN)
+               return;
+       obj->flags |= SEEN;
+       if (parse_tree(tree) < 0)
+               die("bad tree object %s", sha1_to_hex(obj->sha1));
+       name = strdup(name);
+       add_object(obj, p, path, name);
+       me.up = path;
+       me.elem = name;
+       me.elem_len = strlen(name);
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+
+       while (tree_entry(&desc, &entry)) {
+               if (S_ISDIR(entry.mode))
+                       process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
+               else
+                       process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
+       }
+       free(tree->buffer);
+       tree->buffer = NULL;
+}
+
+static void process_tag(struct tag *tag, struct object_array *p, const char *name)
+{
+       struct object *obj = &tag->object;
+       struct name_path me;
+
+       if (obj->flags & SEEN)
+               return;
+       obj->flags |= SEEN;
+
+       me.up = NULL;
+       me.elem = "tag:/";
+       me.elem_len = 5;
+
+       if (parse_tag(tag) < 0)
+               die("bad tag object %s", sha1_to_hex(obj->sha1));
+       add_object(tag->tagged, p, NULL, name);
+}
+
+static void walk_commit_list(struct rev_info *revs)
+{
+       int i;
+       struct commit *commit;
+       struct object_array objects = { 0, 0, NULL };
+
+       /* Walk all commits, process their trees */
+       while ((commit = get_revision(revs)) != NULL)
+               process_tree(commit->tree, &objects, NULL, "");
+
+       /* Then walk all the pending objects, recursively processing them too */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object_array_entry *pending = revs->pending.objects + i;
+               struct object *obj = pending->item;
+               const char *name = pending->name;
+               if (obj->type == OBJ_TAG) {
+                       process_tag((struct tag *) obj, &objects, name);
+                       continue;
+               }
+               if (obj->type == OBJ_TREE) {
+                       process_tree((struct tree *)obj, &objects, NULL, name);
+                       continue;
+               }
+               if (obj->type == OBJ_BLOB) {
+                       process_blob((struct blob *)obj, &objects, NULL, name);
+                       continue;
+               }
+               die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
+       }
+}
+
+static int add_one_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *object = parse_object(sha1);
+       if (!object)
+               die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
+       add_pending_object(&revs, object, "");
+       return 0;
+}
+
+static void add_one_tree(const unsigned char *sha1)
+{
+       struct tree *tree = lookup_tree(sha1);
+       add_pending_object(&revs, &tree->object, "");
+}
+
+static void add_cache_tree(struct cache_tree *it)
+{
+       int i;
+
+       if (it->entry_count >= 0)
+               add_one_tree(it->sha1);
+       for (i = 0; i < it->subtree_nr; i++)
+               add_cache_tree(it->down[i]->cache_tree);
+}
+
+static void add_cache_refs(void)
+{
+       int i;
+
+       read_cache();
+       for (i = 0; i < active_nr; i++) {
+               lookup_blob(active_cache[i]->sha1);
+               /*
+                * We could add the blobs to the pending list, but quite
+                * frankly, we don't care. Once we've looked them up, and
+                * added them as objects, we've really done everything
+                * there is to do for a blob
+                */
+       }
+       if (active_cache_tree)
+               add_cache_tree(active_cache_tree);
+}
+
+int cmd_prune(int argc, const char **argv, char **envp)
+{
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "-n")) {
+                       show_only = 1;
+                       continue;
+               }
+               usage(prune_usage);
+       }
+
+       /*
+        * Set up revision parsing, and mark us as being interested
+        * in all object types, not just commits.
+        */
+       init_revisions(&revs);
+       revs.tag_objects = 1;
+       revs.blob_objects = 1;
+       revs.tree_objects = 1;
+
+       /* Add all external refs */
+       for_each_ref(add_one_ref);
+
+       /* Add all refs from the index file */
+       add_cache_refs();
+
+       /*
+        * Set up the revision walk - this will move all commits
+        * from the pending list to the commit walking list.
+        */
+       prepare_revision_walk(&revs);
+
+       walk_commit_list(&revs);
+
+       prune_object_dir(get_object_directory());
+
+       return 0;
+}
index 66b94078229c09d8105fcd7f5800a2cba567c972..31cbfd73861326ad303472718365838329a55cb8 100644 (file)
@@ -104,7 +104,7 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
                        if (n < MAX_URI)
                                uri[n++] = strdup(s);
                        else
-                               error("more than %d URL's specified, ignoreing the rest", MAX_URI);
+                               error("more than %d URL's specified, ignoring the rest", MAX_URI);
                }
                else if (is_refspec && !has_explicit_refspec)
                        add_refspec(strdup(s));
@@ -273,7 +273,7 @@ static int do_push(const char *repo)
 int cmd_push(int argc, const char **argv, char **envp)
 {
        int i;
-       const char *repo = "origin";    // default repository
+       const char *repo = "origin";    /* default repository */
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
index 9a2099d7307903d62c85f3483a22969ab5094c84..122b6f130b37b27055efd7dffa25f41ef03698e8 100644 (file)
@@ -43,10 +43,7 @@ struct tree_entry_list {
        const unsigned char *sha1;
 };
 
-static struct tree_entry_list df_conflict_list = {
-       .name = NULL,
-       .next = &df_conflict_list
-};
+static struct tree_entry_list df_conflict_list;
 
 typedef int (*merge_fn_t)(struct cache_entry **src);
 
@@ -333,14 +330,9 @@ static void setup_progress_signal(void)
        setitimer(ITIMER_REAL, &v, NULL);
 }
 
+static struct checkout state;
 static void check_updates(struct cache_entry **src, int nr)
 {
-       static struct checkout state = {
-               .base_dir = "",
-               .force = 1,
-               .quiet = 1,
-               .refresh_cache = 1,
-       };
        unsigned short mask = htons(CE_UPDATE);
        unsigned last_percent = 200, cnt = 0, total = 0;
 
@@ -515,7 +507,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
        }
 
        merge->ce_flags &= ~htons(CE_STAGEMASK);
-       add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
+       add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
        return 1;
 }
 
@@ -526,7 +518,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
        else
                verify_absent(ce->name, "removed");
        ce->ce_mode = 0;
-       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
        invalidate_ce_path(ce);
        return 1;
 }
@@ -884,6 +876,12 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
        unsigned char sha1[20];
        merge_fn_t fn = NULL;
 
+       df_conflict_list.next = &df_conflict_list;
+       state.base_dir = "";
+       state.force = 1;
+       state.quiet = 1;
+       state.refresh_cache = 1;
+
        setup_git_directory();
        git_config(git_default_config);
 
@@ -1038,7 +1036,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
        }
 
        if (write_cache(newfd, active_cache, active_nr) ||
-           commit_lock_file(&lock_file))
+           close(newfd) || commit_lock_file(&lock_file))
                die("unable to write new index file");
        return 0;
 }
index 63bad0e96ade657bef21d9ca0e0de2e0ba8af61c..8f328713374679b023f1f8caaacab592c9e4f9a2 100644 (file)
@@ -167,16 +167,16 @@ static void show_commit_list(struct rev_info *revs)
                const char *name = pending->name;
                if (obj->flags & (UNINTERESTING | SEEN))
                        continue;
-               if (obj->type == TYPE_TAG) {
+               if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
                        add_object_array(obj, name, &objects);
                        continue;
                }
-               if (obj->type == TYPE_TREE) {
+               if (obj->type == OBJ_TREE) {
                        process_tree((struct tree *)obj, &objects, NULL, name);
                        continue;
                }
-               if (obj->type == TYPE_BLOB) {
+               if (obj->type == OBJ_BLOB) {
                        process_blob((struct blob *)obj, &objects, NULL, name);
                        continue;
                }
index 5f5ade45aec781df107d4a49a4a554575ac278bd..b3e4386c1baec7c3a56704e61e44e575203ab759 100644 (file)
@@ -164,6 +164,51 @@ static int show_file(const char *arg)
        return 0;
 }
 
+static int try_difference(const char *arg)
+{
+       char *dotdot;
+       unsigned char sha1[20];
+       unsigned char end[20];
+       const char *next;
+       const char *this;
+       int symmetric;
+
+       if (!(dotdot = strstr(arg, "..")))
+               return 0;
+       next = dotdot + 2;
+       this = arg;
+       symmetric = (*next == '.');
+
+       *dotdot = 0;
+       next += symmetric;
+
+       if (!*next)
+               next = "HEAD";
+       if (dotdot == arg)
+               this = "HEAD";
+       if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
+               show_rev(NORMAL, end, next);
+               show_rev(symmetric ? NORMAL : REVERSED, sha1, this);
+               if (symmetric) {
+                       struct commit_list *exclude;
+                       struct commit *a, *b;
+                       a = lookup_commit_reference(sha1);
+                       b = lookup_commit_reference(end);
+                       exclude = get_merge_bases(a, b, 1);
+                       while (exclude) {
+                               struct commit_list *n = exclude->next;
+                               show_rev(REVERSED,
+                                        exclude->item->object.sha1,NULL);
+                               free(exclude);
+                               exclude = n;
+                       }
+               }
+               return 1;
+       }
+       *dotdot = '.';
+       return 0;
+}
+
 int cmd_rev_parse(int argc, const char **argv, char **envp)
 {
        int i, as_is = 0, verify = 0;
@@ -174,7 +219,6 @@ int cmd_rev_parse(int argc, const char **argv, char **envp)
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               char *dotdot;
 
                if (as_is) {
                        if (show_file(arg) && as_is < 2)
@@ -326,23 +370,8 @@ int cmd_rev_parse(int argc, const char **argv, char **envp)
                }
 
                /* Not a flag argument */
-               dotdot = strstr(arg, "..");
-               if (dotdot) {
-                       unsigned char end[20];
-                       const char *next = dotdot + 2;
-                       const char *this = arg;
-                       *dotdot = 0;
-                       if (!*next)
-                               next = "HEAD";
-                       if (dotdot == arg)
-                               this = "HEAD";
-                       if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
-                               show_rev(NORMAL, end, next);
-                               show_rev(REVERSED, sha1, this);
-                               continue;
-                       }
-                       *dotdot = '.';
-               }
+               if (try_difference(arg))
+                       continue;
                if (!get_sha1(arg, sha1)) {
                        show_rev(NORMAL, sha1, arg);
                        continue;
index 4d56a1f07065e6e8ebaa95fdfe843cffb6283af3..bb810ba41ae23c9dc4c38fbb0fa7883a33d43524 100644 (file)
@@ -90,8 +90,7 @@ int cmd_rm(int argc, const char **argv, char **envp)
        seen = NULL;
        for (i = 0; pathspec[i] ; i++)
                /* nothing */;
-       seen = xmalloc(i);
-       memset(seen, 0, i);
+       seen = xcalloc(i, 1);
 
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
@@ -129,7 +128,7 @@ int cmd_rm(int argc, const char **argv, char **envp)
         * workspace. If we fail to remove the first one, we
         * abort the "git rm" (but once we've successfully removed
         * any file at all, we'll go ahead and commit to it all:
-        * by then we've already committed ourself and can't fail
+        * by then we've already committed ourselves and can't fail
         * in the middle)
         */
        if (force) {
@@ -147,7 +146,7 @@ int cmd_rm(int argc, const char **argv, char **envp)
 
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_lock_file(&lock_file))
+                   close(newfd) || commit_lock_file(&lock_file))
                        die("Unable to write new index file");
        }
 
index 09d82278625584213f710a984652a7efc07339c5..82f75b72dea1a5e6c620003d5c0c4c414dfe42e3 100644 (file)
@@ -6,7 +6,7 @@
 #include "builtin.h"
 
 static const char show_branch_usage[] =
-"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
+"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
 
 static int default_num = 0;
 static int default_alloc = 0;
@@ -89,6 +89,8 @@ static int name_first_parent_chain(struct commit *c)
                        name_parent(c, p);
                        i++;
                }
+               else
+                       break;
                c = p;
        }
        return i;
@@ -172,7 +174,7 @@ static void name_commits(struct commit_list *list,
 static int mark_seen(struct commit *commit, struct commit_list **seen_p)
 {
        if (!commit->object.flags) {
-               insert_by_date(commit, seen_p);
+               commit_list_insert(commit, seen_p);
                return 1;
        }
        return 0;
@@ -218,9 +220,8 @@ static void join_revs(struct commit_list **list_p,
         * Postprocess to complete well-poisoning.
         *
         * At this point we have all the commits we have seen in
-        * seen_p list (which happens to be sorted chronologically but
-        * it does not really matter).  Mark anything that can be
-        * reached from uninteresting commits not interesting.
+        * seen_p list.  Mark anything that can be reached from
+        * uninteresting commits not interesting.
         */
        for (;;) {
                int changed = 0;
@@ -701,6 +702,8 @@ int cmd_show_branch(int ac, const char **av, char **envp)
        if (0 <= extra)
                join_revs(&list, &seen, num_rev, extra);
 
+       sort_by_date(&seen);
+
        if (merge_base)
                return show_merge_base(seen, num_rev);
 
index f2e48aae2a04822d26d255dddf36002f58f2093c..e5aaded820bddfe2d5175508cc9c422b55a66eec 100644 (file)
@@ -20,6 +20,7 @@ static char block[BLOCKSIZE];
 static unsigned long offset;
 
 static time_t archive_time;
+static int tar_umask;
 
 /* tries hard to write, either succeeds or dies in the attempt */
 static void reliable_write(const void *data, unsigned long size)
@@ -188,13 +189,13 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path,
        } else {
                if (S_ISDIR(mode)) {
                        *header.typeflag = TYPEFLAG_DIR;
-                       mode |= 0777;
+                       mode = (mode | 0777) & ~tar_umask;
                } else if (S_ISLNK(mode)) {
                        *header.typeflag = TYPEFLAG_LNK;
                        mode |= 0777;
                } else if (S_ISREG(mode)) {
                        *header.typeflag = TYPEFLAG_REG;
-                       mode |= (mode & 0100) ? 0777 : 0666;
+                       mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
                } else {
                        error("unsupported file mode: 0%o (SHA1: %s)",
                              mode, sha1_to_hex(sha1));
@@ -293,6 +294,20 @@ static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
        }
 }
 
+int git_tar_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "tar.umask")) {
+               if (!strcmp(value, "user")) {
+                       tar_umask = umask(0);
+                       umask(tar_umask);
+               } else {
+                       tar_umask = git_config_int(var, value);
+               }
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 static int generate_tar(int argc, const char **argv, char** envp)
 {
        unsigned char sha1[20], tree_sha1[20];
@@ -305,7 +320,7 @@ static int generate_tar(int argc, const char **argv, char** envp)
        current_path.len = current_path.eof = 0;
 
        setup_git_directory();
-       git_config(git_default_config);
+       git_config(git_tar_config);
 
        switch (argc) {
        case 3:
index ef50243452c2f27572c941a5db9365c785f1fbd5..1a4200d151dd8e876c6f84531422cae50b049aa3 100644 (file)
@@ -648,7 +648,7 @@ int cmd_update_index(int argc, const char **argv, char **envp)
  finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_lock_file(lock_file))
+                   close(newfd) || commit_lock_file(lock_file))
                        die("Unable to write new index file");
        }
 
index 00333c7e7ca0e706be425182ff92b98a16ac3883..83094abe0f20d36bf2d9e65539fb2c8b7a5644fc 100644 (file)
@@ -12,6 +12,7 @@ int cmd_update_ref(int argc, const char **argv, char **envp)
        unsigned char sha1[20], oldsha1[20];
        int i;
 
+       setup_ident();
        setup_git_directory();
        git_config(git_default_config);
 
index 70e9b6fcc6d93e9b869a0a1eac144cbea3ecba3f..449a4d1b575b591aa07a40532649f9ea06eb6890 100644 (file)
@@ -35,7 +35,8 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix)
                                      missing_ok, 0) < 0)
                        die("git-write-tree: error building trees");
                if (0 <= newfd) {
-                       if (!write_cache(newfd, active_cache, active_nr))
+                       if (!write_cache(newfd, active_cache, active_nr)
+                                       && !close(newfd))
                                commit_lock_file(lock_file);
                }
                /* Not being able to write is fine -- we are only interested
index f12d5e68f6778fbf96c85b81d250cf7f230edf1d..5339d8627f48a44e3fbf3c81a0790bc467ccd459 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -25,6 +25,8 @@ extern int cmd_diff(int argc, const char **argv, char **envp);
 extern int cmd_format_patch(int argc, const char **argv, char **envp);
 extern int cmd_count_objects(int argc, const char **argv, char **envp);
 
+extern int cmd_prune(int argc, const char **argv, char **envp);
+
 extern int cmd_push(int argc, const char **argv, char **envp);
 extern int cmd_grep(int argc, const char **argv, char **envp);
 extern int cmd_rm(int argc, const char **argv, char **envp);
@@ -49,6 +51,7 @@ extern int cmd_cat_file(int argc, const char **argv, char **envp);
 extern int cmd_rev_parse(int argc, const char **argv, char **envp);
 extern int cmd_update_index(int argc, const char **argv, char **envp);
 extern int cmd_update_ref(int argc, const char **argv, char **envp);
+extern int cmd_fmt_merge_msg(int argc, const char **argv, char **envp);
 
 extern int cmd_write_tree(int argc, const char **argv, char **envp);
 extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
diff --git a/cache.h b/cache.h
index 87199396a05fbbbb695379119535eb9eefae2514..eee5fc9f8d75af20a1d03d569c702980f6c04469 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -176,6 +176,7 @@ extern int commit_lock_file(struct lock_file *);
 extern void rollback_lock_file(struct lock_file *);
 
 /* Environment bits from configuration mechanism */
+extern int use_legacy_headers;
 extern int trust_executable_bit;
 extern int assume_unchanged;
 extern int prefer_symlink_refs;
@@ -183,6 +184,7 @@ extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
+extern int zlib_compression_level;
 
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
@@ -218,8 +220,6 @@ int safe_create_leading_directories(char *path);
 char *enter_repo(char *path, int strict);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
-extern int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size);
-extern int parse_sha1_header(char *hdr, char *type, unsigned long *sizep);
 extern int sha1_object_info(const unsigned char *, char *, unsigned long *);
 extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size);
 extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
@@ -321,13 +321,17 @@ struct ref {
        char name[FLEX_ARRAY]; /* more */
 };
 
+#define REF_NORMAL     (1u << 0)
+#define REF_HEADS      (1u << 1)
+#define REF_TAGS       (1u << 2)
+
 extern int git_connect(int fd[2], char *url, const char *prog);
 extern int finish_connect(pid_t pid);
 extern int path_match(const char *path, int nr, char **match);
 extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                      int nr_refspec, char **refspec, int all);
 extern int get_ack(int fd, unsigned char *result_sha1);
-extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, int ignore_funny);
+extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
 extern int server_supports(const char *feature);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1);
@@ -377,6 +381,7 @@ extern int receive_keep_pack(int fd[2], const char *me, int quiet, int);
 
 /* pager.c */
 extern void setup_pager(void);
+extern int pager_in_use;
 
 /* base85 */
 int decode_85(char *dst, char *line, int linelen);
index ea40bc29bed17fbd6e3326edaa6fb3c38b742de6..61152f34b7fc00e745a2f7524c4da5b5a1a780a2 100644 (file)
@@ -49,14 +49,7 @@ static int checkout_stage; /* default to checkout stage0 */
 static int to_tempfile;
 static char topath[4][MAXPATHLEN+1];
 
-static struct checkout state = {
-       .base_dir = "",
-       .base_dir_len = 0,
-       .force = 0,
-       .quiet = 0,
-       .not_new = 0,
-       .refresh_cache = 0,
-};
+static struct checkout state;
 
 static void write_tempfile_record (const char *name)
 {
@@ -177,6 +170,7 @@ int main(int argc, char **argv)
        int all = 0;
        int read_from_stdin = 0;
 
+       state.base_dir = "";
        prefix = setup_git_directory();
        git_config(git_default_config);
        prefix_length = prefix ? strlen(prefix) : 0;
@@ -311,7 +305,7 @@ int main(int argc, char **argv)
 
        if (0 <= newfd &&
            (write_cache(newfd, active_cache, active_nr) ||
-            commit_lock_file(&lock_file)))
+            close(newfd) || commit_lock_file(&lock_file)))
                die("Unable to write new index file");
        return 0;
 }
index 22542217ee6aeba5058d02d99b27e20ca894af7f..919112bba9271a8e3903b06946cf91036411f1a4 100644 (file)
@@ -320,7 +320,7 @@ static int give_context(struct sline *sline, unsigned long cnt, int num_parent)
        unsigned long i;
 
        /* Two groups of interesting lines may have a short gap of
-        * unintersting lines.  Connect such groups to give them a
+        * uninteresting lines.  Connect such groups to give them a
         * bit of context.
         *
         * We first start from what the interesting() function says,
@@ -639,8 +639,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        /* deleted file */
                        result_size = 0;
                        elem->mode = 0;
-                       result = xmalloc(1);
-                       result[0] = 0;
+                       result = xcalloc(1, 1);
                }
                if (0 <= fd)
                        close(fd);
@@ -702,7 +701,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                const char *abb;
 
                if (rev->loginfo)
-                       show_log(rev, rev->loginfo, "\n");
+                       show_log(rev, opt->msg_sep);
                dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path);
                printf("index ");
                for (i = 0; i < num_parent; i++) {
@@ -770,9 +769,9 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
                inter_name_termination = 0;
 
        if (rev->loginfo)
-               show_log(rev, rev->loginfo, "\n");
+               show_log(rev, opt->msg_sep);
 
-       if (opt->output_format == DIFF_FORMAT_RAW) {
+       if (opt->output_format & DIFF_FORMAT_RAW) {
                offset = strlen(COLONS) - num_parent;
                if (offset < 0)
                        offset = 0;
@@ -792,8 +791,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
                printf(" %s ", diff_unique_abbrev(p->sha1, opt->abbrev));
        }
 
-       if (opt->output_format == DIFF_FORMAT_RAW ||
-           opt->output_format == DIFF_FORMAT_NAME_STATUS) {
+       if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) {
                for (i = 0; i < num_parent; i++)
                        putchar(p->parent[i].status);
                putchar(inter_name_termination);
@@ -819,17 +817,12 @@ void show_combined_diff(struct combine_diff_path *p,
        struct diff_options *opt = &rev->diffopt;
        if (!p->len)
                return;
-       switch (opt->output_format) {
-       case DIFF_FORMAT_RAW:
-       case DIFF_FORMAT_NAME_STATUS:
-       case DIFF_FORMAT_NAME:
+       if (opt->output_format & (DIFF_FORMAT_RAW |
+                                 DIFF_FORMAT_NAME |
+                                 DIFF_FORMAT_NAME_STATUS)) {
                show_raw_diff(p, num_parent, rev);
-               return;
-       case DIFF_FORMAT_PATCH:
+       } else if (opt->output_format & DIFF_FORMAT_PATCH) {
                show_patch_diff(p, num_parent, dense, rev);
-               return;
-       default:
-               return;
        }
 }
 
@@ -842,22 +835,20 @@ void diff_tree_combined(const unsigned char *sha1,
        struct diff_options *opt = &rev->diffopt;
        struct diff_options diffopts;
        struct combine_diff_path *p, *paths = NULL;
-       int i, num_paths;
-       int do_diffstat;
+       int i, num_paths, needsep, show_log_first;
 
-       do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
-                      opt->with_stat);
        diffopts = *opt;
-       diffopts.with_raw = 0;
-       diffopts.with_stat = 0;
+       diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diffopts.recursive = 1;
 
+       show_log_first = !!rev->loginfo;
+       needsep = 0;
        /* find set of paths that everybody touches */
        for (i = 0; i < num_parent; i++) {
                /* show stat against the first parent even
                 * when doing combined diff.
                 */
-               if (i == 0 && do_diffstat)
+               if (i == 0 && opt->output_format & DIFF_FORMAT_DIFFSTAT)
                        diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
                else
                        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
@@ -865,12 +856,12 @@ void diff_tree_combined(const unsigned char *sha1,
                diffcore_std(&diffopts);
                paths = intersect_paths(paths, i, num_parent);
 
-               if (do_diffstat && rev->loginfo)
-                       show_log(rev, rev->loginfo,
-                                opt->with_stat ? "---\n" : "\n");
+               if (show_log_first && i == 0) {
+                       show_log(rev, opt->msg_sep);
+                       if (rev->verbose_header && opt->output_format)
+                               putchar(opt->line_termination);
+               }
                diff_flush(&diffopts);
-               if (opt->with_stat)
-                       putchar('\n');
        }
 
        /* find out surviving paths */
@@ -879,17 +870,25 @@ void diff_tree_combined(const unsigned char *sha1,
                        num_paths++;
        }
        if (num_paths) {
-               if (opt->with_raw) {
-                       int saved_format = opt->output_format;
-                       opt->output_format = DIFF_FORMAT_RAW;
+               if (opt->output_format & (DIFF_FORMAT_RAW |
+                                         DIFF_FORMAT_NAME |
+                                         DIFF_FORMAT_NAME_STATUS)) {
                        for (p = paths; p; p = p->next) {
-                               show_combined_diff(p, num_parent, dense, rev);
+                               if (p->len)
+                                       show_raw_diff(p, num_parent, rev);
                        }
-                       opt->output_format = saved_format;
-                       putchar(opt->line_termination);
+                       needsep = 1;
                }
-               for (p = paths; p; p = p->next) {
-                       show_combined_diff(p, num_parent, dense, rev);
+               else if (opt->output_format & DIFF_FORMAT_DIFFSTAT)
+                       needsep = 1;
+               if (opt->output_format & DIFF_FORMAT_PATCH) {
+                       if (needsep)
+                               putchar(opt->line_termination);
+                       for (p = paths; p; p = p->next) {
+                               if (p->len)
+                                       show_patch_diff(p, num_parent, dense,
+                                                       rev);
+                       }
                }
        }
 
index e51ffa1c6cf5948c0e6c6d0b905fc868f4464ccf..77f0ca175c66b0ef0b7ac5b24672b106c6f17178 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -56,7 +56,7 @@ static struct commit *check_commit(struct object *obj,
                                   const unsigned char *sha1,
                                   int quiet)
 {
-       if (obj->type != TYPE_COMMIT) {
+       if (obj->type != OBJ_COMMIT) {
                if (!quiet)
                        error("Object %s is a %s, not a commit",
                              sha1_to_hex(sha1), typename(obj->type));
@@ -86,11 +86,11 @@ struct commit *lookup_commit(const unsigned char *sha1)
        if (!obj) {
                struct commit *ret = alloc_commit_node();
                created_object(sha1, &ret->object);
-               ret->object.type = TYPE_COMMIT;
+               ret->object.type = OBJ_COMMIT;
                return ret;
        }
        if (!obj->type)
-               obj->type = TYPE_COMMIT;
+               obj->type = OBJ_COMMIT;
        return check_commit(obj, sha1, 0);
 }
 
@@ -397,12 +397,13 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
 {
        struct commit_list *parents;
 
-       parents = commit->parents;
        commit->object.flags &= ~mark;
+       parents = commit->parents;
        while (parents) {
                struct commit *parent = parents->item;
-               if (parent && parent->object.parsed &&
-                   (parent->object.flags & mark))
+
+               /* Have we already cleared this? */
+               if (mark & parent->object.flags)
                        clear_commit_marks(parent, mark);
                parents = parents->next;
        }
@@ -654,6 +655,9 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                        continue;
                }
 
+               if (!subject)
+                       body = 1;
+
                if (is_empty_line(line, &linelen)) {
                        if (!body)
                                continue;
@@ -661,8 +665,6 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                                continue;
                        if (fmt == CMIT_FMT_SHORT)
                                break;
-               } else {
-                       body = 1;
                }
 
                if (subject) {
@@ -701,6 +703,12 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
        /* Make sure there is an EOLN for the non-oneline case */
        if (fmt != CMIT_FMT_ONELINE)
                buf[offset++] = '\n';
+       /*
+        * make sure there is another EOLN to separate the headers from whatever
+        * body the caller appends if we haven't already written a body
+        */
+       if (fmt == CMIT_FMT_EMAIL && !body)
+               buf[offset++] = '\n';
        buf[offset] = '\0';
        return offset;
 }
@@ -846,3 +854,147 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
        }
        free(nodes);
 }
+
+/* merge-rebase stuff */
+
+/* bits #0..7 in revision.h */
+#define PARENT1                (1u<< 8)
+#define PARENT2                (1u<< 9)
+#define STALE          (1u<<10)
+#define RESULT         (1u<<11)
+
+static struct commit *interesting(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & STALE)
+                       continue;
+               return commit;
+       }
+       return NULL;
+}
+
+static struct commit_list *merge_bases(struct commit *one, struct commit *two)
+{
+       struct commit_list *list = NULL;
+       struct commit_list *result = NULL;
+
+       if (one == two)
+               /* We do not mark this even with RESULT so we do not
+                * have to clean it up.
+                */
+               return commit_list_insert(one, &result);
+
+       parse_commit(one);
+       parse_commit(two);
+
+       one->object.flags |= PARENT1;
+       two->object.flags |= PARENT2;
+       insert_by_date(one, &list);
+       insert_by_date(two, &list);
+
+       while (interesting(list)) {
+               struct commit *commit;
+               struct commit_list *parents;
+               struct commit_list *n;
+               int flags;
+
+               commit = list->item;
+               n = list->next;
+               free(list);
+               list = n;
+
+               flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
+               if (flags == (PARENT1 | PARENT2)) {
+                       if (!(commit->object.flags & RESULT)) {
+                               commit->object.flags |= RESULT;
+                               insert_by_date(commit, &result);
+                       }
+                       /* Mark parents of a found merge stale */
+                       flags |= STALE;
+               }
+               parents = commit->parents;
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parents = parents->next;
+                       if ((p->object.flags & flags) == flags)
+                               continue;
+                       parse_commit(p);
+                       p->object.flags |= flags;
+                       insert_by_date(p, &list);
+               }
+       }
+
+       /* Clean up the result to remove stale ones */
+       list = result; result = NULL;
+       while (list) {
+               struct commit_list *n = list->next;
+               if (!(list->item->object.flags & STALE))
+                       insert_by_date(list->item, &result);
+               free(list);
+               list = n;
+       }
+       return result;
+}
+
+struct commit_list *get_merge_bases(struct commit *one,
+                                   struct commit *two,
+                                    int cleanup)
+{
+       const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
+       struct commit_list *list;
+       struct commit **rslt;
+       struct commit_list *result;
+       int cnt, i, j;
+
+       result = merge_bases(one, two);
+       if (one == two)
+               return result;
+       if (!result || !result->next) {
+               if (cleanup) {
+                       clear_commit_marks(one, all_flags);
+                       clear_commit_marks(two, all_flags);
+               }
+               return result;
+       }
+
+       /* There are more than one */
+       cnt = 0;
+       list = result;
+       while (list) {
+               list = list->next;
+               cnt++;
+       }
+       rslt = xcalloc(cnt, sizeof(*rslt));
+       for (list = result, i = 0; list; list = list->next)
+               rslt[i++] = list->item;
+       free_commit_list(result);
+
+       clear_commit_marks(one, all_flags);
+       clear_commit_marks(two, all_flags);
+       for (i = 0; i < cnt - 1; i++) {
+               for (j = i+1; j < cnt; j++) {
+                       if (!rslt[i] || !rslt[j])
+                               continue;
+                       result = merge_bases(rslt[i], rslt[j]);
+                       clear_commit_marks(rslt[i], all_flags);
+                       clear_commit_marks(rslt[j], all_flags);
+                       for (list = result; list; list = list->next) {
+                               if (rslt[i] == list->item)
+                                       rslt[i] = NULL;
+                               if (rslt[j] == list->item)
+                                       rslt[j] = NULL;
+                       }
+               }
+       }
+
+       /* Surviving ones in rslt[] are the independent results */
+       result = NULL;
+       for (i = 0; i < cnt; i++) {
+               if (rslt[i])
+                       insert_by_date(rslt[i], &result);
+       }
+       free(rslt);
+       return result;
+}
index 7c9ca3fbed728d946e48c787720b3ab5ad575a85..779ed82ed0ce2fb1a643b1cfa0ffe0fd3107ed09 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -105,4 +105,6 @@ struct commit_graft *read_graft_line(char *buf, int len);
 int register_commit_graft(struct commit_graft *, int);
 int read_graft_file(const char *graft_file);
 
+extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
+
 #endif /* COMMIT_H */
index bbd26c7b0e80798f1bf4e92b6150bd30c44f3143..6474eab1192fd2f66c810dc49a35b8b99db8ff1f 100644 (file)
@@ -568,7 +568,7 @@ class Popen(object):
         # Windows methods
         #
         def _get_handles(self, stdin, stdout, stderr):
-            """Construct and return tupel with IO objects:
+            """Construct and return tuple with IO objects:
             p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
             """
             if stdin == None and stdout == None and stderr == None:
@@ -635,7 +635,7 @@ class Popen(object):
 
 
         def _find_w9xpopen(self):
-            """Find and return absolut path to w9xpopen.exe"""
+            """Find and return absolute path to w9xpopen.exe"""
             w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)),
                                     "w9xpopen.exe")
             if not os.path.exists(w9xpopen):
@@ -812,7 +812,7 @@ class Popen(object):
         # POSIX methods
         #
         def _get_handles(self, stdin, stdout, stderr):
-            """Construct and return tupel with IO objects:
+            """Construct and return tuple with IO objects:
             p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
             """
             p2cread, p2cwrite = None, None
index ec44827da4afbcf89a5eed37c7f557a7ffdce35d..0ac6aebbbcbd666d5bd8201ce28e1868bd6d5a30 100644 (file)
--- a/config.c
+++ b/config.c
@@ -244,9 +244,9 @@ int git_config_bool(const char *name, const char *value)
                return 1;
        if (!*value)
                return 0;
-       if (!strcasecmp(value, "true"))
+       if (!strcasecmp(value, "true") || !strcasecmp(value, "yes"))
                return 1;
-       if (!strcasecmp(value, "false"))
+       if (!strcasecmp(value, "false") || !strcasecmp(value, "no"))
                return 0;
        return git_config_int(name, value) != 0;
 }
@@ -279,6 +279,21 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.legacyheaders")) {
+               use_legacy_headers = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "core.compression")) {
+               int level = git_config_int(var, value);
+               if (level == -1)
+                       level = Z_DEFAULT_COMPRESSION;
+               else if (level < 0 || level > Z_BEST_COMPRESSION)
+                       die("bad zlib compression level %d", level);
+               zlib_compression_level = level;
+               return 0;
+       }
+
        if (!strcmp(var, "user.name")) {
                strlcpy(git_default_name, value, sizeof(git_default_name));
                return 0;
index cb4656d79d3bd2d22f719724f4c393b6ac70f633..4422a0d8d38c225c6c4716ce8ff826bc1acbd981 100644 (file)
--- a/connect.c
+++ b/connect.c
 
 static char *server_capabilities = NULL;
 
+static int check_ref(const char *name, int len, unsigned int flags)
+{
+       if (!flags)
+               return 1;
+
+       if (len > 45 || memcmp(name, "refs/", 5))
+               return 0;
+
+       /* Skip the "refs/" part */
+       name += 5;
+       len -= 5;
+
+       /* REF_NORMAL means that we don't want the magic fake tag refs */
+       if ((flags & REF_NORMAL) && check_ref_format(name) < 0)
+               return 0;
+
+       /* REF_HEADS means that we want regular branch heads */
+       if ((flags & REF_HEADS) && !memcmp(name, "heads/", 6))
+               return 1;
+
+       /* REF_TAGS means that we want tags */
+       if ((flags & REF_TAGS) && !memcmp(name, "tags/", 5))
+               return 1;
+
+       /* All type bits clear means that we are ok with anything */
+       return !(flags & ~REF_NORMAL);
+}
+
 /*
  * Read all the refs from the other end
  */
 struct ref **get_remote_heads(int in, struct ref **list,
-                             int nr_match, char **match, int ignore_funny)
+                             int nr_match, char **match,
+                             unsigned int flags)
 {
        *list = NULL;
        for (;;) {
@@ -43,10 +72,8 @@ struct ref **get_remote_heads(int in, struct ref **list,
                        server_capabilities = strdup(name + name_len + 1);
                }
 
-               if (ignore_funny && 45 < len && !memcmp(name, "refs/", 5) &&
-                   check_ref_format(name + 5))
+               if (!check_ref(name, name_len, flags))
                        continue;
-
                if (nr_match && !path_match(name, nr_match, match))
                        continue;
                ref = xcalloc(1, sizeof(*ref) + len - 40);
@@ -328,7 +355,7 @@ static enum protocol get_protocol(const char *name)
  */
 static int git_tcp_connect_sock(char *host)
 {
-       int sockfd = -1;
+       int sockfd = -1, saved_errno = 0;
        char *colon, *end;
        const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
@@ -362,9 +389,12 @@ static int git_tcp_connect_sock(char *host)
        for (ai0 = ai; ai; ai = ai->ai_next) {
                sockfd = socket(ai->ai_family,
                                ai->ai_socktype, ai->ai_protocol);
-               if (sockfd < 0)
+               if (sockfd < 0) {
+                       saved_errno = errno;
                        continue;
+               }
                if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       saved_errno = errno;
                        close(sockfd);
                        sockfd = -1;
                        continue;
@@ -375,7 +405,7 @@ static int git_tcp_connect_sock(char *host)
        freeaddrinfo(ai0);
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(errno));
+               die("unable to connect a socket (%s)", strerror(saved_errno));
 
        return sockfd;
 }
@@ -387,7 +417,7 @@ static int git_tcp_connect_sock(char *host)
  */
 static int git_tcp_connect_sock(char *host)
 {
-       int sockfd = -1;
+       int sockfd = -1, saved_errno = 0;
        char *colon, *end;
        char *port = STR(DEFAULT_GIT_PORT), *ep;
        struct hostent *he;
@@ -426,8 +456,10 @@ static int git_tcp_connect_sock(char *host)
 
        for (ap = he->h_addr_list; *ap; ap++) {
                sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
-               if (sockfd < 0)
+               if (sockfd < 0) {
+                       saved_errno = errno;
                        continue;
+               }
 
                memset(&sa, 0, sizeof sa);
                sa.sin_family = he->h_addrtype;
@@ -435,6 +467,7 @@ static int git_tcp_connect_sock(char *host)
                memcpy(&sa.sin_addr, *ap, he->h_length);
 
                if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+                       saved_errno = errno;
                        close(sockfd);
                        sockfd = -1;
                        continue;
@@ -443,7 +476,7 @@ static int git_tcp_connect_sock(char *host)
        }
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(errno));
+               die("unable to connect a socket (%s)", strerror(saved_errno));
 
        return sockfd;
 }
index 5789cfb2654607052f38ce788777520eaccc7bd4..9566a765ef5fc66c80cba02a8868b3e629193b15 100755 (executable)
@@ -110,7 +110,7 @@ foreach $config_file (@config_files) {
     }
 }
 
-# colordiff specfic options here.  Need to pre-declare if using variables
+# colordiff specific options here.  Need to pre-declare if using variables
 GetOptions(
     "no-banner" => sub { $show_banner = 0 },
     "plain-text=s" => \&set_color,
index ebd00ef9c471f284d26fecd44e3fd7668903b50a..68de9be0c7cca8645275a042900766b6ea2712c7 100644 (file)
 ;;;; ------------------------------------------------------------
 
 (defgroup git nil
-  "Git user interface")
+  "A user interface for the git versioning system."
+  :group 'tools)
 
 (defcustom git-committer-name nil
   "User name to use for commits.
-The default is to fall back to the repository config, then to `add-log-full-name' and then to `user-full-name'."
+The default is to fall back to the repository config,
+then to `add-log-full-name' and then to `user-full-name'."
   :group 'git
   :type '(choice (const :tag "Default" nil)
                  (string :tag "Name")))
 
 (defcustom git-committer-email nil
   "Email address to use for commits.
-The default is to fall back to the git repository config, then to `add-log-mailing-address' and then to `user-mail-address'."
+The default is to fall back to the git repository config,
+then to `add-log-mailing-address' and then to `user-mail-address'."
   :group 'git
   :type '(choice (const :tag "Default" nil)
                  (string :tag "Email")))
@@ -81,11 +84,18 @@ The default is to fall back to the git repository config, then to `add-log-maili
   :group 'git
   :type 'boolean)
 
+(defcustom git-reuse-status-buffer t
+  "Whether `git-status' should try to reuse an existing buffer
+if there is already one that displays the same directory."
+  :group 'git
+  :type 'boolean)
+
 (defcustom git-per-dir-ignore-file ".gitignore"
   "Name of the per-directory ignore file."
   :group 'git
   :type 'string)
 
+
 (defface git-status-face
   '((((class color) (background light)) (:foreground "purple")))
   "Git mode face used to highlight added and modified files."
@@ -149,7 +159,8 @@ The default is to fall back to the git repository config, then to `add-log-maili
     (apply #'call-process "git" nil buffer nil args)))
 
 (defun git-call-process-env-string (env &rest args)
-  "Wrapper for call-process that sets environment strings, and returns the process output as a string."
+  "Wrapper for call-process that sets environment strings,
+and returns the process output as a string."
   (with-temp-buffer
     (and (eq 0 (apply #' git-call-process-env t env args))
          (buffer-string))))
@@ -254,7 +265,7 @@ The default is to fall back to the git repository config, then to `add-log-maili
     (set-buffer (find-file-noselect ignore-name))
     (goto-char (point-max))
     (unless (zerop (current-column)) (insert "\n"))
-    (insert name "\n")
+    (insert "/" name "\n")
     (sort-lines nil (point-min) (point-max))
     (save-buffer))
   (when created
@@ -580,6 +591,8 @@ The default is to fall back to the git repository config, then to `add-log-maili
                             (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
                             (with-current-buffer buffer (erase-buffer))
                             (git-set-files-state files 'uptodate)
+                            (when (file-directory-p ".git/rr-cache")
+                              (git-run-command nil nil "rerere"))
                             (git-refresh-files)
                             (git-refresh-ewoc-hf git-status)
                             (message "Committed %s." commit))
@@ -939,6 +952,8 @@ The default is to fall back to the git repository config, then to `add-log-maili
   (let ((map (make-keymap))
         (diff-map (make-sparse-keymap)))
     (suppress-keymap map)
+    (define-key map "?"   'git-help)
+    (define-key map "h"   'git-help)
     (define-key map " "   'git-next-file)
     (define-key map "a"   'git-add-file)
     (define-key map "c"   'git-commit-file)
@@ -995,12 +1010,28 @@ Commands:
   (set (make-local-variable 'list-buffers-directory) default-directory)
   (run-hooks 'git-status-mode-hook)))
 
+(defun git-find-status-buffer (dir)
+  "Find the git status buffer handling a specified directory."
+  (let ((list (buffer-list))
+        (fulldir (expand-file-name dir))
+        found)
+    (while (and list (not found))
+      (let ((buffer (car list)))
+        (with-current-buffer buffer
+          (when (and list-buffers-directory
+                     (string-equal fulldir (expand-file-name list-buffers-directory))
+                     (string-match "\\*git-status\\*$" (buffer-name buffer)))
+            (setq found buffer))))
+      (setq list (cdr list)))
+    found))
+
 (defun git-status (dir)
   "Entry point into git-status mode."
   (interactive "DSelect directory: ")
   (setq dir (git-get-top-dir dir))
   (if (file-directory-p (concat (file-name-as-directory dir) ".git"))
-      (let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir))))
+      (let ((buffer (or (and git-reuse-status-buffer (git-find-status-buffer dir))
+                        (create-file-buffer (expand-file-name "*git-status*" dir)))))
         (switch-to-buffer buffer)
         (cd dir)
         (git-status-mode)
@@ -1008,5 +1039,10 @@ Commands:
         (goto-char (point-min)))
     (message "%s is not a git working tree." dir)))
 
+(defun git-help ()
+  "Display help for Git mode."
+  (interactive)
+  (describe-function 'git-status-mode))
+
 (provide 'git)
 ;;; git.el ends here
index 2453cdcfae1f168df4533745e5730215be2af3c7..3f6ed699f0848cab6709e736e245b968e963978c 100644 (file)
   "Register FILE into the git version-control system."
   (vc-git--run-command file "update-index" "--add" "--"))
 
-(defun vc-git-print-log (file)
+(defun vc-git-print-log (file &optional buffer)
   (let ((name (file-relative-name file))
         (coding-system-for-read git-commits-coding-system))
-    (vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
+    (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
 
-(defun vc-git-diff (file &optional rev1 rev2)
-  (let ((name (file-relative-name file)))
+(defun vc-git-diff (file &optional rev1 rev2 buffer)
+  (let ((name (file-relative-name file))
+        (buf (or buffer "*vc-diff*")))
     (if (and rev1 rev2)
-        (vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
-      (vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
+        (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
+      (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
     ; git-diff-index doesn't set exit status like diff does
     (if (vc-git-workfile-unchanged-p file) 0 1)))
 
diff --git a/contrib/git-svn/.gitignore b/contrib/git-svn/.gitignore
deleted file mode 100644 (file)
index d8d87e3..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-git-svn
-git-svn.xml
-git-svn.html
-git-svn.1
diff --git a/contrib/git-svn/Makefile b/contrib/git-svn/Makefile
deleted file mode 100644 (file)
index 8cac688..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-all: git-svn
-
-prefix?=$(HOME)
-bindir?=$(prefix)/bin
-mandir?=$(prefix)/man
-man1dir=$(mandir)/man1
-INSTALL?=install
-doc_conf=../../Documentation/asciidoc.conf
--include ../../config.mak
-
-git-svn: git-svn.perl
-       cp $< $@
-       chmod +x $@
-
-install: all
-       $(INSTALL) -d -m755 $(DESTDIR)$(bindir)
-       $(INSTALL) git-svn $(DESTDIR)$(bindir)
-
-install-doc: doc
-       $(INSTALL) git-svn.1 $(DESTDIR)$(man1dir)
-
-doc: git-svn.1
-git-svn.1 : git-svn.xml
-       xmlto man git-svn.xml
-git-svn.xml : git-svn.txt
-       asciidoc -b docbook -d manpage \
-               -f ../../Documentation/asciidoc.conf $<
-git-svn.html : git-svn.txt
-       asciidoc -b xhtml11 -d manpage \
-               -f ../../Documentation/asciidoc.conf $<
-test: git-svn
-       cd t && for i in t????-*.sh; do $(SHELL) ./$$i $(TEST_FLAGS); done
-
-# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
-full-test:
-       $(MAKE) test GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
-       $(MAKE) test GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
-       $(MAKE) test GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
-                                                       LC_ALL=en_US.UTF-8
-       $(MAKE) test GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
-                                                       LC_ALL=en_US.UTF-8
-
-clean:
-       rm -f git-svn *.xml *.html *.1
diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl
deleted file mode 100755 (executable)
index b3d3f47..0000000
+++ /dev/null
@@ -1,3368 +0,0 @@
-#!/usr/bin/env perl
-# Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
-# License: GPL v2 or later
-use warnings;
-use strict;
-use vars qw/   $AUTHOR $VERSION
-               $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
-               $GIT_SVN_INDEX $GIT_SVN
-               $GIT_DIR $GIT_SVN_DIR $REVDB/;
-$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
-$VERSION = '1.1.1-broken';
-
-use Cwd qw/abs_path/;
-$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
-$ENV{GIT_DIR} = $GIT_DIR;
-
-my $LC_ALL = $ENV{LC_ALL};
-my $TZ = $ENV{TZ};
-# make sure the svn binary gives consistent output between locales and TZs:
-$ENV{TZ} = 'UTC';
-$ENV{LC_ALL} = 'C';
-$| = 1; # unbuffer STDOUT
-
-# If SVN:: library support is added, please make the dependencies
-# optional and preserve the capability to use the command-line client.
-# use eval { require SVN::... } to make it lazy load
-# We don't use any modules not in the standard Perl distribution:
-use Carp qw/croak/;
-use IO::File qw//;
-use File::Basename qw/dirname basename/;
-use File::Path qw/mkpath/;
-use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
-use File::Spec qw//;
-use POSIX qw/strftime/;
-use IPC::Open3;
-use Memoize;
-memoize('revisions_eq');
-memoize('cmt_metadata');
-memoize('get_commit_time');
-
-my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
-$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
-libsvn_load();
-my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
-my $sha1 = qr/[a-f\d]{40}/;
-my $sha1_short = qr/[a-f\d]{4,40}/;
-my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
-       $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
-       $_repack, $_repack_nr, $_repack_flags, $_q,
-       $_message, $_file, $_follow_parent, $_no_metadata,
-       $_template, $_shared, $_no_default_regex, $_no_graft_copy,
-       $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
-       $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);
-my (@_branch_from, %tree_map, %users, %rusers, %equiv);
-my ($_svn_co_url_revs, $_svn_pg_peg_revs);
-my @repo_path_split_cache;
-
-my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
-               'branch|b=s' => \@_branch_from,
-               'follow-parent|follow' => \$_follow_parent,
-               'branch-all-refs|B' => \$_branch_all_refs,
-               'authors-file|A=s' => \$_authors,
-               'repack:i' => \$_repack,
-               'no-metadata' => \$_no_metadata,
-               'quiet|q' => \$_q,
-               'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
-
-my ($_trunk, $_tags, $_branches);
-my %multi_opts = ( 'trunk|T=s' => \$_trunk,
-               'tags|t=s' => \$_tags,
-               'branches|b=s' => \$_branches );
-my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared );
-my %cmt_opts = ( 'edit|e' => \$_edit,
-               'rmdir' => \$_rmdir,
-               'find-copies-harder' => \$_find_copies_harder,
-               'l=i' => \$_l,
-               'copy-similarity|C=i'=> \$_cp_similarity
-);
-
-# yes, 'native' sets "\n".  Patches to fix this for non-*nix systems welcome:
-my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" );
-
-my %cmd = (
-       fetch => [ \&fetch, "Download new revisions from SVN",
-                       { 'revision|r=s' => \$_revision, %fc_opts } ],
-       init => [ \&init, "Initialize a repo for tracking" .
-                         " (requires URL argument)",
-                         \%init_opts ],
-       commit => [ \&commit, "Commit git revisions to SVN",
-                       {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
-       'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
-                       { 'revision|r=i' => \$_revision } ],
-       rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
-                       { 'no-ignore-externals' => \$_no_ignore_ext,
-                         'copy-remote|remote=s' => \$_cp_remote,
-                         'upgrade' => \$_upgrade } ],
-       'graft-branches' => [ \&graft_branches,
-                       'Detect merges/branches from already imported history',
-                       { 'merge-rx|m' => \@_opt_m,
-                         'branch|b=s' => \@_branch_from,
-                         'branch-all-refs|B' => \$_branch_all_refs,
-                         'no-default-regex' => \$_no_default_regex,
-                         'no-graft-copy' => \$_no_graft_copy } ],
-       'multi-init' => [ \&multi_init,
-                       'Initialize multiple trees (like git-svnimport)',
-                       { %multi_opts, %fc_opts } ],
-       'multi-fetch' => [ \&multi_fetch,
-                       'Fetch multiple trees (like git-svnimport)',
-                       \%fc_opts ],
-       'log' => [ \&show_log, 'Show commit logs',
-                       { 'limit=i' => \$_limit,
-                         'revision|r=s' => \$_revision,
-                         'verbose|v' => \$_verbose,
-                         'incremental' => \$_incremental,
-                         'oneline' => \$_oneline,
-                         'show-commit' => \$_show_commit,
-                         'authors-file|A=s' => \$_authors,
-                       } ],
-       'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
-                       { 'message|m=s' => \$_message,
-                         'file|F=s' => \$_file,
-                       %cmt_opts } ],
-);
-
-my $cmd;
-for (my $i = 0; $i < @ARGV; $i++) {
-       if (defined $cmd{$ARGV[$i]}) {
-               $cmd = $ARGV[$i];
-               splice @ARGV, $i, 1;
-               last;
-       }
-};
-
-my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
-
-read_repo_config(\%opts);
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
-                               'version|V' => \$_version,
-                               'id|i=s' => \$GIT_SVN);
-exit 1 if (!$rv && $cmd ne 'log');
-
-set_default_vals();
-usage(0) if $_help;
-version() if $_version;
-usage(1) unless defined $cmd;
-init_vars();
-load_authors() if $_authors;
-load_all_refs() if $_branch_all_refs;
-svn_compat_check() unless $_use_lib;
-migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init)$/;
-$cmd{$cmd}->[0]->(@ARGV);
-exit 0;
-
-####################### primary functions ######################
-sub usage {
-       my $exit = shift || 0;
-       my $fd = $exit ? \*STDERR : \*STDOUT;
-       print $fd <<"";
-git-svn - bidirectional operations between a single Subversion tree and git
-Usage: $0 <command> [options] [arguments]\n
-
-       print $fd "Available commands:\n" unless $cmd;
-
-       foreach (sort keys %cmd) {
-               next if $cmd && $cmd ne $_;
-               print $fd '  ',pack('A13',$_),$cmd{$_}->[1],"\n";
-               foreach (keys %{$cmd{$_}->[2]}) {
-                       # prints out arguments as they should be passed:
-                       my $x = s#[:=]s$## ? '<arg>' : s#[:=]i$## ? '<num>' : '';
-                       print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
-                                                       "--$_" : "-$_" }
-                                               split /\|/,$_)," $x\n";
-               }
-       }
-       print $fd <<"";
-\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an
-arbitrary identifier if you're tracking multiple SVN branches/repositories in
-one git repository and want to keep them separate.  See git-svn(1) for more
-information.
-
-       exit $exit;
-}
-
-sub version {
-       print "git-svn version $VERSION\n";
-       exit 0;
-}
-
-sub rebuild {
-       if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) {
-               copy_remote_ref();
-       }
-       $SVN_URL = shift or undef;
-       my $newest_rev = 0;
-       if ($_upgrade) {
-               sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
-       } else {
-               check_upgrade_needed();
-       }
-
-       my $pid = open(my $rev_list,'-|');
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
-       }
-       my $latest;
-       while (<$rev_list>) {
-               chomp;
-               my $c = $_;
-               croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
-               my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
-               next if (!@commit); # skip merges
-               my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
-               if (!$rev || !$uuid) {
-                       croak "Unable to extract revision or UUID from ",
-                               "$c, $commit[$#commit]\n";
-               }
-
-               # if we merged or otherwise started elsewhere, this is
-               # how we break out of it
-               next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
-               next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));
-
-               unless (defined $latest) {
-                       if (!$SVN_URL && !$url) {
-                               croak "SVN repository location required: $url\n";
-                       }
-                       $SVN_URL ||= $url;
-                       $SVN_UUID ||= $uuid;
-                       setup_git_svn();
-                       $latest = $rev;
-               }
-               revdb_set($REVDB, $rev, $c);
-               print "r$rev = $c\n";
-               $newest_rev = $rev if ($rev > $newest_rev);
-       }
-       close $rev_list or croak $?;
-
-       goto out if $_use_lib;
-       if (!chdir $SVN_WC) {
-               svn_cmd_checkout($SVN_URL, $latest, $SVN_WC);
-               chdir $SVN_WC or croak $!;
-       }
-
-       $pid = fork;
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               my @svn_up = qw(svn up);
-               push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-               sys(@svn_up,"-r$newest_rev");
-               $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;
-               index_changes();
-               exec('git-write-tree') or croak $!;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-out:
-       if ($_upgrade) {
-               print STDERR <<"";
-Keeping deprecated refs/head/$GIT_SVN-HEAD for now.  Please remove it
-when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
-
-       }
-}
-
-sub init {
-       $SVN_URL = shift or die "SVN repository location required " .
-                               "as a command-line argument\n";
-       $SVN_URL =~ s!/+$!!; # strip trailing slash
-       unless (-d $GIT_DIR) {
-               my @init_db = ('git-init-db');
-               push @init_db, "--template=$_template" if defined $_template;
-               push @init_db, "--shared" if defined $_shared;
-               sys(@init_db);
-       }
-       setup_git_svn();
-}
-
-sub fetch {
-       check_upgrade_needed();
-       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       my $ret = $_use_lib ? fetch_lib(@_) : fetch_cmd(@_);
-       if ($ret->{commit} && quiet_run(qw(git-rev-parse --verify
-                                               refs/heads/master^0))) {
-               sys(qw(git-update-ref refs/heads/master),$ret->{commit});
-       }
-       return $ret;
-}
-
-sub fetch_cmd {
-       my (@parents) = @_;
-       my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);
-       unless ($_revision) {
-               $_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD';
-       }
-       push @log_args, "-r$_revision";
-       push @log_args, '--stop-on-copy' unless $_no_stop_copy;
-
-       my $svn_log = svn_log_raw(@log_args);
-
-       my $base = next_log_entry($svn_log) or croak "No base revision!\n";
-       # don't need last_revision from grab_base_rev() because
-       # user could've specified a different revision to skip (they
-       # didn't want to import certain revisions into git for whatever
-       # reason, so trust $base->{revision} instead.
-       my (undef, $last_commit) = svn_grab_base_rev();
-       unless (-d $SVN_WC) {
-               svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC);
-               chdir $SVN_WC or croak $!;
-               read_uuid();
-               $last_commit = git_commit($base, @parents);
-               assert_tree($last_commit);
-       } else {
-               chdir $SVN_WC or croak $!;
-               read_uuid();
-               # looks like a user manually cp'd and svn switch'ed
-               unless ($last_commit) {
-                       sys(qw/svn revert -R ./);
-                       assert_svn_wc_clean($base->{revision});
-                       $last_commit = git_commit($base, @parents);
-                       assert_tree($last_commit);
-               }
-       }
-       my @svn_up = qw(svn up);
-       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-       my $last = $base;
-       while (my $log_msg = next_log_entry($svn_log)) {
-               if ($last->{revision} >= $log_msg->{revision}) {
-                       croak "Out of order: last >= current: ",
-                               "$last->{revision} >= $log_msg->{revision}\n";
-               }
-               # Revert is needed for cases like:
-               # https://svn.musicpd.org/Jamming/trunk (r166:167), but
-               # I can't seem to reproduce something like that on a test...
-               sys(qw/svn revert -R ./);
-               assert_svn_wc_clean($last->{revision});
-               sys(@svn_up,"-r$log_msg->{revision}");
-               $last_commit = git_commit($log_msg, $last_commit, @parents);
-               $last = $log_msg;
-       }
-       close $svn_log->{fh};
-       $last->{commit} = $last_commit;
-       return $last;
-}
-
-sub fetch_lib {
-       my (@parents) = @_;
-       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       my $repo;
-       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
-       $SVN_LOG ||= libsvn_connect($repo);
-       $SVN ||= libsvn_connect($repo);
-       my ($last_rev, $last_commit) = svn_grab_base_rev();
-       my ($base, $head) = libsvn_parse_revision($last_rev);
-       if ($base > $head) {
-               return { revision => $last_rev, commit => $last_commit }
-       }
-       my $index = set_index($GIT_SVN_INDEX);
-
-       # limit ourselves and also fork() since get_log won't release memory
-       # after processing a revision and SVN stuff seems to leak
-       my $inc = 1000;
-       my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
-       read_uuid();
-       if (defined $last_commit) {
-               unless (-e $GIT_SVN_INDEX) {
-                       sys(qw/git-read-tree/, $last_commit);
-               }
-               chomp (my $x = `git-write-tree`);
-               my ($y) = (`git-cat-file commit $last_commit`
-                                                       =~ /^tree ($sha1)/m);
-               if ($y ne $x) {
-                       unlink $GIT_SVN_INDEX or croak $!;
-                       sys(qw/git-read-tree/, $last_commit);
-               }
-               chomp ($x = `git-write-tree`);
-               if ($y ne $x) {
-                       print STDERR "trees ($last_commit) $y != $x\n",
-                                "Something is seriously wrong...\n";
-               }
-       }
-       while (1) {
-               # fork, because using SVN::Pool with get_log() still doesn't
-               # seem to help enough to keep memory usage down.
-               defined(my $pid = fork) or croak $!;
-               if (!$pid) {
-                       $SVN::Error::handler = \&libsvn_skip_unknown_revs;
-
-                       # Yes I'm perfectly aware that the fourth argument
-                       # below is the limit revisions number.  Unfortunately
-                       # performance sucks with it enabled, so it's much
-                       # faster to fetch revision ranges instead of relying
-                       # on the limiter.
-                       libsvn_get_log($SVN_LOG, '/'.$SVN_PATH,
-                                       $min, $max, 0, 1, 1,
-                               sub {
-                                       my $log_msg;
-                                       if ($last_commit) {
-                                               $log_msg = libsvn_fetch(
-                                                       $last_commit, @_);
-                                               $last_commit = git_commit(
-                                                       $log_msg,
-                                                       $last_commit,
-                                                       @parents);
-                                       } else {
-                                               $log_msg = libsvn_new_tree(@_);
-                                               $last_commit = git_commit(
-                                                       $log_msg, @parents);
-                                       }
-                               });
-                       exit 0;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
-               ($last_rev, $last_commit) = svn_grab_base_rev();
-               last if ($max >= $head);
-               $min = $max + 1;
-               $max += $inc;
-               $max = $head if ($max > $head);
-       }
-       restore_index($index);
-       return { revision => $last_rev, commit => $last_commit };
-}
-
-sub commit {
-       my (@commits) = @_;
-       check_upgrade_needed();
-       if ($_stdin || !@commits) {
-               print "Reading from stdin...\n";
-               @commits = ();
-               while (<STDIN>) {
-                       if (/\b($sha1_short)\b/o) {
-                               unshift @commits, $1;
-                       }
-               }
-       }
-       my @revs;
-       foreach my $c (@commits) {
-               chomp(my @tmp = safe_qx('git-rev-parse',$c));
-               if (scalar @tmp == 1) {
-                       push @revs, $tmp[0];
-               } elsif (scalar @tmp > 1) {
-                       push @revs, reverse (safe_qx('git-rev-list',@tmp));
-               } else {
-                       die "Failed to rev-parse $c\n";
-               }
-       }
-       chomp @revs;
-       $_use_lib ? commit_lib(@revs) : commit_cmd(@revs);
-       print "Done committing ",scalar @revs," revisions to SVN\n";
-}
-
-sub commit_cmd {
-       my (@revs) = @_;
-
-       chdir $SVN_WC or croak "Unable to chdir $SVN_WC: $!\n";
-       my $info = svn_info('.');
-       my $fetched = fetch();
-       if ($info->{Revision} != $fetched->{revision}) {
-               print STDERR "There are new revisions that were fetched ",
-                               "and need to be merged (or acknowledged) ",
-                               "before committing.\n";
-               exit 1;
-       }
-       $info = svn_info('.');
-       read_uuid($info);
-       my $last = $fetched;
-       foreach my $c (@revs) {
-               my $mods = svn_checkout_tree($last, $c);
-               if (scalar @$mods == 0) {
-                       print "Skipping, no changes detected\n";
-                       next;
-               }
-               $last = svn_commit_tree($last, $c);
-       }
-}
-
-sub commit_lib {
-       my (@revs) = @_;
-       my ($r_last, $cmt_last) = svn_grab_base_rev();
-       defined $r_last or die "Must have an existing revision to commit\n";
-       my $fetched = fetch();
-       if ($r_last != $fetched->{revision}) {
-               print STDERR "There are new revisions that were fetched ",
-                               "and need to be merged (or acknowledged) ",
-                               "before committing.\n",
-                               "last rev: $r_last\n",
-                               " current: $fetched->{revision}\n";
-               exit 1;
-       }
-       read_uuid();
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
-       my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
-
-       set_svn_commit_env();
-       foreach my $c (@revs) {
-               my $log_msg = get_commit_message($c, $commit_msg);
-
-               # fork for each commit because there's a memory leak I
-               # can't track down... (it's probably in the SVN code)
-               defined(my $pid = open my $fh, '-|') or croak $!;
-               if (!$pid) {
-                       my $ed = SVN::Git::Editor->new(
-                                       {       r => $r_last,
-                                               ra => $SVN,
-                                               c => $c,
-                                               svn_path => $SVN_PATH
-                                       },
-                                       $SVN->get_commit_editor(
-                                               $log_msg->{msg},
-                                               sub {
-                                                       libsvn_commit_cb(
-                                                               @_, $c,
-                                                               $log_msg->{msg},
-                                                               $r_last,
-                                                               $cmt_last)
-                                               },
-                                               @lock)
-                                       );
-                       my $mods = libsvn_checkout_tree($cmt_last, $c, $ed);
-                       if (@$mods == 0) {
-                               print "No changes\nr$r_last = $cmt_last\n";
-                               $ed->abort_edit;
-                       } else {
-                               $ed->close_edit;
-                       }
-                       exit 0;
-               }
-               my ($r_new, $cmt_new, $no);
-               while (<$fh>) {
-                       print $_;
-                       chomp;
-                       if (/^r(\d+) = ($sha1)$/o) {
-                               ($r_new, $cmt_new) = ($1, $2);
-                       } elsif ($_ eq 'No changes') {
-                               $no = 1;
-                       }
-               }
-               close $fh or croak $?;
-               if (! defined $r_new && ! defined $cmt_new) {
-                       unless ($no) {
-                               die "Failed to parse revision information\n";
-                       }
-               } else {
-                       ($r_last, $cmt_last) = ($r_new, $cmt_new);
-               }
-       }
-       $ENV{LC_ALL} = 'C';
-       unlink $commit_msg;
-}
-
-sub show_ignore {
-       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       $_use_lib ? show_ignore_lib() : show_ignore_cmd();
-}
-
-sub show_ignore_cmd {
-       require File::Find or die $!;
-       if (defined $_revision) {
-               die "-r/--revision option doesn't work unless the Perl SVN ",
-                       "libraries are used\n";
-       }
-       chdir $SVN_WC or croak $!;
-       my %ign;
-       File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){
-               s#^\./##;
-               @{$ign{$_}} = svn_propget_base('svn:ignore', $_);
-               }}, no_chdir=>1},'.');
-
-       print "\n# /\n";
-       foreach (@{$ign{'.'}}) { print '/',$_ if /\S/ }
-       delete $ign{'.'};
-       foreach my $i (sort keys %ign) {
-               print "\n# ",$i,"\n";
-               foreach (@{$ign{$i}}) { print '/',$i,'/',$_ if /\S/ }
-       }
-}
-
-sub show_ignore_lib {
-       my $repo;
-       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
-       $SVN ||= libsvn_connect($repo);
-       my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
-       libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r);
-}
-
-sub graft_branches {
-       my $gr_file = "$GIT_DIR/info/grafts";
-       my ($grafts, $comments) = read_grafts($gr_file);
-       my $gr_sha1;
-
-       if (%$grafts) {
-               # temporarily disable our grafts file to make this idempotent
-               chomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file));
-               rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
-       }
-
-       my $l_map = read_url_paths();
-       my @re = map { qr/$_/is } @_opt_m if @_opt_m;
-       unless ($_no_default_regex) {
-               push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i,
-                       qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i,
-                       qr/\b(?:from|of)\s+([\w\.\-]+)/i );
-       }
-       foreach my $u (keys %$l_map) {
-               if (@re) {
-                       foreach my $p (keys %{$l_map->{$u}}) {
-                               graft_merge_msg($grafts,$l_map,$u,$p,@re);
-                       }
-               }
-               unless ($_no_graft_copy) {
-                       if ($_use_lib) {
-                               graft_file_copy_lib($grafts,$l_map,$u);
-                       } else {
-                               graft_file_copy_cmd($grafts,$l_map,$u);
-                       }
-               }
-       }
-       graft_tree_joins($grafts);
-
-       write_grafts($grafts, $comments, $gr_file);
-       unlink "$gr_file~$gr_sha1" if $gr_sha1;
-}
-
-sub multi_init {
-       my $url = shift;
-       $_trunk ||= 'trunk';
-       $_trunk =~ s#/+$##;
-       $url =~ s#/+$## if $url;
-       if ($_trunk !~ m#^[a-z\+]+://#) {
-               $_trunk = '/' . $_trunk if ($_trunk !~ m#^/#);
-               unless ($url) {
-                       print STDERR "E: '$_trunk' is not a complete URL ",
-                               "and a separate URL is not specified\n";
-                       exit 1;
-               }
-               $_trunk = $url . $_trunk;
-       }
-       if ($GIT_SVN eq 'git-svn') {
-               print "GIT_SVN_ID set to 'trunk' for $_trunk\n";
-               $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
-       }
-       init_vars();
-       init($_trunk);
-       complete_url_ls_init($url, $_branches, '--branches/-b', '');
-       complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
-}
-
-sub multi_fetch {
-       # try to do trunk first, since branches/tags
-       # may be descended from it.
-       if (-e "$GIT_DIR/svn/trunk/info/url") {
-               fetch_child_id('trunk', @_);
-       }
-       rec_fetch('', "$GIT_DIR/svn", @_);
-}
-
-sub show_log {
-       my (@args) = @_;
-       my ($r_min, $r_max);
-       my $r_last = -1; # prevent dupes
-       rload_authors() if $_authors;
-       if (defined $TZ) {
-               $ENV{TZ} = $TZ;
-       } else {
-               delete $ENV{TZ};
-       }
-       if (defined $_revision) {
-               if ($_revision =~ /^(\d+):(\d+)$/) {
-                       ($r_min, $r_max) = ($1, $2);
-               } elsif ($_revision =~ /^\d+$/) {
-                       $r_min = $r_max = $_revision;
-               } else {
-                       print STDERR "-r$_revision is not supported, use ",
-                               "standard \'git log\' arguments instead\n";
-                       exit 1;
-               }
-       }
-
-       my $pid = open(my $log,'-|');
-       defined $pid or croak $!;
-       if (!$pid) {
-               exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!;
-       }
-       setup_pager();
-       my (@k, $c, $d);
-
-       while (<$log>) {
-               if (/^commit ($sha1_short)/o) {
-                       my $cmt = $1;
-                       if ($c && cmt_showable($c) && $c->{r} != $r_last) {
-                               $r_last = $c->{r};
-                               process_commit($c, $r_min, $r_max, \@k) or
-                                                               goto out;
-                       }
-                       $d = undef;
-                       $c = { c => $cmt };
-               } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) {
-                       get_author_info($c, $1, $2, $3);
-               } elsif (/^(?:tree|parent|committer) /) {
-                       # ignore
-               } elsif (/^:\d{6} \d{6} $sha1_short/o) {
-                       push @{$c->{raw}}, $_;
-               } elsif (/^diff /) {
-                       $d = 1;
-                       push @{$c->{diff}}, $_;
-               } elsif ($d) {
-                       push @{$c->{diff}}, $_;
-               } elsif (/^    (git-svn-id:.+)$/) {
-                       (undef, $c->{r}, undef) = extract_metadata($1);
-               } elsif (s/^    //) {
-                       push @{$c->{l}}, $_;
-               }
-       }
-       if ($c && defined $c->{r} && $c->{r} != $r_last) {
-               $r_last = $c->{r};
-               process_commit($c, $r_min, $r_max, \@k);
-       }
-       if (@k) {
-               my $swap = $r_max;
-               $r_max = $r_min;
-               $r_min = $swap;
-               process_commit($_, $r_min, $r_max) foreach reverse @k;
-       }
-out:
-       close $log;
-       print '-' x72,"\n" unless $_incremental || $_oneline;
-}
-
-sub commit_diff_usage {
-       print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n";
-       exit 1
-}
-
-sub commit_diff {
-       if (!$_use_lib) {
-               print STDERR "commit-diff must be used with SVN libraries\n";
-               exit 1;
-       }
-       my $ta = shift or commit_diff_usage();
-       my $tb = shift or commit_diff_usage();
-       if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
-               print STDERR "Needed URL or usable git-svn id command-line\n";
-               commit_diff_usage();
-       }
-       if (defined $_message && defined $_file) {
-               print STDERR "Both --message/-m and --file/-F specified ",
-                               "for the commit message.\n",
-                               "I have no idea what you mean\n";
-               exit 1;
-       }
-       if (defined $_file) {
-               $_message = file_to_s($_message);
-       } else {
-               $_message ||= get_commit_message($tb,
-                                       "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
-       }
-       my $repo;
-       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
-       $SVN_LOG ||= libsvn_connect($repo);
-       $SVN ||= libsvn_connect($repo);
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
-       my $ed = SVN::Git::Editor->new({        r => $SVN->get_latest_revnum,
-                                               ra => $SVN, c => $tb,
-                                               svn_path => $SVN_PATH
-                                       },
-                               $SVN->get_commit_editor($_message,
-                                       sub {print "Committed $_[0]\n"},@lock)
-                               );
-       my $mods = libsvn_checkout_tree($ta, $tb, $ed);
-       if (@$mods == 0) {
-               print "No changes\n$ta == $tb\n";
-               $ed->abort_edit;
-       } else {
-               $ed->close_edit;
-       }
-}
-
-########################### utility functions #########################
-
-sub cmt_showable {
-       my ($c) = @_;
-       return 1 if defined $c->{r};
-       if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
-                               $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
-               my @msg = safe_qx(qw/git-cat-file commit/, $c->{c});
-               shift @msg while ($msg[0] ne "\n");
-               shift @msg;
-               @{$c->{l}} = grep !/^git-svn-id: /, @msg;
-
-               (undef, $c->{r}, undef) = extract_metadata(
-                               (grep(/^git-svn-id: /, @msg))[-1]);
-       }
-       return defined $c->{r};
-}
-
-sub git_svn_log_cmd {
-       my ($r_min, $r_max) = @_;
-       my @cmd = (qw/git-log --abbrev-commit --pretty=raw
-                       --default/, "refs/remotes/$GIT_SVN");
-       push @cmd, '--summary' if $_verbose;
-       return @cmd unless defined $r_max;
-       if ($r_max == $r_min) {
-               push @cmd, '--max-count=1';
-               if (my $c = revdb_get($REVDB, $r_max)) {
-                       push @cmd, $c;
-               }
-       } else {
-               my ($c_min, $c_max);
-               $c_max = revdb_get($REVDB, $r_max);
-               $c_min = revdb_get($REVDB, $r_min);
-               if ($c_min && $c_max) {
-                       if ($r_max > $r_max) {
-                               push @cmd, "$c_min..$c_max";
-                       } else {
-                               push @cmd, "$c_max..$c_min";
-                       }
-               } elsif ($r_max > $r_min) {
-                       push @cmd, $c_max;
-               } else {
-                       push @cmd, $c_min;
-               }
-       }
-       return @cmd;
-}
-
-sub fetch_child_id {
-       my $id = shift;
-       print "Fetching $id\n";
-       my $ref = "$GIT_DIR/refs/remotes/$id";
-       defined(my $pid = open my $fh, '-|') or croak $!;
-       if (!$pid) {
-               $_repack = undef;
-               $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-               init_vars();
-               fetch(@_);
-               exit 0;
-       }
-       while (<$fh>) {
-               print $_;
-               check_repack() if (/^r\d+ = $sha1/);
-       }
-       close $fh or croak $?;
-}
-
-sub rec_fetch {
-       my ($pfx, $p, @args) = @_;
-       my @dir;
-       foreach (sort <$p/*>) {
-               if (-r "$_/info/url") {
-                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
-                       my $id = $pfx . basename $_;
-                       next if $id eq 'trunk';
-                       fetch_child_id($id, @args);
-               } elsif (-d $_) {
-                       push @dir, $_;
-               }
-       }
-       foreach (@dir) {
-               my $x = $_;
-               $x =~ s!^\Q$GIT_DIR\E/svn/!!;
-               rec_fetch($x, $_);
-       }
-}
-
-sub complete_url_ls_init {
-       my ($url, $var, $switch, $pfx) = @_;
-       unless ($var) {
-               print STDERR "W: $switch not specified\n";
-               return;
-       }
-       $var =~ s#/+$##;
-       if ($var !~ m#^[a-z\+]+://#) {
-               $var = '/' . $var if ($var !~ m#^/#);
-               unless ($url) {
-                       print STDERR "E: '$var' is not a complete URL ",
-                               "and a separate URL is not specified\n";
-                       exit 1;
-               }
-               $var = $url . $var;
-       }
-       chomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var)
-                               : safe_qx(qw/svn ls --non-interactive/, $var));
-       my $old = $GIT_SVN;
-       defined(my $pid = fork) or croak $!;
-       if (!$pid) {
-               foreach my $u (map { "$var/$_" } (grep m!/$!, @ls)) {
-                       $u =~ s#/+$##;
-                       if ($u !~ m!\Q$var\E/(.+)$!) {
-                               print STDERR "W: Unrecognized URL: $u\n";
-                               die "This should never happen\n";
-                       }
-                       my $id = $pfx.$1;
-                       print "init $u => $id\n";
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-                       init_vars();
-                       init($u);
-               }
-               exit 0;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-}
-
-sub common_prefix {
-       my $paths = shift;
-       my %common;
-       foreach (@$paths) {
-               my @tmp = split m#/#, $_;
-               my $p = '';
-               while (my $x = shift @tmp) {
-                       $p .= "/$x";
-                       $common{$p} ||= 0;
-                       $common{$p}++;
-               }
-       }
-       foreach (sort {length $b <=> length $a} keys %common) {
-               if ($common{$_} == @$paths) {
-                       return $_;
-               }
-       }
-       return '';
-}
-
-# grafts set here are 'stronger' in that they're based on actual tree
-# matches, and won't be deleted from merge-base checking in write_grafts()
-sub graft_tree_joins {
-       my $grafts = shift;
-       map_tree_joins() if (@_branch_from && !%tree_map);
-       return unless %tree_map;
-
-       git_svn_each(sub {
-               my $i = shift;
-               defined(my $pid = open my $fh, '-|') or croak $!;
-               if (!$pid) {
-                       exec qw/git-rev-list --pretty=raw/,
-                                       "refs/remotes/$i" or croak $!;
-               }
-               while (<$fh>) {
-                       next unless /^commit ($sha1)$/o;
-                       my $c = $1;
-                       my ($t) = (<$fh> =~ /^tree ($sha1)$/o);
-                       next unless $tree_map{$t};
-
-                       my $l;
-                       do {
-                               $l = readline $fh;
-                       } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/);
-
-                       my ($s, $tz) = ($1, $2);
-                       if ($tz =~ s/^\+//) {
-                               $s += tz_to_s_offset($tz);
-                       } elsif ($tz =~ s/^\-//) {
-                               $s -= tz_to_s_offset($tz);
-                       }
-
-                       my ($url_a, $r_a, $uuid_a) = cmt_metadata($c);
-
-                       foreach my $p (@{$tree_map{$t}}) {
-                               next if $p eq $c;
-                               my $mb = eval {
-                                       safe_qx('git-merge-base', $c, $p)
-                               };
-                               next unless ($@ || $?);
-                               if (defined $r_a) {
-                                       # see if SVN says it's a relative
-                                       my ($url_b, $r_b, $uuid_b) =
-                                                       cmt_metadata($p);
-                                       next if (defined $url_b &&
-                                                       defined $url_a &&
-                                                       ($url_a eq $url_b) &&
-                                                       ($uuid_a eq $uuid_b));
-                                       if ($uuid_a eq $uuid_b) {
-                                               if ($r_b < $r_a) {
-                                                       $grafts->{$c}->{$p} = 2;
-                                                       next;
-                                               } elsif ($r_b > $r_a) {
-                                                       $grafts->{$p}->{$c} = 2;
-                                                       next;
-                                               }
-                                       }
-                               }
-                               my $ct = get_commit_time($p);
-                               if ($ct < $s) {
-                                       $grafts->{$c}->{$p} = 2;
-                               } elsif ($ct > $s) {
-                                       $grafts->{$p}->{$c} = 2;
-                               }
-                               # what should we do when $ct == $s ?
-                       }
-               }
-               close $fh or croak $?;
-       });
-}
-
-# this isn't funky-filename safe, but good enough for now...
-sub graft_file_copy_cmd {
-       my ($grafts, $l_map, $u) = @_;
-       my $paths = $l_map->{$u};
-       my $pfx = common_prefix([keys %$paths]);
-       $SVN_URL ||= $u.$pfx;
-       my $pid = open my $fh, '-|';
-       defined $pid or croak $!;
-       unless ($pid) {
-               my @exec = qw/svn log -v/;
-               push @exec, "-r$_revision" if defined $_revision;
-               exec @exec, $u.$pfx or croak $!;
-       }
-       my ($r, $mp) = (undef, undef);
-       while (<$fh>) {
-               chomp;
-               if (/^\-{72}$/) {
-                       $mp = $r = undef;
-               } elsif (/^r(\d+) \| /) {
-                       $r = $1 unless defined $r;
-               } elsif (/^Changed paths:/) {
-                       $mp = 1;
-               } elsif ($mp && m#^   [AR] /(\S.*?) \(from /(\S+?):(\d+)\)$#) {
-                       my ($p1, $p0, $r0) = ($1, $2, $3);
-                       my $c = find_graft_path_commit($paths, $p1, $r);
-                       next unless $c;
-                       find_graft_path_parents($grafts, $paths, $c, $p0, $r0);
-               }
-       }
-}
-
-sub graft_file_copy_lib {
-       my ($grafts, $l_map, $u) = @_;
-       my $tree_paths = $l_map->{$u};
-       my $pfx = common_prefix([keys %$tree_paths]);
-       my ($repo, $path) = repo_path_split($u.$pfx);
-       $SVN_LOG ||= libsvn_connect($repo);
-       $SVN ||= libsvn_connect($repo);
-
-       my ($base, $head) = libsvn_parse_revision();
-       my $inc = 1000;
-       my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
-       my $eh = $SVN::Error::handler;
-       $SVN::Error::handler = \&libsvn_skip_unknown_revs;
-       while (1) {
-               my $pool = SVN::Pool->new;
-               libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1,
-                       sub {
-                               libsvn_graft_file_copies($grafts, $tree_paths,
-                                                       $path, @_);
-                       }, $pool);
-               $pool->clear;
-               last if ($max >= $head);
-               $min = $max + 1;
-               $max += $inc;
-               $max = $head if ($max > $head);
-       }
-       $SVN::Error::handler = $eh;
-}
-
-sub process_merge_msg_matches {
-       my ($grafts, $l_map, $u, $p, $c, @matches) = @_;
-       my (@strong, @weak);
-       foreach (@matches) {
-               # merging with ourselves is not interesting
-               next if $_ eq $p;
-               if ($l_map->{$u}->{$_}) {
-                       push @strong, $_;
-               } else {
-                       push @weak, $_;
-               }
-       }
-       foreach my $w (@weak) {
-               last if @strong;
-               # no exact match, use branch name as regexp.
-               my $re = qr/\Q$w\E/i;
-               foreach (keys %{$l_map->{$u}}) {
-                       if (/$re/) {
-                               push @strong, $l_map->{$u}->{$_};
-                               last;
-                       }
-               }
-               last if @strong;
-               $w = basename($w);
-               $re = qr/\Q$w\E/i;
-               foreach (keys %{$l_map->{$u}}) {
-                       if (/$re/) {
-                               push @strong, $l_map->{$u}->{$_};
-                               last;
-                       }
-               }
-       }
-       my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+)
-                                       \s(?:[a-f\d\-]+)$/xsm);
-       unless (defined $rev) {
-               ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+)
-                                       \@(?:[a-f\d\-]+)/xsm);
-               return unless defined $rev;
-       }
-       foreach my $m (@strong) {
-               my ($r0, $s0) = find_rev_before($rev, $m, 1);
-               $grafts->{$c->{c}}->{$s0} = 1 if defined $s0;
-       }
-}
-
-sub graft_merge_msg {
-       my ($grafts, $l_map, $u, $p, @re) = @_;
-
-       my $x = $l_map->{$u}->{$p};
-       my $rl = rev_list_raw($x);
-       while (my $c = next_rev_list_entry($rl)) {
-               foreach my $re (@re) {
-                       my (@br) = ($c->{m} =~ /$re/g);
-                       next unless @br;
-                       process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br);
-               }
-       }
-}
-
-sub read_uuid {
-       return if $SVN_UUID;
-       if ($_use_lib) {
-               my $pool = SVN::Pool->new;
-               $SVN_UUID = $SVN->get_uuid($pool);
-               $pool->clear;
-       } else {
-               my $info = shift || svn_info('.');
-               $SVN_UUID = $info->{'Repository UUID'} or
-                                       croak "Repository UUID unreadable\n";
-       }
-}
-
-sub quiet_run {
-       my $pid = fork;
-       defined $pid or croak $!;
-       if (!$pid) {
-               open my $null, '>', '/dev/null' or croak $!;
-               open STDERR, '>&', $null or croak $!;
-               open STDOUT, '>&', $null or croak $!;
-               exec @_ or croak $!;
-       }
-       waitpid $pid, 0;
-       return $?;
-}
-
-sub repo_path_split {
-       my $full_url = shift;
-       $full_url =~ s#/+$##;
-
-       foreach (@repo_path_split_cache) {
-               if ($full_url =~ s#$_##) {
-                       my $u = $1;
-                       $full_url =~ s#^/+##;
-                       return ($u, $full_url);
-               }
-       }
-
-       my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
-       $path =~ s#^/+##;
-       my @paths = split(m#/+#, $path);
-
-       if ($_use_lib) {
-               while (1) {
-                       $SVN = libsvn_connect($url);
-                       last if (defined $SVN &&
-                               defined eval { $SVN->get_latest_revnum });
-                       my $n = shift @paths || last;
-                       $url .= "/$n";
-               }
-       } else {
-               while (quiet_run(qw/svn ls --non-interactive/, $url)) {
-                       my $n = shift @paths || last;
-                       $url .= "/$n";
-               }
-       }
-       push @repo_path_split_cache, qr/^(\Q$url\E)/;
-       $path = join('/',@paths);
-       return ($url, $path);
-}
-
-sub setup_git_svn {
-       defined $SVN_URL or croak "SVN repository location required\n";
-       unless (-d $GIT_DIR) {
-               croak "GIT_DIR=$GIT_DIR does not exist!\n";
-       }
-       mkpath([$GIT_SVN_DIR]);
-       mkpath(["$GIT_SVN_DIR/info"]);
-       open my $fh, '>>',$REVDB or croak $!;
-       close $fh;
-       s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url");
-
-}
-
-sub assert_svn_wc_clean {
-       return if $_use_lib;
-       my ($svn_rev) = @_;
-       croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
-       my $lcr = svn_info('.')->{'Last Changed Rev'};
-       if ($svn_rev != $lcr) {
-               print STDERR "Checking for copy-tree ... ";
-               my @diff = grep(/^Index: /,(safe_qx(qw(svn diff),
-                                               "-r$lcr:$svn_rev")));
-               if (@diff) {
-                       croak "Nope!  Expected r$svn_rev, got r$lcr\n";
-               } else {
-                       print STDERR "OK!\n";
-               }
-       }
-       my @status = grep(!/^Performing status on external/,(`svn status`));
-       @status = grep(!/^\s*$/,@status);
-       if (scalar @status) {
-               print STDERR "Tree ($SVN_WC) is not clean:\n";
-               print STDERR $_ foreach @status;
-               croak;
-       }
-}
-
-sub get_tree_from_treeish {
-       my ($treeish) = @_;
-       croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
-       chomp(my $type = `git-cat-file -t $treeish`);
-       my $expected;
-       while ($type eq 'tag') {
-               chomp(($treeish, $type) = `git-cat-file tag $treeish`);
-       }
-       if ($type eq 'commit') {
-               $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
-               ($expected) = ($expected =~ /^tree ($sha1)$/);
-               die "Unable to get tree from $treeish\n" unless $expected;
-       } elsif ($type eq 'tree') {
-               $expected = $treeish;
-       } else {
-               die "$treeish is a $type, expected tree, tag or commit\n";
-       }
-       return $expected;
-}
-
-sub assert_tree {
-       return if $_use_lib;
-       my ($treeish) = @_;
-       my $expected = get_tree_from_treeish($treeish);
-
-       my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';
-       if (-e $tmpindex) {
-               unlink $tmpindex or croak $!;
-       }
-       my $old_index = set_index($tmpindex);
-       index_changes(1);
-       chomp(my $tree = `git-write-tree`);
-       restore_index($old_index);
-       if ($tree ne $expected) {
-               croak "Tree mismatch, Got: $tree, Expected: $expected\n";
-       }
-       unlink $tmpindex;
-}
-
-sub parse_diff_tree {
-       my $diff_fh = shift;
-       local $/ = "\0";
-       my $state = 'meta';
-       my @mods;
-       while (<$diff_fh>) {
-               chomp $_; # this gets rid of the trailing "\0"
-               if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
-                                       $sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
-                       push @mods, {   mode_a => $1, mode_b => $2,
-                                       sha1_b => $3, chg => $4 };
-                       if ($4 =~ /^(?:C|R)$/) {
-                               $state = 'file_a';
-                       } else {
-                               $state = 'file_b';
-                       }
-               } elsif ($state eq 'file_a') {
-                       my $x = $mods[$#mods] or croak "Empty array\n";
-                       if ($x->{chg} !~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       $x->{file_a} = $_;
-                       $state = 'file_b';
-               } elsif ($state eq 'file_b') {
-                       my $x = $mods[$#mods] or croak "Empty array\n";
-                       if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       $x->{file_b} = $_;
-                       $state = 'meta';
-               } else {
-                       croak "Error parsing $_\n";
-               }
-       }
-       close $diff_fh or croak $?;
-
-       return \@mods;
-}
-
-sub svn_check_prop_executable {
-       my $m = shift;
-       return if -l $m->{file_b};
-       if ($m->{mode_b} =~ /755$/) {
-               chmod((0755 &~ umask),$m->{file_b}) or croak $!;
-               if ($m->{mode_a} !~ /755$/) {
-                       sys(qw(svn propset svn:executable 1), $m->{file_b});
-               }
-               -x $m->{file_b} or croak "$m->{file_b} is not executable!\n";
-       } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
-               sys(qw(svn propdel svn:executable), $m->{file_b});
-               chmod((0644 &~ umask),$m->{file_b}) or croak $!;
-               -x $m->{file_b} and croak "$m->{file_b} is executable!\n";
-       }
-}
-
-sub svn_ensure_parent_path {
-       my $dir_b = dirname(shift);
-       svn_ensure_parent_path($dir_b) if ($dir_b ne File::Spec->curdir);
-       mkpath([$dir_b]) unless (-d $dir_b);
-       sys(qw(svn add -N), $dir_b) unless (-d "$dir_b/.svn");
-}
-
-sub precommit_check {
-       my $mods = shift;
-       my (%rm_file, %rmdir_check, %added_check);
-
-       my %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 );
-       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-               if ($m->{chg} eq 'R') {
-                       if (-d $m->{file_b}) {
-                               err_dir_to_file("$m->{file_a} => $m->{file_b}");
-                       }
-                       # dir/$file => dir/file/$file
-                       my $dirname = dirname($m->{file_b});
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($dirname ne $m->{file_a}) {
-                                       $dirname = dirname($dirname);
-                                       next;
-                               }
-                               err_file_to_dir("$m->{file_a} => $m->{file_b}");
-                       }
-                       # baz/zzz => baz (baz is a file)
-                       $dirname = dirname($m->{file_a});
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($dirname ne $m->{file_b}) {
-                                       $dirname = dirname($dirname);
-                                       next;
-                               }
-                               err_dir_to_file("$m->{file_a} => $m->{file_b}");
-                       }
-               }
-               if ($m->{chg} =~ /^(D|R)$/) {
-                       my $t = $1 eq 'D' ? 'file_b' : 'file_a';
-                       $rm_file{ $m->{$t} } = 1;
-                       my $dirname = dirname( $m->{$t} );
-                       my $basename = basename( $m->{$t} );
-                       $rmdir_check{$dirname}->{$basename} = 1;
-               } elsif ($m->{chg} =~ /^(?:A|C)$/) {
-                       if (-d $m->{file_b}) {
-                               err_dir_to_file($m->{file_b});
-                       }
-                       my $dirname = dirname( $m->{file_b} );
-                       my $basename = basename( $m->{file_b} );
-                       $added_check{$dirname}->{$basename} = 1;
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($rm_file{$dirname}) {
-                                       err_file_to_dir($m->{file_b});
-                               }
-                               $dirname = dirname $dirname;
-                       }
-               }
-       }
-       return (\%rmdir_check, \%added_check);
-
-       sub err_dir_to_file {
-               my $file = shift;
-               print STDERR "Node change from directory to file ",
-                               "is not supported by Subversion: ",$file,"\n";
-               exit 1;
-       }
-       sub err_file_to_dir {
-               my $file = shift;
-               print STDERR "Node change from file to directory ",
-                               "is not supported by Subversion: ",$file,"\n";
-               exit 1;
-       }
-}
-
-
-sub get_diff {
-       my ($from, $treeish) = @_;
-       assert_tree($from);
-       print "diff-tree $from $treeish\n";
-       my $pid = open my $diff_fh, '-|';
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               my @diff_tree = qw(git-diff-tree -z -r);
-               if ($_cp_similarity) {
-                       push @diff_tree, "-C$_cp_similarity";
-               } else {
-                       push @diff_tree, '-C';
-               }
-               push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
-               push @diff_tree, "-l$_l" if defined $_l;
-               exec(@diff_tree, $from, $treeish) or croak $!;
-       }
-       return parse_diff_tree($diff_fh);
-}
-
-sub svn_checkout_tree {
-       my ($from, $treeish) = @_;
-       my $mods = get_diff($from->{commit}, $treeish);
-       return $mods unless (scalar @$mods);
-       my ($rm, $add) = precommit_check($mods);
-
-       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
-       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-               if ($m->{chg} eq 'C') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       sys(qw(svn cp),         $m->{file_a}, $m->{file_b});
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'D') {
-                       sys(qw(svn rm --force), $m->{file_b});
-               } elsif ($m->{chg} eq 'R') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       sys(qw(svn mv --force), $m->{file_a}, $m->{file_b});
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'M') {
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'T') {
-                       sys(qw(svn rm --force),$m->{file_b});
-                       apply_mod_line_blob($m);
-                       sys(qw(svn add), $m->{file_b});
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'A') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       apply_mod_line_blob($m);
-                       sys(qw(svn add), $m->{file_b});
-                       svn_check_prop_executable($m);
-               } else {
-                       croak "Invalid chg: $m->{chg}\n";
-               }
-       }
-
-       assert_tree($treeish);
-       if ($_rmdir) { # remove empty directories
-               handle_rmdir($rm, $add);
-       }
-       assert_tree($treeish);
-       return $mods;
-}
-
-sub libsvn_checkout_tree {
-       my ($from, $treeish, $ed) = @_;
-       my $mods = get_diff($from, $treeish);
-       return $mods unless (scalar @$mods);
-       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
-       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-               my $f = $m->{chg};
-               if (defined $o{$f}) {
-                       $ed->$f($m, $_q);
-               } else {
-                       croak "Invalid change type: $f\n";
-               }
-       }
-       $ed->rmdirs($_q) if $_rmdir;
-       return $mods;
-}
-
-# svn ls doesn't work with respect to the current working tree, but what's
-# in the repository.  There's not even an option for it... *sigh*
-# (added files don't show up and removed files remain in the ls listing)
-sub svn_ls_current {
-       my ($dir, $rm, $add) = @_;
-       chomp(my @ls = safe_qx('svn','ls',$dir));
-       my @ret = ();
-       foreach (@ls) {
-               s#/$##; # trailing slashes are evil
-               push @ret, $_ unless $rm->{$dir}->{$_};
-       }
-       if (exists $add->{$dir}) {
-               push @ret, keys %{$add->{$dir}};
-       }
-       return \@ret;
-}
-
-sub handle_rmdir {
-       my ($rm, $add) = @_;
-
-       foreach my $dir (sort {length $b <=> length $a} keys %$rm) {
-               my $ls = svn_ls_current($dir, $rm, $add);
-               next if (scalar @$ls);
-               sys(qw(svn rm --force),$dir);
-
-               my $dn = dirname $dir;
-               $rm->{ $dn }->{ basename $dir } = 1;
-               $ls = svn_ls_current($dn, $rm, $add);
-               while (scalar @$ls == 0 && $dn ne File::Spec->curdir) {
-                       sys(qw(svn rm --force),$dn);
-                       $dir = basename $dn;
-                       $dn = dirname $dn;
-                       $rm->{ $dn }->{ $dir } = 1;
-                       $ls = svn_ls_current($dn, $rm, $add);
-               }
-       }
-}
-
-sub get_commit_message {
-       my ($commit, $commit_msg) = (@_);
-       my %log_msg = ( msg => '' );
-       open my $msg, '>', $commit_msg or croak $!;
-
-       chomp(my $type = `git-cat-file -t $commit`);
-       if ($type eq 'commit') {
-               my $pid = open my $msg_fh, '-|';
-               defined $pid or croak $!;
-
-               if ($pid == 0) {
-                       exec(qw(git-cat-file commit), $commit) or croak $!;
-               }
-               my $in_msg = 0;
-               while (<$msg_fh>) {
-                       if (!$in_msg) {
-                               $in_msg = 1 if (/^\s*$/);
-                       } elsif (/^git-svn-id: /) {
-                               # skip this, we regenerate the correct one
-                               # on re-fetch anyways
-                       } else {
-                               print $msg $_ or croak $!;
-                       }
-               }
-               close $msg_fh or croak $?;
-       }
-       close $msg or croak $!;
-
-       if ($_edit || ($type eq 'tree')) {
-               my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
-               system($editor, $commit_msg);
-       }
-
-       # file_to_s removes all trailing newlines, so just use chomp() here:
-       open $msg, '<', $commit_msg or croak $!;
-       { local $/; chomp($log_msg{msg} = <$msg>); }
-       close $msg or croak $!;
-
-       return \%log_msg;
-}
-
-sub set_svn_commit_env {
-       if (defined $LC_ALL) {
-               $ENV{LC_ALL} = $LC_ALL;
-       } else {
-               delete $ENV{LC_ALL};
-       }
-}
-
-sub svn_commit_tree {
-       my ($last, $commit) = @_;
-       my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
-       my $log_msg = get_commit_message($commit, $commit_msg);
-       my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/);
-       print "Committing $commit: $oneline\n";
-
-       set_svn_commit_env();
-       my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
-       $ENV{LC_ALL} = 'C';
-       unlink $commit_msg;
-       my ($committed) = ($ci_output[$#ci_output] =~ /(\d+)/);
-       if (!defined $committed) {
-               my $out = join("\n",@ci_output);
-               print STDERR "W: Trouble parsing \`svn commit' output:\n\n",
-                               $out, "\n\nAssuming English locale...";
-               ($committed) = ($out =~ /^Committed revision \d+\./sm);
-               defined $committed or die " FAILED!\n",
-                       "Commit output failed to parse committed revision!\n",
-               print STDERR " OK\n";
-       }
-
-       my @svn_up = qw(svn up);
-       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-       if ($_optimize_commits && ($committed == ($last->{revision} + 1))) {
-               push @svn_up, "-r$committed";
-               sys(@svn_up);
-               my $info = svn_info('.');
-               my $date = $info->{'Last Changed Date'} or die "Missing date\n";
-               if ($info->{'Last Changed Rev'} != $committed) {
-                       croak "$info->{'Last Changed Rev'} != $committed\n"
-               }
-               my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
-                                       /(\d{4})\-(\d\d)\-(\d\d)\s
-                                        (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
-                                        or croak "Failed to parse date: $date\n";
-               $log_msg->{date} = "$tz $Y-$m-$d $H:$M:$S";
-               $log_msg->{author} = $info->{'Last Changed Author'};
-               $log_msg->{revision} = $committed;
-               $log_msg->{msg} .= "\n";
-               $log_msg->{parents} = [ $last->{commit} ];
-               $log_msg->{commit} = git_commit($log_msg, $commit);
-               return $log_msg;
-       }
-       # resync immediately
-       push @svn_up, "-r$last->{revision}";
-       sys(@svn_up);
-       return fetch("$committed=$commit");
-}
-
-sub rev_list_raw {
-       my (@args) = @_;
-       my $pid = open my $fh, '-|';
-       defined $pid or croak $!;
-       if (!$pid) {
-               exec(qw/git-rev-list --pretty=raw/, @args) or croak $!;
-       }
-       return { fh => $fh, t => { } };
-}
-
-sub next_rev_list_entry {
-       my $rl = shift;
-       my $fh = $rl->{fh};
-       my $x = $rl->{t};
-       while (<$fh>) {
-               if (/^commit ($sha1)$/o) {
-                       if ($x->{c}) {
-                               $rl->{t} = { c => $1 };
-                               return $x;
-                       } else {
-                               $x->{c} = $1;
-                       }
-               } elsif (/^parent ($sha1)$/o) {
-                       $x->{p}->{$1} = 1;
-               } elsif (s/^    //) {
-                       $x->{m} ||= '';
-                       $x->{m} .= $_;
-               }
-       }
-       return ($x != $rl->{t}) ? $x : undef;
-}
-
-# read the entire log into a temporary file (which is removed ASAP)
-# and store the file handle + parser state
-sub svn_log_raw {
-       my (@log_args) = @_;
-       my $log_fh = IO::File->new_tmpfile or croak $!;
-       my $pid = fork;
-       defined $pid or croak $!;
-       if (!$pid) {
-               open STDOUT, '>&', $log_fh or croak $!;
-               exec (qw(svn log), @log_args) or croak $!
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-       seek $log_fh, 0, 0 or croak $!;
-       return { state => 'sep', fh => $log_fh };
-}
-
-sub next_log_entry {
-       my $log = shift; # retval of svn_log_raw()
-       my $ret = undef;
-       my $fh = $log->{fh};
-
-       while (<$fh>) {
-               chomp;
-               if (/^\-{72}$/) {
-                       if ($log->{state} eq 'msg') {
-                               if ($ret->{lines}) {
-                                       $ret->{msg} .= $_."\n";
-                                       unless(--$ret->{lines}) {
-                                               $log->{state} = 'sep';
-                                       }
-                               } else {
-                                       croak "Log parse error at: $_\n",
-                                               $ret->{revision},
-                                               "\n";
-                               }
-                               next;
-                       }
-                       if ($log->{state} ne 'sep') {
-                               croak "Log parse error at: $_\n",
-                                       "state: $log->{state}\n",
-                                       $ret->{revision},
-                                       "\n";
-                       }
-                       $log->{state} = 'rev';
-
-                       # if we have an empty log message, put something there:
-                       if ($ret) {
-                               $ret->{msg} ||= "\n";
-                               delete $ret->{lines};
-                               return $ret;
-                       }
-                       next;
-               }
-               if ($log->{state} eq 'rev' && s/^r(\d+)\s*\|\s*//) {
-                       my $rev = $1;
-                       my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3);
-                       ($lines) = ($lines =~ /(\d+)/);
-                       my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
-                                       /(\d{4})\-(\d\d)\-(\d\d)\s
-                                        (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
-                                        or croak "Failed to parse date: $date\n";
-                       $ret = {        revision => $rev,
-                                       date => "$tz $Y-$m-$d $H:$M:$S",
-                                       author => $author,
-                                       lines => $lines,
-                                       msg => '' };
-                       if (defined $_authors && ! defined $users{$author}) {
-                               die "Author: $author not defined in ",
-                                               "$_authors file\n";
-                       }
-                       $log->{state} = 'msg_start';
-                       next;
-               }
-               # skip the first blank line of the message:
-               if ($log->{state} eq 'msg_start' && /^$/) {
-                       $log->{state} = 'msg';
-               } elsif ($log->{state} eq 'msg') {
-                       if ($ret->{lines}) {
-                               $ret->{msg} .= $_."\n";
-                               unless (--$ret->{lines}) {
-                                       $log->{state} = 'sep';
-                               }
-                       } else {
-                               croak "Log parse error at: $_\n",
-                                       $ret->{revision},"\n";
-                       }
-               }
-       }
-       return $ret;
-}
-
-sub svn_info {
-       my $url = shift || $SVN_URL;
-
-       my $pid = open my $info_fh, '-|';
-       defined $pid or croak $!;
-
-       if ($pid == 0) {
-               exec(qw(svn info),$url) or croak $!;
-       }
-
-       my $ret = {};
-       # only single-lines seem to exist in svn info output
-       while (<$info_fh>) {
-               chomp $_;
-               if (m#^([^:]+)\s*:\s*(\S.*)$#) {
-                       $ret->{$1} = $2;
-                       push @{$ret->{-order}}, $1;
-               }
-       }
-       close $info_fh or croak $?;
-       return $ret;
-}
-
-sub sys { system(@_) == 0 or croak $? }
-
-sub eol_cp {
-       my ($from, $to) = @_;
-       my $es = svn_propget_base('svn:eol-style', $to);
-       open my $rfd, '<', $from or croak $!;
-       binmode $rfd or croak $!;
-       open my $wfd, '>', $to or croak $!;
-       binmode $wfd or croak $!;
-       eol_cp_fd($rfd, $wfd, $es);
-       close $rfd or croak $!;
-       close $wfd or croak $!;
-}
-
-sub eol_cp_fd {
-       my ($rfd, $wfd, $es) = @_;
-       my $eol = defined $es ? $EOL{$es} : undef;
-       my $buf;
-       use bytes;
-       while (1) {
-               my ($r, $w, $t);
-               defined($r = sysread($rfd, $buf, 4096)) or croak $!;
-               return unless $r;
-               if ($eol) {
-                       if ($buf =~ /\015$/) {
-                               my $c;
-                               defined($r = sysread($rfd,$c,1)) or croak $!;
-                               $buf .= $c if $r > 0;
-                       }
-                       $buf =~ s/(?:\015\012|\015|\012)/$eol/gs;
-                       $r = length($buf);
-               }
-               for ($w = 0; $w < $r; $w += $t) {
-                       $t = syswrite($wfd, $buf, $r - $w, $w) or croak $!;
-               }
-       }
-       no bytes;
-}
-
-sub do_update_index {
-       my ($z_cmd, $cmd, $no_text_base) = @_;
-
-       my $z = open my $p, '-|';
-       defined $z or croak $!;
-       unless ($z) { exec @$z_cmd or croak $! }
-
-       my $pid = open my $ui, '|-';
-       defined $pid or croak $!;
-       unless ($pid) {
-               exec('git-update-index',"--$cmd",'-z','--stdin') or croak $!;
-       }
-       local $/ = "\0";
-       while (my $x = <$p>) {
-               chomp $x;
-               if (!$no_text_base && lstat $x && ! -l _ &&
-                               svn_propget_base('svn:keywords', $x)) {
-                       my $mode = -x _ ? 0755 : 0644;
-                       my ($v,$d,$f) = File::Spec->splitpath($x);
-                       my $tb = File::Spec->catfile($d, '.svn', 'tmp',
-                                               'text-base',"$f.svn-base");
-                       $tb =~ s#^/##;
-                       unless (-f $tb) {
-                               $tb = File::Spec->catfile($d, '.svn',
-                                               'text-base',"$f.svn-base");
-                               $tb =~ s#^/##;
-                       }
-                       unlink $x or croak $!;
-                       eol_cp($tb, $x);
-                       chmod(($mode &~ umask), $x) or croak $!;
-               }
-               print $ui $x,"\0";
-       }
-       close $ui or croak $?;
-}
-
-sub index_changes {
-       return if $_use_lib;
-
-       if (!-f "$GIT_SVN_DIR/info/exclude") {
-               open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!;
-               print $fd '.svn',"\n";
-               close $fd or croak $!;
-       }
-       my $no_text_base = shift;
-       do_update_index([qw/git-diff-files --name-only -z/],
-                       'remove',
-                       $no_text_base);
-       do_update_index([qw/git-ls-files -z --others/,
-                               "--exclude-from=$GIT_SVN_DIR/info/exclude"],
-                       'add',
-                       $no_text_base);
-}
-
-sub s_to_file {
-       my ($str, $file, $mode) = @_;
-       open my $fd,'>',$file or croak $!;
-       print $fd $str,"\n" or croak $!;
-       close $fd or croak $!;
-       chmod ($mode &~ umask, $file) if (defined $mode);
-}
-
-sub file_to_s {
-       my $file = shift;
-       open my $fd,'<',$file or croak "$!: file: $file\n";
-       local $/;
-       my $ret = <$fd>;
-       close $fd or croak $!;
-       $ret =~ s/\s*$//s;
-       return $ret;
-}
-
-sub assert_revision_unknown {
-       my $r = shift;
-       if (my $c = revdb_get($REVDB, $r)) {
-               croak "$r = $c already exists! Why are we refetching it?";
-       }
-}
-
-sub trees_eq {
-       my ($x, $y) = @_;
-       my @x = safe_qx('git-cat-file','commit',$x);
-       my @y = safe_qx('git-cat-file','commit',$y);
-       if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/
-                               || $y[0] !~ /^tree $sha1\n$/) {
-               print STDERR "Trees not equal: $y[0] != $x[0]\n";
-               return 0
-       }
-       return 1;
-}
-
-sub git_commit {
-       my ($log_msg, @parents) = @_;
-       assert_revision_unknown($log_msg->{revision});
-       map_tree_joins() if (@_branch_from && !%tree_map);
-
-       my (@tmp_parents, @exec_parents, %seen_parent);
-       if (my $lparents = $log_msg->{parents}) {
-               @tmp_parents = @$lparents
-       }
-       # commit parents can be conditionally bound to a particular
-       # svn revision via: "svn_revno=commit_sha1", filter them out here:
-       foreach my $p (@parents) {
-               next unless defined $p;
-               if ($p =~ /^(\d+)=($sha1_short)$/o) {
-                       if ($1 == $log_msg->{revision}) {
-                               push @tmp_parents, $2;
-                       }
-               } else {
-                       push @tmp_parents, $p if $p =~ /$sha1_short/o;
-               }
-       }
-       my $tree = $log_msg->{tree};
-       if (!defined $tree) {
-               my $index = set_index($GIT_SVN_INDEX);
-               index_changes();
-               chomp($tree = `git-write-tree`);
-               croak $? if $?;
-               restore_index($index);
-       }
-
-       # just in case we clobber the existing ref, we still want that ref
-       # as our parent:
-       if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) {
-               push @tmp_parents, $cur;
-       }
-
-       if (exists $tree_map{$tree}) {
-               foreach my $p (@{$tree_map{$tree}}) {
-                       my $skip;
-                       foreach (@tmp_parents) {
-                               # see if a common parent is found
-                               my $mb = eval {
-                                       safe_qx('git-merge-base', $_, $p)
-                               };
-                               next if ($@ || $?);
-                               $skip = 1;
-                               last;
-                       }
-                       next if $skip;
-                       my ($url_p, $r_p, $uuid_p) = cmt_metadata($p);
-                       next if (($SVN_UUID eq $uuid_p) &&
-                                               ($log_msg->{revision} > $r_p));
-                       next if (defined $url_p && defined $SVN_URL &&
-                                               ($SVN_UUID eq $uuid_p) &&
-                                               ($url_p eq $SVN_URL));
-                       push @tmp_parents, $p;
-               }
-       }
-       foreach (@tmp_parents) {
-               next if $seen_parent{$_};
-               $seen_parent{$_} = 1;
-               push @exec_parents, $_;
-               # MAXPARENT is defined to 16 in commit-tree.c:
-               last if @exec_parents > 16;
-       }
-
-       set_commit_env($log_msg);
-       my @exec = ('git-commit-tree', $tree);
-       push @exec, '-p', $_  foreach @exec_parents;
-       defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
-                                                               or croak $!;
-       print $msg_fh $log_msg->{msg} or croak $!;
-       unless ($_no_metadata) {
-               print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
-                                       " $SVN_UUID\n" or croak $!;
-       }
-       $msg_fh->flush == 0 or croak $!;
-       close $msg_fh or croak $!;
-       chomp(my $commit = do { local $/; <$out_fh> });
-       close $out_fh or croak $!;
-       waitpid $pid, 0;
-       croak $? if $?;
-       if ($commit !~ /^$sha1$/o) {
-               die "Failed to commit, invalid sha1: $commit\n";
-       }
-       sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
-       revdb_set($REVDB, $log_msg->{revision}, $commit);
-
-       # this output is read via pipe, do not change:
-       print "r$log_msg->{revision} = $commit\n";
-       check_repack();
-       return $commit;
-}
-
-sub check_repack {
-       if ($_repack && (--$_repack_nr == 0)) {
-               $_repack_nr = $_repack;
-               sys("git repack $_repack_flags");
-       }
-}
-
-sub set_commit_env {
-       my ($log_msg) = @_;
-       my $author = $log_msg->{author};
-       if (!defined $author || length $author == 0) {
-               $author = '(no author)';
-       }
-       my ($name,$email) = defined $users{$author} ?  @{$users{$author}}
-                               : ($author,"$author\@$SVN_UUID");
-       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
-       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
-       $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
-}
-
-sub apply_mod_line_blob {
-       my $m = shift;
-       if ($m->{mode_b} =~ /^120/) {
-               blob_to_symlink($m->{sha1_b}, $m->{file_b});
-       } else {
-               blob_to_file($m->{sha1_b}, $m->{file_b});
-       }
-}
-
-sub blob_to_symlink {
-       my ($blob, $link) = @_;
-       defined $link or croak "\$link not defined!\n";
-       croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
-       if (-l $link || -f _) {
-               unlink $link or croak $!;
-       }
-
-       my $dest = `git-cat-file blob $blob`; # no newline, so no chomp
-       symlink $dest, $link or croak $!;
-}
-
-sub blob_to_file {
-       my ($blob, $file) = @_;
-       defined $file or croak "\$file not defined!\n";
-       croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
-       if (-l $file || -f _) {
-               unlink $file or croak $!;
-       }
-
-       open my $blob_fh, '>', $file or croak "$!: $file\n";
-       my $pid = fork;
-       defined $pid or croak $!;
-
-       if ($pid == 0) {
-               open STDOUT, '>&', $blob_fh or croak $!;
-               exec('git-cat-file','blob',$blob) or croak $!;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-
-       close $blob_fh or croak $!;
-}
-
-sub safe_qx {
-       my $pid = open my $child, '-|';
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               exec(@_) or croak $!;
-       }
-       my @ret = (<$child>);
-       close $child or croak $?;
-       die $? if $?; # just in case close didn't error out
-       return wantarray ? @ret : join('',@ret);
-}
-
-sub svn_compat_check {
-       if ($_follow_parent) {
-               print STDERR 'E: --follow-parent functionality is only ',
-                               "available when SVN libraries are used\n";
-               exit 1;
-       }
-       my @co_help = safe_qx(qw(svn co -h));
-       unless (grep /ignore-externals/,@co_help) {
-               print STDERR "W: Installed svn version does not support ",
-                               "--ignore-externals\n";
-               $_no_ignore_ext = 1;
-       }
-       if (grep /usage: checkout URL\[\@REV\]/,@co_help) {
-               $_svn_co_url_revs = 1;
-       }
-       if (grep /\[TARGET\[\@REV\]\.\.\.\]/, `svn propget -h`) {
-               $_svn_pg_peg_revs = 1;
-       }
-
-       # I really, really hope nobody hits this...
-       unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) {
-               print STDERR <<'';
-W: The installed svn version does not support the --stop-on-copy flag in
-   the log command.
-   Lets hope the directory you're tracking is not a branch or tag
-   and was never moved within the repository...
-
-               $_no_stop_copy = 1;
-       }
-}
-
-# *sigh*, new versions of svn won't honor -r<rev> without URL@<rev>,
-# (and they won't honor URL@<rev> without -r<rev>, too!)
-sub svn_cmd_checkout {
-       my ($url, $rev, $dir) = @_;
-       my @cmd = ('svn','co', "-r$rev");
-       push @cmd, '--ignore-externals' unless $_no_ignore_ext;
-       $url .= "\@$rev" if $_svn_co_url_revs;
-       sys(@cmd, $url, $dir);
-}
-
-sub check_upgrade_needed {
-       if (!-r $REVDB) {
-               -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
-               open my $fh, '>>',$REVDB or croak $!;
-               close $fh;
-       }
-       my $old = eval {
-               my $pid = open my $child, '-|';
-               defined $pid or croak $!;
-               if ($pid == 0) {
-                       close STDERR;
-                       exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $!;
-               }
-               my @ret = (<$child>);
-               close $child or croak $?;
-               die $? if $?; # just in case close didn't error out
-               return wantarray ? @ret : join('',@ret);
-       };
-       return unless $old;
-       my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
-       if ($@ || !$head) {
-               print STDERR "Please run: $0 rebuild --upgrade\n";
-               exit 1;
-       }
-}
-
-# fills %tree_map with a reverse mapping of trees to commits.  Useful
-# for finding parents to commit on.
-sub map_tree_joins {
-       my %seen;
-       foreach my $br (@_branch_from) {
-               my $pid = open my $pipe, '-|';
-               defined $pid or croak $!;
-               if ($pid == 0) {
-                       exec(qw(git-rev-list --topo-order --pretty=raw), $br)
-                                                               or croak $!;
-               }
-               while (<$pipe>) {
-                       if (/^commit ($sha1)$/o) {
-                               my $commit = $1;
-
-                               # if we've seen a commit,
-                               # we've seen its parents
-                               last if $seen{$commit};
-                               my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
-                               unless (defined $tree) {
-                                       die "Failed to parse commit $commit\n";
-                               }
-                               push @{$tree_map{$tree}}, $commit;
-                               $seen{$commit} = 1;
-                       }
-               }
-               close $pipe; # we could be breaking the pipe early
-       }
-}
-
-sub load_all_refs {
-       if (@_branch_from) {
-               print STDERR '--branch|-b parameters are ignored when ',
-                       "--branch-all-refs|-B is passed\n";
-       }
-
-       # don't worry about rev-list on non-commit objects/tags,
-       # it shouldn't blow up if a ref is a blob or tree...
-       chomp(@_branch_from = `git-rev-parse --symbolic --all`);
-}
-
-# '<svn username> = real-name <email address>' mapping based on git-svnimport:
-sub load_authors {
-       open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
-       while (<$authors>) {
-               chomp;
-               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
-               my ($user, $name, $email) = ($1, $2, $3);
-               $users{$user} = [$name, $email];
-       }
-       close $authors or croak $!;
-}
-
-sub rload_authors {
-       open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
-       while (<$authors>) {
-               chomp;
-               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
-               my ($user, $name, $email) = ($1, $2, $3);
-               $rusers{"$name <$email>"} = $user;
-       }
-       close $authors or croak $!;
-}
-
-sub svn_propget_base {
-       my ($p, $f) = @_;
-       $f .= '@BASE' if $_svn_pg_peg_revs;
-       return safe_qx(qw/svn propget/, $p, $f);
-}
-
-sub git_svn_each {
-       my $sub = shift;
-       foreach (`git-rev-parse --symbolic --all`) {
-               next unless s#^refs/remotes/##;
-               chomp $_;
-               next unless -f "$GIT_DIR/svn/$_/info/url";
-               &$sub($_);
-       }
-}
-
-sub migrate_revdb {
-       git_svn_each(sub {
-               my $id = shift;
-               defined(my $pid = fork) or croak $!;
-               if (!$pid) {
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-                       init_vars();
-                       exit 0 if -r $REVDB;
-                       print "Upgrading svn => git mapping...\n";
-                       -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
-                       open my $fh, '>>',$REVDB or croak $!;
-                       close $fh;
-                       rebuild();
-                       print "Done upgrading. You may now delete the ",
-                               "deprecated $GIT_SVN_DIR/revs directory\n";
-                       exit 0;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
-       });
-}
-
-sub migration_check {
-       migrate_revdb() unless (-e $REVDB);
-       return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR);
-       print "Upgrading repository...\n";
-       unless (-d "$GIT_DIR/svn") {
-               mkdir "$GIT_DIR/svn" or croak $!;
-       }
-       print "Data from a previous version of git-svn exists, but\n\t",
-                               "$GIT_SVN_DIR\n\t(required for this version ",
-                               "($VERSION) of git-svn) does not.\n";
-
-       foreach my $x (`git-rev-parse --symbolic --all`) {
-               next unless $x =~ s#^refs/remotes/##;
-               chomp $x;
-               next unless -f "$GIT_DIR/$x/info/url";
-               my $u = eval { file_to_s("$GIT_DIR/$x/info/url") };
-               next unless $u;
-               my $dn = dirname("$GIT_DIR/svn/$x");
-               mkpath([$dn]) unless -d $dn;
-               rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x";
-       }
-       migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);
-       print "Done upgrading.\n";
-}
-
-sub find_rev_before {
-       my ($r, $id, $eq_ok) = @_;
-       my $f = "$GIT_DIR/svn/$id/.rev_db";
-       return (undef,undef) unless -r $f;
-       --$r unless $eq_ok;
-       while ($r > 0) {
-               if (my $c = revdb_get($f, $r)) {
-                       return ($r, $c);
-               }
-               --$r;
-       }
-       return (undef, undef);
-}
-
-sub init_vars {
-       $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
-       $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN";
-       $REVDB = "$GIT_SVN_DIR/.rev_db";
-       $GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
-       $SVN_URL = undef;
-       $SVN_WC = "$GIT_SVN_DIR/tree";
-       %tree_map = ();
-}
-
-# convert GetOpt::Long specs for use by git-repo-config
-sub read_repo_config {
-       return unless -d $GIT_DIR;
-       my $opts = shift;
-       foreach my $o (keys %$opts) {
-               my $v = $opts->{$o};
-               my ($key) = ($o =~ /^([a-z\-]+)/);
-               $key =~ s/-//g;
-               my $arg = 'git-repo-config';
-               $arg .= ' --int' if ($o =~ /[:=]i$/);
-               $arg .= ' --bool' if ($o !~ /[:=][sfi]$/);
-               if (ref $v eq 'ARRAY') {
-                       chomp(my @tmp = `$arg --get-all svn.$key`);
-                       @$v = @tmp if @tmp;
-               } else {
-                       chomp(my $tmp = `$arg --get svn.$key`);
-                       if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {
-                               $$v = $tmp;
-                       }
-               }
-       }
-}
-
-sub set_default_vals {
-       if (defined $_repack) {
-               $_repack = 1000 if ($_repack <= 0);
-               $_repack_nr = $_repack;
-               $_repack_flags ||= '-d';
-       }
-}
-
-sub read_grafts {
-       my $gr_file = shift;
-       my ($grafts, $comments) = ({}, {});
-       if (open my $fh, '<', $gr_file) {
-               my @tmp;
-               while (<$fh>) {
-                       if (/^($sha1)\s+/) {
-                               my $c = $1;
-                               if (@tmp) {
-                                       @{$comments->{$c}} = @tmp;
-                                       @tmp = ();
-                               }
-                               foreach my $p (split /\s+/, $_) {
-                                       $grafts->{$c}->{$p} = 1;
-                               }
-                       } else {
-                               push @tmp, $_;
-                       }
-               }
-               close $fh or croak $!;
-               @{$comments->{'END'}} = @tmp if @tmp;
-       }
-       return ($grafts, $comments);
-}
-
-sub write_grafts {
-       my ($grafts, $comments, $gr_file) = @_;
-
-       open my $fh, '>', $gr_file or croak $!;
-       foreach my $c (sort keys %$grafts) {
-               if ($comments->{$c}) {
-                       print $fh $_ foreach @{$comments->{$c}};
-               }
-               my $p = $grafts->{$c};
-               my %x; # real parents
-               delete $p->{$c}; # commits are not self-reproducing...
-               my $pid = open my $ch, '-|';
-               defined $pid or croak $!;
-               if (!$pid) {
-                       exec(qw/git-cat-file commit/, $c) or croak $!;
-               }
-               while (<$ch>) {
-                       if (/^parent ($sha1)/) {
-                               $x{$1} = $p->{$1} = 1;
-                       } else {
-                               last unless /^\S/;
-                       }
-               }
-               close $ch; # breaking the pipe
-
-               # if real parents are the only ones in the grafts, drop it
-               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
-
-               my (@ip, @jp, $mb);
-               my %del = %x;
-               @ip = @jp = keys %$p;
-               foreach my $i (@ip) {
-                       next if $del{$i} || $p->{$i} == 2;
-                       foreach my $j (@jp) {
-                               next if $i eq $j || $del{$j} || $p->{$j} == 2;
-                               $mb = eval { safe_qx('git-merge-base',$i,$j) };
-                               next unless $mb;
-                               chomp $mb;
-                               next if $x{$mb};
-                               if ($mb eq $j) {
-                                       delete $p->{$i};
-                                       $del{$i} = 1;
-                               } elsif ($mb eq $i) {
-                                       delete $p->{$j};
-                                       $del{$j} = 1;
-                               }
-                       }
-               }
-
-               # if real parents are the only ones in the grafts, drop it
-               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
-
-               print $fh $c, ' ', join(' ', sort keys %$p),"\n";
-       }
-       if ($comments->{'END'}) {
-               print $fh $_ foreach @{$comments->{'END'}};
-       }
-       close $fh or croak $!;
-}
-
-sub read_url_paths_all {
-       my ($l_map, $pfx, $p) = @_;
-       my @dir;
-       foreach (<$p/*>) {
-               if (-r "$_/info/url") {
-                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
-                       my $id = $pfx . basename $_;
-                       my $url = file_to_s("$_/info/url");
-                       my ($u, $p) = repo_path_split($url);
-                       $l_map->{$u}->{$p} = $id;
-               } elsif (-d $_) {
-                       push @dir, $_;
-               }
-       }
-       foreach (@dir) {
-               my $x = $_;
-               $x =~ s!^\Q$GIT_DIR\E/svn/!!o;
-               read_url_paths_all($l_map, $x, $_);
-       }
-}
-
-# this one only gets ids that have been imported, not new ones
-sub read_url_paths {
-       my $l_map = {};
-       git_svn_each(sub { my $x = shift;
-                       my $url = file_to_s("$GIT_DIR/svn/$x/info/url");
-                       my ($u, $p) = repo_path_split($url);
-                       $l_map->{$u}->{$p} = $x;
-                       });
-       return $l_map;
-}
-
-sub extract_metadata {
-       my $id = shift or return (undef, undef, undef);
-       my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
-                                                       \s([a-f\d\-]+)$/x);
-       if (!$rev || !$uuid || !$url) {
-               # some of the original repositories I made had
-               # indentifiers like this:
-               ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
-       }
-       return ($url, $rev, $uuid);
-}
-
-sub cmt_metadata {
-       return extract_metadata((grep(/^git-svn-id: /,
-               safe_qx(qw/git-cat-file commit/, shift)))[-1]);
-}
-
-sub get_commit_time {
-       my $cmt = shift;
-       defined(my $pid = open my $fh, '-|') or croak $!;
-       if (!$pid) {
-               exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!;
-       }
-       while (<$fh>) {
-               /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
-               my ($s, $tz) = ($1, $2);
-               if ($tz =~ s/^\+//) {
-                       $s += tz_to_s_offset($tz);
-               } elsif ($tz =~ s/^\-//) {
-                       $s -= tz_to_s_offset($tz);
-               }
-               close $fh;
-               return $s;
-       }
-       die "Can't get commit time for commit: $cmt\n";
-}
-
-sub tz_to_s_offset {
-       my ($tz) = @_;
-       $tz =~ s/(\d\d)$//;
-       return ($1 * 60) + ($tz * 3600);
-}
-
-sub setup_pager { # translated to Perl from pager.c
-       return unless (-t *STDOUT);
-       my $pager = $ENV{PAGER};
-       if (!defined $pager) {
-               $pager = 'less';
-       } elsif (length $pager == 0 || $pager eq 'cat') {
-               return;
-       }
-       pipe my $rfd, my $wfd or return;
-       defined(my $pid = fork) or croak $!;
-       if (!$pid) {
-               open STDOUT, '>&', $wfd or croak $!;
-               return;
-       }
-       open STDIN, '<&', $rfd or croak $!;
-       $ENV{LESS} ||= '-S';
-       exec $pager or croak "Can't run pager: $!\n";;
-}
-
-sub get_author_info {
-       my ($dest, $author, $t, $tz) = @_;
-       $author =~ s/(?:^\s*|\s*$)//g;
-       $dest->{a_raw} = $author;
-       my $_a;
-       if ($_authors) {
-               $_a = $rusers{$author} || undef;
-       }
-       if (!$_a) {
-               ($_a) = ($author =~ /<([^>]+)\@[^>]+>$/);
-       }
-       $dest->{t} = $t;
-       $dest->{tz} = $tz;
-       $dest->{a} = $_a;
-       # Date::Parse isn't in the standard Perl distro :(
-       if ($tz =~ s/^\+//) {
-               $t += tz_to_s_offset($tz);
-       } elsif ($tz =~ s/^\-//) {
-               $t -= tz_to_s_offset($tz);
-       }
-       $dest->{t_utc} = $t;
-}
-
-sub process_commit {
-       my ($c, $r_min, $r_max, $defer) = @_;
-       if (defined $r_min && defined $r_max) {
-               if ($r_min == $c->{r} && $r_min == $r_max) {
-                       show_commit($c);
-                       return 0;
-               }
-               return 1 if $r_min == $r_max;
-               if ($r_min < $r_max) {
-                       # we need to reverse the print order
-                       return 0 if (defined $_limit && --$_limit < 0);
-                       push @$defer, $c;
-                       return 1;
-               }
-               if ($r_min != $r_max) {
-                       return 1 if ($r_min < $c->{r});
-                       return 1 if ($r_max > $c->{r});
-               }
-       }
-       return 0 if (defined $_limit && --$_limit < 0);
-       show_commit($c);
-       return 1;
-}
-
-sub show_commit {
-       my $c = shift;
-       if ($_oneline) {
-               my $x = "\n";
-               if (my $l = $c->{l}) {
-                       while ($l->[0] =~ /^\s*$/) { shift @$l }
-                       $x = $l->[0];
-               }
-               $_l_fmt ||= 'A' . length($c->{r});
-               print 'r',pack($_l_fmt, $c->{r}),' | ';
-               print "$c->{c} | " if $_show_commit;
-               print $x;
-       } else {
-               show_commit_normal($c);
-       }
-}
-
-sub show_commit_normal {
-       my ($c) = @_;
-       print '-' x72, "\nr$c->{r} | ";
-       print "$c->{c} | " if $_show_commit;
-       print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
-                                localtime($c->{t_utc})), ' | ';
-       my $nr_line = 0;
-
-       if (my $l = $c->{l}) {
-               while ($l->[$#$l] eq "\n" && $l->[($#$l - 1)] eq "\n") {
-                       pop @$l;
-               }
-               $nr_line = scalar @$l;
-               if (!$nr_line) {
-                       print "1 line\n\n\n";
-               } else {
-                       if ($nr_line == 1) {
-                               $nr_line = '1 line';
-                       } else {
-                               $nr_line .= ' lines';
-                       }
-                       print $nr_line, "\n\n";
-                       print $_ foreach @$l;
-               }
-       } else {
-               print "1 line\n\n";
-
-       }
-       foreach my $x (qw/raw diff/) {
-               if ($c->{$x}) {
-                       print "\n";
-                       print $_ foreach @{$c->{$x}}
-               }
-       }
-}
-
-sub libsvn_load {
-       return unless $_use_lib;
-       $_use_lib = eval {
-               require SVN::Core;
-               if ($SVN::Core::VERSION lt '1.1.0') {
-                       die "Need SVN::Core 1.1.0 or better ",
-                                       "(got $SVN::Core::VERSION) ",
-                                       "Falling back to command-line svn\n";
-               }
-               require SVN::Ra;
-               require SVN::Delta;
-               push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
-               my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
-                                       $SVN::Node::dir.$SVN::Node::unknown.
-                                       $SVN::Node::none.$SVN::Node::file.
-                                       $SVN::Node::dir.$SVN::Node::unknown;
-               1;
-       };
-}
-
-sub libsvn_connect {
-       my ($url) = @_;
-       my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
-                         SVN::Client::get_ssl_server_trust_file_provider(),
-                         SVN::Client::get_username_provider()]);
-       my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
-       return $s;
-}
-
-sub libsvn_get_file {
-       my ($gui, $f, $rev) = @_;
-       my $p = $f;
-       return unless ($p =~ s#^\Q$SVN_PATH\E/?##);
-
-       my ($hash, $pid, $in, $out);
-       my $pool = SVN::Pool->new;
-       defined($pid = open3($in, $out, '>&STDERR',
-                               qw/git-hash-object -w --stdin/)) or croak $!;
-       # redirect STDOUT for SVN 1.1.x compatibility
-       open my $stdout, '>&', \*STDOUT or croak $!;
-       open STDOUT, '>&', $in or croak $!;
-       my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool);
-       $in->flush == 0 or croak $!;
-       open STDOUT, '>&', $stdout or croak $!;
-       close $in or croak $!;
-       close $stdout or croak $!;
-       $pool->clear;
-       chomp($hash = do { local $/; <$out> });
-       close $out or croak $!;
-       waitpid $pid, 0;
-       $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
-
-       my $mode = exists $props->{'svn:executable'} ? '100755' : '100644';
-       if (exists $props->{'svn:special'}) {
-               $mode = '120000';
-               my $link = `git-cat-file blob $hash`;
-               $link =~ s/^link // or die "svn:special file with contents: <",
-                                               $link, "> is not understood\n";
-               defined($pid = open3($in, $out, '>&STDERR',
-                               qw/git-hash-object -w --stdin/)) or croak $!;
-               print $in $link;
-               $in->flush == 0 or croak $!;
-               close $in or croak $!;
-               chomp($hash = do { local $/; <$out> });
-               close $out or croak $!;
-               waitpid $pid, 0;
-               $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
-       }
-       print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!;
-}
-
-sub libsvn_log_entry {
-       my ($rev, $author, $date, $msg, $parents) = @_;
-       my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
-                                        (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x)
-                               or die "Unable to parse date: $date\n";
-       if (defined $_authors && ! defined $users{$author}) {
-               die "Author: $author not defined in $_authors file\n";
-       }
-       return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
-               author => $author, msg => $msg."\n", parents => $parents || [] }
-}
-
-sub process_rm {
-       my ($gui, $last_commit, $f) = @_;
-       $f =~ s#^\Q$SVN_PATH\E/?## or return;
-       # remove entire directories.
-       if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
-               defined(my $pid = open my $ls, '-|') or croak $!;
-               if (!$pid) {
-                       exec(qw/git-ls-tree -r --name-only -z/,
-                               $last_commit,'--',$f) or croak $!;
-               }
-               local $/ = "\0";
-               while (<$ls>) {
-                       print $gui '0 ',0 x 40,"\t",$_ or croak $!;
-               }
-               close $ls or croak $?;
-       } else {
-               print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
-       }
-}
-
-sub libsvn_fetch {
-       my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
-       open my $gui, '| git-update-index -z --index-info' or croak $!;
-       my @amr;
-       foreach my $f (keys %$paths) {
-               my $m = $paths->{$f}->action();
-               $f =~ s#^/+##;
-               if ($m =~ /^[DR]$/) {
-                       print "\t$m\t$f\n" unless $_q;
-                       process_rm($gui, $last_commit, $f);
-                       next if $m eq 'D';
-                       # 'R' can be file replacements, too, right?
-               }
-               my $pool = SVN::Pool->new;
-               my $t = $SVN->check_path($f, $rev, $pool);
-               if ($t == $SVN::Node::file) {
-                       if ($m =~ /^[AMR]$/) {
-                               push @amr, [ $m, $f ];
-                       } else {
-                               die "Unrecognized action: $m, ($f r$rev)\n";
-                       }
-               }
-               $pool->clear;
-       }
-       foreach (@amr) {
-               print "\t$_->[0]\t$_->[1]\n" unless $_q;
-               libsvn_get_file($gui, $_->[1], $rev)
-       }
-       close $gui or croak $?;
-       return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
-}
-
-sub svn_grab_base_rev {
-       defined(my $pid = open my $fh, '-|') or croak $!;
-       if (!$pid) {
-               open my $null, '>', '/dev/null' or croak $!;
-               open STDERR, '>&', $null or croak $!;
-               exec qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0"
-                                                               or croak $!;
-       }
-       chomp(my $c = do { local $/; <$fh> });
-       close $fh;
-       if (defined $c && length $c) {
-               my ($url, $rev, $uuid) = cmt_metadata($c);
-               return ($rev, $c) if defined $rev;
-       }
-       if ($_no_metadata) {
-               my $offset = -41; # from tail
-               my $rl;
-               open my $fh, '<', $REVDB or
-                       die "--no-metadata specified and $REVDB not readable\n";
-               seek $fh, $offset, 2;
-               $rl = readline $fh;
-               defined $rl or return (undef, undef);
-               chomp $rl;
-               while ($c ne $rl && tell $fh != 0) {
-                       $offset -= 41;
-                       seek $fh, $offset, 2;
-                       $rl = readline $fh;
-                       defined $rl or return (undef, undef);
-                       chomp $rl;
-               }
-               my $rev = tell $fh;
-               croak $! if ($rev < -1);
-               $rev =  ($rev - 41) / 41;
-               close $fh or croak $!;
-               return ($rev, $c);
-       }
-       return (undef, undef);
-}
-
-sub libsvn_parse_revision {
-       my $base = shift;
-       my $head = $SVN->get_latest_revnum();
-       if (!defined $_revision || $_revision eq 'BASE:HEAD') {
-               return ($base + 1, $head) if (defined $base);
-               return (0, $head);
-       }
-       return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
-       return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
-       if ($_revision =~ /^BASE:(\d+)$/) {
-               return ($base + 1, $1) if (defined $base);
-               return (0, $head);
-       }
-       return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/);
-       die "revision argument: $_revision not understood by git-svn\n",
-               "Try using the command-line svn client instead\n";
-}
-
-sub libsvn_traverse {
-       my ($gui, $pfx, $path, $rev) = @_;
-       my $cwd = "$pfx/$path";
-       my $pool = SVN::Pool->new;
-       $cwd =~ s#^/+##g;
-       my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);
-       foreach my $d (keys %$dirent) {
-               my $t = $dirent->{$d}->kind;
-               if ($t == $SVN::Node::dir) {
-                       libsvn_traverse($gui, $cwd, $d, $rev);
-               } elsif ($t == $SVN::Node::file) {
-                       print "\tA\t$cwd/$d\n" unless $_q;
-                       libsvn_get_file($gui, "$cwd/$d", $rev);
-               }
-       }
-       $pool->clear;
-}
-
-sub libsvn_traverse_ignore {
-       my ($fh, $path, $r) = @_;
-       $path =~ s#^/+##g;
-       my $pool = SVN::Pool->new;
-       my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
-       my $p = $path;
-       $p =~ s#^\Q$SVN_PATH\E/?##;
-       print $fh length $p ? "\n# $p\n" : "\n# /\n";
-       if (my $s = $props->{'svn:ignore'}) {
-               $s =~ s/[\r\n]+/\n/g;
-               chomp $s;
-               if (length $p == 0) {
-                       $s =~ s#\n#\n/$p#g;
-                       print $fh "/$s\n";
-               } else {
-                       $s =~ s#\n#\n/$p/#g;
-                       print $fh "/$p/$s\n";
-               }
-       }
-       foreach (sort keys %$dirent) {
-               next if $dirent->{$_}->kind != $SVN::Node::dir;
-               libsvn_traverse_ignore($fh, "$path/$_", $r);
-       }
-       $pool->clear;
-}
-
-sub revisions_eq {
-       my ($path, $r0, $r1) = @_;
-       return 1 if $r0 == $r1;
-       my $nr = 0;
-       if ($_use_lib) {
-               # should be OK to use Pool here (r1 - r0) should be small
-               my $pool = SVN::Pool->new;
-               libsvn_get_log($SVN, "/$path", $r0, $r1,
-                               0, 1, 1, sub {$nr++}, $pool);
-               $pool->clear;
-       } else {
-               my ($url, undef) = repo_path_split($SVN_URL);
-               my $svn_log = svn_log_raw("$url/$path","-r$r0:$r1");
-               while (next_log_entry($svn_log)) { $nr++ }
-               close $svn_log->{fh};
-       }
-       return 0 if ($nr > 1);
-       return 1;
-}
-
-sub libsvn_find_parent_branch {
-       my ($paths, $rev, $author, $date, $msg) = @_;
-       my $svn_path = '/'.$SVN_PATH;
-
-       # look for a parent from another branch:
-       my $i = $paths->{$svn_path} or return;
-       my $branch_from = $i->copyfrom_path or return;
-       my $r = $i->copyfrom_rev;
-       print STDERR  "Found possible branch point: ",
-                               "$branch_from => $svn_path, $r\n";
-       $branch_from =~ s#^/##;
-       my $l_map = {};
-       read_url_paths_all($l_map, '', "$GIT_DIR/svn");
-       my $url = $SVN->{url};
-       defined $l_map->{$url} or return;
-       my $id = $l_map->{$url}->{$branch_from};
-       if (!defined $id && $_follow_parent) {
-               print STDERR "Following parent: $branch_from\@$r\n";
-               # auto create a new branch and follow it
-               $id = basename($branch_from);
-               $id .= '@'.$r if -r "$GIT_DIR/svn/$id";
-               while (-r "$GIT_DIR/svn/$id") {
-                       # just grow a tail if we're not unique enough :x
-                       $id .= '-';
-               }
-       }
-       return unless defined $id;
-
-       my ($r0, $parent) = find_rev_before($r,$id,1);
-       if ($_follow_parent && (!defined $r0 || !defined $parent)) {
-               defined(my $pid = fork) or croak $!;
-               if (!$pid) {
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-                       init_vars();
-                       $SVN_URL = "$url/$branch_from";
-                       $SVN_LOG = $SVN = undef;
-                       setup_git_svn();
-                       # we can't assume SVN_URL exists at r+1:
-                       $_revision = "0:$r";
-                       fetch_lib();
-                       exit 0;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
-               ($r0, $parent) = find_rev_before($r,$id,1);
-       }
-       return unless (defined $r0 && defined $parent);
-       if (revisions_eq($branch_from, $r0, $r)) {
-               unlink $GIT_SVN_INDEX;
-               print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
-               sys(qw/git-read-tree/, $parent);
-               return libsvn_fetch($parent, $paths, $rev,
-                                       $author, $date, $msg);
-       }
-       print STDERR "Nope, branch point not imported or unknown\n";
-       return undef;
-}
-
-sub libsvn_get_log {
-       my ($ra, @args) = @_;
-       if ($SVN::Core::VERSION le '1.2.0') {
-               splice(@args, 3, 1);
-       }
-       $ra->get_log(@args);
-}
-
-sub libsvn_new_tree {
-       if (my $log_entry = libsvn_find_parent_branch(@_)) {
-               return $log_entry;
-       }
-       my ($paths, $rev, $author, $date, $msg) = @_;
-       open my $gui, '| git-update-index -z --index-info' or croak $!;
-       my $pool = SVN::Pool->new;
-       libsvn_traverse($gui, '', $SVN_PATH, $rev, $pool);
-       $pool->clear;
-       close $gui or croak $?;
-       return libsvn_log_entry($rev, $author, $date, $msg);
-}
-
-sub find_graft_path_commit {
-       my ($tree_paths, $p1, $r1) = @_;
-       foreach my $x (keys %$tree_paths) {
-               next unless ($p1 =~ /^\Q$x\E/);
-               my $i = $tree_paths->{$x};
-               my ($r0, $parent) = find_rev_before($r1,$i,1);
-               return $parent if (defined $r0 && $r0 == $r1);
-               print STDERR "r$r1 of $i not imported\n";
-               next;
-       }
-       return undef;
-}
-
-sub find_graft_path_parents {
-       my ($grafts, $tree_paths, $c, $p0, $r0) = @_;
-       foreach my $x (keys %$tree_paths) {
-               next unless ($p0 =~ /^\Q$x\E/);
-               my $i = $tree_paths->{$x};
-               my ($r, $parent) = find_rev_before($r0, $i, 1);
-               if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {
-                       my ($url_b, undef, $uuid_b) = cmt_metadata($c);
-                       my ($url_a, undef, $uuid_a) = cmt_metadata($parent);
-                       next if ($url_a && $url_b && $url_a eq $url_b &&
-                                                       $uuid_b eq $uuid_a);
-                       $grafts->{$c}->{$parent} = 1;
-               }
-       }
-}
-
-sub libsvn_graft_file_copies {
-       my ($grafts, $tree_paths, $path, $paths, $rev) = @_;
-       foreach (keys %$paths) {
-               my $i = $paths->{$_};
-               my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path,
-                                       $i->copyfrom_rev);
-               next unless (defined $p0 && defined $r0);
-
-               my $p1 = $_;
-               $p1 =~ s#^/##;
-               $p0 =~ s#^/##;
-               my $c = find_graft_path_commit($tree_paths, $p1, $rev);
-               next unless $c;
-               find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0);
-       }
-}
-
-sub set_index {
-       my $old = $ENV{GIT_INDEX_FILE};
-       $ENV{GIT_INDEX_FILE} = shift;
-       return $old;
-}
-
-sub restore_index {
-       my ($old) = @_;
-       if (defined $old) {
-               $ENV{GIT_INDEX_FILE} = $old;
-       } else {
-               delete $ENV{GIT_INDEX_FILE};
-       }
-}
-
-sub libsvn_commit_cb {
-       my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_;
-       if ($_optimize_commits && $rev == ($r_last + 1)) {
-               my $log = libsvn_log_entry($rev,$committer,$date,$msg);
-               $log->{tree} = get_tree_from_treeish($c);
-               my $cmt = git_commit($log, $cmt_last, $c);
-               my @diff = safe_qx('git-diff-tree', $cmt, $c);
-               if (@diff) {
-                       print STDERR "Trees differ: $cmt $c\n",
-                                       join('',@diff),"\n";
-                       exit 1;
-               }
-       } else {
-               fetch("$rev=$c");
-       }
-}
-
-sub libsvn_ls_fullurl {
-       my $fullurl = shift;
-       my ($repo, $path) = repo_path_split($fullurl);
-       $SVN ||= libsvn_connect($repo);
-       my @ret;
-       my $pool = SVN::Pool->new;
-       my ($dirent, undef, undef) = $SVN->get_dir($path,
-                                               $SVN->get_latest_revnum, $pool);
-       foreach my $d (keys %$dirent) {
-               if ($dirent->{$d}->kind == $SVN::Node::dir) {
-                       push @ret, "$d/"; # add '/' for compat with cli svn
-               }
-       }
-       $pool->clear;
-       return @ret;
-}
-
-
-sub libsvn_skip_unknown_revs {
-       my $err = shift;
-       my $errno = $err->apr_err();
-       # Maybe the branch we're tracking didn't
-       # exist when the repo started, so it's
-       # not an error if it doesn't, just continue
-       #
-       # Wonderfully consistent library, eh?
-       # 160013 - svn:// and file://
-       # 175002 - http(s)://
-       #   More codes may be discovered later...
-       if ($errno == 175002 || $errno == 160013) {
-               return;
-       }
-       croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
-};
-
-# Tie::File seems to be prone to offset errors if revisions get sparse,
-# it's not that fast, either.  Tie::File is also not in Perl 5.6.  So
-# one of my favorite modules is out :<  Next up would be one of the DBM
-# modules, but I'm not sure which is most portable...  So I'll just
-# go with something that's plain-text, but still capable of
-# being randomly accessed.  So here's my ultra-simple fixed-width
-# database.  All records are 40 characters + "\n", so it's easy to seek
-# to a revision: (41 * rev) is the byte offset.
-# A record of 40 0s denotes an empty revision.
-# And yes, it's still pretty fast (faster than Tie::File).
-sub revdb_set {
-       my ($file, $rev, $commit) = @_;
-       length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
-       open my $fh, '+<', $file or croak $!;
-       my $offset = $rev * 41;
-       # assume that append is the common case:
-       seek $fh, 0, 2 or croak $!;
-       my $pos = tell $fh;
-       if ($pos < $offset) {
-               print $fh (('0' x 40),"\n") x (($offset - $pos) / 41);
-       }
-       seek $fh, $offset, 0 or croak $!;
-       print $fh $commit,"\n";
-       close $fh or croak $!;
-}
-
-sub revdb_get {
-       my ($file, $rev) = @_;
-       my $ret;
-       my $offset = $rev * 41;
-       open my $fh, '<', $file or croak $!;
-       seek $fh, $offset, 0;
-       if (tell $fh == $offset) {
-               $ret = readline $fh;
-               if (defined $ret) {
-                       chomp $ret;
-                       $ret = undef if ($ret =~ /^0{40}$/);
-               }
-       }
-       close $fh or croak $!;
-       return $ret;
-}
-
-sub copy_remote_ref {
-       my $origin = $_cp_remote ? $_cp_remote : 'origin';
-       my $ref = "refs/remotes/$GIT_SVN";
-       if (safe_qx('git-ls-remote', $origin, $ref)) {
-               sys(qw/git fetch/, $origin, "$ref:$ref");
-       } else {
-               die "Unable to find remote reference: ",
-                               "refs/remotes/$GIT_SVN on $origin\n";
-       }
-}
-
-package SVN::Git::Editor;
-use vars qw/@ISA/;
-use strict;
-use warnings;
-use Carp qw/croak/;
-use IO::File;
-
-sub new {
-       my $class = shift;
-       my $git_svn = shift;
-       my $self = SVN::Delta::Editor->new(@_);
-       bless $self, $class;
-       foreach (qw/svn_path c r ra /) {
-               die "$_ required!\n" unless (defined $git_svn->{$_});
-               $self->{$_} = $git_svn->{$_};
-       }
-       $self->{pool} = SVN::Pool->new;
-       $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };
-       $self->{rm} = { };
-       require Digest::MD5;
-       return $self;
-}
-
-sub split_path {
-       return ($_[0] =~ m#^(.*?)/?([^/]+)$#);
-}
-
-sub repo_path {
-       (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]"
-                                       : $_[0]->{svn_path}
-}
-
-sub url_path {
-       my ($self, $path) = @_;
-       $self->{ra}->{url} . '/' . $self->repo_path($path);
-}
-
-sub rmdirs {
-       my ($self, $q) = @_;
-       my $rm = $self->{rm};
-       delete $rm->{''}; # we never delete the url we're tracking
-       return unless %$rm;
-
-       foreach (keys %$rm) {
-               my @d = split m#/#, $_;
-               my $c = shift @d;
-               $rm->{$c} = 1;
-               while (@d) {
-                       $c .= '/' . shift @d;
-                       $rm->{$c} = 1;
-               }
-       }
-       delete $rm->{$self->{svn_path}};
-       delete $rm->{''}; # we never delete the url we're tracking
-       return unless %$rm;
-
-       defined(my $pid = open my $fh,'-|') or croak $!;
-       if (!$pid) {
-               exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;
-       }
-       local $/ = "\0";
-       my @svn_path = split m#/#, $self->{svn_path};
-       while (<$fh>) {
-               chomp;
-               my @dn = (@svn_path, (split m#/#, $_));
-               while (pop @dn) {
-                       delete $rm->{join '/', @dn};
-               }
-               unless (%$rm) {
-                       close $fh;
-                       return;
-               }
-       }
-       close $fh;
-
-       my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
-       foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
-               $self->close_directory($bat->{$d}, $p);
-               my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
-               print "\tD+\t/$d/\n" unless $q;
-               $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
-               delete $bat->{$d};
-       }
-}
-
-sub open_or_add_dir {
-       my ($self, $full_path, $baton) = @_;
-       my $p = SVN::Pool->new;
-       my $t = $self->{ra}->check_path($full_path, $self->{r}, $p);
-       $p->clear;
-       if ($t == $SVN::Node::none) {
-               return $self->add_directory($full_path, $baton,
-                                               undef, -1, $self->{pool});
-       } elsif ($t == $SVN::Node::dir) {
-               return $self->open_directory($full_path, $baton,
-                                               $self->{r}, $self->{pool});
-       }
-       print STDERR "$full_path already exists in repository at ",
-               "r$self->{r} and it is not a directory (",
-               ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
-       exit 1;
-}
-
-sub ensure_path {
-       my ($self, $path) = @_;
-       my $bat = $self->{bat};
-       $path = $self->repo_path($path);
-       return $bat->{''} unless (length $path);
-       my @p = split m#/+#, $path;
-       my $c = shift @p;
-       $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
-       while (@p) {
-               my $c0 = $c;
-               $c .= '/' . shift @p;
-               $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0});
-       }
-       return $bat->{$c};
-}
-
-sub A {
-       my ($self, $m, $q) = @_;
-       my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
-       my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
-                                       undef, -1);
-       print "\tA\t$m->{file_b}\n" unless $q;
-       $self->chg_file($fbat, $m);
-       $self->close_file($fbat,undef,$self->{pool});
-}
-
-sub C {
-       my ($self, $m, $q) = @_;
-       my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
-       my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
-                               $self->url_path($m->{file_a}), $self->{r});
-       print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q;
-       $self->chg_file($fbat, $m);
-       $self->close_file($fbat,undef,$self->{pool});
-}
-
-sub delete_entry {
-       my ($self, $path, $pbat) = @_;
-       my $rpath = $self->repo_path($path);
-       my ($dir, $file) = split_path($rpath);
-       $self->{rm}->{$dir} = 1;
-       $self->SUPER::delete_entry($rpath, $self->{r}, $pbat, $self->{pool});
-}
-
-sub R {
-       my ($self, $m, $q) = @_;
-       my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
-       my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
-                               $self->url_path($m->{file_a}), $self->{r});
-       print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q;
-       $self->chg_file($fbat, $m);
-       $self->close_file($fbat,undef,$self->{pool});
-
-       ($dir, $file) = split_path($m->{file_a});
-       $pbat = $self->ensure_path($dir);
-       $self->delete_entry($m->{file_a}, $pbat);
-}
-
-sub M {
-       my ($self, $m, $q) = @_;
-       my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
-       my $fbat = $self->open_file($self->repo_path($m->{file_b}),
-                               $pbat,$self->{r},$self->{pool});
-       print "\t$m->{chg}\t$m->{file_b}\n" unless $q;
-       $self->chg_file($fbat, $m);
-       $self->close_file($fbat,undef,$self->{pool});
-}
-
-sub T { shift->M(@_) }
-
-sub change_file_prop {
-       my ($self, $fbat, $pname, $pval) = @_;
-       $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
-}
-
-sub chg_file {
-       my ($self, $fbat, $m) = @_;
-       if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
-               $self->change_file_prop($fbat,'svn:executable','*');
-       } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
-               $self->change_file_prop($fbat,'svn:executable',undef);
-       }
-       my $fh = IO::File->new_tmpfile or croak $!;
-       if ($m->{mode_b} =~ /^120/) {
-               print $fh 'link ' or croak $!;
-               $self->change_file_prop($fbat,'svn:special','*');
-       } elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
-               $self->change_file_prop($fbat,'svn:special',undef);
-       }
-       defined(my $pid = fork) or croak $!;
-       if (!$pid) {
-               open STDOUT, '>&', $fh or croak $!;
-               exec qw/git-cat-file blob/, $m->{sha1_b} or croak $!;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-       $fh->flush == 0 or croak $!;
-       seek $fh, 0, 0 or croak $!;
-
-       my $md5 = Digest::MD5->new;
-       $md5->addfile($fh) or croak $!;
-       seek $fh, 0, 0 or croak $!;
-
-       my $exp = $md5->hexdigest;
-       my $atd = $self->apply_textdelta($fbat, undef, $self->{pool});
-       my $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool});
-       die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
-
-       close $fh or croak $!;
-}
-
-sub D {
-       my ($self, $m, $q) = @_;
-       my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
-       print "\tD\t$m->{file_b}\n" unless $q;
-       $self->delete_entry($m->{file_b}, $pbat);
-}
-
-sub close_edit {
-       my ($self) = @_;
-       my ($p,$bat) = ($self->{pool}, $self->{bat});
-       foreach (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$bat) {
-               $self->close_directory($bat->{$_}, $p);
-       }
-       $self->SUPER::close_edit($p);
-       $p->clear;
-}
-
-sub abort_edit {
-       my ($self) = @_;
-       $self->SUPER::abort_edit($self->{pool});
-       $self->{pool}->clear;
-}
-
-__END__
-
-Data structures:
-
-$svn_log hashref (as returned by svn_log_raw)
-{
-       fh => file handle of the log file,
-       state => state of the log file parser (sep/msg/rev/msg_start...)
-}
-
-$log_msg hashref as returned by next_log_entry($svn_log)
-{
-       msg => 'whitespace-formatted log entry
-',                                             # trailing newline is preserved
-       revision => '8',                        # integer
-       date => '2004-02-24T17:01:44.108345Z',  # commit date
-       author => 'committer name'
-};
-
-
-@mods = array of diff-index line hashes, each element represents one line
-       of diff-index output
-
-diff-index line ($m hash)
-{
-       mode_a => first column of diff-index output, no leading ':',
-       mode_b => second column of diff-index output,
-       sha1_b => sha1sum of the final blob,
-       chg => change type [MCRADT],
-       file_a => original file name of a file (iff chg is 'C' or 'R')
-       file_b => new/current file name of a file (any chg)
-}
-;
-
-# retval of read_url_paths{,_all}();
-$l_map = {
-       # repository root url
-       'https://svn.musicpd.org' => {
-               # repository path               # GIT_SVN_ID
-               'mpd/trunk'             =>      'trunk',
-               'mpd/tags/0.11.5'       =>      'tags/0.11.5',
-       },
-}
-
-Notes:
-       I don't trust the each() function on unless I created %hash myself
-       because the internal iterator may not have started at base.
diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt
deleted file mode 100644 (file)
index f7d3de4..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-git-svn(1)
-==========
-
-NAME
-----
-git-svn - bidirectional operation between a single Subversion branch and git
-
-SYNOPSIS
---------
-'git-svn' <command> [options] [arguments]
-
-DESCRIPTION
------------
-git-svn is a simple conduit for changesets between a single Subversion
-branch and git.
-
-git-svn is not to be confused with git-svnimport.  The were designed
-with very different goals in mind.
-
-git-svn is designed for an individual developer who wants a
-bidirectional flow of changesets between a single branch in Subversion
-and an arbitrary number of branches in git.  git-svnimport is designed
-for read-only operation on repositories that match a particular layout
-(albeit the recommended one by SVN developers).
-
-For importing svn, git-svnimport is potentially more powerful when
-operating on repositories organized under the recommended
-trunk/branch/tags structure, and should be faster, too.
-
-git-svn mostly ignores the very limited view of branching that
-Subversion has.  This allows git-svn to be much easier to use,
-especially on repositories that are not organized in a manner that
-git-svnimport is designed for.
-
-COMMANDS
---------
-init::
-       Creates an empty git repository with additional metadata
-       directories for git-svn.  The Subversion URL must be specified
-       as a command-line argument.
-
-fetch::
-       Fetch unfetched revisions from the Subversion URL we are
-       tracking.  refs/remotes/git-svn will be updated to the
-       latest revision.
-
-       Note: You should never attempt to modify the remotes/git-svn
-       branch outside of git-svn.  Instead, create a branch from
-       remotes/git-svn and work on that branch.  Use the 'commit'
-       command (see below) to write git commits back to
-       remotes/git-svn.
-
-       See 'Additional Fetch Arguments' if you are interested in
-       manually joining branches on commit.
-
-commit::
-       Commit specified commit or tree objects to SVN.  This relies on
-       your imported fetch data being up-to-date.  This makes
-       absolutely no attempts to do patching when committing to SVN, it
-       simply overwrites files with those specified in the tree or
-       commit.  All merging is assumed to have taken place
-       independently of git-svn functions.
-
-rebuild::
-       Not a part of daily usage, but this is a useful command if
-       you've just cloned a repository (using git-clone) that was
-       tracked with git-svn.  Unfortunately, git-clone does not clone
-       git-svn metadata and the svn working tree that git-svn uses for
-       its operations.  This rebuilds the metadata so git-svn can
-       resume fetch operations.  A Subversion URL may be optionally
-       specified at the command-line if the directory/repository you're
-       tracking has moved or changed protocols.
-
-show-ignore::
-       Recursively finds and lists the svn:ignore property on
-       directories.  The output is suitable for appending to
-       the $GIT_DIR/info/exclude file.
-
-OPTIONS
--------
--r <ARG>::
---revision <ARG>::
-       Only used with the 'fetch' command.
-
-       Takes any valid -r<argument> svn would accept and passes it
-       directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax
-       is also supported.  This is passed directly to svn, see svn
-       documentation for more details.
-
-       This can allow you to make partial mirrors when running fetch.
-
--::
---stdin::
-       Only used with the 'commit' command.
-
-       Read a list of commits from stdin and commit them in reverse
-       order.  Only the leading sha1 is read from each line, so
-       git-rev-list --pretty=oneline output can be used.
-
---rmdir::
-       Only used with the 'commit' command.
-
-       Remove directories from the SVN tree if there are no files left
-       behind.  SVN can version empty directories, and they are not
-       removed by default if there are no files left in them.  git
-       cannot version empty directories.  Enabling this flag will make
-       the commit to SVN act like git.
-
-       repo-config key: svn.rmdir
-
--e::
---edit::
-       Only used with the 'commit' command.
-
-       Edit the commit message before committing to SVN.  This is off by
-       default for objects that are commits, and forced on when committing
-       tree objects.
-
-       repo-config key: svn.edit
-
--l<num>::
---find-copies-harder::
-       Both of these are only used with the 'commit' command.
-
-       They are both passed directly to git-diff-tree see
-       git-diff-tree(1) for more information.
-
-       repo-config key: svn.l
-       repo-config key: svn.findcopiesharder
-
--A<filename>::
---authors-file=<filename>::
-
-       Syntax is compatible with the files used by git-svnimport and
-       git-cvsimport:
-
-------------------------------------------------------------------------
-loginname = Joe User <user@example.com>
-------------------------------------------------------------------------
-
-       If this option is specified and git-svn encounters an SVN
-       committer name that does not exist in the authors-file, git-svn
-       will abort operation. The user will then have to add the
-       appropriate entry.  Re-running the previous git-svn command
-       after the authors-file is modified should continue operation.
-
-       repo-config key: svn.authors-file
-
-ADVANCED OPTIONS
-----------------
--b<refname>::
---branch <refname>::
-       Used with 'fetch' or 'commit'.
-
-       This can be used to join arbitrary git branches to remotes/git-svn
-       on new commits where the tree object is equivalent.
-
-       When used with different GIT_SVN_ID values, tags and branches in
-       SVN can be tracked this way, as can some merges where the heads
-       end up having completely equivalent content.  This can even be
-       used to track branches across multiple SVN _repositories_.
-
-       This option may be specified multiple times, once for each
-       branch.
-
-       repo-config key: svn.branch
-
--i<GIT_SVN_ID>::
---id <GIT_SVN_ID>::
-       This sets GIT_SVN_ID (instead of using the environment).  See
-       the section on "Tracking Multiple Repositories or Branches" for
-       more information on using GIT_SVN_ID.
-
-COMPATIBILITY OPTIONS
----------------------
---upgrade::
-       Only used with the 'rebuild' command.
-
-       Run this if you used an old version of git-svn that used
-       "git-svn-HEAD" instead of "remotes/git-svn" as the branch
-       for tracking the remote.
-
---no-ignore-externals::
-       Only used with the 'fetch' and 'rebuild' command.
-
-       By default, git-svn passes --ignore-externals to svn to avoid
-       fetching svn:external trees into git.  Pass this flag to enable
-       externals tracking directly via git.
-
-       Versions of svn that do not support --ignore-externals are
-       automatically detected and this flag will be automatically
-       enabled for them.
-
-       Otherwise, do not enable this flag unless you know what you're
-       doing.
-
-       repo-config key: svn.noignoreexternals
-
-Basic Examples
-~~~~~~~~~~~~~~
-
-Tracking and contributing to an Subversion managed-project:
-
-------------------------------------------------------------------------
-# Initialize a tree (like git init-db):
-       git-svn init http://svn.foo.org/project/trunk
-# Fetch remote revisions:
-       git-svn fetch
-# Create your own branch to hack on:
-       git checkout -b my-branch remotes/git-svn
-# Commit only the git commits you want to SVN:
-       git-svn commit <tree-ish> [<tree-ish_2> ...]
-# Commit all the git commits from my-branch that don't exist in SVN:
-       git-svn commit remotes/git-svn..my-branch
-# Something is committed to SVN, pull the latest into your branch:
-       git-svn fetch && git pull . remotes/git-svn
-# Append svn:ignore settings to the default git exclude file:
-       git-svn show-ignore >> .git/info/exclude
-------------------------------------------------------------------------
-
-DESIGN PHILOSOPHY
------------------
-Merge tracking in Subversion is lacking and doing branched development
-with Subversion is cumbersome as a result.  git-svn completely forgoes
-any automated merge/branch tracking on the Subversion side and leaves it
-entirely up to the user on the git side.  It's simply not worth it to do
-a useful translation when the the original signal is weak.
-
-TRACKING MULTIPLE REPOSITORIES OR BRANCHES
-------------------------------------------
-This is for advanced users, most users should ignore this section.
-
-Because git-svn does not care about relationships between different
-branches or directories in a Subversion repository, git-svn has a simple
-hack to allow it to track an arbitrary number of related _or_ unrelated
-SVN repositories via one git repository.  Simply set the GIT_SVN_ID
-environment variable to a name other other than "git-svn" (the default)
-and git-svn will ignore the contents of the $GIT_DIR/git-svn directory
-and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that
-invocation.  The interface branch will be remotes/$GIT_SVN_ID, instead of
-remotes/git-svn.  Any remotes/$GIT_SVN_ID branch should never be modified
-by the user outside of git-svn commands.
-
-ADDITIONAL FETCH ARGUMENTS
---------------------------
-This is for advanced users, most users should ignore this section.
-
-Unfetched SVN revisions may be imported as children of existing commits
-by specifying additional arguments to 'fetch'.  Additional parents may
-optionally be specified in the form of sha1 hex sums at the
-command-line.  Unfetched SVN revisions may also be tied to particular
-git commits with the following syntax:
-
-       svn_revision_number=git_commit_sha1
-
-This allows you to tie unfetched SVN revision 375 to your current HEAD::
-
-       `git-svn fetch 375=$(git-rev-parse HEAD)`
-
-Advanced Example: Tracking a Reorganized Repository
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you're tracking a directory that has moved, or otherwise been
-branched or tagged off of another directory in the repository and you
-care about the full history of the project, then you can read this
-section.
-
-This is how Yann Dirson tracked the trunk of the ufoai directory when
-the /trunk directory of his repository was moved to /ufoai/trunk and
-he needed to continue tracking /ufoai/trunk where /trunk left off.
-
-------------------------------------------------------------------------
-       # This log message shows when the repository was reorganized:
-       r166 | ydirson | 2006-03-02 01:36:55 +0100 (Thu, 02 Mar 2006) | 1 line
-       Changed paths:
-          D /trunk
-          A /ufoai/trunk (from /trunk:165)
-
-       # First we start tracking the old revisions:
-       GIT_SVN_ID=git-oldsvn git-svn init \
-                       https://svn.sourceforge.net/svnroot/ufoai/trunk
-       GIT_SVN_ID=git-oldsvn git-svn fetch -r1:165
-
-       # And now, we continue tracking the new revisions:
-       GIT_SVN_ID=git-newsvn git-svn init \
-             https://svn.sourceforge.net/svnroot/ufoai/ufoai/trunk
-       GIT_SVN_ID=git-newsvn git-svn fetch \
-             166=`git-rev-parse refs/remotes/git-oldsvn`
-------------------------------------------------------------------------
-
-BUGS
-----
-If somebody commits a conflicting changeset to SVN at a bad moment
-(right before you commit) causing a conflict and your commit to fail,
-your svn working tree ($GIT_DIR/git-svn/tree) may be dirtied.  The
-easiest thing to do is probably just to rm -rf $GIT_DIR/git-svn/tree and
-run 'rebuild'.
-
-We ignore all SVN properties except svn:executable.  Too difficult to
-map them since we rely heavily on git write-tree being _exactly_ the
-same on both the SVN and git working trees and I prefer not to clutter
-working trees with metadata files.
-
-svn:keywords can't be ignored in Subversion (at least I don't know of
-a way to ignore them).
-
-Renamed and copied directories are not detected by git and hence not
-tracked when committing to SVN.  I do not plan on adding support for
-this as it's quite difficult and time-consuming to get working for all
-the possible corner cases (git doesn't do it, either).  Renamed and
-copied files are fully supported if they're similar enough for git to
-detect them.
-
-Author
-------
-Written by Eric Wong <normalperson@yhbt.net>.
-
-Documentation
--------------
-Written by Eric Wong <normalperson@yhbt.net>.
diff --git a/contrib/git-svn/t/lib-git-svn.sh b/contrib/git-svn/t/lib-git-svn.sh
deleted file mode 100644 (file)
index d7f972a..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-PATH=$PWD/../:$PATH
-if test -d ../../../t
-then
-    cd ../../../t
-else
-    echo "Must be run in contrib/git-svn/t" >&2
-    exit 1
-fi
-
-. ./test-lib.sh
-
-GIT_DIR=$PWD/.git
-GIT_SVN_DIR=$GIT_DIR/svn/git-svn
-SVN_TREE=$GIT_SVN_DIR/svn-tree
-
-svnadmin >/dev/null 2>&1
-if test $? != 1
-then
-    test_expect_success 'skipping contrib/git-svn test' :
-    test_done
-    exit
-fi
-
-svn >/dev/null 2>&1
-if test $? != 1
-then
-    test_expect_success 'skipping contrib/git-svn test' :
-    test_done
-    exit
-fi
-
-svnrepo=$PWD/svnrepo
-
-set -e
-
-if svnadmin create --help | grep fs-type >/dev/null
-then
-       svnadmin create --fs-type fsfs "$svnrepo"
-else
-       svnadmin create "$svnrepo"
-fi
-
-svnrepo="file://$svnrepo/test-git-svn"
-
-
diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh
deleted file mode 100644 (file)
index b482bb6..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006 Eric Wong
-#
-
-test_description='git-svn tests'
-GIT_SVN_LC_ALL=$LC_ALL
-
-case "$LC_ALL" in
-*.UTF-8)
-       have_utf8=t
-       ;;
-*)
-       have_utf8=
-       ;;
-esac
-
-. ./lib-git-svn.sh
-
-mkdir import
-cd import
-
-echo foo > foo
-if test -z "$NO_SYMLINK"
-then
-       ln -s foo foo.link
-fi
-mkdir -p dir/a/b/c/d/e
-echo 'deep dir' > dir/a/b/c/d/e/file
-mkdir -p bar
-echo 'zzz' > bar/zzz
-echo '#!/bin/sh' > exec.sh
-chmod +x exec.sh
-svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
-
-cd ..
-rm -rf import
-
-test_expect_success \
-    'initialize git-svn' \
-    "git-svn init $svnrepo"
-
-test_expect_success \
-    'import an SVN revision into git' \
-    'git-svn fetch'
-
-test_expect_success "checkout from svn" "svn co $svnrepo $SVN_TREE"
-
-name='try a deep --rmdir with a commit'
-git checkout -f -b mybranch remotes/git-svn
-mv dir/a/b/c/d/e/file dir/file
-cp dir/file file
-git update-index --add --remove dir/a/b/c/d/e/file dir/file file
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch &&
-     svn up $SVN_TREE &&
-     test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
-
-
-name='detect node change from file to directory #1'
-mkdir dir/new_file
-mv dir/file dir/new_file/file
-mv dir/new_file dir/file
-git update-index --remove dir/file
-git update-index --add dir/file/file
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \
-    || true
-
-
-name='detect node change from directory to file #1'
-rm -rf dir $GIT_DIR/index
-git checkout -f -b mybranch2 remotes/git-svn
-mv bar/zzz zzz
-rm -rf bar
-mv zzz bar
-git update-index --remove -- bar/zzz
-git update-index --add -- bar
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
-    || true
-
-
-name='detect node change from file to directory #2'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch3 remotes/git-svn
-rm bar/zzz
-git-update-index --remove bar/zzz
-mkdir bar/zzz
-echo yyy > bar/zzz/yyy
-git-update-index --add bar/zzz/yyy
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
-    || true
-
-
-name='detect node change from directory to file #2'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch4 remotes/git-svn
-rm -rf dir
-git update-index --remove -- dir/file
-touch dir
-echo asdf > dir
-git update-index --add -- dir
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
-    || true
-
-
-name='remove executable bit from a file'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch5 remotes/git-svn
-chmod -x exec.sh
-git update-index exec.sh
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-     svn up $SVN_TREE &&
-     test ! -x $SVN_TREE/exec.sh"
-
-
-name='add executable bit back file'
-chmod +x exec.sh
-git update-index exec.sh
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-     svn up $SVN_TREE &&
-     test -x $SVN_TREE/exec.sh"
-
-
-
-if test -z "$NO_SYMLINK"
-then
-       name='executable file becomes a symlink to bar/zzz (file)'
-       rm exec.sh
-       ln -s bar/zzz exec.sh
-       git update-index exec.sh
-       git commit -m "$name"
-
-       test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -L $SVN_TREE/exec.sh"
-
-       name='new symlink is added to a file that was also just made executable'
-       chmod +x bar/zzz
-       ln -s bar/zzz exec-2.sh
-       git update-index --add bar/zzz exec-2.sh
-       git commit -m "$name"
-
-       test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -x $SVN_TREE/bar/zzz &&
-            test -L $SVN_TREE/exec-2.sh"
-
-       name='modify a symlink to become a file'
-       git help > help || true
-       rm exec-2.sh
-       cp help exec-2.sh
-       git update-index exec-2.sh
-       git commit -m "$name"
-
-       test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -f $SVN_TREE/exec-2.sh &&
-            test ! -L $SVN_TREE/exec-2.sh &&
-            diff -u help $SVN_TREE/exec-2.sh"
-fi
-
-
-if test "$have_utf8" = t
-then
-       name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
-       echo '# hello' >> exec-2.sh
-       git update-index exec-2.sh
-       git commit -m 'éï∏'
-       export LC_ALL="$GIT_SVN_LC_ALL"
-       test_expect_success "$name" "git-svn commit HEAD"
-       unset LC_ALL
-else
-       echo "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)"
-fi
-
-name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'
-GIT_SVN_ID=alt
-export GIT_SVN_ID
-test_expect_success "$name" \
-    "git-svn init $svnrepo && git-svn fetch &&
-     git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
-     git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
-     diff -u a b"
-
-if test -n "$NO_SYMLINK"
-then
-       test_done
-       exit 0
-fi
-
-name='check imported tree checksums expected tree checksums'
-rm -f expected
-if test "$have_utf8" = t
-then
-       echo tree f735671b89a7eb30cab1d8597de35bd4271ab813 > expected
-fi
-cat >> expected <<\EOF
-tree 4b9af72bb861eaed053854ec502cf7df72618f0f
-tree 031b8d557afc6fea52894eaebb45bec52f1ba6d1
-tree 0b094cbff17168f24c302e297f55bfac65eb8bd3
-tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
-tree 56a30b966619b863674f5978696f4a3594f2fca9
-tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
-tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
-EOF
-test_expect_success "$name" "diff -u a expected"
-
-test_done
-
diff --git a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh
deleted file mode 100644 (file)
index a5a235f..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006 Eric Wong
-#
-
-test_description='git-svn property tests'
-. ./lib-git-svn.sh
-
-mkdir import
-
-a_crlf=
-a_lf=
-a_cr=
-a_ne_crlf=
-a_ne_lf=
-a_ne_cr=
-a_empty=
-a_empty_lf=
-a_empty_cr=
-a_empty_crlf=
-
-cd import
-       cat >> kw.c <<\EOF
-/* Somebody prematurely put a keyword into this file */
-/* $Id$ */
-EOF
-
-       printf "Hello\r\nWorld\r\n" > crlf
-       a_crlf=`git-hash-object -w crlf`
-       printf "Hello\rWorld\r" > cr
-       a_cr=`git-hash-object -w cr`
-       printf "Hello\nWorld\n" > lf
-       a_lf=`git-hash-object -w lf`
-
-       printf "Hello\r\nWorld" > ne_crlf
-       a_ne_crlf=`git-hash-object -w ne_crlf`
-       printf "Hello\nWorld" > ne_lf
-       a_ne_lf=`git-hash-object -w ne_lf`
-       printf "Hello\rWorld" > ne_cr
-       a_ne_cr=`git-hash-object -w ne_cr`
-
-       touch empty
-       a_empty=`git-hash-object -w empty`
-       printf "\n" > empty_lf
-       a_empty_lf=`git-hash-object -w empty_lf`
-       printf "\r" > empty_cr
-       a_empty_cr=`git-hash-object -w empty_cr`
-       printf "\r\n" > empty_crlf
-       a_empty_crlf=`git-hash-object -w empty_crlf`
-
-       svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
-cd ..
-
-rm -rf import
-test_expect_success 'checkout working copy from svn' "svn co $svnrepo test_wc"
-test_expect_success 'setup some commits to svn' \
-       'cd test_wc &&
-               echo Greetings >> kw.c &&
-               svn commit -m "Not yet an Id" &&
-               svn up &&
-               echo Hello world >> kw.c &&
-               svn commit -m "Modified file, but still not yet an Id" &&
-               svn up &&
-               svn propset svn:keywords Id kw.c &&
-               svn commit -m "Propset Id" &&
-               svn up &&
-       cd ..'
-
-test_expect_success 'initialize git-svn' "git-svn init $svnrepo"
-test_expect_success 'fetch revisions from svn' 'git-svn fetch'
-
-name='test svn:keywords ignoring'
-test_expect_success "$name" \
-       'git checkout -b mybranch remotes/git-svn &&
-       echo Hi again >> kw.c &&
-       git commit -a -m "test keywoards ignoring" &&
-       git-svn commit remotes/git-svn..mybranch &&
-       git pull . remotes/git-svn'
-
-expect='/* $Id$ */'
-got="`sed -ne 2p kw.c`"
-test_expect_success 'raw $Id$ found in kw.c' "test '$expect' = '$got'"
-
-test_expect_success "propset CR on crlf files" \
-       'cd test_wc &&
-               svn propset svn:eol-style CR empty &&
-               svn propset svn:eol-style CR crlf &&
-               svn propset svn:eol-style CR ne_crlf &&
-               svn commit -m "propset CR on crlf files" &&
-               svn up &&
-        cd ..'
-
-test_expect_success 'fetch and pull latest from svn and checkout a new wc' \
-       "git-svn fetch &&
-        git pull . remotes/git-svn &&
-        svn co $svnrepo new_wc"
-
-for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf
-do
-       test_expect_success "Comparing $i" "cmp $i new_wc/$i"
-done
-
-
-cd test_wc
-       printf '$Id$\rHello\rWorld\r' > cr
-       printf '$Id$\rHello\rWorld' > ne_cr
-       a_cr=`printf '$Id$\r\nHello\r\nWorld\r\n' | git-hash-object --stdin`
-       a_ne_cr=`printf '$Id$\r\nHello\r\nWorld' | git-hash-object --stdin`
-       test_expect_success 'Set CRLF on cr files' \
-       'svn propset svn:eol-style CRLF cr &&
-        svn propset svn:eol-style CRLF ne_cr &&
-        svn propset svn:keywords Id cr &&
-        svn propset svn:keywords Id ne_cr &&
-        svn commit -m "propset CRLF on cr files" &&
-        svn up'
-cd ..
-test_expect_success 'fetch and pull latest from svn' \
-       'git-svn fetch && git pull . remotes/git-svn'
-
-b_cr="`git-hash-object cr`"
-b_ne_cr="`git-hash-object ne_cr`"
-
-test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'"
-test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'"
-
-test_done
diff --git a/contrib/git-svn/t/t0002-deep-rmdir.sh b/contrib/git-svn/t/t0002-deep-rmdir.sh
deleted file mode 100644 (file)
index d693d18..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-test_description='git-svn rmdir'
-. ./lib-git-svn.sh
-
-test_expect_success 'initialize repo' "
-       mkdir import &&
-       cd import &&
-       mkdir -p deeply/nested/directory/number/1 &&
-       mkdir -p deeply/nested/directory/number/2 &&
-       echo foo > deeply/nested/directory/number/1/file &&
-       echo foo > deeply/nested/directory/number/2/another &&
-       svn import -m 'import for git-svn' . $svnrepo &&
-       cd ..
-       "
-
-test_expect_success 'mirror via git-svn' "
-       git-svn init $svnrepo &&
-       git-svn fetch &&
-       git checkout -f -b test-rmdir remotes/git-svn
-       "
-
-test_expect_success 'Try a commit on rmdir' "
-       git rm -f deeply/nested/directory/number/2/another &&
-       git commit -a -m 'remove another' &&
-       git-svn commit --rmdir HEAD &&
-       svn ls -R $svnrepo | grep ^deeply/nested/directory/number/1
-       "
-
-
-test_done
diff --git a/contrib/git-svn/t/t0003-graft-branches.sh b/contrib/git-svn/t/t0003-graft-branches.sh
deleted file mode 100644 (file)
index cc62d4e..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-test_description='git-svn graft-branches'
-. ./lib-git-svn.sh
-
-test_expect_success 'initialize repo' "
-       mkdir import &&
-       cd import &&
-       mkdir -p trunk branches tags &&
-       echo hello > trunk/readme &&
-       svn import -m 'import for git-svn' . $svnrepo &&
-       cd .. &&
-       svn cp -m 'tag a' $svnrepo/trunk $svnrepo/tags/a &&
-       svn cp -m 'branch a' $svnrepo/trunk $svnrepo/branches/a &&
-       svn co $svnrepo wc &&
-       cd wc &&
-       echo feedme >> branches/a/readme &&
-       svn commit -m hungry &&
-       svn up &&
-       cd trunk &&
-       svn merge -r3:4 $svnrepo/branches/a &&
-       svn commit -m 'merge with a' &&
-       cd ../.. &&
-       svn log -v $svnrepo &&
-       git-svn init -i trunk $svnrepo/trunk &&
-       git-svn init -i a $svnrepo/branches/a &&
-       git-svn init -i tags/a $svnrepo/tags/a &&
-       git-svn fetch -i tags/a &&
-       git-svn fetch -i a &&
-       git-svn fetch -i trunk
-       "
-
-r1=`git-rev-list remotes/trunk | tail -n1`
-r2=`git-rev-list remotes/tags/a | tail -n1`
-r3=`git-rev-list remotes/a | tail -n1`
-r4=`git-rev-list remotes/a | head -n1`
-r5=`git-rev-list remotes/trunk | head -n1`
-
-test_expect_success 'test graft-branches regexes and copies' "
-       test -n "$r1" &&
-       test -n "$r2" &&
-       test -n "$r3" &&
-       test -n "$r4" &&
-       test -n "$r5" &&
-       git-svn graft-branches &&
-       grep '^$r2 $r1' $GIT_DIR/info/grafts &&
-       grep '^$r3 $r1' $GIT_DIR/info/grafts &&
-       grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r4' | grep '$r1'
-       "
-
-test_debug 'gitk --all & sleep 1'
-
-test_expect_success 'test graft-branches with tree-joins' "
-       rm $GIT_DIR/info/grafts &&
-       git-svn graft-branches --no-default-regex --no-graft-copy -B &&
-       grep '^$r3 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r2' &&
-       grep '^$r2 $r1' $GIT_DIR/info/grafts &&
-       grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r4'
-       "
-
-# the result of this is kinda funky, we have a strange history and
-# this is just a test :)
-test_debug 'gitk --all &'
-
-test_done
diff --git a/contrib/git-svn/t/t0004-follow-parent.sh b/contrib/git-svn/t/t0004-follow-parent.sh
deleted file mode 100644 (file)
index 01488ff..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006 Eric Wong
-#
-
-test_description='git-svn --follow-parent fetching'
-. ./lib-git-svn.sh
-
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: --follow-parent needs SVN libraries'
-       test_done
-       exit 0
-fi
-
-test_expect_success 'initialize repo' "
-       mkdir import &&
-       cd import &&
-       mkdir -p trunk &&
-       echo hello > trunk/readme &&
-       svn import -m 'initial' . $svnrepo &&
-       cd .. &&
-       svn co $svnrepo wc &&
-       cd wc &&
-       echo world >> trunk/readme &&
-       svn commit -m 'another commit' &&
-       svn up &&
-       svn mv -m 'rename to thunk' trunk thunk &&
-       svn up &&
-       echo goodbye >> thunk/readme &&
-       svn commit -m 'bye now' &&
-       cd ..
-       "
-
-test_expect_success 'init and fetch --follow-parent a moved directory' "
-       git-svn init -i thunk $svnrepo/thunk &&
-       git-svn fetch --follow-parent -i thunk &&
-       git-rev-parse --verify refs/remotes/trunk &&
-       test '$?' -eq '0'
-       "
-
-test_debug 'gitk --all &'
-
-test_done
diff --git a/contrib/git-svn/t/t0005-commit-diff.sh b/contrib/git-svn/t/t0005-commit-diff.sh
deleted file mode 100644 (file)
index f994b72..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2006 Eric Wong
-test_description='git-svn commit-diff'
-. ./lib-git-svn.sh
-
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: commit-diff needs SVN libraries'
-       test_done
-       exit 0
-fi
-
-test_expect_success 'initialize repo' "
-       mkdir import &&
-       cd import &&
-       echo hello > readme &&
-       svn import -m 'initial' . $svnrepo &&
-       cd .. &&
-       echo hello > readme &&
-       git update-index --add readme &&
-       git commit -a -m 'initial' &&
-       echo world >> readme &&
-       git commit -a -m 'another'
-       "
-
-head=`git rev-parse --verify HEAD^0`
-prev=`git rev-parse --verify HEAD^1`
-
-# the internals of the commit-diff command are the same as the regular
-# commit, so only a basic test of functionality is needed since we've
-# already tested commit extensively elsewhere
-
-test_expect_success 'test the commit-diff command' "
-       test -n '$prev' && test -n '$head' &&
-       git-svn commit-diff '$prev' '$head' '$svnrepo' &&
-       svn co $svnrepo wc &&
-       cmp readme wc/readme
-       "
-
-test_done
index 0fabd8981c6047e5144cfb3776b4f4a5588f24e5..ebea8e472bb2eede02219999b0cfd9a63f211d4b 100644 (file)
@@ -240,14 +240,14 @@ static void convert_date(void *buffer, unsigned long size, unsigned char *result
 {
        char *new = xmalloc(size + 100);
        unsigned long newlen = 0;
-       
-       // "tree <sha1>\n"
+
+       /* "tree <sha1>\n" */
        memcpy(new + newlen, buffer, 46);
        newlen += 46;
        buffer = (char *) buffer + 46;
        size -= 46;
 
-       // "parent <sha1>\n"
+       /* "parent <sha1>\n" */
        while (!memcmp(buffer, "parent ", 7)) {
                memcpy(new + newlen, buffer, 48);
                newlen += 48;
@@ -255,12 +255,12 @@ static void convert_date(void *buffer, unsigned long size, unsigned char *result
                size -= 48;
        }
 
-       // "author xyz <xyz> date"
+       /* "author xyz <xyz> date" */
        newlen += convert_date_line(new + newlen, &buffer, &size);
-       // "committer xyz <xyz> date"
+       /* "committer xyz <xyz> date" */
        newlen += convert_date_line(new + newlen, &buffer, &size);
 
-       // Rest
+       /* Rest */
        memcpy(new + newlen, buffer, size);
        newlen += size;
 
index ebaad0397f0bce50db7542abb904ba42b6173344..6a7b40fd09ea9aa365d70dc8019f83e481192c07 100644 (file)
@@ -122,7 +122,7 @@ int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
        void *out;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        maxsize = deflateBound(&stream, size);
        out = xmalloc(maxsize);
 
index e096bd7ef638273b525848fbe0018863871ea93d..e4ec676301c965e0b0ec81995f4e1fb77363c373 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -95,6 +95,12 @@ static void loginfo(const char *err, ...)
        va_end(params);
 }
 
+static void NORETURN daemon_die(const char *err, va_list params)
+{
+       logreport(LOG_ERR, err, params);
+       exit(1);
+}
+
 static int avoid_alias(char *p)
 {
        int sl, ndot;
@@ -656,6 +662,45 @@ static int service_loop(int socknum, int *socklist)
        }
 }
 
+/* if any standard file descriptor is missing open it to /dev/null */
+static void sanitize_stdfds(void)
+{
+       int fd = open("/dev/null", O_RDWR, 0);
+       while (fd != -1 && fd < 2)
+               fd = dup(fd);
+       if (fd == -1)
+               die("open /dev/null or dup failed: %s", strerror(errno));
+       if (fd > 2)
+               close(fd);
+}
+
+static void daemonize(void)
+{
+       switch (fork()) {
+               case 0:
+                       break;
+               case -1:
+                       die("fork failed: %s", strerror(errno));
+               default:
+                       exit(0);
+       }
+       if (setsid() == -1)
+               die("setsid failed: %s", strerror(errno));
+       close(0);
+       close(1);
+       close(2);
+       sanitize_stdfds();
+}
+
+static void store_pid(const char *path)
+{
+       FILE *f = fopen(path, "w");
+       if (!f)
+               die("cannot open pid file %s: %s", path, strerror(errno));
+       fprintf(f, "%d\n", getpid());
+       fclose(f);
+}
+
 static int serve(int port)
 {
        int socknum, *socklist;
@@ -671,6 +716,8 @@ int main(int argc, char **argv)
 {
        int port = DEFAULT_GIT_PORT;
        int inetd_mode = 0;
+       const char *pid_file = NULL;
+       int detach = 0;
        int i;
 
        /* Without this we cannot rely on waitpid() to tell
@@ -735,6 +782,15 @@ int main(int argc, char **argv)
                        user_path = arg + 12;
                        continue;
                }
+               if (!strncmp(arg, "--pid-file=", 11)) {
+                       pid_file = arg + 11;
+                       continue;
+               }
+               if (!strcmp(arg, "--detach")) {
+                       detach = 1;
+                       log_syslog = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--")) {
                        ok_paths = &argv[i+1];
                        break;
@@ -746,17 +802,14 @@ int main(int argc, char **argv)
                usage(daemon_usage);
        }
 
-       if (log_syslog)
+       if (log_syslog) {
                openlog("git-daemon", 0, LOG_DAEMON);
-
-       if (strict_paths && (!ok_paths || !*ok_paths)) {
-               if (!inetd_mode)
-                       die("git-daemon: option --strict-paths requires a whitelist");
-
-               logerror("option --strict-paths requires a whitelist");
-               exit (1);
+               set_die_routine(daemon_die);
        }
 
+       if (strict_paths && (!ok_paths || !*ok_paths))
+               die("option --strict-paths requires a whitelist");
+
        if (inetd_mode) {
                struct sockaddr_storage ss;
                struct sockaddr *peer = (struct sockaddr *)&ss;
@@ -770,5 +823,13 @@ int main(int argc, char **argv)
                return execute(peer);
        }
 
+       if (detach)
+               daemonize();
+       else
+               sanitize_stdfds();
+
+       if (pid_file)
+               store_pid(pid_file);
+
        return serve(port);
 }
index 8e68d5df3303ed75c2fbb9c8b3d1025785a622c7..324ca8965b9512de59b515cdcc29801022553d1d 100644 (file)
@@ -67,7 +67,7 @@ static int get_name(const char *path, const unsigned char *sha1)
         * Otherwise only annotated tags are used.
         */
        if (!strncmp(path, "refs/tags/", 10)) {
-               if (object->type == TYPE_TAG)
+               if (object->type == OBJ_TAG)
                        prio = 2;
                else
                        prio = 1;
index 8b9172aa2ef7402b0a847815bb11a26d1c4444fe..7da9205a5da6a4f016bc6b556bee01895cdcc19d 100644 (file)
@@ -148,7 +148,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
                return NULL;
 
        /* Determine index hash size.  Note that indexing skips the
-          first byte to allow for optimizing the rabin polynomial
+          first byte to allow for optimizing the Rabin's polynomial
           initialization in create_delta(). */
        entries = (bufsize - 1)  / RABIN_WINDOW;
        hsize = entries / 4;
@@ -205,7 +205,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
 
        /*
         * Determine a limit on the number of entries in the same hash
-        * bucket.  This guard us against patological data sets causing
+        * bucket.  This guards us against pathological data sets causing
         * really bad hash distribution with most entries in the same hash
         * bucket that would bring us to O(m*n) computing costs (m and n
         * corresponding to reference and target buffer sizes).
@@ -240,7 +240,7 @@ void free_delta_index(struct delta_index *index)
 
 /*
  * The maximum size for any opcode sequence, including the initial header
- * plus rabin window plus biggest copy.
+ * plus Rabin window plus biggest copy.
  */
 #define MAX_OP_SIZE    (5 + 5 + 1 + RABIN_WINDOW + 7)
 
diff --git a/diff.c b/diff.c
index 5a71489a471568fb3aa51af5d74fa269ec4fadf1..6a713764831e7521958f4b280f2e1d3911fb3cea 100644 (file)
--- a/diff.c
+++ b/diff.c
 
 static int use_size_cache;
 
+static int diff_detect_rename_default = 0;
 static int diff_rename_limit_default = -1;
 static int diff_use_color_default = 0;
 
-enum color_diff {
-       DIFF_RESET = 0,
-       DIFF_PLAIN = 1,
-       DIFF_METAINFO = 2,
-       DIFF_FRAGINFO = 3,
-       DIFF_FILE_OLD = 4,
-       DIFF_FILE_NEW = 5,
-};
-
-#define COLOR_NORMAL  ""
-#define COLOR_BOLD    "\033[1m"
-#define COLOR_DIM     "\033[2m"
-#define COLOR_UL      "\033[4m"
-#define COLOR_BLINK   "\033[5m"
-#define COLOR_REVERSE "\033[7m"
-#define COLOR_RESET   "\033[m"
-
-#define COLOR_BLACK   "\033[30m"
-#define COLOR_RED     "\033[31m"
-#define COLOR_GREEN   "\033[32m"
-#define COLOR_YELLOW  "\033[33m"
-#define COLOR_BLUE    "\033[34m"
-#define COLOR_MAGENTA "\033[35m"
-#define COLOR_CYAN    "\033[36m"
-#define COLOR_WHITE   "\033[37m"
-
-static const char *diff_colors[] = {
-       [DIFF_RESET]    = COLOR_RESET,
-       [DIFF_PLAIN]    = COLOR_NORMAL,
-       [DIFF_METAINFO] = COLOR_BOLD,
-       [DIFF_FRAGINFO] = COLOR_CYAN,
-       [DIFF_FILE_OLD] = COLOR_RED,
-       [DIFF_FILE_NEW] = COLOR_GREEN,
+/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
+static char diff_colors[][24] = {
+       "\033[m",       /* reset */
+       "",             /* normal */
+       "\033[1m",      /* bold */
+       "\033[36m",     /* cyan */
+       "\033[31m",     /* red */
+       "\033[32m",     /* green */
+       "\033[33m"      /* yellow */
 };
 
 static int parse_diff_color_slot(const char *var, int ofs)
@@ -63,45 +40,131 @@ static int parse_diff_color_slot(const char *var, int ofs)
                return DIFF_FILE_OLD;
        if (!strcasecmp(var+ofs, "new"))
                return DIFF_FILE_NEW;
+       if (!strcasecmp(var+ofs, "commit"))
+               return DIFF_COMMIT;
        die("bad config variable '%s'", var);
 }
 
-static const char *parse_diff_color_value(const char *value, const char *var)
-{
-       if (!strcasecmp(value, "normal"))
-               return COLOR_NORMAL;
-       if (!strcasecmp(value, "bold"))
-               return COLOR_BOLD;
-       if (!strcasecmp(value, "dim"))
-               return COLOR_DIM;
-       if (!strcasecmp(value, "ul"))
-               return COLOR_UL;
-       if (!strcasecmp(value, "blink"))
-               return COLOR_BLINK;
-       if (!strcasecmp(value, "reverse"))
-               return COLOR_REVERSE;
-       if (!strcasecmp(value, "reset"))
-               return COLOR_RESET;
-       if (!strcasecmp(value, "black"))
-               return COLOR_BLACK;
-       if (!strcasecmp(value, "red"))
-               return COLOR_RED;
-       if (!strcasecmp(value, "green"))
-               return COLOR_GREEN;
-       if (!strcasecmp(value, "yellow"))
-               return COLOR_YELLOW;
-       if (!strcasecmp(value, "blue"))
-               return COLOR_BLUE;
-       if (!strcasecmp(value, "magenta"))
-               return COLOR_MAGENTA;
-       if (!strcasecmp(value, "cyan"))
-               return COLOR_CYAN;
-       if (!strcasecmp(value, "white"))
-               return COLOR_WHITE;
+static int parse_color(const char *name, int len)
+{
+       static const char * const color_names[] = {
+               "normal", "black", "red", "green", "yellow",
+               "blue", "magenta", "cyan", "white"
+       };
+       char *end;
+       int i;
+       for (i = 0; i < ARRAY_SIZE(color_names); i++) {
+               const char *str = color_names[i];
+               if (!strncasecmp(name, str, len) && !str[len])
+                       return i - 1;
+       }
+       i = strtol(name, &end, 10);
+       if (*name && !*end && i >= -1 && i <= 255)
+               return i;
+       return -2;
+}
+
+static int parse_attr(const char *name, int len)
+{
+       static const int attr_values[] = { 1, 2, 4, 5, 7 };
+       static const char * const attr_names[] = {
+               "bold", "dim", "ul", "blink", "reverse"
+       };
+       int i;
+       for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
+               const char *str = attr_names[i];
+               if (!strncasecmp(name, str, len) && !str[len])
+                       return attr_values[i];
+       }
+       return -1;
+}
+
+static void parse_diff_color_value(const char *value, const char *var, char *dst)
+{
+       const char *ptr = value;
+       int attr = -1;
+       int fg = -2;
+       int bg = -2;
+
+       if (!strcasecmp(value, "reset")) {
+               strcpy(dst, "\033[m");
+               return;
+       }
+
+       /* [fg [bg]] [attr] */
+       while (*ptr) {
+               const char *word = ptr;
+               int val, len = 0;
+
+               while (word[len] && !isspace(word[len]))
+                       len++;
+
+               ptr = word + len;
+               while (*ptr && isspace(*ptr))
+                       ptr++;
+
+               val = parse_color(word, len);
+               if (val >= -1) {
+                       if (fg == -2) {
+                               fg = val;
+                               continue;
+                       }
+                       if (bg == -2) {
+                               bg = val;
+                               continue;
+                       }
+                       goto bad;
+               }
+               val = parse_attr(word, len);
+               if (val < 0 || attr != -1)
+                       goto bad;
+               attr = val;
+       }
+
+       if (attr >= 0 || fg >= 0 || bg >= 0) {
+               int sep = 0;
+
+               *dst++ = '\033';
+               *dst++ = '[';
+               if (attr >= 0) {
+                       *dst++ = '0' + attr;
+                       sep++;
+               }
+               if (fg >= 0) {
+                       if (sep++)
+                               *dst++ = ';';
+                       if (fg < 8) {
+                               *dst++ = '3';
+                               *dst++ = '0' + fg;
+                       } else {
+                               dst += sprintf(dst, "38;5;%d", fg);
+                       }
+               }
+               if (bg >= 0) {
+                       if (sep++)
+                               *dst++ = ';';
+                       if (bg < 8) {
+                               *dst++ = '4';
+                               *dst++ = '0' + bg;
+                       } else {
+                               dst += sprintf(dst, "48;5;%d", bg);
+                       }
+               }
+               *dst++ = 'm';
+       }
+       *dst = 0;
+       return;
+bad:
        die("bad config value '%s' for variable '%s'", value, var);
 }
 
-int git_diff_config(const char *var, const char *value)
+/*
+ * These are to give UI layer defaults.
+ * The core-level commands such as git-diff-files should
+ * never be affected by the setting of diff.renames
+ * the user happens to have in the configuration file.
+ */
+int git_diff_ui_config(const char *var, const char *value)
 {
        if (!strcmp(var, "diff.renamelimit")) {
                diff_rename_limit_default = git_config_int(var, value);
@@ -110,8 +173,14 @@ int git_diff_config(const char *var, const char *value)
        if (!strcmp(var, "diff.color")) {
                if (!value)
                        diff_use_color_default = 1; /* bool */
-               else if (!strcasecmp(value, "auto"))
-                       diff_use_color_default = isatty(1);
+               else if (!strcasecmp(value, "auto")) {
+                       diff_use_color_default = 0;
+                       if (isatty(1) || pager_in_use) {
+                               char *term = getenv("TERM");
+                               if (term && strcmp(term, "dumb"))
+                                       diff_use_color_default = 1;
+                       }
+               }
                else if (!strcasecmp(value, "never"))
                        diff_use_color_default = 0;
                else if (!strcasecmp(value, "always"))
@@ -120,9 +189,19 @@ int git_diff_config(const char *var, const char *value)
                        diff_use_color_default = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.renames")) {
+               if (!value)
+                       diff_detect_rename_default = DIFF_DETECT_RENAME;
+               else if (!strcasecmp(value, "copies") ||
+                        !strcasecmp(value, "copy"))
+                       diff_detect_rename_default = DIFF_DETECT_COPY;
+               else if (git_config_bool(var,value))
+                       diff_detect_rename_default = DIFF_DETECT_RENAME;
+               return 0;
+       }
        if (!strncmp(var, "diff.color.", 11)) {
                int slot = parse_diff_color_slot(var, 11);
-               diff_colors[slot] = parse_diff_color_value(value, var);
+               parse_diff_color_value(value, var, diff_colors[slot]);
                return 0;
        }
        return git_default_config(var, value);
@@ -285,7 +364,7 @@ struct emit_callback {
        const char **label_path;
 };
 
-static inline const char *get_color(int diff_use_color, enum color_diff ix)
+const char *diff_get_color(int diff_use_color, enum color_diff ix)
 {
        if (diff_use_color)
                return diff_colors[ix];
@@ -296,8 +375,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
        int i;
        struct emit_callback *ecbdata = priv;
-       const char *set = get_color(ecbdata->color_diff, DIFF_METAINFO);
-       const char *reset = get_color(ecbdata->color_diff, DIFF_RESET);
+       const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
+       const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
 
        if (ecbdata->label_path[0]) {
                printf("%s--- %s%s\n", set, ecbdata->label_path[0], reset);
@@ -312,7 +391,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                ;
        if (2 <= i && i < len && line[i] == ' ') {
                ecbdata->nparents = i - 1;
-               set = get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+               set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
        }
        else if (len < ecbdata->nparents)
                set = reset;
@@ -325,11 +404,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                        else if (line[i] == '+')
                                color = DIFF_FILE_NEW;
                }
-               set = get_color(ecbdata->color_diff, color);
+               set = diff_get_color(ecbdata->color_diff, color);
        }
        if (len > 0 && line[len-1] == '\n')
                len--;
-       printf("%s%.*s%s\n", set, (int) len, line, reset);
+       fputs (set, stdout);
+       fwrite (line, len, 1, stdout);
+       puts (reset);
 }
 
 static char *pprint_rename(const char *a, const char *b)
@@ -583,7 +664,7 @@ static unsigned char *deflate_it(char *data,
        z_stream stream;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        bound = deflateBound(&stream, size);
        deflated = xmalloc(bound);
        stream.next_out = deflated;
@@ -680,8 +761,8 @@ static void builtin_diff(const char *name_a,
        mmfile_t mf1, mf2;
        const char *lbl[2];
        char *a_one, *b_two;
-       const char *set = get_color(o->color_diff, DIFF_METAINFO);
-       const char *reset = get_color(o->color_diff, DIFF_RESET);
+       const char *set = diff_get_color(o->color_diff, DIFF_METAINFO);
+       const char *reset = diff_get_color(o->color_diff, DIFF_RESET);
 
        a_one = quote_two("a/", name_a);
        b_two = quote_two("b/", name_b);
@@ -721,7 +802,7 @@ static void builtin_diff(const char *name_a,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) {
+       if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) {
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
@@ -1420,15 +1501,16 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 void diff_setup(struct diff_options *options)
 {
        memset(options, 0, sizeof(*options));
-       options->output_format = DIFF_FORMAT_RAW;
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
        options->context = 3;
+       options->msg_sep = "";
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
        options->color_diff = diff_use_color_default;
+       options->detect_rename = diff_detect_rename_default;
 }
 
 int diff_setup_done(struct diff_options *options)
@@ -1438,22 +1520,28 @@ int diff_setup_done(struct diff_options *options)
            (0 <= options->rename_limit && !options->detect_rename))
                return -1;
 
+       if (options->output_format & (DIFF_FORMAT_NAME |
+                                     DIFF_FORMAT_NAME_STATUS |
+                                     DIFF_FORMAT_CHECKDIFF |
+                                     DIFF_FORMAT_NO_OUTPUT))
+               options->output_format &= ~(DIFF_FORMAT_RAW |
+                                           DIFF_FORMAT_DIFFSTAT |
+                                           DIFF_FORMAT_SUMMARY |
+                                           DIFF_FORMAT_PATCH);
+
        /*
         * These cases always need recursive; we do not drop caller-supplied
         * recursive bits for other formats here.
         */
-       if ((options->output_format == DIFF_FORMAT_PATCH) ||
-           (options->output_format == DIFF_FORMAT_DIFFSTAT) ||
-           (options->output_format == DIFF_FORMAT_CHECKDIFF))
+       if (options->output_format & (DIFF_FORMAT_PATCH |
+                                     DIFF_FORMAT_DIFFSTAT |
+                                     DIFF_FORMAT_CHECKDIFF))
                options->recursive = 1;
-
        /*
-        * These combinations do not make sense.
+        * Also pickaxe would not work very well if you do not say recursive
         */
-       if (options->output_format == DIFF_FORMAT_RAW)
-               options->with_raw = 0;
-       if (options->output_format == DIFF_FORMAT_DIFFSTAT)
-               options->with_stat  = 0;
+       if (options->pickaxe)
+               options->recursive = 1;
 
        if (options->detect_rename && options->rename_limit < 0)
                options->rename_limit = diff_rename_limit_default;
@@ -1526,22 +1614,22 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 {
        const char *arg = av[0];
        if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
-               options->output_format = DIFF_FORMAT_PATCH;
+               options->output_format |= DIFF_FORMAT_PATCH;
        else if (opt_arg(arg, 'U', "unified", &options->context))
-               options->output_format = DIFF_FORMAT_PATCH;
+               options->output_format |= DIFF_FORMAT_PATCH;
+       else if (!strcmp(arg, "--raw"))
+               options->output_format |= DIFF_FORMAT_RAW;
        else if (!strcmp(arg, "--patch-with-raw")) {
-               options->output_format = DIFF_FORMAT_PATCH;
-               options->with_raw = 1;
+               options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
        }
        else if (!strcmp(arg, "--stat"))
-               options->output_format = DIFF_FORMAT_DIFFSTAT;
+               options->output_format |= DIFF_FORMAT_DIFFSTAT;
        else if (!strcmp(arg, "--check"))
-               options->output_format = DIFF_FORMAT_CHECKDIFF;
+               options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
-               options->summary = 1;
+               options->output_format |= DIFF_FORMAT_SUMMARY;
        else if (!strcmp(arg, "--patch-with-stat")) {
-               options->output_format = DIFF_FORMAT_PATCH;
-               options->with_stat = 1;
+               options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT;
        }
        else if (!strcmp(arg, "-z"))
                options->line_termination = 0;
@@ -1550,19 +1638,23 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--full-index"))
                options->full_index = 1;
        else if (!strcmp(arg, "--binary")) {
-               options->output_format = DIFF_FORMAT_PATCH;
+               options->output_format |= DIFF_FORMAT_PATCH;
                options->full_index = options->binary = 1;
        }
+       else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) {
+               options->text = 1;
+       }
        else if (!strcmp(arg, "--name-only"))
-               options->output_format = DIFF_FORMAT_NAME;
+               options->output_format |= DIFF_FORMAT_NAME;
        else if (!strcmp(arg, "--name-status"))
-               options->output_format = DIFF_FORMAT_NAME_STATUS;
+               options->output_format |= DIFF_FORMAT_NAME_STATUS;
        else if (!strcmp(arg, "-R"))
                options->reverse_diff = 1;
        else if (!strncmp(arg, "-S", 2))
                options->pickaxe = arg + 2;
-       else if (!strcmp(arg, "-s"))
-               options->output_format = DIFF_FORMAT_NO_OUTPUT;
+       else if (!strcmp(arg, "-s")) {
+               options->output_format |= DIFF_FORMAT_NO_OUTPUT;
+       }
        else if (!strncmp(arg, "-O", 2))
                options->orderfile = arg + 2;
        else if (!strncmp(arg, "--diff-filter=", 14))
@@ -1601,10 +1693,14 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
        else if (!strcmp(arg, "--color"))
                options->color_diff = 1;
+       else if (!strcmp(arg, "--no-color"))
+               options->color_diff = 0;
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
                options->xdl_opts |= XDF_IGNORE_WHITESPACE;
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
                options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+       else if (!strcmp(arg, "--no-renames"))
+               options->detect_rename = 0;
        else
                return 0;
        return 1;
@@ -1737,15 +1833,17 @@ const char *diff_unique_abbrev(const unsigned char *sha1, int len)
 }
 
 static void diff_flush_raw(struct diff_filepair *p,
-                          int line_termination,
-                          int inter_name_termination,
-                          struct diff_options *options,
-                          int output_format)
+                          struct diff_options *options)
 {
        int two_paths;
        char status[10];
        int abbrev = options->abbrev;
        const char *path_one, *path_two;
+       int inter_name_termination = '\t';
+       int line_termination = options->line_termination;
+
+       if (!line_termination)
+               inter_name_termination = 0;
 
        path_one = p->one->path;
        path_two = p->two->path;
@@ -1774,7 +1872,7 @@ static void diff_flush_raw(struct diff_filepair *p,
                two_paths = 0;
                break;
        }
-       if (output_format != DIFF_FORMAT_NAME_STATUS) {
+       if (!(options->output_format & DIFF_FORMAT_NAME_STATUS)) {
                printf(":%06o %06o %s ",
                       p->one->mode, p->two->mode,
                       diff_unique_abbrev(p->one->sha1, abbrev));
@@ -1983,48 +2081,30 @@ static void diff_resolve_rename_copy(void)
        diff_debug_queue("resolve-rename-copy done", q);
 }
 
-static void flush_one_pair(struct diff_filepair *p,
-                          int diff_output_format,
-                          struct diff_options *options,
-                          struct diffstat_t *diffstat)
+static int check_pair_status(struct diff_filepair *p)
 {
-       int inter_name_termination = '\t';
-       int line_termination = options->line_termination;
-       if (!line_termination)
-               inter_name_termination = 0;
-
        switch (p->status) {
        case DIFF_STATUS_UNKNOWN:
-               break;
+               return 0;
        case 0:
                die("internal error in diff-resolve-rename-copy");
-               break;
        default:
-               switch (diff_output_format) {
-               case DIFF_FORMAT_DIFFSTAT:
-                       diff_flush_stat(p, options, diffstat);
-                       break;
-               case DIFF_FORMAT_CHECKDIFF:
-                       diff_flush_checkdiff(p, options);
-                       break;
-               case DIFF_FORMAT_PATCH:
-                       diff_flush_patch(p, options);
-                       break;
-               case DIFF_FORMAT_RAW:
-               case DIFF_FORMAT_NAME_STATUS:
-                       diff_flush_raw(p, line_termination,
-                                      inter_name_termination,
-                                      options, diff_output_format);
-                       break;
-               case DIFF_FORMAT_NAME:
-                       diff_flush_name(p, line_termination);
-                       break;
-               case DIFF_FORMAT_NO_OUTPUT:
-                       break;
-               }
+               return 1;
        }
 }
 
+static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
+{
+       int fmt = opt->output_format;
+
+       if (fmt & DIFF_FORMAT_CHECKDIFF)
+               diff_flush_checkdiff(p, opt);
+       else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS))
+               diff_flush_raw(p, opt);
+       else if (fmt & DIFF_FORMAT_NAME)
+               diff_flush_name(p, opt->line_termination);
+}
+
 static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
 {
        if (fs->mode)
@@ -2243,58 +2323,96 @@ int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
        return result;
 }
 
-void diff_flush(struct diff_options *options)
+static int is_summary_empty(const struct diff_queue_struct *q)
 {
-       struct diff_queue_struct *q = &diff_queued_diff;
        int i;
-       int diff_output_format = options->output_format;
-       struct diffstat_t *diffstat = NULL;
 
-       if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
-               diffstat = xcalloc(sizeof (struct diffstat_t), 1);
-               diffstat->xm.consume = diffstat_consume;
+       for (i = 0; i < q->nr; i++) {
+               const struct diff_filepair *p = q->queue[i];
+
+               switch (p->status) {
+               case DIFF_STATUS_DELETED:
+               case DIFF_STATUS_ADDED:
+               case DIFF_STATUS_COPIED:
+               case DIFF_STATUS_RENAMED:
+                       return 0;
+               default:
+                       if (p->score)
+                               return 0;
+                       if (p->one->mode && p->two->mode &&
+                           p->one->mode != p->two->mode)
+                               return 0;
+                       break;
+               }
        }
+       return 1;
+}
+
+void diff_flush(struct diff_options *options)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i, output_format = options->output_format;
+       int separator = 0;
+
+       /*
+        * Order: raw, stat, summary, patch
+        * or:    name/name-status/checkdiff (other bits clear)
+        */
+       if (!q->nr)
+               goto free_queue;
 
-       if (options->with_raw) {
+       if (output_format & (DIFF_FORMAT_RAW |
+                            DIFF_FORMAT_NAME |
+                            DIFF_FORMAT_NAME_STATUS |
+                            DIFF_FORMAT_CHECKDIFF)) {
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+                       if (check_pair_status(p))
+                               flush_one_pair(p, options);
                }
-               putchar(options->line_termination);
+               separator++;
        }
-       if (options->with_stat) {
+
+       if (output_format & DIFF_FORMAT_DIFFSTAT) {
+               struct diffstat_t diffstat;
+
+               memset(&diffstat, 0, sizeof(struct diffstat_t));
+               diffstat.xm.consume = diffstat_consume;
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
-                                      diffstat);
+                       if (check_pair_status(p))
+                               diff_flush_stat(p, options, &diffstat);
                }
-               show_stats(diffstat);
-               free(diffstat);
-               diffstat = NULL;
-               if (options->summary)
-                       for (i = 0; i < q->nr; i++)
-                               diff_summary(q->queue[i]);
-               if (options->stat_sep)
-                       fputs(options->stat_sep, stdout);
-               else
-                       putchar(options->line_termination);
-       }
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               flush_one_pair(p, diff_output_format, options, diffstat);
+               show_stats(&diffstat);
+               separator++;
        }
 
-       if (diffstat) {
-               show_stats(diffstat);
-               free(diffstat);
+       if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
+               for (i = 0; i < q->nr; i++)
+                       diff_summary(q->queue[i]);
+               separator++;
        }
 
-       for (i = 0; i < q->nr; i++) {
-               if (diffstat && options->summary)
-                       diff_summary(q->queue[i]);
-               diff_free_filepair(q->queue[i]);
+       if (output_format & DIFF_FORMAT_PATCH) {
+               if (separator) {
+                       if (options->stat_sep) {
+                               /* attach patch instead of inline */
+                               fputs(options->stat_sep, stdout);
+                       } else {
+                               putchar(options->line_termination);
+                       }
+               }
+
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (check_pair_status(p))
+                               diff_flush_patch(p, options);
+               }
        }
 
+       for (i = 0; i < q->nr; i++)
+               diff_free_filepair(q->queue[i]);
+free_queue:
        free(q->queue);
        q->queue = NULL;
        q->nr = q->alloc = 0;
diff --git a/diff.h b/diff.h
index d5068af7d16a0905edfeaa24276461c05437f3be..2cced530fa60ad7206b2cdb1b2509f42469e8f73 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -20,19 +20,32 @@ typedef void (*add_remove_fn_t)(struct diff_options *options,
                    const unsigned char *sha1,
                    const char *base, const char *path);
 
+#define DIFF_FORMAT_RAW                0x0001
+#define DIFF_FORMAT_DIFFSTAT   0x0002
+#define DIFF_FORMAT_SUMMARY    0x0004
+#define DIFF_FORMAT_PATCH      0x0008
+
+/* These override all above */
+#define DIFF_FORMAT_NAME       0x0010
+#define DIFF_FORMAT_NAME_STATUS        0x0020
+#define DIFF_FORMAT_CHECKDIFF  0x0040
+
+/* Same as output_format = 0 but we know that -s flag was given
+ * and we should not give default value to output_format.
+ */
+#define DIFF_FORMAT_NO_OUTPUT  0x0080
+
 struct diff_options {
        const char *filter;
        const char *orderfile;
        const char *pickaxe;
        unsigned recursive:1,
-                with_raw:1,
-                with_stat:1,
                 tree_in_recursive:1,
                 binary:1,
+                text:1,
                 full_index:1,
                 silent_on_remove:1,
                 find_copies_harder:1,
-                summary:1,
                 color_diff:1;
        int context;
        int break_opt;
@@ -45,6 +58,7 @@ struct diff_options {
        int rename_limit;
        int setup;
        int abbrev;
+       const char *msg_sep;
        const char *stat_sep;
        long xdl_opts;
 
@@ -55,6 +69,17 @@ struct diff_options {
        add_remove_fn_t add_remove;
 };
 
+enum color_diff {
+       DIFF_RESET = 0,
+       DIFF_PLAIN = 1,
+       DIFF_METAINFO = 2,
+       DIFF_FRAGINFO = 3,
+       DIFF_FILE_OLD = 4,
+       DIFF_FILE_NEW = 5,
+       DIFF_COMMIT = 6,
+};
+const char *diff_get_color(int diff_use_color, enum color_diff ix);
+
 extern const char mime_boundary_leader[];
 
 extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
@@ -109,7 +134,7 @@ extern int diff_scoreopt_parse(const char *opt);
 #define DIFF_SETUP_USE_CACHE           2
 #define DIFF_SETUP_USE_SIZE_CACHE      4
 
-extern int git_diff_config(const char *var, const char *value);
+extern int git_diff_ui_config(const char *var, const char *value);
 extern void diff_setup(struct diff_options *);
 extern int diff_opt_parse(struct diff_options *, const char **, int);
 extern int diff_setup_done(struct diff_options *);
@@ -148,18 +173,10 @@ extern void diffcore_std_no_resolve(struct diff_options *);
 "  -O<file>      reorder diffs according to the <file>.\n" \
 "  -S<string>    find filepair whose only one side contains the string.\n" \
 "  --pickaxe-all\n" \
-"                show all files diff when -S is used and hit is found.\n"
+"                show all files diff when -S is used and hit is found.\n" \
+"  -a  --text    treat all files as text.\n"
 
 extern int diff_queue_is_empty(void);
-
-#define DIFF_FORMAT_RAW                1
-#define DIFF_FORMAT_PATCH      2
-#define DIFF_FORMAT_NO_OUTPUT  3
-#define DIFF_FORMAT_NAME       4
-#define DIFF_FORMAT_NAME_STATUS        5
-#define DIFF_FORMAT_DIFFSTAT   6
-#define DIFF_FORMAT_CHECKDIFF  7
-
 extern void diff_flush(struct diff_options*);
 
 /* diff-raw status letters */
index d57e8656cdcf1b999df30c01d3a8a042bc3ebb75..1de8d32502a77b641e004e9df9da3fcc188eef9c 100644 (file)
@@ -96,11 +96,15 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
        return &(rename_src[first]);
 }
 
-static int is_exact_match(struct diff_filespec *src, struct diff_filespec *dst)
+static int is_exact_match(struct diff_filespec *src,
+                         struct diff_filespec *dst,
+                         int contents_too)
 {
        if (src->sha1_valid && dst->sha1_valid &&
            !memcmp(src->sha1, dst->sha1, 20))
                return 1;
+       if (!contents_too)
+               return 0;
        if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1))
                return 0;
        if (src->size != dst->size)
@@ -242,7 +246,7 @@ void diffcore_rename(struct diff_options *options)
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
        struct diff_score *mx;
-       int i, j, rename_count;
+       int i, j, rename_count, contents_too;
        int num_create, num_src, dst_cnt;
 
        if (!minimum_score)
@@ -273,16 +277,23 @@ void diffcore_rename(struct diff_options *options)
 
        /* We really want to cull the candidates list early
         * with cheap tests in order to avoid doing deltas.
+        * The first round matches up the up-to-date entries,
+        * and then during the second round we try to match
+        * cache-dirty entries as well.
         */
-       for (i = 0; i < rename_dst_nr; i++) {
-               struct diff_filespec *two = rename_dst[i].two;
-               for (j = 0; j < rename_src_nr; j++) {
-                       struct diff_filespec *one = rename_src[j].one;
-                       if (!is_exact_match(one, two))
-                               continue;
-                       record_rename_pair(i, j, MAX_SCORE);
-                       rename_count++;
-                       break; /* we are done with this entry */
+       for (contents_too = 0; contents_too < 2; contents_too++) {
+               for (i = 0; i < rename_dst_nr; i++) {
+                       struct diff_filespec *two = rename_dst[i].two;
+                       if (rename_dst[i].pair)
+                               continue; /* dealt with an earlier round */
+                       for (j = 0; j < rename_src_nr; j++) {
+                               struct diff_filespec *one = rename_src[j].one;
+                               if (!is_exact_match(one, two, contents_too))
+                                       continue;
+                               record_rename_pair(i, j, MAX_SCORE);
+                               rename_count++;
+                               break; /* we are done with this entry */
+                       }
                }
        }
 
diff --git a/dir.c b/dir.c
index d778ecd8908d7a32519cc9c31d8333de3fad0ae6..092d07736c60d4c6d86201ebeb841498c3be92a0 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -336,7 +336,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                                if (dir->show_other_directories &&
                                    (subdir || !dir->hide_empty_directories) &&
                                    !dir_exists(fullname, baselen + len)) {
-                                       // Rewind the read subdirectory
+                                       /* Rewind the read subdirectory */
                                        while (dir->nr > rewind_base)
                                                free(dir->entries[--dir->nr]);
                                        break;
index 3de8eb3b2a2359a9f8a9f702076292f1e1429df3..42f39d657ef0056667bb8814622e0dde2733e373 100644 (file)
@@ -11,6 +11,7 @@
 
 char git_default_email[MAX_GITNAME];
 char git_default_name[MAX_GITNAME];
+int use_legacy_headers = 1;
 int trust_executable_bit = 1;
 int assume_unchanged = 0;
 int prefer_symlink_refs = 0;
@@ -20,6 +21,8 @@ int repository_format_version = 0;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
 int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace = NULL;
+int zlib_compression_level = Z_DEFAULT_COMPRESSION;
+int pager_in_use;
 
 static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
        *git_graft_file;
index c1539d12ce9e350811b5b110206f5577a151e8ef..62f51fcd6e367d2dc7e3dc8d967ee05fd1648d04 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "exec_cmd.h"
+#include "quote.h"
 #define MAX_ARGS       32
 
 extern char **environ;
@@ -96,9 +97,27 @@ int execv_git_cmd(const char **argv)
                tmp = argv[0];
                argv[0] = git_command;
 
+               if (getenv("GIT_TRACE")) {
+                       const char **p = argv;
+                       fputs("trace: exec:", stderr);
+                       while (*p) {
+                               fputc(' ', stderr);
+                               sq_quote_print(stderr, *p);
+                               ++p;
+                       }
+                       putc('\n', stderr);
+                       fflush(stderr);
+               }
+
                /* execve() can only ever return if it fails */
                execve(git_command, (char **)argv, environ);
 
+               if (getenv("GIT_TRACE")) {
+                       fprintf(stderr, "trace: exec failed: %s\n",
+                               strerror(errno));
+                       fflush(stderr);
+               }
+
                argv[0] = tmp;
        }
        return -1;
index c16b0c481bb4b7dd3810e7dfbcb21243572f6844..81d13712962dfcf16c4f0f5a574b6950f6c3f154 100644 (file)
@@ -198,8 +198,8 @@ int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband)
 
 /*
  * A "binary msec" is a power-of-two-msec, aka 1/1024th of a second.
- * Keeing the time in that format means that "bytes / msecs" means
- * is the same as kB/s (modulo rounding).
+ * Keeping the time in that format means that "bytes / msecs" means
+ * the same as kB/s (modulo rounding).
  *
  * 1000512 is a magic number (usecs in a second, rounded up by half
  * of 1024, to make "rounding" come out right ;)
index f2c51ebe4b2b524e5d5bce6af8aa3105df19f177..b7824dbed4e10d912a0977865e24e21ff9295aae 100644 (file)
@@ -46,7 +46,7 @@ static int rev_list_insert_ref(const char *path, const unsigned char *sha1)
 {
        struct object *o = deref_tag(parse_object(sha1), path, 0);
 
-       if (o && o->type == TYPE_COMMIT)
+       if (o && o->type == OBJ_COMMIT)
                rev_list_push((struct commit *)o, SEEN);
 
        return 0;
@@ -256,14 +256,14 @@ static int mark_complete(const char *path, const unsigned char *sha1)
 {
        struct object *o = parse_object(sha1);
 
-       while (o && o->type == TYPE_TAG) {
+       while (o && o->type == OBJ_TAG) {
                struct tag *t = (struct tag *) o;
                if (!t->tagged)
                        break; /* broken repository */
                o->flags |= COMPLETE;
                o = parse_object(t->tagged->sha1);
        }
-       if (o && o->type == TYPE_COMMIT) {
+       if (o && o->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *)o;
                commit->object.flags |= COMPLETE;
                insert_by_date(commit, &complete);
@@ -357,7 +357,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
                 * in sync with the other side at some time after
                 * that (it is OK if we guess wrong here).
                 */
-               if (o->type == TYPE_COMMIT) {
+               if (o->type == OBJ_COMMIT) {
                        struct commit *commit = (struct commit *)o;
                        if (!cutoff || cutoff < commit->date)
                                cutoff = commit->date;
@@ -376,7 +376,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
                struct object *o = deref_tag(lookup_object(ref->old_sha1),
                                             NULL, 0);
 
-               if (!o || o->type != TYPE_COMMIT || !(o->flags & COMPLETE))
+               if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
                        continue;
 
                if (!(o->flags & SEEN)) {
diff --git a/fetch.c b/fetch.c
index 238032b798008cd1f96d5083b63dc582f8191a1d..989d7a47884ec7bac05d329baeb97bdb12b7b22e 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -118,20 +118,20 @@ static struct object_list **process_queue_end = &process_queue;
 
 static int process_object(struct object *obj)
 {
-       if (obj->type == TYPE_COMMIT) {
+       if (obj->type == OBJ_COMMIT) {
                if (process_commit((struct commit *)obj))
                        return -1;
                return 0;
        }
-       if (obj->type == TYPE_TREE) {
+       if (obj->type == OBJ_TREE) {
                if (process_tree((struct tree *)obj))
                        return -1;
                return 0;
        }
-       if (obj->type == TYPE_BLOB) {
+       if (obj->type == OBJ_BLOB) {
                return 0;
        }
-       if (obj->type == TYPE_TAG) {
+       if (obj->type == OBJ_TAG) {
                if (process_tag((struct tag *)obj))
                        return -1;
                return 0;
index ef54a8a411b01bb4f6becd24c68fad3fcbd15211..e167f4105ffcf50f0ebfe03378b687569b6a668e 100644 (file)
@@ -297,13 +297,13 @@ static int fsck_sha1(unsigned char *sha1)
        if (obj->flags & SEEN)
                return 0;
        obj->flags |= SEEN;
-       if (obj->type == TYPE_BLOB)
+       if (obj->type == OBJ_BLOB)
                return 0;
-       if (obj->type == TYPE_TREE)
+       if (obj->type == OBJ_TREE)
                return fsck_tree((struct tree *) obj);
-       if (obj->type == TYPE_COMMIT)
+       if (obj->type == OBJ_COMMIT)
                return fsck_commit((struct commit *) obj);
-       if (obj->type == TYPE_TAG)
+       if (obj->type == OBJ_TAG)
                return fsck_tag((struct tag *) obj);
        /* By now, parse_object() would've returned NULL instead. */
        return objerror(obj, "unknown type '%d' (internal fsck error)", obj->type);
@@ -472,7 +472,7 @@ static int fsck_cache_tree(struct cache_tree *it)
                }
                mark_reachable(obj, REACHABLE);
                obj->used = 1;
-               if (obj->type != TYPE_TREE)
+               if (obj->type != OBJ_TREE)
                        err |= objerror(obj, "non-tree in cache-tree");
        }
        for (i = 0; i < it->subtree_nr; i++)
index 679045a540c60e747074a9127bb7003f8a510b1c..04f01194356ea0c91a01eed682b068c15e313ee6 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -77,7 +77,7 @@ fall_back_3way () {
     # This is not so wrong.  Depending on which base we picked,
     # orig_tree may be wildly different from ours, but his_tree
     # has the same set of wildly different changes in parts the
-    # patch did not touch, so resolve ends up cancelling them,
+    # patch did not touch, so resolve ends up canceling them,
     # saying that we reverted all those changes.
 
     git-merge-resolve $orig_tree -- HEAD $his_tree || {
@@ -91,6 +91,7 @@ fall_back_3way () {
 }
 
 prec=4
+rloga=am
 dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
 
 while case "$#" in 0) break;; esac
@@ -130,6 +131,9 @@ do
        --resolvemsg=*)
        resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
 
+       --reflog-action=*)
+       rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;;
+
        --)
        shift; break ;;
        -*)
@@ -152,8 +156,10 @@ fi
 
 if test -d "$dotest"
 then
-       test ",$#," = ",0," ||
-       die "previous dotest directory $dotest still exists but mbox given."
+       if test ",$#," != ",0," || ! tty -s
+       then
+               die "previous dotest directory $dotest still exists but mbox given."
+       fi
        resume=yes
 else
        # Make sure we are not given --skip nor --resolved
@@ -413,7 +419,7 @@ do
        parent=$(git-rev-parse --verify HEAD) &&
        commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
        echo Committed: $commit &&
-       git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
+       git-update-ref -m "$rloga: $SUBJECT" HEAD $commit $parent ||
        stop_here $this
 
        if test -x "$GIT_DIR"/hooks/post-applypatch
index a6a7a482cdcf34e3a1d545c7d5deab599a7c0d2f..6db2f48241d0f393e43413ddb52d0986c523e372 100755 (executable)
@@ -102,10 +102,10 @@ while (my $bound = pop @stack) {
 push @revqueue, $head;
 init_claim( defined $starting_rev ? $head : 'dirty');
 unless (defined $starting_rev) {
-       my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename)
+       my $diff = open_pipe("git","diff","HEAD", "--",$filename)
                or die "Failed to call git diff to check for dirty state: $!";
 
-       _git_diff_parse($diff, $head, "dirty", (
+       _git_diff_parse($diff, [$head], "dirty", (
                                'author' => gitvar_name("GIT_AUTHOR_IDENT"),
                                'author_date' => sprintf("%s +0000",time()),
                                )
@@ -154,14 +154,13 @@ sub handle_rev {
 
                my %revinfo = git_commit_info($rev);
 
-               foreach my $p (@{$revs{$rev}{'parents'}}) {
-
-                       git_diff_parse($p, $rev, %revinfo);
-                       push @revqueue, $p;
-               }
+               if (exists $revs{$rev}{parents} &&
+                   scalar @{$revs{$rev}{parents}} != 0) {
 
+                       git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo);
+                       push @revqueue, @{$revs{$rev}{'parents'}};
 
-               if (scalar @{$revs{$rev}{parents}} == 0) {
+               } else {
                        # We must be at the initial rev here, so claim everything that is left.
                        for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) {
                                if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') {
@@ -252,89 +251,171 @@ sub git_find_parent {
 # Get a diff between the current revision and a parent.
 # Record the commit information that results.
 sub git_diff_parse {
-       my ($parent, $rev, %revinfo) = @_;
+       my ($parents, $rev, %revinfo) = @_;
 
-       my $diff = open_pipe("git-diff-tree","-M","-p",$rev,$parent,"--",
-                       $revs{$rev}{'filename'}, $revs{$parent}{'filename'})
+       my @filenames = ( $revs{$rev}{'filename'} );
+       foreach my $parent (@$parents) {
+               push @filenames, $revs{$parent}{'filename'};
+       }
+
+       my $diff = open_pipe("git-diff-tree","-M","-p","-c",$rev,"--",
+                               @filenames )
                or die "Failed to call git-diff for annotation: $!";
 
-       _git_diff_parse($diff, $parent, $rev, %revinfo);
+       _git_diff_parse($diff, $parents, $rev, %revinfo);
 
        close($diff);
 }
 
 sub _git_diff_parse {
-       my ($diff, $parent, $rev, %revinfo) = @_;
+       my ($diff, $parents, $rev, %revinfo) = @_;
+
+       my $ri = 0;
 
-       my ($ri, $pi) = (0,0);
        my $slines = $revs{$rev}{'lines'};
-       my @plines;
+       my (%plines, %pi);
 
        my $gotheader = 0;
        my ($remstart);
-       my ($hunk_start, $hunk_index);
+       my $parent_count = @$parents;
+
+       my $diff_header_regexp = "^@";
+       $diff_header_regexp .= "@" x @$parents;
+       $diff_header_regexp .= ' -\d+,\d+' x @$parents;
+       $diff_header_regexp .= ' \+(\d+),\d+';
+
+       my %claim_regexps;
+       my $allparentplus = '^' . '\\+' x @$parents . '(.*)$';
+
+       {
+               my $i = 0;
+               foreach my $parent (@$parents) {
+
+                       $pi{$parent} = 0;
+                       my $r = '^' . '.' x @$parents . '(.*)$';
+                       my $p = $r;
+                       substr($p,$i+1, 1) = '\\+';
+
+                       my $m = $r;
+                       substr($m,$i+1, 1) = '-';
+
+                       $claim_regexps{$parent}{plus} = $p;
+                       $claim_regexps{$parent}{minus} = $m;
+
+                       $plines{$parent} = [];
+
+                       $i++;
+               }
+       }
+
+       DIFF:
        while(<$diff>) {
                chomp;
-               if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) {
-                       $remstart = $1;
-                       # Adjust for 0-based arrays
-                       $remstart--;
-                       # Reinit hunk tracking.
-                       $hunk_start = $remstart;
-                       $hunk_index = 0;
+               if (m/$diff_header_regexp/) {
+                       $remstart = $1 - 1;
+                       # (0-based arrays)
+
                        $gotheader = 1;
 
-                       for (my $i = $ri; $i < $remstart; $i++) {
-                               $plines[$pi++] = $slines->[$i];
-                               $ri++;
+                       printf("Copying from %d to %d\n", $ri, $remstart);
+                       foreach my $parent (@$parents) {
+                               for (my $i = $ri; $i < $remstart; $i++) {
+                                       $plines{$parent}[$pi{$parent}++] = $slines->[$i];
+                               }
                        }
-                       next;
-               } elsif (!$gotheader) {
-                       next;
-               }
+                       $ri = $remstart;
 
-               if (m/^\+(.*)$/) {
-                       my $line = $1;
-                       $plines[$pi++] = [ $line, '', '', '', 0 ];
-                       next;
+                       next DIFF;
 
-               } elsif (m/^-(.*)$/) {
-                       my $line = $1;
-                       if (get_line($slines, $ri) eq $line) {
-                               # Found a match, claim
-                               claim_line($ri, $rev, $slines, %revinfo);
-                       } else {
-                               die sprintf("Sync error: %d/%d\n|%s\n|%s\n%s => %s\n",
-                                               $ri, $hunk_start + $hunk_index,
-                                               $line,
-                                               get_line($slines, $ri),
-                                               $rev, $parent);
-                       }
-                       $ri++;
+               } elsif (!$gotheader) {
+                       # Skip over the leadin.
+                       next DIFF;
+               }
 
-               } elsif (m/^\\/) {
+               if (m/^\\/) {
                        ;
                        # Skip \No newline at end of file.
                        # But this can be internationalized, so only look
                        # for an initial \
 
                } else {
-                       if (substr($_,1) ne get_line($slines,$ri) ) {
-                               die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n",
-                                               $hunk_start + $hunk_index, $ri,
-                                               substr($_,1),
-                                               get_line($slines,$ri),
-                                               $rev, $parent);
+                       my %claims = ();
+                       my $negclaim = 0;
+                       my $allclaimed = 0;
+                       my $line;
+
+                       if (m/$allparentplus/) {
+                               claim_line($ri, $rev, $slines, %revinfo);
+                               $allclaimed = 1;
+
+                       }
+
+                       PARENT:
+                       foreach my $parent (keys %claim_regexps) {
+                               my $m = $claim_regexps{$parent}{minus};
+                               my $p = $claim_regexps{$parent}{plus};
+
+                               if (m/$m/) {
+                                       $line = $1;
+                                       $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ];
+                                       $negclaim++;
+
+                               } elsif (m/$p/) {
+                                       $line = $1;
+                                       if (get_line($slines, $ri) eq $line) {
+                                               # Found a match, claim
+                                               $claims{$parent}++;
+
+                                       } else {
+                                               die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n",
+                                                               $ri, $line,
+                                                               get_line($slines, $ri),
+                                                               $rev, $parent);
+                                       }
+                               }
+                       }
+
+                       if (%claims) {
+                               foreach my $parent (@$parents) {
+                                       next if $claims{$parent} || $allclaimed;
+                                       $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
+                                           #[ $line, '', '', '', 0 ];
+                               }
+                               $ri++;
+
+                       } elsif ($negclaim) {
+                               next DIFF;
+
+                       } else {
+                               if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) {
+                                       foreach my $parent (@$parents) {
+                                               printf("parent %s is on line %d\n", $parent, $pi{$parent});
+                                       }
+
+                                       die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n",
+                                                   $ri,
+                                               substr($_,scalar @$parents),
+                                               get_line($slines,$ri), $rev);
+                               }
+                               foreach my $parent (@$parents) {
+                                       $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
+                               }
+                               $ri++;
                        }
-                       $plines[$pi++] = $slines->[$ri++];
                }
-               $hunk_index++;
        }
+
        for (my $i = $ri; $i < @{$slines} ; $i++) {
-               push @plines, $slines->[$ri++];
+               foreach my $parent (@$parents) {
+                       push @{$plines{$parent}}, $slines->[$ri];
+               }
+               $ri++;
+       }
+
+       foreach my $parent (@$parents) {
+               $revs{$parent}{lines} = $plines{$parent};
        }
 
-       $revs{$parent}{lines} = \@plines;
        return;
 }
 
index e4b09472e1fa27a6431b431572af7353c4f0a9b2..8df2aee4c2d031ac10c66af0a079bd022c281c0d 100755 (executable)
@@ -182,7 +182,7 @@ git-apply --index "$PATCHFILE" || {
        # This is not so wrong.  Depending on which base we picked,
        # orig_tree may be wildly different from ours, but his_tree
        # has the same set of wildly different changes in parts the
-       # patch did not touch, so resolve ends up cancelling them,
+       # patch did not touch, so resolve ends up canceling them,
        # saying that we reverted all those changes.
 
        if git-merge-resolve $orig_tree -- HEAD $his_tree
index 740bc1fd52286dfb486570bf6ea727e9cbaefbfc..ada60ec240a8b3eb4ef6efea6b6223fc8523705f 100755 (executable)
@@ -14,7 +14,7 @@
 
 Imports a project from one or more Arch repositories. It will follow branches
 and repositories within the namespaces defined by the <archive/branch>
-parameters suppplied. If it cannot find the remote branch a merge comes from
+parameters supplied. If it cannot find the remote branch a merge comes from
 it will just import it as a regular commit. If it can find it, it will mark it 
 as a merge whenever possible.
 
@@ -88,7 +88,7 @@ usage if $opt_h;
 # $arch_branches:
 # values associated with keys:
 #   =1 - Arch version / git 'branch' detected via abrowse on a limit
-#   >1 - Arch version / git 'branch' of an auxilliary branch we've merged
+#   >1 - Arch version / git 'branch' of an auxiliary branch we've merged
 my %arch_branches = map { $_ => 1 } @ARGV;
 
 $ENV{'TMPDIR'} = $opt_t if $opt_t; # $ENV{TMPDIR} will affect tempdir() calls:
@@ -667,7 +667,7 @@ sub apply_cset {
     if (`find $tmp/changeset/patches -type f -name '*.patch'`) {
         # this can be sped up considerably by doing
         #    (find | xargs cat) | patch
-        # but that cna get mucked up by patches
+        # but that can get mucked up by patches
         # with missing trailing newlines or the standard 
         # 'missing newline' flag in the patch - possibly
         # produced with an old/buggy diff.
@@ -1026,7 +1026,7 @@ sub commitid2pset {
 }
 
 
-# an alterative to `command` that allows input to be passed as an array
+# an alternative to `command` that allows input to be passed as an array
 # to work around shell problems with weird characters in arguments
 sub safe_pipe_capture {
     my @output;
index 03df1433ef12014584f23f16e65124c219c2f466..06a8d26945a679b06438308ceb96c69cd76c43db 100755 (executable)
@@ -13,7 +13,7 @@ git bisect log                        show bisect log.'
 . git-sh-setup
 
 sq() {
-       perl -e '
+       @@PERL@@ -e '
                for (@ARGV) {
                        s/'\''/'\'\\\\\'\''/g;
                        print " '\''$_'\''";
index 6a14b2591136d0bbfbec333c2304a7f003134ac2..a92b22a13d6b2932a8d816a8845d791c2f0e36d1 100755 (executable)
@@ -266,7 +266,7 @@ yes,yes)
            echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates"
            ;;
        esac
-       git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD"
+       git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
        ;;
 *)
        case "$repo" in
@@ -296,7 +296,7 @@ yes,yes)
                    done
                    rm -f "$GIT_DIR/TMP_ALT"
                fi
-               git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD"
+               git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
                ;;
        http://*)
                if test -z "@@NO_CURL@@"
@@ -324,7 +324,7 @@ test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
 if test -f "$GIT_DIR/CLONE_HEAD"
 then
        # Read git-fetch-pack -k output and store the remote branches.
-       perl -e "$copy_refs" "$GIT_DIR" "$use_separate_remote" "$origin"
+       @@PERL@@ -e "$copy_refs" "$GIT_DIR" "$use_separate_remote" "$origin"
 fi
 
 cd "$D" || exit
index 22c4ce86c3cc5b35fab27bbe9e75dbe334f58534..4cf3fab05cd3c3367173c86a65665ddfc1d89805 100755 (executable)
@@ -138,32 +138,26 @@ run_status () {
         if test -z "$untracked_files"; then
             option="--directory --no-empty-directory"
         fi
+       hdr_shown=
        if test -f "$GIT_DIR/info/exclude"
        then
-           git-ls-files -z --others $option \
+           git-ls-files --others $option \
                --exclude-from="$GIT_DIR/info/exclude" \
                --exclude-per-directory=.gitignore
        else
-           git-ls-files -z --others $option \
+           git-ls-files --others $option \
                --exclude-per-directory=.gitignore
        fi |
-       perl -e '$/ = "\0";
-           my $shown = 0;
-           while (<>) {
-               chomp;
-               s|\\|\\\\|g;
-               s|\t|\\t|g;
-               s|\n|\\n|g;
-               s/^/#   /;
-               if (!$shown) {
-                   print "#\n# Untracked files:\n";
-                   print "#   (use \"git add\" to add to commit)\n";
-                   print "#\n";
-                   $shown = 1;
-               }
-               print "$_\n";
-           }
-       '
+       while read line; do
+           if [ -z "$hdr_shown" ]; then
+               echo '#'
+               echo '# Untracked files:'
+               echo '#   (use "git add" to add to commit)'
+               echo '#'
+               hdr_shown=1
+           fi
+           echo "#     $line"
+       done
 
        if test -n "$verbose" -a -z "$IS_INITIAL"
        then
@@ -599,7 +593,7 @@ then
        GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
        test '' != "$GIT_AUTHOR_NAME" &&
        test '' != "$GIT_AUTHOR_EMAIL" ||
-       die "malformatted --author parameter"
+       die "malformed --author parameter"
        export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
 elif test '' != "$use_commit"
 then
@@ -635,9 +629,12 @@ fi
 PARENTS="-p HEAD"
 if test -z "$initial_commit"
 then
+       rloga='commit'
        if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+               rloga='commit (merge)'
                PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
        elif test -n "$amend"; then
+               rloga='commit (amend)'
                PARENTS=$(git-cat-file commit HEAD |
                        sed -n -e '/^$/q' -e 's/^parent /-p /p')
        fi
@@ -649,6 +646,7 @@ else
        fi
        PARENTS=""
        current=
+       rloga='commit (initial)'
 fi
 
 if test -z "$no_edit"
@@ -724,7 +722,7 @@ then
        fi &&
        commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
        rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
-       git-update-ref -m "commit: $rlogm" HEAD $commit $current &&
+       git-update-ref -m "$rloga: $rlogm" HEAD $commit $current &&
        rm -f -- "$GIT_DIR/MERGE_HEAD" &&
        if test -f "$NEXT_INDEX"
        then
index d1051d074bcf685dcc57cae90cca9053dc71a76f..99b3dc392afdd5ffb3e2aa8f6fe6b484c2e2f1fe 100755 (executable)
@@ -16,9 +16,9 @@ unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
     die "GIT_DIR is not defined or is unreadable";
 }
 
-our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_m );
+our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m );
 
-getopts('hpvcfm:');
+getopts('hpvcfam:');
 
 $opt_h && usage();
 
@@ -29,7 +29,6 @@ our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX',
                                     TMPDIR => 1,
                                     CLEANUP => 1);
 
-print Dumper(@ARGV);
 # resolve target commit
 my $commit;
 $commit = pop @ARGV;
@@ -53,25 +52,45 @@ if (@ARGV) {
 # find parents from the commit itself
 my @commit  = safe_pipe_capture('git-cat-file', 'commit', $commit);
 my @parents;
-foreach my $p (@commit) {
-    if ($p =~ m/^$/) { # end of commit headers, we're done
-       last;
+my $committer;
+my $author;
+my $stage = 'headers'; # headers, msg
+my $title;
+my $msg = '';
+
+foreach my $line (@commit) {
+    chomp $line;
+    if ($stage eq 'headers' && $line eq '') {
+       $stage = 'msg';
+       next;
     }
-    if ($p =~ m/^parent (\w{40})$/) { # found a parent
-       push @parents, $1;
+
+    if ($stage eq 'headers') {
+       if ($line =~ m/^parent (\w{40})$/) { # found a parent
+           push @parents, $1;
+       } elsif ($line =~ m/^author (.+) \d+ \+\d+$/) {
+           $author = $1;
+       } elsif ($line =~ m/^committer (.+) \d+ \+\d+$/) {
+           $committer = $1;
+       }
+    } else {
+       $msg .= $line . "\n";
+       unless ($title) {
+           $title = $line;
+       }
     }
 }
 
 if ($parent) {
+    my $found;
     # double check that it's a valid parent
     foreach my $p (@parents) {
-       my $found;
        if ($p eq $parent) {
            $found = 1;
            last;
        }; # found it
-       die "Did not find $parent in the parents for this commit!";
     }
+    die "Did not find $parent in the parents for this commit!" if !$found;
 } else { # we don't have a parent from the cmdline...
     if (@parents == 1) { # it's safe to get it from the commit
        $parent = $parents[0];
@@ -84,12 +103,18 @@ $opt_v && print "Applying to CVS commit $commit from parent $parent\n";
 
 # grab the commit message
 open(MSG, ">.msg") or die "Cannot open .msg for writing";
-print MSG $opt_m;
+if ($opt_m) {
+    print MSG $opt_m;
+}
+print MSG $msg;
+if ($opt_a) {
+    print MSG "\n\nAuthor: $author\n";
+    if ($author ne $committer) {
+       print MSG "Committer: $committer\n";
+    }
+}
 close MSG;
 
-`git-cat-file commit $commit | sed -e '1,/^\$/d' >> .msg`;
-$? && die "Error extracting the commit message";
-
 my (@afiles, @dfiles, @mfiles, @dirs);
 my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);
 #print @files;
@@ -179,7 +204,7 @@ my @bfiles = grep(m/^Binary/, safe_pipe_capture('git-diff-tree', '-p', $parent,
 @bfiles = map { chomp } @bfiles;
 foreach my $f (@bfiles) {
     # check that the file in cvs matches the "old" file
-    # extract the file to $tmpdir and comparre with cmp
+    # extract the file to $tmpdir and compare with cmp
     my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}");
     chomp $tree;
     my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
@@ -233,6 +258,7 @@ foreach my $f (@dfiles) {
 }
 
 print "Commit to CVS\n";
+print "Patch: $title\n";
 my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);
 my $cmd = "cvs commit -F .msg $commitfiles";
 
@@ -273,7 +299,7 @@ sub cleanupcvs {
     }
 }
 
-# An alterative to `command` that allows input to be passed as an array
+# An alternative to `command` that allows input to be passed as an array
 # to work around shell problems with weird characters in arguments
 # if the exec returns non-zero we die
 sub safe_pipe_capture {
index 5ccca4f99f31b9fa370baaf7e85795ed2bf49f13..5b73837bb1e1f0b29ec089942a6a785fc38f1cbc 100755 (executable)
@@ -81,7 +81,7 @@ my $methods = {
 
 # $state holds all the bits of information the clients sends us that could
 # potentially be useful when it comes to actually _doing_ something.
-my $state = {};
+my $state = { prependdir => '' };
 $log->info("--------------- STARTING -----------------");
 
 my $TEMP_DIR = tempdir( CLEANUP => 1 );
@@ -547,12 +547,15 @@ sub req_Argument
 {
     my ( $cmd, $data ) = @_;
 
-    # TODO :  Not quite sure how Argument and Argumentx differ, but I assume
-    # it's for multi-line arguments ... somehow ...
+    # Argumentx means: append to last Argument (with a newline in front)
 
     $log->debug("$cmd : $data");
 
-    push @{$state->{arguments}}, $data;
+    if ( $cmd eq 'Argumentx') {
+        ${$state->{arguments}}[$#{$state->{arguments}}] .= "\n" . $data;
+    } else {
+        push @{$state->{arguments}}, $data;
+    }
 }
 
 # expand-modules \n
@@ -779,7 +782,7 @@ sub req_update
 
     #$log->debug("update state : " . Dumper($state));
 
-    # foreach file specified on the commandline ...
+    # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
     {
         $filename = filecleanup($filename);
@@ -1031,7 +1034,7 @@ sub req_ci
 
     my @committedfiles = ();
 
-    # foreach file specified on the commandline ...
+    # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
     {
         my $committedfile = $filename;
@@ -1145,7 +1148,7 @@ sub req_ci
 
     $updater->update();
 
-    # foreach file specified on the commandline ...
+    # foreach file specified on the command line ...
     foreach my $filename ( @committedfiles )
     {
         $filename = filecleanup($filename);
@@ -1190,7 +1193,7 @@ sub req_status
     # if no files were specified, we need to work out what files we should be providing status on ...
     argsfromdir($updater);
 
-    # foreach file specified on the commandline ...
+    # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
     {
         $filename = filecleanup($filename);
@@ -1291,7 +1294,7 @@ sub req_diff
     # if no files were specified, we need to work out what files we should be providing status on ...
     argsfromdir($updater);
 
-    # foreach file specified on the commandline ...
+    # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
     {
         $filename = filecleanup($filename);
@@ -1433,7 +1436,7 @@ sub req_log
     # if no files were specified, we need to work out what files we should be providing status on ...
     argsfromdir($updater);
 
-    # foreach file specified on the commandline ...
+    # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
     {
         $filename = filecleanup($filename);
@@ -1519,7 +1522,7 @@ sub req_annotate
 
     chdir $tmpdir;
 
-    # foreach file specified on the commandline ...
+    # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
     {
         $filename = filecleanup($filename);
@@ -2129,12 +2132,6 @@ sub update
     # first lets get the commit list
     $ENV{GIT_DIR} = $self->{git_path};
 
-    # prepare database queries
-    my $db_insert_rev = $self->{dbh}->prepare_cached("INSERT INTO revision (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
-    my $db_insert_mergelog = $self->{dbh}->prepare_cached("INSERT INTO commitmsgs (key, value) VALUES (?,?)",{},1);
-    my $db_delete_head = $self->{dbh}->prepare_cached("DELETE FROM head",{},1);
-    my $db_insert_head = $self->{dbh}->prepare_cached("INSERT INTO head (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
-
     my $commitinfo = `git-cat-file commit $self->{module} 2>&1`;
     unless ( $commitinfo =~ /tree\s+[a-zA-Z0-9]{40}/ )
     {
@@ -2323,7 +2320,7 @@ sub update
                         author => $commit->{author},
                         mode => $git_perms,
                     };
-                    $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
                 }
                 elsif ( $3 eq "M" )
                 {
@@ -2337,7 +2334,7 @@ sub update
                         author => $commit->{author},
                         mode => $git_perms,
                     };
-                    $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
                 }
                 elsif ( $3 eq "A" )
                 {
@@ -2351,7 +2348,7 @@ sub update
                         author => $commit->{author},
                         mode => $git_perms,
                     };
-                    $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
                 }
                 else
                 {
@@ -2408,7 +2405,7 @@ sub update
                     };
 
 
-                    $db_insert_rev->execute($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
                 }
             }
             close FILELIST;
@@ -2424,7 +2421,7 @@ sub update
                     $head->{$file}{modified} = $commit->{date};
                     $head->{$file}{author} = $commit->{author};
 
-                    $db_insert_rev->execute($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
+                    $self->insert_rev($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
                 }
             }
             # END : "Detect deleted files"
@@ -2433,7 +2430,7 @@ sub update
 
         if (exists $commit->{mergemsg})
         {
-            $db_insert_mergelog->execute($commit->{hash}, $commit->{mergemsg});
+            $self->insert_mergelog($commit->{hash}, $commit->{mergemsg});
         }
 
         $lastpicked = $commit->{hash};
@@ -2441,10 +2438,10 @@ sub update
         $self->_set_prop("last_commit", $commit->{hash});
     }
 
-    $db_delete_head->execute();
+    $self->delete_head();
     foreach my $file ( keys %$head )
     {
-        $db_insert_head->execute(
+        $self->insert_head(
             $file,
             $head->{$file}{revision},
             $head->{$file}{filehash},
@@ -2462,6 +2459,54 @@ sub update
     $self->{dbh}->commit() or die "Failed to commit changes to SQLite";
 }
 
+sub insert_rev
+{
+    my $self = shift;
+    my $name = shift;
+    my $revision = shift;
+    my $filehash = shift;
+    my $commithash = shift;
+    my $modified = shift;
+    my $author = shift;
+    my $mode = shift;
+
+    my $insert_rev = $self->{dbh}->prepare_cached("INSERT INTO revision (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+    $insert_rev->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
+}
+
+sub insert_mergelog
+{
+    my $self = shift;
+    my $key = shift;
+    my $value = shift;
+
+    my $insert_mergelog = $self->{dbh}->prepare_cached("INSERT INTO commitmsgs (key, value) VALUES (?,?)",{},1);
+    $insert_mergelog->execute($key, $value);
+}
+
+sub delete_head
+{
+    my $self = shift;
+
+    my $delete_head = $self->{dbh}->prepare_cached("DELETE FROM head",{},1);
+    $delete_head->execute();
+}
+
+sub insert_head
+{
+    my $self = shift;
+    my $name = shift;
+    my $revision = shift;
+    my $filehash = shift;
+    my $commithash = shift;
+    my $modified = shift;
+    my $author = shift;
+    my $mode = shift;
+
+    my $insert_head = $self->{dbh}->prepare_cached("INSERT INTO head (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+    $insert_head->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
+}
+
 sub _headrev
 {
     my $self = shift;
index 48818f822451b3d69e7cd90e81e34bf137ead4dd..c2eebee798d10f7ed5977fc4e56cf02731031971 100755 (executable)
@@ -11,6 +11,7 @@ LF='
 '
 IFS="$LF"
 
+rloga=fetch
 no_tags=
 tags=
 append=
@@ -19,6 +20,7 @@ verbose=
 update_head_ok=
 exec=
 upload_pack=
+keep=--thin
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -51,6 +53,9 @@ do
        -k|--k|--ke|--kee|--keep)
                keep=--keep
                ;;
+       --reflog-action=*)
+               rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+               ;;
        -*)
                usage
                ;;
@@ -65,7 +70,8 @@ case "$#" in
 0)
        test -f "$GIT_DIR/branches/origin" ||
                test -f "$GIT_DIR/remotes/origin" ||
-                       die "Where do you want to fetch from today?"
+                       git-repo-config --get remote.origin.url >/dev/null ||
+                               die "Where do you want to fetch from today?"
        set origin ;;
 esac
 
@@ -75,6 +81,9 @@ refs=
 rref=
 rsync_slurped_objects=
 
+rloga="$rloga $remote_nick"
+test "$remote_nick" = "$remote" || rloga="$rloga $remote"
+
 if test "" = "$append"
 then
        : >"$GIT_DIR/FETCH_HEAD"
@@ -146,14 +155,15 @@ fast_forward_local () {
        then
                if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
                then
-                       [ "$verbose" ] && echo >&2 "* $1: same as $3"
+                       [ "$verbose" ] && echo >&2 "* $1: same as $3" ||:
                else
                        echo >&2 "* $1: updating with $3"
+                       git-update-ref -m "$rloga: updating tag" "$1" "$2"
                fi
        else
                echo >&2 "* $1: storing $3"
+               git-update-ref -m "$rloga: storing tag" "$1" "$2"
        fi
-       git-update-ref "$1" "$2"
        ;;
 
     refs/heads/* | refs/remotes/*)
@@ -174,7 +184,7 @@ fast_forward_local () {
            *,$local)
                echo >&2 "* $1: fast forward to $3"
                echo >&2 "  from $local to $2"
-               git-update-ref "$1" "$2" "$local"
+               git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local"
                ;;
            *)
                false
@@ -184,7 +194,7 @@ fast_forward_local () {
                case ",$force,$single_force," in
                *,t,*)
                        echo >&2 "  forcing update."
-                       git-update-ref "$1" "$2" "$local"
+                       git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local"
                        ;;
                *)
                        echo >&2 "  not updating."
@@ -194,7 +204,7 @@ fast_forward_local () {
            }
        else
            echo >&2 "* $1: storing $3"
-           git-update-ref "$1" "$2"
+           git-update-ref -m "$rloga: storing head" "$1" "$2"
        fi
        ;;
     esac
@@ -215,9 +225,16 @@ reflist=$(get_remote_refs_for_fetch "$@")
 if test "$tags"
 then
        taglist=`IFS="  " &&
-                 git-ls-remote $upload_pack --tags "$remote" |
+                 (
+                       git-ls-remote $upload_pack --tags "$remote" ||
+                       echo fail ouch
+                 ) |
                  while read sha1 name
                  do
+                       case "$sha1" in
+                       fail)
+                               exit 1
+                       esac
                        case "$name" in
                        *^*) continue ;;
                        esac
@@ -227,7 +244,7 @@ then
                        else
                            echo >&2 "warning: tag ${name} ignored"
                        fi
-                 done`
+                 done` || exit
        if test "$#" -gt 1
        then
                # remote URL plus explicit refspecs; we need to merge them.
@@ -278,7 +295,7 @@ fetch_main () {
          head="ref: $remote_name"
          while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
          do
-           remote_name_quoted=$(perl -e '
+           remote_name_quoted=$(@@PERL@@ -e '
              my $u = $ARGV[0];
               $u =~ s/^ref:\s*//;
              $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
@@ -339,7 +356,7 @@ fetch_main () {
     ( : subshell because we muck with IFS
       IFS="    $LF"
       (
-         git-fetch-pack $exec $keep --thin "$remote" $rref || echo failed "$remote"
+         git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote"
       ) |
       while read sha1 remote_name
       do
@@ -422,7 +439,9 @@ case ",$update_head_ok,$orig_head," in
        curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
        if test "$curr_head" != "$orig_head"
        then
-               git-update-ref HEAD "$orig_head"
+           git-update-ref \
+                       -m "$rloga: Undoing incorrectly fetched HEAD." \
+                       HEAD "$orig_head"
                die "Cannot fetch into the current branch."
        fi
        ;;
diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl
deleted file mode 100755 (executable)
index 5986e54..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-# Read .git/FETCH_HEAD and make a human readable merge message
-# by grouping branches and tags together to form a single line.
-
-use strict;
-
-my @src;
-my %src;
-sub andjoin {
-       my ($label, $labels, $stuff) = @_;
-       my $l = scalar @$stuff;
-       my $m = '';
-       if ($l == 0) {
-               return ();
-       }
-       if ($l == 1) {
-               $m = "$label$stuff->[0]";
-       }
-       else {
-               $m = ("$labels" .
-                     join (', ', @{$stuff}[0..$l-2]) .
-                     " and $stuff->[-1]");
-       }
-       return ($m);
-}
-
-sub repoconfig {
-       my ($val) = qx{git-repo-config --get merge.summary};
-       return $val;
-}
-
-sub current_branch {
-       my ($bra) = qx{git-symbolic-ref HEAD};
-       chomp($bra);
-       $bra =~ s|^refs/heads/||;
-       if ($bra ne 'master') {
-               $bra = " into $bra";
-       } else {
-               $bra = "";
-       }
-       return $bra;
-}
-
-sub shortlog {
-       my ($tip) = @_;
-       my @result;
-       foreach ( qx{git-log --no-merges --topo-order --pretty=oneline $tip ^HEAD} ) {
-               s/^[0-9a-f]{40}\s+//;
-               push @result, $_;
-       }
-       die "git-log failed\n" if $?;
-       return @result;
-}
-
-my @origin = ();
-while (<>) {
-       my ($bname, $tname, $gname, $src, $sha1, $origin);
-       chomp;
-       s/^([0-9a-f]*)  //;
-       $sha1 = $1;
-       next if (/^not-for-merge/);
-       s/^     //;
-       if (s/ of (.*)$//) {
-               $src = $1;
-       } else {
-               # Pulling HEAD
-               $src = $_;
-               $_ = 'HEAD';
-       }
-       if (! exists $src{$src}) {
-               push @src, $src;
-               $src{$src} = {
-                       BRANCH => [],
-                       TAG => [],
-                       R_BRANCH => [],
-                       GENERIC => [],
-                       # &1 == has HEAD.
-                       # &2 == has others.
-                       HEAD_STATUS => 0,
-               };
-       }
-       if (/^branch (.*)$/) {
-               $origin = $1;
-               push @{$src{$src}{BRANCH}}, $1;
-               $src{$src}{HEAD_STATUS} |= 2;
-       }
-       elsif (/^tag (.*)$/) {
-               $origin = $_;
-               push @{$src{$src}{TAG}}, $1;
-               $src{$src}{HEAD_STATUS} |= 2;
-       }
-       elsif (/^remote branch (.*)$/) {
-               $origin = $1;
-               push @{$src{$src}{R_BRANCH}}, $1;
-               $src{$src}{HEAD_STATUS} |= 2;
-       }
-       elsif (/^HEAD$/) {
-               $origin = $src;
-               $src{$src}{HEAD_STATUS} |= 1;
-       }
-       else {
-               push @{$src{$src}{GENERIC}}, $_;
-               $src{$src}{HEAD_STATUS} |= 2;
-               $origin = $src;
-       }
-       if ($src eq '.' || $src eq $origin) {
-               $origin =~ s/^'(.*)'$/$1/;
-               push @origin, [$sha1, "$origin"];
-       }
-       else {
-               push @origin, [$sha1, "$origin of $src"];
-       }
-}
-
-my @msg;
-for my $src (@src) {
-       if ($src{$src}{HEAD_STATUS} == 1) {
-               # Only HEAD is fetched, nothing else.
-               push @msg, $src;
-               next;
-       }
-       my @this;
-       if ($src{$src}{HEAD_STATUS} == 3) {
-               # HEAD is fetched among others.
-               push @this, andjoin('', '', ['HEAD']);
-       }
-       push @this, andjoin("branch ", "branches ",
-                          $src{$src}{BRANCH});
-       push @this, andjoin("remote branch ", "remote branches ",
-                          $src{$src}{R_BRANCH});
-       push @this, andjoin("tag ", "tags ",
-                          $src{$src}{TAG});
-       push @this, andjoin("commit ", "commits ",
-                           $src{$src}{GENERIC});
-       my $this = join(', ', @this);
-       if ($src ne '.') {
-               $this .= " of $src";
-       }
-       push @msg, $this;
-}
-
-my $into = current_branch();
-
-print "Merge ", join("; ", @msg), $into, "\n";
-
-if (!repoconfig) {
-       exit(0);
-}
-
-# We limit the merge message to the latst 20 or so per each branch.
-my $limit = 20;
-
-for (@origin) {
-       my ($sha1, $name) = @$_;
-       my @log = shortlog($sha1);
-       if ($limit + 1 <= @log) {
-               print "\n* $name: (" . scalar(@log) . " commits)\n";
-       }
-       else {
-               print "\n* $name:\n";
-       }
-       my $cnt = 0;
-       for my $log (@log) {
-               if ($limit < ++$cnt) {
-                       print "  ...\n";
-                       last;
-               }
-               print "  $log";
-       }
-}
diff --git a/git-instaweb.sh b/git-instaweb.sh
new file mode 100755 (executable)
index 0000000..9829c59
--- /dev/null
@@ -0,0 +1,237 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+USAGE='[--start] [--stop] [--restart]
+  [--local] [--httpd=<httpd>] [--port=<port>] [--browser=<browser>]
+  [--module-path=<path> (for Apache2 only)]'
+
+. git-sh-setup
+
+case "$GIT_DIR" in
+/*)
+       fqgitdir="$GIT_DIR" ;;
+*)
+       fqgitdir="$PWD/$GIT_DIR" ;;
+esac
+
+local="`git repo-config --bool --get instaweb.local`"
+httpd="`git repo-config --get instaweb.httpd`"
+browser="`git repo-config --get instaweb.browser`"
+port=`git repo-config --get instaweb.port`
+module_path="`git repo-config --get instaweb.modulepath`"
+
+conf=$GIT_DIR/gitweb/httpd.conf
+
+# Defaults:
+
+# if installed, it doesn't need further configuration (module_path)
+test -z "$httpd" && httpd='lighttpd -f'
+
+# probably the most popular browser among gitweb users
+test -z "$browser" && browser='firefox'
+
+# any untaken local port will do...
+test -z "$port" && port=1234
+
+start_httpd () {
+       httpd_only="`echo $httpd | cut -f1 -d' '`"
+       if test "`expr index $httpd_only /`" -eq '1' || \
+                               which $httpd_only >/dev/null
+       then
+               $httpd $fqgitdir/gitweb/httpd.conf
+       else
+               # many httpds are installed in /usr/sbin or /usr/local/sbin
+               # these days and those are not in most users $PATHs
+               for i in /usr/local/sbin /usr/sbin
+               do
+                       if test -x "$i/$httpd_only"
+                       then
+                               # don't quote $httpd, there can be
+                               # arguments to it (-f)
+                               $i/$httpd "$fqgitdir/gitweb/httpd.conf"
+                               return
+                       fi
+               done
+       fi
+}
+
+stop_httpd () {
+       test -f "$fqgitdir/pid" && kill `cat "$fqgitdir/pid"`
+}
+
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       --stop|stop)
+               stop_httpd
+               exit 0
+               ;;
+       --start|start)
+               start_httpd
+               exit 0
+               ;;
+       --restart|restart)
+               stop_httpd
+               start_httpd
+               exit 0
+               ;;
+       --local|-l)
+               local=true
+               ;;
+       -d|--httpd|--httpd=*)
+               case "$#,$1" in
+               *,*=*)
+                       httpd=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+               1,*)
+                       usage ;;
+               *)
+                       httpd="$2"
+                       shift ;;
+               esac
+               ;;
+       -b|--browser|--browser=*)
+               case "$#,$1" in
+               *,*=*)
+                       browser=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+               1,*)
+                       usage ;;
+               *)
+                       browser="$2"
+                       shift ;;
+               esac
+               ;;
+       -p|--port|--port=*)
+               case "$#,$1" in
+               *,*=*)
+                       port=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+               1,*)
+                       usage ;;
+               *)
+                       port="$2"
+                       shift ;;
+               esac
+               ;;
+       -m|--module-path=*|--module-path)
+               case "$#,$1" in
+               *,*=*)
+                       module_path=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+               1,*)
+                       usage ;;
+               *)
+                       module_path="$2"
+                       shift ;;
+               esac
+               ;;
+       *)
+               usage
+               ;;
+       esac
+       shift
+done
+
+mkdir -p "$GIT_DIR/gitweb/tmp"
+GIT_EXEC_PATH="`git --exec-path`"
+GIT_DIR="$fqgitdir"
+export GIT_EXEC_PATH GIT_DIR
+
+
+lighttpd_conf () {
+       cat > "$conf" <<EOF
+server.document-root = "$fqgitdir/gitweb"
+server.port = $port
+server.modules = ( "mod_cgi" )
+server.indexfiles = ( "gitweb.cgi" )
+server.pid-file = "$fqgitdir/pid"
+cgi.assign = ( ".cgi" => "" )
+mimetype.assign = ( ".css" => "text/css" )
+EOF
+       test "$local" = true && echo 'server.bind = "127.0.0.1"' >> "$conf"
+}
+
+apache2_conf () {
+       test -z "$module_path" && module_path=/usr/lib/apache2/modules
+       mkdir -p "$GIT_DIR/gitweb/logs"
+       bind=
+       test "$local" = true && bind='127.0.0.1:'
+       echo 'text/css css' > $fqgitdir/mime.types
+       cat > "$conf" <<EOF
+ServerRoot "$fqgitdir/gitweb"
+DocumentRoot "$fqgitdir/gitweb"
+PidFile "$fqgitdir/pid"
+Listen $bind$port
+TypesConfig $fqgitdir/mime.types
+DirectoryIndex gitweb.cgi
+EOF
+
+       # check to see if Dennis Stosberg's mod_perl compatibility patch
+       # (<20060621130708.Gcbc6e5c@leonov.stosberg.net>) has been applied
+       if test -f "$module_path/mod_perl.so" && grep '^our $gitbin' \
+                               "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null
+       then
+               # favor mod_perl if available
+               cat >> "$conf" <<EOF
+LoadModule perl_module $module_path/mod_perl.so
+PerlPassEnv GIT_DIR
+PerlPassEnv GIT_EXEC_DIR
+<Location /gitweb.cgi>
+       SetHandler perl-script
+       PerlResponseHandler ModPerl::Registry
+       PerlOptions +ParseHeaders
+       Options +ExecCGI
+</Location>
+EOF
+       else
+               # plain-old CGI
+               list_mods=`echo "$httpd" | sed "s/-f$/-l/"`
+               $list_mods | grep 'mod_cgi\.c' >/dev/null || \
+               echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf"
+               cat >> "$conf" <<EOF
+AddHandler cgi-script .cgi
+<Location /gitweb.cgi>
+       Options +ExecCGI
+</Location>
+EOF
+       fi
+}
+
+script='
+s#^\(my\|our\) $projectroot =.*#\1 $projectroot = "'`dirname $fqgitdir`'";#
+s#\(my\|our\) $gitbin =.*#\1 $gitbin = "'$GIT_EXEC_PATH'";#
+s#\(my\|our\) $projects_list =.*#\1 $projects_list = $projectroot;#
+s#\(my\|our\) $git_temp =.*#\1 $git_temp = "'$fqgitdir/gitweb/tmp'";#'
+
+gitweb_cgi () {
+       cat > "$1.tmp" <<\EOFGITWEB
+@@GITWEB_CGI@@
+EOFGITWEB
+       sed "$script" "$1.tmp"  > "$1"
+       chmod +x "$1"
+       rm -f "$1.tmp"
+}
+
+gitweb_css () {
+       cat > "$1" <<\EOFGITWEB
+@@GITWEB_CSS@@
+EOFGITWEB
+}
+
+gitweb_cgi $GIT_DIR/gitweb/gitweb.cgi
+gitweb_css $GIT_DIR/gitweb/gitweb.css
+
+case "$httpd" in
+*lighttpd*)
+       lighttpd_conf
+       ;;
+*apache2*)
+       apache2_conf
+       ;;
+*)
+       echo "Unknown httpd specified: $httpd"
+       exit 1
+       ;;
+esac
+
+start_httpd
+test -z "$browser" && browser=echo
+$browser http://127.0.0.1:$port
index 5619409f1c7ee9c694c2de59aff195f971502226..fba4b0cb5fffbb1ad3f39c670c6364975c52186a 100755 (executable)
@@ -8,7 +8,7 @@
 #   $2 - file in branch1 SHA1 (or empty)
 #   $3 - file in branch2 SHA1 (or empty)
 #   $4 - pathname in repository
-#   $5 - orignal file mode (or empty)
+#   $5 - original file mode (or empty)
 #   $6 - file in branch1 mode (or empty)
 #   $7 - file in branch2 mode (or empty)
 #
index ce8a31fda050f36b24b8dffa5ee29e7dde074963..4039435ce4a938fdf9cae4eaa74303d5cc7527d0 100755 (executable)
@@ -47,7 +47,7 @@ cacheOnly = False
 
 def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None):
     '''Merge the commits h1 and h2, return the resulting virtual
-    commit object and a flag indicating the cleaness of the merge.'''
+    commit object and a flag indicating the cleanness of the merge.'''
     assert(isinstance(h1, Commit) and isinstance(h2, Commit))
 
     global outputIndent
index 24e3b507ef1f907e7146a2653189f45a92db08fd..a9cfafb1dfba6e85834a33caf6b3c69910594bf6 100755 (executable)
@@ -58,7 +58,13 @@ squash_message () {
 }
 
 finish () {
-       test '' = "$2" || echo "$2"
+       if test '' = "$2"
+       then
+               rlogm="$rloga"
+       else
+               echo "$2"
+               rlogm="$rloga: $2"
+       fi
        case "$squash" in
        t)
                echo "Squash commit -- not updating HEAD"
@@ -70,7 +76,7 @@ finish () {
                        echo "No merge message -- not updating HEAD"
                        ;;
                *)
-                       git-update-ref HEAD "$1" "$head" || exit 1
+                       git-update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
                        ;;
                esac
                ;;
@@ -88,6 +94,7 @@ finish () {
        esac
 }
 
+rloga=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -117,6 +124,9 @@ do
                        die "available strategies are: $all_strategies" ;;
                esac
                ;;
+       --reflog-action=*)
+               rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+               ;;
        -*)     usage ;;
        *)      break ;;
        esac
@@ -131,6 +141,7 @@ shift
 
 # All the rest are remote heads
 test "$#" = 0 && usage ;# we need at least one remote head.
+test "$rloga" = '' && rloga="merge: $@"
 
 remoteheads=
 for remote
@@ -316,7 +327,7 @@ if test '' != "$result_tree"
 then
     parents=$(git-show-branch --independent "$head" "$@" | sed -e 's/^/-p /')
     result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents) || exit
-    finish "$result_commit" "Merge $result_commit, made by $wt_strategy."
+    finish "$result_commit" "Merge made by $wt_strategy."
     dropsave
     exit 0
 fi
diff --git a/git-prune.sh b/git-prune.sh
deleted file mode 100755 (executable)
index c5a5d29..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-
-USAGE='[-n] [--] [<head>...]'
-. git-sh-setup
-
-dryrun=
-echo=
-while case "$#" in 0) break ;; esac
-do
-    case "$1" in
-    -n) dryrun=-n echo=echo ;;
-    --) break ;;
-    -*) usage ;;
-    *)  break ;;
-    esac
-    shift;
-done
-
-sync
-case "$#" in
-0) git-fsck-objects --full --cache --unreachable ;;
-*) git-fsck-objects --full --cache --unreachable $(git-rev-parse --all) "$@" ;;
-esac |
-
-sed -ne '/unreachable /{
-    s/unreachable [^ ][^ ]* //
-    s|\(..\)|\1/|p
-}' | {
-       cd "$GIT_OBJECT_DIRECTORY" || exit
-       xargs $echo rm -f
-       rmdir 2>/dev/null [0-9a-f][0-9a-f]
-}
-
-git-prune-packed $dryrun
-
-if redundant=$(git-pack-redundant --all 2>/dev/null) && test "" != "$redundant"
-then
-       if test "" = "$dryrun"
-       then
-               echo "$redundant" | xargs rm -f
-       else
-               echo rm -f "$redundant"
-       fi
-fi
index 076785c96b30b4fd8ab944324fd53db7c584567a..f380437997f053d15a177a941f3b8a1543c56a18 100755 (executable)
@@ -45,7 +45,7 @@ do
 done
 
 orig_head=$(git-rev-parse --verify HEAD) || die "Pulling into a black hole?"
-git-fetch --update-head-ok "$@" || exit 1
+git-fetch --update-head-ok --reflog-action=pull "$@" || exit 1
 
 curr_head=$(git-rev-parse --verify HEAD)
 if test "$curr_head" != "$orig_head"
@@ -102,5 +102,6 @@ case "$strategy_args" in
 esac
 
 merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
-git-merge $no_summary $no_commit $squash $strategy_args \
+git-merge "--reflog-action=pull $*" \
+       $no_summary $no_commit $squash $strategy_args \
        "$merge_name" HEAD $merge_head
index f10cadbf159bcd63af0c3834bd7cd2fab6cce57c..21775fc21ae6668b04972ef942b2d90a789f4af6 100755 (executable)
@@ -63,7 +63,7 @@ esac
 shift ;# away the initial 'x'
 
 # $# is now 0 if there was no explicit refspec on the command line
-# and there was no defalt refspec to push from remotes/ file.
+# and there was no default refspec to push from remotes/ file.
 # we will let git-send-pack to do its "matching refs" thing.
 
 case "$remote" in
index 86b51abd21748c19235a703b1b523525d05383cd..10135da3ac04e3175fa45ca26b5ea31605a47284 100755 (executable)
@@ -49,7 +49,7 @@ if [ -n "$quilt_author" ] ; then
        quilt_author_email=$(expr "z$quilt_author" : '.*<\([^>]*\)') &&
        test '' != "$quilt_author_name" &&
        test '' != "$quilt_author_email" ||
-       die "malformatted --author parameter"
+       die "malformed --author parameter"
 fi
 
 # Quilt patch directory
@@ -112,7 +112,7 @@ for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do
                git-apply --index -C1 "$tmp_patch" &&
                tree=$(git-write-tree) &&
                commit=$((echo "$SUBJECT"; echo; cat "$tmp_msg") | git-commit-tree $tree -p $commit) &&
-               git-update-ref HEAD $commit || exit 4
+               git-update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
        fi
 done
 rm -rf $tmp_dir || exit 5
index 3945e067141ec1bc87456179a59de8f692bdf9fa..29028dd5fc0669cd2d7d3b956bfdd2583cc785b7 100755 (executable)
@@ -131,7 +131,8 @@ do
                        finish_rb_merge
                        exit
                fi
-               git am --resolved --3way --resolvemsg="$RESOLVEMSG"
+               git am --resolved --3way --resolvemsg="$RESOLVEMSG" \
+                       --reflog-action=rebase
                exit
                ;;
        --skip)
@@ -150,7 +151,8 @@ do
                        finish_rb_merge
                        exit
                fi
-               git am -3 --skip --resolvemsg="$RESOLVEMSG"
+               git am -3 --skip --resolvemsg="$RESOLVEMSG" \
+                       --reflog-action=rebase
                exit
                ;;
        --abort)
@@ -288,7 +290,8 @@ fi
 if test -z "$do_merge"
 then
        git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD |
-       git am --binary -3 -k --resolvemsg="$RESOLVEMSG"
+       git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \
+               --reflog-action=rebase
        exit $?
 fi
 
@@ -311,7 +314,7 @@ echo "$prev_head" > "$dotest/prev_head"
 
 msgnum=0
 for cmt in `git-rev-list --no-merges "$upstream"..ORIG_HEAD \
-                       | perl -e 'print reverse <>'`
+                       | @@PERL@@ -e 'print reverse <>'`
 do
        msgnum=$(($msgnum + 1))
        echo "$cmt" > "$dotest/cmt.$msgnum"
index 640ad8d90b9a9afd00506a9697af1d3e560e1255..9da92fb06139dc53061876f1532d9ee8c46d5346 100755 (executable)
@@ -43,7 +43,9 @@ case ",$all_into_one," in
        ;;
 esac
 pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra"
-name=$(git-rev-list --objects --all $rev_list 2>&1 |
+name=$( { git-rev-list --objects --all $rev_list ||
+         echo "git-rev-list died with exit code $?"
+       } |
        git-pack-objects --non-empty $pack_objects .tmp-pack) ||
        exit 1
 if [ -z "$name" ]; then
index 46451d0d64bdff2c0061d100c8d3b3bd38c21454..5c0224090a252bed3258bbbb127e84e61a874197 100755 (executable)
@@ -17,7 +17,11 @@ case "$1" in
         usage ;;
 esac
 
-rev=$(git-rev-parse --verify --default HEAD "$@") || exit
+case $# in
+0) rev=HEAD ;;
+1) rev=$(git-rev-parse --verify "$1") || exit ;;
+*) usage ;;
+esac
 rev=$(git-rev-parse --verify $rev^0) || exit
 
 # We need to remember the set of paths that _could_ be left
index 1c7aaefa2528b73fea3eee22b086b94fa91854c5..a7bc680d90cb503c50d25b15bcaba662c5f5b49e 100755 (executable)
@@ -15,6 +15,7 @@ dropheads() {
 
 head=$(git-rev-parse --verify "$1"^0) &&
 merge=$(git-rev-parse --verify "$2"^0) &&
+merge_name="$2" &&
 merge_msg="$3" || usage
 
 #
@@ -43,7 +44,8 @@ case "$common" in
 "$head")
        echo "Updating from $head to $merge"
        git-read-tree -u -m $head $merge || exit 1
-       git-update-ref HEAD "$merge" "$head"
+       git-update-ref -m "resolve $merge_name: Fast forward" \
+               HEAD "$merge" "$head"
        git-diff-tree -p $head $merge | git-apply --stat
        dropheads
        exit 0
@@ -100,6 +102,7 @@ if [ $? -ne 0 ]; then
 fi
 result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree -p $head -p $merge)
 echo "Committed merge $result_commit"
-git-update-ref HEAD "$result_commit" "$head"
+git-update-ref -m "resolve $merge_name: In-index merge" \
+       HEAD "$result_commit" "$head"
 git-diff-tree -p $head $result_commit | git-apply --stat
 dropheads
index de8b5f0f0fbb42e82327670e6916382f6f2d3e47..2bf35d116c2141a1750a0ca0bd8f7297e0c237b6 100755 (executable)
@@ -84,7 +84,7 @@ revert)
                s/^[^ ]* /Revert "/
                s/$/"/'
        echo
-       echo "This reverts $commit commit."
+       echo "This reverts commit $commit."
        test "$rev" = "$commit" ||
        echo "(original 'git revert' arguments: $@)"
        base=$commit next=$prev
index c5d9e733512ddd4d266c85d4f5cdec4a7d74fa56..a83c7e90948fc3fe1b1ac82335704d66d060edab 100755 (executable)
@@ -22,9 +22,55 @@ use Term::ReadLine;
 use Getopt::Long;
 use Data::Dumper;
 
+package FakeTerm;
+sub new {
+       my ($class, $reason) = @_;
+       return bless \$reason, shift;
+}
+sub readline {
+       my $self = shift;
+       die "Cannot use readline on FakeTerm: $$self";
+}
+package main;
+
 # most mail servers generate the Date: header, but not all...
-$ENV{LC_ALL} = 'C';
-use POSIX qw/strftime/;
+sub format_2822_time {
+       my ($time) = @_;
+       my @localtm = localtime($time);
+       my @gmttm = gmtime($time);
+       my $localmin = $localtm[1] + $localtm[2] * 60;
+       my $gmtmin = $gmttm[1] + $gmttm[2] * 60;
+       if ($localtm[0] != $gmttm[0]) {
+               die "local zone differs from GMT by a non-minute interval\n";
+       }
+       if ((($gmttm[6] + 1) % 7) == $localtm[6]) {
+               $localmin += 1440;
+       } elsif ((($gmttm[6] - 1) % 7) == $localtm[6]) {
+               $localmin -= 1440;
+       } elsif ($gmttm[6] != $localtm[6]) {
+               die "local time offset greater than or equal to 24 hours\n";
+       }
+       my $offset = $localmin - $gmtmin;
+       my $offhour = $offset / 60;
+       my $offmin = abs($offset % 60);
+       if (abs($offhour) >= 24) {
+               die ("local time offset greater than or equal to 24 hours\n");
+       }
+
+       return sprintf("%s, %2d %s %d %02d:%02d:%02d %s%02d%02d",
+                      qw(Sun Mon Tue Wed Thu Fri Sat)[$localtm[6]],
+                      $localtm[3],
+                      qw(Jan Feb Mar Apr May Jun
+                         Jul Aug Sep Oct Nov Dec)[$localtm[4]],
+                      $localtm[5]+1900,
+                      $localtm[2],
+                      $localtm[1],
+                      $localtm[0],
+                      ($offset >= 0) ? '+' : '-',
+                      abs($offhour),
+                      $offmin,
+                      );
+}
 
 my $have_email_valid = eval { require Email::Valid; 1 };
 my $smtp;
@@ -46,7 +92,12 @@ my $smtp_server;
 # Example reply to:
 #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
 
-my $term = new Term::ReadLine 'git-send-email';
+my $term = eval {
+       new Term::ReadLine 'git-send-email';
+};
+if ($@) {
+       $term = new FakeTerm "$@: going non-interactive";
+}
 
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
@@ -310,7 +361,7 @@ Options:
    --smtp-server  If set, specifies the outgoing SMTP server to use.
                   Defaults to localhost.
 
-  --suppress-from Supress sending emails to yourself if your address
+  --suppress-from Suppress sending emails to yourself if your address
                   appears in a From: line.
 
    --quiet     Make git-send-email less verbose.  One line per email should be
@@ -371,7 +422,7 @@ sub send_message
        my @recipients = unique_email_list(@to);
        my $to = join (",\n\t", @recipients);
        @recipients = unique_email_list(@recipients,@cc,@bcclist);
-       my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
+       my $date = format_2822_time($time++);
        my $gitversion = '@@GIT_VERSION@@';
        if ($gitversion =~ m/..GIT_VERSION../) {
            $gitversion = `git --version`;
@@ -384,7 +435,6 @@ sub send_message
 To: $to
 Cc: $cc
 Subject: $subject
-Reply-To: $from
 Date: $date
 Message-Id: $message_id
 X-Mailer: git-send-email $gitversion
diff --git a/git-svn.perl b/git-svn.perl
new file mode 100755 (executable)
index 0000000..6453771
--- /dev/null
@@ -0,0 +1,3387 @@
+#!/usr/bin/env perl
+# Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
+# License: GPL v2 or later
+use warnings;
+use strict;
+use vars qw/   $AUTHOR $VERSION
+               $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
+               $GIT_SVN_INDEX $GIT_SVN
+               $GIT_DIR $GIT_SVN_DIR $REVDB/;
+$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
+$VERSION = '@@GIT_VERSION@@';
+
+use Cwd qw/abs_path/;
+$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
+$ENV{GIT_DIR} = $GIT_DIR;
+
+my $LC_ALL = $ENV{LC_ALL};
+my $TZ = $ENV{TZ};
+# make sure the svn binary gives consistent output between locales and TZs:
+$ENV{TZ} = 'UTC';
+$ENV{LC_ALL} = 'C';
+$| = 1; # unbuffer STDOUT
+
+# If SVN:: library support is added, please make the dependencies
+# optional and preserve the capability to use the command-line client.
+# use eval { require SVN::... } to make it lazy load
+# We don't use any modules not in the standard Perl distribution:
+use Carp qw/croak/;
+use IO::File qw//;
+use File::Basename qw/dirname basename/;
+use File::Path qw/mkpath/;
+use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
+use File::Spec qw//;
+use POSIX qw/strftime/;
+use IPC::Open3;
+use Memoize;
+memoize('revisions_eq');
+memoize('cmt_metadata');
+memoize('get_commit_time');
+
+my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
+$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
+libsvn_load();
+my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
+my $sha1 = qr/[a-f\d]{40}/;
+my $sha1_short = qr/[a-f\d]{4,40}/;
+my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
+       $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
+       $_repack, $_repack_nr, $_repack_flags, $_q,
+       $_message, $_file, $_follow_parent, $_no_metadata,
+       $_template, $_shared, $_no_default_regex, $_no_graft_copy,
+       $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
+       $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);
+my (@_branch_from, %tree_map, %users, %rusers, %equiv);
+my ($_svn_co_url_revs, $_svn_pg_peg_revs);
+my @repo_path_split_cache;
+
+my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
+               'branch|b=s' => \@_branch_from,
+               'follow-parent|follow' => \$_follow_parent,
+               'branch-all-refs|B' => \$_branch_all_refs,
+               'authors-file|A=s' => \$_authors,
+               'repack:i' => \$_repack,
+               'no-metadata' => \$_no_metadata,
+               'quiet|q' => \$_q,
+               'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
+
+my ($_trunk, $_tags, $_branches);
+my %multi_opts = ( 'trunk|T=s' => \$_trunk,
+               'tags|t=s' => \$_tags,
+               'branches|b=s' => \$_branches );
+my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared );
+my %cmt_opts = ( 'edit|e' => \$_edit,
+               'rmdir' => \$_rmdir,
+               'find-copies-harder' => \$_find_copies_harder,
+               'l=i' => \$_l,
+               'copy-similarity|C=i'=> \$_cp_similarity
+);
+
+# yes, 'native' sets "\n".  Patches to fix this for non-*nix systems welcome:
+my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" );
+
+my %cmd = (
+       fetch => [ \&fetch, "Download new revisions from SVN",
+                       { 'revision|r=s' => \$_revision, %fc_opts } ],
+       init => [ \&init, "Initialize a repo for tracking" .
+                         " (requires URL argument)",
+                         \%init_opts ],
+       commit => [ \&commit, "Commit git revisions to SVN",
+                       {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
+       'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
+                       { 'revision|r=i' => \$_revision } ],
+       rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
+                       { 'no-ignore-externals' => \$_no_ignore_ext,
+                         'copy-remote|remote=s' => \$_cp_remote,
+                         'upgrade' => \$_upgrade } ],
+       'graft-branches' => [ \&graft_branches,
+                       'Detect merges/branches from already imported history',
+                       { 'merge-rx|m' => \@_opt_m,
+                         'branch|b=s' => \@_branch_from,
+                         'branch-all-refs|B' => \$_branch_all_refs,
+                         'no-default-regex' => \$_no_default_regex,
+                         'no-graft-copy' => \$_no_graft_copy } ],
+       'multi-init' => [ \&multi_init,
+                       'Initialize multiple trees (like git-svnimport)',
+                       { %multi_opts, %fc_opts } ],
+       'multi-fetch' => [ \&multi_fetch,
+                       'Fetch multiple trees (like git-svnimport)',
+                       \%fc_opts ],
+       'log' => [ \&show_log, 'Show commit logs',
+                       { 'limit=i' => \$_limit,
+                         'revision|r=s' => \$_revision,
+                         'verbose|v' => \$_verbose,
+                         'incremental' => \$_incremental,
+                         'oneline' => \$_oneline,
+                         'show-commit' => \$_show_commit,
+                         'authors-file|A=s' => \$_authors,
+                       } ],
+       'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
+                       { 'message|m=s' => \$_message,
+                         'file|F=s' => \$_file,
+                       %cmt_opts } ],
+);
+
+my $cmd;
+for (my $i = 0; $i < @ARGV; $i++) {
+       if (defined $cmd{$ARGV[$i]}) {
+               $cmd = $ARGV[$i];
+               splice @ARGV, $i, 1;
+               last;
+       }
+};
+
+my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
+
+read_repo_config(\%opts);
+my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
+                               'version|V' => \$_version,
+                               'id|i=s' => \$GIT_SVN);
+exit 1 if (!$rv && $cmd ne 'log');
+
+set_default_vals();
+usage(0) if $_help;
+version() if $_version;
+usage(1) unless defined $cmd;
+init_vars();
+load_authors() if $_authors;
+load_all_refs() if $_branch_all_refs;
+svn_compat_check() unless $_use_lib;
+migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
+$cmd{$cmd}->[0]->(@ARGV);
+exit 0;
+
+####################### primary functions ######################
+sub usage {
+       my $exit = shift || 0;
+       my $fd = $exit ? \*STDERR : \*STDOUT;
+       print $fd <<"";
+git-svn - bidirectional operations between a single Subversion tree and git
+Usage: $0 <command> [options] [arguments]\n
+
+       print $fd "Available commands:\n" unless $cmd;
+
+       foreach (sort keys %cmd) {
+               next if $cmd && $cmd ne $_;
+               print $fd '  ',pack('A13',$_),$cmd{$_}->[1],"\n";
+               foreach (keys %{$cmd{$_}->[2]}) {
+                       # prints out arguments as they should be passed:
+                       my $x = s#[:=]s$## ? '<arg>' : s#[:=]i$## ? '<num>' : '';
+                       print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
+                                                       "--$_" : "-$_" }
+                                               split /\|/,$_)," $x\n";
+               }
+       }
+       print $fd <<"";
+\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an
+arbitrary identifier if you're tracking multiple SVN branches/repositories in
+one git repository and want to keep them separate.  See git-svn(1) for more
+information.
+
+       exit $exit;
+}
+
+sub version {
+       print "git-svn version $VERSION\n";
+       exit 0;
+}
+
+sub rebuild {
+       if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) {
+               copy_remote_ref();
+       }
+       $SVN_URL = shift or undef;
+       my $newest_rev = 0;
+       if ($_upgrade) {
+               sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
+       } else {
+               check_upgrade_needed();
+       }
+
+       my $pid = open(my $rev_list,'-|');
+       defined $pid or croak $!;
+       if ($pid == 0) {
+               exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
+       }
+       my $latest;
+       while (<$rev_list>) {
+               chomp;
+               my $c = $_;
+               croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
+               my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
+               next if (!@commit); # skip merges
+               my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
+               if (!$rev || !$uuid) {
+                       croak "Unable to extract revision or UUID from ",
+                               "$c, $commit[$#commit]\n";
+               }
+
+               # if we merged or otherwise started elsewhere, this is
+               # how we break out of it
+               next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
+               next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));
+
+               unless (defined $latest) {
+                       if (!$SVN_URL && !$url) {
+                               croak "SVN repository location required: $url\n";
+                       }
+                       $SVN_URL ||= $url;
+                       $SVN_UUID ||= $uuid;
+                       setup_git_svn();
+                       $latest = $rev;
+               }
+               revdb_set($REVDB, $rev, $c);
+               print "r$rev = $c\n";
+               $newest_rev = $rev if ($rev > $newest_rev);
+       }
+       close $rev_list or croak $?;
+
+       goto out if $_use_lib;
+       if (!chdir $SVN_WC) {
+               svn_cmd_checkout($SVN_URL, $latest, $SVN_WC);
+               chdir $SVN_WC or croak $!;
+       }
+
+       $pid = fork;
+       defined $pid or croak $!;
+       if ($pid == 0) {
+               my @svn_up = qw(svn up);
+               push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
+               sys(@svn_up,"-r$newest_rev");
+               $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;
+               index_changes();
+               exec('git-write-tree') or croak $!;
+       }
+       waitpid $pid, 0;
+       croak $? if $?;
+out:
+       if ($_upgrade) {
+               print STDERR <<"";
+Keeping deprecated refs/head/$GIT_SVN-HEAD for now.  Please remove it
+when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
+
+       }
+}
+
+sub init {
+       my $url = shift or die "SVN repository location required " .
+                               "as a command-line argument\n";
+       $url =~ s!/+$!!; # strip trailing slash
+
+       if (my $repo_path = shift) {
+               unless (-d $repo_path) {
+                       mkpath([$repo_path]);
+               }
+               $GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git";
+               init_vars();
+       }
+
+       $SVN_URL = $url;
+       unless (-d $GIT_DIR) {
+               my @init_db = ('git-init-db');
+               push @init_db, "--template=$_template" if defined $_template;
+               push @init_db, "--shared" if defined $_shared;
+               sys(@init_db);
+       }
+       setup_git_svn();
+}
+
+sub fetch {
+       check_upgrade_needed();
+       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
+       my $ret = $_use_lib ? fetch_lib(@_) : fetch_cmd(@_);
+       if ($ret->{commit} && quiet_run(qw(git-rev-parse --verify
+                                               refs/heads/master^0))) {
+               sys(qw(git-update-ref refs/heads/master),$ret->{commit});
+       }
+       return $ret;
+}
+
+sub fetch_cmd {
+       my (@parents) = @_;
+       my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);
+       unless ($_revision) {
+               $_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD';
+       }
+       push @log_args, "-r$_revision";
+       push @log_args, '--stop-on-copy' unless $_no_stop_copy;
+
+       my $svn_log = svn_log_raw(@log_args);
+
+       my $base = next_log_entry($svn_log) or croak "No base revision!\n";
+       # don't need last_revision from grab_base_rev() because
+       # user could've specified a different revision to skip (they
+       # didn't want to import certain revisions into git for whatever
+       # reason, so trust $base->{revision} instead.
+       my (undef, $last_commit) = svn_grab_base_rev();
+       unless (-d $SVN_WC) {
+               svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC);
+               chdir $SVN_WC or croak $!;
+               read_uuid();
+               $last_commit = git_commit($base, @parents);
+               assert_tree($last_commit);
+       } else {
+               chdir $SVN_WC or croak $!;
+               read_uuid();
+               # looks like a user manually cp'd and svn switch'ed
+               unless ($last_commit) {
+                       sys(qw/svn revert -R ./);
+                       assert_svn_wc_clean($base->{revision});
+                       $last_commit = git_commit($base, @parents);
+                       assert_tree($last_commit);
+               }
+       }
+       my @svn_up = qw(svn up);
+       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
+       my $last = $base;
+       while (my $log_msg = next_log_entry($svn_log)) {
+               if ($last->{revision} >= $log_msg->{revision}) {
+                       croak "Out of order: last >= current: ",
+                               "$last->{revision} >= $log_msg->{revision}\n";
+               }
+               # Revert is needed for cases like:
+               # https://svn.musicpd.org/Jamming/trunk (r166:167), but
+               # I can't seem to reproduce something like that on a test...
+               sys(qw/svn revert -R ./);
+               assert_svn_wc_clean($last->{revision});
+               sys(@svn_up,"-r$log_msg->{revision}");
+               $last_commit = git_commit($log_msg, $last_commit, @parents);
+               $last = $log_msg;
+       }
+       close $svn_log->{fh};
+       $last->{commit} = $last_commit;
+       return $last;
+}
+
+sub fetch_lib {
+       my (@parents) = @_;
+       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
+       my $repo;
+       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
+       $SVN_LOG ||= libsvn_connect($repo);
+       $SVN ||= libsvn_connect($repo);
+       my ($last_rev, $last_commit) = svn_grab_base_rev();
+       my ($base, $head) = libsvn_parse_revision($last_rev);
+       if ($base > $head) {
+               return { revision => $last_rev, commit => $last_commit }
+       }
+       my $index = set_index($GIT_SVN_INDEX);
+
+       # limit ourselves and also fork() since get_log won't release memory
+       # after processing a revision and SVN stuff seems to leak
+       my $inc = 1000;
+       my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
+       read_uuid();
+       if (defined $last_commit) {
+               unless (-e $GIT_SVN_INDEX) {
+                       sys(qw/git-read-tree/, $last_commit);
+               }
+               chomp (my $x = `git-write-tree`);
+               my ($y) = (`git-cat-file commit $last_commit`
+                                                       =~ /^tree ($sha1)/m);
+               if ($y ne $x) {
+                       unlink $GIT_SVN_INDEX or croak $!;
+                       sys(qw/git-read-tree/, $last_commit);
+               }
+               chomp ($x = `git-write-tree`);
+               if ($y ne $x) {
+                       print STDERR "trees ($last_commit) $y != $x\n",
+                                "Something is seriously wrong...\n";
+               }
+       }
+       while (1) {
+               # fork, because using SVN::Pool with get_log() still doesn't
+               # seem to help enough to keep memory usage down.
+               defined(my $pid = fork) or croak $!;
+               if (!$pid) {
+                       $SVN::Error::handler = \&libsvn_skip_unknown_revs;
+
+                       # Yes I'm perfectly aware that the fourth argument
+                       # below is the limit revisions number.  Unfortunately
+                       # performance sucks with it enabled, so it's much
+                       # faster to fetch revision ranges instead of relying
+                       # on the limiter.
+                       libsvn_get_log($SVN_LOG, '/'.$SVN_PATH,
+                                       $min, $max, 0, 1, 1,
+                               sub {
+                                       my $log_msg;
+                                       if ($last_commit) {
+                                               $log_msg = libsvn_fetch(
+                                                       $last_commit, @_);
+                                               $last_commit = git_commit(
+                                                       $log_msg,
+                                                       $last_commit,
+                                                       @parents);
+                                       } else {
+                                               $log_msg = libsvn_new_tree(@_);
+                                               $last_commit = git_commit(
+                                                       $log_msg, @parents);
+                                       }
+                               });
+                       exit 0;
+               }
+               waitpid $pid, 0;
+               croak $? if $?;
+               ($last_rev, $last_commit) = svn_grab_base_rev();
+               last if ($max >= $head);
+               $min = $max + 1;
+               $max += $inc;
+               $max = $head if ($max > $head);
+       }
+       restore_index($index);
+       return { revision => $last_rev, commit => $last_commit };
+}
+
+sub commit {
+       my (@commits) = @_;
+       check_upgrade_needed();
+       if ($_stdin || !@commits) {
+               print "Reading from stdin...\n";
+               @commits = ();
+               while (<STDIN>) {
+                       if (/\b($sha1_short)\b/o) {
+                               unshift @commits, $1;
+                       }
+               }
+       }
+       my @revs;
+       foreach my $c (@commits) {
+               chomp(my @tmp = safe_qx('git-rev-parse',$c));
+               if (scalar @tmp == 1) {
+                       push @revs, $tmp[0];
+               } elsif (scalar @tmp > 1) {
+                       push @revs, reverse (safe_qx('git-rev-list',@tmp));
+               } else {
+                       die "Failed to rev-parse $c\n";
+               }
+       }
+       chomp @revs;
+       $_use_lib ? commit_lib(@revs) : commit_cmd(@revs);
+       print "Done committing ",scalar @revs," revisions to SVN\n";
+}
+
+sub commit_cmd {
+       my (@revs) = @_;
+
+       chdir $SVN_WC or croak "Unable to chdir $SVN_WC: $!\n";
+       my $info = svn_info('.');
+       my $fetched = fetch();
+       if ($info->{Revision} != $fetched->{revision}) {
+               print STDERR "There are new revisions that were fetched ",
+                               "and need to be merged (or acknowledged) ",
+                               "before committing.\n";
+               exit 1;
+       }
+       $info = svn_info('.');
+       read_uuid($info);
+       my $last = $fetched;
+       foreach my $c (@revs) {
+               my $mods = svn_checkout_tree($last, $c);
+               if (scalar @$mods == 0) {
+                       print "Skipping, no changes detected\n";
+                       next;
+               }
+               $last = svn_commit_tree($last, $c);
+       }
+}
+
+sub commit_lib {
+       my (@revs) = @_;
+       my ($r_last, $cmt_last) = svn_grab_base_rev();
+       defined $r_last or die "Must have an existing revision to commit\n";
+       my $fetched = fetch();
+       if ($r_last != $fetched->{revision}) {
+               print STDERR "There are new revisions that were fetched ",
+                               "and need to be merged (or acknowledged) ",
+                               "before committing.\n",
+                               "last rev: $r_last\n",
+                               " current: $fetched->{revision}\n";
+               exit 1;
+       }
+       read_uuid();
+       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+       my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
+
+       set_svn_commit_env();
+       foreach my $c (@revs) {
+               my $log_msg = get_commit_message($c, $commit_msg);
+
+               # fork for each commit because there's a memory leak I
+               # can't track down... (it's probably in the SVN code)
+               defined(my $pid = open my $fh, '-|') or croak $!;
+               if (!$pid) {
+                       my $ed = SVN::Git::Editor->new(
+                                       {       r => $r_last,
+                                               ra => $SVN,
+                                               c => $c,
+                                               svn_path => $SVN_PATH
+                                       },
+                                       $SVN->get_commit_editor(
+                                               $log_msg->{msg},
+                                               sub {
+                                                       libsvn_commit_cb(
+                                                               @_, $c,
+                                                               $log_msg->{msg},
+                                                               $r_last,
+                                                               $cmt_last)
+                                               },
+                                               @lock)
+                                       );
+                       my $mods = libsvn_checkout_tree($cmt_last, $c, $ed);
+                       if (@$mods == 0) {
+                               print "No changes\nr$r_last = $cmt_last\n";
+                               $ed->abort_edit;
+                       } else {
+                               $ed->close_edit;
+                       }
+                       exit 0;
+               }
+               my ($r_new, $cmt_new, $no);
+               while (<$fh>) {
+                       print $_;
+                       chomp;
+                       if (/^r(\d+) = ($sha1)$/o) {
+                               ($r_new, $cmt_new) = ($1, $2);
+                       } elsif ($_ eq 'No changes') {
+                               $no = 1;
+                       }
+               }
+               close $fh or croak $?;
+               if (! defined $r_new && ! defined $cmt_new) {
+                       unless ($no) {
+                               die "Failed to parse revision information\n";
+                       }
+               } else {
+                       ($r_last, $cmt_last) = ($r_new, $cmt_new);
+               }
+       }
+       $ENV{LC_ALL} = 'C';
+       unlink $commit_msg;
+}
+
+sub show_ignore {
+       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
+       $_use_lib ? show_ignore_lib() : show_ignore_cmd();
+}
+
+sub show_ignore_cmd {
+       require File::Find or die $!;
+       if (defined $_revision) {
+               die "-r/--revision option doesn't work unless the Perl SVN ",
+                       "libraries are used\n";
+       }
+       chdir $SVN_WC or croak $!;
+       my %ign;
+       File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){
+               s#^\./##;
+               @{$ign{$_}} = svn_propget_base('svn:ignore', $_);
+               }}, no_chdir=>1},'.');
+
+       print "\n# /\n";
+       foreach (@{$ign{'.'}}) { print '/',$_ if /\S/ }
+       delete $ign{'.'};
+       foreach my $i (sort keys %ign) {
+               print "\n# ",$i,"\n";
+               foreach (@{$ign{$i}}) { print '/',$i,'/',$_ if /\S/ }
+       }
+}
+
+sub show_ignore_lib {
+       my $repo;
+       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
+       $SVN ||= libsvn_connect($repo);
+       my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
+       libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r);
+}
+
+sub graft_branches {
+       my $gr_file = "$GIT_DIR/info/grafts";
+       my ($grafts, $comments) = read_grafts($gr_file);
+       my $gr_sha1;
+
+       if (%$grafts) {
+               # temporarily disable our grafts file to make this idempotent
+               chomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file));
+               rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
+       }
+
+       my $l_map = read_url_paths();
+       my @re = map { qr/$_/is } @_opt_m if @_opt_m;
+       unless ($_no_default_regex) {
+               push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i,
+                       qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i,
+                       qr/\b(?:from|of)\s+([\w\.\-]+)/i );
+       }
+       foreach my $u (keys %$l_map) {
+               if (@re) {
+                       foreach my $p (keys %{$l_map->{$u}}) {
+                               graft_merge_msg($grafts,$l_map,$u,$p,@re);
+                       }
+               }
+               unless ($_no_graft_copy) {
+                       if ($_use_lib) {
+                               graft_file_copy_lib($grafts,$l_map,$u);
+                       } else {
+                               graft_file_copy_cmd($grafts,$l_map,$u);
+                       }
+               }
+       }
+       graft_tree_joins($grafts);
+
+       write_grafts($grafts, $comments, $gr_file);
+       unlink "$gr_file~$gr_sha1" if $gr_sha1;
+}
+
+sub multi_init {
+       my $url = shift;
+       $_trunk ||= 'trunk';
+       $_trunk =~ s#/+$##;
+       $url =~ s#/+$## if $url;
+       if ($_trunk !~ m#^[a-z\+]+://#) {
+               $_trunk = '/' . $_trunk if ($_trunk !~ m#^/#);
+               unless ($url) {
+                       print STDERR "E: '$_trunk' is not a complete URL ",
+                               "and a separate URL is not specified\n";
+                       exit 1;
+               }
+               $_trunk = $url . $_trunk;
+       }
+       if ($GIT_SVN eq 'git-svn') {
+               print "GIT_SVN_ID set to 'trunk' for $_trunk\n";
+               $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
+       }
+       init_vars();
+       init($_trunk);
+       complete_url_ls_init($url, $_branches, '--branches/-b', '');
+       complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
+}
+
+sub multi_fetch {
+       # try to do trunk first, since branches/tags
+       # may be descended from it.
+       if (-e "$GIT_DIR/svn/trunk/info/url") {
+               fetch_child_id('trunk', @_);
+       }
+       rec_fetch('', "$GIT_DIR/svn", @_);
+}
+
+sub show_log {
+       my (@args) = @_;
+       my ($r_min, $r_max);
+       my $r_last = -1; # prevent dupes
+       rload_authors() if $_authors;
+       if (defined $TZ) {
+               $ENV{TZ} = $TZ;
+       } else {
+               delete $ENV{TZ};
+       }
+       if (defined $_revision) {
+               if ($_revision =~ /^(\d+):(\d+)$/) {
+                       ($r_min, $r_max) = ($1, $2);
+               } elsif ($_revision =~ /^\d+$/) {
+                       $r_min = $r_max = $_revision;
+               } else {
+                       print STDERR "-r$_revision is not supported, use ",
+                               "standard \'git log\' arguments instead\n";
+                       exit 1;
+               }
+       }
+
+       my $pid = open(my $log,'-|');
+       defined $pid or croak $!;
+       if (!$pid) {
+               exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!;
+       }
+       setup_pager();
+       my (@k, $c, $d);
+
+       while (<$log>) {
+               if (/^commit ($sha1_short)/o) {
+                       my $cmt = $1;
+                       if ($c && cmt_showable($c) && $c->{r} != $r_last) {
+                               $r_last = $c->{r};
+                               process_commit($c, $r_min, $r_max, \@k) or
+                                                               goto out;
+                       }
+                       $d = undef;
+                       $c = { c => $cmt };
+               } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) {
+                       get_author_info($c, $1, $2, $3);
+               } elsif (/^(?:tree|parent|committer) /) {
+                       # ignore
+               } elsif (/^:\d{6} \d{6} $sha1_short/o) {
+                       push @{$c->{raw}}, $_;
+               } elsif (/^diff /) {
+                       $d = 1;
+                       push @{$c->{diff}}, $_;
+               } elsif ($d) {
+                       push @{$c->{diff}}, $_;
+               } elsif (/^    (git-svn-id:.+)$/) {
+                       (undef, $c->{r}, undef) = extract_metadata($1);
+               } elsif (s/^    //) {
+                       push @{$c->{l}}, $_;
+               }
+       }
+       if ($c && defined $c->{r} && $c->{r} != $r_last) {
+               $r_last = $c->{r};
+               process_commit($c, $r_min, $r_max, \@k);
+       }
+       if (@k) {
+               my $swap = $r_max;
+               $r_max = $r_min;
+               $r_min = $swap;
+               process_commit($_, $r_min, $r_max) foreach reverse @k;
+       }
+out:
+       close $log;
+       print '-' x72,"\n" unless $_incremental || $_oneline;
+}
+
+sub commit_diff_usage {
+       print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n";
+       exit 1
+}
+
+sub commit_diff {
+       if (!$_use_lib) {
+               print STDERR "commit-diff must be used with SVN libraries\n";
+               exit 1;
+       }
+       my $ta = shift or commit_diff_usage();
+       my $tb = shift or commit_diff_usage();
+       if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
+               print STDERR "Needed URL or usable git-svn id command-line\n";
+               commit_diff_usage();
+       }
+       if (defined $_message && defined $_file) {
+               print STDERR "Both --message/-m and --file/-F specified ",
+                               "for the commit message.\n",
+                               "I have no idea what you mean\n";
+               exit 1;
+       }
+       if (defined $_file) {
+               $_message = file_to_s($_file);
+       } else {
+               $_message ||= get_commit_message($tb,
+                                       "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
+       }
+       my $repo;
+       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
+       $SVN_LOG ||= libsvn_connect($repo);
+       $SVN ||= libsvn_connect($repo);
+       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+       my $ed = SVN::Git::Editor->new({        r => $SVN->get_latest_revnum,
+                                               ra => $SVN, c => $tb,
+                                               svn_path => $SVN_PATH
+                                       },
+                               $SVN->get_commit_editor($_message,
+                                       sub {print "Committed $_[0]\n"},@lock)
+                               );
+       my $mods = libsvn_checkout_tree($ta, $tb, $ed);
+       if (@$mods == 0) {
+               print "No changes\n$ta == $tb\n";
+               $ed->abort_edit;
+       } else {
+               $ed->close_edit;
+       }
+}
+
+########################### utility functions #########################
+
+sub cmt_showable {
+       my ($c) = @_;
+       return 1 if defined $c->{r};
+       if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
+                               $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
+               my @msg = safe_qx(qw/git-cat-file commit/, $c->{c});
+               shift @msg while ($msg[0] ne "\n");
+               shift @msg;
+               @{$c->{l}} = grep !/^git-svn-id: /, @msg;
+
+               (undef, $c->{r}, undef) = extract_metadata(
+                               (grep(/^git-svn-id: /, @msg))[-1]);
+       }
+       return defined $c->{r};
+}
+
+sub git_svn_log_cmd {
+       my ($r_min, $r_max) = @_;
+       my @cmd = (qw/git-log --abbrev-commit --pretty=raw
+                       --default/, "refs/remotes/$GIT_SVN");
+       push @cmd, '--summary' if $_verbose;
+       return @cmd unless defined $r_max;
+       if ($r_max == $r_min) {
+               push @cmd, '--max-count=1';
+               if (my $c = revdb_get($REVDB, $r_max)) {
+                       push @cmd, $c;
+               }
+       } else {
+               my ($c_min, $c_max);
+               $c_max = revdb_get($REVDB, $r_max);
+               $c_min = revdb_get($REVDB, $r_min);
+               if ($c_min && $c_max) {
+                       if ($r_max > $r_max) {
+                               push @cmd, "$c_min..$c_max";
+                       } else {
+                               push @cmd, "$c_max..$c_min";
+                       }
+               } elsif ($r_max > $r_min) {
+                       push @cmd, $c_max;
+               } else {
+                       push @cmd, $c_min;
+               }
+       }
+       return @cmd;
+}
+
+sub fetch_child_id {
+       my $id = shift;
+       print "Fetching $id\n";
+       my $ref = "$GIT_DIR/refs/remotes/$id";
+       defined(my $pid = open my $fh, '-|') or croak $!;
+       if (!$pid) {
+               $_repack = undef;
+               $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+               init_vars();
+               fetch(@_);
+               exit 0;
+       }
+       while (<$fh>) {
+               print $_;
+               check_repack() if (/^r\d+ = $sha1/);
+       }
+       close $fh or croak $?;
+}
+
+sub rec_fetch {
+       my ($pfx, $p, @args) = @_;
+       my @dir;
+       foreach (sort <$p/*>) {
+               if (-r "$_/info/url") {
+                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
+                       my $id = $pfx . basename $_;
+                       next if $id eq 'trunk';
+                       fetch_child_id($id, @args);
+               } elsif (-d $_) {
+                       push @dir, $_;
+               }
+       }
+       foreach (@dir) {
+               my $x = $_;
+               $x =~ s!^\Q$GIT_DIR\E/svn/!!;
+               rec_fetch($x, $_);
+       }
+}
+
+sub complete_url_ls_init {
+       my ($url, $var, $switch, $pfx) = @_;
+       unless ($var) {
+               print STDERR "W: $switch not specified\n";
+               return;
+       }
+       $var =~ s#/+$##;
+       if ($var !~ m#^[a-z\+]+://#) {
+               $var = '/' . $var if ($var !~ m#^/#);
+               unless ($url) {
+                       print STDERR "E: '$var' is not a complete URL ",
+                               "and a separate URL is not specified\n";
+                       exit 1;
+               }
+               $var = $url . $var;
+       }
+       chomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var)
+                               : safe_qx(qw/svn ls --non-interactive/, $var));
+       my $old = $GIT_SVN;
+       defined(my $pid = fork) or croak $!;
+       if (!$pid) {
+               foreach my $u (map { "$var/$_" } (grep m!/$!, @ls)) {
+                       $u =~ s#/+$##;
+                       if ($u !~ m!\Q$var\E/(.+)$!) {
+                               print STDERR "W: Unrecognized URL: $u\n";
+                               die "This should never happen\n";
+                       }
+                       my $id = $pfx.$1;
+                       print "init $u => $id\n";
+                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+                       init_vars();
+                       init($u);
+               }
+               exit 0;
+       }
+       waitpid $pid, 0;
+       croak $? if $?;
+}
+
+sub common_prefix {
+       my $paths = shift;
+       my %common;
+       foreach (@$paths) {
+               my @tmp = split m#/#, $_;
+               my $p = '';
+               while (my $x = shift @tmp) {
+                       $p .= "/$x";
+                       $common{$p} ||= 0;
+                       $common{$p}++;
+               }
+       }
+       foreach (sort {length $b <=> length $a} keys %common) {
+               if ($common{$_} == @$paths) {
+                       return $_;
+               }
+       }
+       return '';
+}
+
+# grafts set here are 'stronger' in that they're based on actual tree
+# matches, and won't be deleted from merge-base checking in write_grafts()
+sub graft_tree_joins {
+       my $grafts = shift;
+       map_tree_joins() if (@_branch_from && !%tree_map);
+       return unless %tree_map;
+
+       git_svn_each(sub {
+               my $i = shift;
+               defined(my $pid = open my $fh, '-|') or croak $!;
+               if (!$pid) {
+                       exec qw/git-rev-list --pretty=raw/,
+                                       "refs/remotes/$i" or croak $!;
+               }
+               while (<$fh>) {
+                       next unless /^commit ($sha1)$/o;
+                       my $c = $1;
+                       my ($t) = (<$fh> =~ /^tree ($sha1)$/o);
+                       next unless $tree_map{$t};
+
+                       my $l;
+                       do {
+                               $l = readline $fh;
+                       } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/);
+
+                       my ($s, $tz) = ($1, $2);
+                       if ($tz =~ s/^\+//) {
+                               $s += tz_to_s_offset($tz);
+                       } elsif ($tz =~ s/^\-//) {
+                               $s -= tz_to_s_offset($tz);
+                       }
+
+                       my ($url_a, $r_a, $uuid_a) = cmt_metadata($c);
+
+                       foreach my $p (@{$tree_map{$t}}) {
+                               next if $p eq $c;
+                               my $mb = eval {
+                                       safe_qx('git-merge-base', $c, $p)
+                               };
+                               next unless ($@ || $?);
+                               if (defined $r_a) {
+                                       # see if SVN says it's a relative
+                                       my ($url_b, $r_b, $uuid_b) =
+                                                       cmt_metadata($p);
+                                       next if (defined $url_b &&
+                                                       defined $url_a &&
+                                                       ($url_a eq $url_b) &&
+                                                       ($uuid_a eq $uuid_b));
+                                       if ($uuid_a eq $uuid_b) {
+                                               if ($r_b < $r_a) {
+                                                       $grafts->{$c}->{$p} = 2;
+                                                       next;
+                                               } elsif ($r_b > $r_a) {
+                                                       $grafts->{$p}->{$c} = 2;
+                                                       next;
+                                               }
+                                       }
+                               }
+                               my $ct = get_commit_time($p);
+                               if ($ct < $s) {
+                                       $grafts->{$c}->{$p} = 2;
+                               } elsif ($ct > $s) {
+                                       $grafts->{$p}->{$c} = 2;
+                               }
+                               # what should we do when $ct == $s ?
+                       }
+               }
+               close $fh or croak $?;
+       });
+}
+
+# this isn't funky-filename safe, but good enough for now...
+sub graft_file_copy_cmd {
+       my ($grafts, $l_map, $u) = @_;
+       my $paths = $l_map->{$u};
+       my $pfx = common_prefix([keys %$paths]);
+       $SVN_URL ||= $u.$pfx;
+       my $pid = open my $fh, '-|';
+       defined $pid or croak $!;
+       unless ($pid) {
+               my @exec = qw/svn log -v/;
+               push @exec, "-r$_revision" if defined $_revision;
+               exec @exec, $u.$pfx or croak $!;
+       }
+       my ($r, $mp) = (undef, undef);
+       while (<$fh>) {
+               chomp;
+               if (/^\-{72}$/) {
+                       $mp = $r = undef;
+               } elsif (/^r(\d+) \| /) {
+                       $r = $1 unless defined $r;
+               } elsif (/^Changed paths:/) {
+                       $mp = 1;
+               } elsif ($mp && m#^   [AR] /(\S.*?) \(from /(\S+?):(\d+)\)$#) {
+                       my ($p1, $p0, $r0) = ($1, $2, $3);
+                       my $c = find_graft_path_commit($paths, $p1, $r);
+                       next unless $c;
+                       find_graft_path_parents($grafts, $paths, $c, $p0, $r0);
+               }
+       }
+}
+
+sub graft_file_copy_lib {
+       my ($grafts, $l_map, $u) = @_;
+       my $tree_paths = $l_map->{$u};
+       my $pfx = common_prefix([keys %$tree_paths]);
+       my ($repo, $path) = repo_path_split($u.$pfx);
+       $SVN_LOG ||= libsvn_connect($repo);
+       $SVN ||= libsvn_connect($repo);
+
+       my ($base, $head) = libsvn_parse_revision();
+       my $inc = 1000;
+       my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
+       my $eh = $SVN::Error::handler;
+       $SVN::Error::handler = \&libsvn_skip_unknown_revs;
+       while (1) {
+               my $pool = SVN::Pool->new;
+               libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1,
+                       sub {
+                               libsvn_graft_file_copies($grafts, $tree_paths,
+                                                       $path, @_);
+                       }, $pool);
+               $pool->clear;
+               last if ($max >= $head);
+               $min = $max + 1;
+               $max += $inc;
+               $max = $head if ($max > $head);
+       }
+       $SVN::Error::handler = $eh;
+}
+
+sub process_merge_msg_matches {
+       my ($grafts, $l_map, $u, $p, $c, @matches) = @_;
+       my (@strong, @weak);
+       foreach (@matches) {
+               # merging with ourselves is not interesting
+               next if $_ eq $p;
+               if ($l_map->{$u}->{$_}) {
+                       push @strong, $_;
+               } else {
+                       push @weak, $_;
+               }
+       }
+       foreach my $w (@weak) {
+               last if @strong;
+               # no exact match, use branch name as regexp.
+               my $re = qr/\Q$w\E/i;
+               foreach (keys %{$l_map->{$u}}) {
+                       if (/$re/) {
+                               push @strong, $l_map->{$u}->{$_};
+                               last;
+                       }
+               }
+               last if @strong;
+               $w = basename($w);
+               $re = qr/\Q$w\E/i;
+               foreach (keys %{$l_map->{$u}}) {
+                       if (/$re/) {
+                               push @strong, $l_map->{$u}->{$_};
+                               last;
+                       }
+               }
+       }
+       my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+)
+                                       \s(?:[a-f\d\-]+)$/xsm);
+       unless (defined $rev) {
+               ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+)
+                                       \@(?:[a-f\d\-]+)/xsm);
+               return unless defined $rev;
+       }
+       foreach my $m (@strong) {
+               my ($r0, $s0) = find_rev_before($rev, $m, 1);
+               $grafts->{$c->{c}}->{$s0} = 1 if defined $s0;
+       }
+}
+
+sub graft_merge_msg {
+       my ($grafts, $l_map, $u, $p, @re) = @_;
+
+       my $x = $l_map->{$u}->{$p};
+       my $rl = rev_list_raw($x);
+       while (my $c = next_rev_list_entry($rl)) {
+               foreach my $re (@re) {
+                       my (@br) = ($c->{m} =~ /$re/g);
+                       next unless @br;
+                       process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br);
+               }
+       }
+}
+
+sub read_uuid {
+       return if $SVN_UUID;
+       if ($_use_lib) {
+               my $pool = SVN::Pool->new;
+               $SVN_UUID = $SVN->get_uuid($pool);
+               $pool->clear;
+       } else {
+               my $info = shift || svn_info('.');
+               $SVN_UUID = $info->{'Repository UUID'} or
+                                       croak "Repository UUID unreadable\n";
+       }
+}
+
+sub quiet_run {
+       my $pid = fork;
+       defined $pid or croak $!;
+       if (!$pid) {
+               open my $null, '>', '/dev/null' or croak $!;
+               open STDERR, '>&', $null or croak $!;
+               open STDOUT, '>&', $null or croak $!;
+               exec @_ or croak $!;
+       }
+       waitpid $pid, 0;
+       return $?;
+}
+
+sub repo_path_split {
+       my $full_url = shift;
+       $full_url =~ s#/+$##;
+
+       foreach (@repo_path_split_cache) {
+               if ($full_url =~ s#$_##) {
+                       my $u = $1;
+                       $full_url =~ s#^/+##;
+                       return ($u, $full_url);
+               }
+       }
+
+       my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
+       $path =~ s#^/+##;
+       my @paths = split(m#/+#, $path);
+
+       if ($_use_lib) {
+               while (1) {
+                       $SVN = libsvn_connect($url);
+                       last if (defined $SVN &&
+                               defined eval { $SVN->get_latest_revnum });
+                       my $n = shift @paths || last;
+                       $url .= "/$n";
+               }
+       } else {
+               while (quiet_run(qw/svn ls --non-interactive/, $url)) {
+                       my $n = shift @paths || last;
+                       $url .= "/$n";
+               }
+       }
+       push @repo_path_split_cache, qr/^(\Q$url\E)/;
+       $path = join('/',@paths);
+       return ($url, $path);
+}
+
+sub setup_git_svn {
+       defined $SVN_URL or croak "SVN repository location required\n";
+       unless (-d $GIT_DIR) {
+               croak "GIT_DIR=$GIT_DIR does not exist!\n";
+       }
+       mkpath([$GIT_SVN_DIR]);
+       mkpath(["$GIT_SVN_DIR/info"]);
+       open my $fh, '>>',$REVDB or croak $!;
+       close $fh;
+       s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url");
+
+}
+
+sub assert_svn_wc_clean {
+       return if $_use_lib;
+       my ($svn_rev) = @_;
+       croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
+       my $lcr = svn_info('.')->{'Last Changed Rev'};
+       if ($svn_rev != $lcr) {
+               print STDERR "Checking for copy-tree ... ";
+               my @diff = grep(/^Index: /,(safe_qx(qw(svn diff),
+                                               "-r$lcr:$svn_rev")));
+               if (@diff) {
+                       croak "Nope!  Expected r$svn_rev, got r$lcr\n";
+               } else {
+                       print STDERR "OK!\n";
+               }
+       }
+       my @status = grep(!/^Performing status on external/,(`svn status`));
+       @status = grep(!/^\s*$/,@status);
+       if (scalar @status) {
+               print STDERR "Tree ($SVN_WC) is not clean:\n";
+               print STDERR $_ foreach @status;
+               croak;
+       }
+}
+
+sub get_tree_from_treeish {
+       my ($treeish) = @_;
+       croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
+       chomp(my $type = `git-cat-file -t $treeish`);
+       my $expected;
+       while ($type eq 'tag') {
+               chomp(($treeish, $type) = `git-cat-file tag $treeish`);
+       }
+       if ($type eq 'commit') {
+               $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
+               ($expected) = ($expected =~ /^tree ($sha1)$/);
+               die "Unable to get tree from $treeish\n" unless $expected;
+       } elsif ($type eq 'tree') {
+               $expected = $treeish;
+       } else {
+               die "$treeish is a $type, expected tree, tag or commit\n";
+       }
+       return $expected;
+}
+
+sub assert_tree {
+       return if $_use_lib;
+       my ($treeish) = @_;
+       my $expected = get_tree_from_treeish($treeish);
+
+       my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';
+       if (-e $tmpindex) {
+               unlink $tmpindex or croak $!;
+       }
+       my $old_index = set_index($tmpindex);
+       index_changes(1);
+       chomp(my $tree = `git-write-tree`);
+       restore_index($old_index);
+       if ($tree ne $expected) {
+               croak "Tree mismatch, Got: $tree, Expected: $expected\n";
+       }
+       unlink $tmpindex;
+}
+
+sub parse_diff_tree {
+       my $diff_fh = shift;
+       local $/ = "\0";
+       my $state = 'meta';
+       my @mods;
+       while (<$diff_fh>) {
+               chomp $_; # this gets rid of the trailing "\0"
+               if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
+                                       $sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
+                       push @mods, {   mode_a => $1, mode_b => $2,
+                                       sha1_b => $3, chg => $4 };
+                       if ($4 =~ /^(?:C|R)$/) {
+                               $state = 'file_a';
+                       } else {
+                               $state = 'file_b';
+                       }
+               } elsif ($state eq 'file_a') {
+                       my $x = $mods[$#mods] or croak "Empty array\n";
+                       if ($x->{chg} !~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       $x->{file_a} = $_;
+                       $state = 'file_b';
+               } elsif ($state eq 'file_b') {
+                       my $x = $mods[$#mods] or croak "Empty array\n";
+                       if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       $x->{file_b} = $_;
+                       $state = 'meta';
+               } else {
+                       croak "Error parsing $_\n";
+               }
+       }
+       close $diff_fh or croak $?;
+
+       return \@mods;
+}
+
+sub svn_check_prop_executable {
+       my $m = shift;
+       return if -l $m->{file_b};
+       if ($m->{mode_b} =~ /755$/) {
+               chmod((0755 &~ umask),$m->{file_b}) or croak $!;
+               if ($m->{mode_a} !~ /755$/) {
+                       sys(qw(svn propset svn:executable 1), $m->{file_b});
+               }
+               -x $m->{file_b} or croak "$m->{file_b} is not executable!\n";
+       } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
+               sys(qw(svn propdel svn:executable), $m->{file_b});
+               chmod((0644 &~ umask),$m->{file_b}) or croak $!;
+               -x $m->{file_b} and croak "$m->{file_b} is executable!\n";
+       }
+}
+
+sub svn_ensure_parent_path {
+       my $dir_b = dirname(shift);
+       svn_ensure_parent_path($dir_b) if ($dir_b ne File::Spec->curdir);
+       mkpath([$dir_b]) unless (-d $dir_b);
+       sys(qw(svn add -N), $dir_b) unless (-d "$dir_b/.svn");
+}
+
+sub precommit_check {
+       my $mods = shift;
+       my (%rm_file, %rmdir_check, %added_check);
+
+       my %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 );
+       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+               if ($m->{chg} eq 'R') {
+                       if (-d $m->{file_b}) {
+                               err_dir_to_file("$m->{file_a} => $m->{file_b}");
+                       }
+                       # dir/$file => dir/file/$file
+                       my $dirname = dirname($m->{file_b});
+                       while ($dirname ne File::Spec->curdir) {
+                               if ($dirname ne $m->{file_a}) {
+                                       $dirname = dirname($dirname);
+                                       next;
+                               }
+                               err_file_to_dir("$m->{file_a} => $m->{file_b}");
+                       }
+                       # baz/zzz => baz (baz is a file)
+                       $dirname = dirname($m->{file_a});
+                       while ($dirname ne File::Spec->curdir) {
+                               if ($dirname ne $m->{file_b}) {
+                                       $dirname = dirname($dirname);
+                                       next;
+                               }
+                               err_dir_to_file("$m->{file_a} => $m->{file_b}");
+                       }
+               }
+               if ($m->{chg} =~ /^(D|R)$/) {
+                       my $t = $1 eq 'D' ? 'file_b' : 'file_a';
+                       $rm_file{ $m->{$t} } = 1;
+                       my $dirname = dirname( $m->{$t} );
+                       my $basename = basename( $m->{$t} );
+                       $rmdir_check{$dirname}->{$basename} = 1;
+               } elsif ($m->{chg} =~ /^(?:A|C)$/) {
+                       if (-d $m->{file_b}) {
+                               err_dir_to_file($m->{file_b});
+                       }
+                       my $dirname = dirname( $m->{file_b} );
+                       my $basename = basename( $m->{file_b} );
+                       $added_check{$dirname}->{$basename} = 1;
+                       while ($dirname ne File::Spec->curdir) {
+                               if ($rm_file{$dirname}) {
+                                       err_file_to_dir($m->{file_b});
+                               }
+                               $dirname = dirname $dirname;
+                       }
+               }
+       }
+       return (\%rmdir_check, \%added_check);
+
+       sub err_dir_to_file {
+               my $file = shift;
+               print STDERR "Node change from directory to file ",
+                               "is not supported by Subversion: ",$file,"\n";
+               exit 1;
+       }
+       sub err_file_to_dir {
+               my $file = shift;
+               print STDERR "Node change from file to directory ",
+                               "is not supported by Subversion: ",$file,"\n";
+               exit 1;
+       }
+}
+
+
+sub get_diff {
+       my ($from, $treeish) = @_;
+       assert_tree($from);
+       print "diff-tree $from $treeish\n";
+       my $pid = open my $diff_fh, '-|';
+       defined $pid or croak $!;
+       if ($pid == 0) {
+               my @diff_tree = qw(git-diff-tree -z -r);
+               if ($_cp_similarity) {
+                       push @diff_tree, "-C$_cp_similarity";
+               } else {
+                       push @diff_tree, '-C';
+               }
+               push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+               push @diff_tree, "-l$_l" if defined $_l;
+               exec(@diff_tree, $from, $treeish) or croak $!;
+       }
+       return parse_diff_tree($diff_fh);
+}
+
+sub svn_checkout_tree {
+       my ($from, $treeish) = @_;
+       my $mods = get_diff($from->{commit}, $treeish);
+       return $mods unless (scalar @$mods);
+       my ($rm, $add) = precommit_check($mods);
+
+       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+               if ($m->{chg} eq 'C') {
+                       svn_ensure_parent_path( $m->{file_b} );
+                       sys(qw(svn cp),         $m->{file_a}, $m->{file_b});
+                       apply_mod_line_blob($m);
+                       svn_check_prop_executable($m);
+               } elsif ($m->{chg} eq 'D') {
+                       sys(qw(svn rm --force), $m->{file_b});
+               } elsif ($m->{chg} eq 'R') {
+                       svn_ensure_parent_path( $m->{file_b} );
+                       sys(qw(svn mv --force), $m->{file_a}, $m->{file_b});
+                       apply_mod_line_blob($m);
+                       svn_check_prop_executable($m);
+               } elsif ($m->{chg} eq 'M') {
+                       apply_mod_line_blob($m);
+                       svn_check_prop_executable($m);
+               } elsif ($m->{chg} eq 'T') {
+                       sys(qw(svn rm --force),$m->{file_b});
+                       apply_mod_line_blob($m);
+                       sys(qw(svn add), $m->{file_b});
+                       svn_check_prop_executable($m);
+               } elsif ($m->{chg} eq 'A') {
+                       svn_ensure_parent_path( $m->{file_b} );
+                       apply_mod_line_blob($m);
+                       sys(qw(svn add), $m->{file_b});
+                       svn_check_prop_executable($m);
+               } else {
+                       croak "Invalid chg: $m->{chg}\n";
+               }
+       }
+
+       assert_tree($treeish);
+       if ($_rmdir) { # remove empty directories
+               handle_rmdir($rm, $add);
+       }
+       assert_tree($treeish);
+       return $mods;
+}
+
+sub libsvn_checkout_tree {
+       my ($from, $treeish, $ed) = @_;
+       my $mods = get_diff($from, $treeish);
+       return $mods unless (scalar @$mods);
+       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+               my $f = $m->{chg};
+               if (defined $o{$f}) {
+                       $ed->$f($m, $_q);
+               } else {
+                       croak "Invalid change type: $f\n";
+               }
+       }
+       $ed->rmdirs($_q) if $_rmdir;
+       return $mods;
+}
+
+# svn ls doesn't work with respect to the current working tree, but what's
+# in the repository.  There's not even an option for it... *sigh*
+# (added files don't show up and removed files remain in the ls listing)
+sub svn_ls_current {
+       my ($dir, $rm, $add) = @_;
+       chomp(my @ls = safe_qx('svn','ls',$dir));
+       my @ret = ();
+       foreach (@ls) {
+               s#/$##; # trailing slashes are evil
+               push @ret, $_ unless $rm->{$dir}->{$_};
+       }
+       if (exists $add->{$dir}) {
+               push @ret, keys %{$add->{$dir}};
+       }
+       return \@ret;
+}
+
+sub handle_rmdir {
+       my ($rm, $add) = @_;
+
+       foreach my $dir (sort {length $b <=> length $a} keys %$rm) {
+               my $ls = svn_ls_current($dir, $rm, $add);
+               next if (scalar @$ls);
+               sys(qw(svn rm --force),$dir);
+
+               my $dn = dirname $dir;
+               $rm->{ $dn }->{ basename $dir } = 1;
+               $ls = svn_ls_current($dn, $rm, $add);
+               while (scalar @$ls == 0 && $dn ne File::Spec->curdir) {
+                       sys(qw(svn rm --force),$dn);
+                       $dir = basename $dn;
+                       $dn = dirname $dn;
+                       $rm->{ $dn }->{ $dir } = 1;
+                       $ls = svn_ls_current($dn, $rm, $add);
+               }
+       }
+}
+
+sub get_commit_message {
+       my ($commit, $commit_msg) = (@_);
+       my %log_msg = ( msg => '' );
+       open my $msg, '>', $commit_msg or croak $!;
+
+       chomp(my $type = `git-cat-file -t $commit`);
+       if ($type eq 'commit' || $type eq 'tag') {
+               my $pid = open my $msg_fh, '-|';
+               defined $pid or croak $!;
+
+               if ($pid == 0) {
+                       exec('git-cat-file', $type, $commit) or croak $!;
+               }
+               my $in_msg = 0;
+               while (<$msg_fh>) {
+                       if (!$in_msg) {
+                               $in_msg = 1 if (/^\s*$/);
+                       } elsif (/^git-svn-id: /) {
+                               # skip this, we regenerate the correct one
+                               # on re-fetch anyways
+                       } else {
+                               print $msg $_ or croak $!;
+                       }
+               }
+               close $msg_fh or croak $?;
+       }
+       close $msg or croak $!;
+
+       if ($_edit || ($type eq 'tree')) {
+               my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
+               system($editor, $commit_msg);
+       }
+
+       # file_to_s removes all trailing newlines, so just use chomp() here:
+       open $msg, '<', $commit_msg or croak $!;
+       { local $/; chomp($log_msg{msg} = <$msg>); }
+       close $msg or croak $!;
+
+       return \%log_msg;
+}
+
+sub set_svn_commit_env {
+       if (defined $LC_ALL) {
+               $ENV{LC_ALL} = $LC_ALL;
+       } else {
+               delete $ENV{LC_ALL};
+       }
+}
+
+sub svn_commit_tree {
+       my ($last, $commit) = @_;
+       my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
+       my $log_msg = get_commit_message($commit, $commit_msg);
+       my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/);
+       print "Committing $commit: $oneline\n";
+
+       set_svn_commit_env();
+       my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
+       $ENV{LC_ALL} = 'C';
+       unlink $commit_msg;
+       my ($committed) = ($ci_output[$#ci_output] =~ /(\d+)/);
+       if (!defined $committed) {
+               my $out = join("\n",@ci_output);
+               print STDERR "W: Trouble parsing \`svn commit' output:\n\n",
+                               $out, "\n\nAssuming English locale...";
+               ($committed) = ($out =~ /^Committed revision \d+\./sm);
+               defined $committed or die " FAILED!\n",
+                       "Commit output failed to parse committed revision!\n",
+               print STDERR " OK\n";
+       }
+
+       my @svn_up = qw(svn up);
+       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
+       if ($_optimize_commits && ($committed == ($last->{revision} + 1))) {
+               push @svn_up, "-r$committed";
+               sys(@svn_up);
+               my $info = svn_info('.');
+               my $date = $info->{'Last Changed Date'} or die "Missing date\n";
+               if ($info->{'Last Changed Rev'} != $committed) {
+                       croak "$info->{'Last Changed Rev'} != $committed\n"
+               }
+               my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
+                                       /(\d{4})\-(\d\d)\-(\d\d)\s
+                                        (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
+                                        or croak "Failed to parse date: $date\n";
+               $log_msg->{date} = "$tz $Y-$m-$d $H:$M:$S";
+               $log_msg->{author} = $info->{'Last Changed Author'};
+               $log_msg->{revision} = $committed;
+               $log_msg->{msg} .= "\n";
+               $log_msg->{parents} = [ $last->{commit} ];
+               $log_msg->{commit} = git_commit($log_msg, $commit);
+               return $log_msg;
+       }
+       # resync immediately
+       push @svn_up, "-r$last->{revision}";
+       sys(@svn_up);
+       return fetch("$committed=$commit");
+}
+
+sub rev_list_raw {
+       my (@args) = @_;
+       my $pid = open my $fh, '-|';
+       defined $pid or croak $!;
+       if (!$pid) {
+               exec(qw/git-rev-list --pretty=raw/, @args) or croak $!;
+       }
+       return { fh => $fh, t => { } };
+}
+
+sub next_rev_list_entry {
+       my $rl = shift;
+       my $fh = $rl->{fh};
+       my $x = $rl->{t};
+       while (<$fh>) {
+               if (/^commit ($sha1)$/o) {
+                       if ($x->{c}) {
+                               $rl->{t} = { c => $1 };
+                               return $x;
+                       } else {
+                               $x->{c} = $1;
+                       }
+               } elsif (/^parent ($sha1)$/o) {
+                       $x->{p}->{$1} = 1;
+               } elsif (s/^    //) {
+                       $x->{m} ||= '';
+                       $x->{m} .= $_;
+               }
+       }
+       return ($x != $rl->{t}) ? $x : undef;
+}
+
+# read the entire log into a temporary file (which is removed ASAP)
+# and store the file handle + parser state
+sub svn_log_raw {
+       my (@log_args) = @_;
+       my $log_fh = IO::File->new_tmpfile or croak $!;
+       my $pid = fork;
+       defined $pid or croak $!;
+       if (!$pid) {
+               open STDOUT, '>&', $log_fh or croak $!;
+               exec (qw(svn log), @log_args) or croak $!
+       }
+       waitpid $pid, 0;
+       croak $? if $?;
+       seek $log_fh, 0, 0 or croak $!;
+       return { state => 'sep', fh => $log_fh };
+}
+
+sub next_log_entry {
+       my $log = shift; # retval of svn_log_raw()
+       my $ret = undef;
+       my $fh = $log->{fh};
+
+       while (<$fh>) {
+               chomp;
+               if (/^\-{72}$/) {
+                       if ($log->{state} eq 'msg') {
+                               if ($ret->{lines}) {
+                                       $ret->{msg} .= $_."\n";
+                                       unless(--$ret->{lines}) {
+                                               $log->{state} = 'sep';
+                                       }
+                               } else {
+                                       croak "Log parse error at: $_\n",
+                                               $ret->{revision},
+                                               "\n";
+                               }
+                               next;
+                       }
+                       if ($log->{state} ne 'sep') {
+                               croak "Log parse error at: $_\n",
+                                       "state: $log->{state}\n",
+                                       $ret->{revision},
+                                       "\n";
+                       }
+                       $log->{state} = 'rev';
+
+                       # if we have an empty log message, put something there:
+                       if ($ret) {
+                               $ret->{msg} ||= "\n";
+                               delete $ret->{lines};
+                               return $ret;
+                       }
+                       next;
+               }
+               if ($log->{state} eq 'rev' && s/^r(\d+)\s*\|\s*//) {
+                       my $rev = $1;
+                       my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3);
+                       ($lines) = ($lines =~ /(\d+)/);
+                       my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
+                                       /(\d{4})\-(\d\d)\-(\d\d)\s
+                                        (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
+                                        or croak "Failed to parse date: $date\n";
+                       $ret = {        revision => $rev,
+                                       date => "$tz $Y-$m-$d $H:$M:$S",
+                                       author => $author,
+                                       lines => $lines,
+                                       msg => '' };
+                       if (defined $_authors && ! defined $users{$author}) {
+                               die "Author: $author not defined in ",
+                                               "$_authors file\n";
+                       }
+                       $log->{state} = 'msg_start';
+                       next;
+               }
+               # skip the first blank line of the message:
+               if ($log->{state} eq 'msg_start' && /^$/) {
+                       $log->{state} = 'msg';
+               } elsif ($log->{state} eq 'msg') {
+                       if ($ret->{lines}) {
+                               $ret->{msg} .= $_."\n";
+                               unless (--$ret->{lines}) {
+                                       $log->{state} = 'sep';
+                               }
+                       } else {
+                               croak "Log parse error at: $_\n",
+                                       $ret->{revision},"\n";
+                       }
+               }
+       }
+       return $ret;
+}
+
+sub svn_info {
+       my $url = shift || $SVN_URL;
+
+       my $pid = open my $info_fh, '-|';
+       defined $pid or croak $!;
+
+       if ($pid == 0) {
+               exec(qw(svn info),$url) or croak $!;
+       }
+
+       my $ret = {};
+       # only single-lines seem to exist in svn info output
+       while (<$info_fh>) {
+               chomp $_;
+               if (m#^([^:]+)\s*:\s*(\S.*)$#) {
+                       $ret->{$1} = $2;
+                       push @{$ret->{-order}}, $1;
+               }
+       }
+       close $info_fh or croak $?;
+       return $ret;
+}
+
+sub sys { system(@_) == 0 or croak $? }
+
+sub eol_cp {
+       my ($from, $to) = @_;
+       my $es = svn_propget_base('svn:eol-style', $to);
+       open my $rfd, '<', $from or croak $!;
+       binmode $rfd or croak $!;
+       open my $wfd, '>', $to or croak $!;
+       binmode $wfd or croak $!;
+       eol_cp_fd($rfd, $wfd, $es);
+       close $rfd or croak $!;
+       close $wfd or croak $!;
+}
+
+sub eol_cp_fd {
+       my ($rfd, $wfd, $es) = @_;
+       my $eol = defined $es ? $EOL{$es} : undef;
+       my $buf;
+       use bytes;
+       while (1) {
+               my ($r, $w, $t);
+               defined($r = sysread($rfd, $buf, 4096)) or croak $!;
+               return unless $r;
+               if ($eol) {
+                       if ($buf =~ /\015$/) {
+                               my $c;
+                               defined($r = sysread($rfd,$c,1)) or croak $!;
+                               $buf .= $c if $r > 0;
+                       }
+                       $buf =~ s/(?:\015\012|\015|\012)/$eol/gs;
+                       $r = length($buf);
+               }
+               for ($w = 0; $w < $r; $w += $t) {
+                       $t = syswrite($wfd, $buf, $r - $w, $w) or croak $!;
+               }
+       }
+       no bytes;
+}
+
+sub do_update_index {
+       my ($z_cmd, $cmd, $no_text_base) = @_;
+
+       my $z = open my $p, '-|';
+       defined $z or croak $!;
+       unless ($z) { exec @$z_cmd or croak $! }
+
+       my $pid = open my $ui, '|-';
+       defined $pid or croak $!;
+       unless ($pid) {
+               exec('git-update-index',"--$cmd",'-z','--stdin') or croak $!;
+       }
+       local $/ = "\0";
+       while (my $x = <$p>) {
+               chomp $x;
+               if (!$no_text_base && lstat $x && ! -l _ &&
+                               svn_propget_base('svn:keywords', $x)) {
+                       my $mode = -x _ ? 0755 : 0644;
+                       my ($v,$d,$f) = File::Spec->splitpath($x);
+                       my $tb = File::Spec->catfile($d, '.svn', 'tmp',
+                                               'text-base',"$f.svn-base");
+                       $tb =~ s#^/##;
+                       unless (-f $tb) {
+                               $tb = File::Spec->catfile($d, '.svn',
+                                               'text-base',"$f.svn-base");
+                               $tb =~ s#^/##;
+                       }
+                       unlink $x or croak $!;
+                       eol_cp($tb, $x);
+                       chmod(($mode &~ umask), $x) or croak $!;
+               }
+               print $ui $x,"\0";
+       }
+       close $ui or croak $?;
+}
+
+sub index_changes {
+       return if $_use_lib;
+
+       if (!-f "$GIT_SVN_DIR/info/exclude") {
+               open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!;
+               print $fd '.svn',"\n";
+               close $fd or croak $!;
+       }
+       my $no_text_base = shift;
+       do_update_index([qw/git-diff-files --name-only -z/],
+                       'remove',
+                       $no_text_base);
+       do_update_index([qw/git-ls-files -z --others/,
+                               "--exclude-from=$GIT_SVN_DIR/info/exclude"],
+                       'add',
+                       $no_text_base);
+}
+
+sub s_to_file {
+       my ($str, $file, $mode) = @_;
+       open my $fd,'>',$file or croak $!;
+       print $fd $str,"\n" or croak $!;
+       close $fd or croak $!;
+       chmod ($mode &~ umask, $file) if (defined $mode);
+}
+
+sub file_to_s {
+       my $file = shift;
+       open my $fd,'<',$file or croak "$!: file: $file\n";
+       local $/;
+       my $ret = <$fd>;
+       close $fd or croak $!;
+       $ret =~ s/\s*$//s;
+       return $ret;
+}
+
+sub assert_revision_unknown {
+       my $r = shift;
+       if (my $c = revdb_get($REVDB, $r)) {
+               croak "$r = $c already exists! Why are we refetching it?";
+       }
+}
+
+sub trees_eq {
+       my ($x, $y) = @_;
+       my @x = safe_qx('git-cat-file','commit',$x);
+       my @y = safe_qx('git-cat-file','commit',$y);
+       if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/
+                               || $y[0] !~ /^tree $sha1\n$/) {
+               print STDERR "Trees not equal: $y[0] != $x[0]\n";
+               return 0
+       }
+       return 1;
+}
+
+sub git_commit {
+       my ($log_msg, @parents) = @_;
+       assert_revision_unknown($log_msg->{revision});
+       map_tree_joins() if (@_branch_from && !%tree_map);
+
+       my (@tmp_parents, @exec_parents, %seen_parent);
+       if (my $lparents = $log_msg->{parents}) {
+               @tmp_parents = @$lparents
+       }
+       # commit parents can be conditionally bound to a particular
+       # svn revision via: "svn_revno=commit_sha1", filter them out here:
+       foreach my $p (@parents) {
+               next unless defined $p;
+               if ($p =~ /^(\d+)=($sha1_short)$/o) {
+                       if ($1 == $log_msg->{revision}) {
+                               push @tmp_parents, $2;
+                       }
+               } else {
+                       push @tmp_parents, $p if $p =~ /$sha1_short/o;
+               }
+       }
+       my $tree = $log_msg->{tree};
+       if (!defined $tree) {
+               my $index = set_index($GIT_SVN_INDEX);
+               index_changes();
+               chomp($tree = `git-write-tree`);
+               croak $? if $?;
+               restore_index($index);
+       }
+
+       # just in case we clobber the existing ref, we still want that ref
+       # as our parent:
+       if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) {
+               push @tmp_parents, $cur;
+       }
+
+       if (exists $tree_map{$tree}) {
+               foreach my $p (@{$tree_map{$tree}}) {
+                       my $skip;
+                       foreach (@tmp_parents) {
+                               # see if a common parent is found
+                               my $mb = eval {
+                                       safe_qx('git-merge-base', $_, $p)
+                               };
+                               next if ($@ || $?);
+                               $skip = 1;
+                               last;
+                       }
+                       next if $skip;
+                       my ($url_p, $r_p, $uuid_p) = cmt_metadata($p);
+                       next if (($SVN_UUID eq $uuid_p) &&
+                                               ($log_msg->{revision} > $r_p));
+                       next if (defined $url_p && defined $SVN_URL &&
+                                               ($SVN_UUID eq $uuid_p) &&
+                                               ($url_p eq $SVN_URL));
+                       push @tmp_parents, $p;
+               }
+       }
+       foreach (@tmp_parents) {
+               next if $seen_parent{$_};
+               $seen_parent{$_} = 1;
+               push @exec_parents, $_;
+               # MAXPARENT is defined to 16 in commit-tree.c:
+               last if @exec_parents > 16;
+       }
+
+       set_commit_env($log_msg);
+       my @exec = ('git-commit-tree', $tree);
+       push @exec, '-p', $_  foreach @exec_parents;
+       defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
+                                                               or croak $!;
+       print $msg_fh $log_msg->{msg} or croak $!;
+       unless ($_no_metadata) {
+               print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
+                                       " $SVN_UUID\n" or croak $!;
+       }
+       $msg_fh->flush == 0 or croak $!;
+       close $msg_fh or croak $!;
+       chomp(my $commit = do { local $/; <$out_fh> });
+       close $out_fh or croak $!;
+       waitpid $pid, 0;
+       croak $? if $?;
+       if ($commit !~ /^$sha1$/o) {
+               die "Failed to commit, invalid sha1: $commit\n";
+       }
+       sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
+       revdb_set($REVDB, $log_msg->{revision}, $commit);
+
+       # this output is read via pipe, do not change:
+       print "r$log_msg->{revision} = $commit\n";
+       check_repack();
+       return $commit;
+}
+
+sub check_repack {
+       if ($_repack && (--$_repack_nr == 0)) {
+               $_repack_nr = $_repack;
+               sys("git repack $_repack_flags");
+       }
+}
+
+sub set_commit_env {
+       my ($log_msg) = @_;
+       my $author = $log_msg->{author};
+       if (!defined $author || length $author == 0) {
+               $author = '(no author)';
+       }
+       my ($name,$email) = defined $users{$author} ?  @{$users{$author}}
+                               : ($author,"$author\@$SVN_UUID");
+       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
+       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
+       $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
+}
+
+sub apply_mod_line_blob {
+       my $m = shift;
+       if ($m->{mode_b} =~ /^120/) {
+               blob_to_symlink($m->{sha1_b}, $m->{file_b});
+       } else {
+               blob_to_file($m->{sha1_b}, $m->{file_b});
+       }
+}
+
+sub blob_to_symlink {
+       my ($blob, $link) = @_;
+       defined $link or croak "\$link not defined!\n";
+       croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
+       if (-l $link || -f _) {
+               unlink $link or croak $!;
+       }
+
+       my $dest = `git-cat-file blob $blob`; # no newline, so no chomp
+       symlink $dest, $link or croak $!;
+}
+
+sub blob_to_file {
+       my ($blob, $file) = @_;
+       defined $file or croak "\$file not defined!\n";
+       croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
+       if (-l $file || -f _) {
+               unlink $file or croak $!;
+       }
+
+       open my $blob_fh, '>', $file or croak "$!: $file\n";
+       my $pid = fork;
+       defined $pid or croak $!;
+
+       if ($pid == 0) {
+               open STDOUT, '>&', $blob_fh or croak $!;
+               exec('git-cat-file','blob',$blob) or croak $!;
+       }
+       waitpid $pid, 0;
+       croak $? if $?;
+
+       close $blob_fh or croak $!;
+}
+
+sub safe_qx {
+       my $pid = open my $child, '-|';
+       defined $pid or croak $!;
+       if ($pid == 0) {
+               exec(@_) or croak $!;
+       }
+       my @ret = (<$child>);
+       close $child or croak $?;
+       die $? if $?; # just in case close didn't error out
+       return wantarray ? @ret : join('',@ret);
+}
+
+sub svn_compat_check {
+       if ($_follow_parent) {
+               print STDERR 'E: --follow-parent functionality is only ',
+                               "available when SVN libraries are used\n";
+               exit 1;
+       }
+       my @co_help = safe_qx(qw(svn co -h));
+       unless (grep /ignore-externals/,@co_help) {
+               print STDERR "W: Installed svn version does not support ",
+                               "--ignore-externals\n";
+               $_no_ignore_ext = 1;
+       }
+       if (grep /usage: checkout URL\[\@REV\]/,@co_help) {
+               $_svn_co_url_revs = 1;
+       }
+       if (grep /\[TARGET\[\@REV\]\.\.\.\]/, `svn propget -h`) {
+               $_svn_pg_peg_revs = 1;
+       }
+
+       # I really, really hope nobody hits this...
+       unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) {
+               print STDERR <<'';
+W: The installed svn version does not support the --stop-on-copy flag in
+   the log command.
+   Lets hope the directory you're tracking is not a branch or tag
+   and was never moved within the repository...
+
+               $_no_stop_copy = 1;
+       }
+}
+
+# *sigh*, new versions of svn won't honor -r<rev> without URL@<rev>,
+# (and they won't honor URL@<rev> without -r<rev>, too!)
+sub svn_cmd_checkout {
+       my ($url, $rev, $dir) = @_;
+       my @cmd = ('svn','co', "-r$rev");
+       push @cmd, '--ignore-externals' unless $_no_ignore_ext;
+       $url .= "\@$rev" if $_svn_co_url_revs;
+       sys(@cmd, $url, $dir);
+}
+
+sub check_upgrade_needed {
+       if (!-r $REVDB) {
+               -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
+               open my $fh, '>>',$REVDB or croak $!;
+               close $fh;
+       }
+       my $old = eval {
+               my $pid = open my $child, '-|';
+               defined $pid or croak $!;
+               if ($pid == 0) {
+                       close STDERR;
+                       exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $!;
+               }
+               my @ret = (<$child>);
+               close $child or croak $?;
+               die $? if $?; # just in case close didn't error out
+               return wantarray ? @ret : join('',@ret);
+       };
+       return unless $old;
+       my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
+       if ($@ || !$head) {
+               print STDERR "Please run: $0 rebuild --upgrade\n";
+               exit 1;
+       }
+}
+
+# fills %tree_map with a reverse mapping of trees to commits.  Useful
+# for finding parents to commit on.
+sub map_tree_joins {
+       my %seen;
+       foreach my $br (@_branch_from) {
+               my $pid = open my $pipe, '-|';
+               defined $pid or croak $!;
+               if ($pid == 0) {
+                       exec(qw(git-rev-list --topo-order --pretty=raw), $br)
+                                                               or croak $!;
+               }
+               while (<$pipe>) {
+                       if (/^commit ($sha1)$/o) {
+                               my $commit = $1;
+
+                               # if we've seen a commit,
+                               # we've seen its parents
+                               last if $seen{$commit};
+                               my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
+                               unless (defined $tree) {
+                                       die "Failed to parse commit $commit\n";
+                               }
+                               push @{$tree_map{$tree}}, $commit;
+                               $seen{$commit} = 1;
+                       }
+               }
+               close $pipe; # we could be breaking the pipe early
+       }
+}
+
+sub load_all_refs {
+       if (@_branch_from) {
+               print STDERR '--branch|-b parameters are ignored when ',
+                       "--branch-all-refs|-B is passed\n";
+       }
+
+       # don't worry about rev-list on non-commit objects/tags,
+       # it shouldn't blow up if a ref is a blob or tree...
+       chomp(@_branch_from = `git-rev-parse --symbolic --all`);
+}
+
+# '<svn username> = real-name <email address>' mapping based on git-svnimport:
+sub load_authors {
+       open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+       while (<$authors>) {
+               chomp;
+               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               my ($user, $name, $email) = ($1, $2, $3);
+               $users{$user} = [$name, $email];
+       }
+       close $authors or croak $!;
+}
+
+sub rload_authors {
+       open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+       while (<$authors>) {
+               chomp;
+               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               my ($user, $name, $email) = ($1, $2, $3);
+               $rusers{"$name <$email>"} = $user;
+       }
+       close $authors or croak $!;
+}
+
+sub svn_propget_base {
+       my ($p, $f) = @_;
+       $f .= '@BASE' if $_svn_pg_peg_revs;
+       return safe_qx(qw/svn propget/, $p, $f);
+}
+
+sub git_svn_each {
+       my $sub = shift;
+       foreach (`git-rev-parse --symbolic --all`) {
+               next unless s#^refs/remotes/##;
+               chomp $_;
+               next unless -f "$GIT_DIR/svn/$_/info/url";
+               &$sub($_);
+       }
+}
+
+sub migrate_revdb {
+       git_svn_each(sub {
+               my $id = shift;
+               defined(my $pid = fork) or croak $!;
+               if (!$pid) {
+                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+                       init_vars();
+                       exit 0 if -r $REVDB;
+                       print "Upgrading svn => git mapping...\n";
+                       -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
+                       open my $fh, '>>',$REVDB or croak $!;
+                       close $fh;
+                       rebuild();
+                       print "Done upgrading. You may now delete the ",
+                               "deprecated $GIT_SVN_DIR/revs directory\n";
+                       exit 0;
+               }
+               waitpid $pid, 0;
+               croak $? if $?;
+       });
+}
+
+sub migration_check {
+       migrate_revdb() unless (-e $REVDB);
+       return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR);
+       print "Upgrading repository...\n";
+       unless (-d "$GIT_DIR/svn") {
+               mkdir "$GIT_DIR/svn" or croak $!;
+       }
+       print "Data from a previous version of git-svn exists, but\n\t",
+                               "$GIT_SVN_DIR\n\t(required for this version ",
+                               "($VERSION) of git-svn) does not.\n";
+
+       foreach my $x (`git-rev-parse --symbolic --all`) {
+               next unless $x =~ s#^refs/remotes/##;
+               chomp $x;
+               next unless -f "$GIT_DIR/$x/info/url";
+               my $u = eval { file_to_s("$GIT_DIR/$x/info/url") };
+               next unless $u;
+               my $dn = dirname("$GIT_DIR/svn/$x");
+               mkpath([$dn]) unless -d $dn;
+               rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x";
+       }
+       migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);
+       print "Done upgrading.\n";
+}
+
+sub find_rev_before {
+       my ($r, $id, $eq_ok) = @_;
+       my $f = "$GIT_DIR/svn/$id/.rev_db";
+       return (undef,undef) unless -r $f;
+       --$r unless $eq_ok;
+       while ($r > 0) {
+               if (my $c = revdb_get($f, $r)) {
+                       return ($r, $c);
+               }
+               --$r;
+       }
+       return (undef, undef);
+}
+
+sub init_vars {
+       $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
+       $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN";
+       $REVDB = "$GIT_SVN_DIR/.rev_db";
+       $GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
+       $SVN_URL = undef;
+       $SVN_WC = "$GIT_SVN_DIR/tree";
+       %tree_map = ();
+}
+
+# convert GetOpt::Long specs for use by git-repo-config
+sub read_repo_config {
+       return unless -d $GIT_DIR;
+       my $opts = shift;
+       foreach my $o (keys %$opts) {
+               my $v = $opts->{$o};
+               my ($key) = ($o =~ /^([a-z\-]+)/);
+               $key =~ s/-//g;
+               my $arg = 'git-repo-config';
+               $arg .= ' --int' if ($o =~ /[:=]i$/);
+               $arg .= ' --bool' if ($o !~ /[:=][sfi]$/);
+               if (ref $v eq 'ARRAY') {
+                       chomp(my @tmp = `$arg --get-all svn.$key`);
+                       @$v = @tmp if @tmp;
+               } else {
+                       chomp(my $tmp = `$arg --get svn.$key`);
+                       if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {
+                               $$v = $tmp;
+                       }
+               }
+       }
+}
+
+sub set_default_vals {
+       if (defined $_repack) {
+               $_repack = 1000 if ($_repack <= 0);
+               $_repack_nr = $_repack;
+               $_repack_flags ||= '-d';
+       }
+}
+
+sub read_grafts {
+       my $gr_file = shift;
+       my ($grafts, $comments) = ({}, {});
+       if (open my $fh, '<', $gr_file) {
+               my @tmp;
+               while (<$fh>) {
+                       if (/^($sha1)\s+/) {
+                               my $c = $1;
+                               if (@tmp) {
+                                       @{$comments->{$c}} = @tmp;
+                                       @tmp = ();
+                               }
+                               foreach my $p (split /\s+/, $_) {
+                                       $grafts->{$c}->{$p} = 1;
+                               }
+                       } else {
+                               push @tmp, $_;
+                       }
+               }
+               close $fh or croak $!;
+               @{$comments->{'END'}} = @tmp if @tmp;
+       }
+       return ($grafts, $comments);
+}
+
+sub write_grafts {
+       my ($grafts, $comments, $gr_file) = @_;
+
+       open my $fh, '>', $gr_file or croak $!;
+       foreach my $c (sort keys %$grafts) {
+               if ($comments->{$c}) {
+                       print $fh $_ foreach @{$comments->{$c}};
+               }
+               my $p = $grafts->{$c};
+               my %x; # real parents
+               delete $p->{$c}; # commits are not self-reproducing...
+               my $pid = open my $ch, '-|';
+               defined $pid or croak $!;
+               if (!$pid) {
+                       exec(qw/git-cat-file commit/, $c) or croak $!;
+               }
+               while (<$ch>) {
+                       if (/^parent ($sha1)/) {
+                               $x{$1} = $p->{$1} = 1;
+                       } else {
+                               last unless /^\S/;
+                       }
+               }
+               close $ch; # breaking the pipe
+
+               # if real parents are the only ones in the grafts, drop it
+               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
+
+               my (@ip, @jp, $mb);
+               my %del = %x;
+               @ip = @jp = keys %$p;
+               foreach my $i (@ip) {
+                       next if $del{$i} || $p->{$i} == 2;
+                       foreach my $j (@jp) {
+                               next if $i eq $j || $del{$j} || $p->{$j} == 2;
+                               $mb = eval { safe_qx('git-merge-base',$i,$j) };
+                               next unless $mb;
+                               chomp $mb;
+                               next if $x{$mb};
+                               if ($mb eq $j) {
+                                       delete $p->{$i};
+                                       $del{$i} = 1;
+                               } elsif ($mb eq $i) {
+                                       delete $p->{$j};
+                                       $del{$j} = 1;
+                               }
+                       }
+               }
+
+               # if real parents are the only ones in the grafts, drop it
+               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
+
+               print $fh $c, ' ', join(' ', sort keys %$p),"\n";
+       }
+       if ($comments->{'END'}) {
+               print $fh $_ foreach @{$comments->{'END'}};
+       }
+       close $fh or croak $!;
+}
+
+sub read_url_paths_all {
+       my ($l_map, $pfx, $p) = @_;
+       my @dir;
+       foreach (<$p/*>) {
+               if (-r "$_/info/url") {
+                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
+                       my $id = $pfx . basename $_;
+                       my $url = file_to_s("$_/info/url");
+                       my ($u, $p) = repo_path_split($url);
+                       $l_map->{$u}->{$p} = $id;
+               } elsif (-d $_) {
+                       push @dir, $_;
+               }
+       }
+       foreach (@dir) {
+               my $x = $_;
+               $x =~ s!^\Q$GIT_DIR\E/svn/!!o;
+               read_url_paths_all($l_map, $x, $_);
+       }
+}
+
+# this one only gets ids that have been imported, not new ones
+sub read_url_paths {
+       my $l_map = {};
+       git_svn_each(sub { my $x = shift;
+                       my $url = file_to_s("$GIT_DIR/svn/$x/info/url");
+                       my ($u, $p) = repo_path_split($url);
+                       $l_map->{$u}->{$p} = $x;
+                       });
+       return $l_map;
+}
+
+sub extract_metadata {
+       my $id = shift or return (undef, undef, undef);
+       my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
+                                                       \s([a-f\d\-]+)$/x);
+       if (!$rev || !$uuid || !$url) {
+               # some of the original repositories I made had
+               # identifiers like this:
+               ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
+       }
+       return ($url, $rev, $uuid);
+}
+
+sub cmt_metadata {
+       return extract_metadata((grep(/^git-svn-id: /,
+               safe_qx(qw/git-cat-file commit/, shift)))[-1]);
+}
+
+sub get_commit_time {
+       my $cmt = shift;
+       defined(my $pid = open my $fh, '-|') or croak $!;
+       if (!$pid) {
+               exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!;
+       }
+       while (<$fh>) {
+               /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
+               my ($s, $tz) = ($1, $2);
+               if ($tz =~ s/^\+//) {
+                       $s += tz_to_s_offset($tz);
+               } elsif ($tz =~ s/^\-//) {
+                       $s -= tz_to_s_offset($tz);
+               }
+               close $fh;
+               return $s;
+       }
+       die "Can't get commit time for commit: $cmt\n";
+}
+
+sub tz_to_s_offset {
+       my ($tz) = @_;
+       $tz =~ s/(\d\d)$//;
+       return ($1 * 60) + ($tz * 3600);
+}
+
+sub setup_pager { # translated to Perl from pager.c
+       return unless (-t *STDOUT);
+       my $pager = $ENV{PAGER};
+       if (!defined $pager) {
+               $pager = 'less';
+       } elsif (length $pager == 0 || $pager eq 'cat') {
+               return;
+       }
+       pipe my $rfd, my $wfd or return;
+       defined(my $pid = fork) or croak $!;
+       if (!$pid) {
+               open STDOUT, '>&', $wfd or croak $!;
+               return;
+       }
+       open STDIN, '<&', $rfd or croak $!;
+       $ENV{LESS} ||= '-S';
+       exec $pager or croak "Can't run pager: $!\n";;
+}
+
+sub get_author_info {
+       my ($dest, $author, $t, $tz) = @_;
+       $author =~ s/(?:^\s*|\s*$)//g;
+       $dest->{a_raw} = $author;
+       my $_a;
+       if ($_authors) {
+               $_a = $rusers{$author} || undef;
+       }
+       if (!$_a) {
+               ($_a) = ($author =~ /<([^>]+)\@[^>]+>$/);
+       }
+       $dest->{t} = $t;
+       $dest->{tz} = $tz;
+       $dest->{a} = $_a;
+       # Date::Parse isn't in the standard Perl distro :(
+       if ($tz =~ s/^\+//) {
+               $t += tz_to_s_offset($tz);
+       } elsif ($tz =~ s/^\-//) {
+               $t -= tz_to_s_offset($tz);
+       }
+       $dest->{t_utc} = $t;
+}
+
+sub process_commit {
+       my ($c, $r_min, $r_max, $defer) = @_;
+       if (defined $r_min && defined $r_max) {
+               if ($r_min == $c->{r} && $r_min == $r_max) {
+                       show_commit($c);
+                       return 0;
+               }
+               return 1 if $r_min == $r_max;
+               if ($r_min < $r_max) {
+                       # we need to reverse the print order
+                       return 0 if (defined $_limit && --$_limit < 0);
+                       push @$defer, $c;
+                       return 1;
+               }
+               if ($r_min != $r_max) {
+                       return 1 if ($r_min < $c->{r});
+                       return 1 if ($r_max > $c->{r});
+               }
+       }
+       return 0 if (defined $_limit && --$_limit < 0);
+       show_commit($c);
+       return 1;
+}
+
+sub show_commit {
+       my $c = shift;
+       if ($_oneline) {
+               my $x = "\n";
+               if (my $l = $c->{l}) {
+                       while ($l->[0] =~ /^\s*$/) { shift @$l }
+                       $x = $l->[0];
+               }
+               $_l_fmt ||= 'A' . length($c->{r});
+               print 'r',pack($_l_fmt, $c->{r}),' | ';
+               print "$c->{c} | " if $_show_commit;
+               print $x;
+       } else {
+               show_commit_normal($c);
+       }
+}
+
+sub show_commit_normal {
+       my ($c) = @_;
+       print '-' x72, "\nr$c->{r} | ";
+       print "$c->{c} | " if $_show_commit;
+       print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
+                                localtime($c->{t_utc})), ' | ';
+       my $nr_line = 0;
+
+       if (my $l = $c->{l}) {
+               while ($l->[$#$l] eq "\n" && $l->[($#$l - 1)] eq "\n") {
+                       pop @$l;
+               }
+               $nr_line = scalar @$l;
+               if (!$nr_line) {
+                       print "1 line\n\n\n";
+               } else {
+                       if ($nr_line == 1) {
+                               $nr_line = '1 line';
+                       } else {
+                               $nr_line .= ' lines';
+                       }
+                       print $nr_line, "\n\n";
+                       print $_ foreach @$l;
+               }
+       } else {
+               print "1 line\n\n";
+
+       }
+       foreach my $x (qw/raw diff/) {
+               if ($c->{$x}) {
+                       print "\n";
+                       print $_ foreach @{$c->{$x}}
+               }
+       }
+}
+
+sub libsvn_load {
+       return unless $_use_lib;
+       $_use_lib = eval {
+               require SVN::Core;
+               if ($SVN::Core::VERSION lt '1.1.0') {
+                       die "Need SVN::Core 1.1.0 or better ",
+                                       "(got $SVN::Core::VERSION) ",
+                                       "Falling back to command-line svn\n";
+               }
+               require SVN::Ra;
+               require SVN::Delta;
+               push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
+               my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
+                                       $SVN::Node::dir.$SVN::Node::unknown.
+                                       $SVN::Node::none.$SVN::Node::file.
+                                       $SVN::Node::dir.$SVN::Node::unknown;
+               1;
+       };
+}
+
+sub libsvn_connect {
+       my ($url) = @_;
+       my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
+                         SVN::Client::get_ssl_server_trust_file_provider(),
+                         SVN::Client::get_username_provider()]);
+       my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
+       return $s;
+}
+
+sub libsvn_get_file {
+       my ($gui, $f, $rev) = @_;
+       my $p = $f;
+       return unless ($p =~ s#^\Q$SVN_PATH\E/##);
+
+       my ($hash, $pid, $in, $out);
+       my $pool = SVN::Pool->new;
+       defined($pid = open3($in, $out, '>&STDERR',
+                               qw/git-hash-object -w --stdin/)) or croak $!;
+       # redirect STDOUT for SVN 1.1.x compatibility
+       open my $stdout, '>&', \*STDOUT or croak $!;
+       open STDOUT, '>&', $in or croak $!;
+       my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool);
+       $in->flush == 0 or croak $!;
+       open STDOUT, '>&', $stdout or croak $!;
+       close $in or croak $!;
+       close $stdout or croak $!;
+       $pool->clear;
+       chomp($hash = do { local $/; <$out> });
+       close $out or croak $!;
+       waitpid $pid, 0;
+       $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
+
+       my $mode = exists $props->{'svn:executable'} ? '100755' : '100644';
+       if (exists $props->{'svn:special'}) {
+               $mode = '120000';
+               my $link = `git-cat-file blob $hash`;
+               $link =~ s/^link // or die "svn:special file with contents: <",
+                                               $link, "> is not understood\n";
+               defined($pid = open3($in, $out, '>&STDERR',
+                               qw/git-hash-object -w --stdin/)) or croak $!;
+               print $in $link;
+               $in->flush == 0 or croak $!;
+               close $in or croak $!;
+               chomp($hash = do { local $/; <$out> });
+               close $out or croak $!;
+               waitpid $pid, 0;
+               $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
+       }
+       print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!;
+}
+
+sub libsvn_log_entry {
+       my ($rev, $author, $date, $msg, $parents) = @_;
+       my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
+                                        (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x)
+                               or die "Unable to parse date: $date\n";
+       if (defined $_authors && ! defined $users{$author}) {
+               die "Author: $author not defined in $_authors file\n";
+       }
+       return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
+               author => $author, msg => $msg."\n", parents => $parents || [] }
+}
+
+sub process_rm {
+       my ($gui, $last_commit, $f) = @_;
+       $f =~ s#^\Q$SVN_PATH\E/?## or return;
+       # remove entire directories.
+       if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
+               defined(my $pid = open my $ls, '-|') or croak $!;
+               if (!$pid) {
+                       exec(qw/git-ls-tree -r --name-only -z/,
+                               $last_commit,'--',$f) or croak $!;
+               }
+               local $/ = "\0";
+               while (<$ls>) {
+                       print $gui '0 ',0 x 40,"\t",$_ or croak $!;
+               }
+               close $ls or croak $?;
+       } else {
+               print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
+       }
+}
+
+sub libsvn_fetch {
+       my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
+       open my $gui, '| git-update-index -z --index-info' or croak $!;
+       my @amr;
+       foreach my $f (keys %$paths) {
+               my $m = $paths->{$f}->action();
+               $f =~ s#^/+##;
+               if ($m =~ /^[DR]$/) {
+                       print "\t$m\t$f\n" unless $_q;
+                       process_rm($gui, $last_commit, $f);
+                       next if $m eq 'D';
+                       # 'R' can be file replacements, too, right?
+               }
+               my $pool = SVN::Pool->new;
+               my $t = $SVN->check_path($f, $rev, $pool);
+               if ($t == $SVN::Node::file) {
+                       if ($m =~ /^[AMR]$/) {
+                               push @amr, [ $m, $f ];
+                       } else {
+                               die "Unrecognized action: $m, ($f r$rev)\n";
+                       }
+               } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) {
+                       my @traversed = ();
+                       libsvn_traverse($gui, '', $f, $rev, \@traversed);
+                       foreach (@traversed) {
+                               push @amr, [ $m, $_ ]
+                       }
+               }
+               $pool->clear;
+       }
+       foreach (@amr) {
+               print "\t$_->[0]\t$_->[1]\n" unless $_q;
+               libsvn_get_file($gui, $_->[1], $rev)
+       }
+       close $gui or croak $?;
+       return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
+}
+
+sub svn_grab_base_rev {
+       defined(my $pid = open my $fh, '-|') or croak $!;
+       if (!$pid) {
+               open my $null, '>', '/dev/null' or croak $!;
+               open STDERR, '>&', $null or croak $!;
+               exec qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0"
+                                                               or croak $!;
+       }
+       chomp(my $c = do { local $/; <$fh> });
+       close $fh;
+       if (defined $c && length $c) {
+               my ($url, $rev, $uuid) = cmt_metadata($c);
+               return ($rev, $c) if defined $rev;
+       }
+       if ($_no_metadata) {
+               my $offset = -41; # from tail
+               my $rl;
+               open my $fh, '<', $REVDB or
+                       die "--no-metadata specified and $REVDB not readable\n";
+               seek $fh, $offset, 2;
+               $rl = readline $fh;
+               defined $rl or return (undef, undef);
+               chomp $rl;
+               while ($c ne $rl && tell $fh != 0) {
+                       $offset -= 41;
+                       seek $fh, $offset, 2;
+                       $rl = readline $fh;
+                       defined $rl or return (undef, undef);
+                       chomp $rl;
+               }
+               my $rev = tell $fh;
+               croak $! if ($rev < -1);
+               $rev =  ($rev - 41) / 41;
+               close $fh or croak $!;
+               return ($rev, $c);
+       }
+       return (undef, undef);
+}
+
+sub libsvn_parse_revision {
+       my $base = shift;
+       my $head = $SVN->get_latest_revnum();
+       if (!defined $_revision || $_revision eq 'BASE:HEAD') {
+               return ($base + 1, $head) if (defined $base);
+               return (0, $head);
+       }
+       return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
+       return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
+       if ($_revision =~ /^BASE:(\d+)$/) {
+               return ($base + 1, $1) if (defined $base);
+               return (0, $head);
+       }
+       return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/);
+       die "revision argument: $_revision not understood by git-svn\n",
+               "Try using the command-line svn client instead\n";
+}
+
+sub libsvn_traverse {
+       my ($gui, $pfx, $path, $rev, $files) = @_;
+       my $cwd = "$pfx/$path";
+       my $pool = SVN::Pool->new;
+       $cwd =~ s#^/+##g;
+       my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);
+       foreach my $d (keys %$dirent) {
+               my $t = $dirent->{$d}->kind;
+               if ($t == $SVN::Node::dir) {
+                       libsvn_traverse($gui, $cwd, $d, $rev, $files);
+               } elsif ($t == $SVN::Node::file) {
+                       my $file = "$cwd/$d";
+                       if (defined $files) {
+                               push @$files, $file;
+                       } else {
+                               print "\tA\t$file\n" unless $_q;
+                               libsvn_get_file($gui, $file, $rev);
+                       }
+               }
+       }
+       $pool->clear;
+}
+
+sub libsvn_traverse_ignore {
+       my ($fh, $path, $r) = @_;
+       $path =~ s#^/+##g;
+       my $pool = SVN::Pool->new;
+       my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
+       my $p = $path;
+       $p =~ s#^\Q$SVN_PATH\E/?##;
+       print $fh length $p ? "\n# $p\n" : "\n# /\n";
+       if (my $s = $props->{'svn:ignore'}) {
+               $s =~ s/[\r\n]+/\n/g;
+               chomp $s;
+               if (length $p == 0) {
+                       $s =~ s#\n#\n/$p#g;
+                       print $fh "/$s\n";
+               } else {
+                       $s =~ s#\n#\n/$p/#g;
+                       print $fh "/$p/$s\n";
+               }
+       }
+       foreach (sort keys %$dirent) {
+               next if $dirent->{$_}->kind != $SVN::Node::dir;
+               libsvn_traverse_ignore($fh, "$path/$_", $r);
+       }
+       $pool->clear;
+}
+
+sub revisions_eq {
+       my ($path, $r0, $r1) = @_;
+       return 1 if $r0 == $r1;
+       my $nr = 0;
+       if ($_use_lib) {
+               # should be OK to use Pool here (r1 - r0) should be small
+               my $pool = SVN::Pool->new;
+               libsvn_get_log($SVN, "/$path", $r0, $r1,
+                               0, 1, 1, sub {$nr++}, $pool);
+               $pool->clear;
+       } else {
+               my ($url, undef) = repo_path_split($SVN_URL);
+               my $svn_log = svn_log_raw("$url/$path","-r$r0:$r1");
+               while (next_log_entry($svn_log)) { $nr++ }
+               close $svn_log->{fh};
+       }
+       return 0 if ($nr > 1);
+       return 1;
+}
+
+sub libsvn_find_parent_branch {
+       my ($paths, $rev, $author, $date, $msg) = @_;
+       my $svn_path = '/'.$SVN_PATH;
+
+       # look for a parent from another branch:
+       my $i = $paths->{$svn_path} or return;
+       my $branch_from = $i->copyfrom_path or return;
+       my $r = $i->copyfrom_rev;
+       print STDERR  "Found possible branch point: ",
+                               "$branch_from => $svn_path, $r\n";
+       $branch_from =~ s#^/##;
+       my $l_map = {};
+       read_url_paths_all($l_map, '', "$GIT_DIR/svn");
+       my $url = $SVN->{url};
+       defined $l_map->{$url} or return;
+       my $id = $l_map->{$url}->{$branch_from};
+       if (!defined $id && $_follow_parent) {
+               print STDERR "Following parent: $branch_from\@$r\n";
+               # auto create a new branch and follow it
+               $id = basename($branch_from);
+               $id .= '@'.$r if -r "$GIT_DIR/svn/$id";
+               while (-r "$GIT_DIR/svn/$id") {
+                       # just grow a tail if we're not unique enough :x
+                       $id .= '-';
+               }
+       }
+       return unless defined $id;
+
+       my ($r0, $parent) = find_rev_before($r,$id,1);
+       if ($_follow_parent && (!defined $r0 || !defined $parent)) {
+               defined(my $pid = fork) or croak $!;
+               if (!$pid) {
+                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+                       init_vars();
+                       $SVN_URL = "$url/$branch_from";
+                       $SVN_LOG = $SVN = undef;
+                       setup_git_svn();
+                       # we can't assume SVN_URL exists at r+1:
+                       $_revision = "0:$r";
+                       fetch_lib();
+                       exit 0;
+               }
+               waitpid $pid, 0;
+               croak $? if $?;
+               ($r0, $parent) = find_rev_before($r,$id,1);
+       }
+       return unless (defined $r0 && defined $parent);
+       if (revisions_eq($branch_from, $r0, $r)) {
+               unlink $GIT_SVN_INDEX;
+               print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
+               sys(qw/git-read-tree/, $parent);
+               return libsvn_fetch($parent, $paths, $rev,
+                                       $author, $date, $msg);
+       }
+       print STDERR "Nope, branch point not imported or unknown\n";
+       return undef;
+}
+
+sub libsvn_get_log {
+       my ($ra, @args) = @_;
+       if ($SVN::Core::VERSION le '1.2.0') {
+               splice(@args, 3, 1);
+       }
+       $ra->get_log(@args);
+}
+
+sub libsvn_new_tree {
+       if (my $log_entry = libsvn_find_parent_branch(@_)) {
+               return $log_entry;
+       }
+       my ($paths, $rev, $author, $date, $msg) = @_;
+       open my $gui, '| git-update-index -z --index-info' or croak $!;
+       libsvn_traverse($gui, '', $SVN_PATH, $rev);
+       close $gui or croak $?;
+       return libsvn_log_entry($rev, $author, $date, $msg);
+}
+
+sub find_graft_path_commit {
+       my ($tree_paths, $p1, $r1) = @_;
+       foreach my $x (keys %$tree_paths) {
+               next unless ($p1 =~ /^\Q$x\E/);
+               my $i = $tree_paths->{$x};
+               my ($r0, $parent) = find_rev_before($r1,$i,1);
+               return $parent if (defined $r0 && $r0 == $r1);
+               print STDERR "r$r1 of $i not imported\n";
+               next;
+       }
+       return undef;
+}
+
+sub find_graft_path_parents {
+       my ($grafts, $tree_paths, $c, $p0, $r0) = @_;
+       foreach my $x (keys %$tree_paths) {
+               next unless ($p0 =~ /^\Q$x\E/);
+               my $i = $tree_paths->{$x};
+               my ($r, $parent) = find_rev_before($r0, $i, 1);
+               if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {
+                       my ($url_b, undef, $uuid_b) = cmt_metadata($c);
+                       my ($url_a, undef, $uuid_a) = cmt_metadata($parent);
+                       next if ($url_a && $url_b && $url_a eq $url_b &&
+                                                       $uuid_b eq $uuid_a);
+                       $grafts->{$c}->{$parent} = 1;
+               }
+       }
+}
+
+sub libsvn_graft_file_copies {
+       my ($grafts, $tree_paths, $path, $paths, $rev) = @_;
+       foreach (keys %$paths) {
+               my $i = $paths->{$_};
+               my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path,
+                                       $i->copyfrom_rev);
+               next unless (defined $p0 && defined $r0);
+
+               my $p1 = $_;
+               $p1 =~ s#^/##;
+               $p0 =~ s#^/##;
+               my $c = find_graft_path_commit($tree_paths, $p1, $rev);
+               next unless $c;
+               find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0);
+       }
+}
+
+sub set_index {
+       my $old = $ENV{GIT_INDEX_FILE};
+       $ENV{GIT_INDEX_FILE} = shift;
+       return $old;
+}
+
+sub restore_index {
+       my ($old) = @_;
+       if (defined $old) {
+               $ENV{GIT_INDEX_FILE} = $old;
+       } else {
+               delete $ENV{GIT_INDEX_FILE};
+       }
+}
+
+sub libsvn_commit_cb {
+       my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_;
+       if ($_optimize_commits && $rev == ($r_last + 1)) {
+               my $log = libsvn_log_entry($rev,$committer,$date,$msg);
+               $log->{tree} = get_tree_from_treeish($c);
+               my $cmt = git_commit($log, $cmt_last, $c);
+               my @diff = safe_qx('git-diff-tree', $cmt, $c);
+               if (@diff) {
+                       print STDERR "Trees differ: $cmt $c\n",
+                                       join('',@diff),"\n";
+                       exit 1;
+               }
+       } else {
+               fetch("$rev=$c");
+       }
+}
+
+sub libsvn_ls_fullurl {
+       my $fullurl = shift;
+       my ($repo, $path) = repo_path_split($fullurl);
+       $SVN ||= libsvn_connect($repo);
+       my @ret;
+       my $pool = SVN::Pool->new;
+       my ($dirent, undef, undef) = $SVN->get_dir($path,
+                                               $SVN->get_latest_revnum, $pool);
+       foreach my $d (keys %$dirent) {
+               if ($dirent->{$d}->kind == $SVN::Node::dir) {
+                       push @ret, "$d/"; # add '/' for compat with cli svn
+               }
+       }
+       $pool->clear;
+       return @ret;
+}
+
+
+sub libsvn_skip_unknown_revs {
+       my $err = shift;
+       my $errno = $err->apr_err();
+       # Maybe the branch we're tracking didn't
+       # exist when the repo started, so it's
+       # not an error if it doesn't, just continue
+       #
+       # Wonderfully consistent library, eh?
+       # 160013 - svn:// and file://
+       # 175002 - http(s)://
+       #   More codes may be discovered later...
+       if ($errno == 175002 || $errno == 160013) {
+               return;
+       }
+       croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
+};
+
+# Tie::File seems to be prone to offset errors if revisions get sparse,
+# it's not that fast, either.  Tie::File is also not in Perl 5.6.  So
+# one of my favorite modules is out :<  Next up would be one of the DBM
+# modules, but I'm not sure which is most portable...  So I'll just
+# go with something that's plain-text, but still capable of
+# being randomly accessed.  So here's my ultra-simple fixed-width
+# database.  All records are 40 characters + "\n", so it's easy to seek
+# to a revision: (41 * rev) is the byte offset.
+# A record of 40 0s denotes an empty revision.
+# And yes, it's still pretty fast (faster than Tie::File).
+sub revdb_set {
+       my ($file, $rev, $commit) = @_;
+       length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
+       open my $fh, '+<', $file or croak $!;
+       my $offset = $rev * 41;
+       # assume that append is the common case:
+       seek $fh, 0, 2 or croak $!;
+       my $pos = tell $fh;
+       if ($pos < $offset) {
+               print $fh (('0' x 40),"\n") x (($offset - $pos) / 41);
+       }
+       seek $fh, $offset, 0 or croak $!;
+       print $fh $commit,"\n";
+       close $fh or croak $!;
+}
+
+sub revdb_get {
+       my ($file, $rev) = @_;
+       my $ret;
+       my $offset = $rev * 41;
+       open my $fh, '<', $file or croak $!;
+       seek $fh, $offset, 0;
+       if (tell $fh == $offset) {
+               $ret = readline $fh;
+               if (defined $ret) {
+                       chomp $ret;
+                       $ret = undef if ($ret =~ /^0{40}$/);
+               }
+       }
+       close $fh or croak $!;
+       return $ret;
+}
+
+sub copy_remote_ref {
+       my $origin = $_cp_remote ? $_cp_remote : 'origin';
+       my $ref = "refs/remotes/$GIT_SVN";
+       if (safe_qx('git-ls-remote', $origin, $ref)) {
+               sys(qw/git fetch/, $origin, "$ref:$ref");
+       } else {
+               die "Unable to find remote reference: ",
+                               "refs/remotes/$GIT_SVN on $origin\n";
+       }
+}
+
+package SVN::Git::Editor;
+use vars qw/@ISA/;
+use strict;
+use warnings;
+use Carp qw/croak/;
+use IO::File;
+
+sub new {
+       my $class = shift;
+       my $git_svn = shift;
+       my $self = SVN::Delta::Editor->new(@_);
+       bless $self, $class;
+       foreach (qw/svn_path c r ra /) {
+               die "$_ required!\n" unless (defined $git_svn->{$_});
+               $self->{$_} = $git_svn->{$_};
+       }
+       $self->{pool} = SVN::Pool->new;
+       $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };
+       $self->{rm} = { };
+       require Digest::MD5;
+       return $self;
+}
+
+sub split_path {
+       return ($_[0] =~ m#^(.*?)/?([^/]+)$#);
+}
+
+sub repo_path {
+       (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]"
+                                       : $_[0]->{svn_path}
+}
+
+sub url_path {
+       my ($self, $path) = @_;
+       $self->{ra}->{url} . '/' . $self->repo_path($path);
+}
+
+sub rmdirs {
+       my ($self, $q) = @_;
+       my $rm = $self->{rm};
+       delete $rm->{''}; # we never delete the url we're tracking
+       return unless %$rm;
+
+       foreach (keys %$rm) {
+               my @d = split m#/#, $_;
+               my $c = shift @d;
+               $rm->{$c} = 1;
+               while (@d) {
+                       $c .= '/' . shift @d;
+                       $rm->{$c} = 1;
+               }
+       }
+       delete $rm->{$self->{svn_path}};
+       delete $rm->{''}; # we never delete the url we're tracking
+       return unless %$rm;
+
+       defined(my $pid = open my $fh,'-|') or croak $!;
+       if (!$pid) {
+               exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;
+       }
+       local $/ = "\0";
+       my @svn_path = split m#/#, $self->{svn_path};
+       while (<$fh>) {
+               chomp;
+               my @dn = (@svn_path, (split m#/#, $_));
+               while (pop @dn) {
+                       delete $rm->{join '/', @dn};
+               }
+               unless (%$rm) {
+                       close $fh;
+                       return;
+               }
+       }
+       close $fh;
+
+       my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
+       foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
+               $self->close_directory($bat->{$d}, $p);
+               my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
+               print "\tD+\t/$d/\n" unless $q;
+               $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
+               delete $bat->{$d};
+       }
+}
+
+sub open_or_add_dir {
+       my ($self, $full_path, $baton) = @_;
+       my $p = SVN::Pool->new;
+       my $t = $self->{ra}->check_path($full_path, $self->{r}, $p);
+       $p->clear;
+       if ($t == $SVN::Node::none) {
+               return $self->add_directory($full_path, $baton,
+                                               undef, -1, $self->{pool});
+       } elsif ($t == $SVN::Node::dir) {
+               return $self->open_directory($full_path, $baton,
+                                               $self->{r}, $self->{pool});
+       }
+       print STDERR "$full_path already exists in repository at ",
+               "r$self->{r} and it is not a directory (",
+               ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
+       exit 1;
+}
+
+sub ensure_path {
+       my ($self, $path) = @_;
+       my $bat = $self->{bat};
+       $path = $self->repo_path($path);
+       return $bat->{''} unless (length $path);
+       my @p = split m#/+#, $path;
+       my $c = shift @p;
+       $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
+       while (@p) {
+               my $c0 = $c;
+               $c .= '/' . shift @p;
+               $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0});
+       }
+       return $bat->{$c};
+}
+
+sub A {
+       my ($self, $m, $q) = @_;
+       my ($dir, $file) = split_path($m->{file_b});
+       my $pbat = $self->ensure_path($dir);
+       my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+                                       undef, -1);
+       print "\tA\t$m->{file_b}\n" unless $q;
+       $self->chg_file($fbat, $m);
+       $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub C {
+       my ($self, $m, $q) = @_;
+       my ($dir, $file) = split_path($m->{file_b});
+       my $pbat = $self->ensure_path($dir);
+       my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+                               $self->url_path($m->{file_a}), $self->{r});
+       print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q;
+       $self->chg_file($fbat, $m);
+       $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub delete_entry {
+       my ($self, $path, $pbat) = @_;
+       my $rpath = $self->repo_path($path);
+       my ($dir, $file) = split_path($rpath);
+       $self->{rm}->{$dir} = 1;
+       $self->SUPER::delete_entry($rpath, $self->{r}, $pbat, $self->{pool});
+}
+
+sub R {
+       my ($self, $m, $q) = @_;
+       my ($dir, $file) = split_path($m->{file_b});
+       my $pbat = $self->ensure_path($dir);
+       my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+                               $self->url_path($m->{file_a}), $self->{r});
+       print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q;
+       $self->chg_file($fbat, $m);
+       $self->close_file($fbat,undef,$self->{pool});
+
+       ($dir, $file) = split_path($m->{file_a});
+       $pbat = $self->ensure_path($dir);
+       $self->delete_entry($m->{file_a}, $pbat);
+}
+
+sub M {
+       my ($self, $m, $q) = @_;
+       my ($dir, $file) = split_path($m->{file_b});
+       my $pbat = $self->ensure_path($dir);
+       my $fbat = $self->open_file($self->repo_path($m->{file_b}),
+                               $pbat,$self->{r},$self->{pool});
+       print "\t$m->{chg}\t$m->{file_b}\n" unless $q;
+       $self->chg_file($fbat, $m);
+       $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub T { shift->M(@_) }
+
+sub change_file_prop {
+       my ($self, $fbat, $pname, $pval) = @_;
+       $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
+}
+
+sub chg_file {
+       my ($self, $fbat, $m) = @_;
+       if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
+               $self->change_file_prop($fbat,'svn:executable','*');
+       } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
+               $self->change_file_prop($fbat,'svn:executable',undef);
+       }
+       my $fh = IO::File->new_tmpfile or croak $!;
+       if ($m->{mode_b} =~ /^120/) {
+               print $fh 'link ' or croak $!;
+               $self->change_file_prop($fbat,'svn:special','*');
+       } elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
+               $self->change_file_prop($fbat,'svn:special',undef);
+       }
+       defined(my $pid = fork) or croak $!;
+       if (!$pid) {
+               open STDOUT, '>&', $fh or croak $!;
+               exec qw/git-cat-file blob/, $m->{sha1_b} or croak $!;
+       }
+       waitpid $pid, 0;
+       croak $? if $?;
+       $fh->flush == 0 or croak $!;
+       seek $fh, 0, 0 or croak $!;
+
+       my $md5 = Digest::MD5->new;
+       $md5->addfile($fh) or croak $!;
+       seek $fh, 0, 0 or croak $!;
+
+       my $exp = $md5->hexdigest;
+       my $atd = $self->apply_textdelta($fbat, undef, $self->{pool});
+       my $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool});
+       die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
+
+       close $fh or croak $!;
+}
+
+sub D {
+       my ($self, $m, $q) = @_;
+       my ($dir, $file) = split_path($m->{file_b});
+       my $pbat = $self->ensure_path($dir);
+       print "\tD\t$m->{file_b}\n" unless $q;
+       $self->delete_entry($m->{file_b}, $pbat);
+}
+
+sub close_edit {
+       my ($self) = @_;
+       my ($p,$bat) = ($self->{pool}, $self->{bat});
+       foreach (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$bat) {
+               $self->close_directory($bat->{$_}, $p);
+       }
+       $self->SUPER::close_edit($p);
+       $p->clear;
+}
+
+sub abort_edit {
+       my ($self) = @_;
+       $self->SUPER::abort_edit($self->{pool});
+       $self->{pool}->clear;
+}
+
+__END__
+
+Data structures:
+
+$svn_log hashref (as returned by svn_log_raw)
+{
+       fh => file handle of the log file,
+       state => state of the log file parser (sep/msg/rev/msg_start...)
+}
+
+$log_msg hashref as returned by next_log_entry($svn_log)
+{
+       msg => 'whitespace-formatted log entry
+',                                             # trailing newline is preserved
+       revision => '8',                        # integer
+       date => '2004-02-24T17:01:44.108345Z',  # commit date
+       author => 'committer name'
+};
+
+
+@mods = array of diff-index line hashes, each element represents one line
+       of diff-index output
+
+diff-index line ($m hash)
+{
+       mode_a => first column of diff-index output, no leading ':',
+       mode_b => second column of diff-index output,
+       sha1_b => sha1sum of the final blob,
+       chg => change type [MCRADT],
+       file_a => original file name of a file (iff chg is 'C' or 'R')
+       file_b => new/current file name of a file (any chg)
+}
+;
+
+# retval of read_url_paths{,_all}();
+$l_map = {
+       # repository root url
+       'https://svn.musicpd.org' => {
+               # repository path               # GIT_SVN_ID
+               'mpd/trunk'             =>      'trunk',
+               'mpd/tags/0.11.5'       =>      'tags/0.11.5',
+       },
+}
+
+Notes:
+       I don't trust the each() function on unless I created %hash myself
+       because the internal iterator may not have started at base.
index 38ac732ca9b677a5647a96db44f7133b47db0648..26dc45479532a78febb511bf36017d6106513e70 100755 (executable)
@@ -534,7 +534,7 @@ sub commit {
        my($author_name,$author_email,$dest);
        my(@old,@new,@parents);
 
-       if (not defined $author) {
+       if (not defined $author or $author eq "") {
                $author_name = $author_email = "unknown";
        } elsif (defined $users_file) {
                die "User $author is not listed in $users_file\n"
diff --git a/git.c b/git.c
index ca8961f0732bacc4efdb3afe9abc2e829ff69ee5..ee5a0e86a71119b5ac11c351a982e31c87649a15 100644 (file)
--- a/git.c
+++ b/git.c
@@ -11,6 +11,7 @@
 #include "git-compat-util.h"
 #include "exec_cmd.h"
 #include "cache.h"
+#include "quote.h"
 
 #include "builtin.h"
 
@@ -120,14 +121,25 @@ static int handle_alias(int *argcp, const char ***argv)
                        if (!strcmp(alias_command, new_argv[0]))
                                die("recursive alias: %s", alias_command);
 
-                       /* insert after command name */
-                       if (*argcp > 1) {
-                               new_argv = realloc(new_argv, sizeof(char*) *
-                                                  (count + *argcp));
-                               memcpy(new_argv + count, *argv + 1,
-                                      sizeof(char*) * *argcp);
+                       if (getenv("GIT_TRACE")) {
+                               int i;
+                               fprintf(stderr, "trace: alias expansion: %s =>",
+                                       alias_command);
+                               for (i = 0; i < count; ++i) {
+                                       fputc(' ', stderr);
+                                       sq_quote_print(stderr, new_argv[i]);
+                               }
+                               fputc('\n', stderr);
+                               fflush(stderr);
                        }
 
+                       new_argv = realloc(new_argv, sizeof(char*) *
+                                          (count + *argcp + 1));
+                       /* insert after command name */
+                       memcpy(new_argv + count, *argv + 1,
+                              sizeof(char*) * *argcp);
+                       new_argv[count+*argcp] = NULL;
+
                        *argv = new_argv;
                        *argcp += count - 1;
 
@@ -187,7 +199,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "mailinfo", cmd_mailinfo },
                { "stripspace", cmd_stripspace },
                { "update-index", cmd_update_index },
-               { "update-ref", cmd_update_ref }
+               { "update-ref", cmd_update_ref },
+               { "fmt-merge-msg", cmd_fmt_merge_msg },
+               { "prune", cmd_prune },
        };
        int i;
 
@@ -201,6 +215,18 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                struct cmd_struct *p = commands+i;
                if (strcmp(p->cmd, cmd))
                        continue;
+
+               if (getenv("GIT_TRACE")) {
+                       int i;
+                       fprintf(stderr, "trace: built-in: git");
+                       for (i = 0; i < argc; ++i) {
+                               fputc(' ', stderr);
+                               sq_quote_print(stderr, argv[i]);
+                       }
+                       putc('\n', stderr);
+                       fflush(stderr);
+               }
+
                exit(p->fn(argc, argv, envp));
        }
 }
@@ -250,6 +276,11 @@ int main(int argc, const char **argv, char **envp)
                cmd = *++argv;
                argc--;
 
+               if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
+                       setup_pager();
+                       continue;
+               }
+
                if (strncmp(cmd, "--", 2))
                        break;
 
index 035e76d0a306e0f7dc74f01c8177a53a9286b006..30cb624b2a38bc5b90915f0061ff3127ac0ee34d 100755 (executable)
@@ -16,54 +16,56 @@ use Encode;
 use Fcntl ':mode';
 binmode STDOUT, ':utf8';
 
-my $cgi = new CGI;
-my $version = "267";
-my $my_url = $cgi->url();
-my $my_uri = $cgi->url(-absolute => 1);
-my $rss_link = "";
+our $cgi = new CGI;
+our $version = "267";
+our $my_url = $cgi->url();
+our $my_uri = $cgi->url(-absolute => 1);
+our $rss_link = "";
 
-# location of the git-core binaries
-my $gitbin = "/usr/bin";
+# core git executable to use
+# this can just be "git" if your webserver has a sensible PATH
+our $GIT = "/usr/bin/git";
 
 # absolute fs-path which will be prepended to the project path
-#my $projectroot = "/pub/scm";
-my $projectroot = "/home/kay/public_html/pub/scm";
+#our $projectroot = "/pub/scm";
+our $projectroot = "/home/kay/public_html/pub/scm";
 
-# version of the git-core binaries
-my $git_version = qx($gitbin/git --version);
-if ($git_version =~ m/git version (.*)$/) {
-       $git_version = $1;
-} else {
-       $git_version = "unknown";
-}
+# version of the core git binary
+our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
 
 # location for temporary files needed for diffs
-my $git_temp = "/tmp/gitweb";
+our $git_temp = "/tmp/gitweb";
+if (! -d $git_temp) {
+    mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp");
+}
 
 # target of the home link on top of all pages
-my $home_link = $my_uri;
+our $home_link = $my_uri;
+
+# name of your site or organization to appear in page titles
+# replace this with something more descriptive for clearer bookmarks
+our $site_name = $ENV{'SERVER_NAME'} || "Untitled";
 
 # html text to include at home page
-my $home_text = "indextext.html";
+our $home_text = "indextext.html";
 
 # URI of default stylesheet
-my $stylesheet = "gitweb.css";
+our $stylesheet = "gitweb.css";
 
 # source of projects list
-#my $projects_list = $projectroot;
-my $projects_list = "index/index.aux";
+#our $projects_list = $projectroot;
+our $projects_list = "index/index.aux";
 
 # default blob_plain mimetype and default charset for text/plain blob
-my $default_blob_plain_mimetype = 'text/plain';
-my $default_text_plain_charset  = undef;
+our $default_blob_plain_mimetype = 'text/plain';
+our $default_text_plain_charset  = undef;
 
 # file to use for guessing MIME types before trying /etc/mime.types
 # (relative to the current git repository)
-my $mimetypes_file = undef;
-
+our $mimetypes_file = undef;
 
 # input validation and dispatch
-my $action = $cgi->param('a');
+our $action = $cgi->param('a');
 if (defined $action) {
        if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
                undef $action;
@@ -78,7 +80,7 @@ if (defined $action) {
        }
 }
 
-my $order = $cgi->param('o');
+our $order = $cgi->param('o');
 if (defined $order) {
        if ($order =~ m/[^0-9a-zA-Z_]/) {
                undef $order;
@@ -86,7 +88,7 @@ if (defined $order) {
        }
 }
 
-my $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
+our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
 if (defined $project) {
        $project =~ s|^/||; $project =~ s|/$||;
        $project = validate_input($project);
@@ -109,7 +111,7 @@ if (defined $project) {
        exit;
 }
 
-my $file_name = $cgi->param('f');
+our $file_name = $cgi->param('f');
 if (defined $file_name) {
        $file_name = validate_input($file_name);
        if (!defined($file_name)) {
@@ -117,7 +119,7 @@ if (defined $file_name) {
        }
 }
 
-my $hash = $cgi->param('h');
+our $hash = $cgi->param('h');
 if (defined $hash) {
        $hash = validate_input($hash);
        if (!defined($hash)) {
@@ -125,7 +127,7 @@ if (defined $hash) {
        }
 }
 
-my $hash_parent = $cgi->param('hp');
+our $hash_parent = $cgi->param('hp');
 if (defined $hash_parent) {
        $hash_parent = validate_input($hash_parent);
        if (!defined($hash_parent)) {
@@ -133,7 +135,7 @@ if (defined $hash_parent) {
        }
 }
 
-my $hash_base = $cgi->param('hb');
+our $hash_base = $cgi->param('hb');
 if (defined $hash_base) {
        $hash_base = validate_input($hash_base);
        if (!defined($hash_base)) {
@@ -141,7 +143,7 @@ if (defined $hash_base) {
        }
 }
 
-my $page = $cgi->param('pg');
+our $page = $cgi->param('pg');
 if (defined $page) {
        if ($page =~ m/[^0-9]$/) {
                undef $page;
@@ -149,7 +151,7 @@ if (defined $page) {
        }
 }
 
-my $searchtext = $cgi->param('s');
+our $searchtext = $cgi->param('s');
 if (defined $searchtext) {
        if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
                undef $searchtext;
@@ -278,7 +280,7 @@ sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
 
-       my $title = "git";
+       my $title = "$site_name git";
        if (defined $project) {
                $title .= " - $project";
                if (defined $action) {
@@ -291,7 +293,17 @@ sub git_header_html {
                        }
                }
        }
-       print $cgi->header(-type=>'text/html',  -charset => 'utf-8', -status=> $status, -expires => $expires);
+       my $content_type;
+       # require explicit support from the UA if we are to send the page as
+       # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
+       # we have to do this because MSIE sometimes globs '*/*', pretending to
+       # support xhtml+xml but choking when it gets what it asked for.
+       if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
+               $content_type = 'application/xhtml+xml';
+       } else {
+               $content_type = 'text/html';
+       }
+       print $cgi->header(-type=>$content_type,  -charset => 'utf-8', -status=> $status, -expires => $expires);
        print <<EOF;
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
@@ -299,7 +311,7 @@ sub git_header_html {
 <!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
 <!-- git core binaries version $git_version -->
 <head>
-<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
 <meta name="robots" content="index, nofollow"/>
 <title>$title</title>
 <link rel="stylesheet" type="text/css" href="$stylesheet"/>
@@ -376,7 +388,7 @@ sub die_error {
 sub git_get_type {
        my $hash = shift;
 
-       open my $fd, "-|", "$gitbin/git-cat-file -t $hash" or return;
+       open my $fd, "-|", "$GIT cat-file -t $hash" or return;
        my $type = <$fd>;
        close $fd or return;
        chomp $type;
@@ -388,7 +400,7 @@ sub git_read_head {
        my $oENV = $ENV{'GIT_DIR'};
        my $retval = undef;
        $ENV{'GIT_DIR'} = "$projectroot/$project";
-       if (open my $fd, "-|", "$gitbin/git-rev-parse", "--verify", "HEAD") {
+       if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") {
                my $head = <$fd>;
                close $fd;
                if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
@@ -428,7 +440,7 @@ sub git_read_tag {
        my %tag;
        my @comment;
 
-       open my $fd, "-|", "$gitbin/git-cat-file tag $tag_id" or return;
+       open my $fd, "-|", "$GIT cat-file tag $tag_id" or return;
        $tag{'id'} = $tag_id;
        while (my $line = <$fd>) {
                chomp $line;
@@ -500,7 +512,7 @@ sub git_read_commit {
                @commit_lines = @$commit_text;
        } else {
                $/ = "\0";
-               open my $fd, "-|", "$gitbin/git-rev-list --header --parents --max-count=1 $commit_id" or return;
+               open my $fd, "-|", "$GIT rev-list --header --parents --max-count=1 $commit_id" or return;
                @commit_lines = split '\n', <$fd>;
                close $fd or return;
                $/ = "\n";
@@ -598,7 +610,7 @@ sub git_diff_print {
        if (defined $from) {
                $from_tmp = "$git_temp/gitweb_" . $$ . "_from";
                open my $fd2, "> $from_tmp";
-               open my $fd, "-|", "$gitbin/git-cat-file blob $from";
+               open my $fd, "-|", "$GIT cat-file blob $from";
                my @file = <$fd>;
                print $fd2 @file;
                close $fd2;
@@ -609,7 +621,7 @@ sub git_diff_print {
        if (defined $to) {
                $to_tmp = "$git_temp/gitweb_" . $$ . "_to";
                open my $fd2, "> $to_tmp";
-               open my $fd, "-|", "$gitbin/git-cat-file blob $to";
+               open my $fd, "-|", "$GIT cat-file blob $to";
                my @file = <$fd>;
                print $fd2 @file;
                close $fd2;
@@ -783,7 +795,7 @@ sub git_read_projects {
        if (-d $projects_list) {
                # search in directory
                my $dir = $projects_list;
-               opendir my $dh, $dir or return undef;
+               opendir my ($dh), $dir or return undef;
                while (my $dir = readdir($dh)) {
                        if (-e "$projectroot/$dir/HEAD") {
                                my $pr = {
@@ -798,7 +810,7 @@ sub git_read_projects {
                # 'git%2Fgit.git Linus+Torvalds'
                # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
                # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
-               open my $fd , $projects_list or return undef;
+               open my ($fd), $projects_list or return undef;
                while (my $line = <$fd>) {
                        chomp $line;
                        my ($path, $owner) = split ' ', $line;
@@ -828,7 +840,7 @@ sub git_get_project_config {
        $key =~ s/^gitweb\.//;
        return if ($key =~ m/\W/);
 
-       my $val = qx($gitbin/git-repo-config --get gitweb.$key);
+       my $val = qx($GIT repo-config --get gitweb.$key);
        return ($val);
 }
 
@@ -1050,7 +1062,7 @@ sub git_summary {
              "<tr><td>owner</td><td>$owner</td></tr>\n" .
              "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
              "</table>\n";
-       open my $fd, "-|", "$gitbin/git-rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed.");
+       open my $fd, "-|", "$GIT rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed.");
        my (@revlist) = map { chomp; $_ } <$fd>;
        close $fd;
        print "<div>\n" .
@@ -1126,7 +1138,7 @@ sub git_summary {
                                      "</td>\n" .
                                      "<td>";
                                if (defined($comment)) {
-                                     print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment);
+                                     print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment));
                                }
                                print "</td>\n" .
                                      "<td class=\"link\">";
@@ -1238,7 +1250,7 @@ sub git_blame {
                $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
                        or die_error(undef, "Error lookup file.");
        }
-       open ($fd, "-|", "$gitbin/git-annotate", '-l', '-t', '-r', $file_name, $hash_base)
+       open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
                or die_error(undef, "Open failed.");
        git_header_html();
        print "<div class=\"page_nav\">\n" .
@@ -1433,7 +1445,7 @@ sub git_get_hash_by_path {
        my $tree = $base;
        my @parts = split '/', $path;
        while (my $part = shift @parts) {
-               open my $fd, "-|", "$gitbin/git-ls-tree $tree" or die_error(undef, "Open git-ls-tree failed.");
+               open my $fd, "-|", "$GIT ls-tree $tree" or die_error(undef, "Open git-ls-tree failed.");
                my (@entries) = map { chomp; $_ } <$fd>;
                close $fd or return undef;
                foreach my $line (@entries) {
@@ -1456,61 +1468,6 @@ sub git_get_hash_by_path {
        }
 }
 
-sub git_blob {
-       if (!defined $hash && defined $file_name) {
-               my $base = $hash_base || git_read_head($project);
-               $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file.");
-       }
-       my $have_blame = git_get_project_config_bool ('blame');
-       open my $fd, "-|", "$gitbin/git-cat-file blob $hash" or die_error(undef, "Open failed.");
-       git_header_html();
-       if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
-               print "<div class=\"page_nav\">\n" .
-                     $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
-                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
-                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
-               if (defined $file_name) {
-                       if ($have_blame) {
-                               print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") .  " | ";
-                       }
-                       print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
-                       " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head") . "<br/>\n";
-               } else {
-                       print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n";
-               }
-               print "</div>\n".
-                      "<div>" .
-                     $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
-                     "</div>\n";
-       } else {
-               print "<div class=\"page_nav\">\n" .
-                     "<br/><br/></div>\n" .
-                     "<div class=\"title\">$hash</div>\n";
-       }
-       if (defined $file_name) {
-               print "<div class=\"page_path\"><b>" . esc_html($file_name) . "</b></div>\n";
-       }
-       print "<div class=\"page_body\">\n";
-       my $nr;
-       while (my $line = <$fd>) {
-               chomp $line;
-               $nr++;
-               while ((my $pos = index($line, "\t")) != -1) {
-                       if (my $count = (8 - ($pos % 8))) {
-                               my $spaces = ' ' x $count;
-                               $line =~ s/\t/$spaces/;
-                       }
-               }
-               printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line);
-       }
-       close $fd or print "Reading blob failed.\n";
-       print "</div>";
-       git_footer_html();
-}
-
 sub mimetype_guess_file {
        my $filename = shift;
        my $mimemap = shift;
@@ -1549,14 +1506,14 @@ sub git_blob_plain_mimetype {
        my $fd = shift;
        my $filename = shift;
 
-       # just in case
-       return $default_blob_plain_mimetype unless $fd;
-
        if ($filename) {
                my $mime = mimetype_guess($filename);
                $mime and return $mime;
        }
 
+       # just in case
+       return $default_blob_plain_mimetype unless $fd;
+
        if (-T $fd) {
                return 'text/plain' .
                       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
@@ -1574,8 +1531,10 @@ sub git_blob_plain_mimetype {
 }
 
 sub git_blob_plain {
-       open my $fd, "-|", "$gitbin/git-cat-file blob $hash" or return;
-       my $type = git_blob_plain_mimetype($fd, $file_name);
+       my $type = shift;
+       open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash");
+
+       $type ||= git_blob_plain_mimetype($fd, $file_name);
 
        # save as filename, even when no $file_name is given
        my $save_as = "$hash";
@@ -1594,6 +1553,66 @@ sub git_blob_plain {
        close $fd;
 }
 
+sub git_blob {
+       if (!defined $hash && defined $file_name) {
+               my $base = $hash_base || git_read_head($project);
+               $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file.");
+       }
+       my $have_blame = git_get_project_config_bool ('blame');
+       open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed.");
+       my $mimetype = git_blob_plain_mimetype($fd, $file_name);
+       if ($mimetype !~ m/^text\//) {
+               close $fd;
+               return git_blob_plain($mimetype);
+       }
+       git_header_html();
+       if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
+               print "<div class=\"page_nav\">\n" .
+                     $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
+                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
+                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
+                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
+                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
+                     " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
+               if (defined $file_name) {
+                       if ($have_blame) {
+                               print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") .  " | ";
+                       }
+                       print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
+                       " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head") . "<br/>\n";
+               } else {
+                       print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n";
+               }
+               print "</div>\n".
+                      "<div>" .
+                     $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
+                     "</div>\n";
+       } else {
+               print "<div class=\"page_nav\">\n" .
+                     "<br/><br/></div>\n" .
+                     "<div class=\"title\">$hash</div>\n";
+       }
+       if (defined $file_name) {
+               print "<div class=\"page_path\"><b>" . esc_html($file_name) . "</b></div>\n";
+       }
+       print "<div class=\"page_body\">\n";
+       my $nr;
+       while (my $line = <$fd>) {
+               chomp $line;
+               $nr++;
+               while ((my $pos = index($line, "\t")) != -1) {
+                       if (my $count = (8 - ($pos % 8))) {
+                               my $spaces = ' ' x $count;
+                               $line =~ s/\t/$spaces/;
+                       }
+               }
+               printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line);
+       }
+       close $fd or print "Reading blob failed.\n";
+       print "</div>";
+       git_footer_html();
+}
+
 sub git_tree {
        if (!defined $hash) {
                $hash = git_read_head($project);
@@ -1606,7 +1625,7 @@ sub git_tree {
                }
        }
        $/ = "\0";
-       open my $fd, "-|", "$gitbin/git-ls-tree -z $hash" or die_error(undef, "Open git-ls-tree failed.");
+       open my $fd, "-|", "$GIT ls-tree -z $hash" or die_error(undef, "Open git-ls-tree failed.");
        chomp (my (@entries) = <$fd>);
        close $fd or die_error(undef, "Reading tree failed.");
        $/ = "\n";
@@ -1669,6 +1688,7 @@ sub git_tree {
                              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") .
 #                            " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") .
                              " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$hash_base;f=$base$t_name")}, "history") .
+                             " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") .
                              "</td>\n";
                } elsif ($t_type eq "tree") {
                        print "<td class=\"list\">" .
@@ -1676,6 +1696,7 @@ sub git_tree {
                              "</td>\n" .
                              "<td class=\"link\">" .
                              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") .
+                             " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$hash_base;f=$base$t_name")}, "history") .
                              "</td>\n";
                }
                print "</tr>\n";
@@ -1687,7 +1708,7 @@ sub git_tree {
 
 sub git_rss {
        # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
-       open my $fd, "-|", "$gitbin/git-rev-list --max-count=150 " . git_read_head($project) or die_error(undef, "Open failed.");
+       open my $fd, "-|", "$GIT rev-list --max-count=150 " . git_read_head($project) or die_error(undef, "Open failed.");
        my (@revlist) = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading rev-list failed.");
        print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
@@ -1707,7 +1728,7 @@ sub git_rss {
                        last;
                }
                my %cd = date_str($co{'committer_epoch'});
-               open $fd, "-|", "$gitbin/git-diff-tree -r $co{'parent'} $co{'id'}" or next;
+               open $fd, "-|", "$GIT diff-tree -r $co{'parent'} $co{'id'}" or next;
                my @difftree = map { chomp; $_ } <$fd>;
                close $fd or next;
                print "<item>\n" .
@@ -1749,7 +1770,7 @@ sub git_opml {
        print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
              "<opml version=\"1.0\">\n".
              "<head>".
-             "  <title>Git OPML Export</title>\n".
+             "  <title>$site_name Git OPML Export</title>\n".
              "</head>\n".
              "<body>\n".
              "<outline text=\"git RSS feeds\">\n";
@@ -1795,7 +1816,7 @@ sub git_log {
              " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 
        my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-       open my $fd, "-|", "$gitbin/git-rev-list $limit $hash" or die_error(undef, "Open failed.");
+       open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed.");
        my (@revlist) = map { chomp; $_ } <$fd>;
        close $fd;
 
@@ -1886,7 +1907,7 @@ sub git_commit {
                $root = " --root";
                $parent = "";
        }
-       open my $fd, "-|", "$gitbin/git-diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed.");
+       open my $fd, "-|", "$GIT diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed.");
        @difftree = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading diff-tree failed.");
 
@@ -2128,7 +2149,7 @@ sub git_commitdiff {
        if (!defined $hash_parent) {
                $hash_parent = $co{'parent'};
        }
-       open my $fd, "-|", "$gitbin/git-diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
+       open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
        my (@difftree) = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading diff-tree failed.");
 
@@ -2218,14 +2239,14 @@ sub git_commitdiff {
 
 sub git_commitdiff_plain {
        mkdir($git_temp, 0700);
-       open my $fd, "-|", "$gitbin/git-diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
+       open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
        my (@difftree) = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading diff-tree failed.");
 
        # try to figure out the next tag after this commit
        my $tagname;
        my $refs = read_info_ref("tags");
-       open $fd, "-|", "$gitbin/git-rev-list HEAD";
+       open $fd, "-|", "$GIT rev-list HEAD";
        chomp (my (@commits) = <$fd>);
        close $fd;
        foreach my $commit (@commits) {
@@ -2295,16 +2316,13 @@ sub git_history {
              "</div>\n";
        print "<div class=\"page_path\"><b>/" . esc_html($file_name) . "</b><br/></div>\n";
 
-       open my $fd, "-|", "$gitbin/git-rev-list $hash | $gitbin/git-diff-tree -r --stdin -- \'$file_name\'";
-       my $commit;
+       open my $fd, "-|",
+               "$GIT rev-list --full-history $hash -- \'$file_name\'";
        print "<table cellspacing=\"0\">\n";
        my $alternate = 0;
        while (my $line = <$fd>) {
                if ($line =~ m/^([0-9a-fA-F]{40})/){
-                       $commit = $1;
-                       next;
-               }
-               if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/ && (defined $commit)) {
+                       my $commit = $1;
                        my %co = git_read_commit($commit);
                        if (!%co) {
                                next;
@@ -2336,7 +2354,6 @@ sub git_history {
                        }
                        print "</td>\n" .
                              "</tr>\n";
-                       undef $commit;
                }
        }
        print "</table>\n";
@@ -2387,7 +2404,7 @@ sub git_search {
        my $alternate = 0;
        if ($commit_search) {
                $/ = "\0";
-               open my $fd, "-|", "$gitbin/git-rev-list --header --parents $hash" or next;
+               open my $fd, "-|", "$GIT rev-list --header --parents $hash" or next;
                while (my $commit_text = <$fd>) {
                        if (!grep m/$searchtext/i, $commit_text) {
                                next;
@@ -2437,7 +2454,7 @@ sub git_search {
 
        if ($pickaxe_search) {
                $/ = "\n";
-               open my $fd, "-|", "$gitbin/git-rev-list $hash | $gitbin/git-diff-tree -r --stdin -S\'$searchtext\'";
+               open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'";
                undef %co;
                my @files;
                while (my $line = <$fd>) {
@@ -2508,7 +2525,7 @@ sub git_shortlog {
              " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 
        my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-       open my $fd, "-|", "$gitbin/git-rev-list $limit $hash" or die_error(undef, "Open failed.");
+       open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed.");
        my (@revlist) = map { chomp; $_ } <$fd>;
        close $fd;
 
index 98410f5b6c971efcc59ac69514936b02fbe68d17..fffdb13d09a40397745861bb6ec5dcc79c798b0d 100644 (file)
@@ -60,6 +60,7 @@ div.page_footer_text {
 
 div.page_body {
        padding: 8px;
+       font-family: monospace;
 }
 
 div.title, a.title {
@@ -79,6 +80,7 @@ div.title_text {
        padding: 6px 0px;
        border: solid #d9d8d1;
        border-width: 0px 0px 1px;
+       font-family: monospace;
 }
 
 div.log_body {
@@ -142,8 +144,13 @@ table {
        padding: 8px 4px;
 }
 
-table.project_list, table.diff_tree {
+table.project_list {
+       border-spacing: 0;
+}
+
+table.diff_tree {
        border-spacing: 0;
+       font-family: monospace;
 }
 
 table.blame {
index 44eba5fd0df292255dce056ec5aa700186096b39..12493fbed2822ce41b5b4c23fb85d6f4a47c104a 100644 (file)
@@ -490,7 +490,7 @@ static int setup_index(struct alt_base *repo, unsigned char *sha1)
 {
        struct packed_git *new_pack;
        if (has_pack_file(sha1))
-               return 0; // don't list this as something we can get
+               return 0; /* don't list this as something we can get */
 
        if (fetch_index(repo, sha1))
                return -1;
@@ -570,7 +570,7 @@ static void process_alternates_response(void *callback_data)
                                                 base[serverlen - 1] != '/');
                                        i += 3;
                                }
-                               // If the server got removed, give up.
+                               /* If the server got removed, give up. */
                                okay = strchr(base, ':') - base + 3 <
                                        serverlen;
                        } else if (alt_req->http_specific) {
@@ -581,7 +581,7 @@ static void process_alternates_response(void *callback_data)
                                        okay = 1;
                                }
                        }
-                       // skip 'objects' at end
+                       /* skip 'objects' at end */
                        if (okay) {
                                target = xmalloc(serverlen + posn - i - 6);
                                strlcpy(target, base, serverlen);
index e281f70e544d1e59c47f61ce14a728ba5ef44a44..47686195cdf41ad58fe66a606033d3156de8fd82 100644 (file)
@@ -492,7 +492,7 @@ static void start_put(struct transfer_request *request)
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        size = deflateBound(&stream, len + hdrlen);
        request->buffer.buffer = xmalloc(size);
 
@@ -1784,16 +1784,16 @@ static int get_delta(struct rev_info *revs, struct remote_lock *lock)
 
                if (obj->flags & (UNINTERESTING | SEEN))
                        continue;
-               if (obj->type == TYPE_TAG) {
+               if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
                        p = add_one_object(obj, p);
                        continue;
                }
-               if (obj->type == TYPE_TREE) {
+               if (obj->type == OBJ_TREE) {
                        p = process_tree((struct tree *)obj, p, NULL, name);
                        continue;
                }
-               if (obj->type == TYPE_BLOB) {
+               if (obj->type == OBJ_BLOB) {
                        p = process_blob((struct blob *)obj, p, NULL, name);
                        continue;
                }
@@ -1960,12 +1960,12 @@ static int ref_newer(const unsigned char *new_sha1,
         * old.  Otherwise we require --force.
         */
        o = deref_tag(parse_object(old_sha1), NULL, 0);
-       if (!o || o->type != TYPE_COMMIT)
+       if (!o || o->type != OBJ_COMMIT)
                return 0;
        old = (struct commit *) o;
 
        o = deref_tag(parse_object(new_sha1), NULL, 0);
-       if (!o || o->type != TYPE_COMMIT)
+       if (!o || o->type != OBJ_COMMIT)
                return 0;
        new = (struct commit *) o;
 
@@ -2044,7 +2044,7 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls)
        fwrite_buffer(ref_info, 1, len, buf);
        free(ref_info);
 
-       if (o->type == TYPE_TAG) {
+       if (o->type == OBJ_TAG) {
                o = deref_tag(o, ls->dentry_name, 0);
                if (o) {
                        len = strlen(ls->dentry_name) + 45;
index ebb49f29701b2435d66eb92fc2a5c382299e8ee8..b67b8dd17af9643e00152d9bd768dca89e08b4ce 100644 (file)
@@ -43,9 +43,10 @@ static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
        return at;
 }
 
-void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
+void show_log(struct rev_info *opt, const char *sep)
 {
        static char this_header[16384];
+       struct log_info *log = opt->loginfo;
        struct commit *commit = log->commit, *parent = log->parent;
        int abbrev = opt->diffopt.abbrev;
        int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
@@ -96,6 +97,11 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
                        subject = "Subject: ";
 
                printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
+               if (opt->message_id)
+                       printf("Message-Id: <%s>\n", opt->message_id);
+               if (opt->ref_message_id)
+                       printf("In-Reply-To: <%s>\nReferences: <%s>\n",
+                              opt->ref_message_id, opt->ref_message_id);
                if (opt->mime_boundary) {
                        static char subject_buffer[1024];
                        static char buffer[1024];
@@ -128,7 +134,8 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
                        opt->diffopt.stat_sep = buffer;
                }
        } else {
-               printf("%s%s",
+               printf("%s%s%s",
+                      diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
                       opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
                       diff_unique_abbrev(commit->object.sha1, abbrev_commit));
                if (opt->parents)
@@ -137,6 +144,8 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
                        printf(" (from %s)",
                               diff_unique_abbrev(parent->object.sha1,
                                                  abbrev_commit));
+               printf("%s",
+                      diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
                putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
        }
 
@@ -163,8 +172,22 @@ int log_tree_diff_flush(struct rev_info *opt)
                return 0;
        }
 
-       if (opt->loginfo && !opt->no_commit_id)
-               show_log(opt, opt->loginfo, opt->diffopt.with_stat ? "---\n" : "\n");
+       if (opt->loginfo && !opt->no_commit_id) {
+               /* When showing a verbose header (i.e. log message),
+                * and not in --pretty=oneline format, we would want
+                * an extra newline between the end of log and the
+                * output for readability.
+                */
+               show_log(opt, opt->diffopt.msg_sep);
+               if (opt->verbose_header &&
+                   opt->commit_format != CMIT_FMT_ONELINE) {
+                       int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
+                       if ((pch & opt->diffopt.output_format) == pch)
+                               printf("---%c", opt->diffopt.line_termination);
+                       else
+                               putchar(opt->diffopt.line_termination);
+               }
+       }
        diff_flush(&opt->diffopt);
        return 1;
 }
@@ -261,7 +284,7 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
        shown = log_tree_diff(opt, commit, &log);
        if (!shown && opt->loginfo && opt->always_show_header) {
                log.parent = NULL;
-               show_log(opt, opt->loginfo, "");
+               show_log(opt, "");
                shown = 1;
        }
        opt->loginfo = NULL;
index a26e4841ff9f568f8536f286e3fc40f09b9dff3b..e82b56a20d3cfad318a4af6ea78fbe098653211d 100644 (file)
@@ -11,6 +11,6 @@ void init_log_tree_opt(struct rev_info *);
 int log_tree_diff_flush(struct rev_info *);
 int log_tree_commit(struct rev_info *, struct commit *);
 int log_tree_opt_parse(struct rev_info *, const char **, int);
-void show_log(struct rev_info *opt, struct log_info *log, const char *sep);
+void show_log(struct rev_info *opt, const char *sep);
 
 #endif
index 4856ca01c33896843c366f3fb5edadd2ea3ced37..59f723f4047f831165b8d52ac13def398e663ac3 100644 (file)
 #include "cache.h"
 #include "commit.h"
 
-#define PARENT1 1
-#define PARENT2 2
-#define UNINTERESTING 4
-
-static struct commit *interesting(struct commit_list *list)
-{
-       while (list) {
-               struct commit *commit = list->item;
-               list = list->next;
-               if (commit->object.flags & UNINTERESTING)
-                       continue;
-               return commit;
-       }
-       return NULL;
-}
-
-/*
- * A pathological example of how this thing works.
- *
- * Suppose we had this commit graph, where chronologically
- * the timestamp on the commit are A <= B <= C <= D <= E <= F
- * and we are trying to figure out the merge base for E and F
- * commits.
- *
- *                  F
- *                 / \
- *            E   A   D
- *             \ /   /  
- *              B   /
- *               \ /
- *                C
- *
- * First we push E and F to list to be processed.  E gets bit 1
- * and F gets bit 2.  The list becomes:
- *
- *     list=F(2) E(1), result=empty
- *
- * Then we pop F, the newest commit, from the list.  Its flag is 2.
- * We scan its parents, mark them reachable from the side that F is
- * reachable from, and push them to the list:
- *
- *     list=E(1) D(2) A(2), result=empty
- *
- * Next pop E and do the same.
- *
- *     list=D(2) B(1) A(2), result=empty
- *
- * Next pop D and do the same.
- *
- *     list=C(2) B(1) A(2), result=empty
- *
- * Next pop C and do the same.
- *
- *     list=B(1) A(2), result=empty
- *
- * Now it is B's turn.  We mark its parent, C, reachable from B's side,
- * and push it to the list:
- *
- *     list=C(3) A(2), result=empty
- *
- * Now pop C and notice it has flags==3.  It is placed on the result list,
- * and the list now contains:
- *
- *     list=A(2), result=C(3)
- *
- * We pop A and do the same.
- * 
- *     list=B(3), result=C(3)
- *
- * Next, we pop B and something very interesting happens.  It has flags==3
- * so it is also placed on the result list, and its parents are marked
- * uninteresting, retroactively, and placed back on the list:
- *
- *    list=C(7), result=C(7) B(3)
- * 
- * Now, list does not have any interesting commit.  So we find the newest
- * commit from the result list that is not marked uninteresting.  Which is
- * commit B.
- *
- *
- * Another pathological example how this thing used to fail to mark an
- * ancestor of a merge base as UNINTERESTING before we introduced the
- * postprocessing phase (mark_reachable_commits).
- *
- *               2
- *               H
- *         1    / \
- *         G   A   \
- *         |\ /     \ 
- *         | B       \
- *         |  \       \
- *          \  C       F
- *           \  \     / 
- *            \  D   /   
- *             \ |  /
- *              \| /
- *               E
- *
- *      list                   A B C D E F G H
- *      G1 H2                  - - - - - - 1 2
- *      H2 E1 B1               - 1 - - 1 - 1 2
- *      F2 E1 B1 A2            2 1 - - 1 2 1 2
- *      E3 B1 A2               2 1 - - 3 2 1 2
- *      B1 A2                  2 1 - - 3 2 1 2
- *      C1 A2                  2 1 1 - 3 2 1 2
- *      D1 A2                  2 1 1 1 3 2 1 2
- *      A2                     2 1 1 1 3 2 1 2
- *      B3                     2 3 1 1 3 2 1 2
- *      C7                     2 3 7 1 3 2 1 2
- *
- * At this point, unfortunately, everybody in the list is
- * uninteresting, so we fail to complete the following two
- * steps to fully marking uninteresting commits.
- *
- *      D7                     2 3 7 7 3 2 1 2
- *      E7                     2 3 7 7 7 2 1 2
- *
- * and we ended up showing E as an interesting merge base.
- * The postprocessing phase re-injects C and continues traversal
- * to contaminate D and E.
- */
-
 static int show_all = 0;
 
-static void mark_reachable_commits(struct commit_list *result,
-                                  struct commit_list *list)
-{
-       struct commit_list *tmp;
-
-       /*
-        * Postprocess to fully contaminate the well.
-        */
-       for (tmp = result; tmp; tmp = tmp->next) {
-               struct commit *c = tmp->item;
-               /* Reinject uninteresting ones to list,
-                * so we can scan their parents.
-                */
-               if (c->object.flags & UNINTERESTING)
-                       commit_list_insert(c, &list);
-       }
-       while (list) {
-               struct commit *c = list->item;
-               struct commit_list *parents;
-
-               tmp = list;
-               list = list->next;
-               free(tmp);
-
-               /* Anything taken out of the list is uninteresting, so
-                * mark all its parents uninteresting.  We do not
-                * parse new ones (we already parsed all the relevant
-                * ones).
-                */
-               parents = c->parents;
-               while (parents) {
-                       struct commit *p = parents->item;
-                       parents = parents->next;
-                       if (!(p->object.flags & UNINTERESTING)) {
-                               p->object.flags |= UNINTERESTING;
-                               commit_list_insert(p, &list);
-                       }
-               }
-       }
-}
-
 static int merge_base(struct commit *rev1, struct commit *rev2)
 {
-       struct commit_list *list = NULL;
-       struct commit_list *result = NULL;
-       struct commit_list *tmp = NULL;
-
-       if (rev1 == rev2) {
-               printf("%s\n", sha1_to_hex(rev1->object.sha1));
-               return 0;
-       }
-
-       parse_commit(rev1);
-       parse_commit(rev2);
-
-       rev1->object.flags |= 1;
-       rev2->object.flags |= 2;
-       insert_by_date(rev1, &list);
-       insert_by_date(rev2, &list);
-
-       while (interesting(list)) {
-               struct commit *commit = list->item;
-               struct commit_list *parents;
-               int flags = commit->object.flags & 7;
-
-               tmp = list;
-               list = list->next;
-               free(tmp);
-               if (flags == 3) {
-                       insert_by_date(commit, &result);
-
-                       /* Mark parents of a found merge uninteresting */
-                       flags |= UNINTERESTING;
-               }
-               parents = commit->parents;
-               while (parents) {
-                       struct commit *p = parents->item;
-                       parents = parents->next;
-                       if ((p->object.flags & flags) == flags)
-                               continue;
-                       parse_commit(p);
-                       p->object.flags |= flags;
-                       insert_by_date(p, &list);
-               }
-       }
+       struct commit_list *result = get_merge_bases(rev1, rev2, 0);
 
        if (!result)
                return 1;
 
-       if (result->next && list)
-               mark_reachable_commits(result, list);
-
        while (result) {
-               struct commit *commit = result->item;
-               result = result->next;
-               if (commit->object.flags & UNINTERESTING)
-                       continue;
-               printf("%s\n", sha1_to_hex(commit->object.sha1));
+               printf("%s\n", sha1_to_hex(result->item->object.sha1));
                if (!show_all)
                        return 0;
-               commit->object.flags |= UNINTERESTING;
+               result = result->next;
        }
+
        return 0;
 }
 
diff --git a/merge-file.c b/merge-file.c
new file mode 100644 (file)
index 0000000..f32c653
--- /dev/null
@@ -0,0 +1,166 @@
+#include "cache.h"
+#include "run-command.h"
+#include "xdiff-interface.h"
+#include "blob.h"
+
+static void rm_temp_file(const char *filename)
+{
+       unlink(filename);
+       free((void *)filename);
+}
+
+static const char *write_temp_file(mmfile_t *f)
+{
+       int fd;
+       const char *tmp = getenv("TMPDIR");
+       char *filename;
+
+       if (!tmp)
+               tmp = "/tmp";
+       filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX");
+       fd = mkstemp(filename);
+       if (fd < 0)
+               return NULL;
+       filename = strdup(filename);
+       if (f->size != xwrite(fd, f->ptr, f->size)) {
+               rm_temp_file(filename);
+               return NULL;
+       }
+       close(fd);
+       return filename;
+}
+
+static void *read_temp_file(const char *filename, unsigned long *size)
+{
+       struct stat st;
+       char *buf = NULL;
+       int fd = open(filename, O_RDONLY);
+       if (fd < 0)
+               return NULL;
+       if (!fstat(fd, &st)) {
+               *size = st.st_size;
+               buf = xmalloc(st.st_size);
+               if (st.st_size != xread(fd, buf, st.st_size)) {
+                       free(buf);
+                       buf = NULL;
+               }
+       }
+       close(fd);
+       return buf;
+}
+
+static int fill_mmfile_blob(mmfile_t *f, struct blob *obj)
+{
+       void *buf;
+       unsigned long size;
+       char type[20];
+
+       buf = read_sha1_file(obj->object.sha1, type, &size);
+       if (!buf)
+               return -1;
+       if (strcmp(type, blob_type))
+               return -1;
+       f->ptr = buf;
+       f->size = size;
+       return 0;
+}
+
+static void free_mmfile(mmfile_t *f)
+{
+       free(f->ptr);
+}
+
+static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
+{
+       void *res;
+       const char *t1, *t2, *t3;
+
+       t1 = write_temp_file(base);
+       t2 = write_temp_file(our);
+       t3 = write_temp_file(their);
+       res = NULL;
+       if (t1 && t2 && t3) {
+               int code = run_command("merge", t2, t1, t3, NULL);
+               if (!code || code == -1)
+                       res = read_temp_file(t2, size);
+       }
+       rm_temp_file(t1);
+       rm_temp_file(t2);
+       rm_temp_file(t3);
+       return res;
+}
+
+static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       mmfile_t *dst = priv_;
+
+       for (i = 0; i < nbuf; i++) {
+               memcpy(dst->ptr + dst->size, mb[i].ptr, mb[i].size);
+               dst->size += mb[i].size;
+       }
+       return 0;
+}
+
+static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
+{
+       unsigned long size = f1->size < f2->size ? f1->size : f2->size;
+       void *ptr = xmalloc(size);
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 3;
+       xecfg.flags = XDL_EMIT_COMMON;
+       ecb.outf = common_outf;
+
+       res->ptr = ptr;
+       res->size = 0;
+
+       ecb.priv = res;
+       return xdl_diff(f1, f2, &xpp, &xecfg, &ecb);
+}
+
+void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
+{
+       void *res = NULL;
+       mmfile_t f1, f2, common;
+
+       /*
+        * Removed in either branch?
+        *
+        * NOTE! This depends on the caller having done the
+        * proper warning about removing a file that got
+        * modified in the other branch!
+        */
+       if (!our || !their) {
+               char type[20];
+               if (base)
+                       return NULL;
+               if (!our)
+                       our = their;
+               return read_sha1_file(our->object.sha1, type, size);
+       }
+
+       if (fill_mmfile_blob(&f1, our) < 0)
+               goto out_no_mmfile;
+       if (fill_mmfile_blob(&f2, their) < 0)
+               goto out_free_f1;
+
+       if (base) {
+               if (fill_mmfile_blob(&common, base) < 0)
+                       goto out_free_f2_f1;
+       } else {
+               if (generate_common_file(&common, &f1, &f2) < 0)
+                       goto out_free_f2_f1;
+       }
+       res = three_way_filemerge(&common, &f1, &f2, size);
+       free_mmfile(&common);
+out_free_f2_f1:
+       free_mmfile(&f2);
+out_free_f1:
+       free_mmfile(&f1);
+out_no_mmfile:
+       return res;
+}
index 9dcaab7a85fdb63b2140923a95b216917bfc0993..7cf00be6d51b17ca60979a9c856e67405906d9a7 100644 (file)
 #include "cache.h"
 #include "tree-walk.h"
+#include "xdiff-interface.h"
+#include "blob.h"
 
 static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
 static int resolve_directories = 1;
 
+struct merge_list {
+       struct merge_list *next;
+       struct merge_list *link;        /* other stages for this object */
+
+       unsigned int stage : 2,
+                    flags : 30;
+       unsigned int mode;
+       const char *path;
+       struct blob *blob;
+};
+
+static struct merge_list *merge_result, **merge_result_end = &merge_result;
+
+static void add_merge_entry(struct merge_list *entry)
+{
+       *merge_result_end = entry;
+       merge_result_end = &entry->next;
+}
+
 static void merge_trees(struct tree_desc t[3], const char *base);
 
+static const char *explanation(struct merge_list *entry)
+{
+       switch (entry->stage) {
+       case 0:
+               return "merged";
+       case 3:
+               return "added in remote";
+       case 2:
+               if (entry->link)
+                       return "added in both";
+               return "added in local";
+       }
+
+       /* Existed in base */
+       entry = entry->link;
+       if (!entry)
+               return "removed in both";
+
+       if (entry->link)
+               return "changed in both";
+
+       if (entry->stage == 3)
+               return "removed in local";
+       return "removed in remote";
+}
+
+extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *);
+
+static void *result(struct merge_list *entry, unsigned long *size)
+{
+       char type[20];
+       struct blob *base, *our, *their;
+
+       if (!entry->stage)
+               return read_sha1_file(entry->blob->object.sha1, type, size);
+       base = NULL;
+       if (entry->stage == 1) {
+               base = entry->blob;
+               entry = entry->link;
+       }
+       our = NULL;
+       if (entry && entry->stage == 2) {
+               our = entry->blob;
+               entry = entry->link;
+       }
+       their = NULL;
+       if (entry)
+               their = entry->blob;
+       return merge_file(base, our, their, size);
+}
+
+static void *origin(struct merge_list *entry, unsigned long *size)
+{
+       char type[20];
+       while (entry) {
+               if (entry->stage == 2)
+                       return read_sha1_file(entry->blob->object.sha1, type, size);
+               entry = entry->link;
+       }
+       return NULL;
+}
+
+static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       for (i = 0; i < nbuf; i++)
+               printf("%.*s", (int) mb[i].size, mb[i].ptr);
+       return 0;
+}
+
+static void show_diff(struct merge_list *entry)
+{
+       unsigned long size;
+       mmfile_t src, dst;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 3;
+       xecfg.flags = 0;
+       ecb.outf = show_outf;
+       ecb.priv = NULL;
+
+       src.ptr = origin(entry, &size);
+       if (!src.ptr)
+               size = 0;
+       src.size = size;
+       dst.ptr = result(entry, &size);
+       if (!dst.ptr)
+               size = 0;
+       dst.size = size;
+       xdl_diff(&src, &dst, &xpp, &xecfg, &ecb);
+       free(src.ptr);
+       free(dst.ptr);
+}
+
+static void show_result_list(struct merge_list *entry)
+{
+       printf("%s\n", explanation(entry));
+       do {
+               struct merge_list *link = entry->link;
+               static const char *desc[4] = { "result", "base", "our", "their" };
+               printf("  %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
+               entry = link;
+       } while (entry);
+}
+
+static void show_result(void)
+{
+       struct merge_list *walk;
+
+       walk = merge_result;
+       while (walk) {
+               show_result_list(walk);
+               show_diff(walk);
+               walk = walk->next;
+       }
+}
+
 /* An empty entry never compares same, not even to another empty entry */
 static int same_entry(struct name_entry *a, struct name_entry *b)
 {
@@ -15,24 +156,34 @@ static int same_entry(struct name_entry *a, struct name_entry *b)
                a->mode == b->mode;
 }
 
-static const char *sha1_to_hex_zero(const unsigned char *sha1)
+static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
 {
-       if (sha1)
-               return sha1_to_hex(sha1);
-       return "0000000000000000000000000000000000000000";
+       struct merge_list *res = xmalloc(sizeof(*res));
+
+       memset(res, 0, sizeof(*res));
+       res->stage = stage;
+       res->path = path;
+       res->mode = mode;
+       res->blob = lookup_blob(sha1);
+       return res;
 }
 
 static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
 {
+       struct merge_list *orig, *final;
+       const char *path;
+
        /* If it's already branch1, don't bother showing it */
        if (!branch1)
                return;
 
-       printf("0 %06o->%06o %s->%s %s%s\n",
-               branch1->mode, result->mode,
-               sha1_to_hex_zero(branch1->sha1),
-               sha1_to_hex_zero(result->sha1),
-               base, result->path);
+       path = strdup(mkpath("%s%s", base, result->path));
+       orig = create_entry(2, branch1->mode, branch1->sha1, path);
+       final = create_entry(0, result->mode, result->sha1, path);
+
+       final->link = orig;
+
+       add_merge_entry(final);
 }
 
 static int unresolved_directory(const char *base, struct name_entry n[3])
@@ -71,16 +222,40 @@ static int unresolved_directory(const char *base, struct name_entry n[3])
        return 1;
 }
 
+
+static struct merge_list *link_entry(unsigned stage, const char *base, struct name_entry *n, struct merge_list *entry)
+{
+       const char *path;
+       struct merge_list *link;
+
+       if (!n->mode)
+               return entry;
+       if (entry)
+               path = entry->path;
+       else
+               path = strdup(mkpath("%s%s", base, n->path));
+       link = create_entry(stage, n->mode, n->sha1, path);
+       link->link = entry;
+       return link;
+}
+
 static void unresolved(const char *base, struct name_entry n[3])
 {
+       struct merge_list *entry = NULL;
+
        if (unresolved_directory(base, n))
                return;
-       if (n[0].sha1)
-               printf("1 %06o %s %s%s\n", n[0].mode, sha1_to_hex(n[0].sha1), base, n[0].path);
-       if (n[1].sha1)
-               printf("2 %06o %s %s%s\n", n[1].mode, sha1_to_hex(n[1].sha1), base, n[1].path);
-       if (n[2].sha1)
-               printf("3 %06o %s %s%s\n", n[2].mode, sha1_to_hex(n[2].sha1), base, n[2].path);
+
+       /*
+        * Do them in reverse order so that the resulting link
+        * list has the stages in order - link_entry adds new
+        * links at the front.
+        */
+       entry = link_entry(3, base, n + 2, entry);
+       entry = link_entry(2, base, n + 1, entry);
+       entry = link_entry(1, base, n + 0, entry);
+
+       add_merge_entry(entry);
 }
 
 /*
@@ -172,5 +347,7 @@ int main(int argc, char **argv)
        free(buf1);
        free(buf2);
        free(buf3);
+
+       show_result();
        return 0;
 }
diff --git a/mktag.c b/mktag.c
index f0fe5285b24d5a19c7d41b437a18a3851ae90802..27f4c4f0413c5a7bb16106d1a0c1d5381c1159d2 100644 (file)
--- a/mktag.c
+++ b/mktag.c
@@ -17,7 +17,7 @@
  * in that size, you're doing something wrong.
  */
 
-// Some random size
+/* Some random size */
 #define MAXSIZE (8192)
 
 /*
@@ -123,7 +123,8 @@ int main(int argc, char **argv)
                die("could not read from stdin");
        }
 
-       // Verify it for some basic sanity: it needs to start with "object <sha1>\ntype\ntagger "
+       /* Verify it for some basic sanity: it needs to start with
+          "object <sha1>\ntype\ntagger " */
        if (verify_tag(buffer, size) < 0)
                die("invalid tag signature file");
 
index 6a23f2d8a2d87c46ac9e1ec7bcb36e540967f93b..f92f14e32f647527e8bf49fced6c632be7298985 100644 (file)
@@ -5,7 +5,7 @@
 #include "refs.h"
 
 static const char name_rev_usage[] =
-       "git-name-rev [--tags] ( --all | --stdin | commitish [commitish...] )\n";
+       "git-name-rev [--tags] ( --all | --stdin | committish [committish...] )\n";
 
 typedef struct rev_name {
        const char *tip_name;
@@ -84,14 +84,14 @@ static int name_ref(const char *path, const unsigned char *sha1)
        if (tags_only && strncmp(path, "refs/tags/", 10))
                return 0;
 
-       while (o && o->type == TYPE_TAG) {
+       while (o && o->type == OBJ_TAG) {
                struct tag *t = (struct tag *) o;
                if (!t->tagged)
                        break; /* broken repository */
                o = parse_object(t->tagged->sha1);
                deref = 1;
        }
-       if (o && o->type == TYPE_COMMIT) {
+       if (o && o->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *)o;
 
                if (!strncmp(path, "refs/heads/", 11))
@@ -111,7 +111,7 @@ static const char* get_rev_name(struct object *o)
        struct rev_name *n;
        struct commit *c;
 
-       if (o->type != TYPE_COMMIT)
+       if (o->type != OBJ_COMMIT)
                return "undefined";
        c = (struct commit *) o;
        n = c->util;
@@ -172,7 +172,7 @@ int main(int argc, char **argv)
                }
 
                o = deref_tag(parse_object(sha1), *argv, 0);
-               if (!o || o->type != TYPE_COMMIT) {
+               if (!o || o->type != OBJ_COMMIT) {
                        fprintf(stderr, "Could not get commit for %s. Skipping.\n",
                                        *argv);
                        continue;
index 31c77ea03a2083ce9ed7c71a2b4d90fb6b944c78..b5d8ed467d1c10c2f2dd596717159f7ee48a8007 100644 (file)
--- a/object.c
+++ b/object.c
@@ -5,88 +5,98 @@
 #include "commit.h"
 #include "tag.h"
 
-static struct object **objs;
-static int nr_objs, obj_allocs;
+static struct object **obj_hash;
+static int nr_objs, obj_hash_size;
 
 unsigned int get_max_object_index(void)
 {
-       return obj_allocs;
+       return obj_hash_size;
 }
 
 struct object *get_indexed_object(unsigned int idx)
 {
-       return objs[idx];
+       return obj_hash[idx];
 }
 
 const char *type_names[] = {
-       "none", "blob", "tree", "commit", "bad"
+       "none", "commit", "tree", "blob", "tag",
+       "bad type 5", "bad type 6", "delta", "bad",
 };
 
+static unsigned int hash_obj(struct object *obj, unsigned int n)
+{
+       unsigned int hash = *(unsigned int *)obj->sha1;
+       return hash % n;
+}
+
+static void insert_obj_hash(struct object *obj, struct object **hash, unsigned int size)
+{
+       int j = hash_obj(obj, size);
+
+       while (hash[j]) {
+               j++;
+               if (j >= size)
+                       j = 0;
+       }
+       hash[j] = obj;
+}
+
 static int hashtable_index(const unsigned char *sha1)
 {
        unsigned int i;
        memcpy(&i, sha1, sizeof(unsigned int));
-       return (int)(i % obj_allocs);
+       return (int)(i % obj_hash_size);
 }
 
-static int find_object(const unsigned char *sha1)
+struct object *lookup_object(const unsigned char *sha1)
 {
        int i;
+       struct object *obj;
 
-       if (!objs)
-               return -1;
+       if (!obj_hash)
+               return NULL;
 
        i = hashtable_index(sha1);
-       while (objs[i]) {
-               if (memcmp(sha1, objs[i]->sha1, 20) == 0)
-                       return i;
+       while ((obj = obj_hash[i]) != NULL) {
+               if (!memcmp(sha1, obj->sha1, 20))
+                       break;
                i++;
-               if (i == obj_allocs)
+               if (i == obj_hash_size)
                        i = 0;
        }
-       return -1 - i;
+       return obj;
 }
 
-struct object *lookup_object(const unsigned char *sha1)
+static void grow_object_hash(void)
 {
-       int pos = find_object(sha1);
-       if (pos >= 0)
-               return objs[pos];
-       return NULL;
+       int i;
+       int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size;
+       struct object **new_hash;
+
+       new_hash = calloc(new_hash_size, sizeof(struct object *));
+       for (i = 0; i < obj_hash_size; i++) {
+               struct object *obj = obj_hash[i];
+               if (!obj)
+                       continue;
+               insert_obj_hash(obj, new_hash, new_hash_size);
+       }
+       free(obj_hash);
+       obj_hash = new_hash;
+       obj_hash_size = new_hash_size;
 }
 
 void created_object(const unsigned char *sha1, struct object *obj)
 {
-       int pos;
-
        obj->parsed = 0;
-       memcpy(obj->sha1, sha1, 20);
-       obj->type = TYPE_NONE;
        obj->used = 0;
+       obj->type = OBJ_NONE;
+       obj->flags = 0;
+       memcpy(obj->sha1, sha1, 20);
 
-       if (obj_allocs - 1 <= nr_objs * 2) {
-               int i, count = obj_allocs;
-               obj_allocs = (obj_allocs < 32 ? 32 : 2 * obj_allocs);
-               objs = xrealloc(objs, obj_allocs * sizeof(struct object *));
-               memset(objs + count, 0, (obj_allocs - count)
-                               * sizeof(struct object *));
-               for (i = 0; i < obj_allocs; i++)
-                       if (objs[i]) {
-                               int j = find_object(objs[i]->sha1);
-                               if (j != i) {
-                                       j = -1 - j;
-                                       objs[j] = objs[i];
-                                       objs[i] = NULL;
-                               }
-                       }
-       }
-
-       pos = find_object(sha1);
-       if (pos >= 0)
-               die("Inserting %s twice\n", sha1_to_hex(sha1));
-       pos = -pos-1;
+       if (obj_hash_size - 1 <= nr_objs * 2)
+               grow_object_hash();
 
-       objs[pos] = obj;
+       insert_obj_hash(obj, obj_hash, obj_hash_size);
        nr_objs++;
 }
 
@@ -122,7 +132,7 @@ struct object *lookup_unknown_object(const unsigned char *sha1)
        if (!obj) {
                union any_object *ret = xcalloc(1, sizeof(*ret));
                created_object(sha1, &ret->object);
-               ret->object.type = TYPE_NONE;
+               ret->object.type = OBJ_NONE;
                return &ret->object;
        }
        return obj;
index e0125e154fd970209d30138858f31ef2017651ac..733faac4ccd1c9a8bb4ed1fc67986b369d9442b3 100644 (file)
--- a/object.h
+++ b/object.h
@@ -24,12 +24,19 @@ struct object_array {
 #define TYPE_BITS   3
 #define FLAG_BITS  27
 
-#define TYPE_NONE   0
-#define TYPE_BLOB   1
-#define TYPE_TREE   2
-#define TYPE_COMMIT 3
-#define TYPE_TAG    4
-#define TYPE_BAD    5
+/*
+ * The object type is stored in 3 bits.
+ */
+enum object_type {
+       OBJ_NONE = 0,
+       OBJ_COMMIT = 1,
+       OBJ_TREE = 2,
+       OBJ_BLOB = 3,
+       OBJ_TAG = 4,
+       /* 5/6 for future expansion */
+       OBJ_DELTA = 7,
+       OBJ_BAD,
+};
 
 struct object {
        unsigned parsed : 1;
@@ -40,14 +47,14 @@ struct object {
 };
 
 extern int track_object_refs;
-extern const char *type_names[];
+extern const char *type_names[9];
 
 extern unsigned int get_max_object_index(void);
 extern struct object *get_indexed_object(unsigned int);
 
 static inline const char *typename(unsigned int type)
 {
-       return type_names[type > TYPE_TAG ? TYPE_BAD : type];
+       return type_names[type > OBJ_BAD ? OBJ_BAD : type];
 }
 
 extern struct object_refs *lookup_object_refs(struct object *);
index 47da33baa3fb70a6a2f117afbcf4e7e08a5806be..861c7f08ff1b68eee141442411b4706a1bcc2518 100644 (file)
@@ -28,7 +28,7 @@ struct object_entry {
        struct object_entry *delta;     /* delta base object */
        struct packed_git *in_pack;     /* already in pack */
        unsigned int in_pack_offset;
-       struct object_entry *delta_child; /* delitified objects who bases me */
+       struct object_entry *delta_child; /* deltified objects who bases me */
        struct object_entry *delta_sibling; /* other deltified objects who
                                             * uses the same base as me
                                             */
@@ -39,7 +39,7 @@ struct object_entry {
 };
 
 /*
- * Objects we are going to pack are colected in objects array (dynamically
+ * Objects we are going to pack are collected in objects array (dynamically
  * expanded).  nr_objects & nr_alloc controls this array.  They are stored
  * in the order we see -- typically rev-list --objects order that gives us
  * nice "minimum seek" order.
@@ -63,6 +63,7 @@ static const char *base_name;
 static unsigned char pack_file_sha1[20];
 static int progress = 1;
 static volatile sig_atomic_t progress_update = 0;
+static int window = 10;
 
 /*
  * The object names in objects array are hashed with this hashtable,
@@ -970,11 +971,12 @@ struct unpacked {
  * one.
  */
 static int try_delta(struct unpacked *trg, struct unpacked *src,
-                    struct delta_index *src_index, unsigned max_depth)
+                    unsigned max_depth)
 {
        struct object_entry *trg_entry = trg->entry;
        struct object_entry *src_entry = src->entry;
-       unsigned long size, src_size, delta_size, sizediff, max_size;
+       unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
+       char type[10];
        void *delta_buf;
 
        /* Don't bother doing diffs between different types */
@@ -1009,19 +1011,38 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                return 0;
 
        /* Now some size filtering heuristics. */
-       size = trg_entry->size;
-       max_size = size/2 - 20;
+       trg_size = trg_entry->size;
+       max_size = trg_size/2 - 20;
        max_size = max_size * (max_depth - src_entry->depth) / max_depth;
        if (max_size == 0)
                return 0;
        if (trg_entry->delta && trg_entry->delta_size <= max_size)
                max_size = trg_entry->delta_size-1;
        src_size = src_entry->size;
-       sizediff = src_size < size ? size - src_size : 0;
+       sizediff = src_size < trg_size ? trg_size - src_size : 0;
        if (sizediff >= max_size)
                return 0;
 
-       delta_buf = create_delta(src_index, trg->data, size, &delta_size, max_size);
+       /* Load data if not already done */
+       if (!trg->data) {
+               trg->data = read_sha1_file(trg_entry->sha1, type, &sz);
+               if (sz != trg_size)
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(trg_entry->sha1), sz, trg_size);
+       }
+       if (!src->data) {
+               src->data = read_sha1_file(src_entry->sha1, type, &sz);
+               if (sz != src_size)
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(src_entry->sha1), sz, src_size);
+       }
+       if (!src->index) {
+               src->index = create_delta_index(src->data, src_size);
+               if (!src->index)
+                       die("out of memory");
+       }
+
+       delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
        if (!delta_buf)
                return 0;
 
@@ -1054,8 +1075,6 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        while (--i >= 0) {
                struct object_entry *entry = list[i];
                struct unpacked *n = array + idx;
-               unsigned long size;
-               char type[10];
                int j;
 
                if (!entry->preferred_base)
@@ -1082,11 +1101,8 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                free_delta_index(n->index);
                n->index = NULL;
                free(n->data);
+               n->data = NULL;
                n->entry = entry;
-               n->data = read_sha1_file(entry->sha1, type, &size);
-               if (size != entry->size)
-                       die("object %s inconsistent object length (%lu vs %lu)",
-                           sha1_to_hex(entry->sha1), size, entry->size);
 
                j = window;
                while (--j > 0) {
@@ -1097,7 +1113,7 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        m = array + other_idx;
                        if (!m->entry)
                                break;
-                       if (try_delta(n, m, m->index, depth) < 0)
+                       if (try_delta(n, m, depth) < 0)
                                break;
                }
                /* if we made n a delta, and if n is already at max
@@ -1107,10 +1123,6 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                if (entry->delta && depth <= entry->depth)
                        continue;
 
-               n->index = create_delta_index(n->data, size);
-               if (!n->index)
-                       die("out of memory");
-
                idx++;
                if (idx >= window)
                        idx = 0;
@@ -1205,16 +1217,26 @@ static void setup_progress_signal(void)
        setitimer(ITIMER_REAL, &v, NULL);
 }
 
+static int git_pack_config(const char *k, const char *v)
+{
+       if(!strcmp(k, "pack.window")) {
+               window = git_config_int(k, v);
+               return 0;
+       }
+       return git_default_config(k, v);
+}
+
 int main(int argc, char **argv)
 {
        SHA_CTX ctx;
        char line[40 + 1 + PATH_MAX + 2];
-       int window = 10, depth = 10, pack_to_stdout = 0;
+       int depth = 10, pack_to_stdout = 0;
        struct object_entry **list;
        int num_preferred_base = 0;
        int i;
 
        setup_git_directory();
+       git_config(git_pack_config);
 
        progress = isatty(2);
        for (i = 1; i < argc; i++) {
diff --git a/pack.h b/pack.h
index 694e0c56f0b67c1ae9b7a94a243aca7b8859e50c..eb07b033ae54941d4bd00ee6bc30c7d50fd039b0 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -1,20 +1,7 @@
 #ifndef PACK_H
 #define PACK_H
 
-/*
- * The packed object type is stored in 3 bits.
- * The type value 0 is a reserved prefix if ever there is more than 7
- * object types, or any future format extensions.
- */
-enum object_type {
-       OBJ_EXT = 0,
-       OBJ_COMMIT = 1,
-       OBJ_TREE = 2,
-       OBJ_BLOB = 3,
-       OBJ_TAG = 4,
-       /* 5/6 for future expansion */
-       OBJ_DELTA = 7,
-};
+#include "object.h"
 
 /*
  * Packed object header
diff --git a/pager.c b/pager.c
index 2d186e8bde1067a62c9615e8d7f0368f98442ca4..280f57f796c84d1c1fd54e295b542c3b33490b3e 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -24,6 +24,8 @@ void setup_pager(void)
        else if (!*pager || !strcmp(pager, "cat"))
                return;
 
+       pager_in_use = 1; /* means we are emitting to terminal */
+
        if (pipe(fd) < 0)
                return;
        pid = fork();
index a90cf2206925ff51b7e8c799b22c8e6cb09c7fc2..2b30980b04e68a84bd300d647e80a34c7d3e621f 100644 (file)
@@ -7,11 +7,11 @@ static const char peek_remote_usage[] =
 "git-peek-remote [--exec=upload-pack] [host:]directory";
 static const char *exec = "git-upload-pack";
 
-static int peek_remote(int fd[2])
+static int peek_remote(int fd[2], unsigned flags)
 {
        struct ref *ref;
 
-       get_remote_heads(fd[0], &ref, 0, NULL, 0);
+       get_remote_heads(fd[0], &ref, 0, NULL, flags);
        packet_flush(fd[1]);
 
        while (ref) {
@@ -28,6 +28,7 @@ int main(int argc, char **argv)
        int fd[2];
        pid_t pid;
        int nongit = 0;
+       unsigned flags = 0;
 
        setup_git_directory_gently(&nongit);
 
@@ -35,22 +36,35 @@ int main(int argc, char **argv)
                char *arg = argv[i];
 
                if (*arg == '-') {
-                       if (!strncmp("--exec=", arg, 7))
+                       if (!strncmp("--exec=", arg, 7)) {
                                exec = arg + 7;
-                       else
-                               usage(peek_remote_usage);
-                       continue;
+                               continue;
+                       }
+                       if (!strcmp("--tags", arg)) {
+                               flags |= REF_TAGS;
+                               continue;
+                       }
+                       if (!strcmp("--heads", arg)) {
+                               flags |= REF_HEADS;
+                               continue;
+                       }
+                       if (!strcmp("--refs", arg)) {
+                               flags |= REF_NORMAL;
+                               continue;
+                       }
+                       usage(peek_remote_usage);
                }
                dest = arg;
                break;
        }
+
        if (!dest || i != argc - 1)
                usage(peek_remote_usage);
 
        pid = git_connect(fd, dest, exec);
        if (pid < 0)
                return 1;
-       ret = peek_remote(fd);
+       ret = peek_remote(fd, flags);
        close(fd[0]);
        close(fd[1]);
        finish_connect(pid);
index e85611a4ef0598f45911357d0d2f1fc354039de4..140cb53370fb23eb56b1872a2f328a0840e2820f 100644 (file)
  *
  * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
  */
-#define FS     80
 
 /*
- * We roll the registers for T, A, B, C, D, E around on each
- * iteration; T on iteration t is A on iteration t+1, and so on.
- * We use registers 7 - 12 for this.
+ * PowerPC calling convention:
+ * %r0 - volatile temp
+ * %r1 - stack pointer.
+ * %r2 - reserved
+ * %r3-%r12 - Incoming arguments & return values; volatile.
+ * %r13-%r31 - Callee-save registers
+ * %lr - Return address, volatile
+ * %ctr - volatile
+ *
+ * Register usage in this routine:
+ * %r0 - temp
+ * %r3 - argument (pointer to 5 words of SHA state)
+ * %r4 - argument (pointer to data to hash)
+ * %r5 - Contant K in SHA round (initially number of blocks to hash)
+ * %r6-%r10 - Working copies of SHA variables A..E (actually E..A order)
+ * %r11-%r26 - Data being hashed W[].
+ * %r27-%r31 - Previous copies of A..E, for final add back.
+ * %ctr - loop count
+ */
+
+
+/*
+ * We roll the registers for A, B, C, D, E around on each
+ * iteration; E on iteration t is D on iteration t+1, and so on.
+ * We use registers 6 - 10 for this.  (Registers 27 - 31 hold
+ * the previous values.)
  */
-#define RT(t)  ((((t)+5)%6)+7)
-#define RA(t)  ((((t)+4)%6)+7)
-#define RB(t)  ((((t)+3)%6)+7)
-#define RC(t)  ((((t)+2)%6)+7)
-#define RD(t)  ((((t)+1)%6)+7)
-#define RE(t)  ((((t)+0)%6)+7)
-
-/* We use registers 16 - 31 for the W values */
-#define W(t)   (((t)%16)+16)
-
-#define STEPD0(t)                              \
-       and     %r6,RB(t),RC(t);                \
-       andc    %r0,RD(t),RB(t);                \
-       rotlwi  RT(t),RA(t),5;                  \
-       rotlwi  RB(t),RB(t),30;                 \
-       or      %r6,%r6,%r0;                    \
-       add     %r0,RE(t),%r15;                 \
-       add     RT(t),RT(t),%r6;                \
-       add     %r0,%r0,W(t);                   \
-       add     RT(t),RT(t),%r0
-
-#define STEPD1(t)                              \
-       xor     %r6,RB(t),RC(t);                \
-       rotlwi  RT(t),RA(t),5;                  \
-       rotlwi  RB(t),RB(t),30;                 \
-       xor     %r6,%r6,RD(t);                  \
-       add     %r0,RE(t),%r15;                 \
-       add     RT(t),RT(t),%r6;                \
-       add     %r0,%r0,W(t);                   \
-       add     RT(t),RT(t),%r0
-
-#define STEPD2(t)                              \
-       and     %r6,RB(t),RC(t);                \
-       and     %r0,RB(t),RD(t);                \
-       rotlwi  RT(t),RA(t),5;                  \
-       rotlwi  RB(t),RB(t),30;                 \
-       or      %r6,%r6,%r0;                    \
-       and     %r0,RC(t),RD(t);                \
-       or      %r6,%r6,%r0;                    \
-       add     %r0,RE(t),%r15;                 \
-       add     RT(t),RT(t),%r6;                \
-       add     %r0,%r0,W(t);                   \
-       add     RT(t),RT(t),%r0
-
-#define LOADW(t)                               \
-       lwz     W(t),(t)*4(%r4)
-
-#define UPDATEW(t)                             \
-       xor     %r0,W((t)-3),W((t)-8);          \
-       xor     W(t),W((t)-16),W((t)-14);       \
-       xor     W(t),W(t),%r0;                  \
-       rotlwi  W(t),W(t),1
-
-#define STEP0LD4(t)                            \
-       STEPD0(t);   LOADW((t)+4);              \
-       STEPD0((t)+1); LOADW((t)+5);            \
-       STEPD0((t)+2); LOADW((t)+6);            \
-       STEPD0((t)+3); LOADW((t)+7)
-
-#define STEPUP4(t, fn)                         \
-       STEP##fn(t);   UPDATEW((t)+4);          \
-       STEP##fn((t)+1); UPDATEW((t)+5);        \
-       STEP##fn((t)+2); UPDATEW((t)+6);        \
-       STEP##fn((t)+3); UPDATEW((t)+7)
-
-#define STEPUP20(t, fn)                                \
-       STEPUP4(t, fn);                         \
-       STEPUP4((t)+4, fn);                     \
-       STEPUP4((t)+8, fn);                     \
-       STEPUP4((t)+12, fn);                    \
-       STEPUP4((t)+16, fn)
+#define RA(t)  (((t)+4)%5+6)
+#define RB(t)  (((t)+3)%5+6)
+#define RC(t)  (((t)+2)%5+6)
+#define RD(t)  (((t)+1)%5+6)
+#define RE(t)  (((t)+0)%5+6)
+
+/* We use registers 11 - 26 for the W values */
+#define W(t)   ((t)%16+11)
+
+/* Register 5 is used for the constant k */
+
+/*
+ * The basic SHA-1 round function is:
+ * E += ROTL(A,5) + F(B,C,D) + W[i] + K;  B = ROTL(B,30)
+ * Then the variables are renamed: (A,B,C,D,E) = (E,A,B,C,D).
+ *
+ * Every 20 rounds, the function F() and the contant K changes:
+ * - 20 rounds of f0(b,c,d) = "bit wise b ? c : d" =  (^b & d) + (b & c)
+ * - 20 rounds of f1(b,c,d) = b^c^d = (b^d)^c
+ * - 20 rounds of f2(b,c,d) = majority(b,c,d) = (b&d) + ((b^d)&c)
+ * - 20 more rounds of f1(b,c,d)
+ *
+ * These are all scheduled for near-optimal performance on a G4.
+ * The G4 is a 3-issue out-of-order machine with 3 ALUs, but it can only
+ * *consider* starting the oldest 3 instructions per cycle.  So to get
+ * maximum performace out of it, you have to treat it as an in-order
+ * machine.  Which means interleaving the computation round t with the
+ * computation of W[t+4].
+ *
+ * The first 16 rounds use W values loaded directly from memory, while the
+ * remaining 64 use values computed from those first 16.  We preload
+ * 4 values before starting, so there are three kinds of rounds:
+ * - The first 12 (all f0) also load the W values from memory.
+ * - The next 64 compute W(i+4) in parallel. 8*f0, 20*f1, 20*f2, 16*f1.
+ * - The last 4 (all f1) do not do anything with W.
+ *
+ * Therefore, we have 6 different round functions:
+ * STEPD0_LOAD(t,s) - Perform round t and load W(s).  s < 16
+ * STEPD0_UPDATE(t,s) - Perform round t and compute W(s).  s >= 16.
+ * STEPD1_UPDATE(t,s)
+ * STEPD2_UPDATE(t,s)
+ * STEPD1(t) - Perform round t with no load or update.
+ *
+ * The G5 is more fully out-of-order, and can find the parallelism
+ * by itself.  The big limit is that it has a 2-cycle ALU latency, so
+ * even though it's 2-way, the code has to be scheduled as if it's
+ * 4-way, which can be a limit.  To help it, we try to schedule the
+ * read of RA(t) as late as possible so it doesn't stall waiting for
+ * the previous round's RE(t-1), and we try to rotate RB(t) as early
+ * as possible while reading RC(t) (= RB(t-1)) as late as possible.
+ */
+
+/* the initial loads. */
+#define LOADW(s) \
+       lwz     W(s),(s)*4(%r4)
+
+/*
+ * Perform a step with F0, and load W(s).  Uses W(s) as a temporary
+ * before loading it.
+ * This is actually 10 instructions, which is an awkward fit.
+ * It can execute grouped as listed, or delayed one instruction.
+ * (If delayed two instructions, there is a stall before the start of the
+ * second line.)  Thus, two iterations take 7 cycles, 3.5 cycles per round.
+ */
+#define STEPD0_LOAD(t,s) \
+add RE(t),RE(t),W(t); andc   %r0,RD(t),RB(t);  and    W(s),RC(t),RB(t); \
+add RE(t),RE(t),%r0;  rotlwi %r0,RA(t),5;      rotlwi RB(t),RB(t),30;   \
+add RE(t),RE(t),W(s); add    %r0,%r0,%r5;      lwz    W(s),(s)*4(%r4);  \
+add RE(t),RE(t),%r0
+
+/*
+ * This is likewise awkward, 13 instructions.  However, it can also
+ * execute starting with 2 out of 3 possible moduli, so it does 2 rounds
+ * in 9 cycles, 4.5 cycles/round.
+ */
+#define STEPD0_UPDATE(t,s,loadk...) \
+add RE(t),RE(t),W(t); andc   %r0,RD(t),RB(t); xor    W(s),W((s)-16),W((s)-3); \
+add RE(t),RE(t),%r0;  and    %r0,RC(t),RB(t); xor    W(s),W(s),W((s)-8);      \
+add RE(t),RE(t),%r0;  rotlwi %r0,RA(t),5;     xor    W(s),W(s),W((s)-14);     \
+add RE(t),RE(t),%r5;  loadk; rotlwi RB(t),RB(t),30;  rotlwi W(s),W(s),1;     \
+add RE(t),RE(t),%r0
+
+/* Nicely optimal.  Conveniently, also the most common. */
+#define STEPD1_UPDATE(t,s,loadk...) \
+add RE(t),RE(t),W(t); xor    %r0,RD(t),RB(t); xor    W(s),W((s)-16),W((s)-3); \
+add RE(t),RE(t),%r5;  loadk; xor %r0,%r0,RC(t);  xor W(s),W(s),W((s)-8);      \
+add RE(t),RE(t),%r0;  rotlwi %r0,RA(t),5;     xor    W(s),W(s),W((s)-14);     \
+add RE(t),RE(t),%r0;  rotlwi RB(t),RB(t),30;  rotlwi W(s),W(s),1
+
+/*
+ * The naked version, no UPDATE, for the last 4 rounds.  3 cycles per.
+ * We could use W(s) as a temp register, but we don't need it.
+ */
+#define STEPD1(t) \
+                        add   RE(t),RE(t),W(t); xor    %r0,RD(t),RB(t); \
+rotlwi RB(t),RB(t),30;  add   RE(t),RE(t),%r5;  xor    %r0,%r0,RC(t);   \
+add    RE(t),RE(t),%r0; rotlwi %r0,RA(t),5;     /* spare slot */        \
+add    RE(t),RE(t),%r0
+
+/*
+ * 14 instructions, 5 cycles per.  The majority function is a bit
+ * awkward to compute.  This can execute with a 1-instruction delay,
+ * but it causes a 2-instruction delay, which triggers a stall.
+ */
+#define STEPD2_UPDATE(t,s,loadk...) \
+add RE(t),RE(t),W(t); and    %r0,RD(t),RB(t); xor    W(s),W((s)-16),W((s)-3); \
+add RE(t),RE(t),%r0;  xor    %r0,RD(t),RB(t); xor    W(s),W(s),W((s)-8);      \
+add RE(t),RE(t),%r5;  loadk; and %r0,%r0,RC(t);  xor W(s),W(s),W((s)-14);     \
+add RE(t),RE(t),%r0;  rotlwi %r0,RA(t),5;     rotlwi W(s),W(s),1;             \
+add RE(t),RE(t),%r0;  rotlwi RB(t),RB(t),30
+
+#define STEP0_LOAD4(t,s)               \
+       STEPD0_LOAD(t,s);               \
+       STEPD0_LOAD((t+1),(s)+1);       \
+       STEPD0_LOAD((t)+2,(s)+2);       \
+       STEPD0_LOAD((t)+3,(s)+3)
+
+#define STEPUP4(fn, t, s, loadk...)            \
+       STEP##fn##_UPDATE(t,s,);                \
+       STEP##fn##_UPDATE((t)+1,(s)+1,);        \
+       STEP##fn##_UPDATE((t)+2,(s)+2,);        \
+       STEP##fn##_UPDATE((t)+3,(s)+3,loadk)
+
+#define STEPUP20(fn, t, s, loadk...)   \
+       STEPUP4(fn, t, s,);             \
+       STEPUP4(fn, (t)+4, (s)+4,);     \
+       STEPUP4(fn, (t)+8, (s)+8,);     \
+       STEPUP4(fn, (t)+12, (s)+12,);   \
+       STEPUP4(fn, (t)+16, (s)+16, loadk)
 
        .globl  sha1_core
 sha1_core:
-       stwu    %r1,-FS(%r1)
-       stw     %r15,FS-68(%r1)
-       stw     %r16,FS-64(%r1)
-       stw     %r17,FS-60(%r1)
-       stw     %r18,FS-56(%r1)
-       stw     %r19,FS-52(%r1)
-       stw     %r20,FS-48(%r1)
-       stw     %r21,FS-44(%r1)
-       stw     %r22,FS-40(%r1)
-       stw     %r23,FS-36(%r1)
-       stw     %r24,FS-32(%r1)
-       stw     %r25,FS-28(%r1)
-       stw     %r26,FS-24(%r1)
-       stw     %r27,FS-20(%r1)
-       stw     %r28,FS-16(%r1)
-       stw     %r29,FS-12(%r1)
-       stw     %r30,FS-8(%r1)
-       stw     %r31,FS-4(%r1)
+       stwu    %r1,-80(%r1)
+       stmw    %r13,4(%r1)
 
        /* Load up A - E */
-       lwz     RA(0),0(%r3)    /* A */
-       lwz     RB(0),4(%r3)    /* B */
-       lwz     RC(0),8(%r3)    /* C */
-       lwz     RD(0),12(%r3)   /* D */
-       lwz     RE(0),16(%r3)   /* E */
+       lmw     %r27,0(%r3)
 
        mtctr   %r5
 
-1:     LOADW(0)
+1:
+       LOADW(0)
+       lis     %r5,0x5a82
+       mr      RE(0),%r31
        LOADW(1)
+       mr      RD(0),%r30
+       mr      RC(0),%r29
        LOADW(2)
+       ori     %r5,%r5,0x7999  /* K0-19 */
+       mr      RB(0),%r28
        LOADW(3)
+       mr      RA(0),%r27
+
+       STEP0_LOAD4(0, 4)
+       STEP0_LOAD4(4, 8)
+       STEP0_LOAD4(8, 12)
+       STEPUP4(D0, 12, 16,)
+       STEPUP4(D0, 16, 20, lis %r5,0x6ed9)
 
-       lis     %r15,0x5a82     /* K0-19 */
-       ori     %r15,%r15,0x7999
-       STEP0LD4(0)
-       STEP0LD4(4)
-       STEP0LD4(8)
-       STEPUP4(12, D0)
-       STEPUP4(16, D0)
-
-       lis     %r15,0x6ed9     /* K20-39 */
-       ori     %r15,%r15,0xeba1
-       STEPUP20(20, D1)
-
-       lis     %r15,0x8f1b     /* K40-59 */
-       ori     %r15,%r15,0xbcdc
-       STEPUP20(40, D2)
-
-       lis     %r15,0xca62     /* K60-79 */
-       ori     %r15,%r15,0xc1d6
-       STEPUP4(60, D1)
-       STEPUP4(64, D1)
-       STEPUP4(68, D1)
-       STEPUP4(72, D1)
+       ori     %r5,%r5,0xeba1  /* K20-39 */
+       STEPUP20(D1, 20, 24, lis %r5,0x8f1b)
+
+       ori     %r5,%r5,0xbcdc  /* K40-59 */
+       STEPUP20(D2, 40, 44, lis %r5,0xca62)
+
+       ori     %r5,%r5,0xc1d6  /* K60-79 */
+       STEPUP4(D1, 60, 64,)
+       STEPUP4(D1, 64, 68,)
+       STEPUP4(D1, 68, 72,)
+       STEPUP4(D1, 72, 76,)
+       addi    %r4,%r4,64
        STEPD1(76)
        STEPD1(77)
        STEPD1(78)
        STEPD1(79)
 
-       lwz     %r20,16(%r3)
-       lwz     %r19,12(%r3)
-       lwz     %r18,8(%r3)
-       lwz     %r17,4(%r3)
-       lwz     %r16,0(%r3)
-       add     %r20,RE(80),%r20
-       add     RD(0),RD(80),%r19
-       add     RC(0),RC(80),%r18
-       add     RB(0),RB(80),%r17
-       add     RA(0),RA(80),%r16
-       mr      RE(0),%r20
-       stw     RA(0),0(%r3)
-       stw     RB(0),4(%r3)
-       stw     RC(0),8(%r3)
-       stw     RD(0),12(%r3)
-       stw     RE(0),16(%r3)
+       /* Add results to original values */
+       add     %r31,%r31,RE(0)
+       add     %r30,%r30,RD(0)
+       add     %r29,%r29,RC(0)
+       add     %r28,%r28,RB(0)
+       add     %r27,%r27,RA(0)
 
-       addi    %r4,%r4,64
        bdnz    1b
 
-       lwz     %r15,FS-68(%r1)
-       lwz     %r16,FS-64(%r1)
-       lwz     %r17,FS-60(%r1)
-       lwz     %r18,FS-56(%r1)
-       lwz     %r19,FS-52(%r1)
-       lwz     %r20,FS-48(%r1)
-       lwz     %r21,FS-44(%r1)
-       lwz     %r22,FS-40(%r1)
-       lwz     %r23,FS-36(%r1)
-       lwz     %r24,FS-32(%r1)
-       lwz     %r25,FS-28(%r1)
-       lwz     %r26,FS-24(%r1)
-       lwz     %r27,FS-20(%r1)
-       lwz     %r28,FS-16(%r1)
-       lwz     %r29,FS-12(%r1)
-       lwz     %r30,FS-8(%r1)
-       lwz     %r31,FS-4(%r1)
-       addi    %r1,%r1,FS
+       /* Save final hash, restore registers, and return */
+       stmw    %r27,0(%r3)
+       lmw     %r13,4(%r1)
+       addi    %r1,%r1,80
        blr
diff --git a/quote.c b/quote.c
index 1910d000a5d288734aaea24da617481223ff9f56..e220dcc280d9ed6be2e2357e2660c7e828f3631b 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -45,6 +45,23 @@ size_t sq_quote_buf(char *dst, size_t n, const char *src)
        return len;
 }
 
+void sq_quote_print(FILE *stream, const char *src)
+{
+       char c;
+
+       fputc('\'', stream);
+       while ((c = *src++)) {
+               if (need_bs_quote(c)) {
+                       fputs("'\\", stream);
+                       fputc(c, stream);
+                       fputc('\'', stream);
+               } else {
+                       fputc(c, stream);
+               }
+       }
+       fputc('\'', stream);
+}
+
 char *sq_quote(const char *src)
 {
        char *buf;
diff --git a/quote.h b/quote.h
index c1ab3788e6d69318638142ebeffc690318a0489a..fc5481e78a6baad9ad72014318a9d0bfdd495b6b 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -29,6 +29,7 @@
  */
 
 extern char *sq_quote(const char *src);
+extern void sq_quote_print(FILE *stream, const char *src);
 extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
 
 /* This unwraps what sq_quote() produces in place, but returns
index 3c32aae7e8ae63bccad83a603f2b86c66965cd26..a50d3612c84d10da35b0b0e1278ecea899a4ec15 100644 (file)
@@ -748,7 +748,7 @@ int read_cache(void)
                die("index file open failed (%s)", strerror(errno));
        }
 
-       size = 0; // avoid gcc warning
+       size = 0; /* avoid gcc warning */
        map = MAP_FAILED;
        if (!fstat(fd, &st)) {
                size = st.st_size;
diff --git a/refs.c b/refs.c
index 713ca467368e24f6112663b7b96921f9d1bbc145..56db394459cec2cd156ee2243a1dbac33867f926 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -362,7 +362,7 @@ static int log_ref_write(struct ref_lock *lock,
        int logfd, written, oflags = O_APPEND | O_WRONLY;
        unsigned maxlen, len;
        char *logrec;
-       const char *comitter;
+       const char *committer;
 
        if (log_all_ref_updates) {
                if (safe_create_leading_directories(lock->log_file) < 0)
@@ -379,24 +379,23 @@ static int log_ref_write(struct ref_lock *lock,
                        lock->log_file, strerror(errno));
        }
 
-       setup_ident();
-       comitter = git_committer_info(1);
+       committer = git_committer_info(1);
        if (msg) {
-               maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;
+               maxlen = strlen(committer) + strlen(msg) + 2*40 + 5;
                logrec = xmalloc(maxlen);
                len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
                        sha1_to_hex(lock->old_sha1),
                        sha1_to_hex(sha1),
-                       comitter,
+                       committer,
                        msg);
        }
        else {
-               maxlen = strlen(comitter) + 2*40 + 4;
+               maxlen = strlen(committer) + 2*40 + 4;
                logrec = xmalloc(maxlen);
                len = snprintf(logrec, maxlen, "%s %s %s\n",
                        sha1_to_hex(lock->old_sha1),
                        sha1_to_hex(sha1),
-                       comitter);
+                       committer);
        }
        written = len <= maxlen ? write(logfd, logrec, len) : -1;
        free(logrec);
index b963f2adfd92050310f7327e1ee10e21d5996a7c..874e349db818d52329e30f2634855e5cf14192b8 100644 (file)
@@ -135,7 +135,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
        /*
         * Tag object? Look what it points to..
         */
-       while (object->type == TYPE_TAG) {
+       while (object->type == OBJ_TAG) {
                struct tag *tag = (struct tag *) object;
                if (revs->tag_objects && !(flags & UNINTERESTING))
                        add_pending_object(revs, object, tag->tag);
@@ -148,7 +148,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
         * Commit object? Just return it, we'll do all the complex
         * reachability crud.
         */
-       if (object->type == TYPE_COMMIT) {
+       if (object->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *)object;
                if (parse_commit(commit) < 0)
                        die("unable to parse commit %s", name);
@@ -164,7 +164,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
         * Tree object? Either mark it uniniteresting, or add it
         * to the list of objects to look at later..
         */
-       if (object->type == TYPE_TREE) {
+       if (object->type == OBJ_TREE) {
                struct tree *tree = (struct tree *)object;
                if (!revs->tree_objects)
                        return NULL;
@@ -179,7 +179,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
        /*
         * Blob object? You know the drill by now..
         */
-       if (object->type == TYPE_BLOB) {
+       if (object->type == OBJ_BLOB) {
                struct blob *blob = (struct blob *)object;
                if (!revs->blob_objects)
                        return NULL;
@@ -280,7 +280,7 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
 static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list **pp, *parent;
-       int tree_changed = 0;
+       int tree_changed = 0, tree_same = 0;
 
        if (!commit->tree)
                return;
@@ -298,6 +298,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                parse_commit(p);
                switch (rev_compare_tree(revs, p->tree, commit->tree)) {
                case REV_TREE_SAME:
+                       tree_same = 1;
                        if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
                                /* Even if a merge with an uninteresting
                                 * side branch brought the entire change
@@ -334,7 +335,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                }
                die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
        }
-       if (tree_changed)
+       if (tree_changed && !tree_same)
                commit->object.flags |= TREECHANGE;
 }
 
@@ -493,11 +494,11 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
                return 0;
        while (1) {
                it = get_reference(revs, arg, sha1, 0);
-               if (it->type != TYPE_TAG)
+               if (it->type != OBJ_TAG)
                        break;
                memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
        }
-       if (it->type != TYPE_COMMIT)
+       if (it->type != OBJ_COMMIT)
                return 0;
        commit = (struct commit *)it;
        for (parents = commit->parents; parents; parents = parents->next) {
@@ -536,6 +537,61 @@ void init_revisions(struct rev_info *revs)
        diff_setup(&revs->diffopt);
 }
 
+static void add_pending_commit_list(struct rev_info *revs,
+                                    struct commit_list *commit_list,
+                                    unsigned int flags)
+{
+       while (commit_list) {
+               struct object *object = &commit_list->item->object;
+               object->flags |= flags;
+               add_pending_object(revs, object, sha1_to_hex(object->sha1));
+               commit_list = commit_list->next;
+       }
+}
+
+static void prepare_show_merge(struct rev_info *revs)
+{
+       struct commit_list *bases;
+       struct commit *head, *other;
+       unsigned char sha1[20];
+       const char **prune = NULL;
+       int i, prune_num = 1; /* counting terminating NULL */
+
+       if (get_sha1("HEAD", sha1) || !(head = lookup_commit(sha1)))
+               die("--merge without HEAD?");
+       if (get_sha1("MERGE_HEAD", sha1) || !(other = lookup_commit(sha1)))
+               die("--merge without MERGE_HEAD?");
+       add_pending_object(revs, &head->object, "HEAD");
+       add_pending_object(revs, &other->object, "MERGE_HEAD");
+       bases = get_merge_bases(head, other, 1);
+       while (bases) {
+               struct commit *it = bases->item;
+               struct commit_list *n = bases->next;
+               free(bases);
+               bases = n;
+               it->object.flags |= UNINTERESTING;
+               add_pending_object(revs, &it->object, "(merge-base)");
+       }
+
+       if (!active_nr)
+               read_cache();
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               if (ce_path_match(ce, revs->prune_data)) {
+                       prune_num++;
+                       prune = xrealloc(prune, sizeof(*prune) * prune_num);
+                       prune[prune_num-2] = ce->name;
+                       prune[prune_num-1] = NULL;
+               }
+               while ((i+1 < active_nr) &&
+                      ce_same_name(ce, active_cache[i+1]))
+                       i++;
+       }
+       revs->prune_data = prune;
+}
+
 /*
  * Parse revision information, filling in the "rev_info" structure,
  * and removing the used arguments from the argument list.
@@ -545,7 +601,7 @@ void init_revisions(struct rev_info *revs)
  */
 int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
 {
-       int i, flags, seen_dashdash;
+       int i, flags, seen_dashdash, show_merge;
        const char **unrecognized = argv + 1;
        int left = 1;
 
@@ -562,7 +618,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                break;
        }
 
-       flags = 0;
+       flags = show_merge = 0;
        for (i = 1; i < argc; i++) {
                struct object *object;
                const char *arg = argv[i];
@@ -629,6 +685,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                def = argv[i];
                                continue;
                        }
+                       if (!strcmp(arg, "--merge")) {
+                               show_merge = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--topo-order")) {
                                revs->topo_order = 1;
                                continue;
@@ -771,27 +831,46 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                        unsigned char from_sha1[20];
                        const char *next = dotdot + 2;
                        const char *this = arg;
+                       int symmetric = *next == '.';
+                       unsigned int flags_exclude = flags ^ UNINTERESTING;
+
                        *dotdot = 0;
+                       next += symmetric;
+
                        if (!*next)
                                next = "HEAD";
                        if (dotdot == arg)
                                this = "HEAD";
                        if (!get_sha1(this, from_sha1) &&
                            !get_sha1(next, sha1)) {
-                               struct object *exclude;
-                               struct object *include;
-
-                               exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
-                               include = get_reference(revs, next, sha1, flags);
-                               if (!exclude || !include)
-                                       die("Invalid revision range %s..%s", arg, next);
+                               struct commit *a, *b;
+                               struct commit_list *exclude;
+
+                               a = lookup_commit_reference(from_sha1);
+                               b = lookup_commit_reference(sha1);
+                               if (!a || !b) {
+                                       die(symmetric ?
+                                           "Invalid symmetric difference expression %s...%s" :
+                                           "Invalid revision range %s..%s",
+                                           arg, next);
+                               }
 
                                if (!seen_dashdash) {
                                        *dotdot = '.';
                                        verify_non_filename(revs->prefix, arg);
                                }
-                               add_pending_object(revs, exclude, this);
-                               add_pending_object(revs, include, next);
+
+                               if (symmetric) {
+                                       exclude = get_merge_bases(a, b, 1);
+                                       add_pending_commit_list(revs, exclude,
+                                                               flags_exclude);
+                                       free_commit_list(exclude);
+                                       a->object.flags |= flags;
+                               } else
+                                       a->object.flags |= flags_exclude;
+                               b->object.flags |= flags;
+                               add_pending_object(revs, &a->object, this);
+                               add_pending_object(revs, &b->object, next);
                                continue;
                        }
                        *dotdot = '.';
@@ -831,6 +910,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                object = get_reference(revs, arg, sha1, flags ^ local_flags);
                add_pending_object(revs, object, arg);
        }
+       if (show_merge)
+               prepare_show_merge(revs);
        if (def && !revs->pending.nr) {
                unsigned char sha1[20];
                struct object *object;
@@ -851,8 +932,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        }
        if (revs->combine_merges) {
                revs->ignore_merges = 0;
-               if (revs->dense_combined_merges &&
-                   (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
+               if (revs->dense_combined_merges && !revs->diffopt.output_format)
                        revs->diffopt.output_format = DIFF_FORMAT_PATCH;
        }
        revs->diffopt.abbrev = revs->abbrev;
@@ -896,6 +976,8 @@ static int rewrite_one(struct rev_info *revs, struct commit **pp)
                struct commit *p = *pp;
                if (!revs->limited)
                        add_parents_to_list(revs, p, &revs->commits);
+               if (p->parents && p->parents->next)
+                       return 0;
                if (p->object.flags & (TREECHANGE | UNINTERESTING))
                        return 0;
                if (!p->parents)
@@ -988,8 +1070,15 @@ struct commit *get_revision(struct rev_info *revs)
                    commit->parents && commit->parents->next)
                        continue;
                if (revs->prune_fn && revs->dense) {
-                       if (!(commit->object.flags & TREECHANGE))
-                               continue;
+                       /* Commit without changes? */
+                       if (!(commit->object.flags & TREECHANGE)) {
+                               /* drop merges unless we want parenthood */
+                               if (!revs->parents)
+                                       continue;
+                               /* non-merge - always ignore it */
+                               if (!commit->parents || !commit->parents->next)
+                                       continue;
+                       }
                        if (revs->parents)
                                rewrite_parents(revs, commit);
                }
index c010a0811604e55f4e46d08d986ae62edfb192ee..e23ec8f45a3b635d83f662e4de5d671e41f37919 100644 (file)
@@ -61,6 +61,8 @@ struct rev_info {
        struct log_info *loginfo;
        int             nr, total;
        const char      *mime_boundary;
+       const char      *message_id;
+       const char      *ref_message_id;
        const char      *add_signoff;
        const char      *extra_headers;
 
index af93b11f238e4583f6e7fb9dab5a2224acf4ee1f..10bc8bc35935c6ba3af70456781d6194c55ba274 100644 (file)
@@ -151,12 +151,12 @@ static int ref_newer(const unsigned char *new_sha1,
         * old.  Otherwise we require --force.
         */
        o = deref_tag(parse_object(old_sha1), NULL, 0);
-       if (!o || o->type != TYPE_COMMIT)
+       if (!o || o->type != OBJ_COMMIT)
                return 0;
        old = (struct commit *) o;
 
        o = deref_tag(parse_object(new_sha1), NULL, 0);
-       if (!o || o->type != TYPE_COMMIT)
+       if (!o || o->type != OBJ_COMMIT)
                return 0;
        new = (struct commit *) o;
 
@@ -239,7 +239,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        int expect_status_report = 0;
 
        /* No funny business with the matcher */
-       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, 1);
+       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
        get_local_heads();
 
        /* Does the other end support the reporting? */
index 0eb5132cc1cf09ea7dacdf659d3c03adae761acf..7df628f2b226f6984d168af0382680b9dae0751a 100644 (file)
@@ -12,7 +12,7 @@ static int add_info_ref(const char *path, const unsigned char *sha1)
        struct object *o = parse_object(sha1);
 
        fprintf(info_ref_fp, "%s        %s\n", sha1_to_hex(sha1), path);
-       if (o->type == TYPE_TAG) {
+       if (o->type == OBJ_TAG) {
                o = deref_tag(o, path, 0);
                if (o)
                        fprintf(info_ref_fp, "%s        %s^{}\n",
@@ -94,7 +94,7 @@ static int read_pack_info_file(const char *infofile)
 
        fp = fopen(infofile, "r");
        if (!fp)
-               return 1; /* nonexisting is not an error. */
+               return 1; /* nonexistent is not an error. */
 
        while (fgets(line, sizeof(line), fp)) {
                int len = strlen(line);
index 817963045b81cef36bf3a9c76b7399a70226a181..43bc2ea0cf039bb9fd02c8313981e85bd7398d33 100644 (file)
@@ -453,7 +453,7 @@ int use_packed_git(struct packed_git *p)
 {
        if (!p->pack_size) {
                struct stat st;
-               // We created the struct before we had the pack
+               /* We created the struct before we had the pack */
                stat(p->pack_name, &st);
                if (!S_ISREG(st.st_mode))
                        die("packfile %s not a regular file", p->pack_name);
@@ -684,26 +684,74 @@ static void *map_sha1_file_internal(const unsigned char *sha1,
        return map;
 }
 
-int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size)
+static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
 {
+       unsigned char c;
+       unsigned int word, bits;
+       unsigned long size;
+       static const char *typename[8] = {
+               NULL,   /* OBJ_EXT */
+               "commit", "tree", "blob", "tag",
+               NULL, NULL, NULL
+       };
+       const char *type;
+
        /* Get the data stream */
        memset(stream, 0, sizeof(*stream));
        stream->next_in = map;
        stream->avail_in = mapsize;
        stream->next_out = buffer;
-       stream->avail_out = size;
+       stream->avail_out = bufsiz;
+
+       /*
+        * Is it a zlib-compressed buffer? If so, the first byte
+        * must be 0x78 (15-bit window size, deflated), and the
+        * first 16-bit word is evenly divisible by 31
+        */
+       word = (map[0] << 8) + map[1];
+       if (map[0] == 0x78 && !(word % 31)) {
+               inflateInit(stream);
+               return inflate(stream, 0);
+       }
+
+       c = *map++;
+       mapsize--;
+       type = typename[(c >> 4) & 7];
+       if (!type)
+               return -1;
 
+       bits = 4;
+       size = c & 0xf;
+       while ((c & 0x80)) {
+               if (bits >= 8*sizeof(long))
+                       return -1;
+               c = *map++;
+               size += (c & 0x7f) << bits;
+               bits += 7;
+               mapsize--;
+       }
+
+       /* Set up the stream for the rest.. */
+       stream->next_in = map;
+       stream->avail_in = mapsize;
        inflateInit(stream);
-       return inflate(stream, 0);
+
+       /* And generate the fake traditional header */
+       stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", type, size);
+       return 0;
 }
 
 static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
 {
        int bytes = strlen(buffer) + 1;
        unsigned char *buf = xmalloc(1+size);
+       unsigned long n;
 
-       memcpy(buf, (char *) buffer + bytes, stream->total_out - bytes);
-       bytes = stream->total_out - bytes;
+       n = stream->total_out - bytes;
+       if (n > size)
+               n = size;
+       memcpy(buf, (char *) buffer + bytes, n);
+       bytes = n;
        if (bytes < size) {
                stream->next_out = buf + bytes;
                stream->avail_out = size - bytes;
@@ -720,7 +768,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
  * too permissive for what we want to check. So do an anal
  * object header parse by hand.
  */
-int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
+static int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
 {
        int i;
        unsigned long size;
@@ -1331,31 +1379,29 @@ char *write_sha1_file_prepare(void *buf,
 static int link_temp_to_file(const char *tmpfile, char *filename)
 {
        int ret;
+       char *dir;
 
        if (!link(tmpfile, filename))
                return 0;
 
        /*
-        * Try to mkdir the last path component if that failed
-        * with an ENOENT.
+        * Try to mkdir the last path component if that failed.
         *
         * Re-try the "link()" regardless of whether the mkdir
         * succeeds, since a race might mean that somebody
         * else succeeded.
         */
        ret = errno;
-       if (ret == ENOENT) {
-               char *dir = strrchr(filename, '/');
-               if (dir) {
-                       *dir = 0;
-                       mkdir(filename, 0777);
-                       if (adjust_shared_perm(filename))
-                               return -2;
-                       *dir = '/';
-                       if (!link(tmpfile, filename))
-                               return 0;
-                       ret = errno;
-               }
+       dir = strrchr(filename, '/');
+       if (dir) {
+               *dir = 0;
+               mkdir(filename, 0777);
+               if (adjust_shared_perm(filename))
+                       return -2;
+               *dir = '/';
+               if (!link(tmpfile, filename))
+                       return 0;
+               ret = errno;
        }
        return ret;
 }
@@ -1414,6 +1460,49 @@ static int write_buffer(int fd, const void *buf, size_t len)
        return 0;
 }
 
+static int write_binary_header(unsigned char *hdr, enum object_type type, unsigned long len)
+{
+       int hdr_len;
+       unsigned char c;
+
+       c = (type << 4) | (len & 15);
+       len >>= 4;
+       hdr_len = 1;
+       while (len) {
+               *hdr++ = c | 0x80;
+               hdr_len++;
+               c = (len & 0x7f);
+               len >>= 7;
+       }
+       *hdr = c;
+       return hdr_len;
+}
+
+static void setup_object_header(z_stream *stream, const char *type, unsigned long len)
+{
+       int obj_type, hdr;
+
+       if (use_legacy_headers) {
+               while (deflate(stream, 0) == Z_OK)
+                       /* nothing */;
+               return;
+       }
+       if (!strcmp(type, blob_type))
+               obj_type = OBJ_BLOB;
+       else if (!strcmp(type, tree_type))
+               obj_type = OBJ_TREE;
+       else if (!strcmp(type, commit_type))
+               obj_type = OBJ_COMMIT;
+       else if (!strcmp(type, tag_type))
+               obj_type = OBJ_TAG;
+       else
+               die("trying to generate bogus object of type '%s'", type);
+       hdr = write_binary_header(stream->next_out, obj_type, len);
+       stream->total_out = hdr;
+       stream->next_out += hdr;
+       stream->avail_out -= hdr;
+}
+
 int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
        int size;
@@ -1458,8 +1547,8 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
-       size = deflateBound(&stream, len+hdrlen);
+       deflateInit(&stream, zlib_compression_level);
+       size = 8 + deflateBound(&stream, len+hdrlen);
        compressed = xmalloc(size);
 
        /* Compress it */
@@ -1469,8 +1558,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        /* First header.. */
        stream.next_in = hdr;
        stream.avail_in = hdrlen;
-       while (deflate(&stream, 0) == Z_OK)
-               /* nothing */;
+       setup_object_header(&stream, type, len);
 
        /* Then the data itself.. */
        stream.next_in = buf;
@@ -1504,14 +1592,14 @@ static void *repack_object(const unsigned char *sha1, unsigned long *objsize)
        int hdrlen;
        void *buf;
 
-       // need to unpack and recompress it by itself
+       /* need to unpack and recompress it by itself */
        unpacked = read_packed_sha1(sha1, type, &len);
 
        hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        size = deflateBound(&stream, len + hdrlen);
        buf = xmalloc(size);
 
@@ -1660,7 +1748,7 @@ int has_sha1_file(const unsigned char *sha1)
 
 /*
  * reads from fd as long as possible into a supplied buffer of size bytes.
- * If neccessary the buffer's size is increased using realloc()
+ * If necessary the buffer's size is increased using realloc()
  *
  * returns 0 if anything went fine and -1 otherwise
  *
index f2cbafa496231e7a9cdbbea2ab4e7d4c4ed761ab..5fe8e5d4bf25d79c3fa76610d1617ee07c1f1e2c 100644 (file)
@@ -381,13 +381,13 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
 
        sp++; /* beginning of type name, or closing brace for empty */
        if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
-               expected_type = TYPE_COMMIT;
+               expected_type = OBJ_COMMIT;
        else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
-               expected_type = TYPE_TREE;
+               expected_type = OBJ_TREE;
        else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
-               expected_type = TYPE_BLOB;
+               expected_type = OBJ_BLOB;
        else if (sp[0] == '}')
-               expected_type = TYPE_NONE;
+               expected_type = OBJ_NONE;
        else
                return -1;
 
@@ -416,9 +416,9 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
                                memcpy(sha1, o->sha1, 20);
                                return 0;
                        }
-                       if (o->type == TYPE_TAG)
+                       if (o->type == OBJ_TAG)
                                o = ((struct tag*) o)->tagged;
-                       else if (o->type == TYPE_COMMIT)
+                       else if (o->type == OBJ_COMMIT)
                                o = &(((struct commit *) o)->tree->object);
                        else
                                return error("%.*s: expected %s type, but the object dereferences to %s type",
index 1e59cd2008d8089efef74b940d1a1094add7e687..28f7fd9174e8c48493983b17fa7b18cdecd41609 100644 (file)
@@ -68,7 +68,7 @@ int fetch(unsigned char *sha1)
        struct object_list *temp;
 
        if (memcmp(sha1, in_transit->item->sha1, 20)) {
-               // we must have already fetched it to clean the queue
+               /* we must have already fetched it to clean the queue */
                return has_sha1_file(sha1) ? 0 : -1;
        }
        prefetches--;
@@ -85,7 +85,7 @@ int fetch(unsigned char *sha1)
                if (read(fd_in, &remote, 1) < 1)
                        return -1;
        }
-       //fprintf(stderr, "Got %d\n", remote);
+       /* fprintf(stderr, "Got %d\n", remote); */
        if (remote < 0)
                return remote;
        ret = write_sha1_from_fd(sha1, fd_in, conn_buf, 4096, &conn_buf_posn);
index 632c55f6d5d66c8e39e213284e163a23b32c2260..89835093fbadf3c55cdf42f7490c6bac98ef2553 100644 (file)
@@ -11,6 +11,7 @@ TAR ?= $(TAR)
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 
 T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+TSVN = $(wildcard t91[0-9][0-9]-*.sh)
 
 ifdef NO_PYTHON
        GIT_TEST_OPTS += --no-python
@@ -24,6 +25,15 @@ $(T):
 clean:
        rm -fr trash
 
+# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
+full-svn-test:
+       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
+       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
+       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
+                                                       LC_ALL=en_US.UTF-8
+       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
+                                                       LC_ALL=en_US.UTF-8
+
 .PHONY: $(T) clean
 .NOTPARALLEL:
 
index 1148b0257dafd4e9b63e030052b47b527b5c0d3b..b6a2edd8878c910ad177b8b277bedf99f60d9416 100644 (file)
@@ -94,7 +94,7 @@ test_expect_success \
 test_expect_success \
     'merge-setup part 4' \
     'echo "evil merge." >>file &&
-     EDITOR=: VISUAL=: git commit -a --amend'
+     git commit -a --amend'
 
 test_expect_success \
     'Two lines blamed on A, one on B, two on B1, one on B2, one on A U Thor' \
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
new file mode 100644 (file)
index 0000000..29a1e72
--- /dev/null
@@ -0,0 +1,50 @@
+. ./test-lib.sh
+
+if test -n "$NO_SVN_TESTS"
+then
+       test_expect_success 'skipping git-svn tests, NO_SVN_TESTS defined' :
+       test_done
+       exit
+fi
+
+GIT_DIR=$PWD/.git
+GIT_SVN_DIR=$GIT_DIR/svn/git-svn
+SVN_TREE=$GIT_SVN_DIR/svn-tree
+
+perl -e 'use SVN::Core' >/dev/null 2>&1
+if test $? -ne 0
+then
+   echo 'Perl SVN libraries not found, tests requiring those will be skipped'
+   GIT_SVN_NO_LIB=1
+fi
+
+svnadmin >/dev/null 2>&1
+if test $? -ne 1
+then
+    test_expect_success 'skipping git-svn tests, svnadmin not found' :
+    test_done
+    exit
+fi
+
+svn >/dev/null 2>&1
+if test $? -ne 1
+then
+    test_expect_success 'skipping git-svn tests, svn not found' :
+    test_done
+    exit
+fi
+
+svnrepo=$PWD/svnrepo
+
+set -e
+
+if svnadmin create --help | grep fs-type >/dev/null
+then
+       svnadmin create --fs-type fsfs "$svnrepo"
+else
+       svnadmin create "$svnrepo"
+fi
+
+svnrepo="file://$svnrepo/test-git-svn"
+
+
index df3e9933658595c2690d4b83e3d5adb2289b887f..04fab26621af3aee3a44913ab290a4893b4f23ca 100755 (executable)
@@ -188,17 +188,29 @@ test_expect_success \
         echo OTHER >F &&
         GIT_AUTHOR_DATE="2005-05-26 23:41" \
         GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
-        h_OTHER=$(git-rev-parse --verify HEAD)
+        h_OTHER=$(git-rev-parse --verify HEAD) &&
+        echo FIXED >F &&
+        GIT_AUTHOR_DATE="2005-05-26 23:44" \
+        GIT_COMMITTER_DATE="2005-05-26 23:44" git-commit --amend &&
+        h_FIXED=$(git-rev-parse --verify HEAD) &&
+        echo TEST+FIXED >F &&
+        echo Merged initial commit and a later commit. >M &&
+        echo $h_TEST >.git/MERGE_HEAD &&
+        GIT_AUTHOR_DATE="2005-05-26 23:45" \
+        GIT_COMMITTER_DATE="2005-05-26 23:45" git-commit -F M &&
+        h_MERGED=$(git-rev-parse --verify HEAD)
         rm -f M'
 
 cat >expect <<EOF
-$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit: add
+$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit (initial): add
 $h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000   commit: The other day this did not work.
+$h_OTHER $h_FIXED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151040 +0000  commit (amend): The other day this did not work.
+$h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 commit (merge): Merged initial commit and a later commit.
 EOF
 test_expect_success \
        'git-commit logged updates' \
        'diff expect .git/logs/$m'
-unset h_TEST h_OTHER
+unset h_TEST h_OTHER h_FIXED h_MERGED
 
 test_expect_success \
        'git-cat-file blob master:F (expect OTHER)' \
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
new file mode 100755 (executable)
index 0000000..b24c829
--- /dev/null
@@ -0,0 +1,250 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Various diff formatting options'
+
+. ./test-lib.sh
+
+LF='
+'
+
+test_expect_success setup '
+
+       GIT_AUTHOR_DATE="2006-06-26 00:00:00 +0000" &&
+       GIT_COMMITTER_DATE="2006-06-26 00:00:00 +0000" &&
+       export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+       mkdir dir &&
+       for i in 1 2 3; do echo $i; done >file0 &&
+       for i in A B; do echo $i; done >dir/sub &&
+       cat file0 >file2 &&
+       git add file0 file2 dir/sub &&
+       git commit -m Initial &&
+
+       git branch initial &&
+       git branch side &&
+
+       GIT_AUTHOR_DATE="2006-06-26 00:01:00 +0000" &&
+       GIT_COMMITTER_DATE="2006-06-26 00:01:00 +0000" &&
+       export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+       for i in 4 5 6; do echo $i; done >>file0 &&
+       for i in C D; do echo $i; done >>dir/sub &&
+       rm -f file2 &&
+       git update-index --remove file0 file2 dir/sub &&
+       git commit -m "Second${LF}${LF}This is the second commit." &&
+
+       GIT_AUTHOR_DATE="2006-06-26 00:02:00 +0000" &&
+       GIT_COMMITTER_DATE="2006-06-26 00:02:00 +0000" &&
+       export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+       for i in A B C; do echo $i; done >file1 &&
+       git add file1 &&
+       for i in E F; do echo $i; done >>dir/sub &&
+       git update-index dir/sub &&
+       git commit -m Third &&
+
+       GIT_AUTHOR_DATE="2006-06-26 00:03:00 +0000" &&
+       GIT_COMMITTER_DATE="2006-06-26 00:03:00 +0000" &&
+       export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+       git checkout side &&
+       for i in A B C; do echo $i; done >>file0 &&
+       for i in 1 2; do echo $i; done >>dir/sub &&
+       cat dir/sub >file3 &&
+       git add file3 &&
+       git update-index file0 dir/sub &&
+       git commit -m Side &&
+
+       GIT_AUTHOR_DATE="2006-06-26 00:04:00 +0000" &&
+       GIT_COMMITTER_DATE="2006-06-26 00:04:00 +0000" &&
+       export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+       git checkout master &&
+       git pull -s ours . side &&
+
+       GIT_AUTHOR_DATE="2006-06-26 00:05:00 +0000" &&
+       GIT_COMMITTER_DATE="2006-06-26 00:05:00 +0000" &&
+       export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+       for i in A B C; do echo $i; done >>file0 &&
+       for i in 1 2; do echo $i; done >>dir/sub &&
+       git update-index file0 dir/sub &&
+
+       git commit --amend &&
+       git show-branch
+'
+
+: <<\EOF
+! [initial] Initial
+ * [master] Merge branch 'side'
+  ! [side] Side
+---
+ -  [master] Merge branch 'side'
+ *+ [side] Side
+ *  [master^] Second
++*+ [initial] Initial
+EOF
+
+V=`git version | sed -e 's/^git version //'`
+while read cmd
+do
+       case "$cmd" in
+       '' | '#'*) continue ;;
+       esac
+       test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'`
+       cnt=`expr $test_count + 1`
+       pfx=`printf "%04d" $cnt`
+       expect="../t4013/diff.$test"
+       actual="$pfx-diff.$test"
+
+       test_expect_success "git $cmd" '
+               {
+                       echo "\$ git $cmd"
+                       git $cmd | sed -e "s/$V/g-i-t--v-e-r-s-i-o-n/"
+                       echo "\$"
+               } >"$actual" &&
+               if test -f "$expect"
+               then
+                       diff -u "$expect" "$actual" &&
+                       rm -f "$actual"
+               else
+                       # this is to help developing new tests.
+                       cp "$actual" "$expect"
+                       false
+               fi
+       '
+done <<\EOF
+diff-tree initial
+diff-tree -r initial
+diff-tree -r --abbrev initial
+diff-tree -r --abbrev=4 initial
+diff-tree --root initial
+diff-tree --root --abbrev initial
+diff-tree --root -r initial
+diff-tree --root -r --abbrev initial
+diff-tree --root -r --abbrev=4 initial
+diff-tree -p initial
+diff-tree --root -p initial
+diff-tree --patch-with-stat initial
+diff-tree --root --patch-with-stat initial
+diff-tree --patch-with-raw initial
+diff-tree --root --patch-with-raw initial
+
+diff-tree --pretty initial
+diff-tree --pretty --root initial
+diff-tree --pretty -p initial
+diff-tree --pretty --stat initial
+diff-tree --pretty --summary initial
+diff-tree --pretty --stat --summary initial
+diff-tree --pretty --root -p initial
+diff-tree --pretty --root --stat initial
+# improved by Timo's patch
+diff-tree --pretty --root --summary initial
+# improved by Timo's patch
+diff-tree --pretty --root --summary -r initial
+diff-tree --pretty --root --stat --summary initial
+diff-tree --pretty --patch-with-stat initial
+diff-tree --pretty --root --patch-with-stat initial
+diff-tree --pretty --patch-with-raw initial
+diff-tree --pretty --root --patch-with-raw initial
+
+diff-tree --pretty=oneline initial
+diff-tree --pretty=oneline --root initial
+diff-tree --pretty=oneline -p initial
+diff-tree --pretty=oneline --root -p initial
+diff-tree --pretty=oneline --patch-with-stat initial
+# improved by Timo's patch
+diff-tree --pretty=oneline --root --patch-with-stat initial
+diff-tree --pretty=oneline --patch-with-raw initial
+diff-tree --pretty=oneline --root --patch-with-raw initial
+
+diff-tree --pretty side
+diff-tree --pretty -p side
+diff-tree --pretty --patch-with-stat side
+
+diff-tree master
+diff-tree -p master
+diff-tree -p -m master
+diff-tree -c master
+diff-tree -c --abbrev master
+diff-tree --cc master
+# stat only should show the diffstat with the first parent
+diff-tree -c --stat master
+diff-tree --cc --stat master
+diff-tree -c --stat --summary master
+diff-tree --cc --stat --summary master
+# stat summary should show the diffstat and summary with the first parent
+diff-tree -c --stat --summary side
+diff-tree --cc --stat --summary side
+# improved by Timo's patch
+diff-tree --cc --patch-with-stat master
+# improved by Timo's patch
+diff-tree --cc --patch-with-stat --summary master
+# this is correct
+diff-tree --cc --patch-with-stat --summary side
+
+log master
+log -p master
+log --root master
+log --root -p master
+log --patch-with-stat master
+log --root --patch-with-stat master
+log --root --patch-with-stat --summary master
+# improved by Timo's patch
+log --root -c --patch-with-stat --summary master
+# improved by Timo's patch
+log --root --cc --patch-with-stat --summary master
+log -SF master
+log -SF -p master
+
+whatchanged master
+whatchanged -p master
+whatchanged --root master
+whatchanged --root -p master
+whatchanged --patch-with-stat master
+whatchanged --root --patch-with-stat master
+whatchanged --root --patch-with-stat --summary master
+# improved by Timo's patch
+whatchanged --root -c --patch-with-stat --summary master
+# improved by Timo's patch
+whatchanged --root --cc --patch-with-stat --summary master
+whatchanged -SF master
+whatchanged -SF -p master
+
+log --patch-with-stat master -- dir/
+whatchanged --patch-with-stat master -- dir/
+log --patch-with-stat --summary master -- dir/
+whatchanged --patch-with-stat --summary master -- dir/
+
+show initial
+show --root initial
+show side
+show master
+show --stat side
+show --stat --summary side
+show --patch-with-stat side
+show --patch-with-raw side
+show --patch-with-stat --summary side
+
+format-patch --stdout initial..side
+format-patch --stdout initial..master^
+format-patch --stdout initial..master
+format-patch --attach --stdout initial..side
+format-patch --attach --stdout initial..master^
+format-patch --attach --stdout initial..master
+
+diff --abbrev initial..side
+diff -r initial..side
+diff --stat initial..side
+diff -r --stat initial..side
+diff initial..side
+diff --patch-with-stat initial..side
+diff --patch-with-raw initial..side
+diff --patch-with-stat -r initial..side
+diff --patch-with-raw -r initial..side
+EOF
+
+test_done
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
new file mode 100644 (file)
index 0000000..3a9f78a
--- /dev/null
@@ -0,0 +1,34 @@
+$ git diff-tree --cc --patch-with-stat --summary master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side
new file mode 100644 (file)
index 0000000..a61ad8c
--- /dev/null
@@ -0,0 +1,39 @@
+$ git diff-tree --cc --patch-with-stat --summary side
+c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_master b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_master
new file mode 100644 (file)
index 0000000..49f23b9
--- /dev/null
@@ -0,0 +1,34 @@
+$ git diff-tree --cc --patch-with-stat master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_--summary_master b/t/t4013/diff.diff-tree_--cc_--stat_--summary_master
new file mode 100644 (file)
index 0000000..cc6eb3b
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --cc --stat --summary master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_--summary_side b/t/t4013/diff.diff-tree_--cc_--stat_--summary_side
new file mode 100644 (file)
index 0000000..50362be
--- /dev/null
@@ -0,0 +1,8 @@
+$ git diff-tree --cc --stat --summary side
+c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+$
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_master b/t/t4013/diff.diff-tree_--cc_--stat_master
new file mode 100644 (file)
index 0000000..fae7f33
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --cc --stat master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_--cc_master b/t/t4013/diff.diff-tree_--cc_master
new file mode 100644 (file)
index 0000000..5ecb4e1
--- /dev/null
@@ -0,0 +1,30 @@
+$ git diff-tree --cc master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.diff-tree_--patch-with-raw_initial b/t/t4013/diff.diff-tree_--patch-with-raw_initial
new file mode 100644 (file)
index 0000000..fc177ab
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --patch-with-raw initial
+$
diff --git a/t/t4013/diff.diff-tree_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--patch-with-stat_initial
new file mode 100644 (file)
index 0000000..bd905b1
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --patch-with-stat initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_--patch-with-raw_initial b/t/t4013/diff.diff-tree_--pretty=oneline_--patch-with-raw_initial
new file mode 100644 (file)
index 0000000..7bb8b45
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty=oneline --patch-with-raw initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--pretty=oneline_--patch-with-stat_initial
new file mode 100644 (file)
index 0000000..cbdde4f
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty=oneline --patch-with-stat initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-raw_initial b/t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-raw_initial
new file mode 100644 (file)
index 0000000..cd79f1a
--- /dev/null
@@ -0,0 +1,33 @@
+$ git diff-tree --pretty=oneline --root --patch-with-raw initial
+444ac553ac7612cc88969031b02b3767fb8a353a Initial
+:000000 100644 0000000000000000000000000000000000000000 35d242ba79ae89ac695e26b3d4c27a8e6f028f9e A     dir/sub
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file0
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial
new file mode 100644 (file)
index 0000000..d5c333a
--- /dev/null
@@ -0,0 +1,34 @@
+$ git diff-tree --pretty=oneline --root --patch-with-stat initial
+444ac553ac7612cc88969031b02b3767fb8a353a Initial
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_--root_-p_initial b/t/t4013/diff.diff-tree_--pretty=oneline_--root_-p_initial
new file mode 100644 (file)
index 0000000..3c5092c
--- /dev/null
@@ -0,0 +1,29 @@
+$ git diff-tree --pretty=oneline --root -p initial
+444ac553ac7612cc88969031b02b3767fb8a353a Initial
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_--root_initial b/t/t4013/diff.diff-tree_--pretty=oneline_--root_initial
new file mode 100644 (file)
index 0000000..08920ac
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --pretty=oneline --root initial
+444ac553ac7612cc88969031b02b3767fb8a353a Initial
+:000000 040000 0000000000000000000000000000000000000000 da7a33fa77d8066d6698643940ce5860fe2d7fb3 A     dir
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file0
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_-p_initial b/t/t4013/diff.diff-tree_--pretty=oneline_-p_initial
new file mode 100644 (file)
index 0000000..94b76bf
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty=oneline -p initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty=oneline_initial b/t/t4013/diff.diff-tree_--pretty=oneline_initial
new file mode 100644 (file)
index 0000000..d50970d
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty=oneline initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--patch-with-raw_initial b/t/t4013/diff.diff-tree_--pretty_--patch-with-raw_initial
new file mode 100644 (file)
index 0000000..3a85316
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty --patch-with-raw initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--pretty_--patch-with-stat_initial
new file mode 100644 (file)
index 0000000..2e08239
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty --patch-with-stat initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side b/t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side
new file mode 100644 (file)
index 0000000..4d30e7e
--- /dev/null
@@ -0,0 +1,43 @@
+$ git diff-tree --pretty --patch-with-stat side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--patch-with-raw_initial b/t/t4013/diff.diff-tree_--pretty_--root_--patch-with-raw_initial
new file mode 100644 (file)
index 0000000..a3203bd
--- /dev/null
@@ -0,0 +1,38 @@
+$ git diff-tree --pretty --root --patch-with-raw initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+:000000 100644 0000000000000000000000000000000000000000 35d242ba79ae89ac695e26b3d4c27a8e6f028f9e A     dir/sub
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file0
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial
new file mode 100644 (file)
index 0000000..7dfa6af
--- /dev/null
@@ -0,0 +1,39 @@
+$ git diff-tree --pretty --root --patch-with-stat initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial
new file mode 100644 (file)
index 0000000..43bfce2
--- /dev/null
@@ -0,0 +1,15 @@
+$ git diff-tree --pretty --root --stat --summary initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--stat_initial b/t/t4013/diff.diff-tree_--pretty_--root_--stat_initial
new file mode 100644 (file)
index 0000000..9154aa4
--- /dev/null
@@ -0,0 +1,12 @@
+$ git diff-tree --pretty --root --stat initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--summary_-r_initial b/t/t4013/diff.diff-tree_--pretty_--root_--summary_-r_initial
new file mode 100644 (file)
index 0000000..ccdaafb
--- /dev/null
@@ -0,0 +1,11 @@
+$ git diff-tree --pretty --root --summary -r initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial
new file mode 100644 (file)
index 0000000..ea48205
--- /dev/null
@@ -0,0 +1,11 @@
+$ git diff-tree --pretty --root --summary initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+ create mode 040000 dir
+ create mode 100644 file0
+ create mode 100644 file2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_-p_initial b/t/t4013/diff.diff-tree_--pretty_--root_-p_initial
new file mode 100644 (file)
index 0000000..d0411f6
--- /dev/null
@@ -0,0 +1,34 @@
+$ git diff-tree --pretty --root -p initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_initial b/t/t4013/diff.diff-tree_--pretty_--root_initial
new file mode 100644 (file)
index 0000000..94e32ea
--- /dev/null
@@ -0,0 +1,11 @@
+$ git diff-tree --pretty --root initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+:000000 040000 0000000000000000000000000000000000000000 da7a33fa77d8066d6698643940ce5860fe2d7fb3 A     dir
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file0
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--stat_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--stat_--summary_initial
new file mode 100644 (file)
index 0000000..c22983a
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty --stat --summary initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--stat_initial b/t/t4013/diff.diff-tree_--pretty_--stat_initial
new file mode 100644 (file)
index 0000000..8fdcfb4
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty --stat initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--summary_initial
new file mode 100644 (file)
index 0000000..9bc2c4f
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty --summary initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_-p_initial b/t/t4013/diff.diff-tree_--pretty_-p_initial
new file mode 100644 (file)
index 0000000..3c9942f
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty -p initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_-p_side b/t/t4013/diff.diff-tree_--pretty_-p_side
new file mode 100644 (file)
index 0000000..b993aa7
--- /dev/null
@@ -0,0 +1,38 @@
+$ git diff-tree --pretty -p side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_initial b/t/t4013/diff.diff-tree_--pretty_initial
new file mode 100644 (file)
index 0000000..14715bf
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree --pretty initial
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_side b/t/t4013/diff.diff-tree_--pretty_side
new file mode 100644 (file)
index 0000000..e9b6e1c
--- /dev/null
@@ -0,0 +1,11 @@
+$ git diff-tree --pretty side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:040000 040000 da7a33fa77d8066d6698643940ce5860fe2d7fb3 f977ed46ae6873c1c30ab878e15a4accedc3618b M     dir
+:100644 100644 01e79c32a8c99c557f0757da7cb6d65b3414466d f4615da674c09df322d6ba8d6b21ecfb1b1ba510 M     file0
+:000000 100644 0000000000000000000000000000000000000000 7289e35bff32727c08dda207511bec138fdb9ea5 A     file3
+$
diff --git a/t/t4013/diff.diff-tree_--root_--abbrev_initial b/t/t4013/diff.diff-tree_--root_--abbrev_initial
new file mode 100644 (file)
index 0000000..5aa84b2
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root --abbrev initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 040000 0000000... da7a33f... A dir
+:000000 100644 0000000... 01e79c3... A file0
+:000000 100644 0000000... 01e79c3... A file2
+$
diff --git a/t/t4013/diff.diff-tree_--root_--patch-with-raw_initial b/t/t4013/diff.diff-tree_--root_--patch-with-raw_initial
new file mode 100644 (file)
index 0000000..d295e47
--- /dev/null
@@ -0,0 +1,33 @@
+$ git diff-tree --root --patch-with-raw initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 100644 0000000000000000000000000000000000000000 35d242ba79ae89ac695e26b3d4c27a8e6f028f9e A     dir/sub
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file0
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--root_--patch-with-stat_initial b/t/t4013/diff.diff-tree_--root_--patch-with-stat_initial
new file mode 100644 (file)
index 0000000..1562b62
--- /dev/null
@@ -0,0 +1,34 @@
+$ git diff-tree --root --patch-with-stat initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--root_-p_initial b/t/t4013/diff.diff-tree_--root_-p_initial
new file mode 100644 (file)
index 0000000..3219c72
--- /dev/null
@@ -0,0 +1,29 @@
+$ git diff-tree --root -p initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.diff-tree_--root_-r_--abbrev=4_initial b/t/t4013/diff.diff-tree_--root_-r_--abbrev=4_initial
new file mode 100644 (file)
index 0000000..0c53616
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root -r --abbrev=4 initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 100644 0000... 35d2... A       dir/sub
+:000000 100644 0000... 01e7... A       file0
+:000000 100644 0000... 01e7... A       file2
+$
diff --git a/t/t4013/diff.diff-tree_--root_-r_--abbrev_initial b/t/t4013/diff.diff-tree_--root_-r_--abbrev_initial
new file mode 100644 (file)
index 0000000..c7b460f
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root -r --abbrev initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 100644 0000000... 35d242b... A dir/sub
+:000000 100644 0000000... 01e79c3... A file0
+:000000 100644 0000000... 01e79c3... A file2
+$
diff --git a/t/t4013/diff.diff-tree_--root_-r_initial b/t/t4013/diff.diff-tree_--root_-r_initial
new file mode 100644 (file)
index 0000000..eed435e
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root -r initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 100644 0000000000000000000000000000000000000000 35d242ba79ae89ac695e26b3d4c27a8e6f028f9e A     dir/sub
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file0
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file2
+$
diff --git a/t/t4013/diff.diff-tree_--root_initial b/t/t4013/diff.diff-tree_--root_initial
new file mode 100644 (file)
index 0000000..ddf6b06
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --root initial
+444ac553ac7612cc88969031b02b3767fb8a353a
+:000000 040000 0000000000000000000000000000000000000000 da7a33fa77d8066d6698643940ce5860fe2d7fb3 A     dir
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file0
+:000000 100644 0000000000000000000000000000000000000000 01e79c32a8c99c557f0757da7cb6d65b3414466d A     file2
+$
diff --git a/t/t4013/diff.diff-tree_-c_--abbrev_master b/t/t4013/diff.diff-tree_-c_--abbrev_master
new file mode 100644 (file)
index 0000000..b8e4aa2
--- /dev/null
@@ -0,0 +1,5 @@
+$ git diff-tree -c --abbrev master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+::100644 100644 100644 cead32e... 7289e35... 992913c... MM     dir/sub
+::100644 100644 100644 b414108... f4615da... 10a8a9f... MM     file0
+$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_--summary_master b/t/t4013/diff.diff-tree_-c_--stat_--summary_master
new file mode 100644 (file)
index 0000000..ac9f641
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree -c --stat --summary master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_--summary_side b/t/t4013/diff.diff-tree_-c_--stat_--summary_side
new file mode 100644 (file)
index 0000000..2afcca1
--- /dev/null
@@ -0,0 +1,8 @@
+$ git diff-tree -c --stat --summary side
+c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_master b/t/t4013/diff.diff-tree_-c_--stat_master
new file mode 100644 (file)
index 0000000..c2fe6a9
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree -c --stat master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_-c_master b/t/t4013/diff.diff-tree_-c_master
new file mode 100644 (file)
index 0000000..e2d2bb2
--- /dev/null
@@ -0,0 +1,5 @@
+$ git diff-tree -c master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+::100644 100644 100644 cead32e925b1420c84c14cbf7cf755e7e45af8ad 7289e35bff32727c08dda207511bec138fdb9ea5 992913c5aa0a5476d10c49ed0f21fc0c6d1aedf3 MM   dir/sub
+::100644 100644 100644 b414108e81e5091fe0974a1858b4d0d22b107f70 f4615da674c09df322d6ba8d6b21ecfb1b1ba510 10a8a9f3657f91a156b9f0184ed79a20adef9f7f MM   file0
+$
diff --git a/t/t4013/diff.diff-tree_-p_-m_master b/t/t4013/diff.diff-tree_-p_-m_master
new file mode 100644 (file)
index 0000000..b60bea0
--- /dev/null
@@ -0,0 +1,80 @@
+$ git diff-tree -p -m master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+diff --git a/dir/sub b/dir/sub
+index 7289e35..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,4 +1,8 @@
+ A
+ B
++C
++D
++E
++F
+ 1
+ 2
+diff --git a/file0 b/file0
+index f4615da..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -1,6 +1,9 @@
+ 1
+ 2
+ 3
++4
++5
++6
+ A
+ B
+ C
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+diff --git a/file3 b/file3
+deleted file mode 100644
+index 7289e35..0000000
+--- a/file3
++++ /dev/null
+@@ -1,4 +0,0 @@
+-A
+-B
+-1
+-2
+$
diff --git a/t/t4013/diff.diff-tree_-p_initial b/t/t4013/diff.diff-tree_-p_initial
new file mode 100644 (file)
index 0000000..e20ce88
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree -p initial
+$
diff --git a/t/t4013/diff.diff-tree_-p_master b/t/t4013/diff.diff-tree_-p_master
new file mode 100644 (file)
index 0000000..b182875
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree -p master
+$
diff --git a/t/t4013/diff.diff-tree_-r_--abbrev=4_initial b/t/t4013/diff.diff-tree_-r_--abbrev=4_initial
new file mode 100644 (file)
index 0000000..c5a3aa5
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree -r --abbrev=4 initial
+$
diff --git a/t/t4013/diff.diff-tree_-r_--abbrev_initial b/t/t4013/diff.diff-tree_-r_--abbrev_initial
new file mode 100644 (file)
index 0000000..0b689b7
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree -r --abbrev initial
+$
diff --git a/t/t4013/diff.diff-tree_-r_initial b/t/t4013/diff.diff-tree_-r_initial
new file mode 100644 (file)
index 0000000..1765d83
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree -r initial
+$
diff --git a/t/t4013/diff.diff-tree_initial b/t/t4013/diff.diff-tree_initial
new file mode 100644 (file)
index 0000000..b49fc53
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree initial
+$
diff --git a/t/t4013/diff.diff-tree_master b/t/t4013/diff.diff-tree_master
new file mode 100644 (file)
index 0000000..fe9226f
--- /dev/null
@@ -0,0 +1,2 @@
+$ git diff-tree master
+$
diff --git a/t/t4013/diff.diff_--abbrev_initial..side b/t/t4013/diff.diff_--abbrev_initial..side
new file mode 100644 (file)
index 0000000..a88e66f
--- /dev/null
@@ -0,0 +1,32 @@
+$ git diff --abbrev initial..side
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff_--patch-with-raw_-r_initial..side b/t/t4013/diff.diff_--patch-with-raw_-r_initial..side
new file mode 100644 (file)
index 0000000..3590dc7
--- /dev/null
@@ -0,0 +1,36 @@
+$ git diff --patch-with-raw -r initial..side
+:100644 100644 35d242b... 7289e35... M dir/sub
+:100644 100644 01e79c3... f4615da... M file0
+:000000 100644 0000000... 7289e35... A file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff_--patch-with-raw_initial..side b/t/t4013/diff.diff_--patch-with-raw_initial..side
new file mode 100644 (file)
index 0000000..b21d5dc
--- /dev/null
@@ -0,0 +1,36 @@
+$ git diff --patch-with-raw initial..side
+:100644 100644 35d242b... 7289e35... M dir/sub
+:100644 100644 01e79c3... f4615da... M file0
+:000000 100644 0000000... 7289e35... A file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff_--patch-with-stat_-r_initial..side b/t/t4013/diff.diff_--patch-with-stat_-r_initial..side
new file mode 100644 (file)
index 0000000..9ed317a
--- /dev/null
@@ -0,0 +1,37 @@
+$ git diff --patch-with-stat -r initial..side
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff_--patch-with-stat_initial..side b/t/t4013/diff.diff_--patch-with-stat_initial..side
new file mode 100644 (file)
index 0000000..8b50629
--- /dev/null
@@ -0,0 +1,37 @@
+$ git diff --patch-with-stat initial..side
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff_--stat_initial..side b/t/t4013/diff.diff_--stat_initial..side
new file mode 100644 (file)
index 0000000..0517b5d
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff --stat initial..side
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff_-r_--stat_initial..side b/t/t4013/diff.diff_-r_--stat_initial..side
new file mode 100644 (file)
index 0000000..245220d
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff -r --stat initial..side
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff_-r_initial..side b/t/t4013/diff.diff_-r_initial..side
new file mode 100644 (file)
index 0000000..5bb2fe2
--- /dev/null
@@ -0,0 +1,32 @@
+$ git diff -r initial..side
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.diff_initial..side b/t/t4013/diff.diff_initial..side
new file mode 100644 (file)
index 0000000..c8adaf5
--- /dev/null
@@ -0,0 +1,32 @@
+$ git diff initial..side
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master b/t/t4013/diff.format-patch_--attach_--stdout_initial..master
new file mode 100644 (file)
index 0000000..b4745e1
--- /dev/null
@@ -0,0 +1,170 @@
+$ git format-patch --attach --stdout initial..master
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
new file mode 100644 (file)
index 0000000..a9d1cd3
--- /dev/null
@@ -0,0 +1,110 @@
+$ git format-patch --attach --stdout initial..master^
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..side b/t/t4013/diff.format-patch_--attach_--stdout_initial..side
new file mode 100644 (file)
index 0000000..57b9d0b
--- /dev/null
@@ -0,0 +1,61 @@
+$ git format-patch --attach --stdout initial..side
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4013/diff.format-patch_--stdout_initial..master b/t/t4013/diff.format-patch_--stdout_initial..master
new file mode 100644 (file)
index 0000000..c33302e
--- /dev/null
@@ -0,0 +1,124 @@
+$ git format-patch --stdout initial..master
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+$
diff --git a/t/t4013/diff.format-patch_--stdout_initial..master^ b/t/t4013/diff.format-patch_--stdout_initial..master^
new file mode 100644 (file)
index 0000000..03d0f96
--- /dev/null
@@ -0,0 +1,79 @@
+$ git format-patch --stdout initial..master^
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+$
diff --git a/t/t4013/diff.format-patch_--stdout_initial..side b/t/t4013/diff.format-patch_--stdout_initial..side
new file mode 100644 (file)
index 0000000..d10a465
--- /dev/null
@@ -0,0 +1,46 @@
+$ git format-patch --stdout initial..side
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+-- 
+g-i-t--v-e-r-s-i-o-n
+
+$
diff --git a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
new file mode 100644 (file)
index 0000000..3ceb8e7
--- /dev/null
@@ -0,0 +1,74 @@
+$ git log --patch-with-stat --summary master -- dir/
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.log_--patch-with-stat_master b/t/t4013/diff.log_--patch-with-stat_master
new file mode 100644 (file)
index 0000000..43d7776
--- /dev/null
@@ -0,0 +1,129 @@
+$ git log --patch-with-stat master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_
new file mode 100644 (file)
index 0000000..5187a26
--- /dev/null
@@ -0,0 +1,74 @@
+$ git log --patch-with-stat master -- dir/
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
new file mode 100644 (file)
index 0000000..c964097
--- /dev/null
@@ -0,0 +1,199 @@
+$ git log --root --cc --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master
new file mode 100644 (file)
index 0000000..ad050af
--- /dev/null
@@ -0,0 +1,167 @@
+$ git log --root --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_master b/t/t4013/diff.log_--root_--patch-with-stat_master
new file mode 100644 (file)
index 0000000..628c6c0
--- /dev/null
@@ -0,0 +1,161 @@
+$ git log --root --patch-with-stat master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
new file mode 100644 (file)
index 0000000..5d4e0f1
--- /dev/null
@@ -0,0 +1,199 @@
+$ git log --root -c --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --combined dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --combined file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_-p_master b/t/t4013/diff.log_--root_-p_master
new file mode 100644 (file)
index 0000000..217a2eb
--- /dev/null
@@ -0,0 +1,142 @@
+$ git log --root -p master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_master b/t/t4013/diff.log_--root_master
new file mode 100644 (file)
index 0000000..e17ccfc
--- /dev/null
@@ -0,0 +1,34 @@
+$ git log --root master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.log_-SF_-p_master b/t/t4013/diff.log_-SF_-p_master
new file mode 100644 (file)
index 0000000..5e32438
--- /dev/null
@@ -0,0 +1,18 @@
+$ git log -SF -p master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+$
diff --git a/t/t4013/diff.log_-SF_master b/t/t4013/diff.log_-SF_master
new file mode 100644 (file)
index 0000000..6162ed2
--- /dev/null
@@ -0,0 +1,8 @@
+$ git log -SF master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+$
diff --git a/t/t4013/diff.log_-p_master b/t/t4013/diff.log_-p_master
new file mode 100644 (file)
index 0000000..f8fefef
--- /dev/null
@@ -0,0 +1,115 @@
+$ git log -p master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.log_master b/t/t4013/diff.log_master
new file mode 100644 (file)
index 0000000..e9d9e7b
--- /dev/null
@@ -0,0 +1,34 @@
+$ git log master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.show_--patch-with-raw_side b/t/t4013/diff.show_--patch-with-raw_side
new file mode 100644 (file)
index 0000000..221b46a
--- /dev/null
@@ -0,0 +1,42 @@
+$ git show --patch-with-raw side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b... 7289e35... M dir/sub
+:100644 100644 01e79c3... f4615da... M file0
+:000000 100644 0000000... 7289e35... A file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.show_--patch-with-stat_--summary_side b/t/t4013/diff.show_--patch-with-stat_--summary_side
new file mode 100644 (file)
index 0000000..377f2b7
--- /dev/null
@@ -0,0 +1,44 @@
+$ git show --patch-with-stat --summary side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.show_--patch-with-stat_side b/t/t4013/diff.show_--patch-with-stat_side
new file mode 100644 (file)
index 0000000..fb14c53
--- /dev/null
@@ -0,0 +1,43 @@
+$ git show --patch-with-stat side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.show_--root_initial b/t/t4013/diff.show_--root_initial
new file mode 100644 (file)
index 0000000..8c89136
--- /dev/null
@@ -0,0 +1,34 @@
+$ git show --root initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.show_--stat_--summary_side b/t/t4013/diff.show_--stat_--summary_side
new file mode 100644 (file)
index 0000000..5bd5977
--- /dev/null
@@ -0,0 +1,13 @@
+$ git show --stat --summary side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+$
diff --git a/t/t4013/diff.show_--stat_side b/t/t4013/diff.show_--stat_side
new file mode 100644 (file)
index 0000000..3b22327
--- /dev/null
@@ -0,0 +1,12 @@
+$ git show --stat side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.show_initial b/t/t4013/diff.show_initial
new file mode 100644 (file)
index 0000000..4c4066a
--- /dev/null
@@ -0,0 +1,7 @@
+$ git show initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
diff --git a/t/t4013/diff.show_master b/t/t4013/diff.show_master
new file mode 100644 (file)
index 0000000..9e6e1f2
--- /dev/null
@@ -0,0 +1,36 @@
+$ git show master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.show_side b/t/t4013/diff.show_side
new file mode 100644 (file)
index 0000000..530a073
--- /dev/null
@@ -0,0 +1,38 @@
+$ git show side
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+$
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_
new file mode 100644 (file)
index 0000000..6a467cc
--- /dev/null
@@ -0,0 +1,61 @@
+$ git whatchanged --patch-with-stat --summary master -- dir/
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+$
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_master b/t/t4013/diff.whatchanged_--patch-with-stat_master
new file mode 100644 (file)
index 0000000..1e1bbe1
--- /dev/null
@@ -0,0 +1,116 @@
+$ git whatchanged --patch-with-stat master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+$
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_ b/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
new file mode 100644 (file)
index 0000000..13789f1
--- /dev/null
@@ -0,0 +1,61 @@
+$ git whatchanged --patch-with-stat master -- dir/
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+$
diff --git a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
new file mode 100644 (file)
index 0000000..5facf25
--- /dev/null
@@ -0,0 +1,199 @@
+$ git whatchanged --root --cc --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
new file mode 100644 (file)
index 0000000..0291153
--- /dev/null
@@ -0,0 +1,160 @@
+$ git whatchanged --root --patch-with-stat --summary master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_--patch-with-stat_master b/t/t4013/diff.whatchanged_--root_--patch-with-stat_master
new file mode 100644 (file)
index 0000000..9b0349c
--- /dev/null
@@ -0,0 +1,154 @@
+$ git whatchanged --root --patch-with-stat master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
new file mode 100644 (file)
index 0000000..10f6767
--- /dev/null
@@ -0,0 +1,199 @@
+$ git whatchanged --root -c --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+ dir/sub |    2 ++
+ file0   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --combined dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+  A
+  B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --combined file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+  1
+  2
+  3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_-p_master b/t/t4013/diff.whatchanged_--root_-p_master
new file mode 100644 (file)
index 0000000..ebf1f06
--- /dev/null
@@ -0,0 +1,135 @@
+$ git whatchanged --root -p master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_master b/t/t4013/diff.whatchanged_--root_master
new file mode 100644 (file)
index 0000000..a405cb6
--- /dev/null
@@ -0,0 +1,42 @@
+$ git whatchanged --root master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b... 7289e35... M dir/sub
+:100644 100644 01e79c3... f4615da... M file0
+:000000 100644 0000000... 7289e35... A file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40... cead32e... M dir/sub
+:000000 100644 0000000... b1e6722... A file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+:100644 100644 35d242b... 8422d40... M dir/sub
+:100644 100644 01e79c3... b414108... M file0
+:100644 000000 01e79c3... 0000000... D file2
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+
+:000000 100644 0000000... 35d242b... A dir/sub
+:000000 100644 0000000... 01e79c3... A file0
+:000000 100644 0000000... 01e79c3... A file2
+$
diff --git a/t/t4013/diff.whatchanged_-SF_-p_master b/t/t4013/diff.whatchanged_-SF_-p_master
new file mode 100644 (file)
index 0000000..f39da84
--- /dev/null
@@ -0,0 +1,18 @@
+$ git whatchanged -SF -p master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+$
diff --git a/t/t4013/diff.whatchanged_-SF_master b/t/t4013/diff.whatchanged_-SF_master
new file mode 100644 (file)
index 0000000..0499321
--- /dev/null
@@ -0,0 +1,9 @@
+$ git whatchanged -SF master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40... cead32e... M dir/sub
+$
diff --git a/t/t4013/diff.whatchanged_-p_master b/t/t4013/diff.whatchanged_-p_master
new file mode 100644 (file)
index 0000000..f18d432
--- /dev/null
@@ -0,0 +1,102 @@
+$ git whatchanged -p master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+$
diff --git a/t/t4013/diff.whatchanged_master b/t/t4013/diff.whatchanged_master
new file mode 100644 (file)
index 0000000..cd3bcc2
--- /dev/null
@@ -0,0 +1,32 @@
+$ git whatchanged master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+:100644 100644 35d242b... 7289e35... M dir/sub
+:100644 100644 01e79c3... f4615da... M file0
+:000000 100644 0000000... 7289e35... A file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+:100644 100644 8422d40... cead32e... M dir/sub
+:000000 100644 0000000... b1e6722... A file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+:100644 100644 35d242b... 8422d40... M dir/sub
+:100644 100644 01e79c3... b414108... M file0
+:100644 000000 01e79c3... 0000000... D file2
+$
index a06f6956d5c72cae79129fc01a1be2f30121e6fb..69e9603c784b580ff143e4249c72aea79f64f471 100755 (executable)
@@ -11,31 +11,7 @@ test_description='git-apply should not get confused with rename/copy.
 
 # setup
 
-mkdir -p include/arch/x86_64/klibc klibc/arch/x86_64/include/klibc
-
-cat >include/arch/x86_64/klibc/archsetjmp.h <<\EOF
-/*
- * arch/x86_64/include/klibc/archsetjmp.h
- */
-
-#ifndef _KLIBC_ARCHSETJMP_H
-#define _KLIBC_ARCHSETJMP_H
-
-struct __jmp_buf {
-  unsigned long __rbx;
-  unsigned long __rsp;
-  unsigned long __rbp;
-  unsigned long __r12;
-  unsigned long __r13;
-  unsigned long __r14;
-  unsigned long __r15;
-  unsigned long __rip;
-};
-
-typedef struct __jmp_buf jmp_buf[1];
-
-#endif /* _SETJMP_H */
-EOF
+mkdir -p klibc/arch/x86_64/include/klibc
 
 cat >klibc/arch/x86_64/include/klibc/archsetjmp.h <<\EOF
 /*
@@ -139,7 +115,7 @@ rename to include/arch/m32r/klibc/archsetjmp.h
 +#endif /* _KLIBC_ARCHSETJMP_H */
 EOF
 
-find include klibc -type f -print | xargs git-update-index --add --
+find klibc -type f -print | xargs git-update-index --add --
 
 test_expect_success 'check rename/copy patch' 'git-apply --check patch'
 
diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh
new file mode 100755 (executable)
index 0000000..ca81d72
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-apply should not get confused with type changes.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup repository and commits' '
+       echo "hello world" > foo &&
+       echo "hi planet" > bar &&
+       git update-index --add foo bar &&
+       git commit -m initial &&
+       git branch initial &&
+       rm -f foo &&
+       ln -s bar foo &&
+       git update-index foo &&
+       git commit -m "foo symlinked to bar" &&
+       git branch foo-symlinked-to-bar &&
+       rm -f foo &&
+       echo "how far is the sun?" > foo &&
+       git update-index foo &&
+       git commit -m "foo back to file" &&
+       git branch foo-back-to-file &&
+       rm -f foo &&
+       git update-index --remove foo &&
+       mkdir foo &&
+       echo "if only I knew" > foo/baz &&
+       git update-index --add foo/baz &&
+       git commit -m "foo becomes a directory" &&
+       git branch "foo-becomes-a-directory" &&
+       echo "hello world" > foo/baz &&
+       git update-index foo/baz &&
+       git commit -m "foo/baz is the original foo" &&
+       git branch foo-baz-renamed-from-foo
+       '
+
+test_expect_success 'file renamed from foo to foo/baz' '
+       git checkout -f initial &&
+       git diff-tree -M -p HEAD foo-baz-renamed-from-foo > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+
+test_expect_success 'file renamed from foo/baz to foo' '
+       git checkout -f foo-baz-renamed-from-foo &&
+       git diff-tree -M -p HEAD initial > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+
+test_expect_success 'directory becomes file' '
+       git checkout -f foo-becomes-a-directory &&
+       git diff-tree -p HEAD initial > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+
+test_expect_success 'file becomes directory' '
+       git checkout -f initial &&
+       git diff-tree -p HEAD foo-becomes-a-directory > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+
+test_expect_success 'file becomes symlink' '
+       git checkout -f initial &&
+       git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+
+test_expect_success 'symlink becomes file' '
+       git checkout -f foo-symlinked-to-bar &&
+       git diff-tree -p HEAD foo-back-to-file > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+
+test_expect_success 'symlink becomes directory' '
+       git checkout -f foo-symlinked-to-bar &&
+       git diff-tree -p HEAD foo-becomes-a-directory > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+
+test_expect_success 'directory becomes symlink' '
+       git checkout -f foo-becomes-a-directory &&
+       git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
+       git apply --index < patch
+       '
+test_debug 'cat patch'
+
+
+test_done
index 916ee15ba1ed840fbd0df9d91e4f06fae8fde357..dd9caad1c2a34a6721aafa1840916a5a7471ece8 100755 (executable)
@@ -31,7 +31,7 @@ test_expect_success 'cloning with reference' \
 
 cd "$base_dir"
 
-test_expect_success 'existance of info/alternates' \
+test_expect_success 'existence of info/alternates' \
 'test `wc -l <C/.git/objects/info/alternates` = 2'
 
 cd "$base_dir"
index 097d037f5da5aad41872952533bdaef0b1e50d3c..2e1b48a89b1c8a33c62be530bd40fa4d57eb2836 100755 (executable)
@@ -81,7 +81,7 @@ test_valid_repo"
 
 cd "$base_dir"
 
-test_expect_failure 'that info/alternates is neccessary' \
+test_expect_failure 'that info/alternates is necessary' \
 'cd C &&
 rm .git/objects/info/alternates &&
 test_valid_repo'
index 693de9b32dea04140d58ba98d0db07ac0e724a6b..7831e3461c3dd7d332db56e9f5828a27009c9460 100755 (executable)
@@ -154,7 +154,7 @@ test_sequence()
        test_bisection_diff 0 $_bisect_option u5 ^U
        
 #
-# the following illustrate's Linus' binary bug blatt idea. 
+# the following illustrates Linus' binary bug blatt idea.
 #
 # assume the bug is actually at l3, but you don't know that - all you know is that l3 is broken
 # and it wasn't broken before
diff --git a/t/t6004-rev-list-path-optim.sh b/t/t6004-rev-list-path-optim.sh
new file mode 100755 (executable)
index 0000000..5182dbb
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test_description='git-rev-list trivial path optimization test'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+echo Hello > a &&
+git add a &&
+git commit -m "Initial commit" a
+'
+
+test_expect_success path-optimization '
+    commit=$(echo "Unchanged tree" | git-commit-tree "HEAD^{tree}" -p HEAD) &&
+    test $(git-rev-list $commit | wc -l) = 2 &&
+    test $(git-rev-list $commit -- . | wc -l) = 1
+'
+
+test_done
index 1dce123aecd0baa4bbcf122b9a91c478d6e2ef53..b15920b8521fce8c483fa54880de7eb599c87f1c 100755 (executable)
@@ -44,6 +44,43 @@ A=$(doit 1 A $B)
 G=$(doit 7 G $B $E)
 H=$(doit 8 H $A $F)
 
+# Setup for second test to demonstrate that relying on timestamps in a
+# distributed SCM to provide a _consistent_ partial ordering of commits
+# leads to insanity.
+#
+#               Relative
+# Structure     timestamps
+#
+#   PL  PR        +4  +4
+#  /  \/  \      /  \/  \
+# L2  C2  R2    +3  -1  +3
+# |   |   |     |   |   |
+# L1  C1  R1    +2  -2  +2
+# |   |   |     |   |   |
+# L0  C0  R0    +1  -3  +1
+#   \ |  /        \ |  /
+#     S             0
+#
+# The left and right chains of commits can be of any length and complexity as
+# long as all of the timestamps are greater than that of S.
+
+S=$(doit  0 S)
+
+C0=$(doit -3 C0 $S)
+C1=$(doit -2 C1 $C0)
+C2=$(doit -1 C2 $C1)
+
+L0=$(doit  1 L0 $S)
+L1=$(doit  2 L1 $L0)
+L2=$(doit  3 L2 $L1)
+
+R0=$(doit  1 R0 $S)
+R1=$(doit  2 R1 $R0)
+R2=$(doit  3 R2 $R1)
+
+PL=$(doit  4 PL $L2 $C2)
+PR=$(doit  4 PR $C2 $R2)
+
 test_expect_success 'compute merge-base (single)' \
     'MB=$(git-merge-base G H) &&
      expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
@@ -56,4 +93,12 @@ test_expect_success 'compute merge-base with show-branch' \
     'MB=$(git-show-branch --merge-base G H) &&
      expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
 
+test_expect_success 'compute merge-base (single)' \
+    'MB=$(git-merge-base PL PR) &&
+     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+
+test_expect_success 'compute merge-base (all)' \
+    'MB=$(git-merge-base --all PL PR) &&
+     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+
 test_done
index 262381379f247a74f1656cfdd7f9b0c6d386d372..8f7366da8d53feb95d08fd57c04b96c7bcbb9625 100755 (executable)
@@ -4,7 +4,7 @@
 #
 
 # See http://marc.theaimsgroup.com/?l=git&m=111463358500362&w=2 for a
-# nice decription of what this is about.
+# nice description of what this is about.
 
 
 test_description='Test criss-cross merge'
diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh
new file mode 100755 (executable)
index 0000000..63e49f3
--- /dev/null
@@ -0,0 +1,163 @@
+#!/bin/sh
+#
+# Copyright (c) 2006, Junio C Hamano
+#
+
+test_description='fmt-merge-msg test'
+
+. ./test-lib.sh
+
+datestamp=1151939923
+setdate () {
+       GIT_COMMITTER_DATE="$datestamp +0200"
+       GIT_AUTHOR_DATE="$datestamp +0200"
+       datestamp=`expr "$datestamp" + 1`
+       export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
+
+test_expect_success setup '
+       echo one >one &&
+       git add one &&
+       setdate &&
+       git commit -m "Initial" &&
+
+       echo uno >one &&
+       echo dos >two &&
+       git add two &&
+       setdate &&
+       git commit -a -m "Second" &&
+
+       git checkout -b left &&
+
+       echo $datestamp >one &&
+       setdate &&
+       git commit -a -m "Common #1" &&
+
+       echo $datestamp >one &&
+       setdate &&
+       git commit -a -m "Common #2" &&
+
+       git branch right &&
+
+       echo $datestamp >two &&
+       setdate &&
+       git commit -a -m "Left #3" &&
+
+       echo $datestamp >two &&
+       setdate &&
+       git commit -a -m "Left #4" &&
+
+       echo $datestamp >two &&
+       setdate &&
+       git commit -a -m "Left #5" &&
+
+       git checkout right &&
+
+       echo $datestamp >three &&
+       git add three &&
+       setdate &&
+       git commit -a -m "Right #3" &&
+
+       echo $datestamp >three &&
+       setdate &&
+       git commit -a -m "Right #4" &&
+
+       echo $datestamp >three &&
+       setdate &&
+       git commit -a -m "Right #5" &&
+
+       git show-branch
+'
+
+cat >expected <<\EOF
+Merge branch 'left'
+EOF
+
+test_expect_success 'merge-msg test #1' '
+
+       git checkout master &&
+       git fetch . left &&
+
+       git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+       diff -u actual expected
+'
+
+cat >expected <<\EOF
+Merge branch 'left' of ../trash
+EOF
+
+test_expect_success 'merge-msg test #2' '
+
+       git checkout master &&
+       git fetch ../trash left &&
+
+       git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+       diff -u actual expected
+'
+
+cat >expected <<\EOF
+Merge branch 'left'
+
+* left:
+  Left #5
+  Left #4
+  Left #3
+  Common #2
+  Common #1
+EOF
+
+test_expect_success 'merge-msg test #3' '
+
+       git repo-config merge.summary true &&
+
+       git checkout master &&
+       setdate &&
+       git fetch . left &&
+
+       git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+       diff -u actual expected
+'
+
+cat >expected <<\EOF
+Merge branches 'left' and 'right'
+
+* left:
+  Left #5
+  Left #4
+  Left #3
+  Common #2
+  Common #1
+
+* right:
+  Right #5
+  Right #4
+  Right #3
+  Common #2
+  Common #1
+EOF
+
+test_expect_success 'merge-msg test #4' '
+
+       git repo-config merge.summary true &&
+
+       git checkout master &&
+       setdate &&
+       git fetch . left right &&
+
+       git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+       diff -u actual expected
+'
+
+test_expect_success 'merge-msg test #5' '
+
+       git repo-config merge.summary yes &&
+
+       git checkout master &&
+       setdate &&
+       git fetch . left right &&
+
+       git fmt-merge-msg <.git/FETCH_HEAD >actual &&
+       diff -u actual expected
+'
+
+test_done
index 811a4797a58dbc3411f885535b90e6df99f06334..322eaadc730dd022fffe92aba2cba717a3ebe7dc 100755 (executable)
@@ -38,4 +38,40 @@ test_expect_success \
     'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
     grep -E "^R100.+path1/COPYING.+path0/COPYING"'
 
+test_expect_success \
+    'adding another file' \
+    'cp ../../README path0/README &&
+     git-add path0/README &&
+     git-commit -m add2 -a'
+
+test_expect_success \
+    'moving whole subdirectory' \
+    'git-mv path0 path2'
+
+test_expect_success \
+    'commiting the change' \
+    'git-commit -m dir-move -a'
+
+test_expect_success \
+    'checking the commit' \
+    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+     grep -E "^R100.+path0/COPYING.+path2/COPYING" &&
+     git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+     grep -E "^R100.+path0/README.+path2/README"'
+
+test_expect_success \
+    'moving whole subdirectory into subdirectory' \
+    'git-mv path2 path1'
+
+test_expect_success \
+    'commiting the change' \
+    'git-commit -m dir-move -a'
+
+test_expect_success \
+    'checking the commit' \
+    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+     grep -E "^R100.+path2/COPYING.+path1/path2/COPYING" &&
+     git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+     grep -E "^R100.+path2/README.+path1/path2/README"'
+
 test_done
index 2496397da3c335997779bae43ba37ec6d32810de..3a6490e8f864b3b3193a7b86d54a0e8d81d0dd20 100755 (executable)
@@ -6,4 +6,10 @@ test_description='git-annotate'
 PROG='git annotate'
 . ../annotate-tests.sh
 
+test_expect_success \
+    'Annotating an old revision works' \
+    '[ $(git annotate file master | awk "{print \$3}" | grep -c "^A$") -eq 2 ] && \
+     [ $(git annotate file master | awk "{print \$3}" | grep -c "^B$") -eq 2 ]'
+
+
 test_done
index a61da1efbdb6ac547c1d1167a4caa849b1ee4f9b..e9ea33c18d8e0ffa2612e52748bbab4bf13ef513 100755 (executable)
@@ -25,10 +25,13 @@ test_expect_success \
      git add fake.sendmail
      GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
 
-test_expect_success \
-    'Extract patches and send' \
-    'git format-patch -n HEAD^1
-     git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" ./0001*txt'
+test_expect_success 'Extract patches' '
+    patches=`git format-patch -n HEAD^1`
+'
+
+test_expect_success 'Send patches' '
+     git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+'
 
 cat >expected <<\EOF
 !nobody@example.com!
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh
new file mode 100755 (executable)
index 0000000..bf1d638
--- /dev/null
@@ -0,0 +1,234 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-svn basic tests'
+GIT_SVN_LC_ALL=$LC_ALL
+
+case "$LC_ALL" in
+*.UTF-8)
+       have_utf8=t
+       ;;
+*)
+       have_utf8=
+       ;;
+esac
+
+. ./lib-git-svn.sh
+
+echo 'define NO_SVN_TESTS to skip git-svn tests'
+
+mkdir import
+cd import
+
+echo foo > foo
+if test -z "$NO_SYMLINK"
+then
+       ln -s foo foo.link
+fi
+mkdir -p dir/a/b/c/d/e
+echo 'deep dir' > dir/a/b/c/d/e/file
+mkdir -p bar
+echo 'zzz' > bar/zzz
+echo '#!/bin/sh' > exec.sh
+chmod +x exec.sh
+svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
+
+cd ..
+rm -rf import
+
+test_expect_success \
+    'initialize git-svn' \
+    "git-svn init $svnrepo"
+
+test_expect_success \
+    'import an SVN revision into git' \
+    'git-svn fetch'
+
+test_expect_success "checkout from svn" "svn co $svnrepo $SVN_TREE"
+
+name='try a deep --rmdir with a commit'
+git checkout -f -b mybranch remotes/git-svn
+mv dir/a/b/c/d/e/file dir/file
+cp dir/file file
+git update-index --add --remove dir/a/b/c/d/e/file dir/file file
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch &&
+     svn up $SVN_TREE &&
+     test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
+
+
+name='detect node change from file to directory #1'
+mkdir dir/new_file
+mv dir/file dir/new_file/file
+mv dir/new_file dir/file
+git update-index --remove dir/file
+git update-index --add dir/file/file
+git commit -m "$name"
+
+test_expect_failure "$name" \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \
+    || true
+
+
+name='detect node change from directory to file #1'
+rm -rf dir $GIT_DIR/index
+git checkout -f -b mybranch2 remotes/git-svn
+mv bar/zzz zzz
+rm -rf bar
+mv zzz bar
+git update-index --remove -- bar/zzz
+git update-index --add -- bar
+git commit -m "$name"
+
+test_expect_failure "$name" \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
+    || true
+
+
+name='detect node change from file to directory #2'
+rm -f $GIT_DIR/index
+git checkout -f -b mybranch3 remotes/git-svn
+rm bar/zzz
+git-update-index --remove bar/zzz
+mkdir bar/zzz
+echo yyy > bar/zzz/yyy
+git-update-index --add bar/zzz/yyy
+git commit -m "$name"
+
+test_expect_failure "$name" \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
+    || true
+
+
+name='detect node change from directory to file #2'
+rm -f $GIT_DIR/index
+git checkout -f -b mybranch4 remotes/git-svn
+rm -rf dir
+git update-index --remove -- dir/file
+touch dir
+echo asdf > dir
+git update-index --add -- dir
+git commit -m "$name"
+
+test_expect_failure "$name" \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
+    || true
+
+
+name='remove executable bit from a file'
+rm -f $GIT_DIR/index
+git checkout -f -b mybranch5 remotes/git-svn
+chmod -x exec.sh
+git update-index exec.sh
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+     svn up $SVN_TREE &&
+     test ! -x $SVN_TREE/exec.sh"
+
+
+name='add executable bit back file'
+chmod +x exec.sh
+git update-index exec.sh
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+     svn up $SVN_TREE &&
+     test -x $SVN_TREE/exec.sh"
+
+
+
+if test -z "$NO_SYMLINK"
+then
+       name='executable file becomes a symlink to bar/zzz (file)'
+       rm exec.sh
+       ln -s bar/zzz exec.sh
+       git update-index exec.sh
+       git commit -m "$name"
+
+       test_expect_success "$name" \
+           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+            svn up $SVN_TREE &&
+            test -L $SVN_TREE/exec.sh"
+
+       name='new symlink is added to a file that was also just made executable'
+       chmod +x bar/zzz
+       ln -s bar/zzz exec-2.sh
+       git update-index --add bar/zzz exec-2.sh
+       git commit -m "$name"
+
+       test_expect_success "$name" \
+           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+            svn up $SVN_TREE &&
+            test -x $SVN_TREE/bar/zzz &&
+            test -L $SVN_TREE/exec-2.sh"
+
+       name='modify a symlink to become a file'
+       git help > help || true
+       rm exec-2.sh
+       cp help exec-2.sh
+       git update-index exec-2.sh
+       git commit -m "$name"
+
+       test_expect_success "$name" \
+           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+            svn up $SVN_TREE &&
+            test -f $SVN_TREE/exec-2.sh &&
+            test ! -L $SVN_TREE/exec-2.sh &&
+            diff -u help $SVN_TREE/exec-2.sh"
+fi
+
+
+if test "$have_utf8" = t
+then
+       name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
+       echo '# hello' >> exec-2.sh
+       git update-index exec-2.sh
+       git commit -m 'éï∏'
+       export LC_ALL="$GIT_SVN_LC_ALL"
+       test_expect_success "$name" "git-svn commit HEAD"
+       unset LC_ALL
+else
+       echo "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)"
+fi
+
+name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'
+GIT_SVN_ID=alt
+export GIT_SVN_ID
+test_expect_success "$name" \
+    "git-svn init $svnrepo && git-svn fetch &&
+     git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
+     git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
+     diff -u a b"
+
+if test -n "$NO_SYMLINK"
+then
+       test_done
+       exit 0
+fi
+
+name='check imported tree checksums expected tree checksums'
+rm -f expected
+if test "$have_utf8" = t
+then
+       echo tree f735671b89a7eb30cab1d8597de35bd4271ab813 > expected
+fi
+cat >> expected <<\EOF
+tree 4b9af72bb861eaed053854ec502cf7df72618f0f
+tree 031b8d557afc6fea52894eaebb45bec52f1ba6d1
+tree 0b094cbff17168f24c302e297f55bfac65eb8bd3
+tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
+tree 56a30b966619b863674f5978696f4a3594f2fca9
+tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
+tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
+EOF
+test_expect_success "$name" "diff -u a expected"
+
+test_done
+
diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh
new file mode 100755 (executable)
index 0000000..a5a235f
--- /dev/null
@@ -0,0 +1,126 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-svn property tests'
+. ./lib-git-svn.sh
+
+mkdir import
+
+a_crlf=
+a_lf=
+a_cr=
+a_ne_crlf=
+a_ne_lf=
+a_ne_cr=
+a_empty=
+a_empty_lf=
+a_empty_cr=
+a_empty_crlf=
+
+cd import
+       cat >> kw.c <<\EOF
+/* Somebody prematurely put a keyword into this file */
+/* $Id$ */
+EOF
+
+       printf "Hello\r\nWorld\r\n" > crlf
+       a_crlf=`git-hash-object -w crlf`
+       printf "Hello\rWorld\r" > cr
+       a_cr=`git-hash-object -w cr`
+       printf "Hello\nWorld\n" > lf
+       a_lf=`git-hash-object -w lf`
+
+       printf "Hello\r\nWorld" > ne_crlf
+       a_ne_crlf=`git-hash-object -w ne_crlf`
+       printf "Hello\nWorld" > ne_lf
+       a_ne_lf=`git-hash-object -w ne_lf`
+       printf "Hello\rWorld" > ne_cr
+       a_ne_cr=`git-hash-object -w ne_cr`
+
+       touch empty
+       a_empty=`git-hash-object -w empty`
+       printf "\n" > empty_lf
+       a_empty_lf=`git-hash-object -w empty_lf`
+       printf "\r" > empty_cr
+       a_empty_cr=`git-hash-object -w empty_cr`
+       printf "\r\n" > empty_crlf
+       a_empty_crlf=`git-hash-object -w empty_crlf`
+
+       svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
+cd ..
+
+rm -rf import
+test_expect_success 'checkout working copy from svn' "svn co $svnrepo test_wc"
+test_expect_success 'setup some commits to svn' \
+       'cd test_wc &&
+               echo Greetings >> kw.c &&
+               svn commit -m "Not yet an Id" &&
+               svn up &&
+               echo Hello world >> kw.c &&
+               svn commit -m "Modified file, but still not yet an Id" &&
+               svn up &&
+               svn propset svn:keywords Id kw.c &&
+               svn commit -m "Propset Id" &&
+               svn up &&
+       cd ..'
+
+test_expect_success 'initialize git-svn' "git-svn init $svnrepo"
+test_expect_success 'fetch revisions from svn' 'git-svn fetch'
+
+name='test svn:keywords ignoring'
+test_expect_success "$name" \
+       'git checkout -b mybranch remotes/git-svn &&
+       echo Hi again >> kw.c &&
+       git commit -a -m "test keywoards ignoring" &&
+       git-svn commit remotes/git-svn..mybranch &&
+       git pull . remotes/git-svn'
+
+expect='/* $Id$ */'
+got="`sed -ne 2p kw.c`"
+test_expect_success 'raw $Id$ found in kw.c' "test '$expect' = '$got'"
+
+test_expect_success "propset CR on crlf files" \
+       'cd test_wc &&
+               svn propset svn:eol-style CR empty &&
+               svn propset svn:eol-style CR crlf &&
+               svn propset svn:eol-style CR ne_crlf &&
+               svn commit -m "propset CR on crlf files" &&
+               svn up &&
+        cd ..'
+
+test_expect_success 'fetch and pull latest from svn and checkout a new wc' \
+       "git-svn fetch &&
+        git pull . remotes/git-svn &&
+        svn co $svnrepo new_wc"
+
+for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf
+do
+       test_expect_success "Comparing $i" "cmp $i new_wc/$i"
+done
+
+
+cd test_wc
+       printf '$Id$\rHello\rWorld\r' > cr
+       printf '$Id$\rHello\rWorld' > ne_cr
+       a_cr=`printf '$Id$\r\nHello\r\nWorld\r\n' | git-hash-object --stdin`
+       a_ne_cr=`printf '$Id$\r\nHello\r\nWorld' | git-hash-object --stdin`
+       test_expect_success 'Set CRLF on cr files' \
+       'svn propset svn:eol-style CRLF cr &&
+        svn propset svn:eol-style CRLF ne_cr &&
+        svn propset svn:keywords Id cr &&
+        svn propset svn:keywords Id ne_cr &&
+        svn commit -m "propset CRLF on cr files" &&
+        svn up'
+cd ..
+test_expect_success 'fetch and pull latest from svn' \
+       'git-svn fetch && git pull . remotes/git-svn'
+
+b_cr="`git-hash-object cr`"
+b_ne_cr="`git-hash-object ne_cr`"
+
+test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'"
+test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'"
+
+test_done
diff --git a/t/t9102-git-svn-deep-rmdir.sh b/t/t9102-git-svn-deep-rmdir.sh
new file mode 100755 (executable)
index 0000000..d693d18
--- /dev/null
@@ -0,0 +1,29 @@
+test_description='git-svn rmdir'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' "
+       mkdir import &&
+       cd import &&
+       mkdir -p deeply/nested/directory/number/1 &&
+       mkdir -p deeply/nested/directory/number/2 &&
+       echo foo > deeply/nested/directory/number/1/file &&
+       echo foo > deeply/nested/directory/number/2/another &&
+       svn import -m 'import for git-svn' . $svnrepo &&
+       cd ..
+       "
+
+test_expect_success 'mirror via git-svn' "
+       git-svn init $svnrepo &&
+       git-svn fetch &&
+       git checkout -f -b test-rmdir remotes/git-svn
+       "
+
+test_expect_success 'Try a commit on rmdir' "
+       git rm -f deeply/nested/directory/number/2/another &&
+       git commit -a -m 'remove another' &&
+       git-svn commit --rmdir HEAD &&
+       svn ls -R $svnrepo | grep ^deeply/nested/directory/number/1
+       "
+
+
+test_done
diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh
new file mode 100755 (executable)
index 0000000..cc62d4e
--- /dev/null
@@ -0,0 +1,63 @@
+test_description='git-svn graft-branches'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' "
+       mkdir import &&
+       cd import &&
+       mkdir -p trunk branches tags &&
+       echo hello > trunk/readme &&
+       svn import -m 'import for git-svn' . $svnrepo &&
+       cd .. &&
+       svn cp -m 'tag a' $svnrepo/trunk $svnrepo/tags/a &&
+       svn cp -m 'branch a' $svnrepo/trunk $svnrepo/branches/a &&
+       svn co $svnrepo wc &&
+       cd wc &&
+       echo feedme >> branches/a/readme &&
+       svn commit -m hungry &&
+       svn up &&
+       cd trunk &&
+       svn merge -r3:4 $svnrepo/branches/a &&
+       svn commit -m 'merge with a' &&
+       cd ../.. &&
+       svn log -v $svnrepo &&
+       git-svn init -i trunk $svnrepo/trunk &&
+       git-svn init -i a $svnrepo/branches/a &&
+       git-svn init -i tags/a $svnrepo/tags/a &&
+       git-svn fetch -i tags/a &&
+       git-svn fetch -i a &&
+       git-svn fetch -i trunk
+       "
+
+r1=`git-rev-list remotes/trunk | tail -n1`
+r2=`git-rev-list remotes/tags/a | tail -n1`
+r3=`git-rev-list remotes/a | tail -n1`
+r4=`git-rev-list remotes/a | head -n1`
+r5=`git-rev-list remotes/trunk | head -n1`
+
+test_expect_success 'test graft-branches regexes and copies' "
+       test -n "$r1" &&
+       test -n "$r2" &&
+       test -n "$r3" &&
+       test -n "$r4" &&
+       test -n "$r5" &&
+       git-svn graft-branches &&
+       grep '^$r2 $r1' $GIT_DIR/info/grafts &&
+       grep '^$r3 $r1' $GIT_DIR/info/grafts &&
+       grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r4' | grep '$r1'
+       "
+
+test_debug 'gitk --all & sleep 1'
+
+test_expect_success 'test graft-branches with tree-joins' "
+       rm $GIT_DIR/info/grafts &&
+       git-svn graft-branches --no-default-regex --no-graft-copy -B &&
+       grep '^$r3 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r2' &&
+       grep '^$r2 $r1' $GIT_DIR/info/grafts &&
+       grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r4'
+       "
+
+# the result of this is kinda funky, we have a strange history and
+# this is just a test :)
+test_debug 'gitk --all &'
+
+test_done
diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh
new file mode 100755 (executable)
index 0000000..01488ff
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-svn --follow-parent fetching'
+. ./lib-git-svn.sh
+
+if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
+then
+       echo 'Skipping: --follow-parent needs SVN libraries'
+       test_done
+       exit 0
+fi
+
+test_expect_success 'initialize repo' "
+       mkdir import &&
+       cd import &&
+       mkdir -p trunk &&
+       echo hello > trunk/readme &&
+       svn import -m 'initial' . $svnrepo &&
+       cd .. &&
+       svn co $svnrepo wc &&
+       cd wc &&
+       echo world >> trunk/readme &&
+       svn commit -m 'another commit' &&
+       svn up &&
+       svn mv -m 'rename to thunk' trunk thunk &&
+       svn up &&
+       echo goodbye >> thunk/readme &&
+       svn commit -m 'bye now' &&
+       cd ..
+       "
+
+test_expect_success 'init and fetch --follow-parent a moved directory' "
+       git-svn init -i thunk $svnrepo/thunk &&
+       git-svn fetch --follow-parent -i thunk &&
+       git-rev-parse --verify refs/remotes/trunk &&
+       test '$?' -eq '0'
+       "
+
+test_debug 'gitk --all &'
+
+test_done
diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh
new file mode 100755 (executable)
index 0000000..f994b72
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+test_description='git-svn commit-diff'
+. ./lib-git-svn.sh
+
+if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
+then
+       echo 'Skipping: commit-diff needs SVN libraries'
+       test_done
+       exit 0
+fi
+
+test_expect_success 'initialize repo' "
+       mkdir import &&
+       cd import &&
+       echo hello > readme &&
+       svn import -m 'initial' . $svnrepo &&
+       cd .. &&
+       echo hello > readme &&
+       git update-index --add readme &&
+       git commit -a -m 'initial' &&
+       echo world >> readme &&
+       git commit -a -m 'another'
+       "
+
+head=`git rev-parse --verify HEAD^0`
+prev=`git rev-parse --verify HEAD^1`
+
+# the internals of the commit-diff command are the same as the regular
+# commit, so only a basic test of functionality is needed since we've
+# already tested commit extensively elsewhere
+
+test_expect_success 'test the commit-diff command' "
+       test -n '$prev' && test -n '$head' &&
+       git-svn commit-diff '$prev' '$head' '$svnrepo' &&
+       svn co $svnrepo wc &&
+       cmp readme wc/readme
+       "
+
+test_done
index 05f6e79560ff7e3d92c47221b3ee7adec098e676..470a909891bc1358163af91513972ce7d0c702c0 100755 (executable)
@@ -9,6 +9,8 @@ LC_ALL=C
 PAGER=cat
 TZ=UTC
 export LANG LC_ALL PAGER TZ
+EDITOR=:
+VISUAL=:
 unset AUTHOR_DATE
 unset AUTHOR_EMAIL
 unset AUTHOR_NAME
@@ -26,10 +28,12 @@ unset GIT_DIR
 unset GIT_EXTERNAL_DIFF
 unset GIT_INDEX_FILE
 unset GIT_OBJECT_DIRECTORY
+unset GIT_TRACE
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
 export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
+export EDITOR VISUAL
 
 # Each test should start with something like this, after copyright notices:
 #
diff --git a/tag.c b/tag.c
index 74d0dabe5d8c1f06a3f67475368c34e3b4046456..864ac1bb602b1af07301d0b5f15d31e904b343a6 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -5,7 +5,7 @@ const char *tag_type = "tag";
 
 struct object *deref_tag(struct object *o, const char *warn, int warnlen)
 {
-       while (o && o->type == TYPE_TAG)
+       while (o && o->type == OBJ_TAG)
                o = parse_object(((struct tag *)o)->tagged->sha1);
        if (!o && warn) {
                if (!warnlen)
@@ -21,12 +21,12 @@ struct tag *lookup_tag(const unsigned char *sha1)
         if (!obj) {
                 struct tag *ret = alloc_tag_node();
                 created_object(sha1, &ret->object);
-                ret->object.type = TYPE_TAG;
+                ret->object.type = OBJ_TAG;
                 return ret;
         }
        if (!obj->type)
-               obj->type = TYPE_TAG;
-        if (obj->type != TYPE_TAG) {
+               obj->type = OBJ_TAG;
+        if (obj->type != OBJ_TAG) {
                 error("Object %s is a %s, not a tree",
                       sha1_to_hex(sha1), typename(obj->type));
                 return NULL;
index d7a8f0a849523ebc280e06fe836f3bc448993d0c..76d5ac2477d669aabfef8108cfbb713fbdfef70a 100644 (file)
@@ -60,7 +60,7 @@ then
                        echo "Changes since $prev:"
                        git rev-list --pretty $prev..$3 | $short
                        echo ---
-                       git diff $prev..$3 | diffstat -p1
+                       git diff --stat $prev..$3
                        echo ---
                fi
                ;;
@@ -75,7 +75,7 @@ else
        base=$(git-merge-base "$2" "$3")
        case "$base" in
        "$2")
-               git diff "$3" "^$base" | diffstat -p1
+               git diff --stat "$3" "^$base"
                echo
                echo "New commits:"
                ;;
diff --git a/test-sha1.c b/test-sha1.c
new file mode 100644 (file)
index 0000000..78d7e98
--- /dev/null
@@ -0,0 +1,47 @@
+#include "cache.h"
+
+int main(int ac, char **av)
+{
+       SHA_CTX ctx;
+       unsigned char sha1[20];
+       unsigned bufsz = 8192;
+       char *buffer;
+
+       if (ac == 2)
+               bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024;
+
+       if (!bufsz)
+               bufsz = 8192;
+
+       while ((buffer = malloc(bufsz)) == NULL) {
+               fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz);
+               bufsz /= 2;
+               if (bufsz < 1024)
+                       die("OOPS");
+       }
+
+       SHA1_Init(&ctx);
+
+       while (1) {
+               ssize_t sz, this_sz;
+               char *cp = buffer;
+               unsigned room = bufsz;
+               this_sz = 0;
+               while (room) {
+                       sz = xread(0, cp, room);
+                       if (sz == 0)
+                               break;
+                       if (sz < 0)
+                               die("test-sha1: %s", strerror(errno));
+                       this_sz += sz;
+                       cp += sz;
+                       room -= sz;
+               }
+               if (this_sz == 0)
+                       break;
+               SHA1_Update(&ctx, buffer, this_sz);
+       }
+       SHA1_Final(sha1, &ctx);
+       puts(sha1_to_hex(sha1));
+       exit(0);
+}
diff --git a/test-sha1.sh b/test-sha1.sh
new file mode 100755 (executable)
index 0000000..640856a
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+dd if=/dev/zero bs=1048576 count=100 2>/dev/null |
+/usr/bin/time ./test-sha1 >/dev/null
+
+while read expect cnt pfx
+do
+       case "$expect" in '#'*) continue ;; esac
+       actual=`
+               {
+                       test -z "$pfx" || echo "$pfx"
+                       dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |
+                       tr '[\0]' '[g]'
+               } | ./test-sha1 $cnt
+       `
+       if test "$expect" = "$actual"
+       then
+               echo "OK: $expect $cnt $pfx"
+       else
+               echo >&2 "OOPS: $cnt"
+               echo >&2 "expect: $expect"
+               echo >&2 "actual: $actual"
+               exit 1
+       fi
+done <<EOF
+da39a3ee5e6b4b0d3255bfef95601890afd80709 0
+3f786850e387550fdab836ed7e6dc881de23001b 0 a
+5277cbb45a15902137d332d97e89cf8136545485 0 ab
+03cfd743661f07975fa2f1220c5194cbaff48451 0 abc
+3330b4373640f9e4604991e73c7e86bfd8da2dc3 0 abcd
+ec11312386ad561674f724b8cca7cf1796e26d1d 0 abcde
+bdc37c074ec4ee6050d68bc133c6b912f36474df 0 abcdef
+69bca99b923859f2dc486b55b87f49689b7358c7 0 abcdefg
+e414af7161c9554089f4106d6f1797ef14a73666 0 abcdefgh
+0707f2970043f9f7c22029482db27733deaec029 0 abcdefghi
+a4dd8aa74a5636728fe52451636e2e17726033aa 1
+9986b45e2f4d7086372533bb6953a8652fa3644a 1 frotz
+23d8d4f788e8526b4877548a32577543cbaaf51f 10
+8cd23f822ab44c7f481b8c92d591f6d1fcad431c 10 frotz
+f3b5604a4e604899c1233edb3bf1cc0ede4d8c32 512
+b095bd837a371593048136e429e9ac4b476e1bb3 512 frotz
+08fa81d6190948de5ccca3966340cc48c10cceac 1200 xyzzy
+e33a291f42c30a159733dd98b8b3e4ff34158ca0 4090 4G
+#a3bf783bc20caa958f6cb24dd140a7b21984838d 9999 nitfol
+EOF
+
+exit
+
+# generating test vectors
+# inputs are number of megabytes followed by some random string to prefix.
+
+while read cnt pfx
+do
+       actual=`
+               {
+                       test -z "$pfx" || echo "$pfx"
+                       dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null |
+                       tr '[\0]' '[g]'
+               } | sha1sum |
+               sed -e 's/ .*//'
+       `
+       echo "$actual $cnt $pfx"
+done <<EOF
+0
+0 a
+0 ab
+0 abc
+0 abcd
+0 abcde
+0 abcdef
+0 abcdefg
+0 abcdefgh
+0 abcdefghi
+1
+1 frotz
+10
+10 frotz
+512
+512 frotz
+1200 xyzzy
+4090 4G
+9999 nitfol
+EOF
diff --git a/tree.c b/tree.c
index 10236555cc5c127b9b5b2cac8f2514c1b7e87676..a6032e35ecb2543b6efcae252e5815b804cb6f0a 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -131,12 +131,12 @@ struct tree *lookup_tree(const unsigned char *sha1)
        if (!obj) {
                struct tree *ret = alloc_tree_node();
                created_object(sha1, &ret->object);
-               ret->object.type = TYPE_TREE;
+               ret->object.type = OBJ_TREE;
                return ret;
        }
        if (!obj->type)
-               obj->type = TYPE_TREE;
-       if (obj->type != TYPE_TREE) {
+               obj->type = OBJ_TREE;
+       if (obj->type != OBJ_TREE) {
                error("Object %s is a %s, not a tree",
                      sha1_to_hex(sha1), typename(obj->type));
                return NULL;
@@ -216,11 +216,11 @@ struct tree *parse_tree_indirect(const unsigned char *sha1)
        do {
                if (!obj)
                        return NULL;
-               if (obj->type == TYPE_TREE)
+               if (obj->type == OBJ_TREE)
                        return (struct tree *) obj;
-               else if (obj->type == TYPE_COMMIT)
+               else if (obj->type == OBJ_COMMIT)
                        obj = &(((struct commit *) obj)->tree->object);
-               else if (obj->type == TYPE_TAG)
+               else if (obj->type == OBJ_TAG)
                        obj = ((struct tag *) obj)->tagged;
                else
                        return NULL;
index 3b824b04a262b92808ba138b1922badbbb25da89..48c1ee7968cc6b40cb02cd99d35e66630f922041 100644 (file)
@@ -241,11 +241,6 @@ static void unpack_one(unsigned nr, unsigned total)
        }
 }
 
-/*
- * We unpack from the end, older files first. Now, usually
- * there are deltas etc, so we'll not actually write the
- * objects in that order, but we might as well try..
- */
 static void unpack_all(void)
 {
        int i;
index 2b70c3dcb4e3b1e73d7d5a088ae73de17540b6b7..07ecdb4281cfb32cf16dfc7c46c53e6e8d0247bc 100644 (file)
@@ -1,3 +1,6 @@
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/poll.h>
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
@@ -5,9 +8,6 @@
 #include "object.h"
 #include "commit.h"
 #include "exec_cmd.h"
-#include <signal.h>
-#include <sys/poll.h>
-#include <sys/wait.h>
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
@@ -51,6 +51,10 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
                if (fd == 3)
                        /* emergency quit */
                        fd = 2;
+               if (fd == 2) {
+                       xwrite(fd, data, sz);
+                       return sz;
+               }
                return safe_write(fd, data, sz);
        }
        p = data;
@@ -178,6 +182,8 @@ static void create_pack_file(void)
                ssize_t sz;
                int pe, pu, pollsize;
 
+               reset_timeout();
+
                pollsize = 0;
                pe = pu = -1;
 
@@ -326,7 +332,7 @@ static int got_sha1(char *hex, unsigned char *sha1)
                        o = parse_object(sha1);
                if (!o)
                        die("oops (%s)", sha1_to_hex(sha1));
-               if (o->type == TYPE_COMMIT) {
+               if (o->type == OBJ_COMMIT) {
                        struct commit_list *parents;
                        if (o->flags & THEY_HAVE)
                                return 0;
@@ -457,7 +463,7 @@ static int send_ref(const char *refname, const unsigned char *sha1)
                o->flags |= OUR_REF;
                nr_our_refs++;
        }
-       if (o->type == TYPE_TAG) {
+       if (o->type == OBJ_TAG) {
                o = deref_tag(o, refname, 0);
                packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
        }
index 2ce10b4c0d18aca467d3fb7a47badefa8c7c956f..c9f817818a781164b63c78be2af71a5d21e35d6f 100644 (file)
@@ -39,6 +39,7 @@ extern "C" {
 #define XDL_PATCH_IGNOREBSPACE (1 << 8)
 
 #define XDL_EMIT_FUNCNAMES (1 << 0)
+#define XDL_EMIT_COMMON (1 << 1)
 
 #define XDL_MMB_READONLY (1 << 0)
 
index ed7ad2041c6f81df8b0191374c5b0e4e91683a70..d76e76a0e6d987cdec985e304ad682020fef5e1f 100644 (file)
@@ -84,7 +84,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
                 * We need to extent the diagonal "domain" by one. If the next
                 * values exits the box boundaries we need to change it in the
                 * opposite direction because (max - min) must be a power of two.
-                * Also we initialize the extenal K value to -1 so that we can
+                * Also we initialize the external K value to -1 so that we can
                 * avoid extra conditions check inside the core loop.
                 */
                if (fmin > dmin)
@@ -119,7 +119,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
                 * We need to extent the diagonal "domain" by one. If the next
                 * values exits the box boundaries we need to change it in the
                 * opposite direction because (max - min) must be a power of two.
-                * Also we initialize the extenal K value to -1 so that we can
+                * Also we initialize the external K value to -1 so that we can
                 * avoid extra conditions check inside the core loop.
                 */
                if (bmin > dmin)
@@ -405,7 +405,7 @@ static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
        /*
         * This is the same of what GNU diff does. Move back and forward
         * change groups for a consistent and pretty diff output. This also
-        * helps in finding joineable change groups and reduce the diff size.
+        * helps in finding joinable change groups and reduce the diff size.
         */
        for (ix = ixo = 0;;) {
                /*
index ad5bfb191003f14cd71b2ec7f39fd72ac86eda9b..714c563547462d962b4a6b69b524092e1bc2a036 100644 (file)
@@ -100,6 +100,21 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
 }
 
 
+int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                   xdemitconf_t const *xecfg) {
+       xdfile_t *xdf = &xe->xdf1;
+       const char *rchg = xdf->rchg;
+       long ix;
+
+       for (ix = 0; ix < xdf->nrec; ix++) {
+               if (rchg[ix])
+                       continue;
+               if (xdl_emit_record(xdf, ix, "", ecb))
+                       return -1;
+       }
+       return 0;
+}
+
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                  xdemitconf_t const *xecfg) {
        long s1, s2, e1, e2, lctx;
@@ -107,6 +122,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
        char funcbuf[40];
        long funclen = 0;
 
+       if (xecfg->flags & XDL_EMIT_COMMON)
+               return xdl_emit_common(xe, xscr, ecb, xecfg);
+
        for (xch = xche = xscr; xch; xch = xche->next) {
                xche = xdl_get_hunk(xch, xecfg);