author | Junio C Hamano <junkio@cox.net> | |
Thu, 13 Jul 2006 05:31:22 +0000 (22:31 -0700) | ||
committer | Junio C Hamano <junkio@cox.net> | |
Thu, 13 Jul 2006 05:31:22 +0000 (22:31 -0700) |
* lt/merge-tree:
Improved three-way blob merging code
Prepare "git-merge-tree" for future work
xdiff: generate "anti-diffs" aka what is common to two files
Improved three-way blob merging code
Prepare "git-merge-tree" for future work
xdiff: generate "anti-diffs" aka what is common to two files
261 files changed:
diff --git a/.gitignore b/.gitignore
index 7b954d587ec48314dee6c7d179e57607f958e875..52d61f31939d4fbbd55521cff85939fd1cf97e28 100644 (file)
--- a/.gitignore
+++ b/.gitignore
git-imap-send
git-index-pack
git-init-db
+git-instaweb
git-local-fetch
git-log
git-lost-found
git-ssh-upload
git-status
git-stripspace
+git-svn
git-svnimport
git-symbolic-ref
git-tag
index 8601949e80991823145a6d4c61cb5da918f98275..90722c21fad453791f53d1e8b62df23450622eae 100644 (file)
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 a04c5adf8e522e65fae58ec32db07c46aeebe070..0b434c1f1923fb10b2420a3295e59fd9d163f39b 100644 (file)
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
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.
+
alias.*::
Command aliases for the gitlink:git[1] command wrapper - e.g.
after defining "alias.last = cat-file commit HEAD", the invocation
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].
index 1fbca83141e20599e4d6edd880b1cabbf0cd9913..d2b0bd38de7fb39ccff5687013784ea8ceb22f1c 100644 (file)
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)
-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)
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..27ac72d98fcf8cf2cd0c0ab236e7646ecff7499d 100644 (file)
SYNOPSIS
--------
-'git-cvsexportcommmit' [-h] [-v] [-c] [-p] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-v] [-c] [-p] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
DESCRIPTION
index 481b8b3aa09539ff71a3f48b0b50b75e9ccce7a7..7248b35d952c3fe97496895059af35300694eb98 100644 (file)
commit with these flags.
-q::
- Remain silent even on nonexisting files
+ Remain silent even on nonexistent files
Output format
-------------
index 7ab20803761b0d5ac69e3b9acc31ed4e9d59ef4c..228c4d95bd41d40bbb32cfcb6b9a7dc648280fa5 100644 (file)
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 8a150d861f5b4c3dc468d46fa09dbfce5be020fc..0a4fc14b97fe690f9546c5d6734dfaaa9d0d656f 100644 (file)
-----------
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
--- /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 209e36bacb03c166e1c0c61e8c6e14447cd44f6a..5a17801f6a8d4d307f55e62745a400ad498ebdb9 100644 (file)
-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)
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)
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)
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
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)
--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)
-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)
% 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)
[ \--sparse ]
[ \--no-merges ]
[ \--remove-empty ]
+ [ \--not ]
[ \--all ]
[ \--topo-order ]
[ \--parents ]
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
-------
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
--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
--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)
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.
G H I J
\ / \ /
D E F
- \ | /
- \ | /
- \|/
+ \ | / \
+ \ | / |
+ \|/ |
B C
\ /
\ /
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)
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
--- /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 d79523f56d063aa4a3ee3181fa224694c7970613..0914cbb0ba528653aa50c6a2eb27815bd69d3508 100644 (file)
- *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)
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.
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 51f20c6e67acb8c7eb526fac544992eaec321623..d00cc3ea5219f73aac79709e683e135548555fd3 100644 (file)
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
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:
------------
index 116ddb7fbfd7654bb7330e8f56dd03a9be029cd1..14449ca8baeb7c2c979d0fd3f61ce683fd8e08ce 100644 (file)
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
diff --git a/Documentation/howto/isolate-bugs-with-bisect.txt b/Documentation/howto/isolate-bugs-with-bisect.txt
index edbcd4c6618d6f9c123c49d4835dfc59b74e07f5..926bbdc3cb1dcab81e0d90de3a0b9b87153e55e6 100644 (file)
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.
diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt
index c2d4a91c7cf0a631ffd3caadea8a6aa25aa4a24c..fcd64e9b9b6d53b46d31e0c797016e9881651604 100644 (file)
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.
diff --git a/Documentation/technical/pack-heuristics.txt b/Documentation/technical/pack-heuristics.txt
index 9aadd5cee5204b1f11e8ac0511609bc86ab5de81..103eb5d989349c8e7e0147920b2e218caba9daf9 100644 (file)
<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
(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!
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)
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.
diff --git a/Documentation/urls.txt b/Documentation/urls.txt
index 74774134e3939cf1f5447d3aaec319994504ce1b..9abec806d9908ab28518e24b32c40eb0701e3c3d 100644 (file)
--- a/Documentation/urls.txt
+++ b/Documentation/urls.txt
- 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://[user@]host.xz/path/to/repo.git/
+- ssh://[user@]host.xz/~user/path/to/repo.git/
+- ssh://[user@]host.xz/~/path/to/repo.git
===============================================================
SSH Is the default transport protocol and also supports an
<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
index f8337e2a4d154157ed805eeb10f52d01513f7adb..7da2c8982978e9ff069eb1cc2338330a45669100 100644 (file)
--- a/INSTALL
+++ b/INSTALL
- "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
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
diff --git a/Makefile b/Makefile
index e880457526ea2fa42eef9577294c9f539499e7f6..649ed4cdc2eb9be953efdda57fb2de7acbf47aa4 100644 (file)
--- a/Makefile
+++ b/Makefile
# 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 PPC_SHA1 environment variable when running make to make use of
# a bundled SHA1 routine optimized for PowerPC.
#
# 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
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
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 = \
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
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
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
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
LIBS = $(GITLIBS) -lz
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))
$(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' \
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 > $@+
+ chmod +x $@+
+ mv $@+ $@
+
# These can record GIT_VERSION
git$X git.spec \
$(patsubst %.sh,%,$(SCRIPT_SH)) \
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)
# with that.
export NO_PYTHON
+export NO_SVN_TESTS
test: all
$(MAKE) -C t/ all
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
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;
};
struct chunk {
- int off1, len1; // ---
- int off2, len2; // +++
+ int off1, len1; /* --- */
+ int off2, len2; /* +++ */
};
struct patch {
}
#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)
{
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;
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")) {
} else if(!strcmp(argv[i], "-S")) {
if (i + 1 < argc &&
!read_ancestry(argv[i + 1], &sha1_p)) {
- compability = 1;
+ compatibility = 1;
i++;
continue;
}
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/builtin-add.c b/builtin-add.c
index bfbbb1bf52e10667f94dd574ab32fe070c98c4cb..2d256981733345f4a430e11812b6851f0351aabd 100644 (file)
--- a/builtin-add.c
+++ b/builtin-add.c
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");
}
diff --git a/builtin-apply.c b/builtin-apply.c
index e9ead002d38368f6de6ba75f25542fe61258cfb9..c903146bb65e6db4dcb527badf864d98db6aabb3 100644 (file)
--- a/builtin-apply.c
+++ b/builtin-apply.c
#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.
*/
* 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;
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");
}
diff --git a/builtin-diff-files.c b/builtin-diff-files.c
index 5afc1d7208f8a13b7363be2127cad3b43baa95dd..81ac2fe64aea23f9db605da181da098dcfca757a 100644 (file)
--- a/builtin-diff-files.c
+++ b/builtin-diff-files.c
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;
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),
diff --git a/builtin-diff-index.c b/builtin-diff-index.c
index c42ef9a7a76e7d8f6bc25d593865b9343d4e74b5..a1fa1b85cf741cd6b5f06afd02abbe618e86f5d1 100644 (file)
--- a/builtin-diff-index.c
+++ b/builtin-diff-index.c
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;
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.
diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c
index 7c157ca889eaf85b38be2764fca83814bb83a3f4..9c62702941569511a149eba89a8be2b478f04b70 100644 (file)
--- a/builtin-diff-stages.c
+++ b/builtin-diff-stages.c
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] == '-') {
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) ||
diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c
index 3409a39a9f73f9ed67547f5cc79619c1e402f47b..b6106685942e3cb3a5db2b3a67123b5bd554d856 100644 (file)
--- a/builtin-diff-tree.c
+++ b/builtin-diff-tree.c
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;
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
diff --git a/builtin-diff.c b/builtin-diff.c
index 99a2f766055d6c8c3231e38bd369169d3c742782..ae901dd25ea4d8d82752db5b28f354fd2eadfb56 100644 (file)
--- a/builtin-diff.c
+++ b/builtin-diff.c
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--;
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
/* 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)))
*/
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,
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--;
{
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;
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));
* 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.
*/
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
--- /dev/null
+++ b/builtin-fmt-merge-msg.c
@@ -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 != TYPE_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;
+}
+
diff --git a/builtin-grep.c b/builtin-grep.c
index 2e7986cecefc964f665b10d363569951c1f40293..4c2f7dfe03086af3e638f19a541d6c281aba927c 100644 (file)
--- a/builtin-grep.c
+++ b/builtin-grep.c
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;
#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)
}
}
+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)
{
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.
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)
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;
* 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.
*/
case 'A': case 'B': case 'C':
if (!arg[2]) {
if (argc <= 1)
- usage(builtin_grep_usage);
+ die(emsg_missing_context_len);
scan = *++argv;
argc--;
}
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;
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));
/* 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 {
diff --git a/builtin-help.c b/builtin-help.c
index 7470faa56692e17608d202000b78a91565360b72..335fe5fedcc6523a3b49a3e00686fe381098bcda 100644 (file)
--- a/builtin-help.c
+++ b/builtin-help.c
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");
diff --git a/builtin-log.c b/builtin-log.c
index 44d2d136f585a74e462556a4f756c7a297cadd51..7e5cab15c106f59417804a25d094c94e78d64063 100644 (file)
--- a/builtin-log.c
+++ b/builtin-log.c
/* 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();
{
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;
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)
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);
}
freopen(filename, "w", stdout);
}
+static int get_patch_id(struct commit *commit, struct diff_options *options,
+ unsigned char *sha1)
+{
+ diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1,
+ "", options);
+ diffcore_std(options);
+ return diff_flush_patch_id(options, sha1);
+}
+
+static void get_patch_ids(struct rev_info *rev, struct diff_options *options)
+{
+ struct rev_info check_rev;
+ struct commit *commit;
+ struct object *o1, *o2;
+ unsigned flags1, flags2;
+ unsigned char sha1[20];
+
+ if (rev->pending.nr != 2)
+ die("Need exactly one range.");
+
+ o1 = rev->pending.objects[0].item;
+ flags1 = o1->flags;
+ o2 = rev->pending.objects[1].item;
+ flags2 = o2->flags;
+
+ if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
+ die("Not a range.");
+
+ diff_setup(options);
+ options->recursive = 1;
+ if (diff_setup_done(options) < 0)
+ die("diff_setup_done failed");
+
+ /* given a range a..b get all patch ids for b..a */
+ init_revisions(&check_rev);
+ o1->flags ^= UNINTERESTING;
+ o2->flags ^= UNINTERESTING;
+ add_pending_object(&check_rev, o1, "o1");
+ add_pending_object(&check_rev, o2, "o2");
+ prepare_revision_walk(&check_rev);
+
+ while ((commit = get_revision(&check_rev)) != NULL) {
+ /* ignore merges */
+ if (commit->parents && commit->parents->next)
+ continue;
+
+ if (!get_patch_id(commit, options, sha1))
+ created_object(sha1, xcalloc(1, sizeof(struct object)));
+ }
+
+ /* reset for next revision walk */
+ clear_commit_marks((struct commit *)o1,
+ SEEN | UNINTERESTING | SHOWN | ADDED);
+ clear_commit_marks((struct commit *)o2,
+ SEEN | UNINTERESTING | SHOWN | ADDED);
+ o1->flags = flags1;
+ o2->flags = flags2;
+}
+
int cmd_format_patch(int argc, const char **argv, char **envp)
{
struct commit *commit;
int numbered = 0;
int start_number = -1;
int keep_subject = 0;
+ int ignore_if_in_upstream = 0;
+ struct diff_options patch_id_opts;
char *add_signoff = NULL;
+ 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;
/*
rev.mime_boundary = git_version_string;
else if (!strncmp(argv[i], "--attach=", 9))
rev.mime_boundary = argv[i] + 9;
+ else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
+ ignore_if_in_upstream = 1;
else
argv[j++] = argv[i];
}
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?");
add_head(&rev);
}
+ if (ignore_if_in_upstream)
+ get_patch_ids(&rev, &patch_id_opts);
+
if (!use_stdout)
realstdout = fdopen(dup(1), "w");
prepare_revision_walk(&rev);
while ((commit = get_revision(&rev)) != NULL) {
+ unsigned char sha1[20];
+
/* ignore merges */
if (commit->parents && commit->parents->next)
continue;
+
+ if (ignore_if_in_upstream &&
+ !get_patch_id(commit, &patch_id_opts, sha1) &&
+ lookup_object(sha1))
+ continue;
+
nr++;
list = realloc(list, nr * sizeof(list[0]));
list[nr - 1] = commit;
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 3e40747cf57ed4a8f7f20d83510ae08bcfcbd33a..ac53f76f689fdc0a44b6e4921dc08b880e1349b0 100644 (file)
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
}
}
-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;
/* 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;
}
#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);
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;
break;
}
if (sz < 0)
- return;
+ return rfc2047;
if (metainfo_charset)
convert_to_utf8(piecebuf, charset_q);
strcpy(out, piecebuf);
}
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-push.c b/builtin-push.c
index 66b94078229c09d8105fcd7f5800a2cba567c972..31cbfd73861326ad303472718365838329a55cb8 100644 (file)
--- a/builtin-push.c
+++ b/builtin-push.c
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));
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];
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 9a2099d7307903d62c85f3483a22969ab5094c84..6df5d7c5cbe2cd524cda9e0636c3ed5a8259cf33 100644 (file)
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
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);
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;
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);
}
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;
}
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 5f5ade45aec781df107d4a49a4a554575ac278bd..b3e4386c1baec7c3a56704e61e44e575203ab759 100644 (file)
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
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;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
- char *dotdot;
if (as_is) {
if (show_file(arg) && as_is < 2)
}
/* 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;
diff --git a/builtin-rm.c b/builtin-rm.c
index 4d56a1f07065e6e8ebaa95fdfe843cffb6283af3..5deb81171996029b31da14109c408ffba33f7957 100644 (file)
--- a/builtin-rm.c
+++ b/builtin-rm.c
* 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) {
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");
}
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
index 09d82278625584213f710a984652a7efc07339c5..260cb221b902aa05d389f79988434770ed8c9131 100644 (file)
--- a/builtin-show-branch.c
+++ b/builtin-show-branch.c
#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;
diff --git a/builtin-update-index.c b/builtin-update-index.c
index ef50243452c2f27572c941a5db9365c785f1fbd5..1a4200d151dd8e876c6f84531422cae50b049aa3 100644 (file)
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
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");
}
diff --git a/builtin-update-ref.c b/builtin-update-ref.c
index 00333c7e7ca0e706be425182ff92b98a16ac3883..83094abe0f20d36bf2d9e65539fb2c8b7a5644fc 100644 (file)
--- a/builtin-update-ref.c
+++ b/builtin-update-ref.c
unsigned char sha1[20], oldsha1[20];
int i;
+ setup_ident();
setup_git_directory();
git_config(git_default_config);
diff --git a/builtin-write-tree.c b/builtin-write-tree.c
index 70e9b6fcc6d93e9b869a0a1eac144cbea3ecba3f..449a4d1b575b591aa07a40532649f9ea06eb6890 100644 (file)
--- a/builtin-write-tree.c
+++ b/builtin-write-tree.c
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
diff --git a/builtin.h b/builtin.h
index f12d5e68f6778fbf96c85b81d250cf7f230edf1d..d9e5483bd577733c241c9c166a43ce28ebe80328 100644 (file)
--- a/builtin.h
+++ b/builtin.h
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);
index 87199396a05fbbbb695379119535eb9eefae2514..d433d46f23c956aa13b9f0d97344f792234e94ab 100644 (file)
--- a/cache.h
+++ b/cache.h
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;
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);
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);
/* pager.c */
extern void setup_pager(void);
+extern int pager_in_use;
/* base85 */
int decode_85(char *dst, char *line, int linelen);
diff --git a/checkout-index.c b/checkout-index.c
index ea40bc29bed17fbd6e3326edaa6fb3c38b742de6..61152f34b7fc00e745a2f7524c4da5b5a1a780a2 100644 (file)
--- a/checkout-index.c
+++ b/checkout-index.c
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)
{
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;
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;
}
diff --git a/combine-diff.c b/combine-diff.c
index 22542217ee6aeba5058d02d99b27e20ca894af7f..1bc1484645b4021ee71415f3b3ed44e4c88030d6 100644 (file)
--- a/combine-diff.c
+++ b/combine-diff.c
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,
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 +770,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 +792,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);
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;
}
}
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;
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 */
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);
+ }
}
}
diff --git a/commit.c b/commit.c
index e51ffa1c6cf5948c0e6c6d0b905fc868f4464ccf..c6bf10d045a45a38352af0a7dc0b3732b7493182 100644 (file)
--- a/commit.c
+++ b/commit.c
{
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;
}
}
free(nodes);
}
+
+/* merge-rebase stuff */
+
+/* bits #0..7 in revision.h */
+#define PARENT1 (1u<< 8)
+#define PARENT2 (1u<< 9)
+#define STALE (1u<<10)
+
+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;
+}
+
+/*
+ * 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
+ * stale, 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 stale. Which is
+ * commit B.
+ *
+ *
+ * Another pathological example how this thing used to fail to mark an
+ * ancestor of a merge base as STALE 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
+ * stale, so we fail to complete the following two
+ * steps to fully marking stale 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 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 stale ones to list,
+ * so we can scan their parents.
+ */
+ if (c->object.flags & STALE)
+ 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 stale, so
+ * mark all its parents stale. 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 & STALE)) {
+ p->object.flags |= STALE;
+ commit_list_insert(p, &list);
+ }
+ }
+ }
+}
+
+struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2,
+ int cleanup)
+{
+ struct commit_list *list = NULL;
+ struct commit_list *result = NULL;
+ struct commit_list *tmp = NULL;
+
+ if (rev1 == rev2)
+ return commit_list_insert(rev1, &result);
+
+ parse_commit(rev1);
+ parse_commit(rev2);
+
+ rev1->object.flags |= PARENT1;
+ rev2->object.flags |= PARENT2;
+ 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
+ & (PARENT1 | PARENT2 | STALE);
+
+ tmp = list;
+ list = list->next;
+ free(tmp);
+ if (flags == (PARENT1 | PARENT2)) {
+ 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);
+ }
+ }
+
+ if (!result)
+ goto finish;
+
+ if (result->next && list)
+ mark_reachable_commits(result, list);
+
+ /* cull duplicates */
+ for (tmp = result, list = NULL; tmp; ) {
+ struct commit *commit = tmp->item;
+ struct commit_list *next = tmp->next;
+ if (commit->object.flags & STALE) {
+ if (list != NULL)
+ list->next = next;
+ free(tmp);
+ } else {
+ if (list == NULL)
+ result = tmp;
+ list = tmp;
+ commit->object.flags |= STALE;
+ }
+
+ tmp = next;
+ }
+
+ finish:
+ if (cleanup) {
+ clear_commit_marks(rev1, PARENT1 | PARENT2 | STALE);
+ clear_commit_marks(rev2, PARENT1 | PARENT2 | STALE);
+ }
+
+ return result;
+}
diff --git a/commit.h b/commit.h
index 7c9ca3fbed728d946e48c787720b3ab5ad575a85..779ed82ed0ce2fb1a643b1cfa0ffe0fd3107ed09 100644 (file)
--- a/commit.h
+++ b/commit.h
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 */
diff --git a/compat/subprocess.py b/compat/subprocess.py
index bbd26c7b0e80798f1bf4e92b6150bd30c44f3143..6474eab1192fd2f66c810dc49a35b8b99db8ff1f 100644 (file)
--- a/compat/subprocess.py
+++ b/compat/subprocess.py
# 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:
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):
# 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
diff --git a/config.c b/config.c
index ec44827da4afbcf89a5eed37c7f557a7ffdce35d..8445f7dcab3bdf96326b8d6a3e1b8c8a8161cb23 100644 (file)
--- a/config.c
+++ b/config.c
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;
}
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;
diff --git a/connect.c b/connect.c
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 (;;) {
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);
*/
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;
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;
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;
}
*/
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;
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;
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;
}
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)
}
}
-# 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 2453cdcfae1f168df4533745e5730215be2af3c7..3f6ed699f0848cab6709e736e245b968e963978c 100644 (file)
--- a/contrib/emacs/vc-git.el
+++ b/contrib/emacs/vc-git.el
"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
+++ /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
--- a/contrib/git-svn/Makefile
+++ /dev/null
@@ -1,44 +0,0 @@
-all: git-svn
-
-prefix?=$(HOME)
-bindir=$(prefix)/bin
-mandir=$(prefix)/man
-man1=$(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)$(man1)
-
-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
+++ /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
+++ /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
+++ /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
+++ /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
+++ /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
+++ /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
+++ /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
+++ /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
+++ /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
diff --git a/convert-objects.c b/convert-objects.c
index 0fabd8981c6047e5144cfb3776b4f4a5588f24e5..ebea8e472bb2eede02219999b0cfd9a63f211d4b 100644 (file)
--- a/convert-objects.c
+++ b/convert-objects.c
@@ -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;
diff --git a/csum-file.c b/csum-file.c
index ebaad0397f0bce50db7542abb904ba42b6173344..6a7b40fd09ea9aa365d70dc8019f83e481192c07 100644 (file)
--- a/csum-file.c
+++ b/csum-file.c
void *out;
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+ deflateInit(&stream, zlib_compression_level);
maxsize = deflateBound(&stream, size);
out = xmalloc(maxsize);
diff --git a/diff-delta.c b/diff-delta.c
index 8b9172aa2ef7402b0a847815bb11a26d1c4444fe..7da9205a5da6a4f016bc6b556bee01895cdcc19d 100644 (file)
--- a/diff-delta.c
+++ b/diff-delta.c
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;
/*
* 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).
/*
* 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)
index fbb6c26cd9266e0ae453bb6c321f52d4996446a6..287a927ce3131fd3a5c9151e5ea1e560842767a1 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;
#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,
+ COLOR_RESET,
+ COLOR_NORMAL,
+ COLOR_BOLD,
+ COLOR_CYAN,
+ COLOR_RED,
+ COLOR_GREEN
};
static int parse_diff_color_slot(const char *var, int ofs)
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);
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"))
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);
}
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)
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;
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))
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)
(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;
{
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;
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))
}
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;
}
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;
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));
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)
}
}
-void diff_flush(struct diff_options *options)
+struct patch_id_t {
+ struct xdiff_emit_state xm;
+ SHA_CTX *ctx;
+ int patchlen;
+};
+
+static int remove_space(char *line, int len)
+{
+ int i;
+ char *dst = line;
+ unsigned char c;
+
+ for (i = 0; i < len; i++)
+ if (!isspace((c = line[i])))
+ *dst++ = c;
+
+ return dst - line;
+}
+
+static void patch_id_consume(void *priv, char *line, unsigned long len)
+{
+ struct patch_id_t *data = priv;
+ int new_len;
+
+ /* Ignore line numbers when computing the SHA1 of the patch */
+ if (!strncmp(line, "@@ -", 4))
+ return;
+
+ new_len = remove_space(line, len);
+
+ SHA1_Update(data->ctx, line, new_len);
+ data->patchlen += new_len;
+}
+
+/* returns 0 upon success, and writes result into sha1 */
+static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i;
- int diff_output_format = options->output_format;
- struct diffstat_t *diffstat = NULL;
+ SHA_CTX ctx;
+ struct patch_id_t data;
+ char buffer[PATH_MAX * 4 + 20];
- if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
- diffstat = xcalloc(sizeof (struct diffstat_t), 1);
- diffstat->xm.consume = diffstat_consume;
+ SHA1_Init(&ctx);
+ memset(&data, 0, sizeof(struct patch_id_t));
+ data.ctx = &ctx;
+ data.xm.consume = patch_id_consume;
+
+ for (i = 0; i < q->nr; i++) {
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+ mmfile_t mf1, mf2;
+ struct diff_filepair *p = q->queue[i];
+ int len1, len2;
+
+ if (p->status == 0)
+ return error("internal diff status error");
+ if (p->status == DIFF_STATUS_UNKNOWN)
+ continue;
+ if (diff_unmodified_pair(p))
+ continue;
+ if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+ (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+ continue;
+ if (DIFF_PAIR_UNMERGED(p))
+ continue;
+
+ diff_fill_sha1_info(p->one);
+ diff_fill_sha1_info(p->two);
+ if (fill_mmfile(&mf1, p->one) < 0 ||
+ fill_mmfile(&mf2, p->two) < 0)
+ return error("unable to read files to diff");
+
+ /* Maybe hash p->two? into the patch id? */
+ if (mmfile_is_binary(&mf2))
+ continue;
+
+ len1 = remove_space(p->one->path, strlen(p->one->path));
+ len2 = remove_space(p->two->path, strlen(p->two->path));
+ if (p->one->mode == 0)
+ len1 = snprintf(buffer, sizeof(buffer),
+ "diff--gita/%.*sb/%.*s"
+ "newfilemode%06o"
+ "---/dev/null"
+ "+++b/%.*s",
+ len1, p->one->path,
+ len2, p->two->path,
+ p->two->mode,
+ len2, p->two->path);
+ else if (p->two->mode == 0)
+ len1 = snprintf(buffer, sizeof(buffer),
+ "diff--gita/%.*sb/%.*s"
+ "deletedfilemode%06o"
+ "---a/%.*s"
+ "+++/dev/null",
+ len1, p->one->path,
+ len2, p->two->path,
+ p->one->mode,
+ len1, p->one->path);
+ else
+ len1 = snprintf(buffer, sizeof(buffer),
+ "diff--gita/%.*sb/%.*s"
+ "---a/%.*s"
+ "+++b/%.*s",
+ len1, p->one->path,
+ len2, p->two->path,
+ len1, p->one->path,
+ len2, p->two->path);
+ SHA1_Update(&ctx, buffer, len1);
+
+ xpp.flags = XDF_NEED_MINIMAL;
+ xecfg.ctxlen = 3;
+ xecfg.flags = XDL_EMIT_FUNCNAMES;
+ ecb.outf = xdiff_outf;
+ ecb.priv = &data;
+ xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
- if (options->with_raw) {
+ SHA1_Final(sha1, &ctx);
+ return 0;
+}
+
+int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i;
+ int result = diff_get_patch_id(options, sha1);
+
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+
+ free(q->queue);
+ q->queue = NULL;
+ q->nr = q->alloc = 0;
+
+ return result;
+}
+
+static int is_summary_empty(const struct diff_queue_struct *q)
+{
+ int i;
+
+ 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 (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;
index b61fdc8f1e4f34b608ed69cbbac6f74b81d47b53..a06f959938d1313e45daa610a1f3eb71b4c3f835 100644 (file)
--- a/diff.h
+++ b/diff.h
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;
int rename_limit;
int setup;
int abbrev;
+ const char *msg_sep;
const char *stat_sep;
long xdl_opts;
#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 *);
" -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 */
extern int run_diff_index(struct rev_info *revs, int cached);
+extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
+
#endif /* DIFF_H */
diff --git a/diffcore-rename.c b/diffcore-rename.c
index d57e8656cdcf1b999df30c01d3a8a042bc3ebb75..1de8d32502a77b641e004e9df9da3fcc188eef9c 100644 (file)
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
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)
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)
/* 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 */
+ }
}
}
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;
diff --git a/environment.c b/environment.c
index 3de8eb3b2a2359a9f8a9f702076292f1e1429df3..97d42b172be5abbada30da97893c77e67a0bcde0 100644 (file)
--- a/environment.c
+++ b/environment.c
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;
diff --git a/fetch-clone.c b/fetch-clone.c
index c16b0c481bb4b7dd3810e7dfbcb21243572f6844..81d13712962dfcf16c4f0f5a574b6950f6c3f154 100644 (file)
--- a/fetch-clone.c
+++ b/fetch-clone.c
/*
* 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 ;)
diff --git a/fsck-objects.c b/fsck-objects.c
index 769bb2a6a75f34192cfd7d6f99ea8f6efbfdce7e..ef54a8a411b01bb4f6becd24c68fad3fcbd15211 100644 (file)
--- a/fsck-objects.c
+++ b/fsck-objects.c
static void check_connectivity(void)
{
- int i;
+ int i, max;
/* Look up all the requirements, warn about missing objects.. */
- for (i = 0; i < obj_allocs; i++) {
+ max = get_max_object_index();
+ for (i = 0; i < max; i++) {
const struct object_refs *refs;
- struct object *obj = objs[i];
+ struct object *obj = get_indexed_object(i);
if (!obj)
continue;
diff --git a/git-am.sh b/git-am.sh
index 679045a540c60e747074a9127bb7003f8a510b1c..db9a20a811199666fb64f34f1e54e0c841f494ee 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
# 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 || {
diff --git a/git-annotate.perl b/git-annotate.perl
index a6a7a482cdcf34e3a1d545c7d5deab599a7c0d2f..6db2f48241d0f393e43413ddb52d0986c523e372 100755 (executable)
--- a/git-annotate.perl
+++ b/git-annotate.perl
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()),
)
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 '') {
# 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;
}
diff --git a/git-applypatch.sh b/git-applypatch.sh
index e4b09472e1fa27a6431b431572af7353c4f0a9b2..8df2aee4c2d031ac10c66af0a079bd022c281c0d 100755 (executable)
--- a/git-applypatch.sh
+++ b/git-applypatch.sh
# 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
diff --git a/git-archimport.perl b/git-archimport.perl
index 740bc1fd52286dfb486570bf6ea727e9cbaefbfc..ada60ec240a8b3eb4ef6efea6b6223fc8523705f 100755 (executable)
--- a/git-archimport.perl
+++ b/git-archimport.perl
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.
# $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:
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.
}
-# 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;
diff --git a/git-bisect.sh b/git-bisect.sh
index 03df1433ef12014584f23f16e65124c219c2f466..06a8d26945a679b06438308ceb96c69cd76c43db 100755 (executable)
--- a/git-bisect.sh
+++ b/git-bisect.sh
. git-sh-setup
sq() {
- perl -e '
+ @@PERL@@ -e '
for (@ARGV) {
s/'\''/'\'\\\\\'\''/g;
print " '\''$_'\''";
diff --git a/git-clone.sh b/git-clone.sh
index 6a14b2591136d0bbfbec333c2304a7f003134ac2..036880388370a955deba19da1194600a420baef4 100755 (executable)
--- a/git-clone.sh
+++ b/git-clone.sh
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
diff --git a/git-commit.sh b/git-commit.sh
index 7e50cf399b70a5ec26824f0bf5b5210dbe06aedc..802dd7243e233b7be773154311c3d8791fc7d610 100755 (executable)
--- a/git-commit.sh
+++ b/git-commit.sh
NEXT_INDEX="$GIT_DIR/next-index$$"
rm -f "$NEXT_INDEX"
save_index () {
- cp "$THIS_INDEX" "$NEXT_INDEX"
+ cp -p "$THIS_INDEX" "$NEXT_INDEX"
}
report () {
git-ls-files -z --others $option \
--exclude-per-directory=.gitignore
fi |
- perl -e '$/ = "\0";
+ @@PERL@@ -e '$/ = "\0";
my $shown = 0;
while (<>) {
chomp;
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
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
fi
PARENTS=""
current=
+ rloga='commit (initial)'
fi
if test -z "$no_edit"
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..5d13a54194d23ceaa50bc52a4f192156898d661a 100755 (executable)
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
}
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];
@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`;
}
}
-# 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 {
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 5ccca4f99f31b9fa370baaf7e85795ed2bf49f13..c30ef7042751034c5efbc4c2752e5badc69d4f4e 100755 (executable)
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
#$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);
my @committedfiles = ();
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
my $committedfile = $filename;
$updater->update();
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @committedfiles )
{
$filename = filecleanup($filename);
# 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);
# 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);
# 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);
chdir $tmpdir;
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
$filename = filecleanup($filename);
diff --git a/git-fetch.sh b/git-fetch.sh
index 48818f822451b3d69e7cd90e81e34bf137ead4dd..ff1769952b704d3ee3123395274da76e8f09a373 100755 (executable)
--- a/git-fetch.sh
+++ b/git-fetch.sh
'
IFS="$LF"
+rloga=fetch
no_tags=
tags=
append=
-k|--k|--ke|--kee|--keep)
keep=--keep
;;
+ --reflog-action=*)
+ rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
-*)
usage
;;
rref=
rsync_slurped_objects=
+rloga="$rloga $remote_nick"
+test "$remote_nick" = "$remote" || rloga="$rloga $remote"
+
if test "" = "$append"
then
: >"$GIT_DIR/FETCH_HEAD"
[ "$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/*)
*,$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
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."
}
else
echo >&2 "* $1: storing $3"
- git-update-ref "$1" "$2"
+ git-update-ref -m "$rloga: storing head" "$1" "$2"
fi
;;
esac
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;
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
--- a/git-fmt-merge-msg.perl
+++ /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
--- /dev/null
+++ b/git-instaweb.sh
@@ -0,0 +1,235 @@
+#!/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
+ cat >> "$conf" <<EOF
+LoadModule cgi_module $module_path/mod_cgi.so
+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
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
index 5619409f1c7ee9c694c2de59aff195f971502226..fba4b0cb5fffbb1ad3f39c670c6364975c52186a 100755 (executable)
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
# $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)
#
diff --git a/git-merge-recursive.py b/git-merge-recursive.py
index ce8a31fda050f36b24b8dffa5ee29e7dde074963..4039435ce4a938fdf9cae4eaa74303d5cc7527d0 100755 (executable)
--- a/git-merge-recursive.py
+++ b/git-merge-recursive.py
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
diff --git a/git-pull.sh b/git-pull.sh
index 076785c96b30b4fd8ab944324fd53db7c584567a..d337bf4da31c8a86eeaee7b34b910611100ccca8 100755 (executable)
--- a/git-pull.sh
+++ b/git-pull.sh
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"
diff --git a/git-push.sh b/git-push.sh
index f10cadbf159bcd63af0c3834bd7cd2fab6cce57c..21775fc21ae6668b04972ef942b2d90a789f4af6 100755 (executable)
--- a/git-push.sh
+++ b/git-push.sh
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
diff --git a/git-quiltimport.sh b/git-quiltimport.sh
index 86b51abd21748c19235a703b1b523525d05383cd..364baff80645efb225e433dbf5845e678df60760 100755 (executable)
--- a/git-quiltimport.sh
+++ b/git-quiltimport.sh
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
diff --git a/git-rebase.sh b/git-rebase.sh
index 3945e067141ec1bc87456179a59de8f692bdf9fa..1b9e986926cf5b7d25afc6e0dc81ff9fc56c370a 100755 (executable)
--- a/git-rebase.sh
+++ b/git-rebase.sh
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"
diff --git a/git-repack.sh b/git-repack.sh
index eb75c8cda95dabe190cfe9ddba0c6bd7072615f2..640ad8d90b9a9afd00506a9697af1d3e560e1255 100755 (executable)
--- a/git-repack.sh
+++ b/git-repack.sh
fi
mkdir -p "$PACKDIR" || exit
- mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
- mv .tmp-pack-$name.idx "$PACKDIR/pack-$name.idx" ||
- exit
+ for sfx in pack idx
+ do
+ if test -f "$PACKDIR/pack-$name.$sfx"
+ then
+ mv -f "$PACKDIR/pack-$name.$sfx" \
+ "$PACKDIR/old-pack-$name.$sfx"
+ fi
+ done &&
+ mv -f .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
+ mv -f .tmp-pack-$name.idx "$PACKDIR/pack-$name.idx" &&
+ test -f "$PACKDIR/pack-$name.pack" &&
+ test -f "$PACKDIR/pack-$name.idx" || {
+ echo >&2 "Couldn't replace the existing pack with updated one."
+ echo >&2 "The original set of packs have been saved as"
+ echo >&2 "old-pack-$name.{pack,idx} in $PACKDIR."
+ exit 1
+ }
+ rm -f "$PACKDIR/old-pack-$name.pack" "$PACKDIR/old-pack-$name.idx"
fi
if test "$remove_redundant" = t
diff --git a/git-reset.sh b/git-reset.sh
index 46451d0d64bdff2c0061d100c8d3b3bd38c21454..5c0224090a252bed3258bbbb127e84e61a874197 100755 (executable)
--- a/git-reset.sh
+++ b/git-reset.sh
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
diff --git a/git-send-email.perl b/git-send-email.perl
index c5d9e733512ddd4d266c85d4f5cdec4a7d74fa56..a83c7e90948fc3fe1b1ac82335704d66d060edab 100755 (executable)
--- a/git-send-email.perl
+++ b/git-send-email.perl
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;
# 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:
--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
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`;
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
--- /dev/null
+++ b/git-svn.perl
@@ -0,0 +1,3378 @@
+#!/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)$/;
+$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";
+ }
+ }
+ $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/git-svnimport.perl b/git-svnimport.perl
index 38ac732ca9b677a5647a96db44f7133b47db0648..26dc45479532a78febb511bf36017d6106513e70 100755 (executable)
--- a/git-svnimport.perl
+++ b/git-svnimport.perl
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"
index ca8961f0732bacc4efdb3afe9abc2e829ff69ee5..49062ca66e4f37d53f16d4bff10dc71b1c97e6db 100644 (file)
--- a/git.c
+++ b/git.c
{ "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 }
};
int i;
cmd = *++argv;
argc--;
+ if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
+ setup_pager();
+ continue;
+ }
+
if (strncmp(cmd, "--", 2))
break;
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 035e76d0a306e0f7dc74f01c8177a53a9286b006..2fd1e5f78ec8c2eb743c54cc0281821958141ce4 100755 (executable)
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
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;
}
}
-my $order = $cgi->param('o');
+our $order = $cgi->param('o');
if (defined $order) {
if ($order =~ m/[^0-9a-zA-Z_]/) {
undef $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);
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)) {
}
}
-my $hash = $cgi->param('h');
+our $hash = $cgi->param('h');
if (defined $hash) {
$hash = validate_input($hash);
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)) {
}
}
-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)) {
}
}
-my $page = $cgi->param('pg');
+our $page = $cgi->param('pg');
if (defined $page) {
if ($page =~ m/[^0-9]$/) {
undef $page;
}
}
-my $searchtext = $cgi->param('s');
+our $searchtext = $cgi->param('s');
if (defined $searchtext) {
if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
undef $searchtext;
my $status = shift || "200 OK";
my $expires = shift;
- my $title = "git";
+ my $title = "$site_name git";
if (defined $project) {
$title .= " - $project";
if (defined $action) {
}
}
}
- 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">
<!-- 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"/>
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;
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})$/) {
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;
@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";
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;
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;
$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);
}
"<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" .
$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" .
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) {
}
}
-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;
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 : '');
}
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";
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);
}
}
$/ = "\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";
$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\">" .
"</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";
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');
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" .
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";
" | " . $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;
$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.");
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.");
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) {
"</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;
}
print "</td>\n" .
"</tr>\n";
- undef $commit;
}
}
print "</table>\n";
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;
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>) {
" | " . $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;
diff --git a/http-fetch.c b/http-fetch.c
index 44eba5fd0df292255dce056ec5aa700186096b39..12493fbed2822ce41b5b4c23fb85d6f4a47c104a 100644 (file)
--- a/http-fetch.c
+++ b/http-fetch.c
{
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;
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) {
okay = 1;
}
}
- // skip 'objects' at end
+ /* skip 'objects' at end */
if (okay) {
target = xmalloc(serverlen + posn - i - 6);
strlcpy(target, base, serverlen);
diff --git a/http-push.c b/http-push.c
index e281f70e544d1e59c47f61ce14a728ba5ef44a44..f761584d7edf25147b731405c2996478c7177935 100644 (file)
--- a/http-push.c
+++ b/http-push.c
/* 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);
diff --git a/log-tree.c b/log-tree.c
index ebb49f29701b2435d66eb92fc2a5c382299e8ee8..9d8d46fa0038202bd49dee9cf5d1a60f781e5d7d 100644 (file)
--- a/log-tree.c
+++ b/log-tree.c
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;
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;
}
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;
diff --git a/log-tree.h b/log-tree.h
index a26e4841ff9f568f8536f286e3fc40f09b9dff3b..e82b56a20d3cfad318a4af6ea78fbe098653211d 100644 (file)
--- a/log-tree.h
+++ b/log-tree.h
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
diff --git a/merge-base.c b/merge-base.c
index 4856ca01c33896843c366f3fb5edadd2ea3ced37..59f723f4047f831165b8d52ac13def398e663ac3 100644 (file)
--- a/merge-base.c
+++ b/merge-base.c
#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;
}
index f0fe5285b24d5a19c7d41b437a18a3851ae90802..27f4c4f0413c5a7bb16106d1a0c1d5381c1159d2 100644 (file)
--- a/mktag.c
+++ b/mktag.c
* in that size, you're doing something wrong.
*/
-// Some random size
+/* Some random size */
#define MAXSIZE (8192)
/*
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");
diff --git a/name-rev.c b/name-rev.c
index 3a5ac35d163bae5431c9cad8c9e31918c2734241..083d067e17f6bfbdbd936b0fbe3683ac4f1f7b5b 100644 (file)
--- a/name-rev.c
+++ b/name-rev.c
#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;
fwrite(p_start, p - p_start, 1, stdout);
}
} else if (all) {
- int i;
+ int i, max;
- for (i = 0; i < obj_allocs; i++)
- if (objs[i])
- printf("%s %s\n", sha1_to_hex(objs[i]->sha1),
- get_rev_name(objs[i]));
+ max = get_max_object_index();
+ for (i = 0; i < max; i++) {
+ struct object * obj = get_indexed_object(i);
+ if (!obj)
+ continue;
+ printf("%s %s\n", sha1_to_hex(obj->sha1), get_rev_name(obj));
+ }
} else {
int i;
for (i = 0; i < revs.nr; i++)
diff --git a/object.c b/object.c
index 37784cee9a90a7e5b44dd660d6b7ea9191b59486..37277f94384fff1320381a370dd4d6c91a4ded2c 100644 (file)
--- a/object.c
+++ b/object.c
#include "commit.h"
#include "tag.h"
-struct object **objs;
-static int nr_objs;
-int obj_allocs;
+static struct object **obj_hash;
+static int nr_objs, obj_hash_size;
+
+unsigned int get_max_object_index(void)
+{
+ return obj_hash_size;
+}
+
+struct object *get_indexed_object(unsigned int idx)
+{
+ return obj_hash[idx];
+}
const char *type_names[] = {
"none", "blob", "tree", "commit", "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 = TYPE_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++;
}
diff --git a/object.h b/object.h
index 6f23a9a18099cbc9fdc827f53843b1157dd10935..e0125e154fd970209d30138858f31ef2017651ac 100644 (file)
--- a/object.h
+++ b/object.h
};
extern int track_object_refs;
-extern int obj_allocs;
-extern struct object **objs;
extern const char *type_names[];
+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];
diff --git a/pack-objects.c b/pack-objects.c
index bed2497b7974bed96a6ac9478807059cb8de17cb..04a48b925b4db0e302650f7383b5ccbb948665de 100644 (file)
--- a/pack-objects.c
+++ b/pack-objects.c
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
*/
};
/*
- * 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.
* 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 */
if (trg_entry->preferred_base)
return -1;
+ /*
+ * We do not bother to try a delta that we discarded
+ * on an earlier try, but only when reusing delta data.
+ */
+ if (!no_reuse_delta && trg_entry->in_pack &&
+ trg_entry->in_pack == src_entry->in_pack)
+ return 0;
+
/*
* If the current object is at pack edge, take the depth the
* objects that depend on the current object into account --
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;
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)
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) {
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
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;
index 2d186e8bde1067a62c9615e8d7f0368f98442ca4..280f57f796c84d1c1fd54e295b542c3b33490b3e 100644 (file)
--- a/pager.c
+++ b/pager.c
else if (!*pager || !strcmp(pager, "cat"))
return;
+ pager_in_use = 1; /* means we are emitting to terminal */
+
if (pipe(fd) < 0)
return;
pid = fork();
diff --git a/peek-remote.c b/peek-remote.c
index a90cf2206925ff51b7e8c799b22c8e6cb09c7fc2..2b30980b04e68a84bd300d647e80a34c7d3e621f 100644 (file)
--- a/peek-remote.c
+++ b/peek-remote.c
"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) {
int fd[2];
pid_t pid;
int nongit = 0;
+ unsigned flags = 0;
setup_git_directory_gently(&nongit);
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);
diff --git a/ppc/sha1ppc.S b/ppc/sha1ppc.S
index e85611a4ef0598f45911357d0d2f1fc354039de4..140cb53370fb23eb56b1872a2f328a0840e2820f 100644 (file)
--- a/ppc/sha1ppc.S
+++ b/ppc/sha1ppc.S
*
* 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/read-cache.c b/read-cache.c
index 3c32aae7e8ae63bccad83a603f2b86c66965cd26..a50d3612c84d10da35b0b0e1278ecea899a4ec15 100644 (file)
--- a/read-cache.c
+++ b/read-cache.c
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;
index 713ca467368e24f6112663b7b96921f9d1bbc145..56db394459cec2cd156ee2243a1dbac33867f926 100644 (file)
--- a/refs.c
+++ b/refs.c
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)
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);
diff --git a/revision.c b/revision.c
index b963f2adfd92050310f7327e1ee10e21d5996a7c..7df9089f530d5171dc267d95eed4cf90ea51ea69 100644 (file)
--- a/revision.c
+++ b/revision.c
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;
}
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.
*/
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;
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)
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);
}
diff --git a/send-pack.c b/send-pack.c
index af93b11f238e4583f6e7fb9dab5a2224acf4ee1f..4019a4b98155841a9d335bc5a1ebdc7d114c5a4d 100644 (file)
--- a/send-pack.c
+++ b/send-pack.c
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? */
diff --git a/server-info.c b/server-info.c
index 0eb5132cc1cf09ea7dacdf659d3c03adae761acf..fdfe05a2daeeb9253b6fb6a15518aba967821cb2 100644 (file)
--- a/server-info.c
+++ b/server-info.c
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);
diff --git a/sha1_file.c b/sha1_file.c
index 817963045b81cef36bf3a9c76b7399a70226a181..8734d501fec50dc16b298e1473749c64ec25fe81 100644 (file)
--- a/sha1_file.c
+++ b/sha1_file.c
{
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);
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, void *map, unsigned long mapsize, void *buffer, unsigned long size)
{
/* Get the data stream */
memset(stream, 0, sizeof(*stream));
@@ -720,7 +720,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;
@@ -1458,7 +1458,7 @@ 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);
+ deflateInit(&stream, zlib_compression_level);
size = deflateBound(&stream, len+hdrlen);
compressed = xmalloc(size);
@@ -1504,14 +1504,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);
/*
* 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
*
diff --git a/ssh-fetch.c b/ssh-fetch.c
index 1e59cd2008d8089efef74b940d1a1094add7e687..28f7fd9174e8c48493983b17fa7b18cdecd41609 100644 (file)
--- a/ssh-fetch.c
+++ b/ssh-fetch.c
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--;
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);
diff --git a/t/Makefile b/t/Makefile
index 632c55f6d5d66c8e39e213284e163a23b32c2260..89835093fbadf3c55cdf42f7490c6bac98ef2553 100644 (file)
--- a/t/Makefile
+++ b/t/Makefile
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
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:
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 1148b0257dafd4e9b63e030052b47b527b5c0d3b..b6a2edd8878c910ad177b8b277bedf99f60d9416 100644 (file)
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
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
--- /dev/null
+++ b/t/lib-git-svn.sh
@@ -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"
+
+
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index df3e9933658595c2690d4b83e3d5adb2289b887f..04fab26621af3aee3a44913ab290a4893b4f23ca 100755 (executable)
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
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
--- /dev/null
+++ b/t/t4013-diff-various.sh
@@ -0,0 +1,247 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Various diff formatting options'
+
+. ./test-lib.sh
+
+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 &&
+
+ 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
--- /dev/null
@@ -0,0 +1,34 @@
+$ git diff-tree --cc --patch-with-stat --summary master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+ 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
--- /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
--- /dev/null
@@ -0,0 +1,34 @@
+$ git diff-tree --cc --patch-with-stat master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+ 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
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --cc --stat --summary master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+ 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
--- /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
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --cc --stat master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+ 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
--- /dev/null
@@ -0,0 +1,30 @@
+$ git diff-tree --cc master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /dev/null
@@ -0,0 +1,5 @@
+$ git diff-tree -c --abbrev master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+::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
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree -c --stat --summary master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+ 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
--- /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
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree -c --stat master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+ 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
--- /dev/null
@@ -0,0 +1,5 @@
+$ git diff-tree -c master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+::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
--- /dev/null
@@ -0,0 +1,80 @@
+$ git diff-tree -p -m master
+176b998f5d647cbd77a9d8acf4531e930754d16d
+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
+176b998f5d647cbd77a9d8acf4531e930754d16d
+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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /dev/null
@@ -0,0 +1,165 @@
+$ git format-patch --attach --stdout initial..master
+From 7952a93e09bf565b5592766a438b40cd81f4846f 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
+---
+ 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="7952a93e09bf565b5592766a438b40cd81f4846f.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="7952a93e09bf565b5592766a438b40cd81f4846f.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 889b315013ef9f2e2f90aa0b054b267c8a557847 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="889b315013ef9f2e2f90aa0b054b267c8a557847.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="889b315013ef9f2e2f90aa0b054b267c8a557847.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^
--- /dev/null
@@ -0,0 +1,106 @@
+$ git format-patch --attach --stdout initial..master^
+From 7952a93e09bf565b5592766a438b40cd81f4846f 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
+---
+ 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="7952a93e09bf565b5592766a438b40cd81f4846f.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="7952a93e09bf565b5592766a438b40cd81f4846f.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 889b315013ef9f2e2f90aa0b054b267c8a557847 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="889b315013ef9f2e2f90aa0b054b267c8a557847.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="889b315013ef9f2e2f90aa0b054b267c8a557847.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
--- /dev/null
@@ -0,0 +1,60 @@
+$ 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
--- /dev/null
@@ -0,0 +1,120 @@
+$ git format-patch --stdout initial..master
+From 7952a93e09bf565b5592766a438b40cd81f4846f 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
+---
+ 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 889b315013ef9f2e2f90aa0b054b267c8a557847 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^
--- /dev/null
@@ -0,0 +1,76 @@
+$ git format-patch --stdout initial..master^
+From 7952a93e09bf565b5592766a438b40cd81f4846f 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
+---
+ 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 889b315013ef9f2e2f90aa0b054b267c8a557847 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
--- /dev/null
@@ -0,0 +1,45 @@
+$ 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_
--- /dev/null
@@ -0,0 +1,72 @@
+$ git log --patch-with-stat --summary master -- dir/
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,127 @@
+$ git log --patch-with-stat master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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_
--- /dev/null
@@ -0,0 +1,72 @@
+$ git log --patch-with-stat master -- dir/
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,197 @@
+$ git log --root --cc --patch-with-stat --summary master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,165 @@
+$ git log --root --patch-with-stat --summary master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,159 @@
+$ git log --root --patch-with-stat master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,197 @@
+$ git log --root -c --patch-with-stat --summary master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,140 @@
+$ git log --root -p master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+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
--- /dev/null
@@ -0,0 +1,32 @@
+$ git log --root master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+commit 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+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
--- /dev/null
@@ -0,0 +1,18 @@
+$ git log -SF -p master
+commit 889b315013ef9f2e2f90aa0b054b267c8a557847
+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
--- /dev/null
@@ -0,0 +1,8 @@
+$ git log -SF master
+commit 889b315013ef9f2e2f90aa0b054b267c8a557847
+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
--- /dev/null
@@ -0,0 +1,113 @@
+$ git log -p master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+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
--- /dev/null
+++ b/t/t4013/diff.log_master
@@ -0,0 +1,32 @@
+$ git log master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+commit 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /dev/null
+++ b/t/t4013/diff.show_master
@@ -0,0 +1,36 @@
+$ git show master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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
--- /dev/null
+++ b/t/t4013/diff.show_side
@@ -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_
--- /dev/null
@@ -0,0 +1,59 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,114 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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_
--- /dev/null
@@ -0,0 +1,59 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,197 @@
+$ git whatchanged --root --cc --patch-with-stat --summary master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,158 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,152 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,197 @@
+$ git whatchanged --root -c --patch-with-stat --summary master
+commit 176b998f5d647cbd77a9d8acf4531e930754d16d
+Merge: 889b315... 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+---
+ 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
--- /dev/null
@@ -0,0 +1,133 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+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
--- /dev/null
@@ -0,0 +1,40 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+: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
--- /dev/null
@@ -0,0 +1,18 @@
+$ git whatchanged -SF -p master
+commit 889b315013ef9f2e2f90aa0b054b267c8a557847
+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
--- /dev/null
@@ -0,0 +1,9 @@
+$ git whatchanged -SF master
+commit 889b315013ef9f2e2f90aa0b054b267c8a557847
+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
--- /dev/null
@@ -0,0 +1,100 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+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
--- /dev/null
@@ -0,0 +1,30 @@
+$ 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 889b315013ef9f2e2f90aa0b054b267c8a557847
+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 7952a93e09bf565b5592766a438b40cd81f4846f
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+:100644 100644 35d242b... 8422d40... M dir/sub
+:100644 100644 01e79c3... b414108... M file0
+:100644 000000 01e79c3... 0000000... D file2
+$
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
--- /dev/null
+++ b/t/t4014-format-patch.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Format-patch skipping already incorporated patches'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file &&
+ git add file &&
+ git commit -m Initial &&
+ git checkout -b side &&
+
+ for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file &&
+ git update-index file &&
+ git commit -m "Side change #1" &&
+
+ for i in D E F; do echo "$i"; done >>file &&
+ git update-index file &&
+ git commit -m "Side change #2" &&
+ git tag C2 &&
+
+ for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >file &&
+ git update-index file &&
+ git commit -m "Side change #3" &&
+
+ git checkout master &&
+ git diff-tree -p C2 | git apply --index &&
+ git commit -m "Master accepts moral equivalent of #2"
+
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream" '
+
+ git format-patch --stdout master..side >patch0 &&
+ cnt=`grep "^From " patch0 | wc -l` &&
+ test $cnt = 3
+
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream" '
+
+ git format-patch --stdout \
+ --ignore-if-in-upstream master..side >patch1 &&
+ cnt=`grep "^From " patch1 | wc -l` &&
+ test $cnt = 2
+
+'
+
+test_expect_success "format-patch result applies" '
+
+ git checkout -b rebuild-0 master &&
+ git am -3 patch0 &&
+ cnt=`git rev-list master.. | wc -l` &&
+ test $cnt = 2
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream result applies" '
+
+ git checkout -b rebuild-1 master &&
+ git am -3 patch1 &&
+ cnt=`git rev-list master.. | wc -l` &&
+ test $cnt = 2
+'
+
+test_done
index 916ee15ba1ed840fbd0df9d91e4f06fae8fde357..dd9caad1c2a34a6721aafa1840916a5a7471ece8 100755 (executable)
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)
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)
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
index 262381379f247a74f1656cfdd7f9b0c6d386d372..8f7366da8d53feb95d08fd57c04b96c7bcbb9625 100755 (executable)
#
# 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
--- /dev/null
+++ b/t/t6200-fmt-merge-msg.sh
@@ -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
diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh
index 2496397da3c335997779bae43ba37ec6d32810de..3a6490e8f864b3b3193a7b86d54a0e8d81d0dd20 100755 (executable)
--- a/t/t8001-annotate.sh
+++ b/t/t8001-annotate.sh
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
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index a61da1efbdb6ac547c1d1167a4caa849b1ee4f9b..e9ea33c18d8e0ffa2612e52748bbab4bf13ef513 100755 (executable)
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
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
--- /dev/null
+++ b/t/t9100-git-svn-basic.sh
@@ -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
--- /dev/null
+++ b/t/t9101-git-svn-props.sh
@@ -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
--- /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
--- /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
--- /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
--- /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
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 05f6e79560ff7e3d92c47221b3ee7adec098e676..b0d7990a669cb053e5112bd459e9ecaffbd4c504 100755 (executable)
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
PAGER=cat
TZ=UTC
export LANG LC_ALL PAGER TZ
+EDITOR=:
+VISUAL=:
unset AUTHOR_DATE
unset AUTHOR_EMAIL
unset AUTHOR_NAME
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:
#
index d7a8f0a849523ebc280e06fe836f3bc448993d0c..76d5ac2477d669aabfef8108cfbb713fbdfef70a 100644 (file)
--- a/templates/hooks--update
+++ b/templates/hooks--update
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
;;
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
--- /dev/null
+++ b/test-sha1.c
@@ -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
--- /dev/null
+++ b/test-sha1.sh
@@ -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/upload-pack.c b/upload-pack.c
index 2b70c3dcb4e3b1e73d7d5a088ae73de17540b6b7..b18eb9ba0dbe8b5020aaaa698d98a4d1b76aa49c 100644 (file)
--- a/upload-pack.c
+++ b/upload-pack.c
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/poll.h>
#include "cache.h"
#include "refs.h"
#include "pkt-line.h"
#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>";
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index ed7ad2041c6f81df8b0191374c5b0e4e91683a70..d76e76a0e6d987cdec985e304ad682020fef5e1f 100644 (file)
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
* 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)
* 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)
/*
* 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;;) {
/*