author | Junio C Hamano <junkio@cox.net> | |
Wed, 26 Jul 2006 20:35:35 +0000 (13:35 -0700) | ||
committer | Junio C Hamano <junkio@cox.net> | |
Wed, 26 Jul 2006 20:35:35 +0000 (13:35 -0700) |
* pb/configure:
Rename man1 and man7 variables to man1dir and man7dir
Allow INSTALL, bindir, mandir to be set in main Makefile
Rename man1 and man7 variables to man1dir and man7dir
Allow INSTALL, bindir, mandir to be set in main Makefile
298 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
diff --git a/Documentation/Makefile b/Documentation/Makefile
index cc83610588c24e2da863449facd656fb8c07907f..0d9ffb4ad9475de730ab2ee240d8b528b44017e8 100644 (file)
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
html: $(DOC_HTML)
+$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN7): asciidoc.conf
man: man1 man7
man1: $(DOC_MAN1)
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 7ce71510de52805c2bfff59f4c13d8267b9e3f25..8196d787ab13475200f7c0e739b21f525ba9e4ae 100644 (file)
[attributes]
caret=^
+startsb=[
+endsb=]
ifdef::backend-docbook[]
[gitlink-inlinemacro]
index a04c5adf8e522e65fae58ec32db07c46aeebe070..465eb13e76dca5cf4daad9d5ea773f4743452c75 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.
+
+core.legacyheaders::
+ A boolean which enables the legacy object header format in case
+ you want to interoperate with old clients accessing the object
+ database directly (where the "http://" and "rsync://" protocols
+ count as direct access).
+
alias.*::
Command aliases for the gitlink:git[1] command wrapper - e.g.
after defining "alias.last = cat-file commit HEAD", the invocation
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].
Whether to include summaries of merged commits in newly created
merge commit messages. False by default.
+pack.window::
+ The size of the window used by gitlink:git-pack-objects[1] when no
+ window size is given on the command line. Defaults to 10.
+
pull.octopus::
The default merge strategy to use when pulling multiple branches
at once.
The default set of branches for gitlink:git-show-branch[1].
See gitlink:git-show-branch[1].
+tar.umask::
+ By default, git-link:git-tar-tree[1] sets file and directories modes
+ to 0666 or 0777. While this is both useful and acceptable for projects
+ such as the Linux Kernel, it might be excessive for other projects.
+ With this variable, it becomes possible to tell
+ git-link:git-tar-tree[1] to apply a specific umask to the modes above.
+ The special value "user" indicates that the user's current umask will
+ be used. This should be enough for most projects, as it will lead to
+ the same permissions as git-link:git-checkout[1] would use. The default
+ value remains 0, which means world read-write.
+
user.email::
Your email address to be recorded in any newly created commits.
Can be overridden by the 'GIT_AUTHOR_EMAIL' and 'GIT_COMMITTER_EMAIL'
index 1fbca83141e20599e4d6edd880b1cabbf0cd9913..d2b0bd38de7fb39ccff5687013784ea8ceb22f1c 100644 (file)
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..092d0d6730294a5ce701522575bc1f8d91755a1d 100644 (file)
SYNOPSIS
--------
-'git-cvsexportcommmit' [-h] [-v] [-c] [-p] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-v] [-c] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
DESCRIPTION
commit if any hunks fail to apply or there were other problems.
-p::
- Be pedantic (paranoid) when applying patches. Invokes patch with
+ Be pedantic (paranoid) when applying patches. Invokes patch with
--fuzz=0
+-a::
+ Add authorship information. Adds Author line, and Committer (if
+ different from Author) to the message.
+
-f::
Force the merge even if the files are not up to date.
index 481b8b3aa09539ff71a3f48b0b50b75e9ccce7a7..7248b35d952c3fe97496895059af35300694eb98 100644 (file)
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 4ca0014dac64729bee11b6a37bd730b538b2e5ee..67425dc0359ee9774200e1a2e12913d570117e7c 100644 (file)
SYNOPSIS
--------
[verse]
-'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--attach]
+'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--attach] [--thread]
[-s | --signoff] [--diff-options] [--start-number <n>]
+ [--in-reply-to=Message-Id]
<since>[..<until>]
DESCRIPTION
If -n is specified, instead of "[PATCH] Subject", the first line
is formatted as "[PATCH n/m] Subject".
+If given --thread, git-format-patch will generate In-Reply-To and
+References headers to make the second and subsequent patch mails appear
+as replies to the first mail; this also generates a Message-Id header to
+reference.
OPTIONS
-------
--attach::
Create attachments instead of inlining patches.
+--thread::
+ Add In-Reply-To and References headers to make the second and
+ subsequent mails appear as replies to the first. Also generates
+ the Message-Id header to reference.
+
+--in-reply-to=Message-Id::
+ Make the first mail (or all the mails with --no-thread) appear as a
+ reply to the given Message-Id, which avoids breaking threads to
+ provide a new patch series.
CONFIGURATION
-------------
index 8a150d861f5b4c3dc468d46fa09dbfce5be020fc..0a4fc14b97fe690f9546c5d6734dfaaa9d0d656f 100644 (file)
-----------
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 4d8a2ad2d753426a697d9e82ff76e6ad278572db..8520b971111e8b015ac3d08b207a3ab37505ccca 100644 (file)
An example:
--------------------------------------------------------------
- $ cat .git/ignore
+ $ cat .git/info/exclude
# ignore objects and archives, anywhere in the tree.
*.[oa]
$ cat Documentation/.gitignore
!foo.html
$ git-ls-files --ignored \
--exclude='Documentation/*.[0-9]' \
- --exclude-from=.git/ignore \
+ --exclude-from=.git/info/exclude \
--exclude-per-directory=.gitignore
--------------------------------------------------------------
index 209e36bacb03c166e1c0c61e8c6e14447cd44f6a..5a17801f6a8d4d307f55e62745a400ad498ebdb9 100644 (file)
-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 f2675c41933fc4f824f871cc9185a6a1bf010d2b..7a99acf2ec92f9bbab84b664d10bb442482c4911 100644 (file)
Instead of making a tar archive from local repository,
retrieve a tar archive from a remote repository.
-Examples
+CONFIGURATION
+-------------
+By default, file and directories modes are set to 0666 or 0777. It is
+possible to change this by setting the "umask" variable in the
+repository configuration as follows :
+
+[tar]
+ umask = 002 ;# group friendly
+
+The special umask value "user" indicates that the user's current umask
+will be used instead. The default value remains 0, which means world
+readable/writable files and directories.
+
+EXAMPLES
--------
git tar-tree HEAD junk | (cd /var/tmp/ && tar xf -)::
index d79523f56d063aa4a3ee3181fa224694c7970613..0914cbb0ba528653aa50c6a2eb27815bd69d3508 100644 (file)
- *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..ce3058182fead833c45daf02a2806f151b15f61f 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:
------------
gitlink:git-diff-files[1];
gitlink:git-diff-tree[1]
+other
+~~~~~
+'GIT_TRACE'::
+ If this variable is set git will print `trace:` messages on
+ stderr telling about alias expansion, built-in command
+ execution and external command execution.
+
Discussion[[Discussion]]
------------------------
include::README[]
index 116ddb7fbfd7654bb7330e8f56dd03a9be029cd1..14449ca8baeb7c2c979d0fd3f61ce683fd8e08ce 100644 (file)
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.
index b52dfdc3081537c68f2fb12635fc986b0221dd4a..275d18bb545f3b14ad1f7de50d4224f184a507f8 100644 (file)
info/exclude::
This file, by convention among Porcelains, stores the
- exclude pattern list. `git status` looks at it, but
- otherwise it is not looked at by any of the core git
- commands.
+ exclude pattern list. `.gitignore` is the per-directory
+ ignore file. `git status`, `git add`, `git rm` and `git
+ clean` look at it but the core git commands do not look
+ at it. See also: gitlink:git-ls-files[1] `--exclude-from`
+ and `--exclude-per-directory`.
remotes::
Stores shorthands to be used to give URL and default
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..26ecba53fbfad4af4da46680330f998bf92f1b3f 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://{startsb}user@{endsb}host.xz/path/to/repo.git/
+- ssh://{startsb}user@{endsb}host.xz/~user/path/to/repo.git/
+- ssh://{startsb}user@{endsb}host.xz/~/path/to/repo.git
===============================================================
-SSH Is the default transport protocol and also supports an
-scp-like syntax. Both syntaxes support username expansion,
+SSH is the default transport protocol. You can optionally specify
+which user to log-in as, and an alternate, scp-like syntax is also
+supported. Both syntaxes support username expansion,
as does the native git protocol. The following three are
identical to the last three above, respectively:
===============================================================
-- host.xz:/path/to/repo.git/
-- host.xz:~user/path/to/repo.git/
-- host.xz:path/to/repo.git
+- {startsb}user@{endsb}host.xz:/path/to/repo.git/
+- {startsb}user@{endsb}host.xz:~user/path/to/repo.git/
+- {startsb}user@{endsb}host.xz:path/to/repo.git
===============================================================
To sync with a local directory, use:
<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 ccd7c62e5760ec140b5860b30fd557ec3900fa3e..49eaa10b3be884a2aebdcea51ca50d0018e869f5 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 NO_FINK if you are building on Darwin/Mac OS X, have Fink
+# installed in /sw, but don't want GIT to link against any libraries
+# installed there. If defined you may specify your own (or Fink's)
+# include directories and library directories by defining CFLAGS
+# and LDFLAGS appropriately.
+#
+# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X,
+# have DarwinPorts installed in /opt/local, but don't want GIT to
+# link against any libraries installed there. If defined you may
+# specify your own (or DarwinPort's) include directories and
+# library directories by defining CFLAGS and LDFLAGS appropriately.
+#
# Define PPC_SHA1 environment variable when running make to make use of
# a bundled SHA1 routine optimized for PowerPC.
#
# 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
git-fetch.sh \
git-ls-remote.sh \
git-merge-one-file.sh git-parse-remote.sh \
- git-prune.sh git-pull.sh git-rebase.sh \
+ git-pull.sh git-rebase.sh \
git-repack.sh git-request-pull.sh git-reset.sh \
git-resolve.sh git-revert.sh git-sh-setup.sh \
git-tag.sh git-verify-tag.sh \
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 git-prune$X
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
- alloc.o $(DIFF_OBJS)
+ alloc.o merge-file.o $(DIFF_OBJS)
BUILTIN_OBJS = \
builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
builtin-apply.o builtin-show-branch.o builtin-diff-files.o \
builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \
builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \
- builtin-update-ref.o
+ builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
LIBS = $(GITLIBS) -lz
NEEDS_SSL_WITH_CRYPTO = YesPlease
NEEDS_LIBICONV = YesPlease
NO_STRLCPY = YesPlease
- ## fink
- ifeq ($(shell test -d /sw/lib && echo y),y)
- ALL_CFLAGS += -I/sw/include
- ALL_LDFLAGS += -L/sw/lib
+ ifndef NO_FINK
+ ifeq ($(shell test -d /sw/lib && echo y),y)
+ ALL_CFLAGS += -I/sw/include
+ ALL_LDFLAGS += -L/sw/lib
+ endif
endif
- ## darwinports
- ifeq ($(shell test -d /opt/local/lib && echo y),y)
- ALL_CFLAGS += -I/opt/local/include
- ALL_LDFLAGS += -L/opt/local/lib
+ ifndef NO_DARWIN_PORTS
+ ifeq ($(shell test -d /opt/local/lib && echo y),y)
+ ALL_CFLAGS += -I/opt/local/include
+ ALL_LDFLAGS += -L/opt/local/lib
+ endif
endif
endif
ifeq ($(uname_S),SunOS)
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 | sed "s|/usr/bin/git|$(bindir)/git|" > $@+
+ 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),
index 496f270043a617242efa9d27285853feb2597484..d1af2e62f14965fab491a3d9dde712f02b718adc 100644 (file)
--- a/blob.c
+++ b/blob.c
if (!obj) {
struct blob *ret = alloc_blob_node();
created_object(sha1, &ret->object);
- ret->object.type = TYPE_BLOB;
+ ret->object.type = OBJ_BLOB;
return ret;
}
if (!obj->type)
- obj->type = TYPE_BLOB;
- if (obj->type != TYPE_BLOB) {
+ obj->type = OBJ_BLOB;
+ if (obj->type != OBJ_BLOB) {
error("Object %s is a %s, not a blob",
sha1_to_hex(sha1), typename(obj->type));
return NULL;
diff --git a/builtin-add.c b/builtin-add.c
index bfbbb1bf52e10667f94dd574ab32fe070c98c4cb..3a73a173f729e0ca75e650779773316e8ae3fe98 100644 (file)
--- a/builtin-add.c
+++ b/builtin-add.c
for (specs = 0; pathspec[specs]; specs++)
/* nothing */;
- seen = xmalloc(specs);
- memset(seen, 0, specs);
+ seen = xcalloc(specs, 1);
src = dst = dir->entries;
i = dir->nr;
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..d924ac3d0ab663c2ceb05c2cb9943f6caa23c0ef 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;
@@ -1663,13 +1664,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
return 0;
}
-static int check_patch(struct patch *patch)
+static int check_patch(struct patch *patch, struct patch *prev_patch)
{
struct stat st;
const char *old_name = patch->old_name;
const char *new_name = patch->new_name;
const char *name = old_name ? old_name : new_name;
struct cache_entry *ce = NULL;
+ int ok_if_exists;
if (old_name) {
int changed = 0;
old_name, st_mode, patch->old_mode);
}
+ if (new_name && prev_patch && prev_patch->is_delete &&
+ !strcmp(prev_patch->old_name, new_name))
+ /* A type-change diff is always split into a patch to
+ * delete old, immediately followed by a patch to
+ * create new (see diff.c::run_diff()); in such a case
+ * it is Ok that the entry to be deleted by the
+ * previous patch is still in the working tree and in
+ * the index.
+ */
+ ok_if_exists = 1;
+ else
+ ok_if_exists = 0;
+
if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
- if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0)
+ if (check_index &&
+ cache_name_pos(new_name, strlen(new_name)) >= 0 &&
+ !ok_if_exists)
return error("%s: already exists in index", new_name);
if (!cached) {
- if (!lstat(new_name, &st))
- return error("%s: already exists in working directory", new_name);
- if (errno != ENOENT)
+ struct stat nst;
+ if (!lstat(new_name, &nst)) {
+ if (S_ISDIR(nst.st_mode) || ok_if_exists)
+ ; /* ok */
+ else
+ return error("%s: already exists in working directory", new_name);
+ }
+ else if ((errno != ENOENT) && (errno != ENOTDIR))
return error("%s: %s", new_name, strerror(errno));
}
if (!patch->new_mode) {
static int check_patch_list(struct patch *patch)
{
+ struct patch *prev_patch = NULL;
int error = 0;
- for (;patch ; patch = patch->next)
- error |= check_patch(patch);
+ for (prev_patch = NULL; patch ; patch = patch->next) {
+ error |= check_patch(patch, prev_patch);
+ prev_patch = patch;
+ }
return error;
}
@@ -2009,6 +2034,16 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
return;
}
+ if (errno == EEXIST || errno == EACCES) {
+ /* We may be trying to create a file where a directory
+ * used to be.
+ */
+ struct stat st;
+ errno = 0;
+ if (!lstat(path, &st) && S_ISDIR(st.st_mode) && !rmdir(path))
+ errno = EEXIST;
+ }
+
if (errno == EEXIST) {
unsigned int nr = getpid();
cache_tree_invalidate_path(active_cache_tree, path);
}
-static void write_out_one_result(struct patch *patch)
+/* phase zero is to remove, phase one is to create */
+static void write_out_one_result(struct patch *patch, int phase)
{
if (patch->is_delete > 0) {
- remove_file(patch);
+ if (phase == 0)
+ remove_file(patch);
return;
}
if (patch->is_new > 0 || patch->is_copy) {
- create_file(patch);
+ if (phase == 1)
+ create_file(patch);
return;
}
/*
* Rename or modification boils down to the same
* thing: remove the old, write the new
*/
- remove_file(patch);
+ if (phase == 0)
+ remove_file(patch);
+ if (phase == 1)
create_file(patch);
}
static void write_out_results(struct patch *list, int skipped_patch)
{
+ int phase;
+
if (!list && !skipped_patch)
die("No changes");
- while (list) {
- write_out_one_result(list);
- list = list->next;
+ for (phase = 0; phase < 2; phase++) {
+ struct patch *l = list;
+ while (l) {
+ write_out_one_result(l, phase);
+ l = l->next;
+ }
}
}
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..cb38f445611735cff5bc9fbafb3189e6ccc6c2fd 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.
*/
obj = deref_tag(obj, NULL, 0);
if (!obj)
die("invalid object '%s' given.", name);
- if (obj->type == TYPE_COMMIT)
+ if (obj->type == OBJ_COMMIT)
obj = &((struct commit *)obj)->tree->object;
- if (obj->type == TYPE_TREE) {
+ if (obj->type == OBJ_TREE) {
if (ARRAY_SIZE(ent) <= ents)
die("more than %d trees given: '%s'",
(int) ARRAY_SIZE(ent), name);
ents++;
continue;
}
- if (obj->type == TYPE_BLOB) {
+ if (obj->type == OBJ_BLOB) {
if (2 <= blobs)
die("more than two blobs given: '%s'", name);
memcpy(blob[blobs].sha1, obj->sha1, 20);
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
--- /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 != OBJ_COMMIT)
+ return;
+
+ setup_revisions(0, NULL, rev, NULL);
+ rev->ignore_merges = 1;
+ add_pending_object(rev, branch, name);
+ add_pending_object(rev, &head->object, "^HEAD");
+ head->object.flags |= UNINTERESTING;
+ prepare_revision_walk(rev);
+ while ((commit = get_revision(rev)) != NULL) {
+ char *oneline, *bol, *eol;
+
+ /* ignore merges */
+ if (commit->parents && commit->parents->next)
+ continue;
+
+ count++;
+ if (subjects.nr > limit)
+ continue;
+
+ bol = strstr(commit->buffer, "\n\n");
+ if (!bol) {
+ append_to_list(&subjects, strdup(sha1_to_hex(
+ commit->object.sha1)),
+ NULL);
+ continue;
+ }
+
+ bol += 2;
+ eol = strchr(bol, '\n');
+
+ if (eol) {
+ int len = eol - bol;
+ oneline = malloc(len + 1);
+ memcpy(oneline, bol, len);
+ oneline[len] = 0;
+ } else
+ oneline = strdup(bol);
+ append_to_list(&subjects, oneline, NULL);
+ }
+
+ if (count > limit)
+ printf("\n* %s: (%d commits)\n", name, count);
+ else
+ printf("\n* %s:\n", name);
+
+ for (i = 0; i < subjects.nr; i++)
+ if (i >= limit)
+ printf(" ...\n");
+ else
+ printf(" %s\n", subjects.list[i]);
+
+ clear_commit_marks((struct commit *)branch, flags);
+ clear_commit_marks(head, flags);
+ free_commit_list(rev->commits);
+ rev->commits = NULL;
+ rev->pending.nr = 0;
+
+ free_list(&subjects);
+}
+
+int cmd_fmt_merge_msg(int argc, char **argv, char **envp)
+{
+ int limit = 20, i = 0;
+ char line[1024];
+ FILE *in = stdin;
+ const char *sep = "";
+ unsigned char head_sha1[20];
+ const char *head, *current_branch;
+
+ git_config(fmt_merge_msg_config);
+
+ while (argc > 1) {
+ if (!strcmp(argv[1], "--summary"))
+ merge_summary = 1;
+ else if (!strcmp(argv[1], "--no-summary"))
+ merge_summary = 0;
+ else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
+ if (argc < 2)
+ die ("Which file?");
+ if (!strcmp(argv[2], "-"))
+ in = stdin;
+ else {
+ fclose(in);
+ in = fopen(argv[2], "r");
+ }
+ argc--; argv++;
+ } else
+ break;
+ argc--; argv++;
+ }
+
+ if (argc > 1)
+ usage(fmt_merge_msg_usage);
+
+ /* get current branch */
+ head = strdup(git_path("HEAD"));
+ current_branch = resolve_ref(head, head_sha1, 1);
+ current_branch += strlen(head) - 4;
+ free((char *)head);
+ if (!strncmp(current_branch, "refs/heads/", 11))
+ current_branch += 11;
+
+ while (fgets(line, sizeof(line), in)) {
+ i++;
+ if (line[0] == 0)
+ continue;
+ if (handle_line(line))
+ die ("Error in line %d: %s", i, line);
+ }
+
+ printf("Merge ");
+ for (i = 0; i < srcs.nr; i++) {
+ struct src_data *src_data = srcs.payload[i];
+ const char *subsep = "";
+
+ printf(sep);
+ sep = "; ";
+
+ if (src_data->head_status == 1) {
+ printf(srcs.list[i]);
+ continue;
+ }
+ if (src_data->head_status == 3) {
+ subsep = ", ";
+ printf("HEAD");
+ }
+ if (src_data->branch.nr) {
+ printf(subsep);
+ subsep = ", ";
+ print_joined("branch ", "branches ", &src_data->branch);
+ }
+ if (src_data->r_branch.nr) {
+ printf(subsep);
+ subsep = ", ";
+ print_joined("remote branch ", "remote branches ",
+ &src_data->r_branch);
+ }
+ if (src_data->tag.nr) {
+ printf(subsep);
+ subsep = ", ";
+ print_joined("tag ", "tags ", &src_data->tag);
+ }
+ if (src_data->generic.nr) {
+ printf(subsep);
+ print_joined("commit ", "commits ", &src_data->generic);
+ }
+ if (strcmp(".", srcs.list[i]))
+ printf(" of %s", srcs.list[i]);
+ }
+
+ if (!strcmp("master", current_branch))
+ putchar('\n');
+ else
+ printf(" into %s\n", current_branch);
+
+ if (merge_summary) {
+ struct commit *head;
+ struct rev_info rev;
+
+ head = lookup_commit(head_sha1);
+ init_revisions(&rev);
+ rev.commit_format = CMIT_FMT_ONELINE;
+ rev.ignore_merges = 1;
+ rev.limited = 1;
+
+ for (i = 0; i < origins.nr; i++)
+ shortlog(origins.list[i], origins.payload[i],
+ head, &rev, limit);
+ }
+
+ /* No cleanup yet; is standalone anyway */
+
+ return 0;
+}
+
diff --git a/builtin-grep.c b/builtin-grep.c
index 2e7986cecefc964f665b10d363569951c1f40293..a79bac305aea880d6decb787836fe86295ead32e 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 int grep_object(struct grep_opt *opt, const char **paths,
struct object *obj, const char *name)
{
- if (obj->type == TYPE_BLOB)
+ if (obj->type == OBJ_BLOB)
return grep_sha1(opt, obj->sha1, name);
- if (obj->type == TYPE_COMMIT || obj->type == TYPE_TREE) {
+ if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
struct tree_desc tree;
void *data;
int hit;
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 f9515a8a4ac8a53e87b6fb9f73e1964350481bfb..4052cc75bdb2e3915ff17af4d1dc59a3f84f99dd 100644 (file)
--- a/builtin-log.c
+++ b/builtin-log.c
#include "revision.h"
#include "log-tree.h"
#include "builtin.h"
+#include <time.h>
+#include <sys/time.h>
/* this is in builtin-diff.c */
void add_head(struct rev_info *revs);
-static int cmd_log_wc(int argc, const char **argv, char **envp,
+static void cmd_log_init(int argc, const char **argv, char **envp,
struct rev_info *rev)
{
- struct commit *commit;
-
rev->abbrev = DEFAULT_ABBREV;
rev->commit_format = CMIT_FMT_DEFAULT;
rev->verbose_header = 1;
argc = setup_revisions(argc, argv, rev, "HEAD");
- if (rev->always_show_header) {
- if (rev->diffopt.pickaxe || rev->diffopt.filter) {
- rev->always_show_header = 0;
- if (rev->diffopt.output_format == DIFF_FORMAT_RAW)
- rev->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
- }
- }
-
+ if (rev->diffopt.pickaxe || rev->diffopt.filter)
+ rev->always_show_header = 0;
if (argc > 1)
die("unrecognized argument: %s", argv[1]);
+}
+
+static int cmd_log_walk(struct rev_info *rev)
+{
+ struct commit *commit;
prepare_revision_walk(rev);
setup_pager();
{
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);
}
o2->flags = flags2;
}
+static void gen_message_id(char *dest, unsigned int length, char *base)
+{
+ const char *committer = git_committer_info(1);
+ const char *email_start = strrchr(committer, '<');
+ const char *email_end = strrchr(committer, '>');
+ if(!email_start || !email_end || email_start > email_end - 1)
+ die("Could not extract email from committer identity.");
+ snprintf(dest, length, "%s.%lu.git.%.*s", base,
+ (unsigned long) time(NULL),
+ (int)(email_end - email_start - 1), email_start + 1);
+}
+
int cmd_format_patch(int argc, const char **argv, char **envp)
{
struct commit *commit;
int start_number = -1;
int keep_subject = 0;
int ignore_if_in_upstream = 0;
+ int thread = 0;
+ const char *in_reply_to = NULL;
struct diff_options patch_id_opts;
char *add_signoff = NULL;
+ char message_id[1024];
+ char ref_message_id[1024];
+ git_config(git_format_config);
init_revisions(&rev);
rev.commit_format = CMIT_FMT_EMAIL;
rev.verbose_header = 1;
rev.diff = 1;
- rev.diffopt.with_raw = 0;
- rev.diffopt.with_stat = 1;
rev.combine_merges = 0;
rev.ignore_merges = 1;
- rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev.diffopt.msg_sep = "";
+ rev.diffopt.recursive = 1;
- git_config(git_format_config);
rev.extra_headers = extra_headers;
/*
rev.mime_boundary = argv[i] + 9;
else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
ignore_if_in_upstream = 1;
+ else if (!strcmp(argv[i], "--thread"))
+ thread = 1;
+ else if (!strncmp(argv[i], "--in-reply-to=", 14))
+ in_reply_to = argv[i] + 14;
+ else if (!strcmp(argv[i], "--in-reply-to")) {
+ i++;
+ if (i == argc)
+ die("Need a Message-Id for --in-reply-to");
+ in_reply_to = argv[i];
+ }
else
argv[j++] = argv[i];
}
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?");
if (numbered)
rev.total = total + start_number - 1;
rev.add_signoff = add_signoff;
+ rev.ref_message_id = in_reply_to;
while (0 <= --nr) {
int shown;
commit = list[nr];
rev.nr = total - nr + (start_number - 1);
+ /* Make the second and subsequent mails replies to the first */
+ if (thread) {
+ if (nr == (total - 2)) {
+ strncpy(ref_message_id, message_id,
+ sizeof(ref_message_id));
+ ref_message_id[sizeof(ref_message_id)-1]='\0';
+ rev.ref_message_id = ref_message_id;
+ }
+ gen_message_id(message_id, sizeof(message_id),
+ sha1_to_hex(commit->object.sha1));
+ rev.message_id = message_id;
+ }
if (!use_stdout)
reopen_stdout(commit, rev.nr, keep_subject);
shown = log_tree_commit(&rev, commit);
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-prune.c b/builtin-prune.c
--- /dev/null
+++ b/builtin-prune.c
@@ -0,0 +1,259 @@
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "builtin.h"
+#include "cache-tree.h"
+
+static const char prune_usage[] = "git prune [-n]";
+static int show_only = 0;
+static struct rev_info revs;
+
+static int prune_object(char *path, const char *filename, const unsigned char *sha1)
+{
+ if (show_only) {
+ printf("would prune %s/%s\n", path, filename);
+ return 0;
+ }
+ unlink(mkpath("%s/%s", path, filename));
+ rmdir(path);
+ return 0;
+}
+
+static int prune_dir(int i, char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *de;
+
+ if (!dir)
+ return 0;
+
+ while ((de = readdir(dir)) != NULL) {
+ char name[100];
+ unsigned char sha1[20];
+ int len = strlen(de->d_name);
+
+ switch (len) {
+ case 2:
+ if (de->d_name[1] != '.')
+ break;
+ case 1:
+ if (de->d_name[0] != '.')
+ break;
+ continue;
+ case 38:
+ sprintf(name, "%02x", i);
+ memcpy(name+2, de->d_name, len+1);
+ if (get_sha1_hex(name, sha1) < 0)
+ break;
+
+ /*
+ * Do we know about this object?
+ * It must have been reachable
+ */
+ if (lookup_object(sha1))
+ continue;
+
+ prune_object(path, de->d_name, sha1);
+ continue;
+ }
+ fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+ }
+ closedir(dir);
+ return 0;
+}
+
+static void prune_object_dir(const char *path)
+{
+ int i;
+ for (i = 0; i < 256; i++) {
+ static char dir[4096];
+ sprintf(dir, "%s/%02x", path, i);
+ prune_dir(i, dir);
+ }
+}
+
+static void process_blob(struct blob *blob,
+ struct object_array *p,
+ struct name_path *path,
+ const char *name)
+{
+ struct object *obj = &blob->object;
+
+ if (obj->flags & SEEN)
+ return;
+ obj->flags |= SEEN;
+ /* Nothing to do, really .. The blob lookup was the important part */
+}
+
+static void process_tree(struct tree *tree,
+ struct object_array *p,
+ struct name_path *path,
+ const char *name)
+{
+ struct object *obj = &tree->object;
+ struct tree_desc desc;
+ struct name_entry entry;
+ struct name_path me;
+
+ if (obj->flags & SEEN)
+ return;
+ obj->flags |= SEEN;
+ if (parse_tree(tree) < 0)
+ die("bad tree object %s", sha1_to_hex(obj->sha1));
+ name = strdup(name);
+ add_object(obj, p, path, name);
+ me.up = path;
+ me.elem = name;
+ me.elem_len = strlen(name);
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+
+ while (tree_entry(&desc, &entry)) {
+ if (S_ISDIR(entry.mode))
+ process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
+ else
+ process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
+ }
+ free(tree->buffer);
+ tree->buffer = NULL;
+}
+
+static void process_tag(struct tag *tag, struct object_array *p, const char *name)
+{
+ struct object *obj = &tag->object;
+ struct name_path me;
+
+ if (obj->flags & SEEN)
+ return;
+ obj->flags |= SEEN;
+
+ me.up = NULL;
+ me.elem = "tag:/";
+ me.elem_len = 5;
+
+ if (parse_tag(tag) < 0)
+ die("bad tag object %s", sha1_to_hex(obj->sha1));
+ add_object(tag->tagged, p, NULL, name);
+}
+
+static void walk_commit_list(struct rev_info *revs)
+{
+ int i;
+ struct commit *commit;
+ struct object_array objects = { 0, 0, NULL };
+
+ /* Walk all commits, process their trees */
+ while ((commit = get_revision(revs)) != NULL)
+ process_tree(commit->tree, &objects, NULL, "");
+
+ /* Then walk all the pending objects, recursively processing them too */
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object_array_entry *pending = revs->pending.objects + i;
+ struct object *obj = pending->item;
+ const char *name = pending->name;
+ if (obj->type == OBJ_TAG) {
+ process_tag((struct tag *) obj, &objects, name);
+ continue;
+ }
+ if (obj->type == OBJ_TREE) {
+ process_tree((struct tree *)obj, &objects, NULL, name);
+ continue;
+ }
+ if (obj->type == OBJ_BLOB) {
+ process_blob((struct blob *)obj, &objects, NULL, name);
+ continue;
+ }
+ die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
+ }
+}
+
+static int add_one_ref(const char *path, const unsigned char *sha1)
+{
+ struct object *object = parse_object(sha1);
+ if (!object)
+ die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
+ add_pending_object(&revs, object, "");
+ return 0;
+}
+
+static void add_one_tree(const unsigned char *sha1)
+{
+ struct tree *tree = lookup_tree(sha1);
+ add_pending_object(&revs, &tree->object, "");
+}
+
+static void add_cache_tree(struct cache_tree *it)
+{
+ int i;
+
+ if (it->entry_count >= 0)
+ add_one_tree(it->sha1);
+ for (i = 0; i < it->subtree_nr; i++)
+ add_cache_tree(it->down[i]->cache_tree);
+}
+
+static void add_cache_refs(void)
+{
+ int i;
+
+ read_cache();
+ for (i = 0; i < active_nr; i++) {
+ lookup_blob(active_cache[i]->sha1);
+ /*
+ * We could add the blobs to the pending list, but quite
+ * frankly, we don't care. Once we've looked them up, and
+ * added them as objects, we've really done everything
+ * there is to do for a blob
+ */
+ }
+ if (active_cache_tree)
+ add_cache_tree(active_cache_tree);
+}
+
+int cmd_prune(int argc, const char **argv, char **envp)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "-n")) {
+ show_only = 1;
+ continue;
+ }
+ usage(prune_usage);
+ }
+
+ /*
+ * Set up revision parsing, and mark us as being interested
+ * in all object types, not just commits.
+ */
+ init_revisions(&revs);
+ revs.tag_objects = 1;
+ revs.blob_objects = 1;
+ revs.tree_objects = 1;
+
+ /* Add all external refs */
+ for_each_ref(add_one_ref);
+
+ /* Add all refs from the index file */
+ add_cache_refs();
+
+ /*
+ * Set up the revision walk - this will move all commits
+ * from the pending list to the commit walking list.
+ */
+ prepare_revision_walk(&revs);
+
+ walk_commit_list(&revs);
+
+ prune_object_dir(get_object_directory());
+
+ return 0;
+}
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..122b6f130b37b27055efd7dffa25f41ef03698e8 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;
}
merge->ce_flags &= ~htons(CE_STAGEMASK);
- add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
+ add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
return 1;
}
else
verify_absent(ce->name, "removed");
ce->ce_mode = 0;
- add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
invalidate_ce_path(ce);
return 1;
}
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-list.c b/builtin-rev-list.c
index 63bad0e96ade657bef21d9ca0e0de2e0ba8af61c..8f328713374679b023f1f8caaacab592c9e4f9a2 100644 (file)
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
const char *name = pending->name;
if (obj->flags & (UNINTERESTING | SEEN))
continue;
- if (obj->type == TYPE_TAG) {
+ if (obj->type == OBJ_TAG) {
obj->flags |= SEEN;
add_object_array(obj, name, &objects);
continue;
}
- if (obj->type == TYPE_TREE) {
+ if (obj->type == OBJ_TREE) {
process_tree((struct tree *)obj, &objects, NULL, name);
continue;
}
- if (obj->type == TYPE_BLOB) {
+ if (obj->type == OBJ_BLOB) {
process_blob((struct blob *)obj, &objects, NULL, name);
continue;
}
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..bb810ba41ae23c9dc4c38fbb0fa7883a33d43524 100644 (file)
--- a/builtin-rm.c
+++ b/builtin-rm.c
seen = NULL;
for (i = 0; pathspec[i] ; i++)
/* nothing */;
- seen = xmalloc(i);
- memset(seen, 0, i);
+ seen = xcalloc(i, 1);
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
* 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..82f75b72dea1a5e6c620003d5c0c4c414dfe42e3 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;
name_parent(c, p);
i++;
}
+ else
+ break;
c = p;
}
return i;
static int mark_seen(struct commit *commit, struct commit_list **seen_p)
{
if (!commit->object.flags) {
- insert_by_date(commit, seen_p);
+ commit_list_insert(commit, seen_p);
return 1;
}
return 0;
* Postprocess to complete well-poisoning.
*
* At this point we have all the commits we have seen in
- * seen_p list (which happens to be sorted chronologically but
- * it does not really matter). Mark anything that can be
- * reached from uninteresting commits not interesting.
+ * seen_p list. Mark anything that can be reached from
+ * uninteresting commits not interesting.
*/
for (;;) {
int changed = 0;
if (0 <= extra)
join_revs(&list, &seen, num_rev, extra);
+ sort_by_date(&seen);
+
if (merge_base)
return show_merge_base(seen, num_rev);
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
index f2e48aae2a04822d26d255dddf36002f58f2093c..e5aaded820bddfe2d5175508cc9c422b55a66eec 100644 (file)
--- a/builtin-tar-tree.c
+++ b/builtin-tar-tree.c
static unsigned long offset;
static time_t archive_time;
+static int tar_umask;
/* tries hard to write, either succeeds or dies in the attempt */
static void reliable_write(const void *data, unsigned long size)
} else {
if (S_ISDIR(mode)) {
*header.typeflag = TYPEFLAG_DIR;
- mode |= 0777;
+ mode = (mode | 0777) & ~tar_umask;
} else if (S_ISLNK(mode)) {
*header.typeflag = TYPEFLAG_LNK;
mode |= 0777;
} else if (S_ISREG(mode)) {
*header.typeflag = TYPEFLAG_REG;
- mode |= (mode & 0100) ? 0777 : 0666;
+ mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
} else {
error("unsupported file mode: 0%o (SHA1: %s)",
mode, sha1_to_hex(sha1));
}
}
+int git_tar_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "tar.umask")) {
+ if (!strcmp(value, "user")) {
+ tar_umask = umask(0);
+ umask(tar_umask);
+ } else {
+ tar_umask = git_config_int(var, value);
+ }
+ return 0;
+ }
+ return git_default_config(var, value);
+}
+
static int generate_tar(int argc, const char **argv, char** envp)
{
unsigned char sha1[20], tree_sha1[20];
current_path.len = current_path.eof = 0;
setup_git_directory();
- git_config(git_default_config);
+ git_config(git_tar_config);
switch (argc) {
case 3:
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..5339d8627f48a44e3fbf3c81a0790bc467ccd459 100644 (file)
--- a/builtin.h
+++ b/builtin.h
extern int cmd_format_patch(int argc, const char **argv, char **envp);
extern int cmd_count_objects(int argc, const char **argv, char **envp);
+extern int cmd_prune(int argc, const char **argv, char **envp);
+
extern int cmd_push(int argc, const char **argv, char **envp);
extern int cmd_grep(int argc, const char **argv, char **envp);
extern int cmd_rm(int argc, const char **argv, char **envp);
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..eee5fc9f8d75af20a1d03d569c702980f6c04469 100644 (file)
--- a/cache.h
+++ b/cache.h
extern void rollback_lock_file(struct lock_file *);
/* Environment bits from configuration mechanism */
+extern int use_legacy_headers;
extern int trust_executable_bit;
extern int assume_unchanged;
extern int prefer_symlink_refs;
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..919112bba9271a8e3903b06946cf91036411f1a4 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,
/* deleted file */
result_size = 0;
elem->mode = 0;
- result = xmalloc(1);
- result[0] = 0;
+ result = xcalloc(1, 1);
}
if (0 <= fd)
close(fd);
const char *abb;
if (rev->loginfo)
- show_log(rev, rev->loginfo, "\n");
+ show_log(rev, opt->msg_sep);
dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path);
printf("index ");
for (i = 0; i < num_parent; i++) {
@@ -770,9 +769,9 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
inter_name_termination = 0;
if (rev->loginfo)
- show_log(rev, rev->loginfo, "\n");
+ show_log(rev, opt->msg_sep);
- if (opt->output_format == DIFF_FORMAT_RAW) {
+ if (opt->output_format & DIFF_FORMAT_RAW) {
offset = strlen(COLONS) - num_parent;
if (offset < 0)
offset = 0;
@@ -792,8 +791,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
printf(" %s ", diff_unique_abbrev(p->sha1, opt->abbrev));
}
- if (opt->output_format == DIFF_FORMAT_RAW ||
- opt->output_format == DIFF_FORMAT_NAME_STATUS) {
+ if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) {
for (i = 0; i < num_parent; i++)
putchar(p->parent[i].status);
putchar(inter_name_termination);
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..77f0ca175c66b0ef0b7ac5b24672b106c6f17178 100644 (file)
--- a/commit.c
+++ b/commit.c
const unsigned char *sha1,
int quiet)
{
- if (obj->type != TYPE_COMMIT) {
+ if (obj->type != OBJ_COMMIT) {
if (!quiet)
error("Object %s is a %s, not a commit",
sha1_to_hex(sha1), typename(obj->type));
if (!obj) {
struct commit *ret = alloc_commit_node();
created_object(sha1, &ret->object);
- ret->object.type = TYPE_COMMIT;
+ ret->object.type = OBJ_COMMIT;
return ret;
}
if (!obj->type)
- obj->type = TYPE_COMMIT;
+ obj->type = OBJ_COMMIT;
return check_commit(obj, sha1, 0);
}
{
struct commit_list *parents;
- parents = commit->parents;
commit->object.flags &= ~mark;
+ parents = commit->parents;
while (parents) {
struct commit *parent = parents->item;
- if (parent && parent->object.parsed &&
- (parent->object.flags & mark))
+
+ /* Have we already cleared this? */
+ if (mark & parent->object.flags)
clear_commit_marks(parent, mark);
parents = parents->next;
}
@@ -654,6 +655,9 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
continue;
}
+ if (!subject)
+ body = 1;
+
if (is_empty_line(line, &linelen)) {
if (!body)
continue;
@@ -661,8 +665,6 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
continue;
if (fmt == CMIT_FMT_SHORT)
break;
- } else {
- body = 1;
}
if (subject) {
@@ -701,6 +703,12 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
/* Make sure there is an EOLN for the non-oneline case */
if (fmt != CMIT_FMT_ONELINE)
buf[offset++] = '\n';
+ /*
+ * make sure there is another EOLN to separate the headers from whatever
+ * body the caller appends if we haven't already written a body
+ */
+ if (fmt == CMIT_FMT_EMAIL && !body)
+ buf[offset++] = '\n';
buf[offset] = '\0';
return offset;
}
}
free(nodes);
}
+
+/* merge-rebase stuff */
+
+/* bits #0..7 in revision.h */
+#define PARENT1 (1u<< 8)
+#define PARENT2 (1u<< 9)
+#define STALE (1u<<10)
+#define RESULT (1u<<11)
+
+static struct commit *interesting(struct commit_list *list)
+{
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (commit->object.flags & STALE)
+ continue;
+ return commit;
+ }
+ return NULL;
+}
+
+static struct commit_list *merge_bases(struct commit *one, struct commit *two)
+{
+ struct commit_list *list = NULL;
+ struct commit_list *result = NULL;
+
+ if (one == two)
+ /* We do not mark this even with RESULT so we do not
+ * have to clean it up.
+ */
+ return commit_list_insert(one, &result);
+
+ parse_commit(one);
+ parse_commit(two);
+
+ one->object.flags |= PARENT1;
+ two->object.flags |= PARENT2;
+ insert_by_date(one, &list);
+ insert_by_date(two, &list);
+
+ while (interesting(list)) {
+ struct commit *commit;
+ struct commit_list *parents;
+ struct commit_list *n;
+ int flags;
+
+ commit = list->item;
+ n = list->next;
+ free(list);
+ list = n;
+
+ flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->object.flags & RESULT)) {
+ commit->object.flags |= RESULT;
+ insert_by_date(commit, &result);
+ }
+ /* Mark parents of a found merge stale */
+ flags |= STALE;
+ }
+ parents = commit->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+ if ((p->object.flags & flags) == flags)
+ continue;
+ parse_commit(p);
+ p->object.flags |= flags;
+ insert_by_date(p, &list);
+ }
+ }
+
+ /* Clean up the result to remove stale ones */
+ list = result; result = NULL;
+ while (list) {
+ struct commit_list *n = list->next;
+ if (!(list->item->object.flags & STALE))
+ insert_by_date(list->item, &result);
+ free(list);
+ list = n;
+ }
+ return result;
+}
+
+struct commit_list *get_merge_bases(struct commit *one,
+ struct commit *two,
+ int cleanup)
+{
+ const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
+ struct commit_list *list;
+ struct commit **rslt;
+ struct commit_list *result;
+ int cnt, i, j;
+
+ result = merge_bases(one, two);
+ if (one == two)
+ return result;
+ if (!result || !result->next) {
+ if (cleanup) {
+ clear_commit_marks(one, all_flags);
+ clear_commit_marks(two, all_flags);
+ }
+ return result;
+ }
+
+ /* There are more than one */
+ cnt = 0;
+ list = result;
+ while (list) {
+ list = list->next;
+ cnt++;
+ }
+ rslt = xcalloc(cnt, sizeof(*rslt));
+ for (list = result, i = 0; list; list = list->next)
+ rslt[i++] = list->item;
+ free_commit_list(result);
+
+ clear_commit_marks(one, all_flags);
+ clear_commit_marks(two, all_flags);
+ for (i = 0; i < cnt - 1; i++) {
+ for (j = i+1; j < cnt; j++) {
+ if (!rslt[i] || !rslt[j])
+ continue;
+ result = merge_bases(rslt[i], rslt[j]);
+ clear_commit_marks(rslt[i], all_flags);
+ clear_commit_marks(rslt[j], all_flags);
+ for (list = result; list; list = list->next) {
+ if (rslt[i] == list->item)
+ rslt[i] = NULL;
+ if (rslt[j] == list->item)
+ rslt[j] = NULL;
+ }
+ }
+ }
+
+ /* Surviving ones in rslt[] are the independent results */
+ result = NULL;
+ for (i = 0; i < cnt; i++) {
+ if (rslt[i])
+ insert_by_date(rslt[i], &result);
+ }
+ free(rslt);
+ return result;
+}
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..0ac6aebbbcbd666d5bd8201ce28e1868bd6d5a30 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.legacyheaders")) {
+ use_legacy_headers = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (!strcmp(var, "core.compression")) {
+ int level = git_config_int(var, value);
+ if (level == -1)
+ level = Z_DEFAULT_COMPRESSION;
+ else if (level < 0 || level > Z_BEST_COMPRESSION)
+ die("bad zlib compression level %d", level);
+ zlib_compression_level = level;
+ return 0;
+ }
+
if (!strcmp(var, "user.name")) {
strlcpy(git_default_name, value, sizeof(git_default_name));
return 0;
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,
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index ebd00ef9c471f284d26fecd44e3fd7668903b50a..68de9be0c7cca8645275a042900766b6ea2712c7 100644 (file)
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
;;;; ------------------------------------------------------------
(defgroup git nil
- "Git user interface")
+ "A user interface for the git versioning system."
+ :group 'tools)
(defcustom git-committer-name nil
"User name to use for commits.
-The default is to fall back to the repository config, then to `add-log-full-name' and then to `user-full-name'."
+The default is to fall back to the repository config,
+then to `add-log-full-name' and then to `user-full-name'."
:group 'git
:type '(choice (const :tag "Default" nil)
(string :tag "Name")))
(defcustom git-committer-email nil
"Email address to use for commits.
-The default is to fall back to the git repository config, then to `add-log-mailing-address' and then to `user-mail-address'."
+The default is to fall back to the git repository config,
+then to `add-log-mailing-address' and then to `user-mail-address'."
:group 'git
:type '(choice (const :tag "Default" nil)
(string :tag "Email")))
@@ -81,11 +84,18 @@ The default is to fall back to the git repository config, then to `add-log-maili
:group 'git
:type 'boolean)
+(defcustom git-reuse-status-buffer t
+ "Whether `git-status' should try to reuse an existing buffer
+if there is already one that displays the same directory."
+ :group 'git
+ :type 'boolean)
+
(defcustom git-per-dir-ignore-file ".gitignore"
"Name of the per-directory ignore file."
:group 'git
:type 'string)
+
(defface git-status-face
'((((class color) (background light)) (:foreground "purple")))
"Git mode face used to highlight added and modified files."
@@ -149,7 +159,8 @@ The default is to fall back to the git repository config, then to `add-log-maili
(apply #'call-process "git" nil buffer nil args)))
(defun git-call-process-env-string (env &rest args)
- "Wrapper for call-process that sets environment strings, and returns the process output as a string."
+ "Wrapper for call-process that sets environment strings,
+and returns the process output as a string."
(with-temp-buffer
(and (eq 0 (apply #' git-call-process-env t env args))
(buffer-string))))
@@ -254,7 +265,7 @@ The default is to fall back to the git repository config, then to `add-log-maili
(set-buffer (find-file-noselect ignore-name))
(goto-char (point-max))
(unless (zerop (current-column)) (insert "\n"))
- (insert name "\n")
+ (insert "/" name "\n")
(sort-lines nil (point-min) (point-max))
(save-buffer))
(when created
@@ -580,6 +591,8 @@ The default is to fall back to the git repository config, then to `add-log-maili
(condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
(with-current-buffer buffer (erase-buffer))
(git-set-files-state files 'uptodate)
+ (when (file-directory-p ".git/rr-cache")
+ (git-run-command nil nil "rerere"))
(git-refresh-files)
(git-refresh-ewoc-hf git-status)
(message "Committed %s." commit))
@@ -939,6 +952,8 @@ The default is to fall back to the git repository config, then to `add-log-maili
(let ((map (make-keymap))
(diff-map (make-sparse-keymap)))
(suppress-keymap map)
+ (define-key map "?" 'git-help)
+ (define-key map "h" 'git-help)
(define-key map " " 'git-next-file)
(define-key map "a" 'git-add-file)
(define-key map "c" 'git-commit-file)
(set (make-local-variable 'list-buffers-directory) default-directory)
(run-hooks 'git-status-mode-hook)))
+(defun git-find-status-buffer (dir)
+ "Find the git status buffer handling a specified directory."
+ (let ((list (buffer-list))
+ (fulldir (expand-file-name dir))
+ found)
+ (while (and list (not found))
+ (let ((buffer (car list)))
+ (with-current-buffer buffer
+ (when (and list-buffers-directory
+ (string-equal fulldir (expand-file-name list-buffers-directory))
+ (string-match "\\*git-status\\*$" (buffer-name buffer)))
+ (setq found buffer))))
+ (setq list (cdr list)))
+ found))
+
(defun git-status (dir)
"Entry point into git-status mode."
(interactive "DSelect directory: ")
(setq dir (git-get-top-dir dir))
(if (file-directory-p (concat (file-name-as-directory dir) ".git"))
- (let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir))))
+ (let ((buffer (or (and git-reuse-status-buffer (git-find-status-buffer dir))
+ (create-file-buffer (expand-file-name "*git-status*" dir)))))
(switch-to-buffer buffer)
(cd dir)
(git-status-mode)
(goto-char (point-min)))
(message "%s is not a git working tree." dir)))
+(defun git-help ()
+ "Display help for Git mode."
+ (interactive)
+ (describe-function 'git-status-mode))
+
(provide 'git)
;;; git.el ends here
index 2453cdcfae1f168df4533745e5730215be2af3c7..3f6ed699f0848cab6709e736e245b968e963978c 100644 (file)
--- 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
-man1dir=$(mandir)/man1
-INSTALL?=install
-doc_conf=../../Documentation/asciidoc.conf
--include ../../config.mak
-
-git-svn: git-svn.perl
- cp $< $@
- chmod +x $@
-
-install: all
- $(INSTALL) -d -m755 $(DESTDIR)$(bindir)
- $(INSTALL) git-svn $(DESTDIR)$(bindir)
-
-install-doc: doc
- $(INSTALL) git-svn.1 $(DESTDIR)$(man1dir)
-
-doc: git-svn.1
-git-svn.1 : git-svn.xml
- xmlto man git-svn.xml
-git-svn.xml : git-svn.txt
- asciidoc -b docbook -d manpage \
- -f ../../Documentation/asciidoc.conf $<
-git-svn.html : git-svn.txt
- asciidoc -b xhtml11 -d manpage \
- -f ../../Documentation/asciidoc.conf $<
-test: git-svn
- cd t && for i in t????-*.sh; do $(SHELL) ./$$i $(TEST_FLAGS); done
-
-# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
-full-test:
- $(MAKE) test GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
- $(MAKE) test GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
- $(MAKE) test GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
- LC_ALL=en_US.UTF-8
- $(MAKE) test GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
- LC_ALL=en_US.UTF-8
-
-clean:
- rm -f git-svn *.xml *.html *.1
diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl
+++ /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/daemon.c b/daemon.c
index e096bd7ef638273b525848fbe0018863871ea93d..e4ec676301c965e0b0ec81995f4e1fb77363c373 100644 (file)
--- a/daemon.c
+++ b/daemon.c
va_end(params);
}
+static void NORETURN daemon_die(const char *err, va_list params)
+{
+ logreport(LOG_ERR, err, params);
+ exit(1);
+}
+
static int avoid_alias(char *p)
{
int sl, ndot;
}
}
+/* if any standard file descriptor is missing open it to /dev/null */
+static void sanitize_stdfds(void)
+{
+ int fd = open("/dev/null", O_RDWR, 0);
+ while (fd != -1 && fd < 2)
+ fd = dup(fd);
+ if (fd == -1)
+ die("open /dev/null or dup failed: %s", strerror(errno));
+ if (fd > 2)
+ close(fd);
+}
+
+static void daemonize(void)
+{
+ switch (fork()) {
+ case 0:
+ break;
+ case -1:
+ die("fork failed: %s", strerror(errno));
+ default:
+ exit(0);
+ }
+ if (setsid() == -1)
+ die("setsid failed: %s", strerror(errno));
+ close(0);
+ close(1);
+ close(2);
+ sanitize_stdfds();
+}
+
+static void store_pid(const char *path)
+{
+ FILE *f = fopen(path, "w");
+ if (!f)
+ die("cannot open pid file %s: %s", path, strerror(errno));
+ fprintf(f, "%d\n", getpid());
+ fclose(f);
+}
+
static int serve(int port)
{
int socknum, *socklist;
{
int port = DEFAULT_GIT_PORT;
int inetd_mode = 0;
+ const char *pid_file = NULL;
+ int detach = 0;
int i;
/* Without this we cannot rely on waitpid() to tell
user_path = arg + 12;
continue;
}
+ if (!strncmp(arg, "--pid-file=", 11)) {
+ pid_file = arg + 11;
+ continue;
+ }
+ if (!strcmp(arg, "--detach")) {
+ detach = 1;
+ log_syslog = 1;
+ continue;
+ }
if (!strcmp(arg, "--")) {
ok_paths = &argv[i+1];
break;
usage(daemon_usage);
}
- if (log_syslog)
+ if (log_syslog) {
openlog("git-daemon", 0, LOG_DAEMON);
-
- if (strict_paths && (!ok_paths || !*ok_paths)) {
- if (!inetd_mode)
- die("git-daemon: option --strict-paths requires a whitelist");
-
- logerror("option --strict-paths requires a whitelist");
- exit (1);
+ set_die_routine(daemon_die);
}
+ if (strict_paths && (!ok_paths || !*ok_paths))
+ die("option --strict-paths requires a whitelist");
+
if (inetd_mode) {
struct sockaddr_storage ss;
struct sockaddr *peer = (struct sockaddr *)&ss;
return execute(peer);
}
+ if (detach)
+ daemonize();
+ else
+ sanitize_stdfds();
+
+ if (pid_file)
+ store_pid(pid_file);
+
return serve(port);
}
diff --git a/describe.c b/describe.c
index 8e68d5df3303ed75c2fbb9c8b3d1025785a622c7..324ca8965b9512de59b515cdcc29801022553d1d 100644 (file)
--- a/describe.c
+++ b/describe.c
* Otherwise only annotated tags are used.
*/
if (!strncmp(path, "refs/tags/", 10)) {
- if (object->type == TYPE_TAG)
+ if (object->type == OBJ_TAG)
prio = 2;
else
prio = 1;
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 5a71489a471568fb3aa51af5d74fa269ec4fadf1..6a713764831e7521958f4b280f2e1d3911fb3cea 100644 (file)
--- a/diff.c
+++ b/diff.c
static int use_size_cache;
+static int diff_detect_rename_default = 0;
static int diff_rename_limit_default = -1;
static int diff_use_color_default = 0;
-enum color_diff {
- DIFF_RESET = 0,
- DIFF_PLAIN = 1,
- DIFF_METAINFO = 2,
- DIFF_FRAGINFO = 3,
- DIFF_FILE_OLD = 4,
- DIFF_FILE_NEW = 5,
-};
-
-#define COLOR_NORMAL ""
-#define COLOR_BOLD "\033[1m"
-#define COLOR_DIM "\033[2m"
-#define COLOR_UL "\033[4m"
-#define COLOR_BLINK "\033[5m"
-#define COLOR_REVERSE "\033[7m"
-#define COLOR_RESET "\033[m"
-
-#define COLOR_BLACK "\033[30m"
-#define COLOR_RED "\033[31m"
-#define COLOR_GREEN "\033[32m"
-#define COLOR_YELLOW "\033[33m"
-#define COLOR_BLUE "\033[34m"
-#define COLOR_MAGENTA "\033[35m"
-#define COLOR_CYAN "\033[36m"
-#define COLOR_WHITE "\033[37m"
-
-static const char *diff_colors[] = {
- [DIFF_RESET] = COLOR_RESET,
- [DIFF_PLAIN] = COLOR_NORMAL,
- [DIFF_METAINFO] = COLOR_BOLD,
- [DIFF_FRAGINFO] = COLOR_CYAN,
- [DIFF_FILE_OLD] = COLOR_RED,
- [DIFF_FILE_NEW] = COLOR_GREEN,
+/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
+static char diff_colors[][24] = {
+ "\033[m", /* reset */
+ "", /* normal */
+ "\033[1m", /* bold */
+ "\033[36m", /* cyan */
+ "\033[31m", /* red */
+ "\033[32m", /* green */
+ "\033[33m" /* yellow */
};
static int parse_diff_color_slot(const char *var, int ofs)
return DIFF_FILE_OLD;
if (!strcasecmp(var+ofs, "new"))
return DIFF_FILE_NEW;
+ if (!strcasecmp(var+ofs, "commit"))
+ return DIFF_COMMIT;
die("bad config variable '%s'", var);
}
-static const char *parse_diff_color_value(const char *value, const char *var)
-{
- if (!strcasecmp(value, "normal"))
- return COLOR_NORMAL;
- if (!strcasecmp(value, "bold"))
- return COLOR_BOLD;
- if (!strcasecmp(value, "dim"))
- return COLOR_DIM;
- if (!strcasecmp(value, "ul"))
- return COLOR_UL;
- if (!strcasecmp(value, "blink"))
- return COLOR_BLINK;
- if (!strcasecmp(value, "reverse"))
- return COLOR_REVERSE;
- if (!strcasecmp(value, "reset"))
- return COLOR_RESET;
- if (!strcasecmp(value, "black"))
- return COLOR_BLACK;
- if (!strcasecmp(value, "red"))
- return COLOR_RED;
- if (!strcasecmp(value, "green"))
- return COLOR_GREEN;
- if (!strcasecmp(value, "yellow"))
- return COLOR_YELLOW;
- if (!strcasecmp(value, "blue"))
- return COLOR_BLUE;
- if (!strcasecmp(value, "magenta"))
- return COLOR_MAGENTA;
- if (!strcasecmp(value, "cyan"))
- return COLOR_CYAN;
- if (!strcasecmp(value, "white"))
- return COLOR_WHITE;
+static int parse_color(const char *name, int len)
+{
+ static const char * const color_names[] = {
+ "normal", "black", "red", "green", "yellow",
+ "blue", "magenta", "cyan", "white"
+ };
+ char *end;
+ int i;
+ for (i = 0; i < ARRAY_SIZE(color_names); i++) {
+ const char *str = color_names[i];
+ if (!strncasecmp(name, str, len) && !str[len])
+ return i - 1;
+ }
+ i = strtol(name, &end, 10);
+ if (*name && !*end && i >= -1 && i <= 255)
+ return i;
+ return -2;
+}
+
+static int parse_attr(const char *name, int len)
+{
+ static const int attr_values[] = { 1, 2, 4, 5, 7 };
+ static const char * const attr_names[] = {
+ "bold", "dim", "ul", "blink", "reverse"
+ };
+ int i;
+ for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
+ const char *str = attr_names[i];
+ if (!strncasecmp(name, str, len) && !str[len])
+ return attr_values[i];
+ }
+ return -1;
+}
+
+static void parse_diff_color_value(const char *value, const char *var, char *dst)
+{
+ const char *ptr = value;
+ int attr = -1;
+ int fg = -2;
+ int bg = -2;
+
+ if (!strcasecmp(value, "reset")) {
+ strcpy(dst, "\033[m");
+ return;
+ }
+
+ /* [fg [bg]] [attr] */
+ while (*ptr) {
+ const char *word = ptr;
+ int val, len = 0;
+
+ while (word[len] && !isspace(word[len]))
+ len++;
+
+ ptr = word + len;
+ while (*ptr && isspace(*ptr))
+ ptr++;
+
+ val = parse_color(word, len);
+ if (val >= -1) {
+ if (fg == -2) {
+ fg = val;
+ continue;
+ }
+ if (bg == -2) {
+ bg = val;
+ continue;
+ }
+ goto bad;
+ }
+ val = parse_attr(word, len);
+ if (val < 0 || attr != -1)
+ goto bad;
+ attr = val;
+ }
+
+ if (attr >= 0 || fg >= 0 || bg >= 0) {
+ int sep = 0;
+
+ *dst++ = '\033';
+ *dst++ = '[';
+ if (attr >= 0) {
+ *dst++ = '0' + attr;
+ sep++;
+ }
+ if (fg >= 0) {
+ if (sep++)
+ *dst++ = ';';
+ if (fg < 8) {
+ *dst++ = '3';
+ *dst++ = '0' + fg;
+ } else {
+ dst += sprintf(dst, "38;5;%d", fg);
+ }
+ }
+ if (bg >= 0) {
+ if (sep++)
+ *dst++ = ';';
+ if (bg < 8) {
+ *dst++ = '4';
+ *dst++ = '0' + bg;
+ } else {
+ dst += sprintf(dst, "48;5;%d", bg);
+ }
+ }
+ *dst++ = 'm';
+ }
+ *dst = 0;
+ return;
+bad:
die("bad config value '%s' for variable '%s'", value, var);
}
-int git_diff_config(const char *var, const char *value)
+/*
+ * These are to give UI layer defaults.
+ * The core-level commands such as git-diff-files should
+ * never be affected by the setting of diff.renames
+ * the user happens to have in the configuration file.
+ */
+int git_diff_ui_config(const char *var, const char *value)
{
if (!strcmp(var, "diff.renamelimit")) {
diff_rename_limit_default = git_config_int(var, value);
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);
+ parse_diff_color_value(value, var, diff_colors[slot]);
return 0;
}
return git_default_config(var, value);
const char **label_path;
};
-static inline const char *get_color(int diff_use_color, enum color_diff ix)
+const char *diff_get_color(int diff_use_color, enum color_diff ix)
{
if (diff_use_color)
return diff_colors[ix];
{
int i;
struct emit_callback *ecbdata = priv;
- const char *set = get_color(ecbdata->color_diff, DIFF_METAINFO);
- const char *reset = get_color(ecbdata->color_diff, DIFF_RESET);
+ const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
+ const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
if (ecbdata->label_path[0]) {
printf("%s--- %s%s\n", set, ecbdata->label_path[0], reset);
;
if (2 <= i && i < len && line[i] == ' ') {
ecbdata->nparents = i - 1;
- set = get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+ set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
}
else if (len < ecbdata->nparents)
set = reset;
else if (line[i] == '+')
color = DIFF_FILE_NEW;
}
- set = get_color(ecbdata->color_diff, color);
+ set = diff_get_color(ecbdata->color_diff, color);
}
if (len > 0 && line[len-1] == '\n')
len--;
- printf("%s%.*s%s\n", set, (int) len, line, reset);
+ fputs (set, stdout);
+ fwrite (line, len, 1, stdout);
+ puts (reset);
}
static char *pprint_rename(const char *a, const char *b)
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;
mmfile_t mf1, mf2;
const char *lbl[2];
char *a_one, *b_two;
- const char *set = get_color(o->color_diff, DIFF_METAINFO);
- const char *reset = get_color(o->color_diff, DIFF_RESET);
+ const char *set = diff_get_color(o->color_diff, DIFF_METAINFO);
+ const char *reset = diff_get_color(o->color_diff, DIFF_RESET);
a_one = quote_two("a/", name_a);
b_two = quote_two("b/", name_b);
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)
return result;
}
-void diff_flush(struct diff_options *options)
+static int is_summary_empty(const struct diff_queue_struct *q)
{
- struct diff_queue_struct *q = &diff_queued_diff;
int i;
- int diff_output_format = options->output_format;
- struct diffstat_t *diffstat = NULL;
- if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
- diffstat = xcalloc(sizeof (struct diffstat_t), 1);
- diffstat->xm.consume = diffstat_consume;
+ for (i = 0; i < q->nr; i++) {
+ const struct diff_filepair *p = q->queue[i];
+
+ switch (p->status) {
+ case DIFF_STATUS_DELETED:
+ case DIFF_STATUS_ADDED:
+ case DIFF_STATUS_COPIED:
+ case DIFF_STATUS_RENAMED:
+ return 0;
+ default:
+ if (p->score)
+ return 0;
+ if (p->one->mode && p->two->mode &&
+ p->one->mode != p->two->mode)
+ return 0;
+ break;
+ }
}
+ return 1;
+}
+
+void diff_flush(struct diff_options *options)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i, output_format = options->output_format;
+ int separator = 0;
+
+ /*
+ * Order: raw, stat, summary, patch
+ * or: name/name-status/checkdiff (other bits clear)
+ */
+ if (!q->nr)
+ goto free_queue;
- if (options->with_raw) {
+ if (output_format & (DIFF_FORMAT_RAW |
+ DIFF_FORMAT_NAME |
+ DIFF_FORMAT_NAME_STATUS |
+ DIFF_FORMAT_CHECKDIFF)) {
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
- flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+ if (check_pair_status(p))
+ flush_one_pair(p, options);
}
- putchar(options->line_termination);
+ separator++;
}
- if (options->with_stat) {
+
+ if (output_format & DIFF_FORMAT_DIFFSTAT) {
+ struct diffstat_t diffstat;
+
+ memset(&diffstat, 0, sizeof(struct diffstat_t));
+ diffstat.xm.consume = diffstat_consume;
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
- flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
- diffstat);
+ if (check_pair_status(p))
+ diff_flush_stat(p, options, &diffstat);
}
- show_stats(diffstat);
- free(diffstat);
- diffstat = NULL;
- if (options->summary)
- for (i = 0; i < q->nr; i++)
- diff_summary(q->queue[i]);
- if (options->stat_sep)
- fputs(options->stat_sep, stdout);
- else
- putchar(options->line_termination);
- }
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- flush_one_pair(p, diff_output_format, options, diffstat);
+ show_stats(&diffstat);
+ separator++;
}
- if (diffstat) {
- show_stats(diffstat);
- free(diffstat);
+ if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
+ for (i = 0; i < q->nr; i++)
+ diff_summary(q->queue[i]);
+ separator++;
}
- for (i = 0; i < q->nr; i++) {
- if (diffstat && options->summary)
- diff_summary(q->queue[i]);
- diff_free_filepair(q->queue[i]);
+ if (output_format & DIFF_FORMAT_PATCH) {
+ if (separator) {
+ if (options->stat_sep) {
+ /* attach patch instead of inline */
+ fputs(options->stat_sep, stdout);
+ } else {
+ putchar(options->line_termination);
+ }
+ }
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (check_pair_status(p))
+ diff_flush_patch(p, options);
+ }
}
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+free_queue:
free(q->queue);
q->queue = NULL;
q->nr = q->alloc = 0;
index d5068af7d16a0905edfeaa24276461c05437f3be..2cced530fa60ad7206b2cdb1b2509f42469e8f73 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;
add_remove_fn_t add_remove;
};
+enum color_diff {
+ DIFF_RESET = 0,
+ DIFF_PLAIN = 1,
+ DIFF_METAINFO = 2,
+ DIFF_FRAGINFO = 3,
+ DIFF_FILE_OLD = 4,
+ DIFF_FILE_NEW = 5,
+ DIFF_COMMIT = 6,
+};
+const char *diff_get_color(int diff_use_color, enum color_diff ix);
+
extern const char mime_boundary_leader[];
extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
#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 */
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..42f39d657ef0056667bb8814622e0dde2733e373 100644 (file)
--- a/environment.c
+++ b/environment.c
char git_default_email[MAX_GITNAME];
char git_default_name[MAX_GITNAME];
+int use_legacy_headers = 1;
int trust_executable_bit = 1;
int assume_unchanged = 0;
int prefer_symlink_refs = 0;
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/exec_cmd.c b/exec_cmd.c
index c1539d12ce9e350811b5b110206f5577a151e8ef..62f51fcd6e367d2dc7e3dc8d967ee05fd1648d04 100644 (file)
--- a/exec_cmd.c
+++ b/exec_cmd.c
#include "cache.h"
#include "exec_cmd.h"
+#include "quote.h"
#define MAX_ARGS 32
extern char **environ;
tmp = argv[0];
argv[0] = git_command;
+ if (getenv("GIT_TRACE")) {
+ const char **p = argv;
+ fputs("trace: exec:", stderr);
+ while (*p) {
+ fputc(' ', stderr);
+ sq_quote_print(stderr, *p);
+ ++p;
+ }
+ putc('\n', stderr);
+ fflush(stderr);
+ }
+
/* execve() can only ever return if it fails */
execve(git_command, (char **)argv, environ);
+ if (getenv("GIT_TRACE")) {
+ fprintf(stderr, "trace: exec failed: %s\n",
+ strerror(errno));
+ fflush(stderr);
+ }
+
argv[0] = tmp;
}
return -1;
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/fetch-pack.c b/fetch-pack.c
index f2c51ebe4b2b524e5d5bce6af8aa3105df19f177..b7824dbed4e10d912a0977865e24e21ff9295aae 100644 (file)
--- a/fetch-pack.c
+++ b/fetch-pack.c
{
struct object *o = deref_tag(parse_object(sha1), path, 0);
- if (o && o->type == TYPE_COMMIT)
+ if (o && o->type == OBJ_COMMIT)
rev_list_push((struct commit *)o, SEEN);
return 0;
{
struct object *o = parse_object(sha1);
- while (o && o->type == TYPE_TAG) {
+ while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
if (!t->tagged)
break; /* broken repository */
o->flags |= COMPLETE;
o = parse_object(t->tagged->sha1);
}
- if (o && o->type == TYPE_COMMIT) {
+ if (o && o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
commit->object.flags |= COMPLETE;
insert_by_date(commit, &complete);
* in sync with the other side at some time after
* that (it is OK if we guess wrong here).
*/
- if (o->type == TYPE_COMMIT) {
+ if (o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
if (!cutoff || cutoff < commit->date)
cutoff = commit->date;
struct object *o = deref_tag(lookup_object(ref->old_sha1),
NULL, 0);
- if (!o || o->type != TYPE_COMMIT || !(o->flags & COMPLETE))
+ if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
continue;
if (!(o->flags & SEEN)) {
index 238032b798008cd1f96d5083b63dc582f8191a1d..989d7a47884ec7bac05d329baeb97bdb12b7b22e 100644 (file)
--- a/fetch.c
+++ b/fetch.c
static int process_object(struct object *obj)
{
- if (obj->type == TYPE_COMMIT) {
+ if (obj->type == OBJ_COMMIT) {
if (process_commit((struct commit *)obj))
return -1;
return 0;
}
- if (obj->type == TYPE_TREE) {
+ if (obj->type == OBJ_TREE) {
if (process_tree((struct tree *)obj))
return -1;
return 0;
}
- if (obj->type == TYPE_BLOB) {
+ if (obj->type == OBJ_BLOB) {
return 0;
}
- if (obj->type == TYPE_TAG) {
+ if (obj->type == OBJ_TAG) {
if (process_tag((struct tag *)obj))
return -1;
return 0;
diff --git a/fsck-objects.c b/fsck-objects.c
index ef54a8a411b01bb4f6becd24c68fad3fcbd15211..e167f4105ffcf50f0ebfe03378b687569b6a668e 100644 (file)
--- a/fsck-objects.c
+++ b/fsck-objects.c
if (obj->flags & SEEN)
return 0;
obj->flags |= SEEN;
- if (obj->type == TYPE_BLOB)
+ if (obj->type == OBJ_BLOB)
return 0;
- if (obj->type == TYPE_TREE)
+ if (obj->type == OBJ_TREE)
return fsck_tree((struct tree *) obj);
- if (obj->type == TYPE_COMMIT)
+ if (obj->type == OBJ_COMMIT)
return fsck_commit((struct commit *) obj);
- if (obj->type == TYPE_TAG)
+ if (obj->type == OBJ_TAG)
return fsck_tag((struct tag *) obj);
/* By now, parse_object() would've returned NULL instead. */
return objerror(obj, "unknown type '%d' (internal fsck error)", obj->type);
}
mark_reachable(obj, REACHABLE);
obj->used = 1;
- if (obj->type != TYPE_TREE)
+ if (obj->type != OBJ_TREE)
err |= objerror(obj, "non-tree in cache-tree");
}
for (i = 0; i < it->subtree_nr; i++)
diff --git a/git-am.sh b/git-am.sh
index 679045a540c60e747074a9127bb7003f8a510b1c..04f01194356ea0c91a01eed682b068c15e313ee6 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 || {
}
prec=4
+rloga=am
dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
while case "$#" in 0) break;; esac
--resolvemsg=*)
resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
+ --reflog-action=*)
+ rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;;
+
--)
shift; break ;;
-*)
if test -d "$dotest"
then
- test ",$#," = ",0," ||
- die "previous dotest directory $dotest still exists but mbox given."
+ if test ",$#," != ",0," || ! tty -s
+ then
+ die "previous dotest directory $dotest still exists but mbox given."
+ fi
resume=yes
else
# Make sure we are not given --skip nor --resolved
parent=$(git-rev-parse --verify HEAD) &&
commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
echo Committed: $commit &&
- git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
+ git-update-ref -m "$rloga: $SUBJECT" HEAD $commit $parent ||
stop_here $this
if test -x "$GIT_DIR"/hooks/post-applypatch
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..a92b22a13d6b2932a8d816a8845d791c2f0e36d1 100755 (executable)
--- a/git-clone.sh
+++ b/git-clone.sh
echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates"
;;
esac
- git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD"
+ git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
;;
*)
case "$repo" in
done
rm -f "$GIT_DIR/TMP_ALT"
fi
- git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD"
+ git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
;;
http://*)
if test -z "@@NO_CURL@@"
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 22c4ce86c3cc5b35fab27bbe9e75dbe334f58534..4cf3fab05cd3c3367173c86a65665ddfc1d89805 100755 (executable)
--- a/git-commit.sh
+++ b/git-commit.sh
if test -z "$untracked_files"; then
option="--directory --no-empty-directory"
fi
+ hdr_shown=
if test -f "$GIT_DIR/info/exclude"
then
- git-ls-files -z --others $option \
+ git-ls-files --others $option \
--exclude-from="$GIT_DIR/info/exclude" \
--exclude-per-directory=.gitignore
else
- git-ls-files -z --others $option \
+ git-ls-files --others $option \
--exclude-per-directory=.gitignore
fi |
- perl -e '$/ = "\0";
- my $shown = 0;
- while (<>) {
- chomp;
- s|\\|\\\\|g;
- s|\t|\\t|g;
- s|\n|\\n|g;
- s/^/# /;
- if (!$shown) {
- print "#\n# Untracked files:\n";
- print "# (use \"git add\" to add to commit)\n";
- print "#\n";
- $shown = 1;
- }
- print "$_\n";
- }
- '
+ while read line; do
+ if [ -z "$hdr_shown" ]; then
+ echo '#'
+ echo '# Untracked files:'
+ echo '# (use "git add" to add to commit)'
+ echo '#'
+ hdr_shown=1
+ fi
+ echo "# $line"
+ done
if test -n "$verbose" -a -z "$IS_INITIAL"
then
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..99b3dc392afdd5ffb3e2aa8f6fe6b484c2e2f1fe 100755 (executable)
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
die "GIT_DIR is not defined or is unreadable";
}
-our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_m );
+our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m );
-getopts('hpvcfm:');
+getopts('hpvcfam:');
$opt_h && usage();
TMPDIR => 1,
CLEANUP => 1);
-print Dumper(@ARGV);
# resolve target commit
my $commit;
$commit = pop @ARGV;
# find parents from the commit itself
my @commit = safe_pipe_capture('git-cat-file', 'commit', $commit);
my @parents;
-foreach my $p (@commit) {
- if ($p =~ m/^$/) { # end of commit headers, we're done
- last;
+my $committer;
+my $author;
+my $stage = 'headers'; # headers, msg
+my $title;
+my $msg = '';
+
+foreach my $line (@commit) {
+ chomp $line;
+ if ($stage eq 'headers' && $line eq '') {
+ $stage = 'msg';
+ next;
}
- if ($p =~ m/^parent (\w{40})$/) { # found a parent
- push @parents, $1;
+
+ if ($stage eq 'headers') {
+ if ($line =~ m/^parent (\w{40})$/) { # found a parent
+ push @parents, $1;
+ } elsif ($line =~ m/^author (.+) \d+ \+\d+$/) {
+ $author = $1;
+ } elsif ($line =~ m/^committer (.+) \d+ \+\d+$/) {
+ $committer = $1;
+ }
+ } else {
+ $msg .= $line . "\n";
+ unless ($title) {
+ $title = $line;
+ }
}
}
if ($parent) {
+ my $found;
# double check that it's a valid parent
foreach my $p (@parents) {
- my $found;
if ($p eq $parent) {
$found = 1;
last;
}; # found it
- die "Did not find $parent in the parents for this commit!";
}
+ die "Did not find $parent in the parents for this commit!" if !$found;
} else { # we don't have a parent from the cmdline...
if (@parents == 1) { # it's safe to get it from the commit
$parent = $parents[0];
# grab the commit message
open(MSG, ">.msg") or die "Cannot open .msg for writing";
-print MSG $opt_m;
+if ($opt_m) {
+ print MSG $opt_m;
+}
+print MSG $msg;
+if ($opt_a) {
+ print MSG "\n\nAuthor: $author\n";
+ if ($author ne $committer) {
+ print MSG "Committer: $committer\n";
+ }
+}
close MSG;
-`git-cat-file commit $commit | sed -e '1,/^\$/d' >> .msg`;
-$? && die "Error extracting the commit message";
-
my (@afiles, @dfiles, @mfiles, @dirs);
my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);
#print @files;
@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`;
}
print "Commit to CVS\n";
+print "Patch: $title\n";
my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);
my $cmd = "cvs commit -F .msg $commitfiles";
}
}
-# 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..5b73837bb1e1f0b29ec089942a6a785fc38f1cbc 100755 (executable)
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
# $state holds all the bits of information the clients sends us that could
# potentially be useful when it comes to actually _doing_ something.
-my $state = {};
+my $state = { prependdir => '' };
$log->info("--------------- STARTING -----------------");
my $TEMP_DIR = tempdir( CLEANUP => 1 );
{
my ( $cmd, $data ) = @_;
- # TODO : Not quite sure how Argument and Argumentx differ, but I assume
- # it's for multi-line arguments ... somehow ...
+ # Argumentx means: append to last Argument (with a newline in front)
$log->debug("$cmd : $data");
- push @{$state->{arguments}}, $data;
+ if ( $cmd eq 'Argumentx') {
+ ${$state->{arguments}}[$#{$state->{arguments}}] .= "\n" . $data;
+ } else {
+ push @{$state->{arguments}}, $data;
+ }
}
# expand-modules \n
#$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);
# first lets get the commit list
$ENV{GIT_DIR} = $self->{git_path};
- # prepare database queries
- my $db_insert_rev = $self->{dbh}->prepare_cached("INSERT INTO revision (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
- my $db_insert_mergelog = $self->{dbh}->prepare_cached("INSERT INTO commitmsgs (key, value) VALUES (?,?)",{},1);
- my $db_delete_head = $self->{dbh}->prepare_cached("DELETE FROM head",{},1);
- my $db_insert_head = $self->{dbh}->prepare_cached("INSERT INTO head (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
-
my $commitinfo = `git-cat-file commit $self->{module} 2>&1`;
unless ( $commitinfo =~ /tree\s+[a-zA-Z0-9]{40}/ )
{
author => $commit->{author},
mode => $git_perms,
};
- $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
elsif ( $3 eq "M" )
{
author => $commit->{author},
mode => $git_perms,
};
- $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
elsif ( $3 eq "A" )
{
author => $commit->{author},
mode => $git_perms,
};
- $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
else
{
};
- $db_insert_rev->execute($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
}
close FILELIST;
$head->{$file}{modified} = $commit->{date};
$head->{$file}{author} = $commit->{author};
- $db_insert_rev->execute($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
+ $self->insert_rev($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
}
}
# END : "Detect deleted files"
if (exists $commit->{mergemsg})
{
- $db_insert_mergelog->execute($commit->{hash}, $commit->{mergemsg});
+ $self->insert_mergelog($commit->{hash}, $commit->{mergemsg});
}
$lastpicked = $commit->{hash};
$self->_set_prop("last_commit", $commit->{hash});
}
- $db_delete_head->execute();
+ $self->delete_head();
foreach my $file ( keys %$head )
{
- $db_insert_head->execute(
+ $self->insert_head(
$file,
$head->{$file}{revision},
$head->{$file}{filehash},
$self->{dbh}->commit() or die "Failed to commit changes to SQLite";
}
+sub insert_rev
+{
+ my $self = shift;
+ my $name = shift;
+ my $revision = shift;
+ my $filehash = shift;
+ my $commithash = shift;
+ my $modified = shift;
+ my $author = shift;
+ my $mode = shift;
+
+ my $insert_rev = $self->{dbh}->prepare_cached("INSERT INTO revision (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+ $insert_rev->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
+}
+
+sub insert_mergelog
+{
+ my $self = shift;
+ my $key = shift;
+ my $value = shift;
+
+ my $insert_mergelog = $self->{dbh}->prepare_cached("INSERT INTO commitmsgs (key, value) VALUES (?,?)",{},1);
+ $insert_mergelog->execute($key, $value);
+}
+
+sub delete_head
+{
+ my $self = shift;
+
+ my $delete_head = $self->{dbh}->prepare_cached("DELETE FROM head",{},1);
+ $delete_head->execute();
+}
+
+sub insert_head
+{
+ my $self = shift;
+ my $name = shift;
+ my $revision = shift;
+ my $filehash = shift;
+ my $commithash = shift;
+ my $modified = shift;
+ my $author = shift;
+ my $mode = shift;
+
+ my $insert_head = $self->{dbh}->prepare_cached("INSERT INTO head (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+ $insert_head->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
+}
+
sub _headrev
{
my $self = shift;
diff --git a/git-fetch.sh b/git-fetch.sh
index 48818f822451b3d69e7cd90e81e34bf137ead4dd..c2eebee798d10f7ed5977fc4e56cf02731031971 100755 (executable)
--- a/git-fetch.sh
+++ b/git-fetch.sh
'
IFS="$LF"
+rloga=fetch
no_tags=
tags=
append=
update_head_ok=
exec=
upload_pack=
+keep=--thin
while case "$#" in 0) break ;; esac
do
case "$1" in
-k|--k|--ke|--kee|--keep)
keep=--keep
;;
+ --reflog-action=*)
+ rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
-*)
usage
;;
0)
test -f "$GIT_DIR/branches/origin" ||
test -f "$GIT_DIR/remotes/origin" ||
- die "Where do you want to fetch from today?"
+ git-repo-config --get remote.origin.url >/dev/null ||
+ die "Where do you want to fetch from today?"
set origin ;;
esac
rref=
rsync_slurped_objects=
+rloga="$rloga $remote_nick"
+test "$remote_nick" = "$remote" || rloga="$rloga $remote"
+
if test "" = "$append"
then
: >"$GIT_DIR/FETCH_HEAD"
then
if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
then
- [ "$verbose" ] && echo >&2 "* $1: same as $3"
+ [ "$verbose" ] && echo >&2 "* $1: same as $3" ||:
else
echo >&2 "* $1: updating with $3"
+ git-update-ref -m "$rloga: updating tag" "$1" "$2"
fi
else
echo >&2 "* $1: storing $3"
+ git-update-ref -m "$rloga: storing tag" "$1" "$2"
fi
- git-update-ref "$1" "$2"
;;
refs/heads/* | refs/remotes/*)
*,$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
if test "$tags"
then
taglist=`IFS=" " &&
- git-ls-remote $upload_pack --tags "$remote" |
+ (
+ git-ls-remote $upload_pack --tags "$remote" ||
+ echo fail ouch
+ ) |
while read sha1 name
do
+ case "$sha1" in
+ fail)
+ exit 1
+ esac
case "$name" in
*^*) continue ;;
esac
else
echo >&2 "warning: tag ${name} ignored"
fi
- done`
+ done` || exit
if test "$#" -gt 1
then
# remote URL plus explicit refspecs; we need to merge them.
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;
( : subshell because we muck with IFS
IFS=" $LF"
(
- git-fetch-pack $exec $keep --thin "$remote" $rref || echo failed "$remote"
+ git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote"
) |
while read sha1 remote_name
do
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,237 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+USAGE='[--start] [--stop] [--restart]
+ [--local] [--httpd=<httpd>] [--port=<port>] [--browser=<browser>]
+ [--module-path=<path> (for Apache2 only)]'
+
+. git-sh-setup
+
+case "$GIT_DIR" in
+/*)
+ fqgitdir="$GIT_DIR" ;;
+*)
+ fqgitdir="$PWD/$GIT_DIR" ;;
+esac
+
+local="`git repo-config --bool --get instaweb.local`"
+httpd="`git repo-config --get instaweb.httpd`"
+browser="`git repo-config --get instaweb.browser`"
+port=`git repo-config --get instaweb.port`
+module_path="`git repo-config --get instaweb.modulepath`"
+
+conf=$GIT_DIR/gitweb/httpd.conf
+
+# Defaults:
+
+# if installed, it doesn't need further configuration (module_path)
+test -z "$httpd" && httpd='lighttpd -f'
+
+# probably the most popular browser among gitweb users
+test -z "$browser" && browser='firefox'
+
+# any untaken local port will do...
+test -z "$port" && port=1234
+
+start_httpd () {
+ httpd_only="`echo $httpd | cut -f1 -d' '`"
+ if test "`expr index $httpd_only /`" -eq '1' || \
+ which $httpd_only >/dev/null
+ then
+ $httpd $fqgitdir/gitweb/httpd.conf
+ else
+ # many httpds are installed in /usr/sbin or /usr/local/sbin
+ # these days and those are not in most users $PATHs
+ for i in /usr/local/sbin /usr/sbin
+ do
+ if test -x "$i/$httpd_only"
+ then
+ # don't quote $httpd, there can be
+ # arguments to it (-f)
+ $i/$httpd "$fqgitdir/gitweb/httpd.conf"
+ return
+ fi
+ done
+ fi
+}
+
+stop_httpd () {
+ test -f "$fqgitdir/pid" && kill `cat "$fqgitdir/pid"`
+}
+
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ --stop|stop)
+ stop_httpd
+ exit 0
+ ;;
+ --start|start)
+ start_httpd
+ exit 0
+ ;;
+ --restart|restart)
+ stop_httpd
+ start_httpd
+ exit 0
+ ;;
+ --local|-l)
+ local=true
+ ;;
+ -d|--httpd|--httpd=*)
+ case "$#,$1" in
+ *,*=*)
+ httpd=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+ 1,*)
+ usage ;;
+ *)
+ httpd="$2"
+ shift ;;
+ esac
+ ;;
+ -b|--browser|--browser=*)
+ case "$#,$1" in
+ *,*=*)
+ browser=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+ 1,*)
+ usage ;;
+ *)
+ browser="$2"
+ shift ;;
+ esac
+ ;;
+ -p|--port|--port=*)
+ case "$#,$1" in
+ *,*=*)
+ port=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+ 1,*)
+ usage ;;
+ *)
+ port="$2"
+ shift ;;
+ esac
+ ;;
+ -m|--module-path=*|--module-path)
+ case "$#,$1" in
+ *,*=*)
+ module_path=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+ 1,*)
+ usage ;;
+ *)
+ module_path="$2"
+ shift ;;
+ esac
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ shift
+done
+
+mkdir -p "$GIT_DIR/gitweb/tmp"
+GIT_EXEC_PATH="`git --exec-path`"
+GIT_DIR="$fqgitdir"
+export GIT_EXEC_PATH GIT_DIR
+
+
+lighttpd_conf () {
+ cat > "$conf" <<EOF
+server.document-root = "$fqgitdir/gitweb"
+server.port = $port
+server.modules = ( "mod_cgi" )
+server.indexfiles = ( "gitweb.cgi" )
+server.pid-file = "$fqgitdir/pid"
+cgi.assign = ( ".cgi" => "" )
+mimetype.assign = ( ".css" => "text/css" )
+EOF
+ test "$local" = true && echo 'server.bind = "127.0.0.1"' >> "$conf"
+}
+
+apache2_conf () {
+ test -z "$module_path" && module_path=/usr/lib/apache2/modules
+ mkdir -p "$GIT_DIR/gitweb/logs"
+ bind=
+ test "$local" = true && bind='127.0.0.1:'
+ echo 'text/css css' > $fqgitdir/mime.types
+ cat > "$conf" <<EOF
+ServerRoot "$fqgitdir/gitweb"
+DocumentRoot "$fqgitdir/gitweb"
+PidFile "$fqgitdir/pid"
+Listen $bind$port
+TypesConfig $fqgitdir/mime.types
+DirectoryIndex gitweb.cgi
+EOF
+
+ # check to see if Dennis Stosberg's mod_perl compatibility patch
+ # (<20060621130708.Gcbc6e5c@leonov.stosberg.net>) has been applied
+ if test -f "$module_path/mod_perl.so" && grep '^our $gitbin' \
+ "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null
+ then
+ # favor mod_perl if available
+ cat >> "$conf" <<EOF
+LoadModule perl_module $module_path/mod_perl.so
+PerlPassEnv GIT_DIR
+PerlPassEnv GIT_EXEC_DIR
+<Location /gitweb.cgi>
+ SetHandler perl-script
+ PerlResponseHandler ModPerl::Registry
+ PerlOptions +ParseHeaders
+ Options +ExecCGI
+</Location>
+EOF
+ else
+ # plain-old CGI
+ list_mods=`echo "$httpd" | sed "s/-f$/-l/"`
+ $list_mods | grep 'mod_cgi\.c' >/dev/null || \
+ echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf"
+ cat >> "$conf" <<EOF
+AddHandler cgi-script .cgi
+<Location /gitweb.cgi>
+ Options +ExecCGI
+</Location>
+EOF
+ fi
+}
+
+script='
+s#^\(my\|our\) $projectroot =.*#\1 $projectroot = "'`dirname $fqgitdir`'";#
+s#\(my\|our\) $gitbin =.*#\1 $gitbin = "'$GIT_EXEC_PATH'";#
+s#\(my\|our\) $projects_list =.*#\1 $projects_list = $projectroot;#
+s#\(my\|our\) $git_temp =.*#\1 $git_temp = "'$fqgitdir/gitweb/tmp'";#'
+
+gitweb_cgi () {
+ cat > "$1.tmp" <<\EOFGITWEB
+@@GITWEB_CGI@@
+EOFGITWEB
+ sed "$script" "$1.tmp" > "$1"
+ chmod +x "$1"
+ rm -f "$1.tmp"
+}
+
+gitweb_css () {
+ cat > "$1" <<\EOFGITWEB
+@@GITWEB_CSS@@
+EOFGITWEB
+}
+
+gitweb_cgi $GIT_DIR/gitweb/gitweb.cgi
+gitweb_css $GIT_DIR/gitweb/gitweb.css
+
+case "$httpd" in
+*lighttpd*)
+ lighttpd_conf
+ ;;
+*apache2*)
+ apache2_conf
+ ;;
+*)
+ echo "Unknown httpd specified: $httpd"
+ exit 1
+ ;;
+esac
+
+start_httpd
+test -z "$browser" && browser=echo
+$browser http://127.0.0.1:$port
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-merge.sh b/git-merge.sh
index 24e3b507ef1f907e7146a2653189f45a92db08fd..a9cfafb1dfba6e85834a33caf6b3c69910594bf6 100755 (executable)
--- a/git-merge.sh
+++ b/git-merge.sh
}
finish () {
- test '' = "$2" || echo "$2"
+ if test '' = "$2"
+ then
+ rlogm="$rloga"
+ else
+ echo "$2"
+ rlogm="$rloga: $2"
+ fi
case "$squash" in
t)
echo "Squash commit -- not updating HEAD"
echo "No merge message -- not updating HEAD"
;;
*)
- git-update-ref HEAD "$1" "$head" || exit 1
+ git-update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
;;
esac
;;
esac
}
+rloga=
while case "$#" in 0) break ;; esac
do
case "$1" in
die "available strategies are: $all_strategies" ;;
esac
;;
+ --reflog-action=*)
+ rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ ;;
-*) usage ;;
*) break ;;
esac
# All the rest are remote heads
test "$#" = 0 && usage ;# we need at least one remote head.
+test "$rloga" = '' && rloga="merge: $@"
remoteheads=
for remote
then
parents=$(git-show-branch --independent "$head" "$@" | sed -e 's/^/-p /')
result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents) || exit
- finish "$result_commit" "Merge $result_commit, made by $wt_strategy."
+ finish "$result_commit" "Merge made by $wt_strategy."
dropsave
exit 0
fi
diff --git a/git-prune.sh b/git-prune.sh
--- a/git-prune.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-
-USAGE='[-n] [--] [<head>...]'
-. git-sh-setup
-
-dryrun=
-echo=
-while case "$#" in 0) break ;; esac
-do
- case "$1" in
- -n) dryrun=-n echo=echo ;;
- --) break ;;
- -*) usage ;;
- *) break ;;
- esac
- shift;
-done
-
-sync
-case "$#" in
-0) git-fsck-objects --full --cache --unreachable ;;
-*) git-fsck-objects --full --cache --unreachable $(git-rev-parse --all) "$@" ;;
-esac |
-
-sed -ne '/unreachable /{
- s/unreachable [^ ][^ ]* //
- s|\(..\)|\1/|p
-}' | {
- cd "$GIT_OBJECT_DIRECTORY" || exit
- xargs $echo rm -f
- rmdir 2>/dev/null [0-9a-f][0-9a-f]
-}
-
-git-prune-packed $dryrun
-
-if redundant=$(git-pack-redundant --all 2>/dev/null) && test "" != "$redundant"
-then
- if test "" = "$dryrun"
- then
- echo "$redundant" | xargs rm -f
- else
- echo rm -f "$redundant"
- fi
-fi
diff --git a/git-pull.sh b/git-pull.sh
index 076785c96b30b4fd8ab944324fd53db7c584567a..f380437997f053d15a177a941f3b8a1543c56a18 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"
esac
merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
-git-merge $no_summary $no_commit $squash $strategy_args \
+git-merge "--reflog-action=pull $*" \
+ $no_summary $no_commit $squash $strategy_args \
"$merge_name" HEAD $merge_head
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..10135da3ac04e3175fa45ca26b5ea31605a47284 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
git-apply --index -C1 "$tmp_patch" &&
tree=$(git-write-tree) &&
commit=$((echo "$SUBJECT"; echo; cat "$tmp_msg") | git-commit-tree $tree -p $commit) &&
- git-update-ref HEAD $commit || exit 4
+ git-update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
fi
done
rm -rf $tmp_dir || exit 5
diff --git a/git-rebase.sh b/git-rebase.sh
index 3945e067141ec1bc87456179a59de8f692bdf9fa..29028dd5fc0669cd2d7d3b956bfdd2583cc785b7 100755 (executable)
--- a/git-rebase.sh
+++ b/git-rebase.sh
finish_rb_merge
exit
fi
- git am --resolved --3way --resolvemsg="$RESOLVEMSG"
+ git am --resolved --3way --resolvemsg="$RESOLVEMSG" \
+ --reflog-action=rebase
exit
;;
--skip)
finish_rb_merge
exit
fi
- git am -3 --skip --resolvemsg="$RESOLVEMSG"
+ git am -3 --skip --resolvemsg="$RESOLVEMSG" \
+ --reflog-action=rebase
exit
;;
--abort)
if test -z "$do_merge"
then
git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD |
- git am --binary -3 -k --resolvemsg="$RESOLVEMSG"
+ git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \
+ --reflog-action=rebase
exit $?
fi
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 640ad8d90b9a9afd00506a9697af1d3e560e1255..9da92fb06139dc53061876f1532d9ee8c46d5346 100755 (executable)
--- a/git-repack.sh
+++ b/git-repack.sh
;;
esac
pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra"
-name=$(git-rev-list --objects --all $rev_list 2>&1 |
+name=$( { git-rev-list --objects --all $rev_list ||
+ echo "git-rev-list died with exit code $?"
+ } |
git-pack-objects --non-empty $pack_objects .tmp-pack) ||
exit 1
if [ -z "$name" ]; then
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-resolve.sh b/git-resolve.sh
index 1c7aaefa2528b73fea3eee22b086b94fa91854c5..a7bc680d90cb503c50d25b15bcaba662c5f5b49e 100755 (executable)
--- a/git-resolve.sh
+++ b/git-resolve.sh
head=$(git-rev-parse --verify "$1"^0) &&
merge=$(git-rev-parse --verify "$2"^0) &&
+merge_name="$2" &&
merge_msg="$3" || usage
#
"$head")
echo "Updating from $head to $merge"
git-read-tree -u -m $head $merge || exit 1
- git-update-ref HEAD "$merge" "$head"
+ git-update-ref -m "resolve $merge_name: Fast forward" \
+ HEAD "$merge" "$head"
git-diff-tree -p $head $merge | git-apply --stat
dropheads
exit 0
fi
result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree -p $head -p $merge)
echo "Committed merge $result_commit"
-git-update-ref HEAD "$result_commit" "$head"
+git-update-ref -m "resolve $merge_name: In-index merge" \
+ HEAD "$result_commit" "$head"
git-diff-tree -p $head $result_commit | git-apply --stat
dropheads
diff --git a/git-revert.sh b/git-revert.sh
index de8b5f0f0fbb42e82327670e6916382f6f2d3e47..2bf35d116c2141a1750a0ca0bd8f7297e0c237b6 100755 (executable)
--- a/git-revert.sh
+++ b/git-revert.sh
s/^[^ ]* /Revert "/
s/$/"/'
echo
- echo "This reverts $commit commit."
+ echo "This reverts commit $commit."
test "$rev" = "$commit" ||
echo "(original 'git revert' arguments: $@)"
base=$commit next=$prev
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,3387 @@
+#!/usr/bin/env perl
+# Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
+# License: GPL v2 or later
+use warnings;
+use strict;
+use vars qw/ $AUTHOR $VERSION
+ $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
+ $GIT_SVN_INDEX $GIT_SVN
+ $GIT_DIR $GIT_SVN_DIR $REVDB/;
+$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
+$VERSION = '@@GIT_VERSION@@';
+
+use Cwd qw/abs_path/;
+$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
+$ENV{GIT_DIR} = $GIT_DIR;
+
+my $LC_ALL = $ENV{LC_ALL};
+my $TZ = $ENV{TZ};
+# make sure the svn binary gives consistent output between locales and TZs:
+$ENV{TZ} = 'UTC';
+$ENV{LC_ALL} = 'C';
+$| = 1; # unbuffer STDOUT
+
+# If SVN:: library support is added, please make the dependencies
+# optional and preserve the capability to use the command-line client.
+# use eval { require SVN::... } to make it lazy load
+# We don't use any modules not in the standard Perl distribution:
+use Carp qw/croak/;
+use IO::File qw//;
+use File::Basename qw/dirname basename/;
+use File::Path qw/mkpath/;
+use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
+use File::Spec qw//;
+use POSIX qw/strftime/;
+use IPC::Open3;
+use Memoize;
+memoize('revisions_eq');
+memoize('cmt_metadata');
+memoize('get_commit_time');
+
+my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
+$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
+libsvn_load();
+my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
+my $sha1 = qr/[a-f\d]{40}/;
+my $sha1_short = qr/[a-f\d]{4,40}/;
+my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
+ $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
+ $_repack, $_repack_nr, $_repack_flags, $_q,
+ $_message, $_file, $_follow_parent, $_no_metadata,
+ $_template, $_shared, $_no_default_regex, $_no_graft_copy,
+ $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
+ $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);
+my (@_branch_from, %tree_map, %users, %rusers, %equiv);
+my ($_svn_co_url_revs, $_svn_pg_peg_revs);
+my @repo_path_split_cache;
+
+my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
+ 'branch|b=s' => \@_branch_from,
+ 'follow-parent|follow' => \$_follow_parent,
+ 'branch-all-refs|B' => \$_branch_all_refs,
+ 'authors-file|A=s' => \$_authors,
+ 'repack:i' => \$_repack,
+ 'no-metadata' => \$_no_metadata,
+ 'quiet|q' => \$_q,
+ 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
+
+my ($_trunk, $_tags, $_branches);
+my %multi_opts = ( 'trunk|T=s' => \$_trunk,
+ 'tags|t=s' => \$_tags,
+ 'branches|b=s' => \$_branches );
+my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared );
+my %cmt_opts = ( 'edit|e' => \$_edit,
+ 'rmdir' => \$_rmdir,
+ 'find-copies-harder' => \$_find_copies_harder,
+ 'l=i' => \$_l,
+ 'copy-similarity|C=i'=> \$_cp_similarity
+);
+
+# yes, 'native' sets "\n". Patches to fix this for non-*nix systems welcome:
+my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" );
+
+my %cmd = (
+ fetch => [ \&fetch, "Download new revisions from SVN",
+ { 'revision|r=s' => \$_revision, %fc_opts } ],
+ init => [ \&init, "Initialize a repo for tracking" .
+ " (requires URL argument)",
+ \%init_opts ],
+ commit => [ \&commit, "Commit git revisions to SVN",
+ { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
+ 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
+ { 'revision|r=i' => \$_revision } ],
+ rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
+ { 'no-ignore-externals' => \$_no_ignore_ext,
+ 'copy-remote|remote=s' => \$_cp_remote,
+ 'upgrade' => \$_upgrade } ],
+ 'graft-branches' => [ \&graft_branches,
+ 'Detect merges/branches from already imported history',
+ { 'merge-rx|m' => \@_opt_m,
+ 'branch|b=s' => \@_branch_from,
+ 'branch-all-refs|B' => \$_branch_all_refs,
+ 'no-default-regex' => \$_no_default_regex,
+ 'no-graft-copy' => \$_no_graft_copy } ],
+ 'multi-init' => [ \&multi_init,
+ 'Initialize multiple trees (like git-svnimport)',
+ { %multi_opts, %fc_opts } ],
+ 'multi-fetch' => [ \&multi_fetch,
+ 'Fetch multiple trees (like git-svnimport)',
+ \%fc_opts ],
+ 'log' => [ \&show_log, 'Show commit logs',
+ { 'limit=i' => \$_limit,
+ 'revision|r=s' => \$_revision,
+ 'verbose|v' => \$_verbose,
+ 'incremental' => \$_incremental,
+ 'oneline' => \$_oneline,
+ 'show-commit' => \$_show_commit,
+ 'authors-file|A=s' => \$_authors,
+ } ],
+ 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
+ { 'message|m=s' => \$_message,
+ 'file|F=s' => \$_file,
+ %cmt_opts } ],
+);
+
+my $cmd;
+for (my $i = 0; $i < @ARGV; $i++) {
+ if (defined $cmd{$ARGV[$i]}) {
+ $cmd = $ARGV[$i];
+ splice @ARGV, $i, 1;
+ last;
+ }
+};
+
+my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
+
+read_repo_config(\%opts);
+my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
+ 'version|V' => \$_version,
+ 'id|i=s' => \$GIT_SVN);
+exit 1 if (!$rv && $cmd ne 'log');
+
+set_default_vals();
+usage(0) if $_help;
+version() if $_version;
+usage(1) unless defined $cmd;
+init_vars();
+load_authors() if $_authors;
+load_all_refs() if $_branch_all_refs;
+svn_compat_check() unless $_use_lib;
+migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
+$cmd{$cmd}->[0]->(@ARGV);
+exit 0;
+
+####################### primary functions ######################
+sub usage {
+ my $exit = shift || 0;
+ my $fd = $exit ? \*STDERR : \*STDOUT;
+ print $fd <<"";
+git-svn - bidirectional operations between a single Subversion tree and git
+Usage: $0 <command> [options] [arguments]\n
+
+ print $fd "Available commands:\n" unless $cmd;
+
+ foreach (sort keys %cmd) {
+ next if $cmd && $cmd ne $_;
+ print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n";
+ foreach (keys %{$cmd{$_}->[2]}) {
+ # prints out arguments as they should be passed:
+ my $x = s#[:=]s$## ? '<arg>' : s#[:=]i$## ? '<num>' : '';
+ print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
+ "--$_" : "-$_" }
+ split /\|/,$_)," $x\n";
+ }
+ }
+ print $fd <<"";
+\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an
+arbitrary identifier if you're tracking multiple SVN branches/repositories in
+one git repository and want to keep them separate. See git-svn(1) for more
+information.
+
+ exit $exit;
+}
+
+sub version {
+ print "git-svn version $VERSION\n";
+ exit 0;
+}
+
+sub rebuild {
+ if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) {
+ copy_remote_ref();
+ }
+ $SVN_URL = shift or undef;
+ my $newest_rev = 0;
+ if ($_upgrade) {
+ sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
+ } else {
+ check_upgrade_needed();
+ }
+
+ my $pid = open(my $rev_list,'-|');
+ defined $pid or croak $!;
+ if ($pid == 0) {
+ exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
+ }
+ my $latest;
+ while (<$rev_list>) {
+ chomp;
+ my $c = $_;
+ croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
+ my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
+ next if (!@commit); # skip merges
+ my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
+ if (!$rev || !$uuid) {
+ croak "Unable to extract revision or UUID from ",
+ "$c, $commit[$#commit]\n";
+ }
+
+ # if we merged or otherwise started elsewhere, this is
+ # how we break out of it
+ next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
+ next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));
+
+ unless (defined $latest) {
+ if (!$SVN_URL && !$url) {
+ croak "SVN repository location required: $url\n";
+ }
+ $SVN_URL ||= $url;
+ $SVN_UUID ||= $uuid;
+ setup_git_svn();
+ $latest = $rev;
+ }
+ revdb_set($REVDB, $rev, $c);
+ print "r$rev = $c\n";
+ $newest_rev = $rev if ($rev > $newest_rev);
+ }
+ close $rev_list or croak $?;
+
+ goto out if $_use_lib;
+ if (!chdir $SVN_WC) {
+ svn_cmd_checkout($SVN_URL, $latest, $SVN_WC);
+ chdir $SVN_WC or croak $!;
+ }
+
+ $pid = fork;
+ defined $pid or croak $!;
+ if ($pid == 0) {
+ my @svn_up = qw(svn up);
+ push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
+ sys(@svn_up,"-r$newest_rev");
+ $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;
+ index_changes();
+ exec('git-write-tree') or croak $!;
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+out:
+ if ($_upgrade) {
+ print STDERR <<"";
+Keeping deprecated refs/head/$GIT_SVN-HEAD for now. Please remove it
+when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
+
+ }
+}
+
+sub init {
+ my $url = shift or die "SVN repository location required " .
+ "as a command-line argument\n";
+ $url =~ s!/+$!!; # strip trailing slash
+
+ if (my $repo_path = shift) {
+ unless (-d $repo_path) {
+ mkpath([$repo_path]);
+ }
+ $GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git";
+ init_vars();
+ }
+
+ $SVN_URL = $url;
+ unless (-d $GIT_DIR) {
+ my @init_db = ('git-init-db');
+ push @init_db, "--template=$_template" if defined $_template;
+ push @init_db, "--shared" if defined $_shared;
+ sys(@init_db);
+ }
+ setup_git_svn();
+}
+
+sub fetch {
+ check_upgrade_needed();
+ $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
+ my $ret = $_use_lib ? fetch_lib(@_) : fetch_cmd(@_);
+ if ($ret->{commit} && quiet_run(qw(git-rev-parse --verify
+ refs/heads/master^0))) {
+ sys(qw(git-update-ref refs/heads/master),$ret->{commit});
+ }
+ return $ret;
+}
+
+sub fetch_cmd {
+ my (@parents) = @_;
+ my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);
+ unless ($_revision) {
+ $_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD';
+ }
+ push @log_args, "-r$_revision";
+ push @log_args, '--stop-on-copy' unless $_no_stop_copy;
+
+ my $svn_log = svn_log_raw(@log_args);
+
+ my $base = next_log_entry($svn_log) or croak "No base revision!\n";
+ # don't need last_revision from grab_base_rev() because
+ # user could've specified a different revision to skip (they
+ # didn't want to import certain revisions into git for whatever
+ # reason, so trust $base->{revision} instead.
+ my (undef, $last_commit) = svn_grab_base_rev();
+ unless (-d $SVN_WC) {
+ svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC);
+ chdir $SVN_WC or croak $!;
+ read_uuid();
+ $last_commit = git_commit($base, @parents);
+ assert_tree($last_commit);
+ } else {
+ chdir $SVN_WC or croak $!;
+ read_uuid();
+ # looks like a user manually cp'd and svn switch'ed
+ unless ($last_commit) {
+ sys(qw/svn revert -R ./);
+ assert_svn_wc_clean($base->{revision});
+ $last_commit = git_commit($base, @parents);
+ assert_tree($last_commit);
+ }
+ }
+ my @svn_up = qw(svn up);
+ push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
+ my $last = $base;
+ while (my $log_msg = next_log_entry($svn_log)) {
+ if ($last->{revision} >= $log_msg->{revision}) {
+ croak "Out of order: last >= current: ",
+ "$last->{revision} >= $log_msg->{revision}\n";
+ }
+ # Revert is needed for cases like:
+ # https://svn.musicpd.org/Jamming/trunk (r166:167), but
+ # I can't seem to reproduce something like that on a test...
+ sys(qw/svn revert -R ./);
+ assert_svn_wc_clean($last->{revision});
+ sys(@svn_up,"-r$log_msg->{revision}");
+ $last_commit = git_commit($log_msg, $last_commit, @parents);
+ $last = $log_msg;
+ }
+ close $svn_log->{fh};
+ $last->{commit} = $last_commit;
+ return $last;
+}
+
+sub fetch_lib {
+ my (@parents) = @_;
+ $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
+ my $repo;
+ ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
+ $SVN_LOG ||= libsvn_connect($repo);
+ $SVN ||= libsvn_connect($repo);
+ my ($last_rev, $last_commit) = svn_grab_base_rev();
+ my ($base, $head) = libsvn_parse_revision($last_rev);
+ if ($base > $head) {
+ return { revision => $last_rev, commit => $last_commit }
+ }
+ my $index = set_index($GIT_SVN_INDEX);
+
+ # limit ourselves and also fork() since get_log won't release memory
+ # after processing a revision and SVN stuff seems to leak
+ my $inc = 1000;
+ my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
+ read_uuid();
+ if (defined $last_commit) {
+ unless (-e $GIT_SVN_INDEX) {
+ sys(qw/git-read-tree/, $last_commit);
+ }
+ chomp (my $x = `git-write-tree`);
+ my ($y) = (`git-cat-file commit $last_commit`
+ =~ /^tree ($sha1)/m);
+ if ($y ne $x) {
+ unlink $GIT_SVN_INDEX or croak $!;
+ sys(qw/git-read-tree/, $last_commit);
+ }
+ chomp ($x = `git-write-tree`);
+ if ($y ne $x) {
+ print STDERR "trees ($last_commit) $y != $x\n",
+ "Something is seriously wrong...\n";
+ }
+ }
+ while (1) {
+ # fork, because using SVN::Pool with get_log() still doesn't
+ # seem to help enough to keep memory usage down.
+ defined(my $pid = fork) or croak $!;
+ if (!$pid) {
+ $SVN::Error::handler = \&libsvn_skip_unknown_revs;
+
+ # Yes I'm perfectly aware that the fourth argument
+ # below is the limit revisions number. Unfortunately
+ # performance sucks with it enabled, so it's much
+ # faster to fetch revision ranges instead of relying
+ # on the limiter.
+ libsvn_get_log($SVN_LOG, '/'.$SVN_PATH,
+ $min, $max, 0, 1, 1,
+ sub {
+ my $log_msg;
+ if ($last_commit) {
+ $log_msg = libsvn_fetch(
+ $last_commit, @_);
+ $last_commit = git_commit(
+ $log_msg,
+ $last_commit,
+ @parents);
+ } else {
+ $log_msg = libsvn_new_tree(@_);
+ $last_commit = git_commit(
+ $log_msg, @parents);
+ }
+ });
+ exit 0;
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+ ($last_rev, $last_commit) = svn_grab_base_rev();
+ last if ($max >= $head);
+ $min = $max + 1;
+ $max += $inc;
+ $max = $head if ($max > $head);
+ }
+ restore_index($index);
+ return { revision => $last_rev, commit => $last_commit };
+}
+
+sub commit {
+ my (@commits) = @_;
+ check_upgrade_needed();
+ if ($_stdin || !@commits) {
+ print "Reading from stdin...\n";
+ @commits = ();
+ while (<STDIN>) {
+ if (/\b($sha1_short)\b/o) {
+ unshift @commits, $1;
+ }
+ }
+ }
+ my @revs;
+ foreach my $c (@commits) {
+ chomp(my @tmp = safe_qx('git-rev-parse',$c));
+ if (scalar @tmp == 1) {
+ push @revs, $tmp[0];
+ } elsif (scalar @tmp > 1) {
+ push @revs, reverse (safe_qx('git-rev-list',@tmp));
+ } else {
+ die "Failed to rev-parse $c\n";
+ }
+ }
+ chomp @revs;
+ $_use_lib ? commit_lib(@revs) : commit_cmd(@revs);
+ print "Done committing ",scalar @revs," revisions to SVN\n";
+}
+
+sub commit_cmd {
+ my (@revs) = @_;
+
+ chdir $SVN_WC or croak "Unable to chdir $SVN_WC: $!\n";
+ my $info = svn_info('.');
+ my $fetched = fetch();
+ if ($info->{Revision} != $fetched->{revision}) {
+ print STDERR "There are new revisions that were fetched ",
+ "and need to be merged (or acknowledged) ",
+ "before committing.\n";
+ exit 1;
+ }
+ $info = svn_info('.');
+ read_uuid($info);
+ my $last = $fetched;
+ foreach my $c (@revs) {
+ my $mods = svn_checkout_tree($last, $c);
+ if (scalar @$mods == 0) {
+ print "Skipping, no changes detected\n";
+ next;
+ }
+ $last = svn_commit_tree($last, $c);
+ }
+}
+
+sub commit_lib {
+ my (@revs) = @_;
+ my ($r_last, $cmt_last) = svn_grab_base_rev();
+ defined $r_last or die "Must have an existing revision to commit\n";
+ my $fetched = fetch();
+ if ($r_last != $fetched->{revision}) {
+ print STDERR "There are new revisions that were fetched ",
+ "and need to be merged (or acknowledged) ",
+ "before committing.\n",
+ "last rev: $r_last\n",
+ " current: $fetched->{revision}\n";
+ exit 1;
+ }
+ read_uuid();
+ my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+ my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
+
+ set_svn_commit_env();
+ foreach my $c (@revs) {
+ my $log_msg = get_commit_message($c, $commit_msg);
+
+ # fork for each commit because there's a memory leak I
+ # can't track down... (it's probably in the SVN code)
+ defined(my $pid = open my $fh, '-|') or croak $!;
+ if (!$pid) {
+ my $ed = SVN::Git::Editor->new(
+ { r => $r_last,
+ ra => $SVN,
+ c => $c,
+ svn_path => $SVN_PATH
+ },
+ $SVN->get_commit_editor(
+ $log_msg->{msg},
+ sub {
+ libsvn_commit_cb(
+ @_, $c,
+ $log_msg->{msg},
+ $r_last,
+ $cmt_last)
+ },
+ @lock)
+ );
+ my $mods = libsvn_checkout_tree($cmt_last, $c, $ed);
+ if (@$mods == 0) {
+ print "No changes\nr$r_last = $cmt_last\n";
+ $ed->abort_edit;
+ } else {
+ $ed->close_edit;
+ }
+ exit 0;
+ }
+ my ($r_new, $cmt_new, $no);
+ while (<$fh>) {
+ print $_;
+ chomp;
+ if (/^r(\d+) = ($sha1)$/o) {
+ ($r_new, $cmt_new) = ($1, $2);
+ } elsif ($_ eq 'No changes') {
+ $no = 1;
+ }
+ }
+ close $fh or croak $?;
+ if (! defined $r_new && ! defined $cmt_new) {
+ unless ($no) {
+ die "Failed to parse revision information\n";
+ }
+ } else {
+ ($r_last, $cmt_last) = ($r_new, $cmt_new);
+ }
+ }
+ $ENV{LC_ALL} = 'C';
+ unlink $commit_msg;
+}
+
+sub show_ignore {
+ $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
+ $_use_lib ? show_ignore_lib() : show_ignore_cmd();
+}
+
+sub show_ignore_cmd {
+ require File::Find or die $!;
+ if (defined $_revision) {
+ die "-r/--revision option doesn't work unless the Perl SVN ",
+ "libraries are used\n";
+ }
+ chdir $SVN_WC or croak $!;
+ my %ign;
+ File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){
+ s#^\./##;
+ @{$ign{$_}} = svn_propget_base('svn:ignore', $_);
+ }}, no_chdir=>1},'.');
+
+ print "\n# /\n";
+ foreach (@{$ign{'.'}}) { print '/',$_ if /\S/ }
+ delete $ign{'.'};
+ foreach my $i (sort keys %ign) {
+ print "\n# ",$i,"\n";
+ foreach (@{$ign{$i}}) { print '/',$i,'/',$_ if /\S/ }
+ }
+}
+
+sub show_ignore_lib {
+ my $repo;
+ ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
+ $SVN ||= libsvn_connect($repo);
+ my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
+ libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r);
+}
+
+sub graft_branches {
+ my $gr_file = "$GIT_DIR/info/grafts";
+ my ($grafts, $comments) = read_grafts($gr_file);
+ my $gr_sha1;
+
+ if (%$grafts) {
+ # temporarily disable our grafts file to make this idempotent
+ chomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file));
+ rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
+ }
+
+ my $l_map = read_url_paths();
+ my @re = map { qr/$_/is } @_opt_m if @_opt_m;
+ unless ($_no_default_regex) {
+ push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i,
+ qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i,
+ qr/\b(?:from|of)\s+([\w\.\-]+)/i );
+ }
+ foreach my $u (keys %$l_map) {
+ if (@re) {
+ foreach my $p (keys %{$l_map->{$u}}) {
+ graft_merge_msg($grafts,$l_map,$u,$p,@re);
+ }
+ }
+ unless ($_no_graft_copy) {
+ if ($_use_lib) {
+ graft_file_copy_lib($grafts,$l_map,$u);
+ } else {
+ graft_file_copy_cmd($grafts,$l_map,$u);
+ }
+ }
+ }
+ graft_tree_joins($grafts);
+
+ write_grafts($grafts, $comments, $gr_file);
+ unlink "$gr_file~$gr_sha1" if $gr_sha1;
+}
+
+sub multi_init {
+ my $url = shift;
+ $_trunk ||= 'trunk';
+ $_trunk =~ s#/+$##;
+ $url =~ s#/+$## if $url;
+ if ($_trunk !~ m#^[a-z\+]+://#) {
+ $_trunk = '/' . $_trunk if ($_trunk !~ m#^/#);
+ unless ($url) {
+ print STDERR "E: '$_trunk' is not a complete URL ",
+ "and a separate URL is not specified\n";
+ exit 1;
+ }
+ $_trunk = $url . $_trunk;
+ }
+ if ($GIT_SVN eq 'git-svn') {
+ print "GIT_SVN_ID set to 'trunk' for $_trunk\n";
+ $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
+ }
+ init_vars();
+ init($_trunk);
+ complete_url_ls_init($url, $_branches, '--branches/-b', '');
+ complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
+}
+
+sub multi_fetch {
+ # try to do trunk first, since branches/tags
+ # may be descended from it.
+ if (-e "$GIT_DIR/svn/trunk/info/url") {
+ fetch_child_id('trunk', @_);
+ }
+ rec_fetch('', "$GIT_DIR/svn", @_);
+}
+
+sub show_log {
+ my (@args) = @_;
+ my ($r_min, $r_max);
+ my $r_last = -1; # prevent dupes
+ rload_authors() if $_authors;
+ if (defined $TZ) {
+ $ENV{TZ} = $TZ;
+ } else {
+ delete $ENV{TZ};
+ }
+ if (defined $_revision) {
+ if ($_revision =~ /^(\d+):(\d+)$/) {
+ ($r_min, $r_max) = ($1, $2);
+ } elsif ($_revision =~ /^\d+$/) {
+ $r_min = $r_max = $_revision;
+ } else {
+ print STDERR "-r$_revision is not supported, use ",
+ "standard \'git log\' arguments instead\n";
+ exit 1;
+ }
+ }
+
+ my $pid = open(my $log,'-|');
+ defined $pid or croak $!;
+ if (!$pid) {
+ exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!;
+ }
+ setup_pager();
+ my (@k, $c, $d);
+
+ while (<$log>) {
+ if (/^commit ($sha1_short)/o) {
+ my $cmt = $1;
+ if ($c && cmt_showable($c) && $c->{r} != $r_last) {
+ $r_last = $c->{r};
+ process_commit($c, $r_min, $r_max, \@k) or
+ goto out;
+ }
+ $d = undef;
+ $c = { c => $cmt };
+ } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) {
+ get_author_info($c, $1, $2, $3);
+ } elsif (/^(?:tree|parent|committer) /) {
+ # ignore
+ } elsif (/^:\d{6} \d{6} $sha1_short/o) {
+ push @{$c->{raw}}, $_;
+ } elsif (/^diff /) {
+ $d = 1;
+ push @{$c->{diff}}, $_;
+ } elsif ($d) {
+ push @{$c->{diff}}, $_;
+ } elsif (/^ (git-svn-id:.+)$/) {
+ (undef, $c->{r}, undef) = extract_metadata($1);
+ } elsif (s/^ //) {
+ push @{$c->{l}}, $_;
+ }
+ }
+ if ($c && defined $c->{r} && $c->{r} != $r_last) {
+ $r_last = $c->{r};
+ process_commit($c, $r_min, $r_max, \@k);
+ }
+ if (@k) {
+ my $swap = $r_max;
+ $r_max = $r_min;
+ $r_min = $swap;
+ process_commit($_, $r_min, $r_max) foreach reverse @k;
+ }
+out:
+ close $log;
+ print '-' x72,"\n" unless $_incremental || $_oneline;
+}
+
+sub commit_diff_usage {
+ print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n";
+ exit 1
+}
+
+sub commit_diff {
+ if (!$_use_lib) {
+ print STDERR "commit-diff must be used with SVN libraries\n";
+ exit 1;
+ }
+ my $ta = shift or commit_diff_usage();
+ my $tb = shift or commit_diff_usage();
+ if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
+ print STDERR "Needed URL or usable git-svn id command-line\n";
+ commit_diff_usage();
+ }
+ if (defined $_message && defined $_file) {
+ print STDERR "Both --message/-m and --file/-F specified ",
+ "for the commit message.\n",
+ "I have no idea what you mean\n";
+ exit 1;
+ }
+ if (defined $_file) {
+ $_message = file_to_s($_file);
+ } else {
+ $_message ||= get_commit_message($tb,
+ "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
+ }
+ my $repo;
+ ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
+ $SVN_LOG ||= libsvn_connect($repo);
+ $SVN ||= libsvn_connect($repo);
+ my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+ my $ed = SVN::Git::Editor->new({ r => $SVN->get_latest_revnum,
+ ra => $SVN, c => $tb,
+ svn_path => $SVN_PATH
+ },
+ $SVN->get_commit_editor($_message,
+ sub {print "Committed $_[0]\n"},@lock)
+ );
+ my $mods = libsvn_checkout_tree($ta, $tb, $ed);
+ if (@$mods == 0) {
+ print "No changes\n$ta == $tb\n";
+ $ed->abort_edit;
+ } else {
+ $ed->close_edit;
+ }
+}
+
+########################### utility functions #########################
+
+sub cmt_showable {
+ my ($c) = @_;
+ return 1 if defined $c->{r};
+ if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
+ $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
+ my @msg = safe_qx(qw/git-cat-file commit/, $c->{c});
+ shift @msg while ($msg[0] ne "\n");
+ shift @msg;
+ @{$c->{l}} = grep !/^git-svn-id: /, @msg;
+
+ (undef, $c->{r}, undef) = extract_metadata(
+ (grep(/^git-svn-id: /, @msg))[-1]);
+ }
+ return defined $c->{r};
+}
+
+sub git_svn_log_cmd {
+ my ($r_min, $r_max) = @_;
+ my @cmd = (qw/git-log --abbrev-commit --pretty=raw
+ --default/, "refs/remotes/$GIT_SVN");
+ push @cmd, '--summary' if $_verbose;
+ return @cmd unless defined $r_max;
+ if ($r_max == $r_min) {
+ push @cmd, '--max-count=1';
+ if (my $c = revdb_get($REVDB, $r_max)) {
+ push @cmd, $c;
+ }
+ } else {
+ my ($c_min, $c_max);
+ $c_max = revdb_get($REVDB, $r_max);
+ $c_min = revdb_get($REVDB, $r_min);
+ if ($c_min && $c_max) {
+ if ($r_max > $r_max) {
+ push @cmd, "$c_min..$c_max";
+ } else {
+ push @cmd, "$c_max..$c_min";
+ }
+ } elsif ($r_max > $r_min) {
+ push @cmd, $c_max;
+ } else {
+ push @cmd, $c_min;
+ }
+ }
+ return @cmd;
+}
+
+sub fetch_child_id {
+ my $id = shift;
+ print "Fetching $id\n";
+ my $ref = "$GIT_DIR/refs/remotes/$id";
+ defined(my $pid = open my $fh, '-|') or croak $!;
+ if (!$pid) {
+ $_repack = undef;
+ $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+ init_vars();
+ fetch(@_);
+ exit 0;
+ }
+ while (<$fh>) {
+ print $_;
+ check_repack() if (/^r\d+ = $sha1/);
+ }
+ close $fh or croak $?;
+}
+
+sub rec_fetch {
+ my ($pfx, $p, @args) = @_;
+ my @dir;
+ foreach (sort <$p/*>) {
+ if (-r "$_/info/url") {
+ $pfx .= '/' if $pfx && $pfx !~ m!/$!;
+ my $id = $pfx . basename $_;
+ next if $id eq 'trunk';
+ fetch_child_id($id, @args);
+ } elsif (-d $_) {
+ push @dir, $_;
+ }
+ }
+ foreach (@dir) {
+ my $x = $_;
+ $x =~ s!^\Q$GIT_DIR\E/svn/!!;
+ rec_fetch($x, $_);
+ }
+}
+
+sub complete_url_ls_init {
+ my ($url, $var, $switch, $pfx) = @_;
+ unless ($var) {
+ print STDERR "W: $switch not specified\n";
+ return;
+ }
+ $var =~ s#/+$##;
+ if ($var !~ m#^[a-z\+]+://#) {
+ $var = '/' . $var if ($var !~ m#^/#);
+ unless ($url) {
+ print STDERR "E: '$var' is not a complete URL ",
+ "and a separate URL is not specified\n";
+ exit 1;
+ }
+ $var = $url . $var;
+ }
+ chomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var)
+ : safe_qx(qw/svn ls --non-interactive/, $var));
+ my $old = $GIT_SVN;
+ defined(my $pid = fork) or croak $!;
+ if (!$pid) {
+ foreach my $u (map { "$var/$_" } (grep m!/$!, @ls)) {
+ $u =~ s#/+$##;
+ if ($u !~ m!\Q$var\E/(.+)$!) {
+ print STDERR "W: Unrecognized URL: $u\n";
+ die "This should never happen\n";
+ }
+ my $id = $pfx.$1;
+ print "init $u => $id\n";
+ $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+ init_vars();
+ init($u);
+ }
+ exit 0;
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+}
+
+sub common_prefix {
+ my $paths = shift;
+ my %common;
+ foreach (@$paths) {
+ my @tmp = split m#/#, $_;
+ my $p = '';
+ while (my $x = shift @tmp) {
+ $p .= "/$x";
+ $common{$p} ||= 0;
+ $common{$p}++;
+ }
+ }
+ foreach (sort {length $b <=> length $a} keys %common) {
+ if ($common{$_} == @$paths) {
+ return $_;
+ }
+ }
+ return '';
+}
+
+# grafts set here are 'stronger' in that they're based on actual tree
+# matches, and won't be deleted from merge-base checking in write_grafts()
+sub graft_tree_joins {
+ my $grafts = shift;
+ map_tree_joins() if (@_branch_from && !%tree_map);
+ return unless %tree_map;
+
+ git_svn_each(sub {
+ my $i = shift;
+ defined(my $pid = open my $fh, '-|') or croak $!;
+ if (!$pid) {
+ exec qw/git-rev-list --pretty=raw/,
+ "refs/remotes/$i" or croak $!;
+ }
+ while (<$fh>) {
+ next unless /^commit ($sha1)$/o;
+ my $c = $1;
+ my ($t) = (<$fh> =~ /^tree ($sha1)$/o);
+ next unless $tree_map{$t};
+
+ my $l;
+ do {
+ $l = readline $fh;
+ } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/);
+
+ my ($s, $tz) = ($1, $2);
+ if ($tz =~ s/^\+//) {
+ $s += tz_to_s_offset($tz);
+ } elsif ($tz =~ s/^\-//) {
+ $s -= tz_to_s_offset($tz);
+ }
+
+ my ($url_a, $r_a, $uuid_a) = cmt_metadata($c);
+
+ foreach my $p (@{$tree_map{$t}}) {
+ next if $p eq $c;
+ my $mb = eval {
+ safe_qx('git-merge-base', $c, $p)
+ };
+ next unless ($@ || $?);
+ if (defined $r_a) {
+ # see if SVN says it's a relative
+ my ($url_b, $r_b, $uuid_b) =
+ cmt_metadata($p);
+ next if (defined $url_b &&
+ defined $url_a &&
+ ($url_a eq $url_b) &&
+ ($uuid_a eq $uuid_b));
+ if ($uuid_a eq $uuid_b) {
+ if ($r_b < $r_a) {
+ $grafts->{$c}->{$p} = 2;
+ next;
+ } elsif ($r_b > $r_a) {
+ $grafts->{$p}->{$c} = 2;
+ next;
+ }
+ }
+ }
+ my $ct = get_commit_time($p);
+ if ($ct < $s) {
+ $grafts->{$c}->{$p} = 2;
+ } elsif ($ct > $s) {
+ $grafts->{$p}->{$c} = 2;
+ }
+ # what should we do when $ct == $s ?
+ }
+ }
+ close $fh or croak $?;
+ });
+}
+
+# this isn't funky-filename safe, but good enough for now...
+sub graft_file_copy_cmd {
+ my ($grafts, $l_map, $u) = @_;
+ my $paths = $l_map->{$u};
+ my $pfx = common_prefix([keys %$paths]);
+ $SVN_URL ||= $u.$pfx;
+ my $pid = open my $fh, '-|';
+ defined $pid or croak $!;
+ unless ($pid) {
+ my @exec = qw/svn log -v/;
+ push @exec, "-r$_revision" if defined $_revision;
+ exec @exec, $u.$pfx or croak $!;
+ }
+ my ($r, $mp) = (undef, undef);
+ while (<$fh>) {
+ chomp;
+ if (/^\-{72}$/) {
+ $mp = $r = undef;
+ } elsif (/^r(\d+) \| /) {
+ $r = $1 unless defined $r;
+ } elsif (/^Changed paths:/) {
+ $mp = 1;
+ } elsif ($mp && m#^ [AR] /(\S.*?) \(from /(\S+?):(\d+)\)$#) {
+ my ($p1, $p0, $r0) = ($1, $2, $3);
+ my $c = find_graft_path_commit($paths, $p1, $r);
+ next unless $c;
+ find_graft_path_parents($grafts, $paths, $c, $p0, $r0);
+ }
+ }
+}
+
+sub graft_file_copy_lib {
+ my ($grafts, $l_map, $u) = @_;
+ my $tree_paths = $l_map->{$u};
+ my $pfx = common_prefix([keys %$tree_paths]);
+ my ($repo, $path) = repo_path_split($u.$pfx);
+ $SVN_LOG ||= libsvn_connect($repo);
+ $SVN ||= libsvn_connect($repo);
+
+ my ($base, $head) = libsvn_parse_revision();
+ my $inc = 1000;
+ my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
+ my $eh = $SVN::Error::handler;
+ $SVN::Error::handler = \&libsvn_skip_unknown_revs;
+ while (1) {
+ my $pool = SVN::Pool->new;
+ libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1,
+ sub {
+ libsvn_graft_file_copies($grafts, $tree_paths,
+ $path, @_);
+ }, $pool);
+ $pool->clear;
+ last if ($max >= $head);
+ $min = $max + 1;
+ $max += $inc;
+ $max = $head if ($max > $head);
+ }
+ $SVN::Error::handler = $eh;
+}
+
+sub process_merge_msg_matches {
+ my ($grafts, $l_map, $u, $p, $c, @matches) = @_;
+ my (@strong, @weak);
+ foreach (@matches) {
+ # merging with ourselves is not interesting
+ next if $_ eq $p;
+ if ($l_map->{$u}->{$_}) {
+ push @strong, $_;
+ } else {
+ push @weak, $_;
+ }
+ }
+ foreach my $w (@weak) {
+ last if @strong;
+ # no exact match, use branch name as regexp.
+ my $re = qr/\Q$w\E/i;
+ foreach (keys %{$l_map->{$u}}) {
+ if (/$re/) {
+ push @strong, $l_map->{$u}->{$_};
+ last;
+ }
+ }
+ last if @strong;
+ $w = basename($w);
+ $re = qr/\Q$w\E/i;
+ foreach (keys %{$l_map->{$u}}) {
+ if (/$re/) {
+ push @strong, $l_map->{$u}->{$_};
+ last;
+ }
+ }
+ }
+ my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+)
+ \s(?:[a-f\d\-]+)$/xsm);
+ unless (defined $rev) {
+ ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+)
+ \@(?:[a-f\d\-]+)/xsm);
+ return unless defined $rev;
+ }
+ foreach my $m (@strong) {
+ my ($r0, $s0) = find_rev_before($rev, $m, 1);
+ $grafts->{$c->{c}}->{$s0} = 1 if defined $s0;
+ }
+}
+
+sub graft_merge_msg {
+ my ($grafts, $l_map, $u, $p, @re) = @_;
+
+ my $x = $l_map->{$u}->{$p};
+ my $rl = rev_list_raw($x);
+ while (my $c = next_rev_list_entry($rl)) {
+ foreach my $re (@re) {
+ my (@br) = ($c->{m} =~ /$re/g);
+ next unless @br;
+ process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br);
+ }
+ }
+}
+
+sub read_uuid {
+ return if $SVN_UUID;
+ if ($_use_lib) {
+ my $pool = SVN::Pool->new;
+ $SVN_UUID = $SVN->get_uuid($pool);
+ $pool->clear;
+ } else {
+ my $info = shift || svn_info('.');
+ $SVN_UUID = $info->{'Repository UUID'} or
+ croak "Repository UUID unreadable\n";
+ }
+}
+
+sub quiet_run {
+ my $pid = fork;
+ defined $pid or croak $!;
+ if (!$pid) {
+ open my $null, '>', '/dev/null' or croak $!;
+ open STDERR, '>&', $null or croak $!;
+ open STDOUT, '>&', $null or croak $!;
+ exec @_ or croak $!;
+ }
+ waitpid $pid, 0;
+ return $?;
+}
+
+sub repo_path_split {
+ my $full_url = shift;
+ $full_url =~ s#/+$##;
+
+ foreach (@repo_path_split_cache) {
+ if ($full_url =~ s#$_##) {
+ my $u = $1;
+ $full_url =~ s#^/+##;
+ return ($u, $full_url);
+ }
+ }
+
+ my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
+ $path =~ s#^/+##;
+ my @paths = split(m#/+#, $path);
+
+ if ($_use_lib) {
+ while (1) {
+ $SVN = libsvn_connect($url);
+ last if (defined $SVN &&
+ defined eval { $SVN->get_latest_revnum });
+ my $n = shift @paths || last;
+ $url .= "/$n";
+ }
+ } else {
+ while (quiet_run(qw/svn ls --non-interactive/, $url)) {
+ my $n = shift @paths || last;
+ $url .= "/$n";
+ }
+ }
+ push @repo_path_split_cache, qr/^(\Q$url\E)/;
+ $path = join('/',@paths);
+ return ($url, $path);
+}
+
+sub setup_git_svn {
+ defined $SVN_URL or croak "SVN repository location required\n";
+ unless (-d $GIT_DIR) {
+ croak "GIT_DIR=$GIT_DIR does not exist!\n";
+ }
+ mkpath([$GIT_SVN_DIR]);
+ mkpath(["$GIT_SVN_DIR/info"]);
+ open my $fh, '>>',$REVDB or croak $!;
+ close $fh;
+ s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url");
+
+}
+
+sub assert_svn_wc_clean {
+ return if $_use_lib;
+ my ($svn_rev) = @_;
+ croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
+ my $lcr = svn_info('.')->{'Last Changed Rev'};
+ if ($svn_rev != $lcr) {
+ print STDERR "Checking for copy-tree ... ";
+ my @diff = grep(/^Index: /,(safe_qx(qw(svn diff),
+ "-r$lcr:$svn_rev")));
+ if (@diff) {
+ croak "Nope! Expected r$svn_rev, got r$lcr\n";
+ } else {
+ print STDERR "OK!\n";
+ }
+ }
+ my @status = grep(!/^Performing status on external/,(`svn status`));
+ @status = grep(!/^\s*$/,@status);
+ if (scalar @status) {
+ print STDERR "Tree ($SVN_WC) is not clean:\n";
+ print STDERR $_ foreach @status;
+ croak;
+ }
+}
+
+sub get_tree_from_treeish {
+ my ($treeish) = @_;
+ croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
+ chomp(my $type = `git-cat-file -t $treeish`);
+ my $expected;
+ while ($type eq 'tag') {
+ chomp(($treeish, $type) = `git-cat-file tag $treeish`);
+ }
+ if ($type eq 'commit') {
+ $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
+ ($expected) = ($expected =~ /^tree ($sha1)$/);
+ die "Unable to get tree from $treeish\n" unless $expected;
+ } elsif ($type eq 'tree') {
+ $expected = $treeish;
+ } else {
+ die "$treeish is a $type, expected tree, tag or commit\n";
+ }
+ return $expected;
+}
+
+sub assert_tree {
+ return if $_use_lib;
+ my ($treeish) = @_;
+ my $expected = get_tree_from_treeish($treeish);
+
+ my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';
+ if (-e $tmpindex) {
+ unlink $tmpindex or croak $!;
+ }
+ my $old_index = set_index($tmpindex);
+ index_changes(1);
+ chomp(my $tree = `git-write-tree`);
+ restore_index($old_index);
+ if ($tree ne $expected) {
+ croak "Tree mismatch, Got: $tree, Expected: $expected\n";
+ }
+ unlink $tmpindex;
+}
+
+sub parse_diff_tree {
+ my $diff_fh = shift;
+ local $/ = "\0";
+ my $state = 'meta';
+ my @mods;
+ while (<$diff_fh>) {
+ chomp $_; # this gets rid of the trailing "\0"
+ if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
+ $sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
+ push @mods, { mode_a => $1, mode_b => $2,
+ sha1_b => $3, chg => $4 };
+ if ($4 =~ /^(?:C|R)$/) {
+ $state = 'file_a';
+ } else {
+ $state = 'file_b';
+ }
+ } elsif ($state eq 'file_a') {
+ my $x = $mods[$#mods] or croak "Empty array\n";
+ if ($x->{chg} !~ /^(?:C|R)$/) {
+ croak "Error parsing $_, $x->{chg}\n";
+ }
+ $x->{file_a} = $_;
+ $state = 'file_b';
+ } elsif ($state eq 'file_b') {
+ my $x = $mods[$#mods] or croak "Empty array\n";
+ if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
+ croak "Error parsing $_, $x->{chg}\n";
+ }
+ if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
+ croak "Error parsing $_, $x->{chg}\n";
+ }
+ $x->{file_b} = $_;
+ $state = 'meta';
+ } else {
+ croak "Error parsing $_\n";
+ }
+ }
+ close $diff_fh or croak $?;
+
+ return \@mods;
+}
+
+sub svn_check_prop_executable {
+ my $m = shift;
+ return if -l $m->{file_b};
+ if ($m->{mode_b} =~ /755$/) {
+ chmod((0755 &~ umask),$m->{file_b}) or croak $!;
+ if ($m->{mode_a} !~ /755$/) {
+ sys(qw(svn propset svn:executable 1), $m->{file_b});
+ }
+ -x $m->{file_b} or croak "$m->{file_b} is not executable!\n";
+ } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
+ sys(qw(svn propdel svn:executable), $m->{file_b});
+ chmod((0644 &~ umask),$m->{file_b}) or croak $!;
+ -x $m->{file_b} and croak "$m->{file_b} is executable!\n";
+ }
+}
+
+sub svn_ensure_parent_path {
+ my $dir_b = dirname(shift);
+ svn_ensure_parent_path($dir_b) if ($dir_b ne File::Spec->curdir);
+ mkpath([$dir_b]) unless (-d $dir_b);
+ sys(qw(svn add -N), $dir_b) unless (-d "$dir_b/.svn");
+}
+
+sub precommit_check {
+ my $mods = shift;
+ my (%rm_file, %rmdir_check, %added_check);
+
+ my %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 );
+ foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+ if ($m->{chg} eq 'R') {
+ if (-d $m->{file_b}) {
+ err_dir_to_file("$m->{file_a} => $m->{file_b}");
+ }
+ # dir/$file => dir/file/$file
+ my $dirname = dirname($m->{file_b});
+ while ($dirname ne File::Spec->curdir) {
+ if ($dirname ne $m->{file_a}) {
+ $dirname = dirname($dirname);
+ next;
+ }
+ err_file_to_dir("$m->{file_a} => $m->{file_b}");
+ }
+ # baz/zzz => baz (baz is a file)
+ $dirname = dirname($m->{file_a});
+ while ($dirname ne File::Spec->curdir) {
+ if ($dirname ne $m->{file_b}) {
+ $dirname = dirname($dirname);
+ next;
+ }
+ err_dir_to_file("$m->{file_a} => $m->{file_b}");
+ }
+ }
+ if ($m->{chg} =~ /^(D|R)$/) {
+ my $t = $1 eq 'D' ? 'file_b' : 'file_a';
+ $rm_file{ $m->{$t} } = 1;
+ my $dirname = dirname( $m->{$t} );
+ my $basename = basename( $m->{$t} );
+ $rmdir_check{$dirname}->{$basename} = 1;
+ } elsif ($m->{chg} =~ /^(?:A|C)$/) {
+ if (-d $m->{file_b}) {
+ err_dir_to_file($m->{file_b});
+ }
+ my $dirname = dirname( $m->{file_b} );
+ my $basename = basename( $m->{file_b} );
+ $added_check{$dirname}->{$basename} = 1;
+ while ($dirname ne File::Spec->curdir) {
+ if ($rm_file{$dirname}) {
+ err_file_to_dir($m->{file_b});
+ }
+ $dirname = dirname $dirname;
+ }
+ }
+ }
+ return (\%rmdir_check, \%added_check);
+
+ sub err_dir_to_file {
+ my $file = shift;
+ print STDERR "Node change from directory to file ",
+ "is not supported by Subversion: ",$file,"\n";
+ exit 1;
+ }
+ sub err_file_to_dir {
+ my $file = shift;
+ print STDERR "Node change from file to directory ",
+ "is not supported by Subversion: ",$file,"\n";
+ exit 1;
+ }
+}
+
+
+sub get_diff {
+ my ($from, $treeish) = @_;
+ assert_tree($from);
+ print "diff-tree $from $treeish\n";
+ my $pid = open my $diff_fh, '-|';
+ defined $pid or croak $!;
+ if ($pid == 0) {
+ my @diff_tree = qw(git-diff-tree -z -r);
+ if ($_cp_similarity) {
+ push @diff_tree, "-C$_cp_similarity";
+ } else {
+ push @diff_tree, '-C';
+ }
+ push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+ push @diff_tree, "-l$_l" if defined $_l;
+ exec(@diff_tree, $from, $treeish) or croak $!;
+ }
+ return parse_diff_tree($diff_fh);
+}
+
+sub svn_checkout_tree {
+ my ($from, $treeish) = @_;
+ my $mods = get_diff($from->{commit}, $treeish);
+ return $mods unless (scalar @$mods);
+ my ($rm, $add) = precommit_check($mods);
+
+ my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+ foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+ if ($m->{chg} eq 'C') {
+ svn_ensure_parent_path( $m->{file_b} );
+ sys(qw(svn cp), $m->{file_a}, $m->{file_b});
+ apply_mod_line_blob($m);
+ svn_check_prop_executable($m);
+ } elsif ($m->{chg} eq 'D') {
+ sys(qw(svn rm --force), $m->{file_b});
+ } elsif ($m->{chg} eq 'R') {
+ svn_ensure_parent_path( $m->{file_b} );
+ sys(qw(svn mv --force), $m->{file_a}, $m->{file_b});
+ apply_mod_line_blob($m);
+ svn_check_prop_executable($m);
+ } elsif ($m->{chg} eq 'M') {
+ apply_mod_line_blob($m);
+ svn_check_prop_executable($m);
+ } elsif ($m->{chg} eq 'T') {
+ sys(qw(svn rm --force),$m->{file_b});
+ apply_mod_line_blob($m);
+ sys(qw(svn add), $m->{file_b});
+ svn_check_prop_executable($m);
+ } elsif ($m->{chg} eq 'A') {
+ svn_ensure_parent_path( $m->{file_b} );
+ apply_mod_line_blob($m);
+ sys(qw(svn add), $m->{file_b});
+ svn_check_prop_executable($m);
+ } else {
+ croak "Invalid chg: $m->{chg}\n";
+ }
+ }
+
+ assert_tree($treeish);
+ if ($_rmdir) { # remove empty directories
+ handle_rmdir($rm, $add);
+ }
+ assert_tree($treeish);
+ return $mods;
+}
+
+sub libsvn_checkout_tree {
+ my ($from, $treeish, $ed) = @_;
+ my $mods = get_diff($from, $treeish);
+ return $mods unless (scalar @$mods);
+ my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+ foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+ my $f = $m->{chg};
+ if (defined $o{$f}) {
+ $ed->$f($m, $_q);
+ } else {
+ croak "Invalid change type: $f\n";
+ }
+ }
+ $ed->rmdirs($_q) if $_rmdir;
+ return $mods;
+}
+
+# svn ls doesn't work with respect to the current working tree, but what's
+# in the repository. There's not even an option for it... *sigh*
+# (added files don't show up and removed files remain in the ls listing)
+sub svn_ls_current {
+ my ($dir, $rm, $add) = @_;
+ chomp(my @ls = safe_qx('svn','ls',$dir));
+ my @ret = ();
+ foreach (@ls) {
+ s#/$##; # trailing slashes are evil
+ push @ret, $_ unless $rm->{$dir}->{$_};
+ }
+ if (exists $add->{$dir}) {
+ push @ret, keys %{$add->{$dir}};
+ }
+ return \@ret;
+}
+
+sub handle_rmdir {
+ my ($rm, $add) = @_;
+
+ foreach my $dir (sort {length $b <=> length $a} keys %$rm) {
+ my $ls = svn_ls_current($dir, $rm, $add);
+ next if (scalar @$ls);
+ sys(qw(svn rm --force),$dir);
+
+ my $dn = dirname $dir;
+ $rm->{ $dn }->{ basename $dir } = 1;
+ $ls = svn_ls_current($dn, $rm, $add);
+ while (scalar @$ls == 0 && $dn ne File::Spec->curdir) {
+ sys(qw(svn rm --force),$dn);
+ $dir = basename $dn;
+ $dn = dirname $dn;
+ $rm->{ $dn }->{ $dir } = 1;
+ $ls = svn_ls_current($dn, $rm, $add);
+ }
+ }
+}
+
+sub get_commit_message {
+ my ($commit, $commit_msg) = (@_);
+ my %log_msg = ( msg => '' );
+ open my $msg, '>', $commit_msg or croak $!;
+
+ chomp(my $type = `git-cat-file -t $commit`);
+ if ($type eq 'commit' || $type eq 'tag') {
+ my $pid = open my $msg_fh, '-|';
+ defined $pid or croak $!;
+
+ if ($pid == 0) {
+ exec('git-cat-file', $type, $commit) or croak $!;
+ }
+ my $in_msg = 0;
+ while (<$msg_fh>) {
+ if (!$in_msg) {
+ $in_msg = 1 if (/^\s*$/);
+ } elsif (/^git-svn-id: /) {
+ # skip this, we regenerate the correct one
+ # on re-fetch anyways
+ } else {
+ print $msg $_ or croak $!;
+ }
+ }
+ close $msg_fh or croak $?;
+ }
+ close $msg or croak $!;
+
+ if ($_edit || ($type eq 'tree')) {
+ my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
+ system($editor, $commit_msg);
+ }
+
+ # file_to_s removes all trailing newlines, so just use chomp() here:
+ open $msg, '<', $commit_msg or croak $!;
+ { local $/; chomp($log_msg{msg} = <$msg>); }
+ close $msg or croak $!;
+
+ return \%log_msg;
+}
+
+sub set_svn_commit_env {
+ if (defined $LC_ALL) {
+ $ENV{LC_ALL} = $LC_ALL;
+ } else {
+ delete $ENV{LC_ALL};
+ }
+}
+
+sub svn_commit_tree {
+ my ($last, $commit) = @_;
+ my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
+ my $log_msg = get_commit_message($commit, $commit_msg);
+ my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/);
+ print "Committing $commit: $oneline\n";
+
+ set_svn_commit_env();
+ my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
+ $ENV{LC_ALL} = 'C';
+ unlink $commit_msg;
+ my ($committed) = ($ci_output[$#ci_output] =~ /(\d+)/);
+ if (!defined $committed) {
+ my $out = join("\n",@ci_output);
+ print STDERR "W: Trouble parsing \`svn commit' output:\n\n",
+ $out, "\n\nAssuming English locale...";
+ ($committed) = ($out =~ /^Committed revision \d+\./sm);
+ defined $committed or die " FAILED!\n",
+ "Commit output failed to parse committed revision!\n",
+ print STDERR " OK\n";
+ }
+
+ my @svn_up = qw(svn up);
+ push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
+ if ($_optimize_commits && ($committed == ($last->{revision} + 1))) {
+ push @svn_up, "-r$committed";
+ sys(@svn_up);
+ my $info = svn_info('.');
+ my $date = $info->{'Last Changed Date'} or die "Missing date\n";
+ if ($info->{'Last Changed Rev'} != $committed) {
+ croak "$info->{'Last Changed Rev'} != $committed\n"
+ }
+ my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
+ /(\d{4})\-(\d\d)\-(\d\d)\s
+ (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
+ or croak "Failed to parse date: $date\n";
+ $log_msg->{date} = "$tz $Y-$m-$d $H:$M:$S";
+ $log_msg->{author} = $info->{'Last Changed Author'};
+ $log_msg->{revision} = $committed;
+ $log_msg->{msg} .= "\n";
+ $log_msg->{parents} = [ $last->{commit} ];
+ $log_msg->{commit} = git_commit($log_msg, $commit);
+ return $log_msg;
+ }
+ # resync immediately
+ push @svn_up, "-r$last->{revision}";
+ sys(@svn_up);
+ return fetch("$committed=$commit");
+}
+
+sub rev_list_raw {
+ my (@args) = @_;
+ my $pid = open my $fh, '-|';
+ defined $pid or croak $!;
+ if (!$pid) {
+ exec(qw/git-rev-list --pretty=raw/, @args) or croak $!;
+ }
+ return { fh => $fh, t => { } };
+}
+
+sub next_rev_list_entry {
+ my $rl = shift;
+ my $fh = $rl->{fh};
+ my $x = $rl->{t};
+ while (<$fh>) {
+ if (/^commit ($sha1)$/o) {
+ if ($x->{c}) {
+ $rl->{t} = { c => $1 };
+ return $x;
+ } else {
+ $x->{c} = $1;
+ }
+ } elsif (/^parent ($sha1)$/o) {
+ $x->{p}->{$1} = 1;
+ } elsif (s/^ //) {
+ $x->{m} ||= '';
+ $x->{m} .= $_;
+ }
+ }
+ return ($x != $rl->{t}) ? $x : undef;
+}
+
+# read the entire log into a temporary file (which is removed ASAP)
+# and store the file handle + parser state
+sub svn_log_raw {
+ my (@log_args) = @_;
+ my $log_fh = IO::File->new_tmpfile or croak $!;
+ my $pid = fork;
+ defined $pid or croak $!;
+ if (!$pid) {
+ open STDOUT, '>&', $log_fh or croak $!;
+ exec (qw(svn log), @log_args) or croak $!
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+ seek $log_fh, 0, 0 or croak $!;
+ return { state => 'sep', fh => $log_fh };
+}
+
+sub next_log_entry {
+ my $log = shift; # retval of svn_log_raw()
+ my $ret = undef;
+ my $fh = $log->{fh};
+
+ while (<$fh>) {
+ chomp;
+ if (/^\-{72}$/) {
+ if ($log->{state} eq 'msg') {
+ if ($ret->{lines}) {
+ $ret->{msg} .= $_."\n";
+ unless(--$ret->{lines}) {
+ $log->{state} = 'sep';
+ }
+ } else {
+ croak "Log parse error at: $_\n",
+ $ret->{revision},
+ "\n";
+ }
+ next;
+ }
+ if ($log->{state} ne 'sep') {
+ croak "Log parse error at: $_\n",
+ "state: $log->{state}\n",
+ $ret->{revision},
+ "\n";
+ }
+ $log->{state} = 'rev';
+
+ # if we have an empty log message, put something there:
+ if ($ret) {
+ $ret->{msg} ||= "\n";
+ delete $ret->{lines};
+ return $ret;
+ }
+ next;
+ }
+ if ($log->{state} eq 'rev' && s/^r(\d+)\s*\|\s*//) {
+ my $rev = $1;
+ my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3);
+ ($lines) = ($lines =~ /(\d+)/);
+ my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
+ /(\d{4})\-(\d\d)\-(\d\d)\s
+ (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
+ or croak "Failed to parse date: $date\n";
+ $ret = { revision => $rev,
+ date => "$tz $Y-$m-$d $H:$M:$S",
+ author => $author,
+ lines => $lines,
+ msg => '' };
+ if (defined $_authors && ! defined $users{$author}) {
+ die "Author: $author not defined in ",
+ "$_authors file\n";
+ }
+ $log->{state} = 'msg_start';
+ next;
+ }
+ # skip the first blank line of the message:
+ if ($log->{state} eq 'msg_start' && /^$/) {
+ $log->{state} = 'msg';
+ } elsif ($log->{state} eq 'msg') {
+ if ($ret->{lines}) {
+ $ret->{msg} .= $_."\n";
+ unless (--$ret->{lines}) {
+ $log->{state} = 'sep';
+ }
+ } else {
+ croak "Log parse error at: $_\n",
+ $ret->{revision},"\n";
+ }
+ }
+ }
+ return $ret;
+}
+
+sub svn_info {
+ my $url = shift || $SVN_URL;
+
+ my $pid = open my $info_fh, '-|';
+ defined $pid or croak $!;
+
+ if ($pid == 0) {
+ exec(qw(svn info),$url) or croak $!;
+ }
+
+ my $ret = {};
+ # only single-lines seem to exist in svn info output
+ while (<$info_fh>) {
+ chomp $_;
+ if (m#^([^:]+)\s*:\s*(\S.*)$#) {
+ $ret->{$1} = $2;
+ push @{$ret->{-order}}, $1;
+ }
+ }
+ close $info_fh or croak $?;
+ return $ret;
+}
+
+sub sys { system(@_) == 0 or croak $? }
+
+sub eol_cp {
+ my ($from, $to) = @_;
+ my $es = svn_propget_base('svn:eol-style', $to);
+ open my $rfd, '<', $from or croak $!;
+ binmode $rfd or croak $!;
+ open my $wfd, '>', $to or croak $!;
+ binmode $wfd or croak $!;
+ eol_cp_fd($rfd, $wfd, $es);
+ close $rfd or croak $!;
+ close $wfd or croak $!;
+}
+
+sub eol_cp_fd {
+ my ($rfd, $wfd, $es) = @_;
+ my $eol = defined $es ? $EOL{$es} : undef;
+ my $buf;
+ use bytes;
+ while (1) {
+ my ($r, $w, $t);
+ defined($r = sysread($rfd, $buf, 4096)) or croak $!;
+ return unless $r;
+ if ($eol) {
+ if ($buf =~ /\015$/) {
+ my $c;
+ defined($r = sysread($rfd,$c,1)) or croak $!;
+ $buf .= $c if $r > 0;
+ }
+ $buf =~ s/(?:\015\012|\015|\012)/$eol/gs;
+ $r = length($buf);
+ }
+ for ($w = 0; $w < $r; $w += $t) {
+ $t = syswrite($wfd, $buf, $r - $w, $w) or croak $!;
+ }
+ }
+ no bytes;
+}
+
+sub do_update_index {
+ my ($z_cmd, $cmd, $no_text_base) = @_;
+
+ my $z = open my $p, '-|';
+ defined $z or croak $!;
+ unless ($z) { exec @$z_cmd or croak $! }
+
+ my $pid = open my $ui, '|-';
+ defined $pid or croak $!;
+ unless ($pid) {
+ exec('git-update-index',"--$cmd",'-z','--stdin') or croak $!;
+ }
+ local $/ = "\0";
+ while (my $x = <$p>) {
+ chomp $x;
+ if (!$no_text_base && lstat $x && ! -l _ &&
+ svn_propget_base('svn:keywords', $x)) {
+ my $mode = -x _ ? 0755 : 0644;
+ my ($v,$d,$f) = File::Spec->splitpath($x);
+ my $tb = File::Spec->catfile($d, '.svn', 'tmp',
+ 'text-base',"$f.svn-base");
+ $tb =~ s#^/##;
+ unless (-f $tb) {
+ $tb = File::Spec->catfile($d, '.svn',
+ 'text-base',"$f.svn-base");
+ $tb =~ s#^/##;
+ }
+ unlink $x or croak $!;
+ eol_cp($tb, $x);
+ chmod(($mode &~ umask), $x) or croak $!;
+ }
+ print $ui $x,"\0";
+ }
+ close $ui or croak $?;
+}
+
+sub index_changes {
+ return if $_use_lib;
+
+ if (!-f "$GIT_SVN_DIR/info/exclude") {
+ open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!;
+ print $fd '.svn',"\n";
+ close $fd or croak $!;
+ }
+ my $no_text_base = shift;
+ do_update_index([qw/git-diff-files --name-only -z/],
+ 'remove',
+ $no_text_base);
+ do_update_index([qw/git-ls-files -z --others/,
+ "--exclude-from=$GIT_SVN_DIR/info/exclude"],
+ 'add',
+ $no_text_base);
+}
+
+sub s_to_file {
+ my ($str, $file, $mode) = @_;
+ open my $fd,'>',$file or croak $!;
+ print $fd $str,"\n" or croak $!;
+ close $fd or croak $!;
+ chmod ($mode &~ umask, $file) if (defined $mode);
+}
+
+sub file_to_s {
+ my $file = shift;
+ open my $fd,'<',$file or croak "$!: file: $file\n";
+ local $/;
+ my $ret = <$fd>;
+ close $fd or croak $!;
+ $ret =~ s/\s*$//s;
+ return $ret;
+}
+
+sub assert_revision_unknown {
+ my $r = shift;
+ if (my $c = revdb_get($REVDB, $r)) {
+ croak "$r = $c already exists! Why are we refetching it?";
+ }
+}
+
+sub trees_eq {
+ my ($x, $y) = @_;
+ my @x = safe_qx('git-cat-file','commit',$x);
+ my @y = safe_qx('git-cat-file','commit',$y);
+ if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/
+ || $y[0] !~ /^tree $sha1\n$/) {
+ print STDERR "Trees not equal: $y[0] != $x[0]\n";
+ return 0
+ }
+ return 1;
+}
+
+sub git_commit {
+ my ($log_msg, @parents) = @_;
+ assert_revision_unknown($log_msg->{revision});
+ map_tree_joins() if (@_branch_from && !%tree_map);
+
+ my (@tmp_parents, @exec_parents, %seen_parent);
+ if (my $lparents = $log_msg->{parents}) {
+ @tmp_parents = @$lparents
+ }
+ # commit parents can be conditionally bound to a particular
+ # svn revision via: "svn_revno=commit_sha1", filter them out here:
+ foreach my $p (@parents) {
+ next unless defined $p;
+ if ($p =~ /^(\d+)=($sha1_short)$/o) {
+ if ($1 == $log_msg->{revision}) {
+ push @tmp_parents, $2;
+ }
+ } else {
+ push @tmp_parents, $p if $p =~ /$sha1_short/o;
+ }
+ }
+ my $tree = $log_msg->{tree};
+ if (!defined $tree) {
+ my $index = set_index($GIT_SVN_INDEX);
+ index_changes();
+ chomp($tree = `git-write-tree`);
+ croak $? if $?;
+ restore_index($index);
+ }
+
+ # just in case we clobber the existing ref, we still want that ref
+ # as our parent:
+ if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) {
+ push @tmp_parents, $cur;
+ }
+
+ if (exists $tree_map{$tree}) {
+ foreach my $p (@{$tree_map{$tree}}) {
+ my $skip;
+ foreach (@tmp_parents) {
+ # see if a common parent is found
+ my $mb = eval {
+ safe_qx('git-merge-base', $_, $p)
+ };
+ next if ($@ || $?);
+ $skip = 1;
+ last;
+ }
+ next if $skip;
+ my ($url_p, $r_p, $uuid_p) = cmt_metadata($p);
+ next if (($SVN_UUID eq $uuid_p) &&
+ ($log_msg->{revision} > $r_p));
+ next if (defined $url_p && defined $SVN_URL &&
+ ($SVN_UUID eq $uuid_p) &&
+ ($url_p eq $SVN_URL));
+ push @tmp_parents, $p;
+ }
+ }
+ foreach (@tmp_parents) {
+ next if $seen_parent{$_};
+ $seen_parent{$_} = 1;
+ push @exec_parents, $_;
+ # MAXPARENT is defined to 16 in commit-tree.c:
+ last if @exec_parents > 16;
+ }
+
+ set_commit_env($log_msg);
+ my @exec = ('git-commit-tree', $tree);
+ push @exec, '-p', $_ foreach @exec_parents;
+ defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
+ or croak $!;
+ print $msg_fh $log_msg->{msg} or croak $!;
+ unless ($_no_metadata) {
+ print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
+ " $SVN_UUID\n" or croak $!;
+ }
+ $msg_fh->flush == 0 or croak $!;
+ close $msg_fh or croak $!;
+ chomp(my $commit = do { local $/; <$out_fh> });
+ close $out_fh or croak $!;
+ waitpid $pid, 0;
+ croak $? if $?;
+ if ($commit !~ /^$sha1$/o) {
+ die "Failed to commit, invalid sha1: $commit\n";
+ }
+ sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
+ revdb_set($REVDB, $log_msg->{revision}, $commit);
+
+ # this output is read via pipe, do not change:
+ print "r$log_msg->{revision} = $commit\n";
+ check_repack();
+ return $commit;
+}
+
+sub check_repack {
+ if ($_repack && (--$_repack_nr == 0)) {
+ $_repack_nr = $_repack;
+ sys("git repack $_repack_flags");
+ }
+}
+
+sub set_commit_env {
+ my ($log_msg) = @_;
+ my $author = $log_msg->{author};
+ if (!defined $author || length $author == 0) {
+ $author = '(no author)';
+ }
+ my ($name,$email) = defined $users{$author} ? @{$users{$author}}
+ : ($author,"$author\@$SVN_UUID");
+ $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
+ $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
+ $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
+}
+
+sub apply_mod_line_blob {
+ my $m = shift;
+ if ($m->{mode_b} =~ /^120/) {
+ blob_to_symlink($m->{sha1_b}, $m->{file_b});
+ } else {
+ blob_to_file($m->{sha1_b}, $m->{file_b});
+ }
+}
+
+sub blob_to_symlink {
+ my ($blob, $link) = @_;
+ defined $link or croak "\$link not defined!\n";
+ croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
+ if (-l $link || -f _) {
+ unlink $link or croak $!;
+ }
+
+ my $dest = `git-cat-file blob $blob`; # no newline, so no chomp
+ symlink $dest, $link or croak $!;
+}
+
+sub blob_to_file {
+ my ($blob, $file) = @_;
+ defined $file or croak "\$file not defined!\n";
+ croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
+ if (-l $file || -f _) {
+ unlink $file or croak $!;
+ }
+
+ open my $blob_fh, '>', $file or croak "$!: $file\n";
+ my $pid = fork;
+ defined $pid or croak $!;
+
+ if ($pid == 0) {
+ open STDOUT, '>&', $blob_fh or croak $!;
+ exec('git-cat-file','blob',$blob) or croak $!;
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+
+ close $blob_fh or croak $!;
+}
+
+sub safe_qx {
+ my $pid = open my $child, '-|';
+ defined $pid or croak $!;
+ if ($pid == 0) {
+ exec(@_) or croak $!;
+ }
+ my @ret = (<$child>);
+ close $child or croak $?;
+ die $? if $?; # just in case close didn't error out
+ return wantarray ? @ret : join('',@ret);
+}
+
+sub svn_compat_check {
+ if ($_follow_parent) {
+ print STDERR 'E: --follow-parent functionality is only ',
+ "available when SVN libraries are used\n";
+ exit 1;
+ }
+ my @co_help = safe_qx(qw(svn co -h));
+ unless (grep /ignore-externals/,@co_help) {
+ print STDERR "W: Installed svn version does not support ",
+ "--ignore-externals\n";
+ $_no_ignore_ext = 1;
+ }
+ if (grep /usage: checkout URL\[\@REV\]/,@co_help) {
+ $_svn_co_url_revs = 1;
+ }
+ if (grep /\[TARGET\[\@REV\]\.\.\.\]/, `svn propget -h`) {
+ $_svn_pg_peg_revs = 1;
+ }
+
+ # I really, really hope nobody hits this...
+ unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) {
+ print STDERR <<'';
+W: The installed svn version does not support the --stop-on-copy flag in
+ the log command.
+ Lets hope the directory you're tracking is not a branch or tag
+ and was never moved within the repository...
+
+ $_no_stop_copy = 1;
+ }
+}
+
+# *sigh*, new versions of svn won't honor -r<rev> without URL@<rev>,
+# (and they won't honor URL@<rev> without -r<rev>, too!)
+sub svn_cmd_checkout {
+ my ($url, $rev, $dir) = @_;
+ my @cmd = ('svn','co', "-r$rev");
+ push @cmd, '--ignore-externals' unless $_no_ignore_ext;
+ $url .= "\@$rev" if $_svn_co_url_revs;
+ sys(@cmd, $url, $dir);
+}
+
+sub check_upgrade_needed {
+ if (!-r $REVDB) {
+ -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
+ open my $fh, '>>',$REVDB or croak $!;
+ close $fh;
+ }
+ my $old = eval {
+ my $pid = open my $child, '-|';
+ defined $pid or croak $!;
+ if ($pid == 0) {
+ close STDERR;
+ exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $!;
+ }
+ my @ret = (<$child>);
+ close $child or croak $?;
+ die $? if $?; # just in case close didn't error out
+ return wantarray ? @ret : join('',@ret);
+ };
+ return unless $old;
+ my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
+ if ($@ || !$head) {
+ print STDERR "Please run: $0 rebuild --upgrade\n";
+ exit 1;
+ }
+}
+
+# fills %tree_map with a reverse mapping of trees to commits. Useful
+# for finding parents to commit on.
+sub map_tree_joins {
+ my %seen;
+ foreach my $br (@_branch_from) {
+ my $pid = open my $pipe, '-|';
+ defined $pid or croak $!;
+ if ($pid == 0) {
+ exec(qw(git-rev-list --topo-order --pretty=raw), $br)
+ or croak $!;
+ }
+ while (<$pipe>) {
+ if (/^commit ($sha1)$/o) {
+ my $commit = $1;
+
+ # if we've seen a commit,
+ # we've seen its parents
+ last if $seen{$commit};
+ my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
+ unless (defined $tree) {
+ die "Failed to parse commit $commit\n";
+ }
+ push @{$tree_map{$tree}}, $commit;
+ $seen{$commit} = 1;
+ }
+ }
+ close $pipe; # we could be breaking the pipe early
+ }
+}
+
+sub load_all_refs {
+ if (@_branch_from) {
+ print STDERR '--branch|-b parameters are ignored when ',
+ "--branch-all-refs|-B is passed\n";
+ }
+
+ # don't worry about rev-list on non-commit objects/tags,
+ # it shouldn't blow up if a ref is a blob or tree...
+ chomp(@_branch_from = `git-rev-parse --symbolic --all`);
+}
+
+# '<svn username> = real-name <email address>' mapping based on git-svnimport:
+sub load_authors {
+ open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+ while (<$authors>) {
+ chomp;
+ next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+ my ($user, $name, $email) = ($1, $2, $3);
+ $users{$user} = [$name, $email];
+ }
+ close $authors or croak $!;
+}
+
+sub rload_authors {
+ open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+ while (<$authors>) {
+ chomp;
+ next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+ my ($user, $name, $email) = ($1, $2, $3);
+ $rusers{"$name <$email>"} = $user;
+ }
+ close $authors or croak $!;
+}
+
+sub svn_propget_base {
+ my ($p, $f) = @_;
+ $f .= '@BASE' if $_svn_pg_peg_revs;
+ return safe_qx(qw/svn propget/, $p, $f);
+}
+
+sub git_svn_each {
+ my $sub = shift;
+ foreach (`git-rev-parse --symbolic --all`) {
+ next unless s#^refs/remotes/##;
+ chomp $_;
+ next unless -f "$GIT_DIR/svn/$_/info/url";
+ &$sub($_);
+ }
+}
+
+sub migrate_revdb {
+ git_svn_each(sub {
+ my $id = shift;
+ defined(my $pid = fork) or croak $!;
+ if (!$pid) {
+ $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+ init_vars();
+ exit 0 if -r $REVDB;
+ print "Upgrading svn => git mapping...\n";
+ -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
+ open my $fh, '>>',$REVDB or croak $!;
+ close $fh;
+ rebuild();
+ print "Done upgrading. You may now delete the ",
+ "deprecated $GIT_SVN_DIR/revs directory\n";
+ exit 0;
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+ });
+}
+
+sub migration_check {
+ migrate_revdb() unless (-e $REVDB);
+ return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR);
+ print "Upgrading repository...\n";
+ unless (-d "$GIT_DIR/svn") {
+ mkdir "$GIT_DIR/svn" or croak $!;
+ }
+ print "Data from a previous version of git-svn exists, but\n\t",
+ "$GIT_SVN_DIR\n\t(required for this version ",
+ "($VERSION) of git-svn) does not.\n";
+
+ foreach my $x (`git-rev-parse --symbolic --all`) {
+ next unless $x =~ s#^refs/remotes/##;
+ chomp $x;
+ next unless -f "$GIT_DIR/$x/info/url";
+ my $u = eval { file_to_s("$GIT_DIR/$x/info/url") };
+ next unless $u;
+ my $dn = dirname("$GIT_DIR/svn/$x");
+ mkpath([$dn]) unless -d $dn;
+ rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x";
+ }
+ migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);
+ print "Done upgrading.\n";
+}
+
+sub find_rev_before {
+ my ($r, $id, $eq_ok) = @_;
+ my $f = "$GIT_DIR/svn/$id/.rev_db";
+ return (undef,undef) unless -r $f;
+ --$r unless $eq_ok;
+ while ($r > 0) {
+ if (my $c = revdb_get($f, $r)) {
+ return ($r, $c);
+ }
+ --$r;
+ }
+ return (undef, undef);
+}
+
+sub init_vars {
+ $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
+ $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN";
+ $REVDB = "$GIT_SVN_DIR/.rev_db";
+ $GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
+ $SVN_URL = undef;
+ $SVN_WC = "$GIT_SVN_DIR/tree";
+ %tree_map = ();
+}
+
+# convert GetOpt::Long specs for use by git-repo-config
+sub read_repo_config {
+ return unless -d $GIT_DIR;
+ my $opts = shift;
+ foreach my $o (keys %$opts) {
+ my $v = $opts->{$o};
+ my ($key) = ($o =~ /^([a-z\-]+)/);
+ $key =~ s/-//g;
+ my $arg = 'git-repo-config';
+ $arg .= ' --int' if ($o =~ /[:=]i$/);
+ $arg .= ' --bool' if ($o !~ /[:=][sfi]$/);
+ if (ref $v eq 'ARRAY') {
+ chomp(my @tmp = `$arg --get-all svn.$key`);
+ @$v = @tmp if @tmp;
+ } else {
+ chomp(my $tmp = `$arg --get svn.$key`);
+ if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {
+ $$v = $tmp;
+ }
+ }
+ }
+}
+
+sub set_default_vals {
+ if (defined $_repack) {
+ $_repack = 1000 if ($_repack <= 0);
+ $_repack_nr = $_repack;
+ $_repack_flags ||= '-d';
+ }
+}
+
+sub read_grafts {
+ my $gr_file = shift;
+ my ($grafts, $comments) = ({}, {});
+ if (open my $fh, '<', $gr_file) {
+ my @tmp;
+ while (<$fh>) {
+ if (/^($sha1)\s+/) {
+ my $c = $1;
+ if (@tmp) {
+ @{$comments->{$c}} = @tmp;
+ @tmp = ();
+ }
+ foreach my $p (split /\s+/, $_) {
+ $grafts->{$c}->{$p} = 1;
+ }
+ } else {
+ push @tmp, $_;
+ }
+ }
+ close $fh or croak $!;
+ @{$comments->{'END'}} = @tmp if @tmp;
+ }
+ return ($grafts, $comments);
+}
+
+sub write_grafts {
+ my ($grafts, $comments, $gr_file) = @_;
+
+ open my $fh, '>', $gr_file or croak $!;
+ foreach my $c (sort keys %$grafts) {
+ if ($comments->{$c}) {
+ print $fh $_ foreach @{$comments->{$c}};
+ }
+ my $p = $grafts->{$c};
+ my %x; # real parents
+ delete $p->{$c}; # commits are not self-reproducing...
+ my $pid = open my $ch, '-|';
+ defined $pid or croak $!;
+ if (!$pid) {
+ exec(qw/git-cat-file commit/, $c) or croak $!;
+ }
+ while (<$ch>) {
+ if (/^parent ($sha1)/) {
+ $x{$1} = $p->{$1} = 1;
+ } else {
+ last unless /^\S/;
+ }
+ }
+ close $ch; # breaking the pipe
+
+ # if real parents are the only ones in the grafts, drop it
+ next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
+
+ my (@ip, @jp, $mb);
+ my %del = %x;
+ @ip = @jp = keys %$p;
+ foreach my $i (@ip) {
+ next if $del{$i} || $p->{$i} == 2;
+ foreach my $j (@jp) {
+ next if $i eq $j || $del{$j} || $p->{$j} == 2;
+ $mb = eval { safe_qx('git-merge-base',$i,$j) };
+ next unless $mb;
+ chomp $mb;
+ next if $x{$mb};
+ if ($mb eq $j) {
+ delete $p->{$i};
+ $del{$i} = 1;
+ } elsif ($mb eq $i) {
+ delete $p->{$j};
+ $del{$j} = 1;
+ }
+ }
+ }
+
+ # if real parents are the only ones in the grafts, drop it
+ next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
+
+ print $fh $c, ' ', join(' ', sort keys %$p),"\n";
+ }
+ if ($comments->{'END'}) {
+ print $fh $_ foreach @{$comments->{'END'}};
+ }
+ close $fh or croak $!;
+}
+
+sub read_url_paths_all {
+ my ($l_map, $pfx, $p) = @_;
+ my @dir;
+ foreach (<$p/*>) {
+ if (-r "$_/info/url") {
+ $pfx .= '/' if $pfx && $pfx !~ m!/$!;
+ my $id = $pfx . basename $_;
+ my $url = file_to_s("$_/info/url");
+ my ($u, $p) = repo_path_split($url);
+ $l_map->{$u}->{$p} = $id;
+ } elsif (-d $_) {
+ push @dir, $_;
+ }
+ }
+ foreach (@dir) {
+ my $x = $_;
+ $x =~ s!^\Q$GIT_DIR\E/svn/!!o;
+ read_url_paths_all($l_map, $x, $_);
+ }
+}
+
+# this one only gets ids that have been imported, not new ones
+sub read_url_paths {
+ my $l_map = {};
+ git_svn_each(sub { my $x = shift;
+ my $url = file_to_s("$GIT_DIR/svn/$x/info/url");
+ my ($u, $p) = repo_path_split($url);
+ $l_map->{$u}->{$p} = $x;
+ });
+ return $l_map;
+}
+
+sub extract_metadata {
+ my $id = shift or return (undef, undef, undef);
+ my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
+ \s([a-f\d\-]+)$/x);
+ if (!$rev || !$uuid || !$url) {
+ # some of the original repositories I made had
+ # identifiers like this:
+ ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
+ }
+ return ($url, $rev, $uuid);
+}
+
+sub cmt_metadata {
+ return extract_metadata((grep(/^git-svn-id: /,
+ safe_qx(qw/git-cat-file commit/, shift)))[-1]);
+}
+
+sub get_commit_time {
+ my $cmt = shift;
+ defined(my $pid = open my $fh, '-|') or croak $!;
+ if (!$pid) {
+ exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!;
+ }
+ while (<$fh>) {
+ /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
+ my ($s, $tz) = ($1, $2);
+ if ($tz =~ s/^\+//) {
+ $s += tz_to_s_offset($tz);
+ } elsif ($tz =~ s/^\-//) {
+ $s -= tz_to_s_offset($tz);
+ }
+ close $fh;
+ return $s;
+ }
+ die "Can't get commit time for commit: $cmt\n";
+}
+
+sub tz_to_s_offset {
+ my ($tz) = @_;
+ $tz =~ s/(\d\d)$//;
+ return ($1 * 60) + ($tz * 3600);
+}
+
+sub setup_pager { # translated to Perl from pager.c
+ return unless (-t *STDOUT);
+ my $pager = $ENV{PAGER};
+ if (!defined $pager) {
+ $pager = 'less';
+ } elsif (length $pager == 0 || $pager eq 'cat') {
+ return;
+ }
+ pipe my $rfd, my $wfd or return;
+ defined(my $pid = fork) or croak $!;
+ if (!$pid) {
+ open STDOUT, '>&', $wfd or croak $!;
+ return;
+ }
+ open STDIN, '<&', $rfd or croak $!;
+ $ENV{LESS} ||= '-S';
+ exec $pager or croak "Can't run pager: $!\n";;
+}
+
+sub get_author_info {
+ my ($dest, $author, $t, $tz) = @_;
+ $author =~ s/(?:^\s*|\s*$)//g;
+ $dest->{a_raw} = $author;
+ my $_a;
+ if ($_authors) {
+ $_a = $rusers{$author} || undef;
+ }
+ if (!$_a) {
+ ($_a) = ($author =~ /<([^>]+)\@[^>]+>$/);
+ }
+ $dest->{t} = $t;
+ $dest->{tz} = $tz;
+ $dest->{a} = $_a;
+ # Date::Parse isn't in the standard Perl distro :(
+ if ($tz =~ s/^\+//) {
+ $t += tz_to_s_offset($tz);
+ } elsif ($tz =~ s/^\-//) {
+ $t -= tz_to_s_offset($tz);
+ }
+ $dest->{t_utc} = $t;
+}
+
+sub process_commit {
+ my ($c, $r_min, $r_max, $defer) = @_;
+ if (defined $r_min && defined $r_max) {
+ if ($r_min == $c->{r} && $r_min == $r_max) {
+ show_commit($c);
+ return 0;
+ }
+ return 1 if $r_min == $r_max;
+ if ($r_min < $r_max) {
+ # we need to reverse the print order
+ return 0 if (defined $_limit && --$_limit < 0);
+ push @$defer, $c;
+ return 1;
+ }
+ if ($r_min != $r_max) {
+ return 1 if ($r_min < $c->{r});
+ return 1 if ($r_max > $c->{r});
+ }
+ }
+ return 0 if (defined $_limit && --$_limit < 0);
+ show_commit($c);
+ return 1;
+}
+
+sub show_commit {
+ my $c = shift;
+ if ($_oneline) {
+ my $x = "\n";
+ if (my $l = $c->{l}) {
+ while ($l->[0] =~ /^\s*$/) { shift @$l }
+ $x = $l->[0];
+ }
+ $_l_fmt ||= 'A' . length($c->{r});
+ print 'r',pack($_l_fmt, $c->{r}),' | ';
+ print "$c->{c} | " if $_show_commit;
+ print $x;
+ } else {
+ show_commit_normal($c);
+ }
+}
+
+sub show_commit_normal {
+ my ($c) = @_;
+ print '-' x72, "\nr$c->{r} | ";
+ print "$c->{c} | " if $_show_commit;
+ print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
+ localtime($c->{t_utc})), ' | ';
+ my $nr_line = 0;
+
+ if (my $l = $c->{l}) {
+ while ($l->[$#$l] eq "\n" && $l->[($#$l - 1)] eq "\n") {
+ pop @$l;
+ }
+ $nr_line = scalar @$l;
+ if (!$nr_line) {
+ print "1 line\n\n\n";
+ } else {
+ if ($nr_line == 1) {
+ $nr_line = '1 line';
+ } else {
+ $nr_line .= ' lines';
+ }
+ print $nr_line, "\n\n";
+ print $_ foreach @$l;
+ }
+ } else {
+ print "1 line\n\n";
+
+ }
+ foreach my $x (qw/raw diff/) {
+ if ($c->{$x}) {
+ print "\n";
+ print $_ foreach @{$c->{$x}}
+ }
+ }
+}
+
+sub libsvn_load {
+ return unless $_use_lib;
+ $_use_lib = eval {
+ require SVN::Core;
+ if ($SVN::Core::VERSION lt '1.1.0') {
+ die "Need SVN::Core 1.1.0 or better ",
+ "(got $SVN::Core::VERSION) ",
+ "Falling back to command-line svn\n";
+ }
+ require SVN::Ra;
+ require SVN::Delta;
+ push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
+ my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
+ $SVN::Node::dir.$SVN::Node::unknown.
+ $SVN::Node::none.$SVN::Node::file.
+ $SVN::Node::dir.$SVN::Node::unknown;
+ 1;
+ };
+}
+
+sub libsvn_connect {
+ my ($url) = @_;
+ my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
+ SVN::Client::get_ssl_server_trust_file_provider(),
+ SVN::Client::get_username_provider()]);
+ my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
+ return $s;
+}
+
+sub libsvn_get_file {
+ my ($gui, $f, $rev) = @_;
+ my $p = $f;
+ return unless ($p =~ s#^\Q$SVN_PATH\E/##);
+
+ my ($hash, $pid, $in, $out);
+ my $pool = SVN::Pool->new;
+ defined($pid = open3($in, $out, '>&STDERR',
+ qw/git-hash-object -w --stdin/)) or croak $!;
+ # redirect STDOUT for SVN 1.1.x compatibility
+ open my $stdout, '>&', \*STDOUT or croak $!;
+ open STDOUT, '>&', $in or croak $!;
+ my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool);
+ $in->flush == 0 or croak $!;
+ open STDOUT, '>&', $stdout or croak $!;
+ close $in or croak $!;
+ close $stdout or croak $!;
+ $pool->clear;
+ chomp($hash = do { local $/; <$out> });
+ close $out or croak $!;
+ waitpid $pid, 0;
+ $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
+
+ my $mode = exists $props->{'svn:executable'} ? '100755' : '100644';
+ if (exists $props->{'svn:special'}) {
+ $mode = '120000';
+ my $link = `git-cat-file blob $hash`;
+ $link =~ s/^link // or die "svn:special file with contents: <",
+ $link, "> is not understood\n";
+ defined($pid = open3($in, $out, '>&STDERR',
+ qw/git-hash-object -w --stdin/)) or croak $!;
+ print $in $link;
+ $in->flush == 0 or croak $!;
+ close $in or croak $!;
+ chomp($hash = do { local $/; <$out> });
+ close $out or croak $!;
+ waitpid $pid, 0;
+ $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
+ }
+ print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!;
+}
+
+sub libsvn_log_entry {
+ my ($rev, $author, $date, $msg, $parents) = @_;
+ my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
+ (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x)
+ or die "Unable to parse date: $date\n";
+ if (defined $_authors && ! defined $users{$author}) {
+ die "Author: $author not defined in $_authors file\n";
+ }
+ return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
+ author => $author, msg => $msg."\n", parents => $parents || [] }
+}
+
+sub process_rm {
+ my ($gui, $last_commit, $f) = @_;
+ $f =~ s#^\Q$SVN_PATH\E/?## or return;
+ # remove entire directories.
+ if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
+ defined(my $pid = open my $ls, '-|') or croak $!;
+ if (!$pid) {
+ exec(qw/git-ls-tree -r --name-only -z/,
+ $last_commit,'--',$f) or croak $!;
+ }
+ local $/ = "\0";
+ while (<$ls>) {
+ print $gui '0 ',0 x 40,"\t",$_ or croak $!;
+ }
+ close $ls or croak $?;
+ } else {
+ print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
+ }
+}
+
+sub libsvn_fetch {
+ my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
+ open my $gui, '| git-update-index -z --index-info' or croak $!;
+ my @amr;
+ foreach my $f (keys %$paths) {
+ my $m = $paths->{$f}->action();
+ $f =~ s#^/+##;
+ if ($m =~ /^[DR]$/) {
+ print "\t$m\t$f\n" unless $_q;
+ process_rm($gui, $last_commit, $f);
+ next if $m eq 'D';
+ # 'R' can be file replacements, too, right?
+ }
+ my $pool = SVN::Pool->new;
+ my $t = $SVN->check_path($f, $rev, $pool);
+ if ($t == $SVN::Node::file) {
+ if ($m =~ /^[AMR]$/) {
+ push @amr, [ $m, $f ];
+ } else {
+ die "Unrecognized action: $m, ($f r$rev)\n";
+ }
+ } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) {
+ my @traversed = ();
+ libsvn_traverse($gui, '', $f, $rev, \@traversed);
+ foreach (@traversed) {
+ push @amr, [ $m, $_ ]
+ }
+ }
+ $pool->clear;
+ }
+ foreach (@amr) {
+ print "\t$_->[0]\t$_->[1]\n" unless $_q;
+ libsvn_get_file($gui, $_->[1], $rev)
+ }
+ close $gui or croak $?;
+ return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
+}
+
+sub svn_grab_base_rev {
+ defined(my $pid = open my $fh, '-|') or croak $!;
+ if (!$pid) {
+ open my $null, '>', '/dev/null' or croak $!;
+ open STDERR, '>&', $null or croak $!;
+ exec qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0"
+ or croak $!;
+ }
+ chomp(my $c = do { local $/; <$fh> });
+ close $fh;
+ if (defined $c && length $c) {
+ my ($url, $rev, $uuid) = cmt_metadata($c);
+ return ($rev, $c) if defined $rev;
+ }
+ if ($_no_metadata) {
+ my $offset = -41; # from tail
+ my $rl;
+ open my $fh, '<', $REVDB or
+ die "--no-metadata specified and $REVDB not readable\n";
+ seek $fh, $offset, 2;
+ $rl = readline $fh;
+ defined $rl or return (undef, undef);
+ chomp $rl;
+ while ($c ne $rl && tell $fh != 0) {
+ $offset -= 41;
+ seek $fh, $offset, 2;
+ $rl = readline $fh;
+ defined $rl or return (undef, undef);
+ chomp $rl;
+ }
+ my $rev = tell $fh;
+ croak $! if ($rev < -1);
+ $rev = ($rev - 41) / 41;
+ close $fh or croak $!;
+ return ($rev, $c);
+ }
+ return (undef, undef);
+}
+
+sub libsvn_parse_revision {
+ my $base = shift;
+ my $head = $SVN->get_latest_revnum();
+ if (!defined $_revision || $_revision eq 'BASE:HEAD') {
+ return ($base + 1, $head) if (defined $base);
+ return (0, $head);
+ }
+ return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
+ return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
+ if ($_revision =~ /^BASE:(\d+)$/) {
+ return ($base + 1, $1) if (defined $base);
+ return (0, $head);
+ }
+ return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/);
+ die "revision argument: $_revision not understood by git-svn\n",
+ "Try using the command-line svn client instead\n";
+}
+
+sub libsvn_traverse {
+ my ($gui, $pfx, $path, $rev, $files) = @_;
+ my $cwd = "$pfx/$path";
+ my $pool = SVN::Pool->new;
+ $cwd =~ s#^/+##g;
+ my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);
+ foreach my $d (keys %$dirent) {
+ my $t = $dirent->{$d}->kind;
+ if ($t == $SVN::Node::dir) {
+ libsvn_traverse($gui, $cwd, $d, $rev, $files);
+ } elsif ($t == $SVN::Node::file) {
+ my $file = "$cwd/$d";
+ if (defined $files) {
+ push @$files, $file;
+ } else {
+ print "\tA\t$file\n" unless $_q;
+ libsvn_get_file($gui, $file, $rev);
+ }
+ }
+ }
+ $pool->clear;
+}
+
+sub libsvn_traverse_ignore {
+ my ($fh, $path, $r) = @_;
+ $path =~ s#^/+##g;
+ my $pool = SVN::Pool->new;
+ my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
+ my $p = $path;
+ $p =~ s#^\Q$SVN_PATH\E/?##;
+ print $fh length $p ? "\n# $p\n" : "\n# /\n";
+ if (my $s = $props->{'svn:ignore'}) {
+ $s =~ s/[\r\n]+/\n/g;
+ chomp $s;
+ if (length $p == 0) {
+ $s =~ s#\n#\n/$p#g;
+ print $fh "/$s\n";
+ } else {
+ $s =~ s#\n#\n/$p/#g;
+ print $fh "/$p/$s\n";
+ }
+ }
+ foreach (sort keys %$dirent) {
+ next if $dirent->{$_}->kind != $SVN::Node::dir;
+ libsvn_traverse_ignore($fh, "$path/$_", $r);
+ }
+ $pool->clear;
+}
+
+sub revisions_eq {
+ my ($path, $r0, $r1) = @_;
+ return 1 if $r0 == $r1;
+ my $nr = 0;
+ if ($_use_lib) {
+ # should be OK to use Pool here (r1 - r0) should be small
+ my $pool = SVN::Pool->new;
+ libsvn_get_log($SVN, "/$path", $r0, $r1,
+ 0, 1, 1, sub {$nr++}, $pool);
+ $pool->clear;
+ } else {
+ my ($url, undef) = repo_path_split($SVN_URL);
+ my $svn_log = svn_log_raw("$url/$path","-r$r0:$r1");
+ while (next_log_entry($svn_log)) { $nr++ }
+ close $svn_log->{fh};
+ }
+ return 0 if ($nr > 1);
+ return 1;
+}
+
+sub libsvn_find_parent_branch {
+ my ($paths, $rev, $author, $date, $msg) = @_;
+ my $svn_path = '/'.$SVN_PATH;
+
+ # look for a parent from another branch:
+ my $i = $paths->{$svn_path} or return;
+ my $branch_from = $i->copyfrom_path or return;
+ my $r = $i->copyfrom_rev;
+ print STDERR "Found possible branch point: ",
+ "$branch_from => $svn_path, $r\n";
+ $branch_from =~ s#^/##;
+ my $l_map = {};
+ read_url_paths_all($l_map, '', "$GIT_DIR/svn");
+ my $url = $SVN->{url};
+ defined $l_map->{$url} or return;
+ my $id = $l_map->{$url}->{$branch_from};
+ if (!defined $id && $_follow_parent) {
+ print STDERR "Following parent: $branch_from\@$r\n";
+ # auto create a new branch and follow it
+ $id = basename($branch_from);
+ $id .= '@'.$r if -r "$GIT_DIR/svn/$id";
+ while (-r "$GIT_DIR/svn/$id") {
+ # just grow a tail if we're not unique enough :x
+ $id .= '-';
+ }
+ }
+ return unless defined $id;
+
+ my ($r0, $parent) = find_rev_before($r,$id,1);
+ if ($_follow_parent && (!defined $r0 || !defined $parent)) {
+ defined(my $pid = fork) or croak $!;
+ if (!$pid) {
+ $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+ init_vars();
+ $SVN_URL = "$url/$branch_from";
+ $SVN_LOG = $SVN = undef;
+ setup_git_svn();
+ # we can't assume SVN_URL exists at r+1:
+ $_revision = "0:$r";
+ fetch_lib();
+ exit 0;
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+ ($r0, $parent) = find_rev_before($r,$id,1);
+ }
+ return unless (defined $r0 && defined $parent);
+ if (revisions_eq($branch_from, $r0, $r)) {
+ unlink $GIT_SVN_INDEX;
+ print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
+ sys(qw/git-read-tree/, $parent);
+ return libsvn_fetch($parent, $paths, $rev,
+ $author, $date, $msg);
+ }
+ print STDERR "Nope, branch point not imported or unknown\n";
+ return undef;
+}
+
+sub libsvn_get_log {
+ my ($ra, @args) = @_;
+ if ($SVN::Core::VERSION le '1.2.0') {
+ splice(@args, 3, 1);
+ }
+ $ra->get_log(@args);
+}
+
+sub libsvn_new_tree {
+ if (my $log_entry = libsvn_find_parent_branch(@_)) {
+ return $log_entry;
+ }
+ my ($paths, $rev, $author, $date, $msg) = @_;
+ open my $gui, '| git-update-index -z --index-info' or croak $!;
+ libsvn_traverse($gui, '', $SVN_PATH, $rev);
+ close $gui or croak $?;
+ return libsvn_log_entry($rev, $author, $date, $msg);
+}
+
+sub find_graft_path_commit {
+ my ($tree_paths, $p1, $r1) = @_;
+ foreach my $x (keys %$tree_paths) {
+ next unless ($p1 =~ /^\Q$x\E/);
+ my $i = $tree_paths->{$x};
+ my ($r0, $parent) = find_rev_before($r1,$i,1);
+ return $parent if (defined $r0 && $r0 == $r1);
+ print STDERR "r$r1 of $i not imported\n";
+ next;
+ }
+ return undef;
+}
+
+sub find_graft_path_parents {
+ my ($grafts, $tree_paths, $c, $p0, $r0) = @_;
+ foreach my $x (keys %$tree_paths) {
+ next unless ($p0 =~ /^\Q$x\E/);
+ my $i = $tree_paths->{$x};
+ my ($r, $parent) = find_rev_before($r0, $i, 1);
+ if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {
+ my ($url_b, undef, $uuid_b) = cmt_metadata($c);
+ my ($url_a, undef, $uuid_a) = cmt_metadata($parent);
+ next if ($url_a && $url_b && $url_a eq $url_b &&
+ $uuid_b eq $uuid_a);
+ $grafts->{$c}->{$parent} = 1;
+ }
+ }
+}
+
+sub libsvn_graft_file_copies {
+ my ($grafts, $tree_paths, $path, $paths, $rev) = @_;
+ foreach (keys %$paths) {
+ my $i = $paths->{$_};
+ my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path,
+ $i->copyfrom_rev);
+ next unless (defined $p0 && defined $r0);
+
+ my $p1 = $_;
+ $p1 =~ s#^/##;
+ $p0 =~ s#^/##;
+ my $c = find_graft_path_commit($tree_paths, $p1, $rev);
+ next unless $c;
+ find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0);
+ }
+}
+
+sub set_index {
+ my $old = $ENV{GIT_INDEX_FILE};
+ $ENV{GIT_INDEX_FILE} = shift;
+ return $old;
+}
+
+sub restore_index {
+ my ($old) = @_;
+ if (defined $old) {
+ $ENV{GIT_INDEX_FILE} = $old;
+ } else {
+ delete $ENV{GIT_INDEX_FILE};
+ }
+}
+
+sub libsvn_commit_cb {
+ my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_;
+ if ($_optimize_commits && $rev == ($r_last + 1)) {
+ my $log = libsvn_log_entry($rev,$committer,$date,$msg);
+ $log->{tree} = get_tree_from_treeish($c);
+ my $cmt = git_commit($log, $cmt_last, $c);
+ my @diff = safe_qx('git-diff-tree', $cmt, $c);
+ if (@diff) {
+ print STDERR "Trees differ: $cmt $c\n",
+ join('',@diff),"\n";
+ exit 1;
+ }
+ } else {
+ fetch("$rev=$c");
+ }
+}
+
+sub libsvn_ls_fullurl {
+ my $fullurl = shift;
+ my ($repo, $path) = repo_path_split($fullurl);
+ $SVN ||= libsvn_connect($repo);
+ my @ret;
+ my $pool = SVN::Pool->new;
+ my ($dirent, undef, undef) = $SVN->get_dir($path,
+ $SVN->get_latest_revnum, $pool);
+ foreach my $d (keys %$dirent) {
+ if ($dirent->{$d}->kind == $SVN::Node::dir) {
+ push @ret, "$d/"; # add '/' for compat with cli svn
+ }
+ }
+ $pool->clear;
+ return @ret;
+}
+
+
+sub libsvn_skip_unknown_revs {
+ my $err = shift;
+ my $errno = $err->apr_err();
+ # Maybe the branch we're tracking didn't
+ # exist when the repo started, so it's
+ # not an error if it doesn't, just continue
+ #
+ # Wonderfully consistent library, eh?
+ # 160013 - svn:// and file://
+ # 175002 - http(s)://
+ # More codes may be discovered later...
+ if ($errno == 175002 || $errno == 160013) {
+ return;
+ }
+ croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
+};
+
+# Tie::File seems to be prone to offset errors if revisions get sparse,
+# it's not that fast, either. Tie::File is also not in Perl 5.6. So
+# one of my favorite modules is out :< Next up would be one of the DBM
+# modules, but I'm not sure which is most portable... So I'll just
+# go with something that's plain-text, but still capable of
+# being randomly accessed. So here's my ultra-simple fixed-width
+# database. All records are 40 characters + "\n", so it's easy to seek
+# to a revision: (41 * rev) is the byte offset.
+# A record of 40 0s denotes an empty revision.
+# And yes, it's still pretty fast (faster than Tie::File).
+sub revdb_set {
+ my ($file, $rev, $commit) = @_;
+ length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
+ open my $fh, '+<', $file or croak $!;
+ my $offset = $rev * 41;
+ # assume that append is the common case:
+ seek $fh, 0, 2 or croak $!;
+ my $pos = tell $fh;
+ if ($pos < $offset) {
+ print $fh (('0' x 40),"\n") x (($offset - $pos) / 41);
+ }
+ seek $fh, $offset, 0 or croak $!;
+ print $fh $commit,"\n";
+ close $fh or croak $!;
+}
+
+sub revdb_get {
+ my ($file, $rev) = @_;
+ my $ret;
+ my $offset = $rev * 41;
+ open my $fh, '<', $file or croak $!;
+ seek $fh, $offset, 0;
+ if (tell $fh == $offset) {
+ $ret = readline $fh;
+ if (defined $ret) {
+ chomp $ret;
+ $ret = undef if ($ret =~ /^0{40}$/);
+ }
+ }
+ close $fh or croak $!;
+ return $ret;
+}
+
+sub copy_remote_ref {
+ my $origin = $_cp_remote ? $_cp_remote : 'origin';
+ my $ref = "refs/remotes/$GIT_SVN";
+ if (safe_qx('git-ls-remote', $origin, $ref)) {
+ sys(qw/git fetch/, $origin, "$ref:$ref");
+ } else {
+ die "Unable to find remote reference: ",
+ "refs/remotes/$GIT_SVN on $origin\n";
+ }
+}
+
+package SVN::Git::Editor;
+use vars qw/@ISA/;
+use strict;
+use warnings;
+use Carp qw/croak/;
+use IO::File;
+
+sub new {
+ my $class = shift;
+ my $git_svn = shift;
+ my $self = SVN::Delta::Editor->new(@_);
+ bless $self, $class;
+ foreach (qw/svn_path c r ra /) {
+ die "$_ required!\n" unless (defined $git_svn->{$_});
+ $self->{$_} = $git_svn->{$_};
+ }
+ $self->{pool} = SVN::Pool->new;
+ $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };
+ $self->{rm} = { };
+ require Digest::MD5;
+ return $self;
+}
+
+sub split_path {
+ return ($_[0] =~ m#^(.*?)/?([^/]+)$#);
+}
+
+sub repo_path {
+ (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]"
+ : $_[0]->{svn_path}
+}
+
+sub url_path {
+ my ($self, $path) = @_;
+ $self->{ra}->{url} . '/' . $self->repo_path($path);
+}
+
+sub rmdirs {
+ my ($self, $q) = @_;
+ my $rm = $self->{rm};
+ delete $rm->{''}; # we never delete the url we're tracking
+ return unless %$rm;
+
+ foreach (keys %$rm) {
+ my @d = split m#/#, $_;
+ my $c = shift @d;
+ $rm->{$c} = 1;
+ while (@d) {
+ $c .= '/' . shift @d;
+ $rm->{$c} = 1;
+ }
+ }
+ delete $rm->{$self->{svn_path}};
+ delete $rm->{''}; # we never delete the url we're tracking
+ return unless %$rm;
+
+ defined(my $pid = open my $fh,'-|') or croak $!;
+ if (!$pid) {
+ exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;
+ }
+ local $/ = "\0";
+ my @svn_path = split m#/#, $self->{svn_path};
+ while (<$fh>) {
+ chomp;
+ my @dn = (@svn_path, (split m#/#, $_));
+ while (pop @dn) {
+ delete $rm->{join '/', @dn};
+ }
+ unless (%$rm) {
+ close $fh;
+ return;
+ }
+ }
+ close $fh;
+
+ my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
+ foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
+ $self->close_directory($bat->{$d}, $p);
+ my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
+ print "\tD+\t/$d/\n" unless $q;
+ $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
+ delete $bat->{$d};
+ }
+}
+
+sub open_or_add_dir {
+ my ($self, $full_path, $baton) = @_;
+ my $p = SVN::Pool->new;
+ my $t = $self->{ra}->check_path($full_path, $self->{r}, $p);
+ $p->clear;
+ if ($t == $SVN::Node::none) {
+ return $self->add_directory($full_path, $baton,
+ undef, -1, $self->{pool});
+ } elsif ($t == $SVN::Node::dir) {
+ return $self->open_directory($full_path, $baton,
+ $self->{r}, $self->{pool});
+ }
+ print STDERR "$full_path already exists in repository at ",
+ "r$self->{r} and it is not a directory (",
+ ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
+ exit 1;
+}
+
+sub ensure_path {
+ my ($self, $path) = @_;
+ my $bat = $self->{bat};
+ $path = $self->repo_path($path);
+ return $bat->{''} unless (length $path);
+ my @p = split m#/+#, $path;
+ my $c = shift @p;
+ $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
+ while (@p) {
+ my $c0 = $c;
+ $c .= '/' . shift @p;
+ $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0});
+ }
+ return $bat->{$c};
+}
+
+sub A {
+ my ($self, $m, $q) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir);
+ my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+ undef, -1);
+ print "\tA\t$m->{file_b}\n" unless $q;
+ $self->chg_file($fbat, $m);
+ $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub C {
+ my ($self, $m, $q) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir);
+ my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+ $self->url_path($m->{file_a}), $self->{r});
+ print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q;
+ $self->chg_file($fbat, $m);
+ $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub delete_entry {
+ my ($self, $path, $pbat) = @_;
+ my $rpath = $self->repo_path($path);
+ my ($dir, $file) = split_path($rpath);
+ $self->{rm}->{$dir} = 1;
+ $self->SUPER::delete_entry($rpath, $self->{r}, $pbat, $self->{pool});
+}
+
+sub R {
+ my ($self, $m, $q) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir);
+ my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
+ $self->url_path($m->{file_a}), $self->{r});
+ print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q;
+ $self->chg_file($fbat, $m);
+ $self->close_file($fbat,undef,$self->{pool});
+
+ ($dir, $file) = split_path($m->{file_a});
+ $pbat = $self->ensure_path($dir);
+ $self->delete_entry($m->{file_a}, $pbat);
+}
+
+sub M {
+ my ($self, $m, $q) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir);
+ my $fbat = $self->open_file($self->repo_path($m->{file_b}),
+ $pbat,$self->{r},$self->{pool});
+ print "\t$m->{chg}\t$m->{file_b}\n" unless $q;
+ $self->chg_file($fbat, $m);
+ $self->close_file($fbat,undef,$self->{pool});
+}
+
+sub T { shift->M(@_) }
+
+sub change_file_prop {
+ my ($self, $fbat, $pname, $pval) = @_;
+ $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
+}
+
+sub chg_file {
+ my ($self, $fbat, $m) = @_;
+ if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
+ $self->change_file_prop($fbat,'svn:executable','*');
+ } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
+ $self->change_file_prop($fbat,'svn:executable',undef);
+ }
+ my $fh = IO::File->new_tmpfile or croak $!;
+ if ($m->{mode_b} =~ /^120/) {
+ print $fh 'link ' or croak $!;
+ $self->change_file_prop($fbat,'svn:special','*');
+ } elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
+ $self->change_file_prop($fbat,'svn:special',undef);
+ }
+ defined(my $pid = fork) or croak $!;
+ if (!$pid) {
+ open STDOUT, '>&', $fh or croak $!;
+ exec qw/git-cat-file blob/, $m->{sha1_b} or croak $!;
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+ $fh->flush == 0 or croak $!;
+ seek $fh, 0, 0 or croak $!;
+
+ my $md5 = Digest::MD5->new;
+ $md5->addfile($fh) or croak $!;
+ seek $fh, 0, 0 or croak $!;
+
+ my $exp = $md5->hexdigest;
+ my $atd = $self->apply_textdelta($fbat, undef, $self->{pool});
+ my $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool});
+ die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
+
+ close $fh or croak $!;
+}
+
+sub D {
+ my ($self, $m, $q) = @_;
+ my ($dir, $file) = split_path($m->{file_b});
+ my $pbat = $self->ensure_path($dir);
+ print "\tD\t$m->{file_b}\n" unless $q;
+ $self->delete_entry($m->{file_b}, $pbat);
+}
+
+sub close_edit {
+ my ($self) = @_;
+ my ($p,$bat) = ($self->{pool}, $self->{bat});
+ foreach (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$bat) {
+ $self->close_directory($bat->{$_}, $p);
+ }
+ $self->SUPER::close_edit($p);
+ $p->clear;
+}
+
+sub abort_edit {
+ my ($self) = @_;
+ $self->SUPER::abort_edit($self->{pool});
+ $self->{pool}->clear;
+}
+
+__END__
+
+Data structures:
+
+$svn_log hashref (as returned by svn_log_raw)
+{
+ fh => file handle of the log file,
+ state => state of the log file parser (sep/msg/rev/msg_start...)
+}
+
+$log_msg hashref as returned by next_log_entry($svn_log)
+{
+ msg => 'whitespace-formatted log entry
+', # trailing newline is preserved
+ revision => '8', # integer
+ date => '2004-02-24T17:01:44.108345Z', # commit date
+ author => 'committer name'
+};
+
+
+@mods = array of diff-index line hashes, each element represents one line
+ of diff-index output
+
+diff-index line ($m hash)
+{
+ mode_a => first column of diff-index output, no leading ':',
+ mode_b => second column of diff-index output,
+ sha1_b => sha1sum of the final blob,
+ chg => change type [MCRADT],
+ file_a => original file name of a file (iff chg is 'C' or 'R')
+ file_b => new/current file name of a file (any chg)
+}
+;
+
+# retval of read_url_paths{,_all}();
+$l_map = {
+ # repository root url
+ 'https://svn.musicpd.org' => {
+ # repository path # GIT_SVN_ID
+ 'mpd/trunk' => 'trunk',
+ 'mpd/tags/0.11.5' => 'tags/0.11.5',
+ },
+}
+
+Notes:
+ I don't trust the each() function on unless I created %hash myself
+ because the internal iterator may not have started at base.
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..ee5a0e86a71119b5ac11c351a982e31c87649a15 100644 (file)
--- a/git.c
+++ b/git.c
#include "git-compat-util.h"
#include "exec_cmd.h"
#include "cache.h"
+#include "quote.h"
#include "builtin.h"
if (!strcmp(alias_command, new_argv[0]))
die("recursive alias: %s", alias_command);
- /* insert after command name */
- if (*argcp > 1) {
- new_argv = realloc(new_argv, sizeof(char*) *
- (count + *argcp));
- memcpy(new_argv + count, *argv + 1,
- sizeof(char*) * *argcp);
+ if (getenv("GIT_TRACE")) {
+ int i;
+ fprintf(stderr, "trace: alias expansion: %s =>",
+ alias_command);
+ for (i = 0; i < count; ++i) {
+ fputc(' ', stderr);
+ sq_quote_print(stderr, new_argv[i]);
+ }
+ fputc('\n', stderr);
+ fflush(stderr);
}
+ new_argv = realloc(new_argv, sizeof(char*) *
+ (count + *argcp + 1));
+ /* insert after command name */
+ memcpy(new_argv + count, *argv + 1,
+ sizeof(char*) * *argcp);
+ new_argv[count+*argcp] = NULL;
+
*argv = new_argv;
*argcp += count - 1;
{ "mailinfo", cmd_mailinfo },
{ "stripspace", cmd_stripspace },
{ "update-index", cmd_update_index },
- { "update-ref", cmd_update_ref }
+ { "update-ref", cmd_update_ref },
+ { "fmt-merge-msg", cmd_fmt_merge_msg },
+ { "prune", cmd_prune },
};
int i;
struct cmd_struct *p = commands+i;
if (strcmp(p->cmd, cmd))
continue;
+
+ if (getenv("GIT_TRACE")) {
+ int i;
+ fprintf(stderr, "trace: built-in: git");
+ for (i = 0; i < argc; ++i) {
+ fputc(' ', stderr);
+ sq_quote_print(stderr, argv[i]);
+ }
+ putc('\n', stderr);
+ fflush(stderr);
+ }
+
exit(p->fn(argc, argv, envp));
}
}
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..30cb624b2a38bc5b90915f0061ff3127ac0ee34d 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;
if (-d $projects_list) {
# search in directory
my $dir = $projects_list;
- opendir my $dh, $dir or return undef;
+ opendir my ($dh), $dir or return undef;
while (my $dir = readdir($dh)) {
if (-e "$projectroot/$dir/HEAD") {
my $pr = {
# 'git%2Fgit.git Linus+Torvalds'
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
- open my $fd , $projects_list or return undef;
+ open my ($fd), $projects_list or return undef;
while (my $line = <$fd>) {
chomp $line;
my ($path, $owner) = split ' ', $line;
$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" .
"</td>\n" .
"<td>";
if (defined($comment)) {
- print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment);
+ print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment));
}
print "</td>\n" .
"<td class=\"link\">";
$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/gitweb/gitweb.css b/gitweb/gitweb.css
index 98410f5b6c971efcc59ac69514936b02fbe68d17..fffdb13d09a40397745861bb6ec5dcc79c798b0d 100644 (file)
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
div.page_body {
padding: 8px;
+ font-family: monospace;
}
div.title, a.title {
padding: 6px 0px;
border: solid #d9d8d1;
border-width: 0px 0px 1px;
+ font-family: monospace;
}
div.log_body {
padding: 8px 4px;
}
-table.project_list, table.diff_tree {
+table.project_list {
+ border-spacing: 0;
+}
+
+table.diff_tree {
border-spacing: 0;
+ font-family: monospace;
}
table.blame {
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..47686195cdf41ad58fe66a606033d3156de8fd82 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);
if (obj->flags & (UNINTERESTING | SEEN))
continue;
- if (obj->type == TYPE_TAG) {
+ if (obj->type == OBJ_TAG) {
obj->flags |= SEEN;
p = add_one_object(obj, p);
continue;
}
- if (obj->type == TYPE_TREE) {
+ if (obj->type == OBJ_TREE) {
p = process_tree((struct tree *)obj, p, NULL, name);
continue;
}
- if (obj->type == TYPE_BLOB) {
+ if (obj->type == OBJ_BLOB) {
p = process_blob((struct blob *)obj, p, NULL, name);
continue;
}
* old. Otherwise we require --force.
*/
o = deref_tag(parse_object(old_sha1), NULL, 0);
- if (!o || o->type != TYPE_COMMIT)
+ if (!o || o->type != OBJ_COMMIT)
return 0;
old = (struct commit *) o;
o = deref_tag(parse_object(new_sha1), NULL, 0);
- if (!o || o->type != TYPE_COMMIT)
+ if (!o || o->type != OBJ_COMMIT)
return 0;
new = (struct commit *) o;
fwrite_buffer(ref_info, 1, len, buf);
free(ref_info);
- if (o->type == TYPE_TAG) {
+ if (o->type == OBJ_TAG) {
o = deref_tag(o, ls->dentry_name, 0);
if (o) {
len = strlen(ls->dentry_name) + 45;
diff --git a/log-tree.c b/log-tree.c
index ebb49f29701b2435d66eb92fc2a5c382299e8ee8..b67b8dd17af9643e00152d9bd768dca89e08b4ce 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;
subject = "Subject: ";
printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
+ if (opt->message_id)
+ printf("Message-Id: <%s>\n", opt->message_id);
+ if (opt->ref_message_id)
+ printf("In-Reply-To: <%s>\nReferences: <%s>\n",
+ opt->ref_message_id, opt->ref_message_id);
if (opt->mime_boundary) {
static char subject_buffer[1024];
static char buffer[1024];
opt->diffopt.stat_sep = buffer;
}
} else {
- printf("%s%s",
+ printf("%s%s%s",
+ diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
diff_unique_abbrev(commit->object.sha1, abbrev_commit));
if (opt->parents)
printf(" (from %s)",
diff_unique_abbrev(parent->object.sha1,
abbrev_commit));
+ printf("%s",
+ diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
}
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;
}
diff --git a/merge-file.c b/merge-file.c
--- /dev/null
+++ b/merge-file.c
@@ -0,0 +1,166 @@
+#include "cache.h"
+#include "run-command.h"
+#include "xdiff-interface.h"
+#include "blob.h"
+
+static void rm_temp_file(const char *filename)
+{
+ unlink(filename);
+ free((void *)filename);
+}
+
+static const char *write_temp_file(mmfile_t *f)
+{
+ int fd;
+ const char *tmp = getenv("TMPDIR");
+ char *filename;
+
+ if (!tmp)
+ tmp = "/tmp";
+ filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX");
+ fd = mkstemp(filename);
+ if (fd < 0)
+ return NULL;
+ filename = strdup(filename);
+ if (f->size != xwrite(fd, f->ptr, f->size)) {
+ rm_temp_file(filename);
+ return NULL;
+ }
+ close(fd);
+ return filename;
+}
+
+static void *read_temp_file(const char *filename, unsigned long *size)
+{
+ struct stat st;
+ char *buf = NULL;
+ int fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+ if (!fstat(fd, &st)) {
+ *size = st.st_size;
+ buf = xmalloc(st.st_size);
+ if (st.st_size != xread(fd, buf, st.st_size)) {
+ free(buf);
+ buf = NULL;
+ }
+ }
+ close(fd);
+ return buf;
+}
+
+static int fill_mmfile_blob(mmfile_t *f, struct blob *obj)
+{
+ void *buf;
+ unsigned long size;
+ char type[20];
+
+ buf = read_sha1_file(obj->object.sha1, type, &size);
+ if (!buf)
+ return -1;
+ if (strcmp(type, blob_type))
+ return -1;
+ f->ptr = buf;
+ f->size = size;
+ return 0;
+}
+
+static void free_mmfile(mmfile_t *f)
+{
+ free(f->ptr);
+}
+
+static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
+{
+ void *res;
+ const char *t1, *t2, *t3;
+
+ t1 = write_temp_file(base);
+ t2 = write_temp_file(our);
+ t3 = write_temp_file(their);
+ res = NULL;
+ if (t1 && t2 && t3) {
+ int code = run_command("merge", t2, t1, t3, NULL);
+ if (!code || code == -1)
+ res = read_temp_file(t2, size);
+ }
+ rm_temp_file(t1);
+ rm_temp_file(t2);
+ rm_temp_file(t3);
+ return res;
+}
+
+static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+ int i;
+ mmfile_t *dst = priv_;
+
+ for (i = 0; i < nbuf; i++) {
+ memcpy(dst->ptr + dst->size, mb[i].ptr, mb[i].size);
+ dst->size += mb[i].size;
+ }
+ return 0;
+}
+
+static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
+{
+ unsigned long size = f1->size < f2->size ? f1->size : f2->size;
+ void *ptr = xmalloc(size);
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ xpp.flags = XDF_NEED_MINIMAL;
+ xecfg.ctxlen = 3;
+ xecfg.flags = XDL_EMIT_COMMON;
+ ecb.outf = common_outf;
+
+ res->ptr = ptr;
+ res->size = 0;
+
+ ecb.priv = res;
+ return xdl_diff(f1, f2, &xpp, &xecfg, &ecb);
+}
+
+void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
+{
+ void *res = NULL;
+ mmfile_t f1, f2, common;
+
+ /*
+ * Removed in either branch?
+ *
+ * NOTE! This depends on the caller having done the
+ * proper warning about removing a file that got
+ * modified in the other branch!
+ */
+ if (!our || !their) {
+ char type[20];
+ if (base)
+ return NULL;
+ if (!our)
+ our = their;
+ return read_sha1_file(our->object.sha1, type, size);
+ }
+
+ if (fill_mmfile_blob(&f1, our) < 0)
+ goto out_no_mmfile;
+ if (fill_mmfile_blob(&f2, their) < 0)
+ goto out_free_f1;
+
+ if (base) {
+ if (fill_mmfile_blob(&common, base) < 0)
+ goto out_free_f2_f1;
+ } else {
+ if (generate_common_file(&common, &f1, &f2) < 0)
+ goto out_free_f2_f1;
+ }
+ res = three_way_filemerge(&common, &f1, &f2, size);
+ free_mmfile(&common);
+out_free_f2_f1:
+ free_mmfile(&f2);
+out_free_f1:
+ free_mmfile(&f1);
+out_no_mmfile:
+ return res;
+}
diff --git a/merge-tree.c b/merge-tree.c
index 9dcaab7a85fdb63b2140923a95b216917bfc0993..7cf00be6d51b17ca60979a9c856e67405906d9a7 100644 (file)
--- a/merge-tree.c
+++ b/merge-tree.c
#include "cache.h"
#include "tree-walk.h"
+#include "xdiff-interface.h"
+#include "blob.h"
static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
static int resolve_directories = 1;
+struct merge_list {
+ struct merge_list *next;
+ struct merge_list *link; /* other stages for this object */
+
+ unsigned int stage : 2,
+ flags : 30;
+ unsigned int mode;
+ const char *path;
+ struct blob *blob;
+};
+
+static struct merge_list *merge_result, **merge_result_end = &merge_result;
+
+static void add_merge_entry(struct merge_list *entry)
+{
+ *merge_result_end = entry;
+ merge_result_end = &entry->next;
+}
+
static void merge_trees(struct tree_desc t[3], const char *base);
+static const char *explanation(struct merge_list *entry)
+{
+ switch (entry->stage) {
+ case 0:
+ return "merged";
+ case 3:
+ return "added in remote";
+ case 2:
+ if (entry->link)
+ return "added in both";
+ return "added in local";
+ }
+
+ /* Existed in base */
+ entry = entry->link;
+ if (!entry)
+ return "removed in both";
+
+ if (entry->link)
+ return "changed in both";
+
+ if (entry->stage == 3)
+ return "removed in local";
+ return "removed in remote";
+}
+
+extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *);
+
+static void *result(struct merge_list *entry, unsigned long *size)
+{
+ char type[20];
+ struct blob *base, *our, *their;
+
+ if (!entry->stage)
+ return read_sha1_file(entry->blob->object.sha1, type, size);
+ base = NULL;
+ if (entry->stage == 1) {
+ base = entry->blob;
+ entry = entry->link;
+ }
+ our = NULL;
+ if (entry && entry->stage == 2) {
+ our = entry->blob;
+ entry = entry->link;
+ }
+ their = NULL;
+ if (entry)
+ their = entry->blob;
+ return merge_file(base, our, their, size);
+}
+
+static void *origin(struct merge_list *entry, unsigned long *size)
+{
+ char type[20];
+ while (entry) {
+ if (entry->stage == 2)
+ return read_sha1_file(entry->blob->object.sha1, type, size);
+ entry = entry->link;
+ }
+ return NULL;
+}
+
+static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+ int i;
+ for (i = 0; i < nbuf; i++)
+ printf("%.*s", (int) mb[i].size, mb[i].ptr);
+ return 0;
+}
+
+static void show_diff(struct merge_list *entry)
+{
+ unsigned long size;
+ mmfile_t src, dst;
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ xpp.flags = XDF_NEED_MINIMAL;
+ xecfg.ctxlen = 3;
+ xecfg.flags = 0;
+ ecb.outf = show_outf;
+ ecb.priv = NULL;
+
+ src.ptr = origin(entry, &size);
+ if (!src.ptr)
+ size = 0;
+ src.size = size;
+ dst.ptr = result(entry, &size);
+ if (!dst.ptr)
+ size = 0;
+ dst.size = size;
+ xdl_diff(&src, &dst, &xpp, &xecfg, &ecb);
+ free(src.ptr);
+ free(dst.ptr);
+}
+
+static void show_result_list(struct merge_list *entry)
+{
+ printf("%s\n", explanation(entry));
+ do {
+ struct merge_list *link = entry->link;
+ static const char *desc[4] = { "result", "base", "our", "their" };
+ printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
+ entry = link;
+ } while (entry);
+}
+
+static void show_result(void)
+{
+ struct merge_list *walk;
+
+ walk = merge_result;
+ while (walk) {
+ show_result_list(walk);
+ show_diff(walk);
+ walk = walk->next;
+ }
+}
+
/* An empty entry never compares same, not even to another empty entry */
static int same_entry(struct name_entry *a, struct name_entry *b)
{
a->mode == b->mode;
}
-static const char *sha1_to_hex_zero(const unsigned char *sha1)
+static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
{
- if (sha1)
- return sha1_to_hex(sha1);
- return "0000000000000000000000000000000000000000";
+ struct merge_list *res = xmalloc(sizeof(*res));
+
+ memset(res, 0, sizeof(*res));
+ res->stage = stage;
+ res->path = path;
+ res->mode = mode;
+ res->blob = lookup_blob(sha1);
+ return res;
}
static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
{
+ struct merge_list *orig, *final;
+ const char *path;
+
/* If it's already branch1, don't bother showing it */
if (!branch1)
return;
- printf("0 %06o->%06o %s->%s %s%s\n",
- branch1->mode, result->mode,
- sha1_to_hex_zero(branch1->sha1),
- sha1_to_hex_zero(result->sha1),
- base, result->path);
+ path = strdup(mkpath("%s%s", base, result->path));
+ orig = create_entry(2, branch1->mode, branch1->sha1, path);
+ final = create_entry(0, result->mode, result->sha1, path);
+
+ final->link = orig;
+
+ add_merge_entry(final);
}
static int unresolved_directory(const char *base, struct name_entry n[3])
return 1;
}
+
+static struct merge_list *link_entry(unsigned stage, const char *base, struct name_entry *n, struct merge_list *entry)
+{
+ const char *path;
+ struct merge_list *link;
+
+ if (!n->mode)
+ return entry;
+ if (entry)
+ path = entry->path;
+ else
+ path = strdup(mkpath("%s%s", base, n->path));
+ link = create_entry(stage, n->mode, n->sha1, path);
+ link->link = entry;
+ return link;
+}
+
static void unresolved(const char *base, struct name_entry n[3])
{
+ struct merge_list *entry = NULL;
+
if (unresolved_directory(base, n))
return;
- if (n[0].sha1)
- printf("1 %06o %s %s%s\n", n[0].mode, sha1_to_hex(n[0].sha1), base, n[0].path);
- if (n[1].sha1)
- printf("2 %06o %s %s%s\n", n[1].mode, sha1_to_hex(n[1].sha1), base, n[1].path);
- if (n[2].sha1)
- printf("3 %06o %s %s%s\n", n[2].mode, sha1_to_hex(n[2].sha1), base, n[2].path);
+
+ /*
+ * Do them in reverse order so that the resulting link
+ * list has the stages in order - link_entry adds new
+ * links at the front.
+ */
+ entry = link_entry(3, base, n + 2, entry);
+ entry = link_entry(2, base, n + 1, entry);
+ entry = link_entry(1, base, n + 0, entry);
+
+ add_merge_entry(entry);
}
/*
free(buf1);
free(buf2);
free(buf3);
+
+ show_result();
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 6a23f2d8a2d87c46ac9e1ec7bcb36e540967f93b..f92f14e32f647527e8bf49fced6c632be7298985 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;
if (tags_only && strncmp(path, "refs/tags/", 10))
return 0;
- while (o && o->type == TYPE_TAG) {
+ while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
if (!t->tagged)
break; /* broken repository */
o = parse_object(t->tagged->sha1);
deref = 1;
}
- if (o && o->type == TYPE_COMMIT) {
+ if (o && o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
if (!strncmp(path, "refs/heads/", 11))
struct rev_name *n;
struct commit *c;
- if (o->type != TYPE_COMMIT)
+ if (o->type != OBJ_COMMIT)
return "undefined";
c = (struct commit *) o;
n = c->util;
}
o = deref_tag(parse_object(sha1), *argv, 0);
- if (!o || o->type != TYPE_COMMIT) {
+ if (!o || o->type != OBJ_COMMIT) {
fprintf(stderr, "Could not get commit for %s. Skipping.\n",
*argv);
continue;
diff --git a/object.c b/object.c
index 31c77ea03a2083ce9ed7c71a2b4d90fb6b944c78..b5d8ed467d1c10c2f2dd596717159f7ee48a8007 100644 (file)
--- a/object.c
+++ b/object.c
#include "commit.h"
#include "tag.h"
-static struct object **objs;
-static int nr_objs, obj_allocs;
+static struct object **obj_hash;
+static int nr_objs, obj_hash_size;
unsigned int get_max_object_index(void)
{
- return obj_allocs;
+ return obj_hash_size;
}
struct object *get_indexed_object(unsigned int idx)
{
- return objs[idx];
+ return obj_hash[idx];
}
const char *type_names[] = {
- "none", "blob", "tree", "commit", "bad"
+ "none", "commit", "tree", "blob", "tag",
+ "bad type 5", "bad type 6", "delta", "bad",
};
+static unsigned int hash_obj(struct object *obj, unsigned int n)
+{
+ unsigned int hash = *(unsigned int *)obj->sha1;
+ return hash % n;
+}
+
+static void insert_obj_hash(struct object *obj, struct object **hash, unsigned int size)
+{
+ int j = hash_obj(obj, size);
+
+ while (hash[j]) {
+ j++;
+ if (j >= size)
+ j = 0;
+ }
+ hash[j] = obj;
+}
+
static int hashtable_index(const unsigned char *sha1)
{
unsigned int i;
memcpy(&i, sha1, sizeof(unsigned int));
- return (int)(i % obj_allocs);
+ return (int)(i % obj_hash_size);
}
-static int find_object(const unsigned char *sha1)
+struct object *lookup_object(const unsigned char *sha1)
{
int i;
+ struct object *obj;
- if (!objs)
- return -1;
+ if (!obj_hash)
+ return NULL;
i = hashtable_index(sha1);
- while (objs[i]) {
- if (memcmp(sha1, objs[i]->sha1, 20) == 0)
- return i;
+ while ((obj = obj_hash[i]) != NULL) {
+ if (!memcmp(sha1, obj->sha1, 20))
+ break;
i++;
- if (i == obj_allocs)
+ if (i == obj_hash_size)
i = 0;
}
- return -1 - i;
+ return obj;
}
-struct object *lookup_object(const unsigned char *sha1)
+static void grow_object_hash(void)
{
- int pos = find_object(sha1);
- if (pos >= 0)
- return objs[pos];
- return NULL;
+ int i;
+ int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size;
+ struct object **new_hash;
+
+ new_hash = calloc(new_hash_size, sizeof(struct object *));
+ for (i = 0; i < obj_hash_size; i++) {
+ struct object *obj = obj_hash[i];
+ if (!obj)
+ continue;
+ insert_obj_hash(obj, new_hash, new_hash_size);
+ }
+ free(obj_hash);
+ obj_hash = new_hash;
+ obj_hash_size = new_hash_size;
}
void created_object(const unsigned char *sha1, struct object *obj)
{
- int pos;
-
obj->parsed = 0;
- memcpy(obj->sha1, sha1, 20);
- obj->type = TYPE_NONE;
obj->used = 0;
+ obj->type = OBJ_NONE;
+ obj->flags = 0;
+ memcpy(obj->sha1, sha1, 20);
- if (obj_allocs - 1 <= nr_objs * 2) {
- int i, count = obj_allocs;
- obj_allocs = (obj_allocs < 32 ? 32 : 2 * obj_allocs);
- objs = xrealloc(objs, obj_allocs * sizeof(struct object *));
- memset(objs + count, 0, (obj_allocs - count)
- * sizeof(struct object *));
- for (i = 0; i < obj_allocs; i++)
- if (objs[i]) {
- int j = find_object(objs[i]->sha1);
- if (j != i) {
- j = -1 - j;
- objs[j] = objs[i];
- objs[i] = NULL;
- }
- }
- }
-
- pos = find_object(sha1);
- if (pos >= 0)
- die("Inserting %s twice\n", sha1_to_hex(sha1));
- pos = -pos-1;
+ if (obj_hash_size - 1 <= nr_objs * 2)
+ grow_object_hash();
- objs[pos] = obj;
+ insert_obj_hash(obj, obj_hash, obj_hash_size);
nr_objs++;
}
if (!obj) {
union any_object *ret = xcalloc(1, sizeof(*ret));
created_object(sha1, &ret->object);
- ret->object.type = TYPE_NONE;
+ ret->object.type = OBJ_NONE;
return &ret->object;
}
return obj;
diff --git a/object.h b/object.h
index e0125e154fd970209d30138858f31ef2017651ac..733faac4ccd1c9a8bb4ed1fc67986b369d9442b3 100644 (file)
--- a/object.h
+++ b/object.h
#define TYPE_BITS 3
#define FLAG_BITS 27
-#define TYPE_NONE 0
-#define TYPE_BLOB 1
-#define TYPE_TREE 2
-#define TYPE_COMMIT 3
-#define TYPE_TAG 4
-#define TYPE_BAD 5
+/*
+ * The object type is stored in 3 bits.
+ */
+enum object_type {
+ OBJ_NONE = 0,
+ OBJ_COMMIT = 1,
+ OBJ_TREE = 2,
+ OBJ_BLOB = 3,
+ OBJ_TAG = 4,
+ /* 5/6 for future expansion */
+ OBJ_DELTA = 7,
+ OBJ_BAD,
+};
struct object {
unsigned parsed : 1;
};
extern int track_object_refs;
-extern const char *type_names[];
+extern const char *type_names[9];
extern unsigned int get_max_object_index(void);
extern struct object *get_indexed_object(unsigned int);
static inline const char *typename(unsigned int type)
{
- return type_names[type > TYPE_TAG ? TYPE_BAD : type];
+ return type_names[type > OBJ_BAD ? OBJ_BAD : type];
}
extern struct object_refs *lookup_object_refs(struct object *);
diff --git a/pack-objects.c b/pack-objects.c
index 47da33baa3fb70a6a2f117afbcf4e7e08a5806be..861c7f08ff1b68eee141442411b4706a1bcc2518 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.
static unsigned char pack_file_sha1[20];
static int progress = 1;
static volatile sig_atomic_t progress_update = 0;
+static int window = 10;
/*
* The object names in objects array are hashed with this hashtable,
* 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 */
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;
setitimer(ITIMER_REAL, &v, NULL);
}
+static int git_pack_config(const char *k, const char *v)
+{
+ if(!strcmp(k, "pack.window")) {
+ window = git_config_int(k, v);
+ return 0;
+ }
+ return git_default_config(k, v);
+}
+
int main(int argc, char **argv)
{
SHA_CTX ctx;
char line[40 + 1 + PATH_MAX + 2];
- int window = 10, depth = 10, pack_to_stdout = 0;
+ int depth = 10, pack_to_stdout = 0;
struct object_entry **list;
int num_preferred_base = 0;
int i;
setup_git_directory();
+ git_config(git_pack_config);
progress = isatty(2);
for (i = 1; i < argc; i++) {
index 694e0c56f0b67c1ae9b7a94a243aca7b8859e50c..eb07b033ae54941d4bd00ee6bc30c7d50fd039b0 100644 (file)
--- a/pack.h
+++ b/pack.h
#ifndef PACK_H
#define PACK_H
-/*
- * The packed object type is stored in 3 bits.
- * The type value 0 is a reserved prefix if ever there is more than 7
- * object types, or any future format extensions.
- */
-enum object_type {
- OBJ_EXT = 0,
- OBJ_COMMIT = 1,
- OBJ_TREE = 2,
- OBJ_BLOB = 3,
- OBJ_TAG = 4,
- /* 5/6 for future expansion */
- OBJ_DELTA = 7,
-};
+#include "object.h"
/*
* Packed object header
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
index 1910d000a5d288734aaea24da617481223ff9f56..e220dcc280d9ed6be2e2357e2660c7e828f3631b 100644 (file)
--- a/quote.c
+++ b/quote.c
return len;
}
+void sq_quote_print(FILE *stream, const char *src)
+{
+ char c;
+
+ fputc('\'', stream);
+ while ((c = *src++)) {
+ if (need_bs_quote(c)) {
+ fputs("'\\", stream);
+ fputc(c, stream);
+ fputc('\'', stream);
+ } else {
+ fputc(c, stream);
+ }
+ }
+ fputc('\'', stream);
+}
+
char *sq_quote(const char *src)
{
char *buf;
index c1ab3788e6d69318638142ebeffc690318a0489a..fc5481e78a6baad9ad72014318a9d0bfdd495b6b 100644 (file)
--- a/quote.h
+++ b/quote.h
*/
extern char *sq_quote(const char *src);
+extern void sq_quote_print(FILE *stream, const char *src);
extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
/* This unwraps what sq_quote() produces in place, but returns
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..874e349db818d52329e30f2634855e5cf14192b8 100644 (file)
--- a/revision.c
+++ b/revision.c
@@ -135,7 +135,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
/*
* Tag object? Look what it points to..
*/
- while (object->type == TYPE_TAG) {
+ while (object->type == OBJ_TAG) {
struct tag *tag = (struct tag *) object;
if (revs->tag_objects && !(flags & UNINTERESTING))
add_pending_object(revs, object, tag->tag);
@@ -148,7 +148,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
* Commit object? Just return it, we'll do all the complex
* reachability crud.
*/
- if (object->type == TYPE_COMMIT) {
+ if (object->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)object;
if (parse_commit(commit) < 0)
die("unable to parse commit %s", name);
@@ -164,7 +164,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
* Tree object? Either mark it uniniteresting, or add it
* to the list of objects to look at later..
*/
- if (object->type == TYPE_TREE) {
+ if (object->type == OBJ_TREE) {
struct tree *tree = (struct tree *)object;
if (!revs->tree_objects)
return NULL;
@@ -179,7 +179,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
/*
* Blob object? You know the drill by now..
*/
- if (object->type == TYPE_BLOB) {
+ if (object->type == OBJ_BLOB) {
struct blob *blob = (struct blob *)object;
if (!revs->blob_objects)
return NULL;
static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
{
struct commit_list **pp, *parent;
- int tree_changed = 0;
+ int tree_changed = 0, tree_same = 0;
if (!commit->tree)
return;
@@ -298,6 +298,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
parse_commit(p);
switch (rev_compare_tree(revs, p->tree, commit->tree)) {
case REV_TREE_SAME:
+ tree_same = 1;
if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
/* Even if a merge with an uninteresting
* side branch brought the entire change
@@ -334,7 +335,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
}
die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
}
- if (tree_changed)
+ if (tree_changed && !tree_same)
commit->object.flags |= TREECHANGE;
}
@@ -493,11 +494,11 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
return 0;
while (1) {
it = get_reference(revs, arg, sha1, 0);
- if (it->type != TYPE_TAG)
+ if (it->type != OBJ_TAG)
break;
memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
}
- if (it->type != TYPE_COMMIT)
+ if (it->type != OBJ_COMMIT)
return 0;
commit = (struct commit *)it;
for (parents = commit->parents; parents; parents = parents->next) {
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/revision.h b/revision.h
index c010a0811604e55f4e46d08d986ae62edfb192ee..e23ec8f45a3b635d83f662e4de5d671e41f37919 100644 (file)
--- a/revision.h
+++ b/revision.h
struct log_info *loginfo;
int nr, total;
const char *mime_boundary;
+ const char *message_id;
+ const char *ref_message_id;
const char *add_signoff;
const char *extra_headers;
diff --git a/send-pack.c b/send-pack.c
index af93b11f238e4583f6e7fb9dab5a2224acf4ee1f..10bc8bc35935c6ba3af70456781d6194c55ba274 100644 (file)
--- a/send-pack.c
+++ b/send-pack.c
* old. Otherwise we require --force.
*/
o = deref_tag(parse_object(old_sha1), NULL, 0);
- if (!o || o->type != TYPE_COMMIT)
+ if (!o || o->type != OBJ_COMMIT)
return 0;
old = (struct commit *) o;
o = deref_tag(parse_object(new_sha1), NULL, 0);
- if (!o || o->type != TYPE_COMMIT)
+ if (!o || o->type != OBJ_COMMIT)
return 0;
new = (struct commit *) o;
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..7df628f2b226f6984d168af0382680b9dae0751a 100644 (file)
--- a/server-info.c
+++ b/server-info.c
struct object *o = parse_object(sha1);
fprintf(info_ref_fp, "%s %s\n", sha1_to_hex(sha1), path);
- if (o->type == TYPE_TAG) {
+ if (o->type == OBJ_TAG) {
o = deref_tag(o, path, 0);
if (o)
fprintf(info_ref_fp, "%s %s^{}\n",
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..43bc2ea0cf039bb9fd02c8313981e85bd7398d33 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, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
{
+ unsigned char c;
+ unsigned int word, bits;
+ unsigned long size;
+ static const char *typename[8] = {
+ NULL, /* OBJ_EXT */
+ "commit", "tree", "blob", "tag",
+ NULL, NULL, NULL
+ };
+ const char *type;
+
/* Get the data stream */
memset(stream, 0, sizeof(*stream));
stream->next_in = map;
stream->avail_in = mapsize;
stream->next_out = buffer;
- stream->avail_out = size;
+ stream->avail_out = bufsiz;
+
+ /*
+ * Is it a zlib-compressed buffer? If so, the first byte
+ * must be 0x78 (15-bit window size, deflated), and the
+ * first 16-bit word is evenly divisible by 31
+ */
+ word = (map[0] << 8) + map[1];
+ if (map[0] == 0x78 && !(word % 31)) {
+ inflateInit(stream);
+ return inflate(stream, 0);
+ }
+
+ c = *map++;
+ mapsize--;
+ type = typename[(c >> 4) & 7];
+ if (!type)
+ return -1;
+ bits = 4;
+ size = c & 0xf;
+ while ((c & 0x80)) {
+ if (bits >= 8*sizeof(long))
+ return -1;
+ c = *map++;
+ size += (c & 0x7f) << bits;
+ bits += 7;
+ mapsize--;
+ }
+
+ /* Set up the stream for the rest.. */
+ stream->next_in = map;
+ stream->avail_in = mapsize;
inflateInit(stream);
- return inflate(stream, 0);
+
+ /* And generate the fake traditional header */
+ stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", type, size);
+ return 0;
}
static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
{
int bytes = strlen(buffer) + 1;
unsigned char *buf = xmalloc(1+size);
+ unsigned long n;
- memcpy(buf, (char *) buffer + bytes, stream->total_out - bytes);
- bytes = stream->total_out - bytes;
+ n = stream->total_out - bytes;
+ if (n > size)
+ n = size;
+ memcpy(buf, (char *) buffer + bytes, n);
+ bytes = n;
if (bytes < size) {
stream->next_out = buf + bytes;
stream->avail_out = size - bytes;
@@ -720,7 +768,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size
* too permissive for what we want to check. So do an anal
* object header parse by hand.
*/
-int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
+static int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
{
int i;
unsigned long size;
static int link_temp_to_file(const char *tmpfile, char *filename)
{
int ret;
+ char *dir;
if (!link(tmpfile, filename))
return 0;
/*
- * Try to mkdir the last path component if that failed
- * with an ENOENT.
+ * Try to mkdir the last path component if that failed.
*
* Re-try the "link()" regardless of whether the mkdir
* succeeds, since a race might mean that somebody
* else succeeded.
*/
ret = errno;
- if (ret == ENOENT) {
- char *dir = strrchr(filename, '/');
- if (dir) {
- *dir = 0;
- mkdir(filename, 0777);
- if (adjust_shared_perm(filename))
- return -2;
- *dir = '/';
- if (!link(tmpfile, filename))
- return 0;
- ret = errno;
- }
+ dir = strrchr(filename, '/');
+ if (dir) {
+ *dir = 0;
+ mkdir(filename, 0777);
+ if (adjust_shared_perm(filename))
+ return -2;
+ *dir = '/';
+ if (!link(tmpfile, filename))
+ return 0;
+ ret = errno;
}
return ret;
}
return 0;
}
+static int write_binary_header(unsigned char *hdr, enum object_type type, unsigned long len)
+{
+ int hdr_len;
+ unsigned char c;
+
+ c = (type << 4) | (len & 15);
+ len >>= 4;
+ hdr_len = 1;
+ while (len) {
+ *hdr++ = c | 0x80;
+ hdr_len++;
+ c = (len & 0x7f);
+ len >>= 7;
+ }
+ *hdr = c;
+ return hdr_len;
+}
+
+static void setup_object_header(z_stream *stream, const char *type, unsigned long len)
+{
+ int obj_type, hdr;
+
+ if (use_legacy_headers) {
+ while (deflate(stream, 0) == Z_OK)
+ /* nothing */;
+ return;
+ }
+ if (!strcmp(type, blob_type))
+ obj_type = OBJ_BLOB;
+ else if (!strcmp(type, tree_type))
+ obj_type = OBJ_TREE;
+ else if (!strcmp(type, commit_type))
+ obj_type = OBJ_COMMIT;
+ else if (!strcmp(type, tag_type))
+ obj_type = OBJ_TAG;
+ else
+ die("trying to generate bogus object of type '%s'", type);
+ hdr = write_binary_header(stream->next_out, obj_type, len);
+ stream->total_out = hdr;
+ stream->next_out += hdr;
+ stream->avail_out -= hdr;
+}
+
int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
{
int size;
@@ -1458,8 +1547,8 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
/* Set it up */
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, Z_BEST_COMPRESSION);
- size = deflateBound(&stream, len+hdrlen);
+ deflateInit(&stream, zlib_compression_level);
+ size = 8 + deflateBound(&stream, len+hdrlen);
compressed = xmalloc(size);
/* Compress it */
@@ -1469,8 +1558,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
/* First header.. */
stream.next_in = hdr;
stream.avail_in = hdrlen;
- while (deflate(&stream, 0) == Z_OK)
- /* nothing */;
+ setup_object_header(&stream, type, len);
/* Then the data itself.. */
stream.next_in = buf;
@@ -1504,14 +1592,14 @@ static void *repack_object(const unsigned char *sha1, unsigned long *objsize)
int hdrlen;
void *buf;
- // need to unpack and recompress it by itself
+ /* need to unpack and recompress it by itself */
unpacked = read_packed_sha1(sha1, type, &len);
hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
/* Set it up */
memset(&stream, 0, sizeof(stream));
- deflateInit(&stream, Z_BEST_COMPRESSION);
+ deflateInit(&stream, zlib_compression_level);
size = deflateBound(&stream, len + hdrlen);
buf = xmalloc(size);
/*
* 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/sha1_name.c b/sha1_name.c
index f2cbafa496231e7a9cdbbea2ab4e7d4c4ed761ab..5fe8e5d4bf25d79c3fa76610d1617ee07c1f1e2c 100644 (file)
--- a/sha1_name.c
+++ b/sha1_name.c
sp++; /* beginning of type name, or closing brace for empty */
if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
- expected_type = TYPE_COMMIT;
+ expected_type = OBJ_COMMIT;
else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
- expected_type = TYPE_TREE;
+ expected_type = OBJ_TREE;
else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
- expected_type = TYPE_BLOB;
+ expected_type = OBJ_BLOB;
else if (sp[0] == '}')
- expected_type = TYPE_NONE;
+ expected_type = OBJ_NONE;
else
return -1;
memcpy(sha1, o->sha1, 20);
return 0;
}
- if (o->type == TYPE_TAG)
+ if (o->type == OBJ_TAG)
o = ((struct tag*) o)->tagged;
- else if (o->type == TYPE_COMMIT)
+ else if (o->type == OBJ_COMMIT)
o = &(((struct commit *) o)->tree->object);
else
return error("%.*s: expected %s type, but the object dereferences to %s type",
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,250 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Various diff formatting options'
+
+. ./test-lib.sh
+
+LF='
+'
+
+test_expect_success setup '
+
+ GIT_AUTHOR_DATE="2006-06-26 00:00:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:00:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+ mkdir dir &&
+ for i in 1 2 3; do echo $i; done >file0 &&
+ for i in A B; do echo $i; done >dir/sub &&
+ cat file0 >file2 &&
+ git add file0 file2 dir/sub &&
+ git commit -m Initial &&
+
+ git branch initial &&
+ git branch side &&
+
+ GIT_AUTHOR_DATE="2006-06-26 00:01:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:01:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+ for i in 4 5 6; do echo $i; done >>file0 &&
+ for i in C D; do echo $i; done >>dir/sub &&
+ rm -f file2 &&
+ git update-index --remove file0 file2 dir/sub &&
+ git commit -m "Second${LF}${LF}This is the second commit." &&
+
+ GIT_AUTHOR_DATE="2006-06-26 00:02:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:02:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+ for i in A B C; do echo $i; done >file1 &&
+ git add file1 &&
+ for i in E F; do echo $i; done >>dir/sub &&
+ git update-index dir/sub &&
+ git commit -m Third &&
+
+ GIT_AUTHOR_DATE="2006-06-26 00:03:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:03:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+ git checkout side &&
+ for i in A B C; do echo $i; done >>file0 &&
+ for i in 1 2; do echo $i; done >>dir/sub &&
+ cat dir/sub >file3 &&
+ git add file3 &&
+ git update-index file0 dir/sub &&
+ git commit -m Side &&
+
+ GIT_AUTHOR_DATE="2006-06-26 00:04:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:04:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+ git checkout master &&
+ git pull -s ours . side &&
+
+ GIT_AUTHOR_DATE="2006-06-26 00:05:00 +0000" &&
+ GIT_COMMITTER_DATE="2006-06-26 00:05:00 +0000" &&
+ export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+ for i in A B C; do echo $i; done >>file0 &&
+ for i in 1 2; do echo $i; done >>dir/sub &&
+ git update-index file0 dir/sub &&
+
+ git commit --amend &&
+ git show-branch
+'
+
+: <<\EOF
+! [initial] Initial
+ * [master] Merge branch 'side'
+ ! [side] Side
+---
+ - [master] Merge branch 'side'
+ *+ [side] Side
+ * [master^] Second
++*+ [initial] Initial
+EOF
+
+V=`git version | sed -e 's/^git version //'`
+while read cmd
+do
+ case "$cmd" in
+ '' | '#'*) continue ;;
+ esac
+ test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'`
+ cnt=`expr $test_count + 1`
+ pfx=`printf "%04d" $cnt`
+ expect="../t4013/diff.$test"
+ actual="$pfx-diff.$test"
+
+ test_expect_success "git $cmd" '
+ {
+ echo "\$ git $cmd"
+ git $cmd | sed -e "s/$V/g-i-t--v-e-r-s-i-o-n/"
+ echo "\$"
+ } >"$actual" &&
+ if test -f "$expect"
+ then
+ diff -u "$expect" "$actual" &&
+ rm -f "$actual"
+ else
+ # this is to help developing new tests.
+ cp "$actual" "$expect"
+ false
+ fi
+ '
+done <<\EOF
+diff-tree initial
+diff-tree -r initial
+diff-tree -r --abbrev initial
+diff-tree -r --abbrev=4 initial
+diff-tree --root initial
+diff-tree --root --abbrev initial
+diff-tree --root -r initial
+diff-tree --root -r --abbrev initial
+diff-tree --root -r --abbrev=4 initial
+diff-tree -p initial
+diff-tree --root -p initial
+diff-tree --patch-with-stat initial
+diff-tree --root --patch-with-stat initial
+diff-tree --patch-with-raw initial
+diff-tree --root --patch-with-raw initial
+
+diff-tree --pretty initial
+diff-tree --pretty --root initial
+diff-tree --pretty -p initial
+diff-tree --pretty --stat initial
+diff-tree --pretty --summary initial
+diff-tree --pretty --stat --summary initial
+diff-tree --pretty --root -p initial
+diff-tree --pretty --root --stat initial
+# improved by Timo's patch
+diff-tree --pretty --root --summary initial
+# improved by Timo's patch
+diff-tree --pretty --root --summary -r initial
+diff-tree --pretty --root --stat --summary initial
+diff-tree --pretty --patch-with-stat initial
+diff-tree --pretty --root --patch-with-stat initial
+diff-tree --pretty --patch-with-raw initial
+diff-tree --pretty --root --patch-with-raw initial
+
+diff-tree --pretty=oneline initial
+diff-tree --pretty=oneline --root initial
+diff-tree --pretty=oneline -p initial
+diff-tree --pretty=oneline --root -p initial
+diff-tree --pretty=oneline --patch-with-stat initial
+# improved by Timo's patch
+diff-tree --pretty=oneline --root --patch-with-stat initial
+diff-tree --pretty=oneline --patch-with-raw initial
+diff-tree --pretty=oneline --root --patch-with-raw initial
+
+diff-tree --pretty side
+diff-tree --pretty -p side
+diff-tree --pretty --patch-with-stat side
+
+diff-tree master
+diff-tree -p master
+diff-tree -p -m master
+diff-tree -c master
+diff-tree -c --abbrev master
+diff-tree --cc master
+# stat only should show the diffstat with the first parent
+diff-tree -c --stat master
+diff-tree --cc --stat master
+diff-tree -c --stat --summary master
+diff-tree --cc --stat --summary master
+# stat summary should show the diffstat and summary with the first parent
+diff-tree -c --stat --summary side
+diff-tree --cc --stat --summary side
+# improved by Timo's patch
+diff-tree --cc --patch-with-stat master
+# improved by Timo's patch
+diff-tree --cc --patch-with-stat --summary master
+# this is correct
+diff-tree --cc --patch-with-stat --summary side
+
+log master
+log -p master
+log --root master
+log --root -p master
+log --patch-with-stat master
+log --root --patch-with-stat master
+log --root --patch-with-stat --summary master
+# improved by Timo's patch
+log --root -c --patch-with-stat --summary master
+# improved by Timo's patch
+log --root --cc --patch-with-stat --summary master
+log -SF master
+log -SF -p master
+
+whatchanged master
+whatchanged -p master
+whatchanged --root master
+whatchanged --root -p master
+whatchanged --patch-with-stat master
+whatchanged --root --patch-with-stat master
+whatchanged --root --patch-with-stat --summary master
+# improved by Timo's patch
+whatchanged --root -c --patch-with-stat --summary master
+# improved by Timo's patch
+whatchanged --root --cc --patch-with-stat --summary master
+whatchanged -SF master
+whatchanged -SF -p master
+
+log --patch-with-stat master -- dir/
+whatchanged --patch-with-stat master -- dir/
+log --patch-with-stat --summary master -- dir/
+whatchanged --patch-with-stat --summary master -- dir/
+
+show initial
+show --root initial
+show side
+show master
+show --stat side
+show --stat --summary side
+show --patch-with-stat side
+show --patch-with-raw side
+show --patch-with-stat --summary side
+
+format-patch --stdout initial..side
+format-patch --stdout initial..master^
+format-patch --stdout initial..master
+format-patch --attach --stdout initial..side
+format-patch --attach --stdout initial..master^
+format-patch --attach --stdout initial..master
+
+diff --abbrev initial..side
+diff -r initial..side
+diff --stat initial..side
+diff -r --stat initial..side
+diff initial..side
+diff --patch-with-stat initial..side
+diff --patch-with-raw initial..side
+diff --patch-with-stat -r initial..side
+diff --patch-with-raw -r initial..side
+EOF
+
+test_done
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master
--- /dev/null
@@ -0,0 +1,34 @@
+$ git diff-tree --cc --patch-with-stat --summary master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+ A
+ B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+ 1
+ 2
+ 3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side b/t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side
--- /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
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+ A
+ B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+ 1
+ 2
+ 3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_--summary_master b/t/t4013/diff.diff-tree_--cc_--stat_--summary_master
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree --cc --stat --summary master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_--cc_--stat_--summary_side b/t/t4013/diff.diff-tree_--cc_--stat_--summary_side
--- /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
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_--cc_master b/t/t4013/diff.diff-tree_--cc_master
--- /dev/null
@@ -0,0 +1,30 @@
+$ git diff-tree --cc master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+ A
+ B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+ 1
+ 2
+ 3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.diff-tree_--patch-with-raw_initial b/t/t4013/diff.diff-tree_--patch-with-raw_initial
--- /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
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+::100644 100644 100644 cead32e... 7289e35... 992913c... MM dir/sub
+::100644 100644 100644 b414108... f4615da... 10a8a9f... MM file0
+$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_--summary_master b/t/t4013/diff.diff-tree_-c_--stat_--summary_master
--- /dev/null
@@ -0,0 +1,6 @@
+$ git diff-tree -c --stat --summary master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_-c_--stat_--summary_side b/t/t4013/diff.diff-tree_-c_--stat_--summary_side
--- /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
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_-c_master b/t/t4013/diff.diff-tree_-c_master
--- /dev/null
@@ -0,0 +1,5 @@
+$ git diff-tree -c master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+::100644 100644 100644 cead32e925b1420c84c14cbf7cf755e7e45af8ad 7289e35bff32727c08dda207511bec138fdb9ea5 992913c5aa0a5476d10c49ed0f21fc0c6d1aedf3 MM dir/sub
+::100644 100644 100644 b414108e81e5091fe0974a1858b4d0d22b107f70 f4615da674c09df322d6ba8d6b21ecfb1b1ba510 10a8a9f3657f91a156b9f0184ed79a20adef9f7f MM file0
+$
diff --git a/t/t4013/diff.diff-tree_-p_-m_master b/t/t4013/diff.diff-tree_-p_-m_master
--- /dev/null
@@ -0,0 +1,80 @@
+$ git diff-tree -p -m master
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+diff --git a/dir/sub b/dir/sub
+index 7289e35..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,4 +1,8 @@
+ A
+ B
++C
++D
++E
++F
+ 1
+ 2
+diff --git a/file0 b/file0
+index f4615da..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -1,6 +1,9 @@
+ 1
+ 2
+ 3
++4
++5
++6
+ A
+ B
+ C
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+diff --git a/file3 b/file3
+deleted file mode 100644
+index 7289e35..0000000
+--- a/file3
++++ /dev/null
+@@ -1,4 +0,0 @@
+-A
+-B
+-1
+-2
+$
diff --git a/t/t4013/diff.diff-tree_-p_initial b/t/t4013/diff.diff-tree_-p_initial
--- /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,170 @@
+$ git format-patch --attach --stdout initial..master
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
--- /dev/null
@@ -0,0 +1,110 @@
+$ git format-patch --attach --stdout initial..master^
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..side b/t/t4013/diff.format-patch_--attach_--stdout_initial..side
--- /dev/null
@@ -0,0 +1,61 @@
+$ git format-patch --attach --stdout initial..side
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch;
+ name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline;
+ filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4013/diff.format-patch_--stdout_initial..master b/t/t4013/diff.format-patch_--stdout_initial..master
--- /dev/null
@@ -0,0 +1,124 @@
+$ git format-patch --stdout initial..master
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+
+This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+--
+g-i-t--v-e-r-s-i-o-n
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+--
+g-i-t--v-e-r-s-i-o-n
+
+
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+--
+g-i-t--v-e-r-s-i-o-n
+
+$
diff --git a/t/t4013/diff.format-patch_--stdout_initial..master^ b/t/t4013/diff.format-patch_--stdout_initial..master^
--- /dev/null
@@ -0,0 +1,79 @@
+$ git format-patch --stdout initial..master^
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+
+This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+--
+g-i-t--v-e-r-s-i-o-n
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+--
+g-i-t--v-e-r-s-i-o-n
+
+$
diff --git a/t/t4013/diff.format-patch_--stdout_initial..side b/t/t4013/diff.format-patch_--stdout_initial..side
--- /dev/null
@@ -0,0 +1,46 @@
+$ git format-patch --stdout initial..side
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+--
+g-i-t--v-e-r-s-i-o-n
+
+$
diff --git a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
--- /dev/null
@@ -0,0 +1,74 @@
+$ git log --patch-with-stat --summary master -- dir/
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_--patch-with-stat_master b/t/t4013/diff.log_--patch-with-stat_master
--- /dev/null
@@ -0,0 +1,129 @@
+$ git log --patch-with-stat master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_
--- /dev/null
@@ -0,0 +1,74 @@
+$ git log --patch-with-stat master -- dir/
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
--- /dev/null
@@ -0,0 +1,199 @@
+$ git log --root --cc --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+ A
+ B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+ 1
+ 2
+ 3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master
--- /dev/null
@@ -0,0 +1,167 @@
+$ git log --root --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_--patch-with-stat_master b/t/t4013/diff.log_--root_--patch-with-stat_master
--- /dev/null
@@ -0,0 +1,161 @@
+$ git log --root --patch-with-stat master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
--- /dev/null
@@ -0,0 +1,199 @@
+$ git log --root -c --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --combined dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+ A
+ B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --combined file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+ 1
+ 2
+ 3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_-p_master b/t/t4013/diff.log_--root_-p_master
--- /dev/null
@@ -0,0 +1,142 @@
+$ git log --root -p master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.log_--root_master b/t/t4013/diff.log_--root_master
--- /dev/null
@@ -0,0 +1,34 @@
+$ git log --root master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_-SF_-p_master b/t/t4013/diff.log_-SF_-p_master
--- /dev/null
@@ -0,0 +1,18 @@
+$ git log -SF -p master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+$
diff --git a/t/t4013/diff.log_-SF_master b/t/t4013/diff.log_-SF_master
--- /dev/null
@@ -0,0 +1,8 @@
+$ git log -SF master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+$
diff --git a/t/t4013/diff.log_-p_master b/t/t4013/diff.log_-p_master
--- /dev/null
@@ -0,0 +1,115 @@
+$ git log -p master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_master b/t/t4013/diff.log_master
--- /dev/null
+++ b/t/t4013/diff.log_master
@@ -0,0 +1,34 @@
+$ git log master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.show_--patch-with-raw_side b/t/t4013/diff.show_--patch-with-raw_side
--- /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 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+ A
+ B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+ 1
+ 2
+ 3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+$
diff --git a/t/t4013/diff.show_side b/t/t4013/diff.show_side
--- /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,61 @@
+$ git whatchanged --patch-with-stat --summary master -- dir/
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+$
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_master b/t/t4013/diff.whatchanged_--patch-with-stat_master
--- /dev/null
@@ -0,0 +1,116 @@
+$ git whatchanged --patch-with-stat master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+$
diff --git a/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_ b/t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_
--- /dev/null
@@ -0,0 +1,61 @@
+$ git whatchanged --patch-with-stat master -- dir/
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ 1 files changed, 2 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+$
diff --git a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
--- /dev/null
@@ -0,0 +1,199 @@
+$ git whatchanged --root --cc --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --cc dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+ A
+ B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --cc file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+ 1
+ 2
+ 3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master
--- /dev/null
@@ -0,0 +1,160 @@
+$ git whatchanged --root --patch-with-stat --summary master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_--patch-with-stat_master b/t/t4013/diff.whatchanged_--root_--patch-with-stat_master
--- /dev/null
@@ -0,0 +1,154 @@
+$ git whatchanged --root --patch-with-stat master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
--- /dev/null
@@ -0,0 +1,199 @@
+$ git whatchanged --root -c --patch-with-stat --summary master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494... c7a2ab9...
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+ dir/sub | 2 ++
+ file0 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+
+diff --combined dir/sub
+index cead32e,7289e35..992913c
+--- a/dir/sub
++++ b/dir/sub
+@@@ -1,6 -1,4 +1,8 @@@
+ A
+ B
+ +C
+ +D
+ +E
+ +F
++ 1
++ 2
+diff --combined file0
+index b414108,f4615da..10a8a9f
+--- a/file0
++++ b/file0
+@@@ -1,6 -1,6 +1,9 @@@
+ 1
+ 2
+ 3
+ +4
+ +5
+ +6
++ A
++ B
++ C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file3 | 4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 +++
+ 3 files changed, 8 insertions(+), 0 deletions(-)
+ create mode 100644 dir/sub
+ create mode 100644 file0
+ create mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_-p_master b/t/t4013/diff.whatchanged_--root_-p_master
--- /dev/null
@@ -0,0 +1,135 @@
+$ git whatchanged --root -p master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+
+diff --git a/dir/sub b/dir/sub
+new file mode 100644
+index 0000000..35d242b
+--- /dev/null
++++ b/dir/sub
+@@ -0,0 +1,2 @@
++A
++B
+diff --git a/file0 b/file0
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file0
+@@ -0,0 +1,3 @@
++1
++2
++3
+diff --git a/file2 b/file2
+new file mode 100644
+index 0000000..01e79c3
+--- /dev/null
++++ b/file2
+@@ -0,0 +1,3 @@
++1
++2
++3
+$
diff --git a/t/t4013/diff.whatchanged_--root_master b/t/t4013/diff.whatchanged_--root_master
--- /dev/null
@@ -0,0 +1,42 @@
+$ git whatchanged --root master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+:100644 100644 35d242b... 7289e35... M dir/sub
+:100644 100644 01e79c3... f4615da... M file0
+:000000 100644 0000000... 7289e35... A file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+:100644 100644 8422d40... cead32e... M dir/sub
+:000000 100644 0000000... b1e6722... A file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+:100644 100644 35d242b... 8422d40... M dir/sub
+:100644 100644 01e79c3... b414108... M file0
+:100644 000000 01e79c3... 0000000... D file2
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+
+:000000 100644 0000000... 35d242b... A dir/sub
+:000000 100644 0000000... 01e79c3... A file0
+:000000 100644 0000000... 01e79c3... A file2
+$
diff --git a/t/t4013/diff.whatchanged_-SF_-p_master b/t/t4013/diff.whatchanged_-SF_-p_master
--- /dev/null
@@ -0,0 +1,18 @@
+$ git whatchanged -SF -p master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+$
diff --git a/t/t4013/diff.whatchanged_-SF_master b/t/t4013/diff.whatchanged_-SF_master
--- /dev/null
@@ -0,0 +1,9 @@
+$ git whatchanged -SF master
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+:100644 100644 8422d40... cead32e... M dir/sub
+$
diff --git a/t/t4013/diff.whatchanged_-p_master b/t/t4013/diff.whatchanged_-p_master
--- /dev/null
@@ -0,0 +1,102 @@
+$ git whatchanged -p master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+$
diff --git a/t/t4013/diff.whatchanged_master b/t/t4013/diff.whatchanged_master
--- /dev/null
@@ -0,0 +1,32 @@
+$ git whatchanged master
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+:100644 100644 35d242b... 7289e35... M dir/sub
+:100644 100644 01e79c3... f4615da... M file0
+:000000 100644 0000000... 7289e35... A file3
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+:100644 100644 8422d40... cead32e... M dir/sub
+:000000 100644 0000000... b1e6722... A file1
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+:100644 100644 35d242b... 8422d40... M dir/sub
+:100644 100644 01e79c3... b414108... M file0
+:100644 000000 01e79c3... 0000000... D file2
+$
index a06f6956d5c72cae79129fc01a1be2f30121e6fb..69e9603c784b580ff143e4249c72aea79f64f471 100755 (executable)
--- a/t/t4112-apply-renames.sh
+++ b/t/t4112-apply-renames.sh
# setup
-mkdir -p include/arch/x86_64/klibc klibc/arch/x86_64/include/klibc
-
-cat >include/arch/x86_64/klibc/archsetjmp.h <<\EOF
-/*
- * arch/x86_64/include/klibc/archsetjmp.h
- */
-
-#ifndef _KLIBC_ARCHSETJMP_H
-#define _KLIBC_ARCHSETJMP_H
-
-struct __jmp_buf {
- unsigned long __rbx;
- unsigned long __rsp;
- unsigned long __rbp;
- unsigned long __r12;
- unsigned long __r13;
- unsigned long __r14;
- unsigned long __r15;
- unsigned long __rip;
-};
-
-typedef struct __jmp_buf jmp_buf[1];
-
-#endif /* _SETJMP_H */
-EOF
+mkdir -p klibc/arch/x86_64/include/klibc
cat >klibc/arch/x86_64/include/klibc/archsetjmp.h <<\EOF
/*
+#endif /* _KLIBC_ARCHSETJMP_H */
EOF
-find include klibc -type f -print | xargs git-update-index --add --
+find klibc -type f -print | xargs git-update-index --add --
test_expect_success 'check rename/copy patch' 'git-apply --check patch'
diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-apply should not get confused with type changes.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success 'setup repository and commits' '
+ echo "hello world" > foo &&
+ echo "hi planet" > bar &&
+ git update-index --add foo bar &&
+ git commit -m initial &&
+ git branch initial &&
+ rm -f foo &&
+ ln -s bar foo &&
+ git update-index foo &&
+ git commit -m "foo symlinked to bar" &&
+ git branch foo-symlinked-to-bar &&
+ rm -f foo &&
+ echo "how far is the sun?" > foo &&
+ git update-index foo &&
+ git commit -m "foo back to file" &&
+ git branch foo-back-to-file &&
+ rm -f foo &&
+ git update-index --remove foo &&
+ mkdir foo &&
+ echo "if only I knew" > foo/baz &&
+ git update-index --add foo/baz &&
+ git commit -m "foo becomes a directory" &&
+ git branch "foo-becomes-a-directory" &&
+ echo "hello world" > foo/baz &&
+ git update-index foo/baz &&
+ git commit -m "foo/baz is the original foo" &&
+ git branch foo-baz-renamed-from-foo
+ '
+
+test_expect_success 'file renamed from foo to foo/baz' '
+ git checkout -f initial &&
+ git diff-tree -M -p HEAD foo-baz-renamed-from-foo > patch &&
+ git apply --index < patch
+ '
+test_debug 'cat patch'
+
+
+test_expect_success 'file renamed from foo/baz to foo' '
+ git checkout -f foo-baz-renamed-from-foo &&
+ git diff-tree -M -p HEAD initial > patch &&
+ git apply --index < patch
+ '
+test_debug 'cat patch'
+
+
+test_expect_success 'directory becomes file' '
+ git checkout -f foo-becomes-a-directory &&
+ git diff-tree -p HEAD initial > patch &&
+ git apply --index < patch
+ '
+test_debug 'cat patch'
+
+
+test_expect_success 'file becomes directory' '
+ git checkout -f initial &&
+ git diff-tree -p HEAD foo-becomes-a-directory > patch &&
+ git apply --index < patch
+ '
+test_debug 'cat patch'
+
+
+test_expect_success 'file becomes symlink' '
+ git checkout -f initial &&
+ git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
+ git apply --index < patch
+ '
+test_debug 'cat patch'
+
+
+test_expect_success 'symlink becomes file' '
+ git checkout -f foo-symlinked-to-bar &&
+ git diff-tree -p HEAD foo-back-to-file > patch &&
+ git apply --index < patch
+ '
+test_debug 'cat patch'
+
+
+test_expect_success 'symlink becomes directory' '
+ git checkout -f foo-symlinked-to-bar &&
+ git diff-tree -p HEAD foo-becomes-a-directory > patch &&
+ git apply --index < patch
+ '
+test_debug 'cat patch'
+
+
+test_expect_success 'directory becomes symlink' '
+ git checkout -f foo-becomes-a-directory &&
+ git diff-tree -p HEAD foo-symlinked-to-bar > patch &&
+ git apply --index < patch
+ '
+test_debug 'cat patch'
+
+
+test_done
index 916ee15ba1ed840fbd0df9d91e4f06fae8fde357..dd9caad1c2a34a6721aafa1840916a5a7471ece8 100755 (executable)
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
diff --git a/t/t6004-rev-list-path-optim.sh b/t/t6004-rev-list-path-optim.sh
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test_description='git-rev-list trivial path optimization test'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+echo Hello > a &&
+git add a &&
+git commit -m "Initial commit" a
+'
+
+test_expect_success path-optimization '
+ commit=$(echo "Unchanged tree" | git-commit-tree "HEAD^{tree}" -p HEAD) &&
+ test $(git-rev-list $commit | wc -l) = 2 &&
+ test $(git-rev-list $commit -- . | wc -l) = 1
+'
+
+test_done
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
index 1dce123aecd0baa4bbcf122b9a91c478d6e2ef53..b15920b8521fce8c483fa54880de7eb599c87f1c 100755 (executable)
--- a/t/t6010-merge-base.sh
+++ b/t/t6010-merge-base.sh
G=$(doit 7 G $B $E)
H=$(doit 8 H $A $F)
+# Setup for second test to demonstrate that relying on timestamps in a
+# distributed SCM to provide a _consistent_ partial ordering of commits
+# leads to insanity.
+#
+# Relative
+# Structure timestamps
+#
+# PL PR +4 +4
+# / \/ \ / \/ \
+# L2 C2 R2 +3 -1 +3
+# | | | | | |
+# L1 C1 R1 +2 -2 +2
+# | | | | | |
+# L0 C0 R0 +1 -3 +1
+# \ | / \ | /
+# S 0
+#
+# The left and right chains of commits can be of any length and complexity as
+# long as all of the timestamps are greater than that of S.
+
+S=$(doit 0 S)
+
+C0=$(doit -3 C0 $S)
+C1=$(doit -2 C1 $C0)
+C2=$(doit -1 C2 $C1)
+
+L0=$(doit 1 L0 $S)
+L1=$(doit 2 L1 $L0)
+L2=$(doit 3 L2 $L1)
+
+R0=$(doit 1 R0 $S)
+R1=$(doit 2 R1 $R0)
+R2=$(doit 3 R2 $R1)
+
+PL=$(doit 4 PL $L2 $C2)
+PR=$(doit 4 PR $C2 $R2)
+
test_expect_success 'compute merge-base (single)' \
'MB=$(git-merge-base G H) &&
expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
'MB=$(git-show-branch --merge-base G H) &&
expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+test_expect_success 'compute merge-base (single)' \
+ 'MB=$(git-merge-base PL PR) &&
+ expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+
+test_expect_success 'compute merge-base (all)' \
+ 'MB=$(git-merge-base --all PL PR) &&
+ expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+
test_done
index 262381379f247a74f1656cfdd7f9b0c6d386d372..8f7366da8d53feb95d08fd57c04b96c7bcbb9625 100755 (executable)
#
# 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/t7001-mv.sh b/t/t7001-mv.sh
index 811a4797a58dbc3411f885535b90e6df99f06334..322eaadc730dd022fffe92aba2cba717a3ebe7dc 100755 (executable)
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
'git-diff-tree -r -M --name-status HEAD^ HEAD | \
grep -E "^R100.+path1/COPYING.+path0/COPYING"'
+test_expect_success \
+ 'adding another file' \
+ 'cp ../../README path0/README &&
+ git-add path0/README &&
+ git-commit -m add2 -a'
+
+test_expect_success \
+ 'moving whole subdirectory' \
+ 'git-mv path0 path2'
+
+test_expect_success \
+ 'commiting the change' \
+ 'git-commit -m dir-move -a'
+
+test_expect_success \
+ 'checking the commit' \
+ 'git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ grep -E "^R100.+path0/COPYING.+path2/COPYING" &&
+ git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ grep -E "^R100.+path0/README.+path2/README"'
+
+test_expect_success \
+ 'moving whole subdirectory into subdirectory' \
+ 'git-mv path2 path1'
+
+test_expect_success \
+ 'commiting the change' \
+ 'git-commit -m dir-move -a'
+
+test_expect_success \
+ 'checking the commit' \
+ 'git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ grep -E "^R100.+path2/COPYING.+path1/path2/COPYING" &&
+ git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ grep -E "^R100.+path2/README.+path1/path2/README"'
+
test_done
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..470a909891bc1358163af91513972ce7d0c702c0 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 GIT_EXTERNAL_DIFF
unset GIT_INDEX_FILE
unset GIT_OBJECT_DIRECTORY
+unset GIT_TRACE
unset SHA1_FILE_DIRECTORIES
unset SHA1_FILE_DIRECTORY
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
+export EDITOR VISUAL
# Each test should start with something like this, after copyright notices:
#
index 74d0dabe5d8c1f06a3f67475368c34e3b4046456..864ac1bb602b1af07301d0b5f15d31e904b343a6 100644 (file)
--- a/tag.c
+++ b/tag.c
struct object *deref_tag(struct object *o, const char *warn, int warnlen)
{
- while (o && o->type == TYPE_TAG)
+ while (o && o->type == OBJ_TAG)
o = parse_object(((struct tag *)o)->tagged->sha1);
if (!o && warn) {
if (!warnlen)
if (!obj) {
struct tag *ret = alloc_tag_node();
created_object(sha1, &ret->object);
- ret->object.type = TYPE_TAG;
+ ret->object.type = OBJ_TAG;
return ret;
}
if (!obj->type)
- obj->type = TYPE_TAG;
- if (obj->type != TYPE_TAG) {
+ obj->type = OBJ_TAG;
+ if (obj->type != OBJ_TAG) {
error("Object %s is a %s, not a tree",
sha1_to_hex(sha1), typename(obj->type));
return NULL;
index d7a8f0a849523ebc280e06fe836f3bc448993d0c..76d5ac2477d669aabfef8108cfbb713fbdfef70a 100644 (file)
--- 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
index 10236555cc5c127b9b5b2cac8f2514c1b7e87676..a6032e35ecb2543b6efcae252e5815b804cb6f0a 100644 (file)
--- a/tree.c
+++ b/tree.c
if (!obj) {
struct tree *ret = alloc_tree_node();
created_object(sha1, &ret->object);
- ret->object.type = TYPE_TREE;
+ ret->object.type = OBJ_TREE;
return ret;
}
if (!obj->type)
- obj->type = TYPE_TREE;
- if (obj->type != TYPE_TREE) {
+ obj->type = OBJ_TREE;
+ if (obj->type != OBJ_TREE) {
error("Object %s is a %s, not a tree",
sha1_to_hex(sha1), typename(obj->type));
return NULL;
do {
if (!obj)
return NULL;
- if (obj->type == TYPE_TREE)
+ if (obj->type == OBJ_TREE)
return (struct tree *) obj;
- else if (obj->type == TYPE_COMMIT)
+ else if (obj->type == OBJ_COMMIT)
obj = &(((struct commit *) obj)->tree->object);
- else if (obj->type == TYPE_TAG)
+ else if (obj->type == OBJ_TAG)
obj = ((struct tag *) obj)->tagged;
else
return NULL;
diff --git a/unpack-objects.c b/unpack-objects.c
index 3b824b04a262b92808ba138b1922badbbb25da89..48c1ee7968cc6b40cb02cd99d35e66630f922041 100644 (file)
--- a/unpack-objects.c
+++ b/unpack-objects.c
}
}
-/*
- * We unpack from the end, older files first. Now, usually
- * there are deltas etc, so we'll not actually write the
- * objects in that order, but we might as well try..
- */
static void unpack_all(void)
{
int i;
diff --git a/upload-pack.c b/upload-pack.c
index 2b70c3dcb4e3b1e73d7d5a088ae73de17540b6b7..07ecdb4281cfb32cf16dfc7c46c53e6e8d0247bc 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>";
if (fd == 3)
/* emergency quit */
fd = 2;
+ if (fd == 2) {
+ xwrite(fd, data, sz);
+ return sz;
+ }
return safe_write(fd, data, sz);
}
p = data;
ssize_t sz;
int pe, pu, pollsize;
+ reset_timeout();
+
pollsize = 0;
pe = pu = -1;
o = parse_object(sha1);
if (!o)
die("oops (%s)", sha1_to_hex(sha1));
- if (o->type == TYPE_COMMIT) {
+ if (o->type == OBJ_COMMIT) {
struct commit_list *parents;
if (o->flags & THEY_HAVE)
return 0;
o->flags |= OUR_REF;
nr_our_refs++;
}
- if (o->type == TYPE_TAG) {
+ if (o->type == OBJ_TAG) {
o = deref_tag(o, refname, 0);
packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
}
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index 2ce10b4c0d18aca467d3fb7a47badefa8c7c956f..c9f817818a781164b63c78be2af71a5d21e35d6f 100644 (file)
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
#define XDL_PATCH_IGNOREBSPACE (1 << 8)
#define XDL_EMIT_FUNCNAMES (1 << 0)
+#define XDL_EMIT_COMMON (1 << 1)
#define XDL_MMB_READONLY (1 << 0)
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;;) {
/*
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index ad5bfb191003f14cd71b2ec7f39fd72ac86eda9b..714c563547462d962b4a6b69b524092e1bc2a036 100644 (file)
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -100,6 +100,21 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
}
+int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg) {
+ xdfile_t *xdf = &xe->xdf1;
+ const char *rchg = xdf->rchg;
+ long ix;
+
+ for (ix = 0; ix < xdf->nrec; ix++) {
+ if (rchg[ix])
+ continue;
+ if (xdl_emit_record(xdf, ix, "", ecb))
+ return -1;
+ }
+ return 0;
+}
+
int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg) {
long s1, s2, e1, e2, lctx;
char funcbuf[40];
long funclen = 0;
+ if (xecfg->flags & XDL_EMIT_COMMON)
+ return xdl_emit_common(xe, xscr, ecb, xecfg);
+
for (xch = xche = xscr; xch; xch = xche->next) {
xche = xdl_get_hunk(xch, xecfg);