Code

Merge branch 'maint'
authorJunio C Hamano <junkio@cox.net>
Fri, 22 Dec 2006 06:56:04 +0000 (22:56 -0800)
committerJunio C Hamano <junkio@cox.net>
Fri, 22 Dec 2006 06:56:04 +0000 (22:56 -0800)
* maint:
  diff --check: fix off by one error
  spurious .sp in manpages

200 files changed:
.gitignore
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/cvs-migration.txt
Documentation/diff-format.txt
Documentation/diff-options.txt
Documentation/git-add.txt
Documentation/git-branch.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-diff.txt
Documentation/git-merge-file.txt [new file with mode: 0644]
Documentation/git-merge-index.txt
Documentation/git-merge.txt
Documentation/git-push.txt
Documentation/git-read-tree.txt
Documentation/git-repo-config.txt
Documentation/git-rerere.txt
Documentation/git-reset.txt
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-show.txt
Documentation/git-svn.txt
Documentation/git-svnimport.txt
Documentation/git-symbolic-ref.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/technical/racy-git.txt
Documentation/tutorial-2.txt
Documentation/tutorial.txt
GIT-VERSION-GEN
INSTALL
Makefile
archive-tar.c
archive-zip.c
blob.c
builtin-add.c
builtin-apply.c
builtin-archive.c
builtin-blame.c
builtin-branch.c
builtin-commit-tree.c
builtin-for-each-ref.c
builtin-grep.c
builtin-init-db.c
builtin-log.c
builtin-ls-files.c
builtin-mailinfo.c
builtin-mailsplit.c
builtin-merge-file.c [new file with mode: 0644]
builtin-mv.c
builtin-name-rev.c
builtin-pack-objects.c
builtin-pack-refs.c
builtin-prune.c
builtin-push.c
builtin-read-tree.c
builtin-repo-config.c
builtin-rev-list.c
builtin-runstatus.c
builtin-shortlog.c [new file with mode: 0644]
builtin-show-branch.c
builtin-show-ref.c
builtin-stripspace.c
builtin-tar-tree.c
builtin-unpack-objects.c
builtin-upload-archive.c
builtin.h
cache.h
color.c
commit.c
compat/inet_ntop.c
compat/mmap.c
compat/setenv.c
compat/strlcpy.c
compat/subprocess.py [deleted file]
compat/unsetenv.c
config.c
config.mak.in
configure.ac
connect.c
contrib/completion/git-completion.bash
contrib/mailmap.linux [new file with mode: 0644]
contrib/vim/syntax/gitcommit.vim
convert-objects.c
daemon.c
date.c
diff-delta.c
diff.c
diff.h
diffcore-order.c
diffcore-pickaxe.c
dir.c
dir.h
entry.c
environment.c
exec_cmd.c
fetch-pack.c
fetch.c
fsck-objects.c
generate-cmdlist.sh
git-am.sh
git-checkout.sh
git-clone.sh
git-commit.sh
git-compat-util.h
git-cvsexportcommit.perl
git-cvsimport.perl
git-cvsserver.perl
git-fetch.sh
git-ls-remote.sh
git-merge-one-file.sh
git-merge-recursive-old.py [deleted file]
git-merge.sh
git-parse-remote.sh
git-pull.sh
git-rebase.sh
git-repack.sh
git-request-pull.sh
git-rerere.perl
git-reset.sh
git-revert.sh
git-shortlog.perl [deleted file]
git-svn.perl
git-tag.sh
git.c
git.spec.in
gitMergeCommon.py [deleted file]
gitk
gitweb/gitweb.css
gitweb/gitweb.perl
grep.c
help.c
ident.c
imap-send.c
index-pack.c
interpolate.c
lockfile.c
log-tree.c
merge-base.c
merge-file.c
merge-index.c
merge-recursive.c
patch-delta.c
path-list.c
path.c
perl/.gitignore
perl/Makefile [new file with mode: 0644]
perl/Makefile.PL
pkt-line.c
read-cache.c
receive-pack.c
refs.c
refs.h
revision.c
revision.h
rsh.c
run-command.c
send-pack.c
sha1_file.c
ssh-upload.c
strbuf.c
t/Makefile
t/lib-git-svn.sh
t/t0000-basic.sh
t/t1004-read-tree-m-u-wf.sh
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1400-update-ref.sh
t/t3200-branch.sh
t/t4013-diff-various.sh
t/t4015-diff-whitespace.sh
t/t5400-send-pack.sh
t/t5510-fetch.sh
t/t6023-merge-file.sh [new file with mode: 0644]
t/t6023-merge-rename-nocruft.sh [new file with mode: 0755]
t/t6024-recursive-merge.sh [new file with mode: 0644]
t/t9100-git-svn-basic.sh
t/t9101-git-svn-props.sh
t/t9102-git-svn-deep-rmdir.sh
t/t9103-git-svn-graft-branches.sh
t/t9104-git-svn-follow-parent.sh
t/t9105-git-svn-commit-diff.sh
t/t9106-git-svn-commit-diff-clobber.sh
t/t9200-git-cvsexportcommit.sh
t/test-lib.sh
templates/remotes-- [deleted file]
test-date.c
test-delta.c
tree.c
unpack-trees.c
unpack-trees.h
upload-pack.c
var.c
wt-status.c
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xmerge.c [new file with mode: 0644]
xdiff/xutils.c

index 4c8c8e411500fe2ed0108c46838d50576ed15185..d706dd92c6a63474616c09388fc9744ff055cedc 100644 (file)
@@ -60,13 +60,13 @@ git-mailsplit
 git-merge
 git-merge-base
 git-merge-index
+git-merge-file
 git-merge-tree
 git-merge-octopus
 git-merge-one-file
 git-merge-ours
 git-merge-recur
 git-merge-recursive
-git-merge-recursive-old
 git-merge-resolve
 git-merge-stupid
 git-mktag
index 9d3c71c3b87a9603973de42c76c4570a94420398..22482d6a94e88544935eee5f560089b9a4bf0dfa 100644 (file)
@@ -31,6 +31,11 @@ Example
                external = "/usr/local/bin/gnu-diff -u"
                renames = true
 
+       [branch "devel"]
+               remote = origin
+               merge = refs/heads/devel
+
+
 Variables
 ~~~~~~~~~
 
@@ -79,8 +84,11 @@ core.logAllRefUpdates::
        file is automatically created for branch heads.
 
        This information can be used to determine what commit
-       was the tip of a branch "2 days ago".  This value is
-       false by default (no automated creation of log files).
+       was the tip of a branch "2 days ago".
+
+       This value is true by default in a repository that has
+       a working directory associated with it, and false by
+       default in a bare repository.
 
 core.repositoryFormatVersion::
        Internal variable identifying the repository format and layout
@@ -125,21 +133,24 @@ apply.whitespace::
 
 branch.<name>.remote::
        When in branch <name>, it tells `git fetch` which remote to fetch.
+       If this option is not given, `git fetch` defaults to remote "origin".
 
 branch.<name>.merge::
-       When in branch <name>, it tells `git fetch` the default remote branch
-       to be merged.
-
-pager.color::
-       A boolean to enable/disable colored output when the pager is in
-       use (default is true).
-
-diff.color::
+       When in branch <name>, it tells `git fetch` the default refspec to
+       be marked for merging in FETCH_HEAD. The value has exactly to match
+       a remote part of one of the refspecs which are fetched from the remote
+       given by "branch.<name>.remote".
+       The merge information is used by `git pull` (which at first calls
+       `git fetch`) to lookup the default branch for merging. Without
+       this option, `git pull` defaults to merge the first refspec fetched.
+       Specify multiple values to get an octopus merge.
+
+color.diff::
        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>::
+color.diff.<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`
@@ -150,6 +161,24 @@ diff.color.<slot>::
        `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, or
        `white`.
 
+color.pager::
+       A boolean to enable/disable colored output when the pager is in
+       use (default is true).
+
+color.status::
+       A boolean to enable/disable color in the output of
+       gitlink:git-status[1]. May be set to `true` (or `always`),
+       `false` (or `never`) or `auto`, in which case colors are used
+       only when the output is to a terminal. Defaults to false.
+
+color.status.<slot>::
+       Use customized color for status colorization. `<slot>` is
+       one of `header` (the header text of the status message),
+       `added` or `updated` (files which are added but not committed),
+       `changed` (files which are changed but not added in the index),
+       or `untracked` (files which are not tracked by git). The values of
+       these variables may be specified as in color.diff.<slot>.
+
 diff.renameLimit::
        The number of files to consider when performing the copy/rename
        detection; equivalent to the git diff option '-l'.
@@ -219,6 +248,12 @@ i18n.commitEncoding::
        browser (and possibly at other places in the future or in other
        porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'.
 
+log.showroot::
+       If true, the initial commit will be shown as a big creation event.
+       This is equivalent to a diff against an empty tree.
+       Tools like gitlink:git-log[1] or gitlink:git-whatchanged[1], which
+       normally hide the root commit will now show it. True by default.
+
 merge.summary::
        Whether to include summaries of merged commits in newly created
        merge commit messages. False by default.
@@ -258,20 +293,6 @@ showbranch.default::
        The default set of branches for gitlink:git-show-branch[1].
        See gitlink:git-show-branch[1].
 
-status.color::
-       A boolean to enable/disable color in the output of
-       gitlink:git-status[1]. May be set to `true` (or `always`),
-       `false` (or `never`) or `auto`, in which case colors are used
-       only when the output is to a terminal. Defaults to false.
-
-status.color.<slot>::
-       Use customized color for status colorization. `<slot>` is
-       one of `header` (the header text of the status message),
-       `updated` (files which are updated but not committed),
-       `changed` (files which are changed but not updated in the index),
-       or `untracked` (files which are not tracked by git). The values of
-       these variables may be specified as in diff.color.<slot>.
-
 tar.umask::
        By default, gitlink:git-tar-tree[1] sets file and directories modes
        to 0666 or 0777. While this is both useful and acceptable for projects
index 47505aa20a56b4dc29fad86d66e8c6638edf03e0..5ea611748c6da80ee7df0387c5c5021a7095f45e 100644 (file)
@@ -57,7 +57,7 @@ $ git-init-db
 to which git will reply
 
 ----------------
-defaulting to local storage area
+Initialized empty Git repository in .git/
 ----------------
 
 which is just git's way of saying that you haven't been doing anything
@@ -336,17 +336,9 @@ $ commit=$(echo 'Initial commit' | git-commit-tree $tree)
 $ git-update-ref HEAD $commit
 ------------------------------------------------
 
-which will say:
-
-----------------
-Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb
-----------------
-
-just to warn you about the fact that it created a totally new commit
-that is not related to anything else. Normally you do this only *once*
-for a project ever, and all later commits will be parented on top of an
-earlier commit, and you'll never see this "Committing initial tree"
-message ever again.
+In this case this creates a totally new commit that is not related to
+anything else. Normally you do this only *once* for a project ever, and
+all later commits will be parented on top of an earlier commit.
 
 Again, normally you'd never actually do this by hand. There is a
 helpful script called `git commit` that will do all of this for you. So
index 6812683a16852ea165dff7dd0582b53138e3f12c..b657f4589f95257547b646ef22d3201fefbae6d6 100644 (file)
 git for CVS users
 =================
 
-So you're a CVS user. That's OK, it's a treatable condition.  The job of
-this document is to put you on the road to recovery, by helping you
-convert an existing cvs repository to git, and by showing you how to use a
-git repository in a cvs-like fashion.
+Git differs from CVS in that every working tree contains a repository with
+a full copy of the project history, and no repository is inherently more
+important than any other.  However, you can emulate the CVS model by
+designating a single shared repository which people can synchronize with;
+this document explains how to do that.
 
 Some basic familiarity with git is required.  This
 link:tutorial.html[tutorial introduction to git] should be sufficient.
 
-First, note some ways that git differs from CVS:
+Developing against a shared repository
+--------------------------------------
 
-  * Commits are atomic and project-wide, not per-file as in CVS.
-
-  * Offline work is supported: you can make multiple commits locally,
-    then submit them when you're ready.
-
-  * Branching is fast and easy.
-
-  * Every working tree contains a repository with a full copy of the
-    project history, and no repository is inherently more important than
-    any other.  However, you can emulate the CVS model by designating a
-    single shared repository which people can synchronize with; see below
-    for details.
-
-Importing a CVS archive
------------------------
-
-First, install version 2.1 or higher of cvsps from
-link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
-sure it is in your path.  The magic command line is then
-
--------------------------------------------
-$ git cvsimport -v -d <cvsroot> -C <destination> <module>
--------------------------------------------
-
-This puts a git archive of the named CVS module in the directory
-<destination>, which will be created if necessary.  The -v option makes
-the conversion script very chatty.
-
-The import checks out from CVS every revision of every file.  Reportedly
-cvsimport can average some twenty revisions per second, so for a
-medium-sized project this should not take more than a couple of minutes.
-Larger projects or remote repositories may take longer.
-
-The main trunk is stored in the git branch named `origin`, and additional
-CVS branches are stored in git branches with the same names.  The most
-recent version of the main trunk is also left checked out on the `master`
-branch, so you can start adding your own changes right away.
-
-The import is incremental, so if you call it again next month it will
-fetch any CVS updates that have been made in the meantime.  For this to
-work, you must not modify the imported branches; instead, create new
-branches for your own changes, and merge in the imported branches as
-necessary.
-
-Development Models
-------------------
-
-CVS users are accustomed to giving a group of developers commit access to
-a common repository.  In the next section we'll explain how to do this
-with git.  However, the distributed nature of git allows other development
-models, and you may want to first consider whether one of them might be a
-better fit for your project.
-
-For example, you can choose a single person to maintain the project's
-primary public repository.  Other developers then clone this repository
-and each work in their own clone.  When they have a series of changes that
-they're happy with, they ask the maintainer to pull from the branch
-containing the changes.  The maintainer reviews their changes and pulls
-them into the primary repository, which other developers pull from as
-necessary to stay coordinated.  The Linux kernel and other projects use
-variants of this model.
-
-With a small group, developers may just pull changes from each other's
-repositories without the need for a central maintainer.
-
-Emulating the CVS Development Model
------------------------------------
-
-Start with an ordinary git working directory containing the project, and
-remove the checked-out files, keeping just the bare .git directory:
-
-------------------------------------------------
-$ mv project/.git /pub/repo.git
-$ rm -r project/
-------------------------------------------------
-
-Next, give every team member read/write access to this repository.  One
-easy way to do this is to give all the team members ssh access to the
-machine where the repository is hosted.  If you don't want to give them a
-full shell on the machine, there is a restricted shell which only allows
-users to do git pushes and pulls; see gitlink:git-shell[1].
-
-Put all the committers in the same group, and make the repository
-writable by that group:
-
-------------------------------------------------
-$ chgrp -R $group repo.git
-$ find repo.git -mindepth 1 -type d |xargs chmod ug+rwx,g+s
-$ GIT_DIR=repo.git git repo-config core.sharedrepository true
-------------------------------------------------
-
-Make sure committers have a umask of at most 027, so that the directories
-they create are writable and searchable by other group members.
-
-Suppose this repository is now set up in /pub/repo.git on the host
+Suppose a shared repository is set up in /pub/repo.git on the host
 foo.com.  Then as an individual committer you can clone the shared
-repository:
+repository over ssh with:
 
 ------------------------------------------------
 $ git clone foo.com:/pub/repo.git/ my-project
@@ -121,7 +29,8 @@ $ git pull origin
 ------------------------------------------------
 
 which merges in any work that others might have done since the clone
-operation.
+operation.  If there are uncommitted changes in your working tree, commit
+them first before running git pull.
 
 [NOTE]
 ================================
@@ -129,20 +38,22 @@ The first `git clone` places the following in the
 `my-project/.git/remotes/origin` file, and that's why the previous step
 and the next step both work.
 ------------
-URL: foo.com:/pub/project.git/ my-project
-Pull: master:origin
+URL: foo.com:/pub/project.git/
+Pull: refs/heads/master:refs/remotes/origin/master
 ------------
 ================================
 
-You can update the shared repository with your changes using:
+You can update the shared repository with your changes by first committing
+your changes, and then using the gitlink:git-push[1] command:
 
 ------------------------------------------------
 $ git push origin master
 ------------------------------------------------
 
-If someone else has updated the repository more recently, `git push`, like
-`cvs commit`, will complain, in which case you must pull any changes
-before attempting the push again.
+to "push" those commits to the shared repository.  If someone else has
+updated the repository more recently, `git push`, like `cvs commit`, will
+complain, in which case you must pull any changes before attempting the
+push again.
 
 In the `git push` command above we specify the name of the remote branch
 to update (`master`).  If we leave that out, `git push` tries to update
@@ -151,21 +62,77 @@ in the local repository.  So the last `push` can be done with either of:
 
 ------------
 $ git push origin
-$ git push repo.shared.xz:/pub/scm/project.git/
+$ git push foo.com:/pub/project.git/
 ------------
 
 as long as the shared repository does not have any branches
 other than `master`.
 
-[NOTE]
-============
-Because of this behavior, if the shared repository and the developer's
-repository both have branches named `origin`, then a push like the above
-attempts to update the `origin` branch in the shared repository from the
-developer's `origin` branch.  The results may be unexpected, so it's
-usually best to remove any branch named `origin` from the shared
-repository.
-============
+Setting Up a Shared Repository
+------------------------------
+
+We assume you have already created a git repository for your project,
+possibly created from scratch or from a tarball (see the
+link:tutorial.html[tutorial]), or imported from an already existing CVS
+repository (see the next section).
+
+Assume your existing repo is at /home/alice/myproject.  Create a new "bare"
+repository (a repository without a working tree) and fetch your project into
+it:
+
+------------------------------------------------
+$ mkdir /pub/my-repo.git
+$ cd /pub/my-repo.git
+$ git --bare init-db --shared
+$ git --bare fetch /home/alice/myproject master:master
+------------------------------------------------
+
+Next, give every team member read/write access to this repository.  One
+easy way to do this is to give all the team members ssh access to the
+machine where the repository is hosted.  If you don't want to give them a
+full shell on the machine, there is a restricted shell which only allows
+users to do git pushes and pulls; see gitlink:git-shell[1].
+
+Put all the committers in the same group, and make the repository
+writable by that group:
+
+------------------------------------------------
+$ chgrp -R $group /pub/my-repo.git
+------------------------------------------------
+
+Make sure committers have a umask of at most 027, so that the directories
+they create are writable and searchable by other group members.
+
+Importing a CVS archive
+-----------------------
+
+First, install version 2.1 or higher of cvsps from
+link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
+sure it is in your path.  Then cd to a checked out CVS working directory
+of the project you are interested in and run gitlink:git-cvsimport[1]:
+
+-------------------------------------------
+$ git cvsimport -C <destination>
+-------------------------------------------
+
+This puts a git archive of the named CVS module in the directory
+<destination>, which will be created if necessary.
+
+The import checks out from CVS every revision of every file.  Reportedly
+cvsimport can average some twenty revisions per second, so for a
+medium-sized project this should not take more than a couple of minutes.
+Larger projects or remote repositories may take longer.
+
+The main trunk is stored in the git branch named `origin`, and additional
+CVS branches are stored in git branches with the same names.  The most
+recent version of the main trunk is also left checked out on the `master`
+branch, so you can start adding your own changes right away.
+
+The import is incremental, so if you call it again next month it will
+fetch any CVS updates that have been made in the meantime.  For this to
+work, you must not modify the imported branches; instead, create new
+branches for your own changes, and merge in the imported branches as
+necessary.
 
 Advanced Shared Repository Management
 -------------------------------------
@@ -178,127 +145,30 @@ You can enforce finer grained permissions using update hooks.  See
 link:howto/update-hook-example.txt[Controlling access to branches using
 update hooks].
 
-CVS annotate
-------------
+Providing CVS Access to a git Repository
+----------------------------------------
+
+It is also possible to provide true CVS access to a git repository, so
+that developers can still use CVS; see gitlink:git-cvsserver[1] for
+details.
+
+Alternative Development Models
+------------------------------
+
+CVS users are accustomed to giving a group of developers commit access to
+a common repository.  As we've seen, this is also possible with git.
+However, the distributed nature of git allows other development models,
+and you may want to first consider whether one of them might be a better
+fit for your project.
+
+For example, you can choose a single person to maintain the project's
+primary public repository.  Other developers then clone this repository
+and each work in their own clone.  When they have a series of changes that
+they're happy with, they ask the maintainer to pull from the branch
+containing the changes.  The maintainer reviews their changes and pulls
+them into the primary repository, which other developers pull from as
+necessary to stay coordinated.  The Linux kernel and other projects use
+variants of this model.
 
-So, something has gone wrong, and you don't know whom to blame, and
-you're an ex-CVS user and used to do "cvs annotate" to see who caused
-the breakage. You're looking for the "git annotate", and it's just
-claiming not to find such a script. You're annoyed.
-
-Yes, that's right.  Core git doesn't do "annotate", although it's
-technically possible, and there are at least two specialized scripts out
-there that can be used to get equivalent information (see the git
-mailing list archives for details). 
-
-git has a couple of alternatives, though, that you may find sufficient
-or even superior depending on your use.  One is called "git-whatchanged"
-(for obvious reasons) and the other one is called "pickaxe" ("a tool for
-the software archaeologist"). 
-
-The "git-whatchanged" script is a truly trivial script that can give you
-a good overview of what has changed in a file or a directory (or an
-arbitrary list of files or directories).  The "pickaxe" support is an
-additional layer that can be used to further specify exactly what you're
-looking for, if you already know the specific area that changed.
-
-Let's step back a bit and think about the reason why you would
-want to do "cvs annotate a-file.c" to begin with.
-
-You would use "cvs annotate" on a file when you have trouble
-with a function (or even a single "if" statement in a function)
-that happens to be defined in the file, which does not do what
-you want it to do.  And you would want to find out why it was
-written that way, because you are about to modify it to suit
-your needs, and at the same time you do not want to break its
-current callers.  For that, you are trying to find out why the
-original author did things that way in the original context.
-
-Many times, it may be enough to see the commit log messages of
-commits that touch the file in question, possibly along with the
-patches themselves, like this:
-
-       $ git-whatchanged -p a-file.c
-
-This will show log messages and patches for each commit that
-touches a-file.
-
-This, however, may not be very useful when this file has many
-modifications that are not related to the piece of code you are
-interested in.  You would see many log messages and patches that
-do not have anything to do with the piece of code you are
-interested in.  As an example, assuming that you have this piece
-of code that you are interested in in the HEAD version:
-
-       if (frotz) {
-               nitfol();
-       }
-
-you would use git-rev-list and git-diff-tree like this:
-
-       $ git-rev-list HEAD |
-         git-diff-tree --stdin -v -p -S'if (frotz) {
-               nitfol();
-       }'
-
-We have already talked about the "\--stdin" form of git-diff-tree
-command that reads the list of commits and compares each commit
-with its parents (otherwise you should go back and read the tutorial).
-The git-whatchanged command internally runs
-the equivalent of the above command, and can be used like this:
-
-       $ git-whatchanged -p -S'if (frotz) {
-               nitfol();
-       }'
-
-When the -S option is used, git-diff-tree command outputs
-differences between two commits only if one tree has the
-specified string in a file and the corresponding file in the
-other tree does not.  The above example looks for a commit that
-has the "if" statement in it in a file, but its parent commit
-does not have it in the same shape in the corresponding file (or
-the other way around, where the parent has it and the commit
-does not), and the differences between them are shown, along
-with the commit message (thanks to the -v flag).  It does not
-show anything for commits that do not touch this "if" statement.
-
-Also, in the original context, the same statement might have
-appeared at first in a different file and later the file was
-renamed to "a-file.c".  CVS annotate would not help you to go
-back across such a rename, but git would still help you in such
-a situation.  For that, you can give the -C flag to
-git-diff-tree, like this:
-
-       $ git-whatchanged -p -C -S'if (frotz) {
-               nitfol();
-       }'
-
-When the -C flag is used, file renames and copies are followed.
-So if the "if" statement in question happens to be in "a-file.c"
-in the current HEAD commit, even if the file was originally
-called "o-file.c" and then renamed in an earlier commit, or if
-the file was created by copying an existing "o-file.c" in an
-earlier commit, you will not lose track.  If the "if" statement
-did not change across such a rename or copy, then the commit that
-does rename or copy would not show in the output, and if the
-"if" statement was modified while the file was still called
-"o-file.c", it would find the commit that changed the statement
-when it was in "o-file.c".
-
-NOTE: The current version of "git-diff-tree -C" is not eager
-  enough to find copies, and it will miss the fact that a-file.c
-  was created by copying o-file.c unless o-file.c was somehow
-  changed in the same commit.
-
-You can use the --pickaxe-all flag in addition to the -S flag.
-This causes the differences from all the files contained in
-those two commits, not just the differences between the files
-that contain this changed "if" statement:
-
-       $ git-whatchanged -p -C -S'if (frotz) {
-               nitfol();
-       }' --pickaxe-all
-
-NOTE: This option is called "--pickaxe-all" because -S
-  option is internally called "pickaxe", a tool for software
-  archaeologists.
+With a small group, developers may just pull changes from each other's
+repositories without the need for a central maintainer.
index e4520e28e53661159454e02c703be772d43bbc09..883c1bb0a638d97278cdb66e288bc766a32626af 100644 (file)
@@ -65,62 +65,17 @@ Generating patches with -p
 
 When "git-diff-index", "git-diff-tree", or "git-diff-files" are run
 with a '-p' option, they do not produce the output described above;
-instead they produce a patch file.
+instead they produce a patch file.  You can customize the creation
+of such patches via the GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS
+environment variables.
 
-The patch generation can be customized at two levels.
-
-1. When the environment variable 'GIT_EXTERNAL_DIFF' is not set,
-   these commands internally invoke "diff" like this:
-
-      diff -L a/<path> -L b/<path> -pu <old> <new>
-+
-For added files, `/dev/null` is used for <old>.  For removed
-files, `/dev/null` is used for <new>
-+
-The "diff" formatting options can be customized via the
-environment variable 'GIT_DIFF_OPTS'.  For example, if you
-prefer context diff:
-
-      GIT_DIFF_OPTS=-c git-diff-index -p HEAD
-
-
-2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
-   program named by it is called, instead of the diff invocation
-   described above.
-+
-For a path that is added, removed, or modified,
-'GIT_EXTERNAL_DIFF' is called with 7 parameters:
-
-     path old-file old-hex old-mode new-file new-hex new-mode
-+
-where:
-
-     <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
-                     contents of <old|new>,
-     <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
-     <old|new>-mode:: are the octal representation of the file modes.
-
-+ 
-The file parameters can point at the user's working file
-(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
-when a new file is added), or a temporary file (e.g. `old-file` in the
-index).  'GIT_EXTERNAL_DIFF' should not worry about unlinking the
-temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits.
-
-For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
-parameter, <path>.
-
-
-git specific extension to diff format
--------------------------------------
-
-What -p option produces is slightly different from the
-traditional diff format.
+What the -p option produces is slightly different from the traditional
+diff format.
 
 1.   It is preceded with a "git diff" header, that looks like
      this:
 
-     diff --git a/file1 b/file2
+       diff --git a/file1 b/file2
 +
 The `a/` and `b/` filenames are the same unless rename/copy is
 involved.  Especially, even for a creation or a deletion,
index e112172ca57da75ce6c2dd447cc97c6aa2b6499e..f12082e134871c37c92a06c68430de2ef2a0413b 100644 (file)
        deleted lines in decimal notation and pathname without
        abbreviation, to make it more machine friendly.
 
+--shortstat::
+       Output only the last line of the --stat format containing total
+       number of modified files, as well as number of added and deleted
+       lines.
+
 --summary::
        Output a condensed summary of extended header information
        such as creations, renames and mode changes.
 -a::
        Shorthand for "--text".
 
+--ignore-space-change::
+       Ignore changes in amount of white space.  This ignores white
+       space at line end, and consider all other sequences of one or
+       more white space characters to be equivalent.
+
+-b::
+       Shorthand for "--ignore-space-change".
+
+--ignore-all-space::
+       Ignore white space when comparing lines.  This ignores
+       difference even if one line has white space where the other
+       line has none.
+
+-w::
+       Shorthand for "--ignore-all-space".
+
 For more detailed explanation on these common options, see also
 link:diffcore.html[diffcore documentation].
index 6342ea33e4a34f19ca04c79157d80cb230c15f5c..d86c0e7f19ee67556b48bb7f94ab9039a0195ea6 100644 (file)
@@ -3,7 +3,7 @@ git-add(1)
 
 NAME
 ----
-git-add - Add files to the index file
+git-add - Add file contents to the changeset to be committed next
 
 SYNOPSIS
 --------
@@ -11,16 +11,31 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-A simple wrapper for git-update-index to add files to the index,
-for people used to do "cvs add".
+All the changed file contents to be committed together in a single set
+of changes must be "added" with the 'add' command before using the
+'commit' command.  This is not only for adding new files.  Even modified
+files must be added to the set of changes about to be committed.
 
-It only adds non-ignored files, to add ignored files use
+This command can be performed multiple times before a commit. The added
+content corresponds to the state of specified file(s) at the time the
+'add' command is used. This means the 'commit' command will not consider
+subsequent changes to already added content if it is not added again before
+the commit.
+
+The 'git status' command can be used to obtain a summary of what is included
+for the next commit.
+
+This command only adds non-ignored files, to add ignored files use
 "git update-index --add".
 
+Please see gitlink:git-commit[1] for alternative ways to add content to a
+commit.
+
+
 OPTIONS
 -------
 <file>...::
-       Files to add to the index (see gitlink:git-ls-files[1]).
+       Files to add content from.
 
 -n::
         Don't actually add the file(s), just show if they exist.
@@ -34,27 +49,12 @@ OPTIONS
        for command-line options).
 
 
-DISCUSSION
-----------
-
-The list of <file> given to the command is fed to `git-ls-files`
-command to list files that are not registered in the index and
-are not ignored/excluded by `$GIT_DIR/info/exclude` file or
-`.gitignore` file in each directory.  This means two things:
-
-. You can put the name of a directory on the command line, and
-  the command will add all files in it and its subdirectories;
-
-. Giving the name of a file that is already in index does not
-  run `git-update-index` on that path.
-
-
 EXAMPLES
 --------
 git-add Documentation/\\*.txt::
 
-       Adds all `\*.txt` files that are not in the index under
-       `Documentation` directory and its subdirectories.
+       Adds content from all `\*.txt` files under `Documentation`
+       directory and its subdirectories.
 +
 Note that the asterisk `\*` is quoted from the shell in this
 example; this lets the command to include the files from
@@ -62,15 +62,18 @@ subdirectories of `Documentation/` directory.
 
 git-add git-*.sh::
 
-       Adds all git-*.sh scripts that are not in the index.
+       Considers adding content from all git-*.sh scripts.
        Because this example lets shell expand the asterisk
        (i.e. you are listing the files explicitly), it does not
-       add `subdir/git-foo.sh` to the index.
+       consider `subdir/git-foo.sh`.
 
 See Also
 --------
+gitlink:git-status[1]
 gitlink:git-rm[1]
-gitlink:git-ls-files[1]
+gitlink:git-mv[1]
+gitlink:git-commit[1]
+gitlink:git-update-index[1]
 
 Author
 ------
index d43ef1dec4f27361af1a32983c53aeea15468fe9..c464bd2fda2e4828d2e5a36ca05b4c66ad592f7c 100644 (file)
@@ -8,23 +8,33 @@ git-branch - List, create, or delete branches.
 SYNOPSIS
 --------
 [verse]
-'git-branch' [-r]
+'git-branch' [-r | -a] [-v [--abbrev=<length>]]
 'git-branch' [-l] [-f] <branchname> [<start-point>]
-'git-branch' (-d | -D) <branchname>...
+'git-branch' (-m | -M) [<oldbranch>] <newbranch>
+'git-branch' (-d | -D) [-r] <branchname>...
 
 DESCRIPTION
 -----------
-With no arguments given (or just `-r`) a list of available branches
+With no arguments given a list of existing branches
 will be shown, the current branch will be highlighted with an asterisk.
+Option `-r` causes the remote-tracking branches to be listed,
+and option `-a` shows both.
 
 In its second form, a new branch named <branchname> will be created.
 It will start out with a head equal to the one given as <start-point>.
 If no <start-point> is given, the branch will be created with a head
 equal to that of the currently checked out branch.
 
+With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
+If <oldbranch> had a corresponding reflog, it is renamed to match
+<newbranch>, and a reflog entry is created to remember the branch
+renaming. If <newbranch> exists, -M must be used to force the rename
+to happen.
+
 With a `-d` or `-D` option, `<branchname>` will be deleted.  You may
 specify more than one branch for deletion.  If the branch currently
-has a ref log then the ref log will also be deleted.
+has a ref log then the ref log will also be deleted. Use -r together with -d
+to delete remote-tracking branches.
 
 
 OPTIONS
@@ -44,8 +54,24 @@ OPTIONS
        Force the creation of a new branch even if it means deleting
        a branch that already exists with the same name.
 
+-m::
+       Move/rename a branch and the corresponding reflog.
+
+-M::
+       Move/rename a branch even if the new branchname already exists.
+
 -r::
-       List only the "remote" branches.
+       List or delete (if used with -d) the remote-tracking branches.
+
+-a::
+       List both remote-tracking branches and local branches.
+
+-v::
+       Show sha1 and commit subjectline for each head.
+
+--abbrev=<length>::
+       Alter minimum display length for sha1 in output listing,
+       default value is 7.
 
 <branchname>::
        The name of the branch to create or delete.
@@ -58,6 +84,12 @@ OPTIONS
        be given as a branch name, a commit-id, or a tag.  If this option
        is omitted, the current branch is assumed.
 
+<oldbranch>::
+       The name of an existing branch to rename.
+
+<newbranch>::
+       The new name for an existing branch. The same restrictions as for
+       <branchname> applies.
 
 
 Examples
@@ -80,10 +112,12 @@ Delete unneeded branch::
 ------------
 $ git clone git://git.kernel.org/.../git.git my.git
 $ cd my.git
-$ git branch -D todo    <1>
+$ git branch -d -r todo html man   <1>
+$ git branch -D test               <2>
 ------------
 +
-<1> delete todo branch even if the "master" branch does not have all
+<1> delete remote-tracking branches "todo", "html", "man"
+<2> delete "test" branch even if the "master" branch does not have all
 commits from todo branch.
 
 
index 86060472ad88088bcb59df5eb9acce097f68cb83..874934a33243106544181ffeae2fee8b03ff5e8d 100644 (file)
@@ -11,26 +11,25 @@ SYNOPSIS
 [verse]
 'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
          [-o <name>] [-u <upload-pack>] [--reference <repository>]
-         [--use-separate-remote] <repository> [<directory>]
+         <repository> [<directory>]
 
 DESCRIPTION
 -----------
-Clones a repository into a newly created directory.  All remote
-branch heads are copied under `$GIT_DIR/refs/heads/`, except
-that the remote `master` is also copied to `origin` branch.
 
-In addition, `$GIT_DIR/remotes/origin` file is set up to have
-this line:
+Clones a repository into a newly created directory, creates
+remote-tracking branches for each branch in the cloned repository
+(visible using `git branch -r`), and creates and checks out a master
+branch equal to the cloned repository's master branch.
 
-       Pull: master:origin
-
-This is to help the typical workflow of working off of the
-remote `master` branch.  Every time `git pull` without argument
-is run, the progress on the remote `master` branch is tracked by
-copying it into the local `origin` branch, and merged into the
-branch you are currently working on.  Remote branches other than
-`master` are also added there to be tracked.
+After the clone, a plain `git fetch` without arguments will update
+all the remote-tracking branches, and a `git pull` without
+arguments will in addition merge the remote master branch into the
+current branch.
 
+This default configuration is achieved by creating references to
+the remote branch heads under `$GIT_DIR/refs/remotes/origin` and
+by initializing `remote.origin.url` and `remote.origin.fetch`
+configuration variables.
 
 OPTIONS
 -------
@@ -71,9 +70,13 @@ OPTIONS
        Make a 'bare' GIT repository.  That is, instead of
        creating `<directory>` and placing the administrative
        files in `<directory>/.git`, make the `<directory>`
-       itself the `$GIT_DIR`. This implies `-n` option.  When
-       this option is used, neither the `origin` branch nor the
-       default `remotes/origin` file is created.
+       itself the `$GIT_DIR`. This obviously implies the `-n`
+       because there is nowhere to check out the working tree.
+       Also the branch heads at the remote are copied directly
+       to corresponding local branch heads, without mapping
+       them to `refs/remotes/origin/`.  When this option is
+       used, neither the `origin` branch nor the default
+       `remotes/origin` file is created.
 
 --origin <name>::
 -o <name>::
@@ -95,11 +98,6 @@ OPTIONS
        if unset the templates are taken from the installation
        defined default, typically `/usr/share/git-core/templates`.
 
---use-separate-remote::
-       Save remotes heads under `$GIT_DIR/remotes/origin/` instead
-       of `$GIT_DIR/refs/heads/`.  Only the master branch is saved
-       in the latter.
-
 <repository>::
        The (possibly remote) repository to clone from.  It can
        be any URL git-fetch supports.
index 517a86b238a91a1c853b5fbe331a5cc984e95878..0b74cd708ecaa63ccc19d2db40790b315539ce8b 100644 (file)
@@ -14,25 +14,41 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Updates the index file for given paths, or all modified files if
-'-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.
+Use 'git commit' when you want to record your changes into the repository
+along with a log message describing what the commit is about. All changes
+to be committed must be explicitly identified using one of the following
+methods:
 
-Several environment variable are used during commits.  They are
-documented in gitlink:git-commit-tree[1].
+1. by using gitlink:git-add[1] to incrementally "add" changes to the
+   next commit before using the 'commit' command (Note: even modified
+   files must be "added");
 
+2. by using gitlink:git-rm[1] to identify content removal for the next
+   commit, again before using the 'commit' command;
+
+3. by directly listing files containing changes to be committed as arguments
+   to the 'commit' command, in which cases only those files alone will be
+   considered for the commit;
+
+4. by using the -a switch with the 'commit' command to automatically "add"
+   changes from all known files i.e. files that have already been committed
+   before, and perform the actual commit.
+
+The gitlink:git-status[1] command can be used to obtain a
+summary of what is included by any of the above for the next
+commit by giving the same set of parameters you would give to
+this command.
+
+If you make a commit and then found a mistake immediately after
+that, you can recover from it with gitlink:git-reset[1].
 
-This command can run `commit-msg`, `pre-commit`, and
-`post-commit` hooks.  See link:hooks.html[hooks] for more
-information.
 
 OPTIONS
 -------
 -a|--all::
-       Update all paths in the index file.  This flag notices
-       files that have been modified and deleted, but new files
-       you have not told git about are not affected.
+       Tell the command to automatically stage files that have
+       been modified and deleted, but new files you have not
+       told git about are not affected.
 
 -c or -C <commit>::
        Take existing commit object, and reuse the log message
@@ -55,16 +71,13 @@ OPTIONS
 -s|--signoff::
        Add Signed-off-by line at the end of the commit message.
 
--v|--verify::
-       Look for suspicious lines the commit introduces, and
-       abort committing if there is one.  The definition of
-       'suspicious lines' is currently the lines that has
-       trailing whitespaces, and the lines whose indentation
-       has a SP character immediately followed by a TAB
-       character.  This is the default.
-
--n|--no-verify::
-       The opposite of `--verify`.
+--no-verify::
+       By default, the command looks for suspicious lines the
+       commit introduces, and aborts committing if there is one.
+       The definition of 'suspicious lines' is currently the
+       lines that has trailing whitespaces, and the lines whose
+       indentation has a SP character immediately followed by a
+       TAB character.  This option turns off the check.
 
 -e|--edit::
        The message taken from file with `-F`, command line with
@@ -95,69 +108,140 @@ but can be used to amend a merge commit.
 --
 
 -i|--include::
-       Instead of committing only the files specified on the
-       command line, update them in the index file and then
-       commit the whole index.  This is the traditional
-       behavior.
+       Before making a commit out of staged contents so far,
+       stage the contents of paths given on the command line
+       as well.  This is usually not what you want unless you
+       are concluding a conflicted merge.
 
--o|--only::
-       Commit only the files specified on the command line.
-       This format cannot be used during a merge, nor when the
-       index and the latest commit does not match on the
-       specified paths to avoid confusion.
+-q|--quiet::
+       Supress commit summary message.
 
 \--::
        Do not interpret any more arguments as options.
 
 <file>...::
-       Files to be committed.  The meaning of these is
-       different between `--include` and `--only`.  Without
-       either, it defaults `--only` semantics.
-
-If you make a commit and then found a mistake immediately after
-that, you can recover from it with gitlink:git-reset[1].
+       When files are given on the command line, the command
+       commits the contents of the named files, without
+       recording the changes already staged.  The contents of
+       these files are also staged for the next commit on top
+       of what have been staged before.
 
 
-Discussion
-----------
-
-`git commit` without _any_ parameter commits the tree structure
-recorded by the current index file.  This is a whole-tree commit
-even the command is invoked from a subdirectory.
-
-`git commit --include paths...` is equivalent to
-
-       git update-index --remove paths...
-       git commit
-
-That is, update the specified paths to the index and then commit
-the whole tree.
-
-`git commit paths...` largely bypasses the index file and
-commits only the changes made to the specified paths.  It has
-however several safety valves to prevent confusion.
-
-. It refuses to run during a merge (i.e. when
-  `$GIT_DIR/MERGE_HEAD` exists), and reminds trained git users
-  that the traditional semantics now needs -i flag.
-
-. It refuses to run if named `paths...` are different in HEAD
-  and the index (ditto about reminding).  Added paths are OK.
-  This is because an earlier `git diff` (not `git diff HEAD`)
-  would have shown the differences since the last `git
-  update-index paths...` to the user, and an inexperienced user
-  may mistakenly think that the changes between the index and
-  the HEAD (i.e. earlier changes made before the last `git
-  update-index paths...` was done) are not being committed.
-
-. It reads HEAD commit into a temporary index file, updates the
-  specified `paths...` and makes a commit.  At the same time,
-  the real index file is also updated with the same `paths...`.
+EXAMPLES
+--------
+When recording your own work, the contents of modified files in
+your working tree are temporarily stored to a staging area
+called the "index" with gitlink:git-add[1].  Removal
+of a file is staged with gitlink:git-rm[1].  After building the
+state to be committed incrementally with these commands, `git
+commit` (without any pathname parameter) is used to record what
+has been staged so far.  This is the most basic form of the
+command.  An example:
+
+------------
+$ edit hello.c
+$ git rm goodbye.c
+$ git add hello.c
+$ git commit
+------------
+
+////////////
+We should fix 'git rm' to remove goodbye.c from both index and
+working tree for the above example.
+////////////
+
+Instead of staging files after each individual change, you can
+tell `git commit` to notice the changes to the files whose
+contents are tracked in
+your working tree and do corresponding `git add` and `git rm`
+for you.  That is, this example does the same as the earlier
+example if there is no other change in your working tree:
+
+------------
+$ edit hello.c
+$ rm goodbye.c
+$ git commit -a
+------------
+
+The command `git commit -a` first looks at your working tree,
+notices that you have modified hello.c and removed goodbye.c,
+and performs necessary `git add` and `git rm` for you.
+
+After staging changes to many files, you can alter the order the
+changes are recorded in, by giving pathnames to `git commit`.
+When pathnames are given, the command makes a commit that
+only records the changes made to the named paths:
+
+------------
+$ edit hello.c hello.h
+$ git add hello.c hello.h
+$ edit Makefile
+$ git commit Makefile
+------------
+
+This makes a commit that records the modification to `Makefile`.
+The changes staged for `hello.c` and `hello.h` are not included
+in the resulting commit.  However, their changes are not lost --
+they are still staged and merely held back.  After the above
+sequence, if you do:
+
+------------
+$ git commit
+------------
+
+this second commit would record the changes to `hello.c` and
+`hello.h` as expected.
+
+After a merge (initiated by either gitlink:git-merge[1] or
+gitlink:git-pull[1]) stops because of conflicts, cleanly merged
+paths are already staged to be committed for you, and paths that
+conflicted are left in unmerged state.  You would have to first
+check which paths are conflicting with gitlink:git-status[1]
+and after fixing them manually in your working tree, you would
+stage the result as usual with gitlink:git-add[1]:
+
+------------
+$ git status | grep unmerged
+unmerged: hello.c
+$ edit hello.c
+$ git add hello.c
+------------
+
+After resolving conflicts and staging the result, `git ls-files -u`
+would stop mentioning the conflicted path.  When you are done,
+run `git commit` to finally record the merge:
+
+------------
+$ git commit
+------------
+
+As with the case to record your own changes, you can use `-a`
+option to save typing.  One difference is that during a merge
+resolution, you cannot use `git commit` with pathnames to
+alter the order the changes are committed, because the merge
+should be recorded as a single commit.  In fact, the command
+refuses to run when given pathnames (but see `-i` option).
+
+
+ENVIRONMENT VARIABLES
+---------------------
+The command specified by either the VISUAL or EDITOR environment
+variables is used to edit the commit log message.
+
+HOOKS
+-----
+This command can run `commit-msg`, `pre-commit`, and
+`post-commit` hooks.  See link:hooks.html[hooks] for more
+information.
 
-`git commit --all` updates the index file with _all_ changes to
-the working tree, and makes a whole-tree commit, regardless of
-which subdirectory the command is invoked in.
 
+SEE ALSO
+--------
+gitlink:git-add[1],
+gitlink:git-rm[1],
+gitlink:git-mv[1],
+gitlink:git-merge[1],
+gitlink:git-commit-tree[1]
 
 Author
 ------
index 228c4d95bd41d40bbb32cfcb6b9a7dc648280fa5..8977877b21b6681528d13f71b558c060e8c26c4d 100644 (file)
@@ -8,36 +8,54 @@ git-diff - Show changes between commits, commit and working tree, etc
 
 SYNOPSIS
 --------
-'git-diff' [ --diff-options ] <tree-ish>{0,2} [<path>...]
+'git-diff' [ --diff-options ] <commit>{0,2} [--] [<path>...]
 
 DESCRIPTION
 -----------
 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 trees given to the command.
 
-* When no <tree-ish> is given, the working tree and the index
-  file are compared, using `git-diff-files`.
+'git-diff' [--options] [--] [<path>...]::
 
-* 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.
+       This form is to view the changes you made relative to
+       the index (staging area for the next commit).  In other
+       words, the differences are what you _could_ tell git to
+       further add to the index but you still haven't.  You can
+       stage these changes by using gitlink:git-add[1].
+
+'git-diff' [--options] --cached [<commit>] [--] [<path>...]::
+
+       This form is to view the changes you staged for the next
+       commit relative to the named <commit>.  Typically you
+       would want comparison with the latest commit, so if you
+       do not give <commit>, it defaults to HEAD.
+
+'git-diff' [--options] <commit> [--] [<path>...]::
+
+       This form is to view the changes you have in your
+       working tree relative to the named <commit>.  You can
+       use HEAD to compare it with the latest commit, or a
+       branch name to compare with the tip of a different
+       branch.
+
+'git-diff' [--options] <commit> <commit> [--] [<path>...]::
+
+       This form is to view the changes between two <commit>,
+       for example, tips of two branches.
+
+Just in case if you are doing something exotic, it should be
+noted that all of the <commit> in the above description can be
+any <tree-ish>.
 
-* When two <tree-ish>s are given, these two trees are compared
-  using `git-diff-tree`.
 
 OPTIONS
 -------
---diff-options::
-       '--diff-options' are passed to the `git-diff-files`,
-       `git-diff-index`, and `git-diff-tree` commands.  See the
-       documentation for these commands for description.
+include::diff-options.txt[]
 
 <path>...::
-       The <path> arguments are also passed to `git-diff-\*`
-       commands.
+       The <paths> parameters, when given, are used to limit
+       the diff to the named paths (you can give directory
+       names and get diff for all files under them).
 
 
 EXAMPLES
@@ -51,7 +69,7 @@ $ git diff --cached   <2>
 $ git diff HEAD       <3>
 ------------
 +
-<1> changes in the working tree since your last git-update-index.
+<1> changes in the working tree not yet staged for the next commit.
 <2> changes between the index and your last commit; what you
 would be committing if you run "git commit" without "-a" option.
 <3> changes in the working tree since your last commit; what you
diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt
new file mode 100644 (file)
index 0000000..29d3faa
--- /dev/null
@@ -0,0 +1,92 @@
+git-merge-file(1)
+=================
+
+NAME
+----
+git-merge-file - three-way file merge
+
+
+SYNOPSIS
+--------
+[verse]
+'git-merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]]
+       [-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file>
+
+
+DESCRIPTION
+-----------
+git-file-merge incorporates all changes that lead from the `<base-file>`
+to `<other-file>` into `<current-file>`. The result ordinarily goes into
+`<current-file>`. git-merge-file is useful for combining separate changes
+to an original. Suppose `<base-file>` is the original, and both
+`<current-file>` and `<other-file>` are modifications of `<base-file>`.
+Then git-merge-file combines both changes.
+
+A conflict occurs if both `<current-file>` and `<other-file>` have changes
+in a common segment of lines. If a conflict is found, git-merge-file
+normally outputs a warning and brackets the conflict with <<<<<<< and
+>>>>>>> lines. A typical conflict will look like this:
+
+       <<<<<<< A
+       lines in file A
+       =======
+       lines in file B
+       >>>>>>> B
+
+If there are conflicts, the user should edit the result and delete one of
+the alternatives.
+
+The exit value of this program is negative on error, and the number of
+conflicts otherwise. If the merge was clean, the exit value is 0.
+
+git-merge-file is designed to be a minimal clone of RCS merge, that is, it
+implements all of RCS merge's functionality which is needed by
+gitlink:git[1].
+
+
+OPTIONS
+-------
+
+-L <label>::
+       This option may be given up to three times, and
+       specifies labels to be used in place of the
+       corresponding file names in conflict reports. That is,
+       `git-merge-file -L x -L y -L z a b c` generates output that
+       looks like it came from files x, y and z instead of
+       from files a, b and c.
+
+-p::
+       Send results to standard output instead of overwriting
+       `<current-file>`.
+
+-q::
+       Quiet;  do  not  warn about conflicts.
+
+
+EXAMPLES
+--------
+
+git merge-file README.my README README.upstream::
+
+       combines the changes of README.my and README.upstream since README,
+       tries to merge them and writes the result into README.my.
+
+git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345::
+
+       merges tmp/a123 and tmp/c345 with the base tmp/b234, but uses labels
+       `a` and `c` instead of `tmp/a123` and `tmp/c345`.
+
+
+Author
+------
+Written by Johannes Schindelin <johannes.schindelin@gmx.de>
+
+
+Documentation
+--------------
+Documentation by Johannes Schindelin and the git-list <git@vger.kernel.org>,
+with parts copied from the original documentation of RCS merge.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 6cd060108292733725aca28cc8f0b83aa7d0f56e..0cf505ea84d231e4e4997676d4169c5c5c7dd9f9 100644 (file)
@@ -40,8 +40,8 @@ If "git-merge-index" is called with multiple <file>s (or -a) then it
 processes them in turn only stopping if merge returns a non-zero exit
 code.
 
-Typically this is run with the a script calling the merge command from
-the RCS package.
+Typically this is run with the a script calling git's imitation of
+the merge command from the RCS package.
 
 A sample script called "git-merge-one-file" is included in the
 distribution.
index bebf30ad3db951c0a0bad01ebee619821781eb39..e2954aa76eef46101f38683eb346bd31bf711a40 100644 (file)
@@ -8,12 +8,14 @@ git-merge - Grand Unified Merge Driver
 
 SYNOPSIS
 --------
-'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>...
-
+[verse]
+'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]...
+       [--reflog-action=<action>]
+       -m=<msg> <remote> <remote>...
 
 DESCRIPTION
 -----------
-This is the top-level user interface to the merge machinery
+This is the top-level interface to the merge machinery
 which drives multiple merge strategy scripts.
 
 
@@ -27,13 +29,19 @@ include::merge-options.txt[]
        to give a good default for automated `git-merge` invocations.
 
 <head>::
-       our branch head commit.
+       Our branch head commit.  This has to be `HEAD`, so new
+       syntax does not require it
 
 <remote>::
-       other branch head merged into our branch.  You need at
+       Other branch head merged into our branch.  You need at
        least one <remote>.  Specifying more than one <remote>
        obviously means you are trying an Octopus.
 
+--reflog-action=<action>::
+       This is used internally when `git-pull` calls this command
+       to record that the merge was created by `pull` command
+       in the `ref-log` entry that results from the merge.
+
 include::merge-strategies.txt[]
 
 
index d4ae99fa530a50fd354dc54004a7342afd693947..197f4b512fbcc969901034e47efa6acefdc5e0ac 100644 (file)
@@ -49,12 +49,14 @@ corresponding remotes file---see below), then all the
 refs that exist both on the local side and on the remote
 side are updated.
 +
-Some short-cut notations are also supported.
+`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
 +
-* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
-* A parameter <ref> without a colon is equivalent to
-  <ref>`:`<ref>, hence updates <ref> in the destination from <ref>
-  in the source.
+A parameter <ref> without a colon is equivalent to
+<ref>`:`<ref>, hence updates <ref> in the destination from <ref>
+in the source.
++
+Pushing an empty <src> allows you to delete the <dst> ref from
+the remote repository.
 
 \--all::
        Instead of naming each ref to push, specifies that all
@@ -75,7 +77,8 @@ include::urls.txt[]
 
 Author
 ------
-Written by Junio C Hamano <junkio@cox.net>
+Written by Junio C Hamano <junkio@cox.net>, later rewritten in C
+by Linus Torvalds <torvalds@osdl.org>
 
 Documentation
 --------------
index 11bd9c0adc5517ef1aadc76abdb8dfb0827c27b0..0ff2890c7fb76155e08eaf9e361bf1eb6cf50f25 100644 (file)
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -71,6 +71,20 @@ OPTIONS
        directory.  Note that the `<prefix>/` value must end
        with a slash.
 
+--exclude-per-directory=<gitignore>::
+       When running the command with `-u` and `-m` options, the
+       merge result may need to overwrite paths that are not
+       tracked in the current branch.  The command usually
+       refuses to proceed with the merge to avoid losing such a
+       path.  However this safety valve sometimes gets in the
+       way.  For example, it often happens that the other
+       branch added a file that used to be a generated file in
+       your branch, and the safety valve triggers when you try
+       to switch to that branch after you ran `make` but before
+       running `make clean` to remove the generated file.  This
+       option tells the command to read per-directory exclude
+       file (usually '.gitignore') and allows such an untracked
+       but explicitly ignored file to be overwritten.
 
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
index 5bede9ac22d260d703c5632f44ffc404013650d0..b379ec5075981347e75d0795e44e57620d89399b 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-repo-config' [--global] [type] name [value [value_regex]]
+'git-repo-config' [--global] [type] --add name value
 'git-repo-config' [--global] [type] --replace-all name [value [value_regex]]
 'git-repo-config' [--global] [type] --get name [value_regex]
 'git-repo-config' [--global] [type] --get-all name [value_regex]
@@ -23,7 +24,8 @@ You can query/set/replace/unset options with this command. The name is
 actually the section and the key separated by a dot, and the value will be
 escaped.
 
-If you want to set/unset an option which can occur on multiple
+Multiple lines can be added to an option by using the '--add' option.
+If you want to update or unset an option which can occur on multiple
 lines, a POSIX regexp `value_regex` needs to be given.  Only the
 existing values that match the regexp are updated or unset.  If
 you want to handle the lines that do *not* match the regex, just
@@ -53,6 +55,10 @@ OPTIONS
        Default behavior is to replace at most one line. This replaces
        all lines matching the key (and optionally the value_regex).
 
+--add::
+       Adds a new line to the option without altering any existing
+       values.  This is the same as providing '^$' as the value_regex.
+
 --get::
        Get the value for a given key (optionally filtered by a regex
        matching the value). Returns error code 1 if the key was not
@@ -194,6 +200,12 @@ To actually match only values with an exclamation mark, you have to
 % git repo-config section.key value '[!]'
 ------------
 
+To add a new proxy, without altering any of the existing ones, use
+
+------------
+% git repo-config core.gitproxy '"proxy" for example.com'
+------------
+
 
 include::config.txt[]
 
index 8b6b6512371fd67fbb56d06cf29e826d3ef69d56..116dca4c0668488a0ba55b43a10e89c40f087b59 100644 (file)
@@ -7,8 +7,7 @@ git-rerere - Reuse recorded resolve
 
 SYNOPSIS
 --------
-'git-rerere'
-
+'git-rerere' [clear|diff|status]
 
 DESCRIPTION
 -----------
@@ -27,6 +26,38 @@ results and applying the previously recorded hand resolution.
 You need to create `$GIT_DIR/rr-cache` directory to enable this
 command.
 
+
+COMMANDS
+--------
+
+Normally, git-rerere is run without arguments or user-intervention.
+However, it has several commands that allow it to interact with
+its working state.
+
+'clear'::
+
+This resets the metadata used by rerere if a merge resolution is to be
+is aborted.  Calling gitlink:git-am[1] --skip or gitlink:git-rebase[1]
+[--skip|--abort] will automatcally invoke this command.
+
+'diff'::
+
+This displays diffs for the current state of the resolution.  It is
+useful for tracking what has changed while the user is resolving
+conflicts.  Additional arguments are passed directly to the system
+diff(1) command installed in PATH.
+
+'status'::
+
+Like diff, but this only prints the filenames that will be tracked
+for resolutions.
+
+'gc'::
+
+This command is used to prune records of conflicted merge that
+occurred long time ago.
+
+
 DISCUSSION
 ----------
 
index 73a0ffc41085e87fbc996fab4baa25ace1951460..4a4ceb62012d7dbd56085a87d62aa4dec928d800 100644 (file)
@@ -31,7 +31,7 @@ OPTIONS
 --soft::
        Does not touch the index file nor the working tree at all, but
        requires them to be in a good order. This leaves all your changed
-       files "Updated but not checked in", as gitlink:git-status[1] would
+       files "Added but not yet committed", as gitlink:git-status[1] would
        put it.
 
 --hard::
index d54fc3e5c6d7ba3be31fa2c1238e2d7039c92e74..95fa9010c1539e4c93d6d402ffd4bb670fd32910 100644 (file)
@@ -8,6 +8,7 @@ git-shortlog - Summarize 'git log' output
 SYNOPSIS
 --------
 git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s]
+git-shortlog [-n|--number] [-s|--summary] [<committish>...]
 
 DESCRIPTION
 -----------
index a2445a48fc98ddd4865bab8d3033880a854fca36..948ff10e6c83b8842de453e798ff7916ac98d911 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current]
                [--more=<n> | --list | --independent | --merge-base]
-               [--no-name | --sha1-name] [<rev> | <glob>]...
+               [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...
 
 DESCRIPTION
 -----------
@@ -86,6 +86,14 @@ OPTIONS
        of "master"), name them with the unique prefix of their
        object names.
 
+--topics::
+       Shows only commits that are NOT on the first branch given.
+       This helps track topic branches by hiding any commit that
+       is already in the main line of development.  When given
+       "git show-branch --topics master topic1 topic2", this
+       will show the revisions given by "git rev-list {caret}master
+       topic1 topic2"
+
 Note that --more, --list, --independent and --merge-base options
 are mutually exclusive.
 
index 4c880a871792a64355ac94537af934aca14879ec..98dea6125da78fa74800925b57dad2f002b1d6f6 100644 (file)
@@ -3,20 +3,27 @@ git-show(1)
 
 NAME
 ----
-git-show - Show one commit with difference it introduces
+git-show - Show various types of objects
 
 
 SYNOPSIS
 --------
-'git-show' <option>...
+'git-show' [options] <object>...
 
 DESCRIPTION
 -----------
-Shows commit log and textual diff for a single commit.  The
-command internally invokes 'git-rev-list' piped to
-'git-diff-tree', and takes command line options for both of
-these commands. It also presents the merge commit in a special
-format as produced by 'git-diff-tree --cc'.
+Shows one or more objects (blobs, trees, tags and commits).
+
+For commits it shows the log message and textual diff. It also
+presents the merge commit in a special format as produced by
+'git-diff-tree --cc'.
+
+For tags, it shows the tag message and the referenced objects.
+
+For trees, it shows the names (equivalent to gitlink:git-ls-tree[1]
+with \--name-only).
+
+For plain blobs, it shows the plain contents.
 
 This manual page describes only the most frequently used options.
 
@@ -28,6 +35,25 @@ OPTIONS
 
 include::pretty-formats.txt[]
 
+
+EXAMPLES
+--------
+
+git show v1.0.0::
+       Shows the tag `v1.0.0`.
+
+git show v1.0.0^{tree}::
+       Shows the tree pointed to by the tag `v1.0.0`.
+
+git show next~10:Documentation/README
+       Shows the contents of the file `Documentation/README` as
+       they were current in the 10th last commit of the branch
+       `next`.
+
+git show master:Makefile master:t/Makefile
+       Concatenates the contents of said Makefiles in the head
+       of the branch `master`.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org> and
index a764d1f8ee52742b85ce8edff22814a32be82dfb..f5f57e8f87d4271a634f13f33d6b33af43909473 100644 (file)
@@ -49,7 +49,7 @@ 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'
+remotes/git-svn and work on that branch.  Use the 'dcommit'
 command (see below) to write git commits back to
 remotes/git-svn.
 
@@ -57,12 +57,14 @@ See '<<fetch-args,Additional Fetch Arguments>>' if you are interested in
 manually joining branches on commit.
 
 'dcommit'::
-       Commit all diffs from the current HEAD directly to the SVN
+       Commit all diffs from a specified head directly to the SVN
        repository, and then rebase or reset (depending on whether or
-       not there is a diff between SVN and HEAD).  It is recommended
+       not there is a diff between SVN and head).  It is recommended
        that you run git-svn fetch and rebase (not pull) your commits
        against the latest changes in the SVN repository.
-       This is advantageous over 'commit' (below) because it produces
+       An optional command-line argument may be specified as an
+       alternative to HEAD.
+       This is advantageous over 'set-tree' (below) because it produces
        cleaner, more linear history.
 
 'log'::
@@ -87,7 +89,7 @@ manually joining branches on commit.
 
        Any other arguments are passed directly to `git log'
 
-'commit'::
+'set-tree'::
        You should consider using 'dcommit' instead of this command.
        Commit specified commit or tree objects to SVN.  This relies on
        your imported fetch data being up-to-date.  This makes
@@ -170,7 +172,7 @@ This can allow you to make partial mirrors when running fetch.
 -::
 --stdin::
 
-Only used with the 'commit' command.
+Only used with the 'set-tree' command.
 
 Read a list of commits from stdin and commit them in reverse
 order.  Only the leading sha1 is read from each line, so
@@ -178,7 +180,7 @@ git-rev-list --pretty=oneline output can be used.
 
 --rmdir::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 Remove directories from the SVN tree if there are no files left
 behind.  SVN can version empty directories, and they are not
@@ -191,7 +193,7 @@ repo-config key: svn.rmdir
 -e::
 --edit::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 Edit the commit message before committing to SVN.  This is off by
 default for objects that are commits, and forced on when committing
@@ -202,7 +204,7 @@ repo-config key: svn.edit
 -l<num>::
 --find-copies-harder::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 They are both passed directly to git-diff-tree see
 gitlink:git-diff-tree[1] for more information.
@@ -274,7 +276,7 @@ ADVANCED OPTIONS
 
 -b<refname>::
 --branch <refname>::
-Used with 'fetch' or 'commit'.
+Used with 'fetch', 'dcommit' or 'set-tree'.
 
 This can be used to join arbitrary git branches to remotes/git-svn
 on new commits where the tree object is equivalent.
@@ -368,7 +370,7 @@ SVN was very wrong.
 Basic Examples
 ~~~~~~~~~~~~~~
 
-Tracking and contributing to an Subversion managed-project:
+Tracking and contributing to a Subversion-managed project:
 
 ------------------------------------------------------------------------
 # Initialize a repo (like git init-db):
@@ -377,10 +379,9 @@ Tracking and contributing to an Subversion managed-project:
        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
+# Do some work, and then commit your new changes to SVN, as well as
+# automatically updating your working HEAD:
+       git-svn dcommit
 # Something is committed to SVN, rebase the latest into your branch:
        git-svn fetch && git rebase remotes/git-svn
 # Append svn:ignore settings to the default git exclude file:
@@ -391,11 +392,11 @@ REBASE VS. PULL
 ---------------
 
 Originally, git-svn recommended that the remotes/git-svn branch be
-pulled from.  This is because the author favored 'git-svn commit B'
-to commit a single head rather than the 'git-svn commit A..B' notation
+pulled from.  This is because the author favored 'git-svn set-tree B'
+to commit a single head rather than the 'git-svn set-tree A..B' notation
 to commit multiple commits.
 
-If you use 'git-svn commit A..B' to commit several diffs and you do not
+If you use 'git-svn set-tree A..B' to commit several diffs and you do not
 have the latest remotes/git-svn merged into my-branch, you should use
 'git rebase' to update your work branch instead of 'git pull'.  'pull'
 can cause non-linear history to be flattened when committing into SVN,
@@ -404,26 +405,24 @@ which can lead to merge commits reversing previous commits in SVN.
 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.
+with Subversion is cumbersome as a result.  git-svn does not do
+automated merge/branch tracking by default and leaves it entirely up to
+the user on the git side.
 
 [[tracking-multiple-repos]]
 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/svn/git-svn directory
-and instead do all of its work in $GIT_DIR/svn/$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.
+SVN repositories via one git repository.  Simply use the --id/-i flag or
+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/svn/git-svn directory and instead do all of its work in
+$GIT_DIR/svn/$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.
 
 [[fetch-args]]
 ADDITIONAL FETCH ARGUMENTS
@@ -486,7 +485,8 @@ If you are not using the SVN::* Perl libraries and 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'.
+probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'.   You
+can avoid this problem entirely by using 'dcommit'.
 
 We ignore all SVN properties except svn:executable.  Too difficult to
 map them since we rely heavily on git write-tree being _exactly_ the
index b1b87c2fcd9b60aef6fb937bd4d28572f1cb8463..2c7c7dad54b5d2c773194b494d98ea521bc54d2f 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
                [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
                [ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
                [ -I <ignorefile_name> ] [ -A <author_file> ]
+               [ -P <path_from_trunk> ]
                <SVN_repository_URL> [ <path> ]
 
 
@@ -103,9 +104,17 @@ repository without -A.
 
 -l <max_rev>::
        Specify a maximum revision number to pull.
++
+Formerly, this option controlled how many revisions to pull,
+due to SVN memory leaks. (These have been worked around.)
 
-       Formerly, this option controlled how many revisions to pull,
-       due to SVN memory leaks. (These have been worked around.)
+-P <path_from_trunk>::
+       Partial import of the SVN tree.
++
+By default, the whole tree on the SVN trunk (/trunk) is imported.
+'-P my/proj' will import starting only from '/trunk/my/proj'.
+This option is useful when you want to import one project from a
+svn repo which hosts multiple projects under the same trunk.
 
 -v::
        Verbosity: let 'svnimport' report what it is doing.
index 68ac6a65df304adf99f0af95698ffc901dda873c..4bc35a1d4bfce9b92394ba0f3e98ed9943f66e2e 100644 (file)
@@ -19,29 +19,22 @@ argument to see on which branch your working tree is on.
 Give two arguments, create or update a symbolic ref <name> to
 point at the given branch <ref>.
 
-Traditionally, `.git/HEAD` is a symlink pointing at
-`refs/heads/master`.  When we want to switch to another branch,
-we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we want
+A symbolic ref is a regular file that stores a string that
+begins with `ref: refs/`.  For example, your `.git/HEAD` is
+a regular file whose contents is `ref: refs/heads/master`.
+
+NOTES
+-----
+In the past, `.git/HEAD` was a symbolic link pointing at
+`refs/heads/master`.  When we wanted to switch to another branch,
+we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we wanted
 to find out which branch we are on, we did `readlink .git/HEAD`.
 This was fine, and internally that is what still happens by
 default, but on platforms that do not have working symlinks,
 or that do not have the `readlink(1)` command, this was a bit
 cumbersome.  On some platforms, `ln -sf` does not even work as
-advertised (horrors).
-
-A symbolic ref can be a regular file that stores a string that
-begins with `ref: refs/`.  For example, your `.git/HEAD` *can*
-be a regular file whose contents is `ref: refs/heads/master`.
-This can be used on a filesystem that does not support symbolic
-links.  Instead of doing `readlink .git/HEAD`, `git-symbolic-ref
-HEAD` can be used to find out which branch we are on.  To point
-the HEAD to `newbranch`, instead of `ln -sf refs/heads/newbranch
-.git/HEAD`, `git-symbolic-ref HEAD refs/heads/newbranch` can be
-used.
-
-Currently, .git/HEAD uses a regular file symbolic ref on Cygwin,
-and everywhere else it is implemented as a symlink.  This can be
-changed at compilation time.
+advertised (horrors).  Therefore symbolic links are now deprecated
+and symbolic refs are used by default.
 
 Author
 ------
index 45476c2e415112347372f034276dec367f7a6a8d..48b82b86f83df4e3fb381da329b48fe12a4ce82d 100644 (file)
@@ -9,7 +9,8 @@ git-tag - Create a tag object signed with GPG
 SYNOPSIS
 --------
 [verse]
-'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <name> [<head>]
+'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg> | -F <file>]
+        <name> [<head>]
 'git-tag' -l [<pattern>]
 
 DESCRIPTION
@@ -60,6 +61,9 @@ OPTIONS
 -m <msg>::
        Use the given tag message (instead of prompting)
 
+-F <file>::
+       Take the tag message from the given file.  Use '-' to
+       read the message from the standard input.
 
 Author
 ------
index 619d65685eb51cdad2ba7faeaa16223ca55b2b81..2a9e97dac1e2c21a8a4aa7a6501a054fcefbef35 100644 (file)
@@ -351,6 +351,9 @@ gitlink:git-init-db[1]::
        Creates an empty git object database, or reinitialize an
        existing one.
 
+gitlink:git-merge-file[1]::
+       Runs a threeway merge.
+
 gitlink:git-merge-index[1]::
        Runs a merge for files needing merging.
 
@@ -639,11 +642,35 @@ git Commits
 git Diffs
 ~~~~~~~~~
 'GIT_DIFF_OPTS'::
+       Only valid setting is "--unified=??" or "-u??" to set the
+       number of context lines shown when a unified diff is created.
+       This takes precedence over any "-U" or "--unified" option
+       value passed on the git diff command line.
+
 'GIT_EXTERNAL_DIFF'::
-       see the "generating patches" section in :
-       gitlink:git-diff-index[1];
-       gitlink:git-diff-files[1];
-       gitlink:git-diff-tree[1]
+       When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
+       program named by it is called, instead of the diff invocation
+       described above.  For a path that is added, removed, or modified,
+        'GIT_EXTERNAL_DIFF' is called with 7 parameters:
+
+       path old-file old-hex old-mode new-file new-hex new-mode
++
+where:
+
+       <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
+                         contents of <old|new>,
+       <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
+       <old|new>-mode:: are the octal representation of the file modes.
+
++
+The file parameters can point at the user's working file
+(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
+when a new file is added), or a temporary file (e.g. `old-file` in the
+index).  'GIT_EXTERNAL_DIFF' should not worry about unlinking the
+temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits.
++
+For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
+parameter, <path>.
 
 other
 ~~~~~
index 7597d041420ab2e84fc607bc3c844824c4528491..5030d9f2f831651f231d5c40d0e2110564646ef2 100644 (file)
@@ -4,7 +4,7 @@ Use of index and Racy git problem
 Background
 ----------
 
-The index is one of the most important data structure in git.
+The index is one of the most important data structures in git.
 It represents a virtual working tree state by recording list of
 paths and their object names and serves as a staging area to
 write out the next tree object to be committed.  The state is
@@ -16,7 +16,7 @@ virtual working tree state in the index and the files in the
 working tree.  The most obvious case is when the user asks `git
 diff` (or its low level implementation, `git diff-files`) or
 `git-ls-files --modified`.  In addition, git internally checks
-if the files in the working tree is different from what are
+if the files in the working tree are different from what are
 recorded in the index to avoid stomping on local changes in them
 during patch application, switching branches, and merging.
 
@@ -24,9 +24,9 @@ In order to speed up this comparison between the files in the
 working tree and the index entries, the index entries record the
 information obtained from the filesystem via `lstat(2)` system
 call when they were last updated.  When checking if they differ,
-git first runs `lstat(2)` on the files and compare the result
+git first runs `lstat(2)` on the files and compares the result
 with this information (this is what was originally done by the
-`ce_match_stat()` function, which the current code does in
+`ce_match_stat()` function, but the current code does it in
 `ce_match_stat_basic()` function).  If some of these "cached
 stat information" fields do not match, git can tell that the
 files are modified without even looking at their contents.
@@ -53,8 +53,9 @@ Racy git
 There is one slight problem with the optimization based on the
 cached stat information.  Consider this sequence:
 
+  : modify 'foo'
   $ git update-index 'foo'
-  : modify 'foo' in-place without changing its size
+  : modify 'foo' again, in-place, without changing its size
 
 The first `update-index` computes the object name of the
 contents of file `foo` and updates the index entry for `foo`
@@ -62,7 +63,8 @@ along with the `struct stat` information.  If the modification
 that follows it happens very fast so that the file's `st_mtime`
 timestamp does not change, after this sequence, the cached stat
 information the index entry records still exactly match what you
-can obtain from the filesystem, but the file `foo` is modified.
+would see in the filesystem, even though the file `foo` is now
+different.
 This way, git can incorrectly think files in the working tree
 are unmodified even though they actually are.  This is called
 the "racy git" problem (discovered by Pasky), and the entries
@@ -87,7 +89,7 @@ the stat information from updated paths, `st_mtime` timestamp of
 it is usually the same as or newer than any of the paths the
 index contains.  And no matter how quick the modification that
 follows `git update-index foo` finishes, the resulting
-`st_mtime` timestamp on `foo` cannot get the timestamp earlier
+`st_mtime` timestamp on `foo` cannot get a value earlier
 than the index file.  Therefore, index entries that can be
 racily clean are limited to the ones that have the same
 timestamp as the index file itself.
@@ -111,7 +113,7 @@ value, and falsely clean entry `foo` would not be caught by the
 timestamp comparison check done with the former logic anymore.
 The latter makes sure that the cached stat information for `foo`
 would never match with the file in the working tree, so later
-checks by `ce_match_stat_basic()` would report the index entry
+checks by `ce_match_stat_basic()` would report that the index entry
 does not match the file and git does not have to fall back on more
 expensive `ce_modified_check_fs()`.
 
@@ -155,17 +157,16 @@ of the cached stat information.
 Avoiding runtime penalty
 ------------------------
 
-In order to avoid the above runtime penalty, the recent "master"
-branch (post 1.4.2) has a code that makes sure the index file
-gets timestamp newer than the youngest files in the index when
+In order to avoid the above runtime penalty, post 1.4.2 git used
+to have a code that made sure the index file
+got timestamp newer than the youngest files in the index when
 there are many young files with the same timestamp as the
 resulting index file would otherwise would have by waiting
 before finishing writing the index file out.
 
-I suspect that in practice the situation where many paths in the
-index are all racily clean is quite rare.  The only code paths
-that can record recent timestamp for large number of paths I
-know of are:
+I suspected that in practice the situation where many paths in the
+index are all racily clean was quite rare.  The only code paths
+that can record recent timestamp for large number of paths are:
 
 . Initial `git add .` of a large project.
 
@@ -188,6 +189,7 @@ youngest file in the working tree.  This means that in these
 cases there actually will not be any racily clean entry in
 the resulting index.
 
-So in summary I think we should not worry about avoiding the
-runtime penalty and get rid of the "wait before finishing
-writing" code out.
+Based on this discussion, the current code does not use the
+"workaround" to avoid the runtime penalty that does not exist in
+practice anymore.  This was done with commit 0fc82cff on Aug 15,
+2006.
index 42b6e7d7d2bbb24d96f29635967945f516b25e15..60e54777dc6cef259a045512f42a50527f430fb9 100644 (file)
@@ -18,17 +18,18 @@ Let's start a new project and create a small amount of history:
 $ mkdir test-project
 $ cd test-project
 $ git init-db
-defaulting to local storage area
+Initialized empty Git repository in .git/
 $ echo 'hello world' > file.txt
 $ git add .
 $ git commit -a -m "initial commit"
-Committing initial tree 92b8b694ffb1675e5975148e1121810081dbdffe
+Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+ create mode 100644 file.txt
 $ echo 'hello world!' >file.txt
 $ git commit -a -m "add emphasis"
+Created commit c4d59f390b9cfd4318117afde11d601c1085f241
 ------------------------------------------------
 
-What are the 40 digits of hex that git responded to the first commit
-with?
+What are the 40 digits of hex that git responded to the commit with?
 
 We saw in part one of the tutorial that commits have names like this.
 It turns out that every object in the git history is stored under
@@ -38,13 +39,25 @@ the same data twice (since identical data is given an identical SHA1
 name), and that the contents of a git object will never change (since
 that would change the object's name as well).
 
+It is expected that the content of the commit object you created while
+following the example above generates a different SHA1 hash than
+the one shown above because the commit object records the time when
+it was created and the name of the person performing the commit.
+
 We can ask git about this particular object with the cat-file
-command--just cut-and-paste from the reply to the initial commit, to
-save yourself typing all 40 hex digits:
+command. Don't copy the 40 hex digits from this example but use those
+from your own version. Note that you can shorten it to only a few
+characters to save yourself typing all 40 hex digits:
 
 ------------------------------------------------
-$ git cat-file -t 92b8b694ffb1675e5975148e1121810081dbdffe
-tree
+$ git-cat-file -t 54196cc2
+commit
+$ git-cat-file commit 54196cc2
+tree 92b8b694ffb1675e5975148e1121810081dbdffe
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+
+initial commit
 ------------------------------------------------
 
 A tree can refer to one or more "blob" objects, each corresponding to
@@ -101,8 +114,7 @@ $ find .git/objects/
 
 and the contents of these files is just the compressed data plus a
 header identifying their length and their type.  The type is either a
-blob, a tree, a commit, or a tag.  We've seen a blob and a tree now,
-so next we should look at a commit.
+blob, a tree, a commit, or a tag.
 
 The simplest commit to find is the HEAD commit, which we can find
 from .git/HEAD:
@@ -341,23 +353,23 @@ situation:
 ------------------------------------------------
 $ git status
 #
-# Updated but not checked in:
+# Added but not yet committed:
 #   (will commit)
 #
 #       new file: closing.txt
 #
 #
-# Changed but not updated:
-#   (use git-update-index to mark for commit)
+# Changed but not added:
+#   (use "git add file1 file2" to include for commit)
 #
 #       modified: file.txt
 #
 ------------------------------------------------
 
 Since the current state of closing.txt is cached in the index file,
-it is listed as "updated but not checked in".  Since file.txt has
+it is listed as "added but not yet committed".  Since file.txt has
 changes in the working directory that aren't reflected in the index,
-it is marked "changed but not updated".  At this point, running "git
+it is marked "changed but not added".  At this point, running "git
 commit" would create a commit that added closing.txt (with its new
 contents), but that didn't modify file.txt.
 
index 6555e58882a0056c1c0ab83471481d5b512c5863..cb808d924be51e111b3e57b1b6e007cdf9f451bb 100644 (file)
@@ -38,7 +38,7 @@ $ git init-db
 Git will reply
 
 ------------------------------------------------
-defaulting to local storage area
+Initialized empty Git repository in .git/
 ------------------------------------------------
 
 You've now initialized the working directory--you may notice a new
@@ -87,14 +87,48 @@ thorough description.  Tools that turn commits into email, for
 example, use the first line on the Subject line and the rest of the
 commit in the body.
 
-To add a new file, first create the file, then
 
-------------------------------------------------
-$ git add path/to/new/file
-------------------------------------------------
+Git tracks content not files
+----------------------------
+
+With git you have to explicitly "add" all the changed _content_ you
+want to commit together. This can be done in a few different ways:
+
+1) By using 'git add <file_spec>...'
+
+   This can be performed multiple times before a commit.  Note that this
+   is not only for adding new files.  Even modified files must be
+   added to the set of changes about to be committed.  The "git status"
+   command gives you a summary of what is included so far for the
+   next commit.  When done you should use the 'git commit' command to
+   make it real.
+
+   Note: don't forget to 'add' a file again if you modified it after the
+   first 'add' and before 'commit'. Otherwise only the previous added
+   state of that file will be committed. This is because git tracks
+   content, so what you're really 'add'ing to the commit is the *content*
+   of the file in the state it is in when you 'add' it.
+
+2) By using 'git commit -a' directly
+
+   This is a quick way to automatically 'add' the content from all files
+   that were modified since the previous commit, and perform the actual
+   commit without having to separately 'add' them beforehand.  This will
+   not add content from new files i.e. files that were never added before.
+   Those files still have to be added explicitly before performing a
+   commit.
 
-then commit as usual.  No special command is required when removing a
-file; just remove it, then tell `commit` about the file as usual.
+But here's a twist. If you do 'git commit <file1> <file2> ...' then only
+the  changes belonging to those explicitly specified files will be
+committed, entirely bypassing the current "added" changes. Those "added"
+changes will still remain available for a subsequent commit though.
+
+However, for normal usage you only have to remember 'git add' + 'git commit'
+and/or 'git commit -a'.
+
+
+Viewing the changelog
+---------------------
 
 At any point you can view the history of your changes using
 
@@ -228,29 +262,28 @@ at /home/bob/myrepo.  She does this with:
 
 ------------------------------------------------
 $ cd /home/alice/project
-$ git pull /home/bob/myrepo
+$ git pull /home/bob/myrepo master
 ------------------------------------------------
 
-This actually pulls changes from the branch in Bob's repository named
-"master".  Alice could request a different branch by adding the name
-of the branch to the end of the git pull command line.
+This merges the changes from Bob's "master" branch into Alice's
+current branch.  If Alice has made her own changes in the meantime,
+then she may need to manually fix any conflicts.  (Note that the
+"master" argument in the above command is actually unnecessary, as it
+is the default.)
 
-This merges Bob's changes into her repository; "git log" will
-now show the new commits.  If Alice has made her own changes in the
-meantime, then Bob's changes will be merged in, and she will need to
-manually fix any conflicts.
+The "pull" command thus performs two operations: it fetches changes
+from a remote branch, then merges them into the current branch.
 
-A more cautious Alice might wish to examine Bob's changes before
-pulling them.  She can do this by creating a temporary branch just
-for the purpose of studying Bob's changes:
+You can perform the first operation alone using the "git fetch"
+command.  For example, Alice could create a temporary branch just to
+track Bob's changes, without merging them with her own, using:
 
 -------------------------------------
 $ git fetch /home/bob/myrepo master:bob-incoming
 -------------------------------------
 
 which fetches the changes from Bob's master branch into a new branch
-named bob-incoming.  (Unlike git pull, git fetch just fetches a copy
-of Bob's line of development without doing any merging).  Then
+named bob-incoming.  Then
 
 -------------------------------------
 $ git log -p master..bob-incoming
@@ -259,8 +292,8 @@ $ git log -p master..bob-incoming
 shows a list of all the changes that Bob made since he branched from
 Alice's master branch.
 
-After examining those changes, and possibly fixing things, Alice can
-pull the changes into her master branch:
+After examining those changes, and possibly fixing things, Alice
+could pull the changes into her master branch:
 
 -------------------------------------
 $ git checkout master
@@ -270,6 +303,18 @@ $ git pull . bob-incoming
 The last command is a pull from the "bob-incoming" branch in Alice's
 own repository.
 
+Alice could also perform both steps at once with:
+
+-------------------------------------
+$ git pull /home/bob/myrepo master:bob-incoming
+-------------------------------------
+
+This is just like the "git pull /home/bob/myrepo master" that we saw
+before, except that it also stores the unmerged changes from bob's
+master branch in bob-incoming before merging them into Alice's
+current branch.  Note that git pull always merges into the current
+branch, regardless of what else is given on the commandline.
+
 Later, Bob can update his repo with Alice's latest changes using
 
 -------------------------------------
index bedc69271e4baafb5b56823cc80fdc7a6f21617c..eca1ff2175c9c62ce51fe6606d615f99925e7734 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.4.4.3.GIT
+DEF_VER=v1.4.4.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index fce6bc39d562271417fe4caeaf2d5dd9691deaf8..e7aea60e92eba80d63e9596d8d78b9447c292bcf 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -72,25 +72,6 @@ Issues of note:
        - expat library; git-http-push uses it for remote lock
          management over DAV.  Similar to "curl" above, this is optional.
 
-       - "GNU diff" to generate patches.  Of course, you don't _have_ to
-         generate patches if you don't want to, but let's face it, you'll
-         be wanting to. Or why did you get git in the first place?
-
-         Non-GNU versions of the diff/patch programs don't generally support
-         the unified patch format (which is the one git uses), so you
-         really do want to get the GNU one.  Trust me, you will want to
-         do that even if it wasn't for git.  There's no point in living
-         in the dark ages any more. 
-
-       - "merge", the standard UNIX three-way merge program.  It usually
-         comes with the "rcs" package on most Linux distributions, so if
-         you have a developer install you probably have it already, but a
-         "graphical user desktop" install might have left it out.
-
-         You'll only need the merge program if you do development using
-         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
           history graphically
 
@@ -99,9 +80,6 @@ Issues of note:
        - "perl" and POSIX-compliant shells are needed to use most of
          the barebone Porcelainish scripts.
 
-       - "python" 2.3 or more recent; if you have 2.3, you may need
-          to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease".
-
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
index c5a180490b0fbb6bae8a9a2a87c3795c89391c2f..76511045a80cf5da72851d8194742de56282d953 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,8 @@ all:
 #
 # Define NO_MMAP if you want to avoid mmap.
 #
-# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
+# Define NO_FAST_WORKING_DIRECTORY if accessing objects in pack files is
+# generally faster on your platform than accessing the working directory.
 #
 # Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
 #
@@ -81,10 +82,6 @@ all:
 # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
 # a missing newline at the end of the file.
 #
-# Define COLLISION_CHECK below if you believe that SHA1's
-# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# sufficient guarantee that no collisions between objects will ever happen.
-#
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
 # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
@@ -93,6 +90,10 @@ all:
 #
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-cache perspective.
+#
+# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
+# MakeMaker (e.g. using ActiveState under Cygwin).
+#
 
 GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -116,7 +117,6 @@ prefix = $(HOME)
 bindir = $(prefix)/bin
 gitexecdir = $(bindir)
 template_dir = $(prefix)/share/git-core/templates/
-GIT_PYTHON_DIR = $(prefix)/share/git-core/python
 # DESTDIR=
 
 # default configuration for gitweb
@@ -135,7 +135,7 @@ GITWEB_FAVICON = git-favicon.png
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
-export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
+export prefix bindir gitexecdir template_dir
 
 CC = gcc
 AR = ar
@@ -174,17 +174,13 @@ SCRIPT_SH = \
 
 SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-shortlog.perl git-rerere.perl \
+       git-rerere.perl \
        git-cvsserver.perl \
        git-svnimport.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
 
-SCRIPT_PYTHON = \
-       git-merge-recursive-old.py
-
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-cherry-pick git-status git-instaweb
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
@@ -227,12 +223,8 @@ endif
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
-ifndef PYTHON_PATH
-       PYTHON_PATH = /usr/bin/python
-endif
 
-PYMODULES = \
-       gitMergeCommon.py
+export PERL_PATH
 
 LIB_FILE=libgit.a
 XDIFF_LIB=xdiff/lib.a
@@ -288,6 +280,7 @@ BUILTIN_OBJS = \
        builtin-ls-tree.o \
        builtin-mailinfo.o \
        builtin-mailsplit.o \
+       builtin-merge-file.o \
        builtin-mv.o \
        builtin-name-rev.o \
        builtin-pack-objects.o \
@@ -300,6 +293,7 @@ BUILTIN_OBJS = \
        builtin-rev-parse.o \
        builtin-rm.o \
        builtin-runstatus.o \
+       builtin-shortlog.o \
        builtin-show-branch.o \
        builtin-stripspace.o \
        builtin-symbolic-ref.o \
@@ -362,6 +356,7 @@ ifeq ($(uname_O),Cygwin)
        NO_SYMLINK_HEAD = YesPlease
        NEEDS_LIBICONV = YesPlease
        NO_C99_FORMAT = YesPlease
+       NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
        # There are conflicting reports about this.
        # On some boxes NO_MMAP is needed, and not so elsewhere.
        # Try uncommenting this if you see things break -- YMMV.
@@ -426,16 +421,6 @@ ifeq ($(uname_S),Darwin)
        endif
 endif
 
-ifdef WITH_OWN_SUBPROCESS_PY
-       PYMODULES += compat/subprocess.py
-else
-       ifeq ($(NO_PYTHON),)
-               ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
-                       PYMODULES += compat/subprocess.py
-               endif
-       endif
-endif
-
 ifndef NO_CURL
        ifdef CURLDIR
                # This is still problematic -- gcc does not always want -R.
@@ -523,6 +508,9 @@ ifdef NO_MMAP
        COMPAT_CFLAGS += -DNO_MMAP
        COMPAT_OBJS += compat/mmap.o
 endif
+ifdef NO_FAST_WORKING_DIRECTORY
+       BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY
+endif
 ifdef NO_IPV6
        BASIC_CFLAGS += -DNO_IPV6
 endif
@@ -564,6 +552,9 @@ endif
 ifdef NO_ACCURATE_DIFF
        BASIC_CFLAGS += -DNO_ACCURATE_DIFF
 endif
+ifdef NO_PERL_MAKEMAKER
+       export NO_PERL_MAKEMAKER
+endif
 
 # Shell quote (do not use $(call) to accommodate ancient setups);
 
@@ -577,8 +568,6 @@ prefix_SQ = $(subst ','\'',$(prefix))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
-PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
-GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
 
@@ -595,8 +584,8 @@ export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 
 all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
 
-all: perl/Makefile
-       $(MAKE) -C perl
+all:
+       $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
        $(MAKE) -C templates
 
 strip: $(PROGRAMS) git$X
@@ -625,12 +614,15 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
            -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' \
            $@.sh >$@+
        chmod +x $@+
        mv $@+ $@
 
-$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile
+$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
+
+perl/perl.mak: GIT-CFLAGS
+       $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
+
 $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        rm -f $@ $@+
        INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
@@ -647,15 +639,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        chmod +x $@+
        mv $@+ $@
 
-$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py GIT-CFLAGS
-       rm -f $@ $@+
-       sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
-           -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \
-           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-           $@.py >$@+
-       chmod +x $@+
-       mv $@+ $@
-
 git-cherry-pick: git-revert
        cp $< $@+
        mv $@+ $@
@@ -692,7 +675,6 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
        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' \
@@ -712,7 +694,6 @@ configure: configure.ac
 git$X git.spec \
        $(patsubst %.sh,%,$(SCRIPT_SH)) \
        $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-       $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
        : GIT-VERSION-FILE
 
 %.o: %.c GIT-CFLAGS
@@ -762,7 +743,8 @@ $(DIFF_OBJS): diffcore.h
 $(LIB_FILE): $(LIB_OBJS)
        rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
 
-XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
+XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
+       xdiff/xmerge.o
 $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
        xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
 
@@ -786,7 +768,7 @@ tags:
        find . -name '*.[hcS]' -print | xargs ctags -a
 
 ### Detect prefix changes
-TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):$(GIT_PYTHON_DIR_SQ):\
+TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
 
 GIT-CFLAGS: .FORCE-GIT-CFLAGS
@@ -802,7 +784,6 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 # However, the environment gets quite big, and some programs have problems
 # with that.
 
-export NO_PYTHON
 export NO_SVN_TESTS
 
 test: all
@@ -811,8 +792,8 @@ test: all
 test-date$X: test-date.c date.o ctype.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
 
-test-delta$X: test-delta.c diff-delta.o patch-delta.o
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
+test-delta$X: test-delta.o diff-delta.o patch-delta.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
 test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
@@ -836,9 +817,7 @@ install: all
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
-       $(MAKE) -C perl install
-       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
-       $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
+       $(MAKE) -C perl prefix='$(prefix_SQ)' install
        if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
        then \
                ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
@@ -908,8 +887,7 @@ clean:
        rm -f $(htmldocs).tar.gz $(manpages).tar.gz
        rm -f gitweb/gitweb.cgi
        $(MAKE) -C Documentation/ clean
-       [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean
-       rm -f perl/ppport.h perl/Makefile.old
+       $(MAKE) -C perl clean
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
        rm -f GIT-VERSION-FILE GIT-CFLAGS
@@ -925,7 +903,6 @@ check-docs::
                case "$$v" in \
                git-merge-octopus | git-merge-ours | git-merge-recursive | \
                git-merge-resolve | git-merge-stupid | git-merge-recur | \
-               git-merge-recursive-old | \
                git-ssh-pull | git-ssh-push ) continue ;; \
                esac ; \
                test -f "Documentation/$$v.txt" || \
index ff0f6e29292cf716ac32e8ae63a58902fcccc120..af47fdc95572bfa67b9c67369222a69c32ddea02 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (c) 2005, 2006 Rene Scharfe
  */
-#include <time.h>
 #include "cache.h"
 #include "commit.h"
 #include "strbuf.h"
index 36e922a1f2ffe34264ac55c002072cb2777e7026..f31b8ed8236c3fcad10ac9a84e2f508f11e1a6cf 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (c) 2006 Rene Scharfe
  */
-#include <time.h>
 #include "cache.h"
 #include "commit.h"
 #include "blob.h"
diff --git a/blob.c b/blob.c
index d1af2e62f14965fab491a3d9dde712f02b718adc..9776beac5827a4c705eddde426817dcc40c525bc 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -1,6 +1,5 @@
 #include "cache.h"
 #include "blob.h"
-#include <stdlib.h>
 
 const char *blob_type = "blob";
 
index febb75ed994b5edc51afc60e2d46483fa9e273f2..f306f82b16c3c3c76416610cd4cabf0a307924ed 100644 (file)
@@ -3,8 +3,6 @@
  *
  * Copyright (C) 2006 Linus Torvalds
  */
-#include <fnmatch.h>
-
 #include "cache.h"
 #include "builtin.h"
 #include "dir.h"
@@ -94,9 +92,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
        newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1);
 
-       if (read_cache() < 0)
-               die("index file corrupt");
-
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
@@ -116,6 +111,11 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                }
                usage(builtin_add_usage);
        }
+       if (argc <= i) {
+               fprintf(stderr, "Nothing specified, nothing added.\n");
+               fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
+               return 0;
+       }
        pathspec = get_pathspec(prefix, argv + i);
 
        fill_directory(&dir, pathspec);
@@ -131,6 +131,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                return 0;
        }
 
+       if (read_cache() < 0)
+               die("index file corrupt");
+
        for (i = 0; i < dir.nr; i++)
                add_file_to_index(dir.entries[i]->name, verbose);
 
index 61f047fd45383ac64f27bef88e41b56efefdea3b..1c3583706835339d2f3a0b0a5d3b989aae2a8142 100644 (file)
@@ -6,7 +6,6 @@
  * This applies patches on top of some (arbitrary) version of the SCM.
  *
  */
-#include <fnmatch.h>
 #include "cache.h"
 #include "cache-tree.h"
 #include "quote.h"
@@ -2119,7 +2118,11 @@ static void numstat_patch_list(struct patch *patch)
        for ( ; patch; patch = patch->next) {
                const char *name;
                name = patch->new_name ? patch->new_name : patch->old_name;
-               printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+               if (patch->is_binary)
+                       printf("-\t-\t");
+               else
+                       printf("%d\t%d\t",
+                              patch->lines_added, patch->lines_deleted);
                if (line_termination && quote_c_style(name, NULL, NULL, 0))
                        quote_c_style(name, NULL, stdout, 0);
                else
index a8a1f079bf4e28a3f07d49b6a4723ee8dea17178..391cf43911a16ec49862c18b7dd4f1f094d49de8 100644 (file)
@@ -2,7 +2,6 @@
  * Copyright (c) 2006 Franck Bui-Huu
  * Copyright (c) 2006 Rene Scharfe
  */
-#include <time.h>
 #include "cache.h"
 #include "builtin.h"
 #include "archive.h"
index dc3ffeaff82377262f0ae47993cf0883b2552092..4a1accf13c1220745c73999c6c345735aeadc3e5 100644 (file)
 #include "revision.h"
 #include "xdiff-interface.h"
 
-#include <time.h>
-#include <sys/time.h>
-#include <regex.h>
-
 static char blame_usage[] =
 "git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
 "  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+"  -b                  Show blank SHA-1 for boundary commits (Default: off)\n"
 "  -l, --long          Show long commit SHA1 (Default: off)\n"
+"  --root              Do not treat root commits as boundaries (Default: off)\n"
 "  -t, --time          Show raw timestamp (Default: off)\n"
 "  -f, --show-name     Show original filename (Default: auto)\n"
 "  -n, --show-number   Show original linenumber (Default: off)\n"
@@ -36,6 +34,8 @@ static int longest_author;
 static int max_orig_digits;
 static int max_digits;
 static int max_score_digits;
+static int show_root;
+static int blank_boundary;
 
 #ifndef DEBUG
 #define DEBUG 0
@@ -1090,6 +1090,14 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
                if (!(commit->object.flags & UNINTERESTING) &&
                    !(revs->max_age != -1 && commit->date  < revs->max_age))
                        pass_blame(sb, suspect, opt);
+               else {
+                       commit->object.flags |= UNINTERESTING;
+                       if (commit->object.parsed)
+                               mark_parents_uninteresting(commit);
+               }
+               /* treat root commit as boundary */
+               if (!commit->parents && !show_root)
+                       commit->object.flags |= UNINTERESTING;
 
                /* Take responsibility for the remaining entries */
                for (ent = sb->ent; ent; ent = ent->next)
@@ -1273,6 +1281,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
                printf("committer-tz %s\n", ci.committer_tz);
                printf("filename %s\n", suspect->path);
                printf("summary %s\n", ci.summary);
+               if (suspect->commit->object.flags & UNINTERESTING)
+                       printf("boundary\n");
        }
        else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
                printf("filename %s\n", suspect->path);
@@ -1308,8 +1318,18 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
+               int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
+
+               if (suspect->commit->object.flags & UNINTERESTING) {
+                       if (!blank_boundary) {
+                               length--;
+                               putchar('^');
+                       }
+                       else
+                               memset(hex, ' ', length);
+               }
 
-               printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex);
+               printf("%.*s", length, hex);
                if (opt & OUTPUT_ANNOTATE_COMPAT)
                        printf("\t(%10s\t%10s\t%d)", ci.author,
                               format_time(ci.author_time, ci.author_tz,
@@ -1626,6 +1646,19 @@ static void prepare_blame_range(struct scoreboard *sb,
                usage(blame_usage);
 }
 
+static int git_blame_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "blame.showroot")) {
+               show_root = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "blame.blankboundary")) {
+               blank_boundary = git_config_bool(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 int cmd_blame(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -1641,6 +1674,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        char type[10];
        const char *bottomtop = NULL;
 
+       git_config(git_blame_config);
        save_commit_buffer = 0;
 
        opt = 0;
@@ -1649,6 +1683,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                const char *arg = argv[i];
                if (*arg != '-')
                        break;
+               else if (!strcmp("-b", arg))
+                       blank_boundary = 1;
+               else if (!strcmp("--root", arg))
+                       show_root = 1;
                else if (!strcmp("-c", arg))
                        output_option |= OUTPUT_ANNOTATE_COMPAT;
                else if (!strcmp("-t", arg))
index 368b68ec91f3d72d1f5e1d1dea503334207f864d..903d5cf05600f9a7e297dc9450ad5980388072e9 100644 (file)
@@ -6,17 +6,74 @@
  */
 
 #include "cache.h"
+#include "color.h"
 #include "refs.h"
 #include "commit.h"
 #include "builtin.h"
 
 static const char builtin_branch_usage[] =
-"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]";
+  "git-branch [-r] (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [-r | -a] [-v [--abbrev=<length>]]";
 
+#define REF_UNKNOWN_TYPE    0x00
+#define REF_LOCAL_BRANCH    0x01
+#define REF_REMOTE_BRANCH   0x02
+#define REF_TAG             0x04
 
 static const char *head;
 static unsigned char head_sha1[20];
 
+static int branch_use_color;
+static char branch_colors[][COLOR_MAXLEN] = {
+       "\033[m",       /* reset */
+       "",             /* PLAIN (normal) */
+       "\033[31m",     /* REMOTE (red) */
+       "",             /* LOCAL (normal) */
+       "\033[32m",     /* CURRENT (green) */
+};
+enum color_branch {
+       COLOR_BRANCH_RESET = 0,
+       COLOR_BRANCH_PLAIN = 1,
+       COLOR_BRANCH_REMOTE = 2,
+       COLOR_BRANCH_LOCAL = 3,
+       COLOR_BRANCH_CURRENT = 4,
+};
+
+static int parse_branch_color_slot(const char *var, int ofs)
+{
+       if (!strcasecmp(var+ofs, "plain"))
+               return COLOR_BRANCH_PLAIN;
+       if (!strcasecmp(var+ofs, "reset"))
+               return COLOR_BRANCH_RESET;
+       if (!strcasecmp(var+ofs, "remote"))
+               return COLOR_BRANCH_REMOTE;
+       if (!strcasecmp(var+ofs, "local"))
+               return COLOR_BRANCH_LOCAL;
+       if (!strcasecmp(var+ofs, "current"))
+               return COLOR_BRANCH_CURRENT;
+       die("bad config variable '%s'", var);
+}
+
+int git_branch_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "color.branch")) {
+               branch_use_color = git_config_colorbool(var, value);
+               return 0;
+       }
+       if (!strncmp(var, "color.branch.", 13)) {
+               int slot = parse_branch_color_slot(var, 13);
+               color_parse(value, var, branch_colors[slot]);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
+const char *branch_get_color(enum color_branch ix)
+{
+       if (branch_use_color)
+               return branch_colors[ix];
+       return "";
+}
+
 static int in_merge_bases(const unsigned char *sha1,
                          struct commit *rev1,
                          struct commit *rev2)
@@ -36,25 +93,59 @@ static int in_merge_bases(const unsigned char *sha1,
        return ret;
 }
 
-static void delete_branches(int argc, const char **argv, int force)
+static int delete_branches(int argc, const char **argv, int force, int kinds)
 {
-       struct commit *rev, *head_rev;
+       struct commit *rev, *head_rev = head_rev;
        unsigned char sha1[20];
-       char *name;
+       char *name = NULL;
+       const char *fmt, *remote;
        int i;
+       int ret = 0;
+
+       switch (kinds) {
+       case REF_REMOTE_BRANCH:
+               fmt = "refs/remotes/%s";
+               remote = "remote ";
+               force = 1;
+               break;
+       case REF_LOCAL_BRANCH:
+               fmt = "refs/heads/%s";
+               remote = "";
+               break;
+       default:
+               die("cannot use -a with -d");
+       }
 
-       head_rev = lookup_commit_reference(head_sha1);
+       if (!force) {
+               head_rev = lookup_commit_reference(head_sha1);
+               if (!head_rev)
+                       die("Couldn't look up commit object for HEAD");
+       }
        for (i = 0; i < argc; i++) {
-               if (!strcmp(head, argv[i]))
-                       die("Cannot delete the branch you are currently on.");
+               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) {
+                       error("Cannot delete the branch '%s' "
+                               "which you are currently on.", argv[i]);
+                       ret = 1;
+                       continue;
+               }
+
+               if (name)
+                       free(name);
 
-               name = xstrdup(mkpath("refs/heads/%s", argv[i]));
-               if (!resolve_ref(name, sha1, 1, NULL))
-                       die("Branch '%s' not found.", argv[i]);
+               name = xstrdup(mkpath(fmt, argv[i]));
+               if (!resolve_ref(name, sha1, 1, NULL)) {
+                       error("%sbranch '%s' not found.",
+                                       remote, argv[i]);
+                       ret = 1;
+                       continue;
+               }
 
                rev = lookup_commit_reference(sha1);
-               if (!rev || !head_rev)
-                       die("Couldn't look up commit objects.");
+               if (!rev) {
+                       error("Couldn't look up commit object for '%s'", name);
+                       ret = 1;
+                       continue;
+               }
 
                /* This checks whether the merge bases of branch and
                 * HEAD contains branch -- which means that the HEAD
@@ -63,62 +154,168 @@ static void delete_branches(int argc, const char **argv, int force)
 
                if (!force &&
                    !in_merge_bases(sha1, rev, head_rev)) {
-                       fprintf(stderr,
-                               "The branch '%s' is not a strict subset of your current HEAD.\n"
-                               "If you are sure you want to delete it, run 'git branch -D %s'.\n",
-                               argv[i], argv[i]);
-                       exit(1);
+                       error("The branch '%s' is not a strict subset of "
+                               "your current HEAD.\n"
+                               "If you are sure you want to delete it, "
+                               "run 'git branch -D %s'.", argv[i], argv[i]);
+                       ret = 1;
+                       continue;
                }
 
-               if (delete_ref(name, sha1))
-                       printf("Error deleting branch '%s'\n", argv[i]);
-               else
-                       printf("Deleted branch %s.\n", argv[i]);
+               if (delete_ref(name, sha1)) {
+                       error("Error deleting %sbranch '%s'", remote,
+                              argv[i]);
+                       ret = 1;
+               } else
+                       printf("Deleted %sbranch %s.\n", remote, argv[i]);
 
-               free(name);
        }
+
+       if (name)
+               free(name);
+
+       return(ret);
 }
 
-static int ref_index, ref_alloc;
-static char **ref_list;
+struct ref_item {
+       char *name;
+       unsigned int kind;
+       unsigned char sha1[20];
+};
+
+struct ref_list {
+       int index, alloc, maxwidth;
+       struct ref_item *list;
+       int kinds;
+};
 
-static int append_ref(const char *refname, const unsigned char *sha1, int flags,
-               void *cb_data)
+static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
-       if (ref_index >= ref_alloc) {
-               ref_alloc = alloc_nr(ref_alloc);
-               ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *));
+       struct ref_list *ref_list = (struct ref_list*)(cb_data);
+       struct ref_item *newitem;
+       int kind = REF_UNKNOWN_TYPE;
+       int len;
+
+       /* Detect kind */
+       if (!strncmp(refname, "refs/heads/", 11)) {
+               kind = REF_LOCAL_BRANCH;
+               refname += 11;
+       } else if (!strncmp(refname, "refs/remotes/", 13)) {
+               kind = REF_REMOTE_BRANCH;
+               refname += 13;
+       } else if (!strncmp(refname, "refs/tags/", 10)) {
+               kind = REF_TAG;
+               refname += 10;
        }
 
-       ref_list[ref_index++] = xstrdup(refname);
+       /* Don't add types the caller doesn't want */
+       if ((kind & ref_list->kinds) == 0)
+               return 0;
+
+       /* Resize buffer */
+       if (ref_list->index >= ref_list->alloc) {
+               ref_list->alloc = alloc_nr(ref_list->alloc);
+               ref_list->list = xrealloc(ref_list->list,
+                               ref_list->alloc * sizeof(struct ref_item));
+       }
+
+       /* Record the new item */
+       newitem = &(ref_list->list[ref_list->index++]);
+       newitem->name = xstrdup(refname);
+       newitem->kind = kind;
+       hashcpy(newitem->sha1, sha1);
+       len = strlen(newitem->name);
+       if (len > ref_list->maxwidth)
+               ref_list->maxwidth = len;
 
        return 0;
 }
 
+static void free_ref_list(struct ref_list *ref_list)
+{
+       int i;
+
+       for (i = 0; i < ref_list->index; i++)
+               free(ref_list->list[i].name);
+       free(ref_list->list);
+}
+
 static int ref_cmp(const void *r1, const void *r2)
 {
-       return strcmp(*(char **)r1, *(char **)r2);
+       struct ref_item *c1 = (struct ref_item *)(r1);
+       struct ref_item *c2 = (struct ref_item *)(r2);
+
+       if (c1->kind != c2->kind)
+               return c1->kind - c2->kind;
+       return strcmp(c1->name, c2->name);
 }
 
-static void print_ref_list(int remote_only)
+static void print_ref_info(const unsigned char *sha1, int abbrev)
 {
-       int i;
-       char c;
+       struct commit *commit;
+       char subject[256];
+
 
-       if (remote_only)
-               for_each_remote_ref(append_ref, NULL);
+       commit = lookup_commit(sha1);
+       if (commit && !parse_commit(commit))
+               pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+                                   subject, sizeof(subject), 0,
+                                   NULL, NULL, 0);
        else
-               for_each_branch_ref(append_ref, NULL);
+               strcpy(subject, " **** invalid ref ****");
 
-       qsort(ref_list, ref_index, sizeof(char *), ref_cmp);
+       printf(" %s %s\n", find_unique_abbrev(sha1, abbrev), subject);
+}
+
+static void print_ref_list(int kinds, int verbose, int abbrev)
+{
+       int i;
+       char c;
+       struct ref_list ref_list;
+       int color;
+
+       memset(&ref_list, 0, sizeof(ref_list));
+       ref_list.kinds = kinds;
+       for_each_ref(append_ref, &ref_list);
+
+       qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
+
+       for (i = 0; i < ref_list.index; i++) {
+               switch( ref_list.list[i].kind ) {
+                       case REF_LOCAL_BRANCH:
+                               color = COLOR_BRANCH_LOCAL;
+                               break;
+                       case REF_REMOTE_BRANCH:
+                               color = COLOR_BRANCH_REMOTE;
+                               break;
+                       default:
+                               color = COLOR_BRANCH_PLAIN;
+                               break;
+               }
 
-       for (i = 0; i < ref_index; i++) {
                c = ' ';
-               if (!strcmp(ref_list[i], head))
+               if (ref_list.list[i].kind == REF_LOCAL_BRANCH &&
+                               !strcmp(ref_list.list[i].name, head)) {
                        c = '*';
+                       color = COLOR_BRANCH_CURRENT;
+               }
 
-               printf("%c %s\n", c, ref_list[i]);
+               if (verbose) {
+                       printf("%c %s%-*s%s", c,
+                                       branch_get_color(color),
+                                       ref_list.maxwidth,
+                                       ref_list.list[i].name,
+                                       branch_get_color(COLOR_BRANCH_RESET));
+                       print_ref_info(ref_list.list[i].sha1, abbrev);
+               }
+               else
+                       printf("%c %s%s%s\n", c,
+                                       branch_get_color(color),
+                                       ref_list.list[i].name,
+                                       branch_get_color(COLOR_BRANCH_RESET));
        }
+
+       free_ref_list(&ref_list);
 }
 
 static void create_branch(const char *name, const char *start,
@@ -158,13 +355,47 @@ static void create_branch(const char *name, const char *start,
                die("Failed to write ref: %s.", strerror(errno));
 }
 
+static void rename_branch(const char *oldname, const char *newname, int force)
+{
+       char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
+       unsigned char sha1[20];
+
+       if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref))
+               die("Old branchname too long");
+
+       if (check_ref_format(oldref))
+               die("Invalid branch name: %s", oldref);
+
+       if (snprintf(newref, sizeof(newref), "refs/heads/%s", newname) > sizeof(newref))
+               die("New branchname too long");
+
+       if (check_ref_format(newref))
+               die("Invalid branch name: %s", newref);
+
+       if (resolve_ref(newref, sha1, 1, NULL) && !force)
+               die("A branch named '%s' already exists.", newname);
+
+       snprintf(logmsg, sizeof(logmsg), "Branch: renamed %s to %s",
+                oldref, newref);
+
+       if (rename_ref(oldref, newref, logmsg))
+               die("Branch rename failed");
+
+       if (!strcmp(oldname, head) && create_symref("HEAD", newref))
+               die("Branch renamed to %s, but HEAD is not updated!", newname);
+}
+
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
-       int delete = 0, force_delete = 0, force_create = 0, remote_only = 0;
+       int delete = 0, force_delete = 0, force_create = 0;
+       int rename = 0, force_rename = 0;
+       int verbose = 0, abbrev = DEFAULT_ABBREV;
        int reflog = 0;
+       int kinds = REF_LOCAL_BRANCH;
        int i;
 
-       git_config(git_default_config);
+       setup_ident();
+       git_config(git_branch_config);
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -188,17 +419,50 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        force_create = 1;
                        continue;
                }
+               if (!strcmp(arg, "-m")) {
+                       rename = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-M")) {
+                       rename = 1;
+                       force_rename = 1;
+                       continue;
+               }
                if (!strcmp(arg, "-r")) {
-                       remote_only = 1;
+                       kinds = REF_REMOTE_BRANCH;
+                       continue;
+               }
+               if (!strcmp(arg, "-a")) {
+                       kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH;
                        continue;
                }
                if (!strcmp(arg, "-l")) {
                        reflog = 1;
                        continue;
                }
+               if (!strncmp(arg, "--abbrev=", 9)) {
+                       abbrev = atoi(arg+9);
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--color")) {
+                       branch_use_color = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--no-color")) {
+                       branch_use_color = 0;
+                       continue;
+               }
                usage(builtin_branch_usage);
        }
 
+       if ((delete && rename) || (delete && force_create) ||
+           (rename && force_create))
+               usage(builtin_branch_usage);
+
        head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
        if (!head)
                die("Failed to resolve HEAD as a valid ref.");
@@ -207,9 +471,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        head += 11;
 
        if (delete)
-               delete_branches(argc - i, argv + i, force_delete);
+               return delete_branches(argc - i, argv + i, force_delete, kinds);
        else if (i == argc)
-               print_ref_list(remote_only);
+               print_ref_list(kinds, verbose, abbrev);
+       else if (rename && (i == argc - 1))
+               rename_branch(head, argv[i], force_rename);
+       else if (rename && (i == argc - 2))
+               rename_branch(argv[i], argv[i + 1], force_rename);
        else if (i == argc - 1)
                create_branch(argv[i], head, force_create, reflog);
        else if (i == argc - 2)
index e2e690a1ae89b47f595288e452da10113f7b2e8f..856f3cd841818bdad4446b3f7b75d5ab22959788 100644 (file)
@@ -107,8 +107,6 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
                if (new_parent(parents))
                        parents++;
        }
-       if (!parents)
-               fprintf(stderr, "Committing initial tree %s\n", argv[1]);
 
        init_buffer(&buffer, &size);
        add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
index 227aa3cd7f887ad44fd6b885479ef2afb586ded4..af72a12a5798265cece6c7b72582fd3672421394 100644 (file)
@@ -6,7 +6,6 @@
 #include "tree.h"
 #include "blob.h"
 #include "quote.h"
-#include <fnmatch.h>
 
 /* Quoting styles */
 #define QUOTE_NONE 0
index ad7dc00cde4e8e08ef35b313525781c135357df3..3b1b1cbbfa7bf348c17c025afa217ffff97087e3 100644 (file)
 #include "tag.h"
 #include "tree-walk.h"
 #include "builtin.h"
-#include <regex.h>
 #include "grep.h"
-#include <fnmatch.h>
-#include <sys/wait.h>
 
 /*
  * git grep pathspecs are somewhat different from diff-tree pathspecs;
@@ -268,7 +265,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
                char *name;
-               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+               if (!S_ISREG(ntohl(ce->ce_mode)))
                        continue;
                if (!pathspec_matches(paths, ce->name))
                        continue;
@@ -280,12 +277,19 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                        memcpy(name + 2, ce->name, len + 1);
                }
                argv[argc++] = name;
-               if (argc < MAXARGS)
+               if (argc < MAXARGS && !ce_stage(ce))
                        continue;
                status = exec_grep(argc, argv);
                if (0 < status)
                        hit = 1;
                argc = nr;
+               if (ce_stage(ce)) {
+                       do {
+                               i++;
+                       } while (i < active_nr &&
+                                !strcmp(ce->name, active_cache[i]->name));
+                       i--; /* compensate for loop control */
+               }
        }
        if (argc > nr) {
                status = exec_grep(argc, argv);
@@ -316,14 +320,24 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 
        for (nr = 0; nr < active_nr; nr++) {
                struct cache_entry *ce = active_cache[nr];
-               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+               if (!S_ISREG(ntohl(ce->ce_mode)))
                        continue;
                if (!pathspec_matches(paths, ce->name))
                        continue;
-               if (cached)
+               if (cached) {
+                       if (ce_stage(ce))
+                               continue;
                        hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
+               }
                else
                        hit |= grep_file(opt, ce->name);
+               if (ce_stage(ce)) {
+                       do {
+                               nr++;
+                       } while (nr < active_nr &&
+                                !strcmp(ce->name, active_cache[nr]->name));
+                       nr--; /* compensate for loop control */
+               }
        }
        free_grep_patterns(opt);
        return hit;
index 235a0ee48f2c5ce09c63a949358eb16cde05332d..01f366ad0bf5860aaf9123ef5d5653c612871a9f 100644 (file)
@@ -124,8 +124,11 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
        int template_len;
        DIR *dir;
 
-       if (!template_dir)
-               template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+       if (!template_dir) {
+               template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
+               if (!template_dir)
+                       template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+       }
        strcpy(template_path, template_dir);
        template_len = strlen(template_path);
        if (template_path[template_len-1] != '/') {
@@ -164,13 +167,14 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
        closedir(dir);
 }
 
-static void create_default_files(const char *git_dir, const char *template_path)
+static int create_default_files(const char *git_dir, const char *template_path)
 {
        unsigned len = strlen(git_dir);
        static char path[PATH_MAX];
        unsigned char sha1[20];
        struct stat st1;
        char repo_version_string[10];
+       int reinit;
 
        if (len > sizeof(path)-50)
                die("insane git directory %s", git_dir);
@@ -218,7 +222,8 @@ static void create_default_files(const char *git_dir, const char *template_path)
         * branch, if it does not exist yet.
         */
        strcpy(path + len, "HEAD");
-       if (read_ref("HEAD", sha1) < 0) {
+       reinit = !read_ref("HEAD", sha1);
+       if (!reinit) {
                if (create_symref("HEAD", "refs/heads/master") < 0)
                        exit(1);
        }
@@ -239,6 +244,11 @@ static void create_default_files(const char *git_dir, const char *template_path)
                git_config_set("core.filemode",
                               filemode ? "true" : "false");
        }
+
+       /* Enable logAllRefUpdates if a working tree is attached */
+       if (!is_bare_git_dir(git_dir))
+               git_config_set("core.logallrefupdates", "true");
+       return reinit;
 }
 
 static const char init_db_usage[] =
@@ -256,7 +266,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        const char *sha1_dir;
        const char *template_dir = NULL;
        char *path;
-       int len, i;
+       int len, i, reinit;
 
        for (i = 1; i < argc; i++, argv++) {
                const char *arg = argv[1];
@@ -274,10 +284,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
         * Set up the default .git directory contents
         */
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
-       if (!git_dir) {
+       if (!git_dir)
                git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-               fprintf(stderr, "defaulting to local storage area\n");
-       }
        safe_create_dir(git_dir, 0);
 
        /* Check to see if the repository version is right.
@@ -287,7 +295,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
         */
        check_repository_format();
 
-       create_default_files(git_dir, template_dir);
+       reinit = create_default_files(git_dir, template_dir);
 
        /*
         * And set up the object store.
@@ -314,5 +322,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                git_config_set("receive.denyNonFastforwards", "true");
        }
 
+       printf("%s%s Git repository in %s/\n",
+               reinit ? "Reinitialized existing" : "Initialized empty",
+               shared_repository ? " shared" : "",
+               git_dir);
+
        return 0;
 }
index fedb0137bc5e3ba50ecb90ce560ce9ce9290d8df..8df3c1394a0b70548708137bfe20126fbea01709 100644 (file)
@@ -10,8 +10,9 @@
 #include "revision.h"
 #include "log-tree.h"
 #include "builtin.h"
-#include <time.h>
-#include <sys/time.h>
+#include "tag.h"
+
+static int default_show_root = 1;
 
 /* this is in builtin-diff.c */
 void add_head(struct rev_info *revs);
@@ -22,6 +23,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
        rev->verbose_header = 1;
+       rev->show_root_diff = default_show_root;
        argc = setup_revisions(argc, argv, rev, "HEAD");
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
                rev->always_show_header = 0;
@@ -44,11 +46,20 @@ static int cmd_log_walk(struct rev_info *rev)
        return 0;
 }
 
+static int git_log_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "log.showroot")) {
+               default_show_root = git_config_bool(var, value);
+               return 0;
+       }
+       return git_diff_ui_config(var, value);
+}
+
 int cmd_whatchanged(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
 
-       git_config(git_diff_ui_config);
+       git_config(git_log_config);
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.diffopt.recursive = 1;
@@ -59,11 +70,45 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
        return cmd_log_walk(&rev);
 }
 
+static int show_object(const unsigned char *sha1, int suppress_header)
+{
+       unsigned long size;
+       char type[20];
+       char *buf = read_sha1_file(sha1, type, &size);
+       int offset = 0;
+
+       if (!buf)
+               return error("Could not read object %s", sha1_to_hex(sha1));
+
+       if (suppress_header)
+               while (offset < size && buf[offset++] != '\n') {
+                       int new_offset = offset;
+                       while (new_offset < size && buf[new_offset++] != '\n')
+                               ; /* do nothing */
+                       offset = new_offset;
+               }
+
+       if (offset < size)
+               fwrite(buf + offset, size - offset, 1, stdout);
+       free(buf);
+       return 0;
+}
+
+static int show_tree_object(const unsigned char *sha1,
+               const char *base, int baselen,
+               const char *pathname, unsigned mode, int stage)
+{
+       printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
+       return 0;
+}
+
 int cmd_show(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
+       struct object_array_entry *objects;
+       int i, count, ret = 0;
 
-       git_config(git_diff_ui_config);
+       git_config(git_log_config);
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.diffopt.recursive = 1;
@@ -73,14 +118,59 @@ int cmd_show(int argc, const char **argv, const char *prefix)
        rev.ignore_merges = 0;
        rev.no_walk = 1;
        cmd_log_init(argc, argv, prefix, &rev);
-       return cmd_log_walk(&rev);
+
+       count = rev.pending.nr;
+       objects = rev.pending.objects;
+       for (i = 0; i < count && !ret; i++) {
+               struct object *o = objects[i].item;
+               const char *name = objects[i].name;
+               switch (o->type) {
+               case OBJ_BLOB:
+                       ret = show_object(o->sha1, 0);
+                       break;
+               case OBJ_TAG: {
+                       struct tag *t = (struct tag *)o;
+
+                       printf("%stag %s%s\n\n",
+                                       diff_get_color(rev.diffopt.color_diff,
+                                               DIFF_COMMIT),
+                                       t->tag,
+                                       diff_get_color(rev.diffopt.color_diff,
+                                               DIFF_RESET));
+                       ret = show_object(o->sha1, 1);
+                       objects[i].item = (struct object *)t->tagged;
+                       i--;
+                       break;
+               }
+               case OBJ_TREE:
+                       printf("%stree %s%s\n\n",
+                                       diff_get_color(rev.diffopt.color_diff,
+                                               DIFF_COMMIT),
+                                       name,
+                                       diff_get_color(rev.diffopt.color_diff,
+                                               DIFF_RESET));
+                       read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
+                                       show_tree_object);
+                       break;
+               case OBJ_COMMIT:
+                       rev.pending.nr = rev.pending.alloc = 0;
+                       rev.pending.objects = NULL;
+                       add_object_array(o, name, &rev.pending);
+                       ret = cmd_log_walk(&rev);
+                       break;
+               default:
+                       ret = error("Unknown type: %d", o->type);
+               }
+       }
+       free(objects);
+       return ret;
 }
 
 int cmd_log(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
 
-       git_config(git_diff_ui_config);
+       git_config(git_log_config);
        init_revisions(&rev, prefix);
        rev.always_show_header = 1;
        cmd_log_init(argc, argv, prefix, &rev);
@@ -106,10 +196,10 @@ static int git_format_config(const char *var, const char *value)
                strcat(extra_headers, value);
                return 0;
        }
-       if (!strcmp(var, "diff.color")) {
+       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                return 0;
        }
-       return git_diff_ui_config(var, value);
+       return git_log_config(var, value);
 }
 
 
index ad8c41e7310032c7a868ecb6a87e542af799c361..21c2a6e2d9b68c0c3bcfac824e85c7b392c72486 100644 (file)
@@ -5,8 +5,6 @@
  *
  * Copyright (C) Linus Torvalds, 2005
  */
-#include <fnmatch.h>
-
 #include "cache.h"
 #include "quote.h"
 #include "dir.h"
@@ -487,10 +485,14 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                for (num = 0; pathspec[num]; num++) {
                        if (ps_matched[num])
                                continue;
-                       error("pathspec '%s' did not match any.",
+                       error("pathspec '%s' did not match any file(s) known to git.",
                              pathspec[num] + prefix_offset);
                        errors++;
                }
+
+               if (errors)
+                       fprintf(stderr, "Did you forget to 'git add'?\n");
+
                return errors ? 1 : 0;
        }
 
index b8d7dbc0b71929a95aaebd79d5912897d5eb70e7..e6472293d47611d415276f6057227d9c93788f63 100644 (file)
@@ -2,15 +2,6 @@
  * Another stupid program, this one parsing the headers of an
  * email to figure out authorship and subject
  */
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#ifndef NO_ICONV
-#include <iconv.h>
-#endif
-#include "git-compat-util.h"
 #include "cache.h"
 #include "builtin.h"
 
index 91a699d34d93462b56f2591193bb5729b42b3950..3bca855aae857cde15f2a249f8e0a5c30b3e7d82 100644 (file)
@@ -4,13 +4,6 @@
  * It just splits a mbox into a list of files: "0001" "0002" ..
  * so you can process them further from there.
  */
-#include <unistd.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <stdio.h>
 #include "cache.h"
 #include "builtin.h"
 
diff --git a/builtin-merge-file.c b/builtin-merge-file.c
new file mode 100644 (file)
index 0000000..6c4c3a3
--- /dev/null
@@ -0,0 +1,79 @@
+#include "cache.h"
+#include "xdiff/xdiff.h"
+
+static const char merge_file_usage[] =
+"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
+
+static int read_file(mmfile_t *ptr, const char *filename)
+{
+       struct stat st;
+       FILE *f;
+
+       if (stat(filename, &st))
+               return error("Could not stat %s", filename);
+       if ((f = fopen(filename, "rb")) == NULL)
+               return error("Could not open %s", filename);
+       ptr->ptr = xmalloc(st.st_size);
+       if (fread(ptr->ptr, st.st_size, 1, f) != 1)
+               return error("Could not read %s", filename);
+       fclose(f);
+       ptr->size = st.st_size;
+       return 0;
+}
+
+int cmd_merge_file(int argc, char **argv, char **envp)
+{
+       char *names[3];
+       mmfile_t mmfs[3];
+       mmbuffer_t result = {NULL, 0};
+       xpparam_t xpp = {XDF_NEED_MINIMAL};
+       int ret = 0, i = 0, to_stdout = 0;
+
+       while (argc > 4) {
+               if (!strcmp(argv[1], "-L") && i < 3) {
+                       names[i++] = argv[2];
+                       argc--;
+                       argv++;
+               } else if (!strcmp(argv[1], "-p") ||
+                               !strcmp(argv[1], "--stdout"))
+                       to_stdout = 1;
+               else if (!strcmp(argv[1], "-q") ||
+                               !strcmp(argv[1], "--quiet"))
+                       freopen("/dev/null", "w", stderr);
+               else
+                       usage(merge_file_usage);
+               argc--;
+               argv++;
+       }
+
+       if (argc != 4)
+               usage(merge_file_usage);
+
+       for (; i < 3; i++)
+               names[i] = argv[i + 1];
+
+       for (i = 0; i < 3; i++)
+               if (read_file(mmfs + i, argv[i + 1]))
+                       return -1;
+
+       ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
+                       &xpp, XDL_MERGE_ZEALOUS, &result);
+
+       for (i = 0; i < 3; i++)
+               free(mmfs[i].ptr);
+
+       if (ret >= 0) {
+               char *filename = argv[1];
+               FILE *f = to_stdout ? stdout : fopen(filename, "wb");
+
+               if (!f)
+                       ret = error("Could not open %s for writing", filename);
+               else if (fwrite(result.ptr, result.size, 1, f) != 1)
+                       ret = error("Could not write to %s", filename);
+               else if (fclose(f))
+                       ret = error("Could not close %s", filename);
+               free(result.ptr);
+       }
+
+       return ret;
+}
index d14a4a7f5c66c5f8712399d4b1fc566e0891125a..737af350b873e90c787cb49960236fc19b62a3bf 100644 (file)
@@ -3,8 +3,6 @@
  *
  * Copyright (C) 2006 Johannes Schindelin
  */
-#include <fnmatch.h>
-
 #include "cache.h"
 #include "builtin.h"
 #include "dir.h"
index 618aa314d22dd16bb62b0a7deb3be14fda411ebe..b4f15cc38ab7fa045fc5cf025ca3fd6251a01973 100644 (file)
@@ -1,4 +1,3 @@
-#include <stdlib.h>
 #include "builtin.h"
 #include "cache.h"
 #include "commit.h"
index 69e5dd39ca6f007d016ca562454cdb75ec9362fb..807be8c3f8a051d9171cf2ee3c9f973f77a2ff08 100644 (file)
@@ -12,8 +12,6 @@
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
-#include <sys/time.h>
-#include <signal.h>
 
 static const char pack_usage[] = "\
 git-pack-objects [{ -q | --progress | --all-progress }] \n\
@@ -514,6 +512,8 @@ static void write_pack_file(void)
        if (do_progress)
                fputc('\n', stderr);
  done:
+       if (written != nr_result)
+               die("wrote %d objects while expecting %d", written, nr_result);
        sha1close(f, pack_file_sha1, 1);
 }
 
@@ -1176,7 +1176,9 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
         * on an earlier try, but only when reusing delta data.
         */
        if (!no_reuse_delta && trg_entry->in_pack &&
-           trg_entry->in_pack == src_entry->in_pack)
+           trg_entry->in_pack == src_entry->in_pack &&
+           trg_entry->in_pack_type != OBJ_REF_DELTA &&
+           trg_entry->in_pack_type != OBJ_OFS_DELTA)
                return 0;
 
        /*
@@ -1660,7 +1662,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
        }
        if (progress)
-               fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n",
-                       nr_result, written, written_delta, reused, reused_delta);
+               fprintf(stderr, "Total %d (delta %d), reused %d (delta %d)\n",
+                       written, written_delta, reused, reused_delta);
        return 0;
 }
index 042d2718f904303600a57cfe8b5de83f5c0d34db..8dc5b9efffc3ad2cd56130155a94aa489d0d0b1b 100644 (file)
@@ -1,5 +1,7 @@
 #include "cache.h"
 #include "refs.h"
+#include "object.h"
+#include "tag.h"
 
 static const char builtin_pack_refs_usage[] =
 "git-pack-refs [--all] [--prune]";
@@ -29,12 +31,26 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
                          int flags, void *cb_data)
 {
        struct pack_refs_cb_data *cb = cb_data;
+       int is_tag_ref;
 
-       if (!cb->all && strncmp(path, "refs/tags/", 10))
-               return 0;
        /* Do not pack the symbolic refs */
-       if (!(flags & REF_ISSYMREF))
-               fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+       if ((flags & REF_ISSYMREF))
+               return 0;
+       is_tag_ref = !strncmp(path, "refs/tags/", 10);
+       if (!cb->all && !is_tag_ref)
+               return 0;
+
+       fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+       if (is_tag_ref) {
+               struct object *o = parse_object(sha1);
+               if (o->type == OBJ_TAG) {
+                       o = deref_tag(o, path, 0);
+                       if (o)
+                               fprintf(cb->refs_file, "^%s\n",
+                                       sha1_to_hex(o->sha1));
+               }
+       }
+
        if (cb->prune && !do_not_prune(flags)) {
                int namelen = strlen(path) + 1;
                struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
@@ -95,6 +111,10 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
        if (!cbdata.refs_file)
                die("unable to create ref-pack file structure (%s)",
                    strerror(errno));
+
+       /* perhaps other traits later as well */
+       fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
+
        for_each_ref(handle_one_ref, &cbdata);
        fflush(cbdata.refs_file);
        fsync(fd);
index d853902c51e78800b76cf4714dffaa79600d3e53..8591d28b8e91c94636e9bf8b7e8ff5abcc0705e9 100644 (file)
@@ -16,8 +16,15 @@ static struct rev_info revs;
 
 static int prune_object(char *path, const char *filename, const unsigned char *sha1)
 {
+       char buf[20];
+       const char *type;
+
        if (show_only) {
-               printf("would prune %s/%s\n", path, filename);
+               if (sha1_object_info(sha1, buf, NULL))
+                       type = "unknown";
+               else
+                       type = buf;
+               printf("%s %s\n", sha1_to_hex(sha1), type);
                return 0;
        }
        unlink(mkpath("%s/%s", path, filename));
index d23974e708cb744f75efbb9f1acbf37606e92b05..b7412e82937a240776cdda07da05797b0b2acb0b 100644 (file)
@@ -57,11 +57,36 @@ static void expand_refspecs(void)
 static void set_refspecs(const char **refs, int nr)
 {
        if (nr) {
-               size_t bytes = nr * sizeof(char *);
-
-               refspec = xrealloc(refspec, bytes);
-               memcpy(refspec, refs, bytes);
-               refspec_nr = nr;
+               int pass;
+               for (pass = 0; pass < 2; pass++) {
+                       /* pass 0 counts and allocates, pass 1 fills */
+                       int i, cnt;
+                       for (i = cnt = 0; i < nr; i++) {
+                               if (!strcmp("tag", refs[i])) {
+                                       int len;
+                                       char *tag;
+                                       if (nr <= ++i)
+                                               die("tag <tag> shorthand without <tag>");
+                                       if (pass) {
+                                               len = strlen(refs[i]) + 11;
+                                               tag = xmalloc(len);
+                                               strcpy(tag, "refs/tags/");
+                                               strcat(tag, refs[i]);
+                                               refspec[cnt] = tag;
+                                       }
+                                       cnt++;
+                                       continue;
+                               }
+                               if (pass)
+                                       refspec[cnt] = refs[i];
+                               cnt++;
+                       }
+                       if (!pass) {
+                               size_t bytes = cnt * sizeof(char *);
+                               refspec_nr = cnt;
+                               refspec = xrealloc(refspec, bytes);
+                       }
+               }
        }
        expand_refspecs();
 }
index c1867d2a0052ad797481f3d5f9aa544952ff251b..8ba436dbace7ec25ec5768a5c6c84f3202f92159 100644 (file)
@@ -10,6 +10,7 @@
 #include "tree-walk.h"
 #include "cache-tree.h"
 #include "unpack-trees.h"
+#include "dir.h"
 #include "builtin.h"
 
 static struct object_list *trees;
@@ -84,7 +85,7 @@ static void prime_cache_tree(void)
 
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <sha1> [<sha2> [<sha3>]])";
 
 static struct lock_file lock_file;
 
@@ -178,6 +179,23 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        continue;
                }
 
+               if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+                       struct dir_struct *dir;
+
+                       if (opts.dir)
+                               die("more than one --exclude-per-directory are given.");
+
+                       dir = calloc(1, sizeof(*opts.dir));
+                       dir->show_ignored = 1;
+                       dir->exclude_per_dir = arg + 24;
+                       opts.dir = dir;
+                       /* We do not need to nor want to do read-directory
+                        * here; we are merely interested in reusing the
+                        * per directory ignore stack mechanism.
+                        */
+                       continue;
+               }
+
                /* using -u and -i at the same time makes no sense */
                if (1 < opts.index_only + opts.update)
                        usage(read_tree_usage);
@@ -190,6 +208,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        }
        if ((opts.update||opts.index_only) && !opts.merge)
                usage(read_tree_usage);
+       if ((opts.dir && !opts.update))
+               die("--exclude-per-directory is meaningless unless -u");
 
        if (opts.prefix) {
                int pfxlen = strlen(opts.prefix);
index 7b6e5725ae6d33350a9648e57139b646ab49696e..90633119d4ae873ed30889e93dbb4307af0ae0d1 100644 (file)
@@ -1,9 +1,8 @@
 #include "builtin.h"
 #include "cache.h"
-#include <regex.h>
 
 static const char git_config_set_usage[] =
-"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --list";
 
 static char *key;
 static regex_t *key_regexp;
@@ -67,10 +66,10 @@ static int get_value(const char* key_, const char* regex_)
        char *global = NULL, *repo_config = NULL;
        const char *local;
 
-       local = getenv("GIT_CONFIG");
+       local = getenv(CONFIG_ENVIRONMENT);
        if (!local) {
                const char *home = getenv("HOME");
-               local = getenv("GIT_CONFIG_LOCAL");
+               local = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!local)
                        local = repo_config = xstrdup(git_path("config"));
                if (home)
@@ -148,6 +147,18 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
                        } else {
                                die("$HOME not set");
                        }
+               } else if (!strcmp(argv[1], "--rename-section")) {
+                       int ret;
+                       if (argc != 4)
+                               usage(git_config_set_usage);
+                       ret = git_config_rename_section(argv[2], argv[3]);
+                       if (ret < 0)
+                               return ret;
+                       if (ret == 0) {
+                               fprintf(stderr, "No such section!\n");
+                               return 1;
+                       }
+                       return 0;
                } else
                        break;
                argc--;
@@ -190,7 +201,9 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
                        use_key_regexp = 1;
                        do_all = 1;
                        return get_value(argv[2], argv[3]);
-               } else if (!strcmp(argv[1], "--replace-all"))
+               } else if (!strcmp(argv[1], "--add"))
+                       return git_config_set_multivar(argv[2], argv[3], "^$", 0);
+               else if (!strcmp(argv[1], "--replace-all"))
 
                        return git_config_set_multivar(argv[2], argv[3], NULL, 1);
                else
index fb7fc92145b6a8baec340176abf40c6b37f45a8e..1bb3a06680194ce5855185ad1a1c7e39eeebba94 100644 (file)
@@ -54,6 +54,12 @@ static void show_commit(struct commit *commit)
                fputs(header_prefix, stdout);
        if (commit->object.flags & BOUNDARY)
                putchar('-');
+       else if (revs.left_right) {
+               if (commit->object.flags & SYMMETRIC_LEFT)
+                       putchar('<');
+               else
+                       putchar('>');
+       }
        if (revs.abbrev_commit && revs.abbrev)
                fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
                      stdout);
index 0b63037dd04e7c66d2e378ee394979cec040fe55..4b489b1214e1ae693a9b0b5166909800f5ca94ac 100644 (file)
@@ -1,5 +1,5 @@
-#include "wt-status.h"
 #include "cache.h"
+#include "wt-status.h"
 
 extern int wt_status_use_color;
 
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
new file mode 100644 (file)
index 0000000..edb4042
--- /dev/null
@@ -0,0 +1,341 @@
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "path-list.h"
+#include "revision.h"
+
+static const char shortlog_usage[] =
+"git-shortlog [-n] [-s] [<commit-id>... ]";
+
+static char *common_repo_prefix;
+
+static int compare_by_number(const void *a1, const void *a2)
+{
+       const struct path_list_item *i1 = a1, *i2 = a2;
+       const struct path_list *l1 = i1->util, *l2 = i2->util;
+
+       if (l1->nr < l2->nr)
+               return 1;
+       else if (l1->nr == l2->nr)
+               return 0;
+       else
+               return -1;
+}
+
+static struct path_list mailmap = {NULL, 0, 0, 0};
+
+static int read_mailmap(const char *filename)
+{
+       char buffer[1024];
+       FILE *f = fopen(filename, "r");
+
+       if (f == NULL)
+               return 1;
+       while (fgets(buffer, sizeof(buffer), f) != NULL) {
+               char *end_of_name, *left_bracket, *right_bracket;
+               char *name, *email;
+               int i;
+               if (buffer[0] == '#') {
+                       static const char abbrev[] = "# repo-abbrev:";
+                       int abblen = sizeof(abbrev) - 1;
+                       int len = strlen(buffer);
+
+                       if (len && buffer[len - 1] == '\n')
+                               buffer[--len] = 0;
+                       if (!strncmp(buffer, abbrev, abblen)) {
+                               char *cp;
+
+                               if (common_repo_prefix)
+                                       free(common_repo_prefix);
+                               common_repo_prefix = xmalloc(len);
+
+                               for (cp = buffer + abblen; isspace(*cp); cp++)
+                                       ; /* nothing */
+                               strcpy(common_repo_prefix, cp);
+                       }
+                       continue;
+               }
+               if ((left_bracket = strchr(buffer, '<')) == NULL)
+                       continue;
+               if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
+                       continue;
+               if (right_bracket == left_bracket + 1)
+                       continue;
+               for (end_of_name = left_bracket; end_of_name != buffer
+                               && isspace(end_of_name[-1]); end_of_name--)
+                       /* keep on looking */
+               if (end_of_name == buffer)
+                       continue;
+               name = xmalloc(end_of_name - buffer + 1);
+               strlcpy(name, buffer, end_of_name - buffer + 1);
+               email = xmalloc(right_bracket - left_bracket);
+               for (i = 0; i < right_bracket - left_bracket - 1; i++)
+                       email[i] = tolower(left_bracket[i + 1]);
+               email[right_bracket - left_bracket - 1] = '\0';
+               path_list_insert(email, &mailmap)->util = name;
+       }
+       fclose(f);
+       return 0;
+}
+
+static int map_email(char *email, char *name, int maxlen)
+{
+       char *p;
+       struct path_list_item *item;
+
+       /* autocomplete common developers */
+       p = strchr(email, '>');
+       if (!p)
+               return 0;
+
+       *p = '\0';
+       /* downcase the email address */
+       for (p = email; *p; p++)
+               *p = tolower(*p);
+       item = path_list_lookup(email, &mailmap);
+       if (item != NULL) {
+               const char *realname = (const char *)item->util;
+               strncpy(name, realname, maxlen);
+               return 1;
+       }
+       return 0;
+}
+
+static void insert_author_oneline(struct path_list *list,
+               const char *author, int authorlen,
+               const char *oneline, int onelinelen)
+{
+       const char *dot3 = common_repo_prefix;
+       char *buffer, *p;
+       struct path_list_item *item;
+       struct path_list *onelines;
+
+       while (authorlen > 0 && isspace(author[authorlen - 1]))
+               authorlen--;
+
+       buffer = xmalloc(authorlen + 1);
+       memcpy(buffer, author, authorlen);
+       buffer[authorlen] = '\0';
+
+       item = path_list_insert(buffer, list);
+       if (item->util == NULL)
+               item->util = xcalloc(1, sizeof(struct path_list));
+       else
+               free(buffer);
+
+       if (!strncmp(oneline, "[PATCH", 6)) {
+               char *eob = strchr(oneline, ']');
+
+               if (eob) {
+                       while (isspace(eob[1]) && eob[1] != '\n')
+                               eob++;
+                       if (eob - oneline < onelinelen) {
+                               onelinelen -= eob - oneline;
+                               oneline = eob;
+                       }
+               }
+       }
+
+       while (onelinelen > 0 && isspace(oneline[0])) {
+               oneline++;
+               onelinelen--;
+       }
+
+       while (onelinelen > 0 && isspace(oneline[onelinelen - 1]))
+               onelinelen--;
+
+       buffer = xmalloc(onelinelen + 1);
+       memcpy(buffer, oneline, onelinelen);
+       buffer[onelinelen] = '\0';
+
+       if (dot3) {
+               int dot3len = strlen(dot3);
+               if (dot3len > 5) {
+                       while ((p = strstr(buffer, dot3)) != NULL) {
+                               int taillen = strlen(p) - dot3len;
+                               memcpy(p, "/.../", 5);
+                               memmove(p + 5, p + dot3len, taillen + 1);
+                       }
+               }
+       }
+
+       onelines = item->util;
+       if (onelines->nr >= onelines->alloc) {
+               onelines->alloc = alloc_nr(onelines->nr);
+               onelines->items = xrealloc(onelines->items,
+                               onelines->alloc
+                               * sizeof(struct path_list_item));
+       }
+
+       onelines->items[onelines->nr].util = NULL;
+       onelines->items[onelines->nr++].path = buffer;
+}
+
+static void read_from_stdin(struct path_list *list)
+{
+       char buffer[1024];
+
+       while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
+               char *bob;
+               if ((buffer[0] == 'A' || buffer[0] == 'a') &&
+                               !strncmp(buffer + 1, "uthor: ", 7) &&
+                               (bob = strchr(buffer + 7, '<')) != NULL) {
+                       char buffer2[1024], offset = 0;
+
+                       if (map_email(bob + 1, buffer, sizeof(buffer)))
+                               bob = buffer + strlen(buffer);
+                       else {
+                               offset = 8;
+                               while (buffer + offset < bob &&
+                                      isspace(bob[-1]))
+                                       bob--;
+                       }
+
+                       while (fgets(buffer2, sizeof(buffer2), stdin) &&
+                                       buffer2[0] != '\n')
+                               ; /* chomp input */
+                       if (fgets(buffer2, sizeof(buffer2), stdin)) {
+                               int l2 = strlen(buffer2);
+                               int i;
+                               for (i = 0; i < l2; i++)
+                                       if (!isspace(buffer2[i]))
+                                               break;
+                               insert_author_oneline(list,
+                                               buffer + offset,
+                                               bob - buffer - offset,
+                                               buffer2 + i, l2 - i);
+                       }
+               }
+       }
+}
+
+static void get_from_rev(struct rev_info *rev, struct path_list *list)
+{
+       char scratch[1024];
+       struct commit *commit;
+
+       prepare_revision_walk(rev);
+       while ((commit = get_revision(rev)) != NULL) {
+               char *author = NULL, *oneline, *buffer;
+               int authorlen = authorlen, onelinelen;
+
+               /* get author and oneline */
+               for (buffer = commit->buffer; buffer && *buffer != '\0' &&
+                               *buffer != '\n'; ) {
+                       char *eol = strchr(buffer, '\n');
+
+                       if (eol == NULL)
+                               eol = buffer + strlen(buffer);
+                       else
+                               eol++;
+
+                       if (!strncmp(buffer, "author ", 7)) {
+                               char *bracket = strchr(buffer, '<');
+
+                               if (bracket == NULL || bracket > eol)
+                                       die("Invalid commit buffer: %s",
+                                           sha1_to_hex(commit->object.sha1));
+
+                               if (map_email(bracket + 1, scratch,
+                                                       sizeof(scratch))) {
+                                       author = scratch;
+                                       authorlen = strlen(scratch);
+                               } else {
+                                       if (bracket[-1] == ' ')
+                                               bracket--;
+
+                                       author = buffer + 7;
+                                       authorlen = bracket - buffer - 7;
+                               }
+                       }
+                       buffer = eol;
+               }
+
+               if (author == NULL)
+                       die ("Missing author: %s",
+                                       sha1_to_hex(commit->object.sha1));
+
+               if (buffer == NULL || *buffer == '\0') {
+                       oneline = "<none>";
+                       onelinelen = sizeof(oneline) + 1;
+               } else {
+                       char *eol;
+
+                       oneline = buffer + 1;
+                       eol = strchr(oneline, '\n');
+                       if (eol == NULL)
+                               onelinelen = strlen(oneline);
+                       else
+                               onelinelen = eol - oneline;
+               }
+
+               insert_author_oneline(list,
+                               author, authorlen, oneline, onelinelen);
+       }
+
+}
+
+int cmd_shortlog(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info rev;
+       struct path_list list = { NULL, 0, 0, 1 };
+       int i, j, sort_by_number = 0, summary = 0;
+
+       /* since -n is a shadowed rev argument, parse our args first */
+       while (argc > 1) {
+               if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
+                       sort_by_number = 1;
+               else if (!strcmp(argv[1], "-s") ||
+                               !strcmp(argv[1], "--summary"))
+                       summary = 1;
+               else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
+                       usage(shortlog_usage);
+               else
+                       break;
+               argv++;
+               argc--;
+       }
+       init_revisions(&rev, prefix);
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       if (argc > 1)
+               die ("unrecognized argument: %s", argv[1]);
+
+       if (!access(".mailmap", R_OK))
+               read_mailmap(".mailmap");
+
+       if (rev.pending.nr == 0)
+               read_from_stdin(&list);
+       else
+               get_from_rev(&rev, &list);
+
+       if (sort_by_number)
+               qsort(list.items, list.nr, sizeof(struct path_list_item),
+                       compare_by_number);
+
+       for (i = 0; i < list.nr; i++) {
+               struct path_list *onelines = list.items[i].util;
+
+               if (summary) {
+                       printf("%s: %d\n", list.items[i].path, onelines->nr);
+               } else {
+                       printf("%s (%d):\n", list.items[i].path, onelines->nr);
+                       for (j = onelines->nr - 1; j >= 0; j--)
+                               printf("      %s\n", onelines->items[j].path);
+                       printf("\n");
+               }
+
+               onelines->strdup_paths = 1;
+               path_list_clear(onelines, 1);
+               free(onelines);
+               list.items[i].util = NULL;
+       }
+
+       list.strdup_paths = 1;
+       path_list_clear(&list, 1);
+       mailmap.strdup_paths = 1;
+       path_list_clear(&mailmap, 1);
+
+       return 0;
+}
+
index fb1a4000d98e27389904578309b935cbda2a85fe..b9d9781d4d6a12860d377607369a4dea95a3c7be 100644 (file)
@@ -1,12 +1,10 @@
-#include <stdlib.h>
-#include <fnmatch.h>
 #include "cache.h"
 #include "commit.h"
 #include "refs.h"
 #include "builtin.h"
 
 static const char show_branch_usage[] =
-"git-show-branch [--sparse] [--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>...] | --reflog[=n] <branch>";
 
 static int default_num;
 static int default_alloc;
@@ -17,6 +15,8 @@ static const char **default_arg;
 #define REV_SHIFT       2
 #define MAX_REVS       (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
 
+#define DEFAULT_REFLOG 4
+
 static struct commit *interesting(struct commit_list *list)
 {
        while (list) {
@@ -570,6 +570,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        int head_at = -1;
        int topics = 0;
        int dense = 1;
+       int reflog = 0;
 
        git_config(git_show_branch_config);
 
@@ -615,6 +616,15 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        dense = 0;
                else if (!strcmp(arg, "--date-order"))
                        lifo = 0;
+               else if (!strcmp(arg, "--reflog")) {
+                       reflog = DEFAULT_REFLOG;
+               }
+               else if (!strncmp(arg, "--reflog=", 9)) {
+                       char *end;
+                       reflog = strtoul(arg + 9, &end, 10);
+                       if (*end != '\0')
+                               die("unrecognized reflog count '%s'", arg + 9);
+               }
                else
                        usage(show_branch_usage);
                ac--; av++;
@@ -622,7 +632,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        ac--; av++;
 
        /* Only one of these is allowed */
-       if (1 < independent + merge_base + (extra != 0))
+       if (1 < independent + merge_base + (extra != 0) + (!!reflog))
                usage(show_branch_usage);
 
        /* If nothing is specified, show all branches by default */
@@ -631,9 +641,22 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 
        if (all_heads + all_tags)
                snarf_refs(all_heads, all_tags);
-       while (0 < ac) {
-               append_one_rev(*av);
-               ac--; av++;
+       if (reflog) {
+               int reflen;
+               if (!ac)
+                       die("--reflog option needs one branch name");
+               reflen = strlen(*av);
+               for (i = 0; i < reflog; i++) {
+                       char *name = xmalloc(reflen + 20);
+                       sprintf(name, "%s@{%d}", *av, i);
+                       append_one_rev(name);
+               }
+       }
+       else {
+               while (0 < ac) {
+                       append_one_rev(*av);
+                       ac--; av++;
+               }
        }
 
        head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
index 06ec400d7f3f2e589111a561a6b18da067a02f1f..853f13f6ae9df60e340a6988a9d74bb246df4df6 100644 (file)
@@ -2,17 +2,28 @@
 #include "refs.h"
 #include "object.h"
 #include "tag.h"
+#include "path-list.h"
 
-static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]";
+static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*] < ref-list";
 
 static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0,
        found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0;
 static const char **pattern;
 
+static void show_one(const char *refname, const unsigned char *sha1)
+{
+       const char *hex = find_unique_abbrev(sha1, abbrev);
+       if (hash_only)
+               printf("%s\n", hex);
+       else
+               printf("%s %s\n", hex, refname);
+}
+
 static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
 {
        struct object *obj;
        const char *hex;
+       unsigned char peeled[20];
 
        if (tags_only || heads_only) {
                int match;
@@ -44,24 +55,93 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
 
 match:
        found_match++;
-       obj = parse_object(sha1);
-       if (!obj) {
-               if (quiet)
-                       return 0;
-               die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1));
-       }
+
+       /* This changes the semantics slightly that even under quiet we
+        * detect and return error if the repository is corrupt and
+        * ref points at a nonexistent object.
+        */
+       if (!has_sha1_file(sha1))
+               die("git-show-ref: bad ref %s (%s)", refname,
+                   sha1_to_hex(sha1));
+
        if (quiet)
                return 0;
 
-       hex = find_unique_abbrev(sha1, abbrev);
-       if (hash_only)
-               printf("%s\n", hex);
-       else
-               printf("%s %s\n", hex, refname);
-       if (deref_tags && obj->type == OBJ_TAG) {
-               obj = deref_tag(obj, refname, 0);
-               hex = find_unique_abbrev(obj->sha1, abbrev);
-               printf("%s %s^{}\n", hex, refname);
+       show_one(refname, sha1);
+
+       if (!deref_tags)
+               return 0;
+
+       if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) {
+               if (!is_null_sha1(peeled)) {
+                       hex = find_unique_abbrev(peeled, abbrev);
+                       printf("%s %s^{}\n", hex, refname);
+               }
+       }
+       else {
+               obj = parse_object(sha1);
+               if (!obj)
+                       die("git-show-ref: bad ref %s (%s)", refname,
+                           sha1_to_hex(sha1));
+               if (obj->type == OBJ_TAG) {
+                       obj = deref_tag(obj, refname, 0);
+                       hex = find_unique_abbrev(obj->sha1, abbrev);
+                       printf("%s %s^{}\n", hex, refname);
+               }
+       }
+       return 0;
+}
+
+static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+       struct path_list *list = (struct path_list *)cbdata;
+       path_list_insert(refname, list);
+       return 0;
+}
+
+/*
+ * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
+ * and
+ * (1) strip "^{}" at the end of line if any;
+ * (2) ignore if match is provided and does not head-match refname;
+ * (3) warn if refname is not a well-formed refname and skip;
+ * (4) ignore if refname is a ref that exists in the local repository;
+ * (5) otherwise output the line.
+ */
+static int exclude_existing(const char *match)
+{
+       static struct path_list existing_refs = { NULL, 0, 0, 0 };
+       char buf[1024];
+       int matchlen = match ? strlen(match) : 0;
+
+       for_each_ref(add_existing, &existing_refs);
+       while (fgets(buf, sizeof(buf), stdin)) {
+               char *ref;
+               int len = strlen(buf);
+
+               if (len > 0 && buf[len - 1] == '\n')
+                       buf[--len] = '\0';
+               if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
+                       len -= 3;
+                       buf[len] = '\0';
+               }
+               for (ref = buf + len; buf < ref; ref--)
+                       if (isspace(ref[-1]))
+                               break;
+               if (match) {
+                       int reflen = buf + len - ref;
+                       if (reflen < matchlen)
+                               continue;
+                       if (strncmp(ref, match, matchlen))
+                               continue;
+               }
+               if (check_ref_format(ref)) {
+                       fprintf(stderr, "warning: ref '%s' ignored\n", ref);
+                       continue;
+               }
+               if (!path_list_has_path(&existing_refs, ref)) {
+                       printf("%s\n", buf);
+               }
        }
        return 0;
 }
@@ -101,13 +181,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
                if (!strncmp(arg, "--hash=", 7) ||
                    (!strncmp(arg, "--abbrev", 8) &&
                     (arg[8] == '=' || arg[8] == '\0'))) {
-                       if (arg[3] != 'h' && !arg[8])
+                       if (arg[2] != 'h' && !arg[8])
                                /* --abbrev only */
                                abbrev = DEFAULT_ABBREV;
                        else {
                                /* --hash= or --abbrev= */
                                char *end;
-                               if (arg[3] == 'h') {
+                               if (arg[2] == 'h') {
                                        hash_only = 1;
                                        arg += 7;
                                }
@@ -133,8 +213,31 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
                        heads_only = 1;
                        continue;
                }
+               if (!strcmp(arg, "--exclude-existing"))
+                       return exclude_existing(NULL);
+               if (!strncmp(arg, "--exclude-existing=", 19))
+                       return exclude_existing(arg + 19);
                usage(show_ref_usage);
        }
+
+       if (verify) {
+               unsigned char sha1[20];
+
+               while (*pattern) {
+                       if (!strncmp(*pattern, "refs/", 5) &&
+                           resolve_ref(*pattern, sha1, 1, NULL)) {
+                               if (!quiet)
+                                       show_one(*pattern, sha1);
+                       }
+                       else if (!quiet)
+                               die("'%s' - not a valid ref", *pattern);
+                       else
+                               return 1;
+                       pattern++;
+               }
+               return 0;
+       }
+
        if (show_head)
                head_ref(show_ref, NULL);
        for_each_ref(show_ref, NULL);
index 09cc9108cdaf2aa20dc10788bccd2fee0ae9b02a..f0d4d9e2d10446a229094c991363f5a17ce4c666 100644 (file)
@@ -1,6 +1,3 @@
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
 #include "builtin.h"
 
 /*
index 4d4cfec878e468b8ab8a7f055c836c3569443914..11e62fc141f592977373630c7671d3e7e914c314 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (c) 2005, 2006 Rene Scharfe
  */
-#include <time.h>
 #include "cache.h"
 #include "commit.h"
 #include "tar.h"
index e6d75748444ef4fc263970d4fe87b4623d2790fc..d351e02649401364e7384d0e784901e6f3fcb119 100644 (file)
@@ -8,8 +8,6 @@
 #include "tag.h"
 #include "tree.h"
 
-#include <sys/time.h>
-
 static int dry_run, quiet, recover, has_errors;
 static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file";
 
index 45c92e163c477af4ec7c36c8ee6fcfd9242a0b2d..e4156f8f48aeed307b1754bada173034dd81a311 100644 (file)
@@ -1,9 +1,6 @@
 /*
  * Copyright (c) 2006 Franck Bui-Huu
  */
-#include <time.h>
-#include <sys/wait.h>
-#include <sys/poll.h>
 #include "cache.h"
 #include "builtin.h"
 #include "archive.h"
index 43fed329ba58a8adcdac9cb2260ad3370d9ad6cc..08519e7c828b7993a54b4603bf5da0b03b972653 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -42,6 +42,7 @@ extern int cmd_ls_files(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
@@ -55,6 +56,7 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
 extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
+extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index f2ec5c8c1416e3167d49813965212d414e6a5a7a..4943056c19ffb72a7cfb994daaa788ec1b01d60b 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -122,7 +122,12 @@ extern int cache_errno;
 #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
 #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
 #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
+#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
+#define CONFIG_ENVIRONMENT "GIT_CONFIG"
+#define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
+#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
 
+extern int is_bare_git_dir(const char *dir);
 extern const char *get_git_dir(void);
 extern char *get_object_directory(void);
 extern char *get_refs_directory(void);
@@ -308,6 +313,7 @@ void datestamp(char *buf, int bufsize);
 unsigned long approxidate(const char *);
 
 extern int setup_ident(void);
+extern void ignore_missing_committer_name();
 extern const char *git_author_info(int);
 extern const char *git_committer_info(int);
 
@@ -403,6 +409,7 @@ extern int git_config_int(const char *, const char *);
 extern int git_config_bool(const char *, const char *);
 extern int git_config_set(const char *, const char *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
+extern int git_config_rename_section(const char *, const char *);
 extern int check_repository_format_version(const char *var, const char *value);
 
 #define MAX_GITNAME (1000)
diff --git a/color.c b/color.c
index d8c8399d5916432eaa4122175f5e5d7ce209001b..09d82eec3d0adf5b7bd8828b0c8df0554695f75c 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,8 +1,5 @@
-#include "color.h"
 #include "cache.h"
-#include "git-compat-util.h"
-
-#include <stdarg.h>
+#include "color.h"
 
 #define COLOR_RESET "\033[m"
 
index a6d543eee7831cd6a479d4cf8b2ba81a1438b298..289ef65eb1162ff8f386bf31fa6ee27008bb3096 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -868,11 +868,11 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
 
 /* 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)
+/* bits #0..15 in revision.h */
+#define PARENT1                (1u<<16)
+#define PARENT2                (1u<<17)
+#define STALE          (1u<<18)
+#define RESULT         (1u<<19)
 
 static struct commit *interesting(struct commit_list *list)
 {
index ec8c1bff539232c581fd10ef1d01c345442a2e35..4d7ab9d9758428c003a74d9f85699d7fc6922e05 100644 (file)
@@ -93,7 +93,7 @@ inet_ntop6(src, dst, size)
         */
        char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
        struct { int base, len; } best, cur;
-       u_int words[NS_IN6ADDRSZ / NS_INT16SZ];
+       unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ];
        int i;
 
        /*
index a4d2e507f73c5595a2ca76c0369349cc11e2426f..0fd46e793d088e6567b20c7e81a5cde7023bdb58 100644 (file)
@@ -1,7 +1,3 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
 #include "../git-compat-util.h"
 
 void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
index b7d76785980b81a6f1057d678d34a732f45ca4cd..3a22ea7b751efb768d72afa2f97fd963e10eec7e 100644 (file)
@@ -1,5 +1,4 @@
-#include <stdlib.h>
-#include <string.h>
+#include "../git-compat-util.h"
 
 int gitsetenv(const char *name, const char *value, int replace)
 {
index b66856a3a50a93262ee4b5bd0294bb0c7fc85b15..4024c360301ebe7d58ac5b84dcbb692341b649ed 100644 (file)
@@ -1,4 +1,4 @@
-#include <string.h>
+#include "../git-compat-util.h"
 
 size_t gitstrlcpy(char *dest, const char *src, size_t size)
 {
diff --git a/compat/subprocess.py b/compat/subprocess.py
deleted file mode 100644 (file)
index 6474eab..0000000
+++ /dev/null
@@ -1,1149 +0,0 @@
-# subprocess - Subprocesses with accessible I/O streams
-#
-# For more information about this module, see PEP 324.
-#
-# This module should remain compatible with Python 2.2, see PEP 291.
-#
-# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se>
-#
-# Licensed to PSF under a Contributor Agreement.
-# See http://www.python.org/2.4/license for licensing details.
-
-r"""subprocess - Subprocesses with accessible I/O streams
-
-This module allows you to spawn processes, connect to their
-input/output/error pipes, and obtain their return codes.  This module
-intends to replace several other, older modules and functions, like:
-
-os.system
-os.spawn*
-os.popen*
-popen2.*
-commands.*
-
-Information about how the subprocess module can be used to replace these
-modules and functions can be found below.
-
-
-
-Using the subprocess module
-===========================
-This module defines one class called Popen:
-
-class Popen(args, bufsize=0, executable=None,
-            stdin=None, stdout=None, stderr=None,
-            preexec_fn=None, close_fds=False, shell=False,
-            cwd=None, env=None, universal_newlines=False,
-            startupinfo=None, creationflags=0):
-
-
-Arguments are:
-
-args should be a string, or a sequence of program arguments.  The
-program to execute is normally the first item in the args sequence or
-string, but can be explicitly set by using the executable argument.
-
-On UNIX, with shell=False (default): In this case, the Popen class
-uses os.execvp() to execute the child program.  args should normally
-be a sequence.  A string will be treated as a sequence with the string
-as the only item (the program to execute).
-
-On UNIX, with shell=True: If args is a string, it specifies the
-command string to execute through the shell.  If args is a sequence,
-the first item specifies the command string, and any additional items
-will be treated as additional shell arguments.
-
-On Windows: the Popen class uses CreateProcess() to execute the child
-program, which operates on strings.  If args is a sequence, it will be
-converted to a string using the list2cmdline method.  Please note that
-not all MS Windows applications interpret the command line the same
-way: The list2cmdline is designed for applications using the same
-rules as the MS C runtime.
-
-bufsize, if given, has the same meaning as the corresponding argument
-to the built-in open() function: 0 means unbuffered, 1 means line
-buffered, any other positive value means use a buffer of
-(approximately) that size.  A negative bufsize means to use the system
-default, which usually means fully buffered.  The default value for
-bufsize is 0 (unbuffered).
-
-stdin, stdout and stderr specify the executed programs' standard
-input, standard output and standard error file handles, respectively.
-Valid values are PIPE, an existing file descriptor (a positive
-integer), an existing file object, and None.  PIPE indicates that a
-new pipe to the child should be created.  With None, no redirection
-will occur; the child's file handles will be inherited from the
-parent.  Additionally, stderr can be STDOUT, which indicates that the
-stderr data from the applications should be captured into the same
-file handle as for stdout.
-
-If preexec_fn is set to a callable object, this object will be called
-in the child process just before the child is executed.
-
-If close_fds is true, all file descriptors except 0, 1 and 2 will be
-closed before the child process is executed.
-
-if shell is true, the specified command will be executed through the
-shell.
-
-If cwd is not None, the current directory will be changed to cwd
-before the child is executed.
-
-If env is not None, it defines the environment variables for the new
-process.
-
-If universal_newlines is true, the file objects stdout and stderr are
-opened as a text files, but lines may be terminated by any of '\n',
-the Unix end-of-line convention, '\r', the Macintosh convention or
-'\r\n', the Windows convention.  All of these external representations
-are seen as '\n' by the Python program.  Note: This feature is only
-available if Python is built with universal newline support (the
-default).  Also, the newlines attribute of the file objects stdout,
-stdin and stderr are not updated by the communicate() method.
-
-The startupinfo and creationflags, if given, will be passed to the
-underlying CreateProcess() function.  They can specify things such as
-appearance of the main window and priority for the new process.
-(Windows only)
-
-
-This module also defines two shortcut functions:
-
-call(*args, **kwargs):
-    Run command with arguments.  Wait for command to complete, then
-    return the returncode attribute.
-
-    The arguments are the same as for the Popen constructor.  Example:
-
-    retcode = call(["ls", "-l"])
-
-
-Exceptions
-----------
-Exceptions raised in the child process, before the new program has
-started to execute, will be re-raised in the parent.  Additionally,
-the exception object will have one extra attribute called
-'child_traceback', which is a string containing traceback information
-from the childs point of view.
-
-The most common exception raised is OSError.  This occurs, for
-example, when trying to execute a non-existent file.  Applications
-should prepare for OSErrors.
-
-A ValueError will be raised if Popen is called with invalid arguments.
-
-
-Security
---------
-Unlike some other popen functions, this implementation will never call
-/bin/sh implicitly.  This means that all characters, including shell
-metacharacters, can safely be passed to child processes.
-
-
-Popen objects
-=============
-Instances of the Popen class have the following methods:
-
-poll()
-    Check if child process has terminated.  Returns returncode
-    attribute.
-
-wait()
-    Wait for child process to terminate.  Returns returncode attribute.
-
-communicate(input=None)
-    Interact with process: Send data to stdin.  Read data from stdout
-    and stderr, until end-of-file is reached.  Wait for process to
-    terminate.  The optional stdin argument should be a string to be
-    sent to the child process, or None, if no data should be sent to
-    the child.
-
-    communicate() returns a tuple (stdout, stderr).
-
-    Note: The data read is buffered in memory, so do not use this
-    method if the data size is large or unlimited.
-
-The following attributes are also available:
-
-stdin
-    If the stdin argument is PIPE, this attribute is a file object
-    that provides input to the child process.  Otherwise, it is None.
-
-stdout
-    If the stdout argument is PIPE, this attribute is a file object
-    that provides output from the child process.  Otherwise, it is
-    None.
-
-stderr
-    If the stderr argument is PIPE, this attribute is file object that
-    provides error output from the child process.  Otherwise, it is
-    None.
-
-pid
-    The process ID of the child process.
-
-returncode
-    The child return code.  A None value indicates that the process
-    hasn't terminated yet.  A negative value -N indicates that the
-    child was terminated by signal N (UNIX only).
-
-
-Replacing older functions with the subprocess module
-====================================================
-In this section, "a ==> b" means that b can be used as a replacement
-for a.
-
-Note: All functions in this section fail (more or less) silently if
-the executed program cannot be found; this module raises an OSError
-exception.
-
-In the following examples, we assume that the subprocess module is
-imported with "from subprocess import *".
-
-
-Replacing /bin/sh shell backquote
----------------------------------
-output=`mycmd myarg`
-==>
-output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0]
-
-
-Replacing shell pipe line
--------------------------
-output=`dmesg | grep hda`
-==>
-p1 = Popen(["dmesg"], stdout=PIPE)
-p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
-output = p2.communicate()[0]
-
-
-Replacing os.system()
----------------------
-sts = os.system("mycmd" + " myarg")
-==>
-p = Popen("mycmd" + " myarg", shell=True)
-sts = os.waitpid(p.pid, 0)
-
-Note:
-
-* Calling the program through the shell is usually not required.
-
-* It's easier to look at the returncode attribute than the
-  exitstatus.
-
-A more real-world example would look like this:
-
-try:
-    retcode = call("mycmd" + " myarg", shell=True)
-    if retcode < 0:
-        print >>sys.stderr, "Child was terminated by signal", -retcode
-    else:
-        print >>sys.stderr, "Child returned", retcode
-except OSError, e:
-    print >>sys.stderr, "Execution failed:", e
-
-
-Replacing os.spawn*
--------------------
-P_NOWAIT example:
-
-pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg")
-==>
-pid = Popen(["/bin/mycmd", "myarg"]).pid
-
-
-P_WAIT example:
-
-retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg")
-==>
-retcode = call(["/bin/mycmd", "myarg"])
-
-
-Vector example:
-
-os.spawnvp(os.P_NOWAIT, path, args)
-==>
-Popen([path] + args[1:])
-
-
-Environment example:
-
-os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env)
-==>
-Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"})
-
-
-Replacing os.popen*
--------------------
-pipe = os.popen(cmd, mode='r', bufsize)
-==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout
-
-pipe = os.popen(cmd, mode='w', bufsize)
-==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
-
-
-(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
-          stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdin, child_stdout) = (p.stdin, p.stdout)
-
-
-(child_stdin,
- child_stdout,
- child_stderr) = os.popen3(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
-          stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
-(child_stdin,
- child_stdout,
- child_stderr) = (p.stdin, p.stdout, p.stderr)
-
-
-(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
-          stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
-(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
-
-
-Replacing popen2.*
-------------------
-Note: If the cmd argument to popen2 functions is a string, the command
-is executed through /bin/sh.  If it is a list, the command is directly
-executed.
-
-(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode)
-==>
-p = Popen(["somestring"], shell=True, bufsize=bufsize
-          stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdout, child_stdin) = (p.stdout, p.stdin)
-
-
-(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode)
-==>
-p = Popen(["mycmd", "myarg"], bufsize=bufsize,
-          stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdout, child_stdin) = (p.stdout, p.stdin)
-
-The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen,
-except that:
-
-* subprocess.Popen raises an exception if the execution fails
-* the capturestderr argument is replaced with the stderr argument.
-* stdin=PIPE and stdout=PIPE must be specified.
-* popen2 closes all filedescriptors by default, but you have to specify
-  close_fds=True with subprocess.Popen.
-
-
-"""
-
-import sys
-mswindows = (sys.platform == "win32")
-
-import os
-import types
-import traceback
-
-if mswindows:
-    import threading
-    import msvcrt
-    if 0: # <-- change this to use pywin32 instead of the _subprocess driver
-        import pywintypes
-        from win32api import GetStdHandle, STD_INPUT_HANDLE, \
-                             STD_OUTPUT_HANDLE, STD_ERROR_HANDLE
-        from win32api import GetCurrentProcess, DuplicateHandle, \
-                             GetModuleFileName, GetVersion
-        from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE
-        from win32pipe import CreatePipe
-        from win32process import CreateProcess, STARTUPINFO, \
-                                 GetExitCodeProcess, STARTF_USESTDHANDLES, \
-                                 STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
-        from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0
-    else:
-        from _subprocess import *
-        class STARTUPINFO:
-            dwFlags = 0
-            hStdInput = None
-            hStdOutput = None
-            hStdError = None
-        class pywintypes:
-            error = IOError
-else:
-    import select
-    import errno
-    import fcntl
-    import pickle
-
-__all__ = ["Popen", "PIPE", "STDOUT", "call"]
-
-try:
-    MAXFD = os.sysconf("SC_OPEN_MAX")
-except:
-    MAXFD = 256
-
-# True/False does not exist on 2.2.0
-try:
-    False
-except NameError:
-    False = 0
-    True = 1
-
-_active = []
-
-def _cleanup():
-    for inst in _active[:]:
-        inst.poll()
-
-PIPE = -1
-STDOUT = -2
-
-
-def call(*args, **kwargs):
-    """Run command with arguments.  Wait for command to complete, then
-    return the returncode attribute.
-
-    The arguments are the same as for the Popen constructor.  Example:
-
-    retcode = call(["ls", "-l"])
-    """
-    return Popen(*args, **kwargs).wait()
-
-
-def list2cmdline(seq):
-    """
-    Translate a sequence of arguments into a command line
-    string, using the same rules as the MS C runtime:
-
-    1) Arguments are delimited by white space, which is either a
-       space or a tab.
-
-    2) A string surrounded by double quotation marks is
-       interpreted as a single argument, regardless of white space
-       contained within.  A quoted string can be embedded in an
-       argument.
-
-    3) A double quotation mark preceded by a backslash is
-       interpreted as a literal double quotation mark.
-
-    4) Backslashes are interpreted literally, unless they
-       immediately precede a double quotation mark.
-
-    5) If backslashes immediately precede a double quotation mark,
-       every pair of backslashes is interpreted as a literal
-       backslash.  If the number of backslashes is odd, the last
-       backslash escapes the next double quotation mark as
-       described in rule 3.
-    """
-
-    # See
-    # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp
-    result = []
-    needquote = False
-    for arg in seq:
-        bs_buf = []
-
-        # Add a space to separate this argument from the others
-        if result:
-            result.append(' ')
-
-        needquote = (" " in arg) or ("\t" in arg)
-        if needquote:
-            result.append('"')
-
-        for c in arg:
-            if c == '\\':
-                # Don't know if we need to double yet.
-                bs_buf.append(c)
-            elif c == '"':
-                # Double backspaces.
-                result.append('\\' * len(bs_buf)*2)
-                bs_buf = []
-                result.append('\\"')
-            else:
-                # Normal char
-                if bs_buf:
-                    result.extend(bs_buf)
-                    bs_buf = []
-                result.append(c)
-
-        # Add remaining backspaces, if any.
-        if bs_buf:
-            result.extend(bs_buf)
-
-        if needquote:
-            result.extend(bs_buf)
-            result.append('"')
-
-    return ''.join(result)
-
-
-class Popen(object):
-    def __init__(self, args, bufsize=0, executable=None,
-                 stdin=None, stdout=None, stderr=None,
-                 preexec_fn=None, close_fds=False, shell=False,
-                 cwd=None, env=None, universal_newlines=False,
-                 startupinfo=None, creationflags=0):
-        """Create new Popen instance."""
-        _cleanup()
-
-        if not isinstance(bufsize, (int, long)):
-            raise TypeError("bufsize must be an integer")
-
-        if mswindows:
-            if preexec_fn is not None:
-                raise ValueError("preexec_fn is not supported on Windows "
-                                 "platforms")
-            if close_fds:
-                raise ValueError("close_fds is not supported on Windows "
-                                 "platforms")
-        else:
-            # POSIX
-            if startupinfo is not None:
-                raise ValueError("startupinfo is only supported on Windows "
-                                 "platforms")
-            if creationflags != 0:
-                raise ValueError("creationflags is only supported on Windows "
-                                 "platforms")
-
-        self.stdin = None
-        self.stdout = None
-        self.stderr = None
-        self.pid = None
-        self.returncode = None
-        self.universal_newlines = universal_newlines
-
-        # Input and output objects. The general principle is like
-        # this:
-        #
-        # Parent                   Child
-        # ------                   -----
-        # p2cwrite   ---stdin--->  p2cread
-        # c2pread    <--stdout---  c2pwrite
-        # errread    <--stderr---  errwrite
-        #
-        # On POSIX, the child objects are file descriptors.  On
-        # Windows, these are Windows file handles.  The parent objects
-        # are file descriptors on both platforms.  The parent objects
-        # are None when not using PIPEs. The child objects are None
-        # when not redirecting.
-
-        (p2cread, p2cwrite,
-         c2pread, c2pwrite,
-         errread, errwrite) = self._get_handles(stdin, stdout, stderr)
-
-        self._execute_child(args, executable, preexec_fn, close_fds,
-                            cwd, env, universal_newlines,
-                            startupinfo, creationflags, shell,
-                            p2cread, p2cwrite,
-                            c2pread, c2pwrite,
-                            errread, errwrite)
-
-        if p2cwrite:
-            self.stdin = os.fdopen(p2cwrite, 'wb', bufsize)
-        if c2pread:
-            if universal_newlines:
-                self.stdout = os.fdopen(c2pread, 'rU', bufsize)
-            else:
-                self.stdout = os.fdopen(c2pread, 'rb', bufsize)
-        if errread:
-            if universal_newlines:
-                self.stderr = os.fdopen(errread, 'rU', bufsize)
-            else:
-                self.stderr = os.fdopen(errread, 'rb', bufsize)
-
-        _active.append(self)
-
-
-    def _translate_newlines(self, data):
-        data = data.replace("\r\n", "\n")
-        data = data.replace("\r", "\n")
-        return data
-
-
-    if mswindows:
-        #
-        # Windows methods
-        #
-        def _get_handles(self, stdin, stdout, stderr):
-            """Construct and return tuple with IO objects:
-            p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
-            """
-            if stdin == None and stdout == None and stderr == None:
-                return (None, None, None, None, None, None)
-
-            p2cread, p2cwrite = None, None
-            c2pread, c2pwrite = None, None
-            errread, errwrite = None, None
-
-            if stdin == None:
-                p2cread = GetStdHandle(STD_INPUT_HANDLE)
-            elif stdin == PIPE:
-                p2cread, p2cwrite = CreatePipe(None, 0)
-                # Detach and turn into fd
-                p2cwrite = p2cwrite.Detach()
-                p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0)
-            elif type(stdin) == types.IntType:
-                p2cread = msvcrt.get_osfhandle(stdin)
-            else:
-                # Assuming file-like object
-                p2cread = msvcrt.get_osfhandle(stdin.fileno())
-            p2cread = self._make_inheritable(p2cread)
-
-            if stdout == None:
-                c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE)
-            elif stdout == PIPE:
-                c2pread, c2pwrite = CreatePipe(None, 0)
-                # Detach and turn into fd
-                c2pread = c2pread.Detach()
-                c2pread = msvcrt.open_osfhandle(c2pread, 0)
-            elif type(stdout) == types.IntType:
-                c2pwrite = msvcrt.get_osfhandle(stdout)
-            else:
-                # Assuming file-like object
-                c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
-            c2pwrite = self._make_inheritable(c2pwrite)
-
-            if stderr == None:
-                errwrite = GetStdHandle(STD_ERROR_HANDLE)
-            elif stderr == PIPE:
-                errread, errwrite = CreatePipe(None, 0)
-                # Detach and turn into fd
-                errread = errread.Detach()
-                errread = msvcrt.open_osfhandle(errread, 0)
-            elif stderr == STDOUT:
-                errwrite = c2pwrite
-            elif type(stderr) == types.IntType:
-                errwrite = msvcrt.get_osfhandle(stderr)
-            else:
-                # Assuming file-like object
-                errwrite = msvcrt.get_osfhandle(stderr.fileno())
-            errwrite = self._make_inheritable(errwrite)
-
-            return (p2cread, p2cwrite,
-                    c2pread, c2pwrite,
-                    errread, errwrite)
-
-
-        def _make_inheritable(self, handle):
-            """Return a duplicate of handle, which is inheritable"""
-            return DuplicateHandle(GetCurrentProcess(), handle,
-                                   GetCurrentProcess(), 0, 1,
-                                   DUPLICATE_SAME_ACCESS)
-
-
-        def _find_w9xpopen(self):
-            """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):
-                # Eeek - file-not-found - possibly an embedding
-                # situation - see if we can locate it in sys.exec_prefix
-                w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix),
-                                        "w9xpopen.exe")
-                if not os.path.exists(w9xpopen):
-                    raise RuntimeError("Cannot locate w9xpopen.exe, which is "
-                                       "needed for Popen to work with your "
-                                       "shell or platform.")
-            return w9xpopen
-
-
-        def _execute_child(self, args, executable, preexec_fn, close_fds,
-                           cwd, env, universal_newlines,
-                           startupinfo, creationflags, shell,
-                           p2cread, p2cwrite,
-                           c2pread, c2pwrite,
-                           errread, errwrite):
-            """Execute program (MS Windows version)"""
-
-            if not isinstance(args, types.StringTypes):
-                args = list2cmdline(args)
-
-            # Process startup details
-            default_startupinfo = STARTUPINFO()
-            if startupinfo == None:
-                startupinfo = default_startupinfo
-            if not None in (p2cread, c2pwrite, errwrite):
-                startupinfo.dwFlags |= STARTF_USESTDHANDLES
-                startupinfo.hStdInput = p2cread
-                startupinfo.hStdOutput = c2pwrite
-                startupinfo.hStdError = errwrite
-
-            if shell:
-                default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW
-                default_startupinfo.wShowWindow = SW_HIDE
-                comspec = os.environ.get("COMSPEC", "cmd.exe")
-                args = comspec + " /c " + args
-                if (GetVersion() >= 0x80000000L or
-                        os.path.basename(comspec).lower() == "command.com"):
-                    # Win9x, or using command.com on NT. We need to
-                    # use the w9xpopen intermediate program. For more
-                    # information, see KB Q150956
-                    # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp)
-                    w9xpopen = self._find_w9xpopen()
-                    args = '"%s" %s' % (w9xpopen, args)
-                    # Not passing CREATE_NEW_CONSOLE has been known to
-                    # cause random failures on win9x.  Specifically a
-                    # dialog: "Your program accessed mem currently in
-                    # use at xxx" and a hopeful warning about the
-                    # stability of your system.  Cost is Ctrl+C wont
-                    # kill children.
-                    creationflags |= CREATE_NEW_CONSOLE
-
-            # Start the process
-            try:
-                hp, ht, pid, tid = CreateProcess(executable, args,
-                                         # no special security
-                                         None, None,
-                                         # must inherit handles to pass std
-                                         # handles
-                                         1,
-                                         creationflags,
-                                         env,
-                                         cwd,
-                                         startupinfo)
-            except pywintypes.error, e:
-                # Translate pywintypes.error to WindowsError, which is
-                # a subclass of OSError.  FIXME: We should really
-                # translate errno using _sys_errlist (or simliar), but
-                # how can this be done from Python?
-                raise WindowsError(*e.args)
-
-            # Retain the process handle, but close the thread handle
-            self._handle = hp
-            self.pid = pid
-            ht.Close()
-
-            # Child is launched. Close the parent's copy of those pipe
-            # handles that only the child should have open.  You need
-            # to make sure that no handles to the write end of the
-            # output pipe are maintained in this process or else the
-            # pipe will not close when the child process exits and the
-            # ReadFile will hang.
-            if p2cread != None:
-                p2cread.Close()
-            if c2pwrite != None:
-                c2pwrite.Close()
-            if errwrite != None:
-                errwrite.Close()
-
-
-        def poll(self):
-            """Check if child process has terminated.  Returns returncode
-            attribute."""
-            if self.returncode == None:
-                if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0:
-                    self.returncode = GetExitCodeProcess(self._handle)
-                    _active.remove(self)
-            return self.returncode
-
-
-        def wait(self):
-            """Wait for child process to terminate.  Returns returncode
-            attribute."""
-            if self.returncode == None:
-                obj = WaitForSingleObject(self._handle, INFINITE)
-                self.returncode = GetExitCodeProcess(self._handle)
-                _active.remove(self)
-            return self.returncode
-
-
-        def _readerthread(self, fh, buffer):
-            buffer.append(fh.read())
-
-
-        def communicate(self, input=None):
-            """Interact with process: Send data to stdin.  Read data from
-            stdout and stderr, until end-of-file is reached.  Wait for
-            process to terminate.  The optional input argument should be a
-            string to be sent to the child process, or None, if no data
-            should be sent to the child.
-
-            communicate() returns a tuple (stdout, stderr)."""
-            stdout = None # Return
-            stderr = None # Return
-
-            if self.stdout:
-                stdout = []
-                stdout_thread = threading.Thread(target=self._readerthread,
-                                                 args=(self.stdout, stdout))
-                stdout_thread.setDaemon(True)
-                stdout_thread.start()
-            if self.stderr:
-                stderr = []
-                stderr_thread = threading.Thread(target=self._readerthread,
-                                                 args=(self.stderr, stderr))
-                stderr_thread.setDaemon(True)
-                stderr_thread.start()
-
-            if self.stdin:
-                if input != None:
-                    self.stdin.write(input)
-                self.stdin.close()
-
-            if self.stdout:
-                stdout_thread.join()
-            if self.stderr:
-                stderr_thread.join()
-
-            # All data exchanged.  Translate lists into strings.
-            if stdout != None:
-                stdout = stdout[0]
-            if stderr != None:
-                stderr = stderr[0]
-
-            # Translate newlines, if requested.  We cannot let the file
-            # object do the translation: It is based on stdio, which is
-            # impossible to combine with select (unless forcing no
-            # buffering).
-            if self.universal_newlines and hasattr(open, 'newlines'):
-                if stdout:
-                    stdout = self._translate_newlines(stdout)
-                if stderr:
-                    stderr = self._translate_newlines(stderr)
-
-            self.wait()
-            return (stdout, stderr)
-
-    else:
-        #
-        # POSIX methods
-        #
-        def _get_handles(self, stdin, stdout, stderr):
-            """Construct and return tuple with IO objects:
-            p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
-            """
-            p2cread, p2cwrite = None, None
-            c2pread, c2pwrite = None, None
-            errread, errwrite = None, None
-
-            if stdin == None:
-                pass
-            elif stdin == PIPE:
-                p2cread, p2cwrite = os.pipe()
-            elif type(stdin) == types.IntType:
-                p2cread = stdin
-            else:
-                # Assuming file-like object
-                p2cread = stdin.fileno()
-
-            if stdout == None:
-                pass
-            elif stdout == PIPE:
-                c2pread, c2pwrite = os.pipe()
-            elif type(stdout) == types.IntType:
-                c2pwrite = stdout
-            else:
-                # Assuming file-like object
-                c2pwrite = stdout.fileno()
-
-            if stderr == None:
-                pass
-            elif stderr == PIPE:
-                errread, errwrite = os.pipe()
-            elif stderr == STDOUT:
-                errwrite = c2pwrite
-            elif type(stderr) == types.IntType:
-                errwrite = stderr
-            else:
-                # Assuming file-like object
-                errwrite = stderr.fileno()
-
-            return (p2cread, p2cwrite,
-                    c2pread, c2pwrite,
-                    errread, errwrite)
-
-
-        def _set_cloexec_flag(self, fd):
-            try:
-                cloexec_flag = fcntl.FD_CLOEXEC
-            except AttributeError:
-                cloexec_flag = 1
-
-            old = fcntl.fcntl(fd, fcntl.F_GETFD)
-            fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag)
-
-
-        def _close_fds(self, but):
-            for i in range(3, MAXFD):
-                if i == but:
-                    continue
-                try:
-                    os.close(i)
-                except:
-                    pass
-
-
-        def _execute_child(self, args, executable, preexec_fn, close_fds,
-                           cwd, env, universal_newlines,
-                           startupinfo, creationflags, shell,
-                           p2cread, p2cwrite,
-                           c2pread, c2pwrite,
-                           errread, errwrite):
-            """Execute program (POSIX version)"""
-
-            if isinstance(args, types.StringTypes):
-                args = [args]
-
-            if shell:
-                args = ["/bin/sh", "-c"] + args
-
-            if executable == None:
-                executable = args[0]
-
-            # For transferring possible exec failure from child to parent
-            # The first char specifies the exception type: 0 means
-            # OSError, 1 means some other error.
-            errpipe_read, errpipe_write = os.pipe()
-            self._set_cloexec_flag(errpipe_write)
-
-            self.pid = os.fork()
-            if self.pid == 0:
-                # Child
-                try:
-                    # Close parent's pipe ends
-                    if p2cwrite:
-                        os.close(p2cwrite)
-                    if c2pread:
-                        os.close(c2pread)
-                    if errread:
-                        os.close(errread)
-                    os.close(errpipe_read)
-
-                    # Dup fds for child
-                    if p2cread:
-                        os.dup2(p2cread, 0)
-                    if c2pwrite:
-                        os.dup2(c2pwrite, 1)
-                    if errwrite:
-                        os.dup2(errwrite, 2)
-
-                    # Close pipe fds.  Make sure we doesn't close the same
-                    # fd more than once.
-                    if p2cread:
-                        os.close(p2cread)
-                    if c2pwrite and c2pwrite not in (p2cread,):
-                        os.close(c2pwrite)
-                    if errwrite and errwrite not in (p2cread, c2pwrite):
-                        os.close(errwrite)
-
-                    # Close all other fds, if asked for
-                    if close_fds:
-                        self._close_fds(but=errpipe_write)
-
-                    if cwd != None:
-                        os.chdir(cwd)
-
-                    if preexec_fn:
-                        apply(preexec_fn)
-
-                    if env == None:
-                        os.execvp(executable, args)
-                    else:
-                        os.execvpe(executable, args, env)
-
-                except:
-                    exc_type, exc_value, tb = sys.exc_info()
-                    # Save the traceback and attach it to the exception object
-                    exc_lines = traceback.format_exception(exc_type,
-                                                           exc_value,
-                                                           tb)
-                    exc_value.child_traceback = ''.join(exc_lines)
-                    os.write(errpipe_write, pickle.dumps(exc_value))
-
-                # This exitcode won't be reported to applications, so it
-                # really doesn't matter what we return.
-                os._exit(255)
-
-            # Parent
-            os.close(errpipe_write)
-            if p2cread and p2cwrite:
-                os.close(p2cread)
-            if c2pwrite and c2pread:
-                os.close(c2pwrite)
-            if errwrite and errread:
-                os.close(errwrite)
-
-            # Wait for exec to fail or succeed; possibly raising exception
-            data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB
-            os.close(errpipe_read)
-            if data != "":
-                os.waitpid(self.pid, 0)
-                child_exception = pickle.loads(data)
-                raise child_exception
-
-
-        def _handle_exitstatus(self, sts):
-            if os.WIFSIGNALED(sts):
-                self.returncode = -os.WTERMSIG(sts)
-            elif os.WIFEXITED(sts):
-                self.returncode = os.WEXITSTATUS(sts)
-            else:
-                # Should never happen
-                raise RuntimeError("Unknown child exit status!")
-
-            _active.remove(self)
-
-
-        def poll(self):
-            """Check if child process has terminated.  Returns returncode
-            attribute."""
-            if self.returncode == None:
-                try:
-                    pid, sts = os.waitpid(self.pid, os.WNOHANG)
-                    if pid == self.pid:
-                        self._handle_exitstatus(sts)
-                except os.error:
-                    pass
-            return self.returncode
-
-
-        def wait(self):
-            """Wait for child process to terminate.  Returns returncode
-            attribute."""
-            if self.returncode == None:
-                pid, sts = os.waitpid(self.pid, 0)
-                self._handle_exitstatus(sts)
-            return self.returncode
-
-
-        def communicate(self, input=None):
-            """Interact with process: Send data to stdin.  Read data from
-            stdout and stderr, until end-of-file is reached.  Wait for
-            process to terminate.  The optional input argument should be a
-            string to be sent to the child process, or None, if no data
-            should be sent to the child.
-
-            communicate() returns a tuple (stdout, stderr)."""
-            read_set = []
-            write_set = []
-            stdout = None # Return
-            stderr = None # Return
-
-            if self.stdin:
-                # Flush stdio buffer.  This might block, if the user has
-                # been writing to .stdin in an uncontrolled fashion.
-                self.stdin.flush()
-                if input:
-                    write_set.append(self.stdin)
-                else:
-                    self.stdin.close()
-            if self.stdout:
-                read_set.append(self.stdout)
-                stdout = []
-            if self.stderr:
-                read_set.append(self.stderr)
-                stderr = []
-
-            while read_set or write_set:
-                rlist, wlist, xlist = select.select(read_set, write_set, [])
-
-                if self.stdin in wlist:
-                    # When select has indicated that the file is writable,
-                    # we can write up to PIPE_BUF bytes without risk
-                    # blocking.  POSIX defines PIPE_BUF >= 512
-                    bytes_written = os.write(self.stdin.fileno(), input[:512])
-                    input = input[bytes_written:]
-                    if not input:
-                        self.stdin.close()
-                        write_set.remove(self.stdin)
-
-                if self.stdout in rlist:
-                    data = os.read(self.stdout.fileno(), 1024)
-                    if data == "":
-                        self.stdout.close()
-                        read_set.remove(self.stdout)
-                    stdout.append(data)
-
-                if self.stderr in rlist:
-                    data = os.read(self.stderr.fileno(), 1024)
-                    if data == "":
-                        self.stderr.close()
-                        read_set.remove(self.stderr)
-                    stderr.append(data)
-
-            # All data exchanged.  Translate lists into strings.
-            if stdout != None:
-                stdout = ''.join(stdout)
-            if stderr != None:
-                stderr = ''.join(stderr)
-
-            # Translate newlines, if requested.  We cannot let the file
-            # object do the translation: It is based on stdio, which is
-            # impossible to combine with select (unless forcing no
-            # buffering).
-            if self.universal_newlines and hasattr(open, 'newlines'):
-                if stdout:
-                    stdout = self._translate_newlines(stdout)
-                if stderr:
-                    stderr = self._translate_newlines(stderr)
-
-            self.wait()
-            return (stdout, stderr)
-
-
-def _demo_posix():
-    #
-    # Example 1: Simple redirection: Get process list
-    #
-    plist = Popen(["ps"], stdout=PIPE).communicate()[0]
-    print "Process list:"
-    print plist
-
-    #
-    # Example 2: Change uid before executing child
-    #
-    if os.getuid() == 0:
-        p = Popen(["id"], preexec_fn=lambda: os.setuid(100))
-        p.wait()
-
-    #
-    # Example 3: Connecting several subprocesses
-    #
-    print "Looking for 'hda'..."
-    p1 = Popen(["dmesg"], stdout=PIPE)
-    p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
-    print repr(p2.communicate()[0])
-
-    #
-    # Example 4: Catch execution error
-    #
-    print
-    print "Trying a weird file..."
-    try:
-        print Popen(["/this/path/does/not/exist"]).communicate()
-    except OSError, e:
-        if e.errno == errno.ENOENT:
-            print "The file didn't exist.  I thought so..."
-            print "Child traceback:"
-            print e.child_traceback
-        else:
-            print "Error", e.errno
-    else:
-        print >>sys.stderr, "Gosh.  No error."
-
-
-def _demo_windows():
-    #
-    # Example 1: Connecting several subprocesses
-    #
-    print "Looking for 'PROMPT' in set output..."
-    p1 = Popen("set", stdout=PIPE, shell=True)
-    p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE)
-    print repr(p2.communicate()[0])
-
-    #
-    # Example 2: Simple execution of program
-    #
-    print "Executing calc..."
-    p = Popen("calc")
-    p.wait()
-
-
-if __name__ == "__main__":
-    if mswindows:
-        _demo_windows()
-    else:
-        _demo_posix()
index 3a5e4ec04ae4a523823e633031b85559bf5dc973..eb29f5e0849370afe90c400271fea12e0f9090aa 100644 (file)
@@ -1,5 +1,4 @@
-#include <stdlib.h>
-#include <string.h>
+#include "../git-compat-util.h"
 
 void gitunsetenv (const char *name)
 {
index 3cae3901aa9b50e8d04a8ce2633a1a1ef8ed0f6c..1662a4626e569b07d96c622b357928216a24538c 100644 (file)
--- a/config.c
+++ b/config.c
@@ -6,7 +6,6 @@
  *
  */
 #include "cache.h"
-#include <regex.h>
 
 #define MAXNAME (256)
 
@@ -314,7 +313,7 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
-       if (!strcmp(var, "pager.color")) {
+       if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
                pager_use_color = git_config_bool(var,value);
                return 0;
        }
@@ -350,10 +349,10 @@ int git_config(config_fn_t fn)
         * $GIT_CONFIG_LOCAL will make it process it in addition to the
         * global config file, the same way it would the per-repository
         * config file otherwise. */
-       filename = getenv("GIT_CONFIG");
+       filename = getenv(CONFIG_ENVIRONMENT);
        if (!filename) {
                home = getenv("HOME");
-               filename = getenv("GIT_CONFIG_LOCAL");
+               filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!filename)
                        filename = repo_config = xstrdup(git_path("config"));
        }
@@ -544,9 +543,9 @@ int git_config_set_multivar(const char* key, const char* value,
        char* lock_file;
        const char* last_dot = strrchr(key, '.');
 
-       config_filename = getenv("GIT_CONFIG");
+       config_filename = getenv(CONFIG_ENVIRONMENT);
        if (!config_filename) {
-               config_filename = getenv("GIT_CONFIG_LOCAL");
+               config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!config_filename)
                        config_filename  = git_path("config");
        }
@@ -746,4 +745,75 @@ out_free:
        return ret;
 }
 
+int git_config_rename_section(const char *old_name, const char *new_name)
+{
+       int ret = 0;
+       char *config_filename;
+       struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
+       int out_fd;
+       char buf[1024];
+
+       config_filename = getenv(CONFIG_ENVIRONMENT);
+       if (!config_filename) {
+               config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
+               if (!config_filename)
+                       config_filename  = git_path("config");
+       }
+       config_filename = xstrdup(config_filename);
+       out_fd = hold_lock_file_for_update(lock, config_filename, 0);
+       if (out_fd < 0) {
+               ret = error("Could not lock config file!");
+               goto out;
+       }
+
+       if (!(config_file = fopen(config_filename, "rb"))) {
+               ret = error("Could not open config file!");
+               goto out;
+       }
+
+       while (fgets(buf, sizeof(buf), config_file)) {
+               int i;
+               for (i = 0; buf[i] && isspace(buf[i]); i++)
+                       ; /* do nothing */
+               if (buf[i] == '[') {
+                       /* it's a section */
+                       int j = 0, dot = 0;
+                       for (i++; buf[i] && buf[i] != ']'; i++) {
+                               if (!dot && isspace(buf[i])) {
+                                       dot = 1;
+                                       if (old_name[j++] != '.')
+                                               break;
+                                       for (i++; isspace(buf[i]); i++)
+                                               ; /* do nothing */
+                                       if (buf[i] != '"')
+                                               break;
+                                       continue;
+                               }
+                               if (buf[i] == '\\' && dot)
+                                       i++;
+                               else if (buf[i] == '"' && dot) {
+                                       for (i++; isspace(buf[i]); i++)
+                                               ; /* do_nothing */
+                                       break;
+                               }
+                               if (buf[i] != old_name[j++])
+                                       break;
+                       }
+                       if (buf[i] == ']') {
+                               /* old_name matches */
+                               ret++;
+                               store.baselen = strlen(new_name);
+                               store_write_section(out_fd, new_name);
+                               continue;
+                       }
+               }
+               write(out_fd, buf, strlen(buf));
+       }
+       fclose(config_file);
+       if (close(out_fd) || commit_lock_file(lock) < 0)
+               ret = error("Cannot commit config file!");
+ out:
+       free(config_filename);
+       return ret;
+}
 
index 1cafa19ed4e4b9ecb683e4bc6c4aaa3ac3322b74..9a578405d856c3633e7137a24964b7b80fd96485 100644 (file)
@@ -13,7 +13,6 @@ bindir = @bindir@
 #gitexecdir = @libexecdir@/git-core/
 datarootdir = @datarootdir@
 template_dir = @datadir@/git-core/templates/
-GIT_PYTHON_DIR = @datadir@/git-core/python
 
 mandir=@mandir@
 
@@ -23,7 +22,6 @@ VPATH = @srcdir@
 export exec_prefix mandir
 export srcdir VPATH
 
-NO_PYTHON=@NO_PYTHON@
 NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
 NO_OPENSSL=@NO_OPENSSL@
 NO_CURL=@NO_CURL@
index cff5722eb9927ec652e3db731fc033508bc44ef2..e153d5382364def42fa062a39148a696e2e49785 100644 (file)
@@ -75,20 +75,6 @@ GIT_ARG_SET_PATH(shell)
 # Define PERL_PATH to provide path to Perl.
 GIT_ARG_SET_PATH(perl)
 #
-# Define PYTHON_PATH to provide path to Python.
-AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python])
-AS_HELP_STRING([--without-python], [don't use python scripts])],
- [if test "$withval" = "no"; then \
-    NO_PYTHON=YesPlease; \
-  elif test "$withval" = "yes"; then \
-    NO_PYTHON=; \
-  else \
-    NO_PYTHON=; \
-    PYTHON_PATH=$withval; \
-  fi; \
- ])
-AC_SUBST(NO_PYTHON)
-AC_SUBST(PYTHON_PATH)
 
 
 ## Checks for programs.
@@ -98,18 +84,6 @@ AC_PROG_CC([cc gcc])
 #AC_PROG_INSTALL               # needs install-sh or install.sh in sources
 AC_CHECK_TOOL(AR, ar, :)
 AC_CHECK_PROGS(TAR, [gtar tar])
-#
-# Define PYTHON_PATH to provide path to Python.
-if test -z "$NO_PYTHON"; then
-       if test -z "$PYTHON_PATH"; then
-               AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2])
-       fi
-       if test -n "$PYTHON_PATH"; then
-               GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@])
-               NO_PYTHON=""
-       fi
-fi
-
 
 ## Checks for libraries.
 AC_MSG_NOTICE([CHECKS for libraries])
@@ -262,22 +236,9 @@ AC_SUBST(NO_SETENV)
 # 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 WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
-AC_CACHE_CHECK([for subprocess.py],
- [ac_cv_python_has_subprocess_py],
-[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then
-       ac_cv_python_has_subprocess_py=yes
-else
-       ac_cv_python_has_subprocess_py=no
-fi])
-if test $ac_cv_python_has_subprocess_py != yes; then
-       GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease])
-fi
-#
 # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
 # a missing newline at the end of the file.
 
-
 ## Site configuration (override autodetection)
 ## --with-PACKAGE[=ARG] and --without-PACKAGE
 AC_MSG_NOTICE([CHECKS for site configuration])
@@ -355,10 +316,6 @@ GIT_PARSE_WITH(iconv))
 
 ## --enable-FEATURE[=ARG] and --disable-FEATURE
 #
-# Define COLLISION_CHECK below if you believe that SHA1's
-# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# sufficient guarantee that no collisions between objects will ever happen.
-#
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
 # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
index c55a20a4aa31e7cf1bbf0dcec6b4ebccb655d850..66daa11a5737efd6ee2dbd6ff2ad0e1475fcba20 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -3,12 +3,6 @@
 #include "pkt-line.h"
 #include "quote.h"
 #include "refs.h"
-#include <sys/wait.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-#include <signal.h>
 
 static char *server_capabilities;
 
@@ -144,6 +138,7 @@ struct refspec {
  * +A:B means overwrite remote B with local A.
  * +A is a shorthand for +A:A.
  * A is a shorthand for A:A.
+ * :B means delete remote B.
  */
 static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
 {
@@ -174,21 +169,58 @@ static int count_refspec_match(const char *pattern,
                               struct ref *refs,
                               struct ref **matched_ref)
 {
-       int match;
        int patlen = strlen(pattern);
+       struct ref *matched_weak = NULL;
+       struct ref *matched = NULL;
+       int weak_match = 0;
+       int match = 0;
 
-       for (match = 0; refs; refs = refs->next) {
+       for (weak_match = match = 0; refs; refs = refs->next) {
                char *name = refs->name;
                int namelen = strlen(name);
+               int weak_match;
+
                if (namelen < patlen ||
                    memcmp(name + namelen - patlen, pattern, patlen))
                        continue;
                if (namelen != patlen && name[namelen - patlen - 1] != '/')
                        continue;
-               match++;
-               *matched_ref = refs;
+
+               /* A match is "weak" if it is with refs outside
+                * heads or tags, and did not specify the pattern
+                * in full (e.g. "refs/remotes/origin/master") or at
+                * least from the toplevel (e.g. "remotes/origin/master");
+                * otherwise "git push $URL master" would result in
+                * ambiguity between remotes/origin/master and heads/master
+                * at the remote site.
+                */
+               if (namelen != patlen &&
+                   patlen != namelen - 5 &&
+                   strncmp(name, "refs/heads/", 11) &&
+                   strncmp(name, "refs/tags/", 10)) {
+                       /* We want to catch the case where only weak
+                        * matches are found and there are multiple
+                        * matches, and where more than one strong
+                        * matches are found, as ambiguous.  One
+                        * strong match with zero or more weak matches
+                        * are acceptable as a unique match.
+                        */
+                       matched_weak = refs;
+                       weak_match++;
+               }
+               else {
+                       matched = refs;
+                       match++;
+               }
+       }
+       if (!matched) {
+               *matched_ref = matched_weak;
+               return weak_match;
+       }
+       else {
+               *matched_ref = matched;
+               return match;
        }
-       return match;
 }
 
 static void link_dst_tail(struct ref *ref, struct ref ***tail)
@@ -203,6 +235,13 @@ static struct ref *try_explicit_object_name(const char *name)
        unsigned char sha1[20];
        struct ref *ref;
        int len;
+
+       if (!*name) {
+               ref = xcalloc(1, sizeof(*ref) + 20);
+               strcpy(ref->name, "(delete)");
+               hashclr(ref->new_sha1);
+               return ref;
+       }
        if (get_sha1(name, sha1))
                return NULL;
        len = strlen(name) + 1;
@@ -225,7 +264,8 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
                        break;
                case 0:
                        /* The source could be in the get_sha1() format
-                        * not a reference name.
+                        * not a reference name.  :refs/other is a
+                        * way to delete 'other' ref at the remote end.
                         */
                        matched_src = try_explicit_object_name(rs[i].src);
                        if (matched_src)
index a43a177160b3b2952e4025bebc47c820ff4003f7..234cd0954b888d814d8d4d86bb41983b80fddade 100755 (executable)
 #    2) Added the following line to your .bashrc:
 #        source ~/.git-completion.sh
 #
+#    3) You may want to make sure the git executable is available
+#       in your PATH before this script is sourced, as some caching
+#       is performed while the script loads.  If git isn't found
+#       at source time then all lookups will be done on demand,
+#       which may be slightly slower.
+#
+#    4) Consider changing your PS1 to also show the current branch:
+#        PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+#
+#       The argument to __git_ps1 will be displayed only if you
+#       are currently in a git repository.  The %s token will be
+#       the name of the current branch.
+#
 
 __gitdir ()
 {
-       echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}"
+       if [ -z "$1" ]; then
+               if [ -n "$__git_dir" ]; then
+                       echo "$__git_dir"
+               elif [ -d .git ]; then
+                       echo .git
+               else
+                       git rev-parse --git-dir 2>/dev/null
+               fi
+       elif [ -d "$1/.git" ]; then
+               echo "$1/.git"
+       else
+               echo "$1"
+       fi
+}
+
+__git_ps1 ()
+{
+       local b="$(git symbolic-ref HEAD 2>/dev/null)"
+       if [ -n "$b" ]; then
+               if [ -n "$1" ]; then
+                       printf "$1" "${b##refs/heads/}"
+               else
+                       printf " (%s)" "${b##refs/heads/}"
+               fi
+       fi
+}
+
+__git_heads ()
+{
+       local cmd i is_hash=y dir="$(__gitdir "$1")"
+       if [ -d "$dir" ]; then
+               for i in $(git --git-dir="$dir" \
+                       for-each-ref --format='%(refname)' \
+                       refs/heads ); do
+                       echo "${i#refs/heads/}"
+               done
+               return
+       fi
+       for i in $(git-ls-remote "$1" 2>/dev/null); do
+               case "$is_hash,$i" in
+               y,*) is_hash=n ;;
+               n,*^{}) is_hash=y ;;
+               n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+               n,*) is_hash=y; echo "$i" ;;
+               esac
+       done
 }
 
 __git_refs ()
 {
-       local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+       local cmd i is_hash=y dir="$(__gitdir "$1")"
        if [ -d "$dir" ]; then
-               cmd=git-peek-remote
-       else
-               cmd=git-ls-remote
+               if [ -e "$dir/HEAD" ]; then echo HEAD; fi
+               for i in $(git --git-dir="$dir" \
+                       for-each-ref --format='%(refname)' \
+                       refs/tags refs/heads refs/remotes); do
+                       case "$i" in
+                               refs/tags/*)    echo "${i#refs/tags/}" ;;
+                               refs/heads/*)   echo "${i#refs/heads/}" ;;
+                               refs/remotes/*) echo "${i#refs/remotes/}" ;;
+                               *)              echo "$i" ;;
+                       esac
+               done
+               return
        fi
-       for i in $($cmd "$dir" 2>/dev/null); do
+       for i in $(git-ls-remote "$dir" 2>/dev/null); do
                case "$is_hash,$i" in
                y,*) is_hash=n ;;
                n,*^{}) is_hash=y ;;
                n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
                n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+               n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;;
                n,*) is_hash=y; echo "$i" ;;
                esac
        done
@@ -45,19 +113,25 @@ __git_refs ()
 
 __git_refs2 ()
 {
-       local cmd i is_hash=y dir="${1:-$(__gitdir)}"
-       if [ -d "$dir" ]; then
-               cmd=git-peek-remote
-       else
-               cmd=git-ls-remote
-       fi
-       for i in $($cmd "$dir" 2>/dev/null); do
+       local i
+       for i in $(__git_refs "$1"); do
+               echo "$i:$i"
+       done
+}
+
+__git_refs_remotes ()
+{
+       local cmd i is_hash=y
+       for i in $(git-ls-remote "$1" 2>/dev/null); do
                case "$is_hash,$i" in
+               n,refs/heads/*)
+                       is_hash=y
+                       echo "$i:refs/remotes/$1/${i#refs/heads/}"
+                       ;;
                y,*) is_hash=n ;;
                n,*^{}) is_hash=y ;;
-               n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;;
-               n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;;
-               n,*) is_hash=y; echo "$i:$i" ;;
+               n,refs/tags/*) is_hash=y;;
+               n,*) is_hash=y; ;;
                esac
        done
 }
@@ -81,6 +155,22 @@ __git_remotes ()
        done
 }
 
+__git_merge_strategies ()
+{
+       if [ -n "$__git_merge_strategylist" ]; then
+               echo "$__git_merge_strategylist"
+               return
+       fi
+       sed -n "/^all_strategies='/{
+               s/^all_strategies='//
+               s/'//
+               p
+               q
+               }" "$(git --exec-path)/git-merge"
+}
+__git_merge_strategylist=
+__git_merge_strategylist="$(__git_merge_strategies 2>/dev/null)"
+
 __git_complete_file ()
 {
        local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
@@ -115,6 +205,84 @@ __git_complete_file ()
        esac
 }
 
+__git_complete_revlist ()
+{
+       local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       *...*)
+               pfx="${cur%...*}..."
+               cur="${cur#*...}"
+               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+               ;;
+       *..*)
+               pfx="${cur%..*}.."
+               cur="${cur#*..}"
+               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+               ;;
+       *)
+               COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+               ;;
+       esac
+}
+
+__git_commands ()
+{
+       if [ -n "$__git_commandlist" ]; then
+               echo "$__git_commandlist"
+               return
+       fi
+       local i IFS=" "$'\n'
+       for i in $(git help -a|egrep '^ ')
+       do
+               case $i in
+               check-ref-format) : plumbing;;
+               commit-tree)      : plumbing;;
+               convert-objects)  : plumbing;;
+               cvsserver)        : daemon;;
+               daemon)           : daemon;;
+               fetch-pack)       : plumbing;;
+               hash-object)      : plumbing;;
+               http-*)           : transport;;
+               index-pack)       : plumbing;;
+               local-fetch)      : plumbing;;
+               mailinfo)         : plumbing;;
+               mailsplit)        : plumbing;;
+               merge-*)          : plumbing;;
+               mktree)           : plumbing;;
+               mktag)            : plumbing;;
+               pack-objects)     : plumbing;;
+               pack-redundant)   : plumbing;;
+               pack-refs)        : plumbing;;
+               parse-remote)     : plumbing;;
+               patch-id)         : plumbing;;
+               peek-remote)      : plumbing;;
+               read-tree)        : plumbing;;
+               receive-pack)     : plumbing;;
+               rerere)           : plumbing;;
+               rev-list)         : plumbing;;
+               rev-parse)        : plumbing;;
+               runstatus)        : plumbing;;
+               sh-setup)         : internal;;
+               shell)            : daemon;;
+               send-pack)        : plumbing;;
+               show-index)       : plumbing;;
+               ssh-*)            : transport;;
+               stripspace)       : plumbing;;
+               symbolic-ref)     : plumbing;;
+               unpack-file)      : plumbing;;
+               unpack-objects)   : plumbing;;
+               update-ref)       : plumbing;;
+               update-server-info) : daemon;;
+               upload-archive)   : plumbing;;
+               upload-pack)      : plumbing;;
+               write-tree)       : plumbing;;
+               *) echo $i;;
+               esac
+       done
+}
+__git_commandlist=
+__git_commandlist="$(__git_commands 2>/dev/null)"
+
 __git_aliases ()
 {
        local i IFS=$'\n'
@@ -140,6 +308,54 @@ __git_aliased_command ()
        done
 }
 
+__git_whitespacelist="nowarn warn error error-all strip"
+
+_git_am ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       if [ -d .dotest ]; then
+               COMPREPLY=($(compgen -W "
+                       --skip --resolved
+                       " -- "$cur"))
+               return
+       fi
+       case "$cur" in
+       --whitespace=*)
+               COMPREPLY=($(compgen -W "$__git_whitespacelist" \
+                       -- "${cur##--whitespace=}"))
+               return
+               ;;
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --signoff --utf8 --binary --3way --interactive
+                       --whitespace=
+                       " -- "$cur"))
+               return
+       esac
+       COMPREPLY=()
+}
+
+_git_apply ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --whitespace=*)
+               COMPREPLY=($(compgen -W "$__git_whitespacelist" \
+                       -- "${cur##--whitespace=}"))
+               return
+               ;;
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --stat --numstat --summary --check --index
+                       --cached --index-info --reverse --reject --unidiff-zero
+                       --apply --no-add --exclude=
+                       --whitespace= --inaccurate-eof --verbose
+                       " -- "$cur"))
+               return
+       esac
+       COMPREPLY=()
+}
+
 _git_branch ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -168,6 +384,35 @@ _git_checkout ()
        COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur"))
 }
 
+_git_cherry_pick ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --edit --no-commit
+                       " -- "$cur"))
+               ;;
+       *)
+               COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+               ;;
+       esac
+}
+
+_git_commit ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --all --author= --signoff --verify --no-verify
+                       --edit --amend --include --only
+                       " -- "$cur"))
+               return
+       esac
+       COMPREPLY=()
+}
+
 _git_diff ()
 {
        __git_complete_file
@@ -209,6 +454,26 @@ _git_fetch ()
        esac
 }
 
+_git_format_patch ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --stdout --attach --thread
+                       --output-directory
+                       --numbered --start-number
+                       --keep-subject
+                       --signoff
+                       --in-reply-to=
+                       --full-index --binary
+                       " -- "$cur"))
+               return
+               ;;
+       esac
+       __git_complete_revlist
+}
+
 _git_ls_remote ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -222,22 +487,53 @@ _git_ls_tree ()
 
 _git_log ()
 {
-       local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+       local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
-       *...*)
-               pfx="${cur%...*}..."
-               cur="${cur#*...}"
-               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+       --pretty=*)
+               COMPREPLY=($(compgen -W "
+                       oneline short medium full fuller email raw
+                       " -- "${cur##--pretty=}"))
+               return
                ;;
-       *..*)
-               pfx="${cur%..*}.."
-               cur="${cur#*..}"
-               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --max-count= --max-age= --since= --after=
+                       --min-age= --before= --until=
+                       --root --not --topo-order --date-order
+                       --no-merges
+                       --abbrev-commit --abbrev=
+                       --relative-date
+                       --author= --committer= --grep=
+                       --all-match
+                       --pretty= --name-status --name-only
+                       " -- "$cur"))
+               return
                ;;
-       *)
-               COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+       esac
+       __git_complete_revlist
+}
+
+_git_merge ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       -s|--strategy)
+               COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur"))
+               return
+       esac
+       case "$cur" in
+       --strategy=*)
+               COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \
+                       -- "${cur##--strategy=}"))
+               return
                ;;
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --no-commit --no-summary --squash --strategy
+                       " -- "$cur"))
+               return
        esac
+       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
 }
 
 _git_merge_base ()
@@ -246,6 +542,12 @@ _git_merge_base ()
        COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
 }
 
+_git_name_rev ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       COMPREPLY=($(compgen -W "--tags --all --stdin" -- "$cur"))
+}
+
 _git_pull ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -298,6 +600,151 @@ _git_push ()
        esac
 }
 
+_git_rebase ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       if [ -d .dotest ]; then
+               COMPREPLY=($(compgen -W "
+                       --continue --skip --abort
+                       " -- "$cur"))
+               return
+       fi
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       -s|--strategy)
+               COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur"))
+               return
+       esac
+       case "$cur" in
+       --strategy=*)
+               COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \
+                       -- "${cur##--strategy=}"))
+               return
+               ;;
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --onto --merge --strategy
+                       " -- "$cur"))
+               return
+       esac
+       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+}
+
+_git_repo_config ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local prv="${COMP_WORDS[COMP_CWORD-1]}"
+       case "$prv" in
+       branch.*.remote)
+               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               return
+               ;;
+       branch.*.merge)
+               COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+               return
+               ;;
+       remote.*.fetch)
+               local remote="${prv#remote.}"
+               remote="${remote%.fetch}"
+               COMPREPLY=($(compgen -W "$(__git_refs_remotes "$remote")" \
+                       -- "$cur"))
+               return
+               ;;
+       remote.*.push)
+               local remote="${prv#remote.}"
+               remote="${remote%.push}"
+               COMPREPLY=($(compgen -W "$(git --git-dir="$(__gitdir)" \
+                       for-each-ref --format='%(refname):%(refname)' \
+                       refs/heads)" -- "$cur"))
+               return
+               ;;
+       *.*)
+               COMPREPLY=()
+               return
+               ;;
+       esac
+       case "$cur" in
+       --*)
+               COMPREPLY=($(compgen -W "
+                       --global --list --replace-all
+                       --get --get-all --get-regexp
+                       --unset --unset-all
+                       " -- "$cur"))
+               return
+               ;;
+       branch.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               COMPREPLY=($(compgen -P "$pfx" -W "remote merge" -- "$cur"))
+               return
+               ;;
+       branch.*)
+               local pfx="${cur%.*}."
+               cur="${cur#*.}"
+               COMPREPLY=($(compgen -P "$pfx" -S . \
+                       -W "$(__git_heads)" -- "$cur"))
+               return
+               ;;
+       remote.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               COMPREPLY=($(compgen -P "$pfx" -W "url fetch push" -- "$cur"))
+               return
+               ;;
+       remote.*)
+               local pfx="${cur%.*}."
+               cur="${cur#*.}"
+               COMPREPLY=($(compgen -P "$pfx" -S . \
+                       -W "$(__git_remotes)" -- "$cur"))
+               return
+               ;;
+       esac
+       COMPREPLY=($(compgen -W "
+               apply.whitespace
+               core.fileMode
+               core.gitProxy
+               core.ignoreStat
+               core.preferSymlinkRefs
+               core.logAllRefUpdates
+               core.repositoryFormatVersion
+               core.sharedRepository
+               core.warnAmbiguousRefs
+               core.compression
+               core.legacyHeaders
+               i18n.commitEncoding
+               diff.color
+               color.diff
+               diff.renameLimit
+               diff.renames
+               pager.color
+               color.pager
+               status.color
+               color.status
+               log.showroot
+               show.difftree
+               showbranch.default
+               whatchanged.difftree
+               http.sslVerify
+               http.sslCert
+               http.sslKey
+               http.sslCAInfo
+               http.sslCAPath
+               http.maxRequests
+               http.lowSpeedLimit http.lowSpeedTime
+               http.noEPSV
+               pack.window
+               repack.useDeltaBaseOffset
+               pull.octopus pull.twohead
+               merge.summary
+               receive.unpackLimit
+               receive.denyNonFastForwards
+               user.name user.email
+               tar.umask
+               gitcvs.enabled
+               gitcvs.logfile
+               branch. remote.
+       " -- "$cur"))
+}
+
 _git_reset ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -308,7 +755,19 @@ _git_reset ()
 _git_show ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+       case "$cur" in
+       --pretty=*)
+               COMPREPLY=($(compgen -W "
+                       oneline short medium full fuller email raw
+                       " -- "${cur##--pretty=}"))
+               return
+               ;;
+       --*)
+               COMPREPLY=($(compgen -W "--pretty=" -- "$cur"))
+               return
+               ;;
+       esac
+       __git_complete_file
 }
 
 _git ()
@@ -327,11 +786,11 @@ _git ()
        done
 
        if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
-               COMPREPLY=($(compgen \
-                       -W "--git-dir= --version \
-                               $(git help -a|egrep '^ ') \
-                           $(__git_aliases)" \
-                       -- "${COMP_WORDS[COMP_CWORD]}"))
+               COMPREPLY=($(compgen -W "
+                       --git-dir= --version --exec-path
+                       $(__git_commands)
+                       $(__git_aliases)
+                       -- "${COMP_WORDS[COMP_CWORD]}"))
                return;
        fi
 
@@ -339,18 +798,27 @@ _git ()
        [ "$expansion" ] && command="$expansion"
 
        case "$command" in
+       am)          _git_am ;;
+       apply)       _git_apply ;;
        branch)      _git_branch ;;
        cat-file)    _git_cat_file ;;
        checkout)    _git_checkout ;;
+       cherry-pick) _git_cherry_pick ;;
+       commit)      _git_commit ;;
        diff)        _git_diff ;;
        diff-tree)   _git_diff_tree ;;
        fetch)       _git_fetch ;;
+       format-patch) _git_format_patch ;;
        log)         _git_log ;;
        ls-remote)   _git_ls_remote ;;
        ls-tree)     _git_ls_tree ;;
+       merge)       _git_merge;;
        merge-base)  _git_merge_base ;;
+       name-rev)    _git_name_rev ;;
        pull)        _git_pull ;;
        push)        _git_push ;;
+       rebase)      _git_rebase ;;
+       repo-config) _git_repo_config ;;
        reset)       _git_reset ;;
        show)        _git_show ;;
        show-branch) _git_log ;;
@@ -367,20 +835,29 @@ _gitk ()
 
 complete -o default -o nospace -F _git git
 complete -o default            -F _gitk gitk
+complete -o default            -F _git_am git-am
+complete -o default            -F _git_apply git-apply
 complete -o default            -F _git_branch git-branch
 complete -o default -o nospace -F _git_cat_file git-cat-file
 complete -o default            -F _git_checkout git-checkout
+complete -o default            -F _git_cherry_pick git-cherry-pick
+complete -o default            -F _git_commit git-commit
 complete -o default -o nospace -F _git_diff git-diff
 complete -o default            -F _git_diff_tree git-diff-tree
 complete -o default -o nospace -F _git_fetch git-fetch
+complete -o default -o nospace -F _git_format_patch git-format-patch
 complete -o default -o nospace -F _git_log git-log
 complete -o default            -F _git_ls_remote git-ls-remote
 complete -o default -o nospace -F _git_ls_tree git-ls-tree
+complete -o default            -F _git_merge git-merge
 complete -o default            -F _git_merge_base git-merge-base
+complete -o default            -F _git_name_rev git-name-rev
 complete -o default -o nospace -F _git_pull git-pull
 complete -o default -o nospace -F _git_push git-push
+complete -o default            -F _git_rebase git-rebase
+complete -o default            -F _git_repo_config git-repo-config
 complete -o default            -F _git_reset git-reset
-complete -o default            -F _git_show git-show
+complete -o default -o nospace -F _git_show git-show
 complete -o default -o nospace -F _git_log git-show-branch
 complete -o default -o nospace -F _git_log git-whatchanged
 
@@ -389,15 +866,20 @@ complete -o default -o nospace -F _git_log git-whatchanged
 # included the '.exe' suffix.
 #
 if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
+complete -o default            -F _git_apply git-apply.exe
 complete -o default -o nospace -F _git git.exe
 complete -o default            -F _git_branch git-branch.exe
 complete -o default -o nospace -F _git_cat_file git-cat-file.exe
 complete -o default -o nospace -F _git_diff git-diff.exe
 complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
+complete -o default -o nospace -F _git_format_patch git-format-patch.exe
 complete -o default -o nospace -F _git_log git-log.exe
 complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
 complete -o default            -F _git_merge_base git-merge-base.exe
+complete -o default            -F _git_name_rev git-name-rev.exe
 complete -o default -o nospace -F _git_push git-push.exe
+complete -o default            -F _git_repo_config git-repo-config
+complete -o default -o nospace -F _git_show git-show.exe
 complete -o default -o nospace -F _git_log git-show-branch.exe
 complete -o default -o nospace -F _git_log git-whatchanged.exe
 fi
diff --git a/contrib/mailmap.linux b/contrib/mailmap.linux
new file mode 100644 (file)
index 0000000..e4907f8
--- /dev/null
@@ -0,0 +1,42 @@
+#
+# Even with git, we don't always have name translations.
+# So have an email->real name table to translate the
+# (hopefully few) missing names
+#
+# repo-abbrev: /pub/scm/linux/kernel/git/
+#
+Adrian Bunk <bunk@stusta.de>
+Andreas Herrmann <aherrman@de.ibm.com>
+Andrew Morton <akpm@osdl.org>
+Andrew Vasquez <andrew.vasquez@qlogic.com>
+Christoph Hellwig <hch@lst.de>
+Corey Minyard <minyard@acm.org>
+David Woodhouse <dwmw2@shinybook.infradead.org>
+Domen Puncer <domen@coderock.org>
+Douglas Gilbert <dougg@torque.net>
+Ed L Cashin <ecashin@coraid.com>
+Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+Felix Moeller <felix@derklecks.de>
+Frank Zago <fzago@systemfabricworks.com>
+Greg Kroah-Hartman <gregkh@suse.de>
+James Bottomley <jejb@mulgrave.(none)>
+James Bottomley <jejb@titanic.il.steeleye.com>
+Jeff Garzik <jgarzik@pretzel.yyz.us>
+Jens Axboe <axboe@suse.de>
+Kay Sievers <kay.sievers@vrfy.org>
+Mitesh shah <mshah@teja.com>
+Morten Welinder <terra@gnome.org>
+Morten Welinder <welinder@anemone.rentec.com>
+Morten Welinder <welinder@darter.rentec.com>
+Morten Welinder <welinder@troll.com>
+Nguyen Anh Quynh <aquynh@gmail.com>
+Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
+Peter A Jonsson <pj@ludd.ltu.se>
+Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+Rudolf Marek <R.Marek@sh.cvut.cz>
+Rui Saraiva <rmps@joel.ist.utl.pt>
+Sachin P Sant <ssant@in.ibm.com>
+Santtu Hyrkk\e,Av\e(B <santtu.hyrkko@gmail.com>
+Simon Kelley <simon@thekelleys.org.uk>
+Tejun Heo <htejun@gmail.com>
+Tony Luck <tony.luck@intel.com>
index a9de09fa2fec161d6a0170553f2b507bca483cef..d911efbb4b2e5a15d7550e5f1513b81915d7a6a9 100644 (file)
@@ -1,7 +1,7 @@
 syn region gitLine start=/^#/ end=/$/
-syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile
+syn region gitCommit start=/^# Added but not yet committed:$/ end=/^#$/ contains=gitHead,gitCommitFile
 syn region gitHead contained start=/^#   (.*)/ end=/^#$/
-syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile
+syn region gitChanged start=/^# Changed but not added:/ end=/^#$/ contains=gitHead,gitChangedFile
 syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile
 
 syn match gitCommitFile contained /^#\t.*/hs=s+2
index 881258311a47f9faa437ec02e99b4a0df1439d53..a63013298566fd3dc21275a90ca976227de8c7f0 100644 (file)
@@ -1,7 +1,3 @@
-#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
-#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
-#define _GNU_SOURCE
-#include <time.h>
 #include "cache.h"
 #include "blob.h"
 #include "commit.h"
index e66bb802da9faed6e01229fffbe56e3d7cca60c6..b129b83e4026490c1e6e77861cd6f03a5007d01e 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1,20 +1,10 @@
-#include <signal.h>
-#include <sys/wait.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/poll.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <syslog.h>
-#include <pwd.h>
-#include <grp.h>
-#include <limits.h>
-#include "pkt-line.h"
 #include "cache.h"
+#include "pkt-line.h"
 #include "exec_cmd.h"
 #include "interpolate.h"
 
+#include <syslog.h>
+
 #ifndef HOST_NAME_MAX
 #define HOST_NAME_MAX 256
 #endif
diff --git a/date.c b/date.c
index 18259227321f47988386c2bd70888b3c967d8ab6..7acb8cbd91bb1491931326ade1905d9c6f7bfdf5 100644 (file)
--- a/date.c
+++ b/date.c
@@ -4,9 +4,6 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 
-#include <time.h>
-#include <sys/time.h>
-
 #include "cache.h"
 
 static time_t my_mktime(struct tm *tm)
index fa16d06c8d1e85a458428c673cb2f589857f5424..9f998d0a73e0127d3a68a7caecb3727569149871 100644 (file)
  *  licensing gets turned into GPLv2 within this project.
  */
 
-#include <stdlib.h>
-#include <string.h>
-#include "delta.h"
-
 #include "git-compat-util.h"
+#include "delta.h"
 
 /* maximum hash entry list for the same hash bucket */
 #define HASH_LIMIT 64
diff --git a/diff.c b/diff.c
index b003c00016995b2afbe3eb23c4e64f7e43761987..f14288bb8a100c43c6709f658b9a9ca44832fd7f 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1,9 +1,6 @@
 /*
  * Copyright (C) 2005 Junio C Hamano
  */
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
 #include "cache.h"
 #include "quote.h"
 #include "diff.h"
@@ -12,6 +9,12 @@
 #include "xdiff-interface.h"
 #include "color.h"
 
+#ifdef NO_FAST_WORKING_DIRECTORY
+#define FAST_WORKING_DIRECTORY 0
+#else
+#define FAST_WORKING_DIRECTORY 1
+#endif
+
 static int use_size_cache;
 
 static int diff_detect_rename_default;
@@ -60,7 +63,7 @@ int git_diff_ui_config(const char *var, const char *value)
                diff_rename_limit_default = git_config_int(var, value);
                return 0;
        }
-       if (!strcmp(var, "diff.color")) {
+       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
@@ -74,7 +77,7 @@ int git_diff_ui_config(const char *var, const char *value)
                        diff_detect_rename_default = DIFF_DETECT_RENAME;
                return 0;
        }
-       if (!strncmp(var, "diff.color.", 11)) {
+       if (!strncmp(var, "diff.color.", 11) || !strncmp(var, "color.diff.", 11)) {
                int slot = parse_diff_color_slot(var, 11);
                color_parse(value, var, diff_colors[slot]);
                return 0;
@@ -795,6 +798,35 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
               set, total_files, adds, dels, reset);
 }
 
+static void show_shortstats(struct diffstat_t* data)
+{
+       int i, adds = 0, dels = 0, total_files = data->nr;
+
+       if (data->nr == 0)
+               return;
+
+       for (i = 0; i < data->nr; i++) {
+               if (!data->files[i]->is_binary &&
+                   !data->files[i]->is_unmerged) {
+                       int added = data->files[i]->added;
+                       int deleted= data->files[i]->deleted;
+                       if (!data->files[i]->is_renamed &&
+                           (added + deleted == 0)) {
+                               total_files--;
+                       } else {
+                               adds += added;
+                               dels += deleted;
+                       }
+               }
+               free(data->files[i]->name);
+               free(data->files[i]);
+       }
+       free(data->files);
+
+       printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
+              total_files, adds, dels);
+}
+
 static void show_numstat(struct diffstat_t* data, struct diff_options *options)
 {
        int i;
@@ -802,7 +834,10 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
 
-               printf("%d\t%d\t", file->added, file->deleted);
+               if (file->is_binary)
+                       printf("-\t-\t");
+               else
+                       printf("%d\t%d\t", file->added, file->deleted);
                if (options->line_termination &&
                    quote_c_style(file->name, NULL, NULL, 0))
                        quote_c_style(file->name, NULL, stdout, 0);
@@ -1155,7 +1190,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
  * the work tree has that object contents, return true, so that
  * prepare_temp_file() does not have to inflate and extract.
  */
-static int work_tree_matches(const char *name, const unsigned char *sha1)
+static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file)
 {
        struct cache_entry *ce;
        struct stat st;
@@ -1176,6 +1211,18 @@ static int work_tree_matches(const char *name, const unsigned char *sha1)
        if (!active_cache)
                return 0;
 
+       /* We want to avoid the working directory if our caller
+        * doesn't need the data in a normal file, this system
+        * is rather slow with its stat/open/mmap/close syscalls,
+        * and the object is contained in a pack file.  The pack
+        * is probably already open and will be faster to obtain
+        * the data through than the working directory.  Loose
+        * objects however would tend to be slower as they need
+        * to be individually opened and inflated.
+        */
+       if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1, NULL))
+               return 0;
+
        len = strlen(name);
        pos = cache_name_pos(name, len);
        if (pos < 0)
@@ -1262,7 +1309,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        if (s->data)
                return err;
        if (!s->sha1_valid ||
-           work_tree_matches(s->path, s->sha1)) {
+           reuse_worktree_file(s->path, s->sha1, 0)) {
                struct stat st;
                int fd;
                if (lstat(s->path, &st) < 0) {
@@ -1369,7 +1416,7 @@ static void prepare_temp_file(const char *name,
        }
 
        if (!one->sha1_valid ||
-           work_tree_matches(name, one->sha1)) {
+           reuse_worktree_file(name, one->sha1, 1)) {
                struct stat st;
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
@@ -1750,6 +1797,7 @@ int diff_setup_done(struct diff_options *options)
                options->output_format &= ~(DIFF_FORMAT_RAW |
                                            DIFF_FORMAT_NUMSTAT |
                                            DIFF_FORMAT_DIFFSTAT |
+                                           DIFF_FORMAT_SHORTSTAT |
                                            DIFF_FORMAT_SUMMARY |
                                            DIFF_FORMAT_PATCH);
 
@@ -1760,6 +1808,7 @@ int diff_setup_done(struct diff_options *options)
        if (options->output_format & (DIFF_FORMAT_PATCH |
                                      DIFF_FORMAT_NUMSTAT |
                                      DIFF_FORMAT_DIFFSTAT |
+                                     DIFF_FORMAT_SHORTSTAT |
                                      DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
                options->recursive = 1;
@@ -1851,6 +1900,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--numstat")) {
                options->output_format |= DIFF_FORMAT_NUMSTAT;
        }
+       else if (!strcmp(arg, "--shortstat")) {
+               options->output_format |= DIFF_FORMAT_SHORTSTAT;
+       }
        else if (!strncmp(arg, "--stat", 6)) {
                char *end;
                int width = options->stat_width;
@@ -2625,7 +2677,7 @@ void diff_flush(struct diff_options *options)
                separator++;
        }
 
-       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) {
+       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
                struct diffstat_t diffstat;
 
                memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2639,6 +2691,8 @@ void diff_flush(struct diff_options *options)
                        show_numstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_DIFFSTAT)
                        show_stats(&diffstat, options);
+               else if (output_format & DIFF_FORMAT_SHORTSTAT)
+                       show_shortstats(&diffstat);
                separator++;
        }
 
diff --git a/diff.h b/diff.h
index 101b2b505dcace41754687a427ad0523daaa50a0..eff445596d98e46d40dd37843e690de27c5fabf1 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -29,6 +29,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_FORMAT_NUMSTAT    0x0004
 #define DIFF_FORMAT_SUMMARY    0x0008
 #define DIFF_FORMAT_PATCH      0x0010
+#define DIFF_FORMAT_SHORTSTAT  0x0020
 
 /* These override all above */
 #define DIFF_FORMAT_NAME       0x0100
index aef6da60447b98a05d91513b4aa74e505b0a0d84..7ad09461858aa6832b828df411fd1b736f035465 100644 (file)
@@ -4,7 +4,6 @@
 #include "cache.h"
 #include "diff.h"
 #include "diffcore.h"
-#include <fnmatch.h>
 
 static char **order;
 static int order_cnt;
index cfcce315babb0edb7ad7be7f2ab2437aaf41eb9d..de44adabf09dcb47090e7865d6cd77677115d2b6 100644 (file)
@@ -5,8 +5,6 @@
 #include "diff.h"
 #include "diffcore.h"
 
-#include <regex.h>
-
 static unsigned int contains(struct diff_filespec *one,
                             const char *needle, unsigned long len,
                             regex_t *regexp)
diff --git a/dir.c b/dir.c
index 96389b32e66af24896220e4738cd93bf2ac75704..16401d8017c5d96dc17ac9d52ab77eabbc4e9270 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -5,9 +5,6 @@
  * Copyright (C) Linus Torvalds, 2005-2006
  *              Junio Hamano, 2005-2006
  */
-#include <dirent.h>
-#include <fnmatch.h>
-
 #include "cache.h"
 #include "dir.h"
 
@@ -156,7 +153,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
                die("cannot use %s as an exclude file", fname);
 }
 
-static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
 {
        char exclude_file[PATH_MAX];
        struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
@@ -170,7 +167,7 @@ static int push_exclude_per_directory(struct dir_struct *dir, const char *base,
        return current_nr;
 }
 
-static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
+void pop_exclude_per_directory(struct dir_struct *dir, int stk)
 {
        struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
 
diff --git a/dir.h b/dir.h
index 313f8ab64e7e3a6be1d22335eb54872c929d857e..550551ab25372c1898ba790e973808ccf0450924 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -43,6 +43,9 @@ extern int common_prefix(const char **pathspec);
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 
 extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen);
+extern int push_exclude_per_directory(struct dir_struct *, const char *, int);
+extern void pop_exclude_per_directory(struct dir_struct *, int);
+
 extern int excluded(struct dir_struct *, const char *);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void add_exclude(const char *string, const char *base,
diff --git a/entry.c b/entry.c
index b2ea0efa82e1a0511fe5aa798618c23827b59bab..88df7139477f94c236f93ca835c28ed4dd9543de 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,5 +1,3 @@
-#include <sys/types.h>
-#include <dirent.h>
 #include "cache.h"
 #include "blob.h"
 
index 84d870ca4eca6e202bcbec388e9a299b887f9a12..f8c7dbceadf2190997816f9a21b211a6a97efe48 100644 (file)
@@ -48,6 +48,16 @@ static void setup_git_env(void)
        git_graft_file = getenv(GRAFT_ENVIRONMENT);
        if (!git_graft_file)
                git_graft_file = xstrdup(git_path("info/grafts"));
+       log_all_ref_updates = !is_bare_git_dir(git_dir);
+}
+
+int is_bare_git_dir (const char *dir)
+{
+       const char *s;
+       if (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))
+               return 0;
+       s = strrchr(dir, '/');
+       return !s || strcmp(s + 1, DEFAULT_GIT_DIR_ENVIRONMENT);
 }
 
 const char *get_git_dir(void)
index 5d6a1247b4a1955dffae2b52da064a6eb489d83b..3996bce33fe11b8f0bae120fabd40a74c66de342 100644 (file)
@@ -21,7 +21,7 @@ const char *git_exec_path(void)
        if (current_exec_path)
                return current_exec_path;
 
-       env = getenv("GIT_EXEC_PATH");
+       env = getenv(EXEC_PATH_ENVIRONMENT);
        if (env && *env) {
                return env;
        }
@@ -35,7 +35,7 @@ int execv_git_cmd(const char **argv)
        char git_command[PATH_MAX + 1];
        int i;
        const char *paths[] = { current_exec_path,
-                               getenv("GIT_EXEC_PATH"),
+                               getenv(EXEC_PATH_ENVIRONMENT),
                                builtin_exec_path };
 
        for (i = 0; i < ARRAY_SIZE(paths); ++i) {
index 0a169dce8574102a03c978bb24f8d0664f1fc9b6..92322cf4da39434d44e4aa52fa0c3f735b22d752 100644 (file)
@@ -5,7 +5,6 @@
 #include "tag.h"
 #include "exec_cmd.h"
 #include "sideband.h"
-#include <sys/wait.h>
 
 static int keep_pack;
 static int quiet;
@@ -566,6 +565,29 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
        return 0;
 }
 
+static int remove_duplicates(int nr_heads, char **heads)
+{
+       int src, dst;
+
+       for (src = dst = 0; src < nr_heads; src++) {
+               /* If heads[src] is different from any of
+                * heads[0..dst], push it in.
+                */
+               int i;
+               for (i = 0; i < dst; i++) {
+                       if (!strcmp(heads[i], heads[src]))
+                               break;
+               }
+               if (i < dst)
+                       continue;
+               if (src != dst)
+                       heads[dst] = heads[src];
+               dst++;
+       }
+       heads[dst] = 0;
+       return dst;
+}
+
 int main(int argc, char **argv)
 {
        int i, ret, nr_heads;
@@ -617,6 +639,8 @@ int main(int argc, char **argv)
        pid = git_connect(fd, dest, exec);
        if (pid < 0)
                return 1;
+       if (heads && nr_heads)
+               nr_heads = remove_duplicates(nr_heads, heads);
        ret = fetch_pack(fd, nr_heads, heads);
        close(fd[0]);
        close(fd[1]);
diff --git a/fetch.c b/fetch.c
index 663b4b2f42744a2d12ba1d8661b8b0d3645ef82b..f69be82f10d287d71f6184c4b9203bdab3ce81fc 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -1,6 +1,5 @@
-#include "fetch.h"
-
 #include "cache.h"
+#include "fetch.h"
 #include "commit.h"
 #include "tree.h"
 #include "tree-walk.h"
index 46b628cb94375e3f645f868efb04547ffc20e6e7..409aea02b4f8570dbaf0a8425eb29e5d45515367 100644 (file)
@@ -1,6 +1,3 @@
-#include <sys/types.h>
-#include <dirent.h>
-
 #include "cache.h"
 #include "commit.h"
 #include "tree.h"
index 5450918be339bf101a6562d518c0f4cc7b37c0f2..06c42b042d999856aacb9e156cffc39eb8dbce3f 100755 (executable)
@@ -4,7 +4,7 @@ echo "/* Automatically generated by $0 */
 struct cmdname_help
 {
     char name[16];
-    char help[64];
+    char help[80];
 };
 
 struct cmdname_help common_cmds[] = {"
index afe322b20fb0b40dfeb1cb17dc3cad09096e943a..0126a77b924e7f1fe7f3913b84350ab35e6e043a 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -246,6 +246,10 @@ last=`cat "$dotest/last"`
 this=`cat "$dotest/next"`
 if test "$skip" = t
 then
+       if test -d "$GIT_DIR/rr-cache"
+       then
+               git-rerere clear
+       fi
        this=`expr "$this" + 1`
        resume=
 fi
@@ -397,17 +401,21 @@ do
                changed="$(git-diff-index --cached --name-only HEAD)"
                if test '' = "$changed"
                then
-                       echo "No changes - did you forget update-index?"
+                       echo "No changes - did you forget to use 'git add'?"
                        stop_here_user_resolve $this
                fi
                unmerged=$(git-ls-files -u)
                if test -n "$unmerged"
                then
                        echo "You still have unmerged paths in your index"
-                       echo "did you forget update-index?"
+                       echo "did you forget to use 'git add'?"
                        stop_here_user_resolve $this
                fi
                apply_status=0
+               if test -d "$GIT_DIR/rr-cache"
+               then
+                       git rerere
+               fi
                ;;
        esac
 
index 737abd0c094ae6cfb201fb46c28f4962a9826a02..4192a99fec1e137599b250a4f65c1b1e4201c601 100755 (executable)
@@ -161,7 +161,7 @@ then
     git-read-tree --reset -u $new
 else
     git-update-index --refresh >/dev/null
-    merge_error=$(git-read-tree -m -u $old $new 2>&1) || (
+    merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
        case "$merge" in
        '')
                echo >&2 "$merge_error"
@@ -172,7 +172,8 @@ else
        git diff-files --name-only | git update-index --remove --stdin &&
        work=`git write-tree` &&
        git read-tree --reset -u $new &&
-       git read-tree -m -u --aggressive $old $new $work || exit
+       git read-tree -m -u --aggressive --exclude-per-directory=.gitignore $old $new $work ||
+       exit
 
        if result=`git write-tree 2>/dev/null`
        then
index 3f006d1a778636e5906ad9e8671adb6561fa0d98..490f3e48db02d01cb480047c011aa7b670c431bc 100755 (executable)
@@ -14,7 +14,7 @@ die() {
 }
 
 usage() {
-       die "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+       die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
 }
 
 get_repo_base() {
@@ -48,6 +48,10 @@ Perhaps git-update-server-info needs to be run there?"
                case "$name" in
                *^*)    continue;;
                esac
+               case "$bare,$name" in
+               yes,* | ,heads/* | ,tags/*) ;;
+               *)      continue ;;
+               esac
                if test -n "$use_separate_remote" &&
                   branch_name=`expr "z$name" : 'zheads/\(.*\)'`
                then
@@ -115,7 +119,7 @@ bare=
 reference=
 origin=
 origin_override=
-use_separate_remote=
+use_separate_remote=t
 while
        case "$#,$1" in
        0,*) break ;;
@@ -133,8 +137,9 @@ while
        *,--template=*)
          template="$1" ;;
        *,-q|*,--quiet) quiet=-q ;;
-       *,--use-separate-remote)
-               use_separate_remote=t ;;
+       *,--use-separate-remote) ;;
+       *,--no-separate-remote)
+               die "clones are always made with separate-remote layout" ;;
        1,--reference) usage ;;
        *,--reference)
                shift; reference="$1" ;;
@@ -169,18 +174,15 @@ repo="$1"
 test -n "$repo" ||
     die 'you must specify a repository to clone.'
 
-# --bare implies --no-checkout
+# --bare implies --no-checkout and --no-separate-remote
 if test yes = "$bare"
 then
        if test yes = "$origin_override"
        then
                die '--bare and --origin $origin options are incompatible.'
        fi
-       if test t = "$use_separate_remote"
-       then
-               die '--bare and --use-separate-remote options are incompatible.'
-       fi
        no_checkout=yes
+       use_separate_remote=
 fi
 
 if test -z "$origin"
@@ -323,12 +325,8 @@ cd "$D" || exit
 
 if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD"
 then
-       # Figure out which remote branch HEAD points at.
-       case "$use_separate_remote" in
-       '')     remote_top=refs/heads ;;
-       *)      remote_top="refs/remotes/$origin" ;;
-       esac
-
+       # a non-bare repository is always in separate-remote layout
+       remote_top="refs/remotes/$origin"
        head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
        case "$head_sha1" in
        'ref: refs/'*)
@@ -362,41 +360,28 @@ then
                )
        )
 
-       # Write out remotes/$origin file, and update our "$head_points_at".
+       # Write out remote.$origin config, and update our "$head_points_at".
        case "$head_points_at" in
        ?*)
-               mkdir -p "$GIT_DIR/remotes" &&
+               # Local default branch
                git-symbolic-ref HEAD "refs/heads/$head_points_at" &&
-               case "$use_separate_remote" in
-               t)      origin_track="$remote_top/$head_points_at"
-                       git-update-ref HEAD "$head_sha1" ;;
-               *)      origin_track="$remote_top/$origin"
-                       git-update-ref "refs/heads/$origin" "$head_sha1" ;;
-               esac &&
-               echo >"$GIT_DIR/remotes/$origin" \
-               "URL: $repo
-Pull: refs/heads/$head_points_at:$origin_track" &&
-               (cd "$GIT_DIR/$remote_top" && find . -type f -print) |
-               while read dotslref
-               do
-                       name=`expr "$dotslref" : './\(.*\)'`
-                       if test "z$head_points_at" = "z$name"
-                       then
-                               continue
-                       fi
-                       if test "$use_separate_remote" = '' &&
-                          test "z$origin" = "z$name"
-                       then
-                               continue
-                       fi
-                       echo "Pull: refs/heads/${name}:$remote_top/${name}"
-               done >>"$GIT_DIR/remotes/$origin" &&
-               case "$use_separate_remote" in
-               t)
-                       rm -f "refs/remotes/$origin/HEAD"
-                       git-symbolic-ref "refs/remotes/$origin/HEAD" \
-                               "refs/remotes/$origin/$head_points_at"
-               esac
+
+               # Tracking branch for the primary branch at the remote.
+               origin_track="$remote_top/$head_points_at" &&
+               git-update-ref HEAD "$head_sha1" &&
+
+               # Upstream URL
+               git-repo-config remote."$origin".url "$repo" &&
+
+               # Set up the mappings to track the remote branches.
+               git-repo-config remote."$origin".fetch \
+                       "refs/heads/*:$remote_top/*" '^$' &&
+               rm -f "refs/remotes/$origin/HEAD"
+               git-symbolic-ref "refs/remotes/$origin/HEAD" \
+                       "refs/remotes/$origin/$head_points_at" &&
+
+               git-repo-config branch."$head_points_at".remote "$origin" &&
+               git-repo-config branch."$head_points_at".merge "refs/heads/$head_points_at"
        esac
 
        case "$no_checkout" in
index 81c3a0cb6152b02ccdcaed607efcd500813ce607..6bce41af4dc73d90028e76ba174e4e7795103d6c 100755 (executable)
@@ -80,6 +80,7 @@ no_edit=
 log_given=
 log_message=
 verify=t
+quiet=
 verbose=
 signoff=
 force_author=
@@ -241,6 +242,10 @@ $1"
                signoff=t
                shift
                ;;
+       -q|--q|--qu|--qui|--quie|--quiet)
+               quiet=t
+               shift
+               ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t
                shift
@@ -350,19 +355,9 @@ t,)
                        refuse_partial "Cannot do a partial commit during a merge."
                fi
                TMP_INDEX="$GIT_DIR/tmp-index$$"
-               if test -z "$initial_commit"
-               then
-                       # make sure index is clean at the specified paths, or
-                       # they are additions.
-                       dirty_in_index=`git-diff-index --cached --name-status \
-                               --diff-filter=DMTU HEAD -- "$@"`
-                       test -z "$dirty_in_index" ||
-                       refuse_partial "Different in index and the last commit:
-$dirty_in_index"
-               fi
                commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
 
-               # Build the temporary index and update the real index
+               # Build a temporary index and update the real index
                # the same way.
                if test -z "$initial_commit"
                then
@@ -525,7 +520,7 @@ then
        current="$(git-rev-parse --verify HEAD)"
 else
        if [ -z "$(git-ls-files)" ]; then
-               echo >&2 Nothing to commit
+               echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
                exit 1
        fi
        PARENTS=""
@@ -625,8 +620,17 @@ then
        git-rerere
 fi
 
-if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0
+if test "$ret" = 0
 then
-       "$GIT_DIR"/hooks/post-commit
+       if test -x "$GIT_DIR"/hooks/post-commit
+       then
+               "$GIT_DIR"/hooks/post-commit
+       fi
+       if test -z "$quiet"
+       then
+               echo "Created${initial_commit:+ initial} commit $commit"
+               git-diff-tree --shortstat --summary --root --no-commit-id HEAD
+       fi
 fi
+
 exit "$ret"
index 0272d043d0b82dc6ad0ff4836fe8db8ed1e1d960..41fa7f67bc0feaecad34f64411e1862d1a10122a 100644 (file)
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
+#ifndef __APPLE_CC__
+#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
+#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
+#endif
+#define _GNU_SOURCE
+#define _BSD_SOURCE
+
 #include <unistd.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <limits.h>
 #include <sys/param.h>
-#include <netinet/in.h>
 #include <sys/types.h>
 #include <dirent.h>
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <fnmatch.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <assert.h>
+#include <regex.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <grp.h>
+
+#ifndef NO_ICONV
+#include <iconv.h>
+#endif
 
 /* On most systems <limits.h> would have given us this, but
  * not on some systems (e.g. GNU/Hurd).
index 7bac16e9463a0d0119218458b853570be6188ff4..4863c91fe3c713c9e1b05d78b9bd22583ff4a4c0 100755 (executable)
@@ -2,9 +2,8 @@
 
 # Known limitations:
 # - does not propagate permissions
-# - tells "ready for commit" even when things could not be completed
-#   (not sure this is true anymore, more testing is needed)
-# - does not handle whitespace in pathnames at all.
+# - error handling has not been extensively tested
+#
 
 use strict;
 use Getopt::Std;
@@ -115,47 +114,40 @@ if ($opt_a) {
 }
 close MSG;
 
-my (@afiles, @dfiles, @mfiles, @dirs);
-my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);
-#print @files;
-$? && die "Error in git-diff-tree";
-foreach my $f (@files) {
-    chomp $f;
-    my @fields = split(m!\s+!, $f);
-    if ($fields[4] eq 'A') {
-        my $path = $fields[5];
-       push @afiles, $path;
-        # add any needed parent directories
-       $path = dirname $path;
-       while (!-d $path and ! grep { $_ eq $path } @dirs) {
-           unshift @dirs, $path;
-           $path = dirname $path;
-       }
-    }
-    if ($fields[4] eq 'M') {
-       push @mfiles, $fields[5];
-    }
-    if ($fields[4] eq 'D') {
-       push @dfiles, $fields[5];
-    }
+`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+
+## apply non-binary changes
+my $fuzz = $opt_p ? 0 : 2;
+
+print "Checking if patch will apply\n";
+
+my @stat;
+open APPLY, "GIT_DIR= git-apply -C$fuzz --binary --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch";
+@stat=<APPLY>;
+close APPLY || die "Cannot patch";
+my (@bfiles,@files,@afiles,@dfiles);
+chomp @stat;
+foreach (@stat) {
+       push (@bfiles,$1) if m/^-\t-\t(.*)$/;
+       push (@files, $1) if m/^-\t-\t(.*)$/;
+       push (@files, $1) if m/^\d+\t\d+\t(.*)$/;
+       push (@afiles,$1) if m/^ create mode [0-7]+ (.*)$/;
+       push (@dfiles,$1) if m/^ delete mode [0-7]+ (.*)$/;
 }
-my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles);
-@binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit);
-map { chomp } @binfiles;
-@abfiles = grep s/^Binary files \/dev\/null and b\/(.*) differ$/$1/, @binfiles;
-@dbfiles = grep s/^Binary files a\/(.*) and \/dev\/null differ$/$1/, @binfiles;
-@mbfiles = grep s/^Binary files a\/(.*) and b\/(.*) differ$/$1/, @binfiles;
-push @bfiles, @abfiles;
-push @bfiles, @dbfiles;
-push @bfiles, @mbfiles;
-push @mfiles, @mbfiles;
-
-$opt_v && print "The commit affects:\n ";
-$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n";
-undef @files; # don't need it anymore
+map { s/^"(.*)"$/$1/g } @bfiles,@files;
+map { s/\\([0-7]{3})/sprintf('%c',oct $1)/eg } @bfiles,@files;
 
 # check that the files are clean and up to date according to cvs
 my $dirty;
+my @dirs;
+foreach my $p (@afiles) {
+    my $path = dirname $p;
+    while (!-d $path and ! grep { $_ eq $path } @dirs) {
+       unshift @dirs, $path;
+       $path = dirname $path;
+    }
+}
+
 foreach my $d (@dirs) {
     if (-e $d) {
        $dirty = 1;
@@ -178,7 +170,8 @@ foreach my $f (@afiles) {
     }
 }
 
-foreach my $f (@mfiles, @dfiles) {
+foreach my $f (@files) {
+    next if grep { $_ eq $f } @afiles;
     # TODO:we need to handle removed in cvs
     my @status = grep(m/^File/,  safe_pipe_capture('cvs', '-q', 'status' ,$f));
     if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
@@ -195,78 +188,18 @@ if ($dirty) {
     }
 }
 
-###
-### NOTE: if you are planning to die() past this point
-###       you MUST call cleanupcvs(@files) before die()
-###
-
+print "Applying\n";
+`GIT_DIR= git-apply -C$fuzz --binary --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch";
 
-print "Creating new directories\n";
+print "Patch applied successfully. Adding new files and directories to CVS\n";
+my $dirtypatch = 0;
 foreach my $d (@dirs) {
-    unless (mkdir $d) {
-        warn "Could not mkdir $d: $!";
-       $dirty = 1;
-    }
-    `cvs add $d`;
-    if ($?) {
-       $dirty = 1;
+    if (system('cvs','add',$d)) {
+       $dirtypatch = 1;
        warn "Failed to cvs add directory $d -- you may need to do it manually";
     }
 }
 
-print "'Patching' binary files\n";
-
-foreach my $f (@bfiles) {
-    # check that the file in cvs matches the "old" file
-    # extract the file to $tmpdir and compare with cmp
-    if (not(grep { $_ eq $f } @afiles)) {
-        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`;
-       chomp $blob;
-        `git-cat-file blob $blob > $tmpdir/blob`;
-        if (system('cmp', '-s', $f, "$tmpdir/blob")) {
-           warn "Binary file $f in CVS does not match parent.\n";
-           if (not $opt_f) {
-               $dirty = 1;
-               next;
-           }
-        }
-    }
-    if (not(grep { $_ eq $f } @dfiles)) {
-       my $tree = safe_pipe_capture('git-rev-parse', "$commit^{tree}");
-       chomp $tree;
-       my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
-       chomp $blob;
-       # replace with the new file
-       `git-cat-file blob $blob > $f`;
-    }
-
-    # TODO: something smart with file modes
-
-}
-if ($dirty) {
-    cleanupcvs(@files);
-    die "Exiting: Binary files in CVS do not match parent";
-}
-
-## apply non-binary changes
-my $fuzz = $opt_p ? 0 : 2;
-
-print "Patching non-binary files\n";
-
-if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) {
-    print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;
-}
-
-my $dirtypatch = 0;
-if (($? >> 8) == 2) {
-    cleanupcvs(@files);
-    die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually";
-} elsif (($? >> 8) == 1) { # some hunks failed to apply
-    $dirtypatch = 1;
-}
-
 foreach my $f (@afiles) {
     if (grep { $_ eq $f } @bfiles) {
       system('cvs', 'add','-kb',$f);
@@ -274,7 +207,7 @@ foreach my $f (@afiles) {
       system('cvs', 'add', $f);
     }
     if ($?) {
-       $dirty = 1;
+       $dirtypatch = 1;
        warn "Failed to cvs add $f -- you may need to do it manually";
     }
 }
@@ -282,35 +215,40 @@ foreach my $f (@afiles) {
 foreach my $f (@dfiles) {
     system('cvs', 'rm', '-f', $f);
     if ($?) {
-       $dirty = 1;
+       $dirtypatch = 1;
        warn "Failed to cvs rm -f $f -- you may need to do it manually";
     }
 }
 
 print "Commit to CVS\n";
-print "Patch: $title\n";
-my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);
-my $cmd = "cvs commit -F .msg $commitfiles";
+print "Patch title (first comment line): $title\n";
+my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files);
+my $cmd = "cvs commit -F .msg @commitfiles";
 
 if ($dirtypatch) {
     print "NOTE: One or more hunks failed to apply cleanly.\n";
-    print "Resolve the conflicts and then commit using:\n";
+    print "You'll need to apply the patch in .cvsexportcommit.diff manually\n";
+    print "using a patch program. After applying the patch and resolving the\n";
+    print "problems you may commit using:";
     print "\n    $cmd\n\n";
     exit(1);
 }
 
-
 if ($opt_c) {
     print "Autocommit\n  $cmd\n";
-    print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles);
+    print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files);
     if ($?) {
-       cleanupcvs(@files);
        die "Exiting: The commit did not succeed";
     }
     print "Committed successfully to CVS\n";
 } else {
     print "Ready for you to commit, just run:\n\n   $cmd\n";
 }
+
+# clean up
+unlink(".cvsexportcommit.diff");
+unlink(".msg");
+
 sub usage {
        print STDERR <<END;
 Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
@@ -318,17 +256,6 @@ END
        exit(1);
 }
 
-# ensure cvs is clean before we die
-sub cleanupcvs {
-    my @files = @_;
-    foreach my $f (@files) {
-       system('cvs', '-q', 'update', '-C', $f);
-       if ($?) {
-           warn "Warning! Failed to cleanup state of $f\n";
-       }
-    }
-}
-
 # 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
@@ -342,3 +269,16 @@ sub safe_pipe_capture {
     }
     return wantarray ? @output : join('',@output);
 }
+
+sub safe_pipe_capture_blob {
+    my $output;
+    if (my $pid = open my $child, '-|') {
+        local $/;
+       undef $/;
+       $output = (<$child>);
+       close $child or die join(' ',@_).": $! $?";
+    } else {
+       exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
+    }
+    return $output;
+}
index b54a9486d2703120ca98a176e4b52b3bf8f91aa6..c5bf2d19cda6104f4fa9ade7c1407bb6ab8b5d1e 100755 (executable)
@@ -29,7 +29,7 @@ use IPC::Open2;
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L);
+our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L);
 my (%conv_author_name, %conv_author_email);
 
 sub usage() {
@@ -90,15 +90,15 @@ usage if $opt_h;
 
 @ARGV <= 1 or usage();
 
-if($opt_d) {
+if ($opt_d) {
        $ENV{"CVSROOT"} = $opt_d;
-} elsif(-f 'CVS/Root') {
+} elsif (-f 'CVS/Root') {
        open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
        $opt_d = <$f>;
        chomp $opt_d;
        close $f;
        $ENV{"CVSROOT"} = $opt_d;
-} elsif($ENV{"CVSROOT"}) {
+} elsif ($ENV{"CVSROOT"}) {
        $opt_d = $ENV{"CVSROOT"};
 } else {
        die "CVSROOT needs to be set";
@@ -141,7 +141,7 @@ use File::Temp qw(tempfile);
 use POSIX qw(strftime dup2);
 
 sub new {
-       my($what,$repo,$subdir) = @_;
+       my ($what,$repo,$subdir) = @_;
        $what=ref($what) if ref($what);
 
        my $self = {};
@@ -161,24 +161,38 @@ sub new {
 sub conn {
        my $self = shift;
        my $repo = $self->{'fullrep'};
-       if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
-               my($user,$pass,$serv,$port) = ($1,$2,$3,$4);
+       if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+               my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
+
+               my ($proxyhost,$proxyport);
+               if ($param && ($param =~ m/proxy=([^;]+)/)) {
+                       $proxyhost = $1;
+                       # Default proxyport, if not specified, is 8080.
+                       $proxyport = 8080;
+                       if ($ENV{"CVS_PROXY_PORT"}) {
+                               $proxyport = $ENV{"CVS_PROXY_PORT"};
+                       }
+                       if ($param =~ m/proxyport=([^;]+)/) {
+                               $proxyport = $1;
+                       }
+               }
+
                $user="anonymous" unless defined $user;
                my $rr2 = "-";
-               unless($port) {
+               unless ($port) {
                        $rr2 = ":pserver:$user\@$serv:$repo";
                        $port=2401;
                }
                my $rr = ":pserver:$user\@$serv:$port$repo";
 
-               unless($pass) {
+               unless ($pass) {
                        open(H,$ENV{'HOME'}."/.cvspass") and do {
                                # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
-                               while(<H>) {
+                               while (<H>) {
                                        chomp;
                                        s/^\/\d+\s+//;
                                        my ($w,$p) = split(/\s/,$_,2);
-                                       if($w eq $rr or $w eq $rr2) {
+                                       if ($w eq $rr or $w eq $rr2) {
                                                $pass = $p;
                                                last;
                                        }
@@ -187,15 +201,45 @@ sub conn {
                }
                $pass="A" unless $pass;
 
-               my $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
-               die "Socket to $serv: $!\n" unless defined $s;
+               my ($s, $rep);
+               if ($proxyhost) {
+
+                       # Use a HTTP Proxy. Only works for HTTP proxies that
+                       # don't require user authentication
+                       #
+                       # See: http://www.ietf.org/rfc/rfc2817.txt
+
+                       $s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
+                       die "Socket to $proxyhost: $!\n" unless defined $s;
+                       $s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
+                               or die "Write to $proxyhost: $!\n";
+                       $s->flush();
+
+                       $rep = <$s>;
+
+                       # The answer should look like 'HTTP/1.x 2yy ....'
+                       if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
+                               die "Proxy connect: $rep\n";
+                       }
+                       # Skip up to the empty line of the proxy server output
+                       # including the response headers.
+                       while ($rep = <$s>) {
+                               last if (!defined $rep ||
+                                        $rep eq "\n" ||
+                                        $rep eq "\r\n");
+                       }
+               } else {
+                       $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
+                       die "Socket to $serv: $!\n" unless defined $s;
+               }
+
                $s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
                        or die "Write to $serv: $!\n";
                $s->flush();
 
-               my $rep = <$s>;
+               $rep = <$s>;
 
-               if($rep ne "I LOVE YOU\n") {
+               if ($rep ne "I LOVE YOU\n") {
                        $rep="<unknown>" unless $rep;
                        die "AuthReply: $rep\n";
                }
@@ -227,7 +271,7 @@ sub conn {
                    }
                }
 
-               unless($pid) {
+               unless ($pid) {
                        $pr->writer();
                        $pw->reader();
                        dup2($pw->fileno(),0);
@@ -250,7 +294,7 @@ sub conn {
        $self->{'socketo'}->flush();
 
        chomp(my $rep=$self->readline());
-       if($rep !~ s/^Valid-requests\s*//) {
+       if ($rep !~ s/^Valid-requests\s*//) {
                $rep="<unknown>" unless $rep;
                die "Expected Valid-requests from server, but got: $rep\n";
        }
@@ -262,14 +306,14 @@ sub conn {
 }
 
 sub readline {
-       my($self) = @_;
+       my ($self) = @_;
        return $self->{'socketi'}->getline();
 }
 
 sub _file {
        # Request a file with a given revision.
        # Trial and error says this is a good way to do it. :-/
-       my($self,$fn,$rev) = @_;
+       my ($self,$fn,$rev) = @_;
        $self->{'socketo'}->write("Argument -N\n") or return undef;
        $self->{'socketo'}->write("Argument -P\n") or return undef;
        # -kk: Linus' version doesn't use it - defaults to off
@@ -291,12 +335,12 @@ sub _file {
 sub _line {
        # Read a line from the server.
        # ... except that 'line' may be an entire file. ;-)
-       my($self, $fh) = @_;
+       my ($self, $fh) = @_;
        die "Not in lines" unless defined $self->{'lines'};
 
        my $line;
        my $res=0;
-       while(defined($line = $self->readline())) {
+       while (defined($line = $self->readline())) {
                # M U gnupg-cvs-rep/AUTHORS
                # Updated gnupg-cvs-rep/
                # /daten/src/rsync/gnupg-cvs-rep/AUTHORS
@@ -305,7 +349,7 @@ sub _line {
                # 0
                # ok
 
-               if($line =~ s/^(?:Created|Updated) //) {
+               if ($line =~ s/^(?:Created|Updated) //) {
                        $line = $self->readline(); # path
                        $line = $self->readline(); # Entries line
                        my $mode = $self->readline(); chomp $mode;
@@ -316,12 +360,12 @@ sub _line {
                        die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
                        $line="";
                        $res = $self->_fetchfile($fh, $cnt);
-               } elsif($line =~ s/^ //) {
+               } elsif ($line =~ s/^ //) {
                        print $fh $line;
                        $res += length($line);
-               } elsif($line =~ /^M\b/) {
+               } elsif ($line =~ /^M\b/) {
                        # output, do nothing
-               } elsif($line =~ /^Mbinary\b/) {
+               } elsif ($line =~ /^Mbinary\b/) {
                        my $cnt;
                        die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
                        chomp $cnt;
@@ -330,12 +374,12 @@ sub _line {
                        $res += $self->_fetchfile($fh, $cnt);
                } else {
                        chomp $line;
-                       if($line eq "ok") {
+                       if ($line eq "ok") {
                                # print STDERR "S: ok (".length($res).")\n";
                                return $res;
-                       } elsif($line =~ s/^E //) {
+                       } elsif ($line =~ s/^E //) {
                                # print STDERR "S: $line\n";
-                       } elsif($line =~ /^(Remove-entry|Removed) /i) {
+                       } elsif ($line =~ /^(Remove-entry|Removed) /i) {
                                $line = $self->readline(); # filename
                                $line = $self->readline(); # OK
                                chomp $line;
@@ -349,7 +393,7 @@ sub _line {
        return undef;
 }
 sub file {
-       my($self,$fn,$rev) = @_;
+       my ($self,$fn,$rev) = @_;
        my $res;
 
        my ($fh, $name) = tempfile('gitcvs.XXXXXX', 
@@ -373,7 +417,7 @@ sub _fetchfile {
        my ($self, $fh, $cnt) = @_;
        my $res = 0;
        my $bufsize = 1024 * 1024;
-       while($cnt) {
+       while ($cnt) {
            if ($bufsize > $cnt) {
                $bufsize = $cnt;
            }
@@ -394,7 +438,7 @@ my $cvs = CVSconn->new($opt_d, $cvs_tree);
 
 
 sub pdate($) {
-       my($d) = @_;
+       my ($d) = @_;
        m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?#
                or die "Unparseable date: $d\n";
        my $y=$1; $y-=1900 if $y>1900;
@@ -402,22 +446,22 @@ sub pdate($) {
 }
 
 sub pmode($) {
-       my($mode) = @_;
+       my ($mode) = @_;
        my $m = 0;
        my $mm = 0;
        my $um = 0;
        for my $x(split(//,$mode)) {
-               if($x eq ",") {
+               if ($x eq ",") {
                        $m |= $mm&$um;
                        $mm = 0;
                        $um = 0;
-               } elsif($x eq "u") { $um |= 0700;
-               } elsif($x eq "g") { $um |= 0070;
-               } elsif($x eq "o") { $um |= 0007;
-               } elsif($x eq "r") { $mm |= 0444;
-               } elsif($x eq "w") { $mm |= 0222;
-               } elsif($x eq "x") { $mm |= 0111;
-               } elsif($x eq "=") { # do nothing
+               } elsif ($x eq "u") { $um |= 0700;
+               } elsif ($x eq "g") { $um |= 0070;
+               } elsif ($x eq "o") { $um |= 0007;
+               } elsif ($x eq "r") { $mm |= 0444;
+               } elsif ($x eq "w") { $mm |= 0222;
+               } elsif ($x eq "x") { $mm |= 0111;
+               } elsif ($x eq "=") { # do nothing
                } else { die "Unknown mode: $mode\n";
                }
        }
@@ -441,7 +485,7 @@ sub get_headref ($$) {
     my $git_dir = shift; 
     
     my $f = "$git_dir/refs/heads/$name";
-    if(open(my $fh, $f)) {
+    if (open(my $fh, $f)) {
            chomp(my $r = <$fh>);
            is_sha1($r) or die "Cannot get head id for $name ($r): $!";
            return $r;
@@ -468,7 +512,7 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
 
 my %index; # holds filenames of one index per branch
 
-unless(-d $git_dir) {
+unless (-d $git_dir) {
        system("git-init-db");
        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
        system("git-read-tree");
@@ -487,7 +531,7 @@ unless(-d $git_dir) {
        chomp ($last_branch = <F>);
        $last_branch = basename($last_branch);
        close(F);
-       unless($last_branch) {
+       unless ($last_branch) {
                warn "Cannot read the last branch name: $! -- assuming 'master'\n";
                $last_branch = "master";
        }
@@ -498,7 +542,7 @@ unless(-d $git_dir) {
        my $fmt = '($ref, $author) = (%(refname), %(author));';
        open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or
                die "Cannot run git-for-each-ref: $!\n";
-       while(defined(my $entry = <H>)) {
+       while (defined(my $entry = <H>)) {
                my ($ref, $author);
                eval($entry) || die "cannot eval refs list: $@";
                my ($head) = ($ref =~ m|^refs/heads/(.*)|);
@@ -528,7 +572,7 @@ unless ($opt_P) {
        print "Running cvsps...\n" if $opt_v;
        my $pid = open(CVSPS,"-|");
        die "Cannot fork: $!\n" unless defined $pid;
-       unless($pid) {
+       unless ($pid) {
                my @opt;
                @opt = split(/,/,$opt_p) if defined $opt_p;
                unshift @opt, '-z', $opt_z if defined $opt_z;
@@ -598,8 +642,8 @@ sub write_tree () {
        return $tree;
 }
 
-my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
-my(@old,@new,@skipped,%ignorebranch);
+my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
+my (@old,@new,@skipped,%ignorebranch);
 
 # commits that cvsps cannot place anywhere...
 $ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
@@ -640,7 +684,7 @@ sub commit {
        foreach my $rx (@mergerx) {
                next unless $logmsg =~ $rx && $1;
                my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
-               if(my $sha1 = get_headref($mparent, $git_dir)) {
+               if (my $sha1 = get_headref($mparent, $git_dir)) {
                        push @commit_args, '-p', $mparent;
                        print "Merge parent branch: $mparent\n" if $opt_v;
                }
@@ -681,9 +725,9 @@ sub commit {
        system("git-update-ref refs/heads/$branch $cid") == 0
                or die "Cannot write branch $branch for update: $!\n";
 
-       if($tag) {
-               my($in, $out) = ('','');
-               my($xtag) = $tag;
+       if ($tag) {
+               my ($in, $out) = ('','');
+               my ($xtag) = $tag;
                $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
                $xtag =~ tr/_/\./ if ( $opt_u );
                $xtag =~ s/[\/]/$opt_s/g;
@@ -718,25 +762,25 @@ sub commit {
 };
 
 my $commitcount = 1;
-while(<CVS>) {
+while (<CVS>) {
        chomp;
-       if($state == 0 and /^-+$/) {
+       if ($state == 0 and /^-+$/) {
                $state = 1;
-       } elsif($state == 0) {
+       } elsif ($state == 0) {
                $state = 1;
                redo;
-       } elsif(($state==0 or $state==1) and s/^PatchSet\s+//) {
+       } elsif (($state==0 or $state==1) and s/^PatchSet\s+//) {
                $patchset = 0+$_;
                $state=2;
-       } elsif($state == 2 and s/^Date:\s+//) {
+       } elsif ($state == 2 and s/^Date:\s+//) {
                $date = pdate($_);
-               unless($date) {
+               unless ($date) {
                        print STDERR "Could not parse date: $_\n";
                        $state=0;
                        next;
                }
                $state=3;
-       } elsif($state == 3 and s/^Author:\s+//) {
+       } elsif ($state == 3 and s/^Author:\s+//) {
                s/\s+$//;
                if (/^(.*?)\s+<(.*)>/) {
                    ($author_name, $author_email) = ($1, $2);
@@ -747,34 +791,34 @@ while(<CVS>) {
                    $author_name = $author_email = $_;
                }
                $state = 4;
-       } elsif($state == 4 and s/^Branch:\s+//) {
+       } elsif ($state == 4 and s/^Branch:\s+//) {
                s/\s+$//;
                s/[\/]/$opt_s/g;
                $branch = $_;
                $state = 5;
-       } elsif($state == 5 and s/^Ancestor branch:\s+//) {
+       } elsif ($state == 5 and s/^Ancestor branch:\s+//) {
                s/\s+$//;
                $ancestor = $_;
                $ancestor = $opt_o if $ancestor eq "HEAD";
                $state = 6;
-       } elsif($state == 5) {
+       } elsif ($state == 5) {
                $ancestor = undef;
                $state = 6;
                redo;
-       } elsif($state == 6 and s/^Tag:\s+//) {
+       } elsif ($state == 6 and s/^Tag:\s+//) {
                s/\s+$//;
-               if($_ eq "(none)") {
+               if ($_ eq "(none)") {
                        $tag = undef;
                } else {
                        $tag = $_;
                }
                $state = 7;
-       } elsif($state == 7 and /^Log:/) {
+       } elsif ($state == 7 and /^Log:/) {
                $logmsg = "";
                $state = 8;
-       } elsif($state == 8 and /^Members:/) {
+       } elsif ($state == 8 and /^Members:/) {
                $branch = $opt_o if $branch eq "HEAD";
-               if(defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
+               if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
                        # skip
                        print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
                        $state = 11;
@@ -785,17 +829,17 @@ while(<CVS>) {
                        $state = 11;
                        next;
                }
-               if($ancestor) {
-                       if($ancestor eq $branch) {
+               if ($ancestor) {
+                       if ($ancestor eq $branch) {
                                print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
                                $ancestor = $opt_o;
                        }
-                       if(-f "$git_dir/refs/heads/$branch") {
+                       if (-f "$git_dir/refs/heads/$branch") {
                                print STDERR "Branch $branch already exists!\n";
                                $state=11;
                                next;
                        }
-                       unless(open(H,"$git_dir/refs/heads/$ancestor")) {
+                       unless (open(H,"$git_dir/refs/heads/$ancestor")) {
                                print STDERR "Branch $ancestor does not exist!\n";
                                $ignorebranch{$branch} = 1;
                                $state=11;
@@ -803,7 +847,7 @@ while(<CVS>) {
                        }
                        chomp(my $id = <H>);
                        close(H);
-                       unless(open(H,"> $git_dir/refs/heads/$branch")) {
+                       unless (open(H,"> $git_dir/refs/heads/$branch")) {
                                print STDERR "Could not create branch $branch: $!\n";
                                $ignorebranch{$branch} = 1;
                                $state=11;
@@ -816,9 +860,9 @@ while(<CVS>) {
                }
                $last_branch = $branch if $branch ne $last_branch;
                $state = 9;
-       } elsif($state == 8) {
+       } elsif ($state == 8) {
                $logmsg .= "$_\n";
-       } elsif($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
+       } elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
 #      VERSION:1.96->1.96.2.1
                my $init = ($2 eq "INITIAL");
                my $fn = $1;
@@ -831,7 +875,7 @@ while(<CVS>) {
                }
                print "Fetching $fn   v $rev\n" if $opt_v;
                my ($tmpname, $size) = $cvs->file($fn,$rev);
-               if($size == -1) {
+               if ($size == -1) {
                        push(@old,$fn);
                        print "Drop $fn\n" if $opt_v;
                } else {
@@ -849,14 +893,14 @@ while(<CVS>) {
                        push(@new,[$mode, $sha, $fn]); # may be resurrected!
                }
                unlink($tmpname);
-       } elsif($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
+       } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
                my $fn = $1;
                $fn =~ s#^/+##;
                push(@old,$fn);
                print "Delete $fn\n" if $opt_v;
-       } elsif($state == 9 and /^\s*$/) {
+       } elsif ($state == 9 and /^\s*$/) {
                $state = 10;
-       } elsif(($state == 9 or $state == 10) and /^-+$/) {
+       } elsif (($state == 9 or $state == 10) and /^-+$/) {
                $commitcount++;
                if ($opt_L && $commitcount > $opt_L) {
                        last;
@@ -866,11 +910,11 @@ while(<CVS>) {
                        system("git repack -a -d");
                }
                $state = 1;
-       } elsif($state == 11 and /^-+$/) {
+       } elsif ($state == 11 and /^-+$/) {
                $state = 1;
-       } elsif(/^-+$/) { # end of unknown-line processing
+       } elsif (/^-+$/) { # end of unknown-line processing
                $state = 1;
-       } elsif($state != 11) { # ignore stuff when skipping
+       } elsif ($state != 11) { # ignore stuff when skipping
                print "* UNKNOWN LINE * $_\n";
        }
 }
@@ -899,7 +943,7 @@ if (defined $orig_git_index) {
 }
 
 # Now switch back to the branch we were in before all of this happened
-if($orig_branch) {
+if ($orig_branch) {
        print "DONE.\n" if $opt_v;
        if ($opt_i) {
                exit 0;
index 197014d9e6cdf3285005262a9e53804860e489c7..df395126b86bbed4d8f785e7eccbdb091c3f888b 100755 (executable)
@@ -946,7 +946,7 @@ sub req_update
 
             $log->debug("Temporary directory for merge is $dir");
 
-            my $return = system("merge", $file_local, $file_old, $file_new);
+            my $return = system("git", "merge-file", $file_local, $file_old, $file_new);
             $return >>= 8;
 
             if ( $return == 0 )
index eb32476bbdc98cc9a34d4026575e4d1a608289be..ffbd44f0e1ab841e6f6a38295860220b01bb26ea 100755 (executable)
@@ -2,7 +2,13 @@
 #
 
 USAGE='<fetch-options> <repository> <refspec>...'
+SUBDIRECTORY_OK=Yes
 . git-sh-setup
+TOP=$(git-rev-parse --show-cdup)
+if test ! -z "$TOP"
+then
+       cd "$TOP"
+fi
 . git-parse-remote
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
@@ -88,6 +94,10 @@ then
        : >"$GIT_DIR/FETCH_HEAD"
 fi
 
+# Global that is reused later
+ls_remote_result=$(git ls-remote $upload_pack "$remote") ||
+       die "Cannot get the repository state from $remote"
+
 append_fetch_head () {
     head_="$1"
     remote_="$2"
@@ -232,11 +242,8 @@ esac
 reflist=$(get_remote_refs_for_fetch "$@")
 if test "$tags"
 then
-       taglist=`IFS="  " &&
-                 (
-                       git-ls-remote $upload_pack --tags "$remote" ||
-                       echo fail ouch
-                 ) |
+       taglist=`IFS='  ' &&
+                 echo "$ls_remote_result" |
                  while read sha1 name
                  do
                        case "$sha1" in
@@ -245,6 +252,8 @@ then
                        esac
                        case "$name" in
                        *^*) continue ;;
+                       refs/tags/*) ;;
+                       *) continue ;;
                        esac
                        if git-check-ref-format "$name"
                        then
@@ -304,22 +313,20 @@ fetch_main () {
                "`git-repo-config --bool http.noEPSV`" = true ]; then
              noepsv_opt="--disable-epsv"
          fi
-         max_depth=5
-         depth=0
-         head="ref: $remote_name"
-         while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
-         do
-           remote_name_quoted=$(@@PERL@@ -e '
-             my $u = $ARGV[0];
-              $u =~ s/^ref:\s*//;
-             $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
-             print "$u";
-         ' "$head")
-           head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted")
-           depth=$( expr \( $depth + 1 \) )
-         done
+
+         # Find $remote_name from ls-remote output.
+         head=$(
+               IFS='   '
+               echo "$ls_remote_result" |
+               while read sha1 name
+               do
+                       test "z$name" = "z$remote_name" || continue
+                       echo "$sha1"
+                       break
+               done
+         )
          expr "z$head" : "z$_x40\$" >/dev/null ||
-             die "Failed to fetch $remote_name from $remote"
+               die "No such ref $remote_name at $remote"
          echo >&2 "Fetching $remote_name from $remote using $proto"
          git-http-fetch -v -a "$head" "$remote/" || exit
          ;;
@@ -359,7 +366,7 @@ fetch_main () {
       esac
 
       append_fetch_head "$head" "$remote" \
-         "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+         "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit
 
   done
 
@@ -413,15 +420,16 @@ fetch_main () {
          done
          local_name=$(expr "z$found" : 'z[^:]*:\(.*\)')
          append_fetch_head "$sha1" "$remote" \
-                 "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
-      done
+                 "$remote_name" "$remote_nick" "$local_name" \
+                 "$not_for_merge" || exit
+      done &&
       if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi
     ) || exit ;;
   esac
 
 }
 
-fetch_main "$reflist"
+fetch_main "$reflist" || exit
 
 # automated tag following
 case "$no_tags$tags" in
@@ -430,17 +438,11 @@ case "$no_tags$tags" in
        *:refs/*)
                # effective only when we are following remote branch
                # using local tracking branch.
-               taglist=$(IFS=" " &&
-               git-ls-remote $upload_pack --tags "$remote" |
-               sed -n  -e 's|^\('"$_x40"'\)    \(refs/tags/.*\)^{}$|\1 \2|p' \
-                       -e 's|^\('"$_x40"'\)    \(refs/tags/.*\)$|\1 \2|p' |
+               taglist=$(IFS=' ' &&
+               echo "$ls_remote_result" |
+               git-show-ref --exclude-existing=refs/tags/ |
                while read sha1 name
                do
-                       git-show-ref --verify --quiet -- "$name" && continue
-                       git-check-ref-format "$name" || {
-                               echo >&2 "warning: tag ${name} ignored"
-                               continue
-                       }
                        git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
                        echo >&2 "Auto-following $name"
                        echo ".${name}:${name}"
@@ -449,7 +451,7 @@ case "$no_tags$tags" in
        case "$taglist" in
        '') ;;
        ?*)
-               fetch_main "$taglist" ;;
+               fetch_main "$taglist" || exit ;;
        esac
 esac
 
index 0f88953f299c1fd1243c3d5d6ae1e71d56e842a8..03b624ef33dd3690a3cf24b4eaab830e5875789b 100755 (executable)
@@ -94,7 +94,7 @@ while read sha1 path
 do
        case "$sha1" in
        failed)
-               die "Failed to find remote refs"
+               exit 1 ;;
        esac
        case "$path" in
        refs/heads/*)
index c49e4c65af606496c7e65b5147e36fee105d9ae7..7d62d7902cb1d7cd2749091b86d532306bea89b3 100755 (executable)
@@ -104,7 +104,7 @@ case "${1:-.}${2:-.}${3:-.}" in
        # Be careful for funny filename such as "-L" in "$4", which
        # would confuse "merge" greatly.
        src1=`git-unpack-file $2`
-       merge "$src1" "$orig" "$src2"
+       git-merge-file "$src1" "$orig" "$src2"
        ret=$?
 
        # Create the working tree file, using "our tree" version from the
diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py
deleted file mode 100755 (executable)
index 4039435..0000000
+++ /dev/null
@@ -1,944 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright (C) 2005 Fredrik Kuivinen
-#
-
-import sys
-sys.path.append('''@@GIT_PYTHON_PATH@@''')
-
-import math, random, os, re, signal, tempfile, stat, errno, traceback
-from heapq import heappush, heappop
-from sets import Set
-
-from gitMergeCommon import *
-
-outputIndent = 0
-def output(*args):
-    sys.stdout.write('  '*outputIndent)
-    printList(args)
-
-originalIndexFile = os.environ.get('GIT_INDEX_FILE',
-                                   os.environ.get('GIT_DIR', '.git') + '/index')
-temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
-                     '/merge-recursive-tmp-index'
-def setupIndex(temporary):
-    try:
-        os.unlink(temporaryIndexFile)
-    except OSError:
-        pass
-    if temporary:
-        newIndex = temporaryIndexFile
-    else:
-        newIndex = originalIndexFile
-    os.environ['GIT_INDEX_FILE'] = newIndex
-
-# This is a global variable which is used in a number of places but
-# only written to in the 'merge' function.
-
-# cacheOnly == True  => Don't leave any non-stage 0 entries in the cache and
-#                       don't update the working directory.
-#              False => Leave unmerged entries in the cache and update
-#                       the working directory.
-
-cacheOnly = False
-
-# The entry point to the merge code
-# ---------------------------------
-
-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 cleanness of the merge.'''
-    assert(isinstance(h1, Commit) and isinstance(h2, Commit))
-
-    global outputIndent
-
-    output('Merging:')
-    output(h1)
-    output(h2)
-    sys.stdout.flush()
-
-    if ancestor:
-        ca = [ancestor]
-    else:
-        assert(isinstance(graph, Graph))
-        ca = getCommonAncestors(graph, h1, h2)
-    output('found', len(ca), 'common ancestor(s):')
-    for x in ca:
-        output(x)
-    sys.stdout.flush()
-
-    mergedCA = ca[0]
-    for h in ca[1:]:
-        outputIndent = callDepth+1
-        [mergedCA, dummy] = merge(mergedCA, h,
-                                  'Temporary merge branch 1',
-                                  'Temporary merge branch 2',
-                                  graph, callDepth+1)
-        outputIndent = callDepth
-        assert(isinstance(mergedCA, Commit))
-
-    global cacheOnly
-    if callDepth == 0:
-        setupIndex(False)
-        cacheOnly = False
-    else:
-        setupIndex(True)
-        runProgram(['git-read-tree', h1.tree()])
-        cacheOnly = True
-
-    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
-                                 branch1Name, branch2Name)
-
-    if graph and (clean or cacheOnly):
-        res = Commit(None, [h1, h2], tree=shaRes)
-        graph.addNode(res)
-    else:
-        res = None
-
-    return [res, clean]
-
-getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
-def getFilesAndDirs(tree):
-    files = Set()
-    dirs = Set()
-    out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree])
-    for l in out.split('\0'):
-        m = getFilesRE.match(l)
-        if m:
-            if m.group(2) == 'tree':
-                dirs.add(m.group(4))
-            elif m.group(2) == 'blob':
-                files.add(m.group(4))
-
-    return [files, dirs]
-
-# Those two global variables are used in a number of places but only
-# written to in 'mergeTrees' and 'uniquePath'. They keep track of
-# every file and directory in the two branches that are about to be
-# merged.
-currentFileSet = None
-currentDirectorySet = None
-
-def mergeTrees(head, merge, common, branch1Name, branch2Name):
-    '''Merge the trees 'head' and 'merge' with the common ancestor
-    'common'. The name of the head branch is 'branch1Name' and the name of
-    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
-    where tree is the resulting tree and cleanMerge is True iff the
-    merge was clean.'''
-    
-    assert(isSha(head) and isSha(merge) and isSha(common))
-
-    if common == merge:
-        output('Already uptodate!')
-        return [head, True]
-
-    if cacheOnly:
-        updateArg = '-i'
-    else:
-        updateArg = '-u'
-
-    [out, code] = runProgram(['git-read-tree', updateArg, '-m',
-                                common, head, merge], returnCode = True)
-    if code != 0:
-        die('git-read-tree:', out)
-
-    [tree, code] = runProgram('git-write-tree', returnCode=True)
-    tree = tree.rstrip()
-    if code != 0:
-        global currentFileSet, currentDirectorySet
-        [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
-        [filesM, dirsM] = getFilesAndDirs(merge)
-        currentFileSet.union_update(filesM)
-        currentDirectorySet.union_update(dirsM)
-
-        entries = unmergedCacheEntries()
-        renamesHead =  getRenames(head, common, head, merge, entries)
-        renamesMerge = getRenames(merge, common, head, merge, entries)
-
-        cleanMerge = processRenames(renamesHead, renamesMerge,
-                                    branch1Name, branch2Name)
-        for entry in entries:
-            if entry.processed:
-                continue
-            if not processEntry(entry, branch1Name, branch2Name):
-                cleanMerge = False
-                
-        if cleanMerge or cacheOnly:
-            tree = runProgram('git-write-tree').rstrip()
-        else:
-            tree = None
-    else:
-        cleanMerge = True
-
-    return [tree, cleanMerge]
-
-# Low level file merging, update and removal
-# ------------------------------------------
-
-def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
-              branch1Name, branch2Name):
-
-    merge = False
-    clean = True
-
-    if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
-        clean = False
-        if stat.S_ISREG(aMode):
-            mode = aMode
-            sha = aSha
-        else:
-            mode = bMode
-            sha = bSha
-    else:
-        if aSha != oSha and bSha != oSha:
-            merge = True
-
-        if aMode == oMode:
-            mode = bMode
-        else:
-            mode = aMode
-
-        if aSha == oSha:
-            sha = bSha
-        elif bSha == oSha:
-            sha = aSha
-        elif stat.S_ISREG(aMode):
-            assert(stat.S_ISREG(bMode))
-
-            orig = runProgram(['git-unpack-file', oSha]).rstrip()
-            src1 = runProgram(['git-unpack-file', aSha]).rstrip()
-            src2 = runProgram(['git-unpack-file', bSha]).rstrip()
-            try:
-                [out, code] = runProgram(['merge',
-                                          '-L', branch1Name + '/' + aPath,
-                                          '-L', 'orig/' + oPath,
-                                          '-L', branch2Name + '/' + bPath,
-                                          src1, orig, src2], returnCode=True)
-            except ProgramError, e:
-                print >>sys.stderr, e
-                die("Failed to execute 'merge'. merge(1) is used as the "
-                    "file-level merge tool. Is 'merge' in your path?")
-
-            sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
-                              src1]).rstrip()
-
-            os.unlink(orig)
-            os.unlink(src1)
-            os.unlink(src2)
-
-            clean = (code == 0)
-        else:
-            assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
-            sha = aSha
-
-            if aSha != bSha:
-                clean = False
-
-    return [sha, mode, clean, merge]
-
-def updateFile(clean, sha, mode, path):
-    updateCache = cacheOnly or clean
-    updateWd = not cacheOnly
-
-    return updateFileExt(sha, mode, path, updateCache, updateWd)
-
-def updateFileExt(sha, mode, path, updateCache, updateWd):
-    if cacheOnly:
-        updateWd = False
-
-    if updateWd:
-        pathComponents = path.split('/')
-        for x in xrange(1, len(pathComponents)):
-            p = '/'.join(pathComponents[0:x])
-
-            try:
-                createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
-            except OSError:
-                createDir = True
-            
-            if createDir:
-                try:
-                    os.mkdir(p)
-                except OSError, e:
-                    die("Couldn't create directory", p, e.strerror)
-
-        prog = ['git-cat-file', 'blob', sha]
-        if stat.S_ISREG(mode):
-            try:
-                os.unlink(path)
-            except OSError:
-                pass
-            if mode & 0100:
-                mode = 0777
-            else:
-                mode = 0666
-            fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
-            proc = subprocess.Popen(prog, stdout=fd)
-            proc.wait()
-            os.close(fd)
-        elif stat.S_ISLNK(mode):
-            linkTarget = runProgram(prog)
-            os.symlink(linkTarget, path)
-        else:
-            assert(False)
-
-    if updateWd and updateCache:
-        runProgram(['git-update-index', '--add', '--', path])
-    elif updateCache:
-        runProgram(['git-update-index', '--add', '--cacheinfo',
-                    '0%o' % mode, sha, path])
-
-def setIndexStages(path,
-                   oSHA1, oMode,
-                   aSHA1, aMode,
-                   bSHA1, bMode,
-                   clear=True):
-    istring = []
-    if clear:
-        istring.append("0 " + ("0" * 40) + "\t" + path + "\0")
-    if oMode:
-        istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path))
-    if aMode:
-        istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path))
-    if bMode:
-        istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path))
-
-    runProgram(['git-update-index', '-z', '--index-info'],
-               input="".join(istring))
-
-def removeFile(clean, path):
-    updateCache = cacheOnly or clean
-    updateWd = not cacheOnly
-
-    if updateCache:
-        runProgram(['git-update-index', '--force-remove', '--', path])
-
-    if updateWd:
-        try:
-            os.unlink(path)
-        except OSError, e:
-            if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
-                raise
-        try:
-            os.removedirs(os.path.dirname(path))
-        except OSError:
-            pass
-
-def uniquePath(path, branch):
-    def fileExists(path):
-        try:
-            os.lstat(path)
-            return True
-        except OSError, e:
-            if e.errno == errno.ENOENT:
-                return False
-            else:
-                raise
-
-    branch = branch.replace('/', '_')
-    newPath = path + '~' + branch
-    suffix = 0
-    while newPath in currentFileSet or \
-          newPath in currentDirectorySet  or \
-          fileExists(newPath):
-        suffix += 1
-        newPath = path + '~' + branch + '_' + str(suffix)
-    currentFileSet.add(newPath)
-    return newPath
-
-# Cache entry management
-# ----------------------
-
-class CacheEntry:
-    def __init__(self, path):
-        class Stage:
-            def __init__(self):
-                self.sha1 = None
-                self.mode = None
-
-            # Used for debugging only
-            def __str__(self):
-                if self.mode != None:
-                    m = '0%o' % self.mode
-                else:
-                    m = 'None'
-
-                if self.sha1:
-                    sha1 = self.sha1
-                else:
-                    sha1 = 'None'
-                return 'sha1: ' + sha1 + ' mode: ' + m
-        
-        self.stages = [Stage(), Stage(), Stage(), Stage()]
-        self.path = path
-        self.processed = False
-
-    def __str__(self):
-        return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
-
-class CacheEntryContainer:
-    def __init__(self):
-        self.entries = {}
-
-    def add(self, entry):
-        self.entries[entry.path] = entry
-
-    def get(self, path):
-        return self.entries.get(path)
-
-    def __iter__(self):
-        return self.entries.itervalues()
-    
-unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
-def unmergedCacheEntries():
-    '''Create a dictionary mapping file names to CacheEntry
-    objects. The dictionary contains one entry for every path with a
-    non-zero stage entry.'''
-
-    lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
-    lines.pop()
-
-    res = CacheEntryContainer()
-    for l in lines:
-        m = unmergedRE.match(l)
-        if m:
-            mode = int(m.group(1), 8)
-            sha1 = m.group(2)
-            stage = int(m.group(3))
-            path = m.group(4)
-
-            e = res.get(path)
-            if not e:
-                e = CacheEntry(path)
-                res.add(e)
-
-            e.stages[stage].mode = mode
-            e.stages[stage].sha1 = sha1
-        else:
-            die('Error: Merge program failed: Unexpected output from',
-                'git-ls-files:', l)
-    return res
-
-lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
-def getCacheEntry(path, origTree, aTree, bTree):
-    '''Returns a CacheEntry object which doesn't have to correspond to
-    a real cache entry in Git's index.'''
-    
-    def parse(out):
-        if out == '':
-            return [None, None]
-        else:
-            m = lsTreeRE.match(out)
-            if not m:
-                die('Unexpected output from git-ls-tree:', out)
-            elif m.group(2) == 'blob':
-                return [m.group(3), int(m.group(1), 8)]
-            else:
-                return [None, None]
-
-    res = CacheEntry(path)
-
-    [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
-    [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
-    [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
-
-    res.stages[1].sha1 = oSha
-    res.stages[1].mode = oMode
-    res.stages[2].sha1 = aSha
-    res.stages[2].mode = aMode
-    res.stages[3].sha1 = bSha
-    res.stages[3].mode = bMode
-
-    return res
-
-# Rename detection and handling
-# -----------------------------
-
-class RenameEntry:
-    def __init__(self,
-                 src, srcSha, srcMode, srcCacheEntry,
-                 dst, dstSha, dstMode, dstCacheEntry,
-                 score):
-        self.srcName = src
-        self.srcSha = srcSha
-        self.srcMode = srcMode
-        self.srcCacheEntry = srcCacheEntry
-        self.dstName = dst
-        self.dstSha = dstSha
-        self.dstMode = dstMode
-        self.dstCacheEntry = dstCacheEntry
-        self.score = score
-
-        self.processed = False
-
-class RenameEntryContainer:
-    def __init__(self):
-        self.entriesSrc = {}
-        self.entriesDst = {}
-
-    def add(self, entry):
-        self.entriesSrc[entry.srcName] = entry
-        self.entriesDst[entry.dstName] = entry
-
-    def getSrc(self, path):
-        return self.entriesSrc.get(path)
-
-    def getDst(self, path):
-        return self.entriesDst.get(path)
-
-    def __iter__(self):
-        return self.entriesSrc.itervalues()
-
-parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
-def getRenames(tree, oTree, aTree, bTree, cacheEntries):
-    '''Get information of all renames which occured between 'oTree' and
-    'tree'. We need the three trees in the merge ('oTree', 'aTree' and
-    'bTree') to be able to associate the correct cache entries with
-    the rename information. 'tree' is always equal to either aTree or bTree.'''
-
-    assert(tree == aTree or tree == bTree)
-    inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
-                      '-z', oTree, tree])
-
-    ret = RenameEntryContainer()
-    try:
-        recs = inp.split("\0")
-        recs.pop() # remove last entry (which is '')
-        it = recs.__iter__()
-        while True:
-            rec = it.next()
-            m = parseDiffRenamesRE.match(rec)
-
-            if not m:
-                die('Unexpected output from git-diff-tree:', rec)
-
-            srcMode = int(m.group(1), 8)
-            dstMode = int(m.group(2), 8)
-            srcSha = m.group(3)
-            dstSha = m.group(4)
-            score = m.group(5)
-            src = it.next()
-            dst = it.next()
-
-            srcCacheEntry = cacheEntries.get(src)
-            if not srcCacheEntry:
-                srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
-                cacheEntries.add(srcCacheEntry)
-
-            dstCacheEntry = cacheEntries.get(dst)
-            if not dstCacheEntry:
-                dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
-                cacheEntries.add(dstCacheEntry)
-
-            ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
-                                dst, dstSha, dstMode, dstCacheEntry,
-                                score))
-    except StopIteration:
-        pass
-    return ret
-
-def fmtRename(src, dst):
-    srcPath = src.split('/')
-    dstPath = dst.split('/')
-    path = []
-    endIndex = min(len(srcPath), len(dstPath)) - 1
-    for x in range(0, endIndex):
-        if srcPath[x] == dstPath[x]:
-            path.append(srcPath[x])
-        else:
-            endIndex = x
-            break
-
-    if len(path) > 0:
-        return '/'.join(path) + \
-               '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
-               '/'.join(dstPath[endIndex:]) + '}'
-    else:
-        return src + ' => ' + dst
-
-def processRenames(renamesA, renamesB, branchNameA, branchNameB):
-    srcNames = Set()
-    for x in renamesA:
-        srcNames.add(x.srcName)
-    for x in renamesB:
-        srcNames.add(x.srcName)
-
-    cleanMerge = True
-    for path in srcNames:
-        if renamesA.getSrc(path):
-            renames1 = renamesA
-            renames2 = renamesB
-            branchName1 = branchNameA
-            branchName2 = branchNameB
-        else:
-            renames1 = renamesB
-            renames2 = renamesA
-            branchName1 = branchNameB
-            branchName2 = branchNameA
-        
-        ren1 = renames1.getSrc(path)
-        ren2 = renames2.getSrc(path)
-
-        ren1.dstCacheEntry.processed = True
-        ren1.srcCacheEntry.processed = True
-
-        if ren1.processed:
-            continue
-
-        ren1.processed = True
-
-        if ren2:
-            # Renamed in 1 and renamed in 2
-            assert(ren1.srcName == ren2.srcName)
-            ren2.dstCacheEntry.processed = True
-            ren2.processed = True
-
-            if ren1.dstName != ren2.dstName:
-                output('CONFLICT (rename/rename): Rename',
-                       fmtRename(path, ren1.dstName), 'in branch', branchName1,
-                       'rename', fmtRename(path, ren2.dstName), 'in',
-                       branchName2)
-                cleanMerge = False
-
-                if ren1.dstName in currentDirectorySet:
-                    dstName1 = uniquePath(ren1.dstName, branchName1)
-                    output(ren1.dstName, 'is a directory in', branchName2,
-                           'adding as', dstName1, 'instead.')
-                    removeFile(False, ren1.dstName)
-                else:
-                    dstName1 = ren1.dstName
-
-                if ren2.dstName in currentDirectorySet:
-                    dstName2 = uniquePath(ren2.dstName, branchName2)
-                    output(ren2.dstName, 'is a directory in', branchName1,
-                           'adding as', dstName2, 'instead.')
-                    removeFile(False, ren2.dstName)
-                else:
-                    dstName2 = ren2.dstName
-                setIndexStages(dstName1,
-                               None, None,
-                               ren1.dstSha, ren1.dstMode,
-                              None, None)
-                setIndexStages(dstName2,
-                               None, None,
-                               None, None,
-                               ren2.dstSha, ren2.dstMode)
-
-            else:
-                removeFile(True, ren1.srcName)
-
-                [resSha, resMode, clean, merge] = \
-                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
-                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
-                                   ren2.dstName, ren2.dstSha, ren2.dstMode,
-                                   branchName1, branchName2)
-
-                if merge or not clean:
-                    output('Renaming', fmtRename(path, ren1.dstName))
-
-                if merge:
-                    output('Auto-merging', ren1.dstName)
-
-                if not clean:
-                    output('CONFLICT (content): merge conflict in',
-                           ren1.dstName)
-                    cleanMerge = False
-
-                    if not cacheOnly:
-                        setIndexStages(ren1.dstName,
-                                       ren1.srcSha, ren1.srcMode,
-                                       ren1.dstSha, ren1.dstMode,
-                                       ren2.dstSha, ren2.dstMode)
-
-                updateFile(clean, resSha, resMode, ren1.dstName)
-        else:
-            removeFile(True, ren1.srcName)
-
-            # Renamed in 1, maybe changed in 2
-            if renamesA == renames1:
-                stage = 3
-            else:
-                stage = 2
-                
-            srcShaOtherBranch  = ren1.srcCacheEntry.stages[stage].sha1
-            srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
-
-            dstShaOtherBranch  = ren1.dstCacheEntry.stages[stage].sha1
-            dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
-
-            tryMerge = False
-            
-            if ren1.dstName in currentDirectorySet:
-                newPath = uniquePath(ren1.dstName, branchName1)
-                output('CONFLICT (rename/directory): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
-                       'directory', ren1.dstName, 'added in', branchName2)
-                output('Renaming', ren1.srcName, 'to', newPath, 'instead')
-                cleanMerge = False
-                removeFile(False, ren1.dstName)
-                updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
-            elif srcShaOtherBranch == None:
-                output('CONFLICT (rename/delete): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1, 'and deleted in', branchName2)
-                cleanMerge = False
-                updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
-            elif dstShaOtherBranch:
-                newPath = uniquePath(ren1.dstName, branchName2)
-                output('CONFLICT (rename/add): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1 + '.', ren1.dstName, 'added in', branchName2)
-                output('Adding as', newPath, 'instead')
-                updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
-                cleanMerge = False
-                tryMerge = True
-            elif renames2.getDst(ren1.dstName):
-                dst2 = renames2.getDst(ren1.dstName)
-                newPath1 = uniquePath(ren1.dstName, branchName1)
-                newPath2 = uniquePath(dst2.dstName, branchName2)
-                output('CONFLICT (rename/rename): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1+'. Rename',
-                       fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
-                output('Renaming', ren1.srcName, 'to', newPath1, 'and',
-                       dst2.srcName, 'to', newPath2, 'instead')
-                removeFile(False, ren1.dstName)
-                updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
-                updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
-                dst2.processed = True
-                cleanMerge = False
-            else:
-                tryMerge = True
-
-            if tryMerge:
-
-                oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode
-                aName, bName = ren1.dstName, ren1.srcName
-                aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch
-                aMode, bMode = ren1.dstMode, srcModeOtherBranch
-                aBranch, bBranch = branchName1, branchName2
-
-                if renamesA != renames1:
-                    aName, bName = bName, aName
-                    aSHA1, bSHA1 = bSHA1, aSHA1
-                    aMode, bMode = bMode, aMode
-                    aBranch, bBranch = bBranch, aBranch
-
-                [resSha, resMode, clean, merge] = \
-                         mergeFile(oName, oSHA1, oMode,
-                                   aName, aSHA1, aMode,
-                                   bName, bSHA1, bMode,
-                                   aBranch, bBranch);
-
-                if merge or not clean:
-                    output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
-
-                if merge:
-                    output('Auto-merging', ren1.dstName)
-
-                if not clean:
-                    output('CONFLICT (rename/modify): Merge conflict in',
-                           ren1.dstName)
-                    cleanMerge = False
-
-                    if not cacheOnly:
-                        setIndexStages(ren1.dstName,
-                                       oSHA1, oMode,
-                                       aSHA1, aMode,
-                                       bSHA1, bMode)
-
-                updateFile(clean, resSha, resMode, ren1.dstName)
-
-    return cleanMerge
-
-# Per entry merge function
-# ------------------------
-
-def processEntry(entry, branch1Name, branch2Name):
-    '''Merge one cache entry.'''
-
-    debug('processing', entry.path, 'clean cache:', cacheOnly)
-
-    cleanMerge = True
-
-    path = entry.path
-    oSha = entry.stages[1].sha1
-    oMode = entry.stages[1].mode
-    aSha = entry.stages[2].sha1
-    aMode = entry.stages[2].mode
-    bSha = entry.stages[3].sha1
-    bMode = entry.stages[3].mode
-
-    assert(oSha == None or isSha(oSha))
-    assert(aSha == None or isSha(aSha))
-    assert(bSha == None or isSha(bSha))
-
-    assert(oMode == None or type(oMode) is int)
-    assert(aMode == None or type(aMode) is int)
-    assert(bMode == None or type(bMode) is int)
-
-    if (oSha and (not aSha or not bSha)):
-    #
-    # Case A: Deleted in one
-    #
-        if (not aSha     and not bSha) or \
-           (aSha == oSha and not bSha) or \
-           (not aSha     and bSha == oSha):
-    # Deleted in both or deleted in one and unchanged in the other
-            if aSha:
-                output('Removing', path)
-            removeFile(True, path)
-        else:
-    # Deleted in one and changed in the other
-            cleanMerge = False
-            if not aSha:
-                output('CONFLICT (delete/modify):', path, 'deleted in',
-                       branch1Name, 'and modified in', branch2Name + '.',
-                       'Version', branch2Name, 'of', path, 'left in tree.')
-                mode = bMode
-                sha = bSha
-            else:
-                output('CONFLICT (modify/delete):', path, 'deleted in',
-                       branch2Name, 'and modified in', branch1Name + '.',
-                       'Version', branch1Name, 'of', path, 'left in tree.')
-                mode = aMode
-                sha = aSha
-
-            updateFile(False, sha, mode, path)
-
-    elif (not oSha and aSha     and not bSha) or \
-         (not oSha and not aSha and bSha):
-    #
-    # Case B: Added in one.
-    #
-        if aSha:
-            addBranch = branch1Name
-            otherBranch = branch2Name
-            mode = aMode
-            sha = aSha
-            conf = 'file/directory'
-        else:
-            addBranch = branch2Name
-            otherBranch = branch1Name
-            mode = bMode
-            sha = bSha
-            conf = 'directory/file'
-    
-        if path in currentDirectorySet:
-            cleanMerge = False
-            newPath = uniquePath(path, addBranch)
-            output('CONFLICT (' + conf + '):',
-                   'There is a directory with name', path, 'in',
-                   otherBranch + '. Adding', path, 'as', newPath)
-
-            removeFile(False, path)
-            updateFile(False, sha, mode, newPath)
-        else:
-            output('Adding', path)
-            updateFile(True, sha, mode, path)
-    
-    elif not oSha and aSha and bSha:
-    #
-    # Case C: Added in both (check for same permissions).
-    #
-        if aSha == bSha:
-            if aMode != bMode:
-                cleanMerge = False
-                output('CONFLICT: File', path,
-                       'added identically in both branches, but permissions',
-                       'conflict', '0%o' % aMode, '->', '0%o' % bMode)
-                output('CONFLICT: adding with permission:', '0%o' % aMode)
-
-                updateFile(False, aSha, aMode, path)
-            else:
-                # This case is handled by git-read-tree
-                assert(False)
-        else:
-            cleanMerge = False
-            newPath1 = uniquePath(path, branch1Name)
-            newPath2 = uniquePath(path, branch2Name)
-            output('CONFLICT (add/add): File', path,
-                   'added non-identically in both branches. Adding as',
-                   newPath1, 'and', newPath2, 'instead.')
-            removeFile(False, path)
-            updateFile(False, aSha, aMode, newPath1)
-            updateFile(False, bSha, bMode, newPath2)
-
-    elif oSha and aSha and bSha:
-    #
-    # case D: Modified in both, but differently.
-    #
-        output('Auto-merging', path)
-        [sha, mode, clean, dummy] = \
-              mergeFile(path, oSha, oMode,
-                        path, aSha, aMode,
-                        path, bSha, bMode,
-                        branch1Name, branch2Name)
-        if clean:
-            updateFile(True, sha, mode, path)
-        else:
-            cleanMerge = False
-            output('CONFLICT (content): Merge conflict in', path)
-
-            if cacheOnly:
-                updateFile(False, sha, mode, path)
-            else:
-                updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
-    else:
-        die("ERROR: Fatal merge failure, shouldn't happen.")
-
-    return cleanMerge
-
-def usage():
-    die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
-
-# main entry point as merge strategy module
-# The first parameters up to -- are merge bases, and the rest are heads.
-
-if len(sys.argv) < 4:
-    usage()
-
-bases = []
-for nextArg in xrange(1, len(sys.argv)):
-    if sys.argv[nextArg] == '--':
-        if len(sys.argv) != nextArg + 3:
-            die('Not handling anything other than two heads merge.')
-        try:
-            h1 = firstBranch = sys.argv[nextArg + 1]
-            h2 = secondBranch = sys.argv[nextArg + 2]
-        except IndexError:
-            usage()
-        break
-    else:
-        bases.append(sys.argv[nextArg])
-
-print 'Merging', h1, 'with', h2
-
-try:
-    h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
-    h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
-
-    if len(bases) == 1:
-        base = runProgram(['git-rev-parse', '--verify',
-                           bases[0] + '^0']).rstrip()
-        ancestor = Commit(base, None)
-        [dummy, clean] = merge(Commit(h1, None), Commit(h2, None),
-                               firstBranch, secondBranch, None, 0,
-                               ancestor)
-    else:
-        graph = buildGraph([h1, h2])
-        [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
-                               firstBranch, secondBranch, graph)
-
-    print ''
-except:
-    if isinstance(sys.exc_info()[1], SystemExit):
-        raise
-    else:
-        traceback.print_exc(None, sys.stderr)
-        sys.exit(2)
-
-if clean:
-    sys.exit(0)
-else:
-    sys.exit(1)
index cb094388bb5c830d37a790f5e7bbf13e8c61352a..4ebfcf65d99f7743048df901b38969c28cc11edb 100755 (executable)
@@ -3,22 +3,20 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <remote>+'
+USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [--reflog-action=<action>] [-m=<merge-message>] <commit>+'
+
 . git-sh-setup
 
 LF='
 '
 
-all_strategies='recur recursive recursive-old octopus resolve stupid ours'
+all_strategies='recur recursive octopus resolve stupid ours'
 default_twohead_strategies='recursive'
 default_octopus_strategies='octopus'
 no_trivial_merge_strategies='ours'
 use_strategies=
 
 index_merge=t
-if test "@@NO_PYTHON@@"; then
-       all_strategies='recur recursive resolve octopus stupid ours'
-fi
 
 dropsave() {
        rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
@@ -93,9 +91,25 @@ finish () {
        esac
 }
 
+merge_name () {
+       remote="$1"
+       rh=$(git-rev-parse --verify "$remote^0" 2>/dev/null) || return
+       bh=$(git-show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
+       if test "$rh" = "$bh"
+       then
+               echo "$rh               branch '$remote' of ."
+       elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
+               git-show-ref -q --verify "refs/heads/$truname" 2>/dev/null
+       then
+               echo "$rh               branch '$truname' (early part) of ."
+       else
+               echo "$rh               commit '$remote'"
+       fi
+}
+
 case "$#" in 0) usage ;; esac
 
-rloga=
+rloga= have_message=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -128,17 +142,74 @@ do
        --reflog-action=*)
                rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
                ;;
+       -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+               merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+               have_message=t
+               ;;
+       -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+               shift
+               case "$#" in
+               1)      usage ;;
+               esac
+               merge_msg="$1"
+               have_message=t
+               ;;
        -*)     usage ;;
        *)      break ;;
        esac
        shift
 done
 
-merge_msg="$1"
-shift
-head_arg="$1"
-head=$(git-rev-parse --verify "$1"^0) || usage
-shift
+# This could be traditional "merge <msg> HEAD <commit>..."  and the
+# way we can tell it is to see if the second token is HEAD, but some
+# people might have misused the interface and used a committish that
+# is the same as HEAD there instead.  Traditional format never would
+# have "-m" so it is an additional safety measure to check for it.
+
+if test -z "$have_message" &&
+       second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) &&
+       head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) &&
+       test "$second_token" = "$head_commit"
+then
+       merge_msg="$1"
+       shift
+       head_arg="$1"
+       shift
+elif ! git-rev-parse --verify HEAD >/dev/null 2>&1
+then
+       # If the merged head is a valid one there is no reason to
+       # forbid "git merge" into a branch yet to be born.  We do
+       # the same for "git pull".
+       if test 1 -ne $#
+       then
+               echo >&2 "Can merge only exactly one commit into empty head"
+               exit 1
+       fi
+
+       rh=$(git rev-parse --verify "$1^0") ||
+               die "$1 - not something we can merge"
+
+       git-update-ref -m "initial pull" HEAD "$rh" "" &&
+       git-read-tree --reset -u HEAD
+       exit
+
+else
+       # We are invoked directly as the first-class UI.
+       head_arg=HEAD
+
+       # All the rest are the commits being merged; prepare
+       # the standard merge summary message to be appended to
+       # the given message.  If remote is invalid we will die
+       # later in the common codepath so we discard the error
+       # in this loop.
+       merge_name=$(for remote
+               do
+                       merge_name "$remote"
+               done | git-fmt-merge-msg
+       )
+       merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
+fi
+head=$(git-rev-parse --verify "$head_arg"^0) || usage
 
 # All the rest are remote heads
 test "$#" = 0 && usage ;# we need at least one remote head.
@@ -147,7 +218,7 @@ test "$rloga" = '' && rloga="merge: $@"
 remoteheads=
 for remote
 do
-       remotehead=$(git-rev-parse --verify "$remote"^0) ||
+       remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
            die "$remote - not something we can merge"
        remoteheads="${remoteheads}$remotehead "
 done
@@ -202,7 +273,7 @@ f,*)
        echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)"
        git-update-index --refresh 2>/dev/null
        new_head=$(git-rev-parse --verify "$1^0") &&
-       git-read-tree -u -v -m $head "$new_head" &&
+       git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
        finish "$new_head" "Fast forward"
        dropsave
        exit 0
@@ -337,7 +408,14 @@ fi
 case "$best_strategy" in
 '')
        restorestate
-       echo >&2 "No merge strategy handled the merge."
+       case "$use_strategies" in
+       ?*' '?*)
+               echo >&2 "No merge strategy handled the merge."
+               ;;
+       *)
+               echo >&2 "Merge with strategy $use_strategies failed."
+               ;;
+       esac
        exit 2
        ;;
 "$wt_strategy")
index c325ef761e4c558ab5c7c560da942e127e1be040..871c08f0973826f96490835dd029b47c64768a53 100755 (executable)
@@ -90,6 +90,41 @@ get_remote_default_refs_for_push () {
        esac
 }
 
+# Called from canon_refs_list_for_fetch -d "$remote", which
+# is called from get_remote_default_refs_for_fetch to grok
+# refspecs that are retrieved from the configuration, but not
+# from get_remote_refs_for_fetch when it deals with refspecs
+# supplied on the command line.  $ls_remote_result has the list
+# of refs available at remote.
+expand_refs_wildcard () {
+       for ref
+       do
+               lref=${ref#'+'}
+               # a non glob pattern is given back as-is.
+               expr "z$lref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || {
+                       echo "$ref"
+                       continue
+               }
+
+               from=`expr "z$lref" : 'z\(refs/.*/\)\*:refs/.*/\*$'`
+               to=`expr "z$lref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'`
+               local_force=
+               test "z$lref" = "z$ref" || local_force='+'
+               echo "$ls_remote_result" |
+               sed -e '/\^{}$/d' |
+               (
+                       IFS='   '
+                       while read sha1 name
+                       do
+                               # ignore the ones that do not start with $from
+                               mapped=${name#"$from"}
+                               test "z$name" = "z$mapped" && continue
+                               echo "${local_force}${name}:${to}${mapped}"
+                       done
+               )
+       done
+}
+
 # Subroutine to canonicalize remote:local notation.
 canon_refs_list_for_fetch () {
        # If called from get_remote_default_refs_for_fetch
@@ -97,6 +132,7 @@ canon_refs_list_for_fetch () {
        # or the first one otherwise; add prefix . to the rest
        # to prevent the secondary branches to be merged by default.
        merge_branches=
+       curr_branch=
        if test "$1" = "-d"
        then
                shift ; remote="$1" ; shift
@@ -107,6 +143,8 @@ canon_refs_list_for_fetch () {
                        merge_branches=$(git-repo-config \
                            --get-all "branch.${curr_branch}.merge")
                fi
+               set x $(expand_refs_wildcard "$@")
+               shift
        fi
        for ref
        do
index e23beb685d15447ac114c58d1795ed15bef88c9d..1703091bbb988e22f39b2c8d4c70ec7340a2f109 100755 (executable)
@@ -76,6 +76,10 @@ merge_head=$(sed -e '/       not-for-merge   /d' \
 
 case "$merge_head" in
 '')
+       curr_branch=$(git-symbolic-ref HEAD | \
+               sed -e 's|^refs/heads/||')
+       echo >&2 "Warning: No merge candidate found because value of config option
+         \"branch.${curr_branch}.merge\" does not match any remote branch fetched."
        echo >&2 "No changes."
        exit 0
        ;;
index 546fa446fc3c6c63488b9ef38cb5bdc960953cb1..2b4f3477fa941afe4f6450f74f6f8cf43a7567c0 100755 (executable)
@@ -139,6 +139,10 @@ do
        --skip)
                if test -d "$dotest"
                then
+                       if test -d "$GIT_DIR/rr-cache"
+                       then
+                               git-rerere clear
+                       fi
                        prev_head="`cat $dotest/prev_head`"
                        end="`cat $dotest/end`"
                        msgnum="`cat $dotest/msgnum`"
@@ -157,6 +161,10 @@ do
                exit
                ;;
        --abort)
+               if test -d "$GIT_DIR/rr-cache"
+               then
+                       git-rerere clear
+               fi
                if test -d "$dotest"
                then
                        rm -r "$dotest"
@@ -302,15 +310,6 @@ then
        exit $?
 fi
 
-if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old"
-then
-       die 'The recursive-old merge strategy is written in Python,
-which this installation of git was not configured with.  Please consider
-a different merge strategy (e.g. recursive, resolve, or stupid)
-or install Python and git with Python support.'
-
-fi
-
 # start doing a rebase with git-merge
 # this is rename-aware if the recursive (default) strategy is used
 
index f150a558ca7a7a659f4eb88be3a275e1a4e87337..067898f1207736732c2c80eabbef5537f8dfef69 100755 (executable)
@@ -67,6 +67,8 @@ name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") ||
 if [ -z "$name" ]; then
        echo Nothing new to pack.
 else
+       chmod a-w "$PACKTMP-$name.pack"
+       chmod a-w "$PACKTMP-$name.idx"
        if test "$quiet" != '-q'; then
            echo "Pack pack-$name created."
        fi
index 4319e35c628efdd86745c560d2dfe306b180fb1b..4eacc3a059ca3d90305cd1f28b91460b56e07167 100755 (executable)
@@ -30,4 +30,4 @@ echo "  $url"
 echo
 
 git log  $baserev..$headrev | git-shortlog ;
-git diff --stat --summary $baserev..$headrev
+git diff -M --stat --summary $baserev..$headrev
index d3664ff491fe73d8a6acf6e2c4da5806c5753ce1..4f692091e73bf633cf986ba2c9bed38bc2c78538 100755 (executable)
@@ -154,7 +154,7 @@ sub find_conflict {
 sub merge {
        my ($name, $path) = @_;
        record_preimage($path, "$rr_dir/$name/thisimage");
-       unless (system('merge', map { "$rr_dir/$name/${_}image" }
+       unless (system('git', 'merge-file', map { "$rr_dir/$name/${_}image" }
                       qw(this pre post))) {
                my $in;
                open $in, "<$rr_dir/$name/thisimage" or
@@ -169,9 +169,66 @@ sub merge {
        return 0;
 }
 
+sub garbage_collect_rerere {
+       # We should allow specifying these from the command line and
+       # that is why the caller gives @ARGV to us, but I am lazy.
+
+       my $cutoff_noresolve = 15; # two weeks
+       my $cutoff_resolve = 60; # two months
+       my @to_remove;
+       while (<$rr_dir/*/preimage>) {
+               my ($dir) = /^(.*)\/preimage$/;
+               my $cutoff = ((-f "$dir/postimage")
+                             ? $cutoff_resolve
+                             : $cutoff_noresolve);
+               my $age = -M "$_";
+               if ($cutoff <= $age) {
+                       push @to_remove, $dir;
+               }
+       }
+       if (@to_remove) {
+               rmtree(\@to_remove);
+       }
+}
+
 -d "$rr_dir" || exit(0);
 
 read_rr();
+
+if (@ARGV) {
+       my $arg = shift @ARGV;
+       if ($arg eq 'clear') {
+               for my $path (keys %merge_rr) {
+                       my $name = $merge_rr{$path};
+                       if (-d "$rr_dir/$name" &&
+                           ! -f "$rr_dir/$name/postimage") {
+                               rmtree(["$rr_dir/$name"]);
+                       }
+               }
+               unlink $merge_rr;
+       }
+       elsif ($arg eq 'status') {
+               for my $path (keys %merge_rr) {
+                       print $path, "\n";
+               }
+       }
+       elsif ($arg eq 'diff') {
+               for my $path (keys %merge_rr) {
+                       my $name = $merge_rr{$path};
+                       system('diff', ((@ARGV == 0) ? ('-u') : @ARGV),
+                               '-L', "a/$path", '-L', "b/$path",
+                               "$rr_dir/$name/preimage", $path);
+               }
+       }
+       elsif ($arg eq 'gc') {
+               garbage_collect_rerere(@ARGV);
+       }
+       else {
+               die "$0 unknown command: $arg\n";
+       }
+       exit 0;
+}
+
 my %conflict = map { $_ => 1 } find_conflict();
 
 # MERGE_RR records paths with conflicts immediately after merge
index c0feb4435d15da9cc9928800cbe270018f044152..2379db082f956e68a360cfbd667761490ecad5dd 100755 (executable)
@@ -1,28 +1,60 @@
 #!/bin/sh
-
-USAGE='[--mixed | --soft | --hard]  [<commit-ish>]'
+#
+# Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
+#
+USAGE='[--mixed | --soft | --hard]  [<commit-ish>] [ [--] <paths>...]'
+SUBDIRECTORY_OK=Yes
 . git-sh-setup
 
-update=
-reset_type=--mixed
-case "$1" in
---mixed | --soft | --hard)
-       reset_type="$1"
+update= reset_type=--mixed
+unset rev
+
+while case $# in 0) break ;; esac
+do
+       case "$1" in
+       --mixed | --soft | --hard)
+               reset_type="$1"
+               ;;
+       --)
+               break
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               rev=$(git-rev-parse --verify "$1") || exit
+               shift
+               break
+               ;;
+       esac
        shift
-       ;;
--*)
-        usage ;;
-esac
+done
 
-case $# in
-0) rev=HEAD ;;
-1) rev=$(git-rev-parse --verify "$1") || exit ;;
-*) usage ;;
-esac
+: ${rev=HEAD}
 rev=$(git-rev-parse --verify $rev^0) || exit
 
-# We need to remember the set of paths that _could_ be left
-# behind before a hard reset, so that we can remove them.
+# Skip -- in "git reset HEAD -- foo" and "git reset -- foo".
+case "$1" in --) shift ;; esac
+
+# git reset --mixed tree [--] paths... can be used to
+# load chosen paths from the tree into the index without
+# affecting the working tree nor HEAD.
+if test $# != 0
+then
+       test "$reset_type" == "--mixed" ||
+               die "Cannot do partial $reset_type reset."
+       git ls-tree -r --full-name $rev -- "$@" |
+       git update-index --add --index-info || exit
+       git update-index --refresh
+       exit
+fi
+
+TOP=$(git-rev-parse --show-cdup)
+if test ! -z "$TOP"
+then
+       cd "$TOP"
+fi
+
 if test "$reset_type" = "--hard"
 then
        update=-u
@@ -54,7 +86,12 @@ update_ref_status=$?
 
 case "$reset_type" in
 --hard )
-       ;; # Nothing else to do
+       test $update_ref_status = 0 && {
+               echo -n "HEAD is now at "
+               GIT_PAGER= git log --max-count=1 --pretty=oneline \
+                       --abbrev-commit HEAD
+       }
+       ;;
 --soft )
        ;; # Nothing else to do
 --mixed )
index 6eab3c72df0db0994a352836018a7fc35ba1eae3..50cc47b0634201e7acaaa0a0642be779aaf7c2da 100755 (executable)
@@ -155,7 +155,7 @@ Conflicts:
                uniq
            } >>"$GIT_DIR/MERGE_MSG"
            echo >&2 "Automatic $me failed.  After resolving the conflicts,"
-           echo >&2 "mark the corrected paths with 'git-update-index <paths>'"
+           echo >&2 "mark the corrected paths with 'git-add <paths>'"
            echo >&2 "and commit the result."
            case "$me" in
            cherry-pick)
diff --git a/git-shortlog.perl b/git-shortlog.perl
deleted file mode 100755 (executable)
index 334fec7..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Getopt::Std;
-use File::Basename qw(basename dirname);
-
-our ($opt_h, $opt_n, $opt_s);
-getopts('hns');
-
-$opt_h && usage();
-
-sub usage {
-       print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < <log_data>\n";
-        exit(1);
-}
-
-my (%mailmap);
-my (%email);
-my (%map);
-my $pstate = 1;
-my $n_records = 0;
-my $n_output = 0;
-
-sub shortlog_entry($$) {
-       my ($name, $desc) = @_;
-       my $key = $name;
-
-       $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g;
-       $desc =~ s#\[PATCH\] ##g;
-
-       # store description in array, in email->{desc list} map
-       if (exists $map{$key}) {
-               # grab ref
-               my $obj = $map{$key};
-
-               # add desc to array
-               push(@$obj, $desc);
-       } else {
-               # create new array, containing 1 item
-               my @arr = ($desc);
-
-               # store ref to array
-               $map{$key} = \@arr;
-       }
-}
-
-# sort comparison function
-sub by_name($$) {
-       my ($a, $b) = @_;
-
-       uc($a) cmp uc($b);
-}
-sub by_nbentries($$) {
-       my ($a, $b) = @_;
-       my $a_entries = $map{$a};
-       my $b_entries = $map{$b};
-
-       @$b_entries - @$a_entries || by_name $a, $b;
-}
-
-my $sort_method = $opt_n ? \&by_nbentries : \&by_name;
-
-sub summary_output {
-       my ($obj, $num, $key);
-
-       foreach $key (sort $sort_method keys %map) {
-               $obj = $map{$key};
-               $num = @$obj;
-               printf "%s: %u\n", $key, $num;
-               $n_output += $num;
-       }
-}
-
-sub shortlog_output {
-       my ($obj, $num, $key, $desc);
-
-       foreach $key (sort $sort_method keys %map) {
-               $obj = $map{$key};
-               $num = @$obj;
-
-               # output author
-               printf "%s (%u):\n", $key, $num;
-
-               # output author's 1-line summaries
-               foreach $desc (reverse @$obj) {
-                       print "  $desc\n";
-                       $n_output++;
-               }
-
-               # blank line separating author from next author
-               print "\n";
-       }
-}
-
-sub changelog_input {
-       my ($author, $desc);
-
-       while (<>) {
-               # get author and email
-               if ($pstate == 1) {
-                       my ($email);
-
-                       next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/;
-
-                       $n_records++;
-
-                       $author = $1;
-                       $email = $2;
-                       $desc = undef;
-
-                       # cset author fixups
-                       if (exists $mailmap{$email}) {
-                               $author = $mailmap{$email};
-                       } elsif (exists $mailmap{$author}) {
-                               $author = $mailmap{$author};
-                       } elsif (!$author) {
-                               $author = $email;
-                       }
-                       $email{$author}{$email}++;
-                       $pstate++;
-               }
-
-               # skip to blank line
-               elsif ($pstate == 2) {
-                       next unless /^\s*$/;
-                       $pstate++;
-               }
-
-               # skip to non-blank line
-               elsif ($pstate == 3) {
-                       next unless /^\s*?(.*)/;
-
-                       # skip lines that are obviously not
-                       # a 1-line cset description
-                       next if /^\s*From: /;
-
-                       chomp;
-                       $desc = $1;
-
-                       &shortlog_entry($author, $desc);
-
-                       $pstate = 1;
-               }
-       
-               else {
-                       die "invalid parse state $pstate";
-               }
-       }
-}
-
-sub read_mailmap {
-       my ($fh, $mailmap) = @_;
-       while (<$fh>) {
-               chomp;
-               if (/^([^#].*?)\s*<(.*)>/) {
-                       $mailmap->{$2} = $1;
-               }
-       }
-}
-
-sub setup_mailmap {
-       read_mailmap(\*DATA, \%mailmap);
-       if (-f '.mailmap') {
-               my $fh = undef;
-               open $fh, '<', '.mailmap';
-               read_mailmap($fh, \%mailmap);
-               close $fh;
-       }
-}
-
-sub finalize {
-       #print "\n$n_records records parsed.\n";
-
-       if ($n_records != $n_output) {
-               die "parse error: input records != output records\n";
-       }
-       if (0) {
-               for my $author (sort keys %email) {
-                       my $e = $email{$author};
-                       for my $email (sort keys %$e) {
-                               print STDERR "$author <$email>\n";
-                       }
-               }
-       }
-}
-
-&setup_mailmap;
-&changelog_input;
-$opt_s ? &summary_output : &shortlog_output;
-&finalize;
-exit(0);
-
-
-__DATA__
-#
-# Even with git, we don't always have name translations.
-# So have an email->real name table to translate the
-# (hopefully few) missing names
-#
-Adrian Bunk <bunk@stusta.de>
-Andreas Herrmann <aherrman@de.ibm.com>
-Andrew Morton <akpm@osdl.org>
-Andrew Vasquez <andrew.vasquez@qlogic.com>
-Christoph Hellwig <hch@lst.de>
-Corey Minyard <minyard@acm.org>
-David Woodhouse <dwmw2@shinybook.infradead.org>
-Domen Puncer <domen@coderock.org>
-Douglas Gilbert <dougg@torque.net>
-Ed L Cashin <ecashin@coraid.com>
-Evgeniy Polyakov <johnpol@2ka.mipt.ru>
-Felix Moeller <felix@derklecks.de>
-Frank Zago <fzago@systemfabricworks.com>
-Greg Kroah-Hartman <gregkh@suse.de>
-James Bottomley <jejb@mulgrave.(none)>
-James Bottomley <jejb@titanic.il.steeleye.com>
-Jeff Garzik <jgarzik@pretzel.yyz.us>
-Jens Axboe <axboe@suse.de>
-Kay Sievers <kay.sievers@vrfy.org>
-Mitesh shah <mshah@teja.com>
-Morten Welinder <terra@gnome.org>
-Morten Welinder <welinder@anemone.rentec.com>
-Morten Welinder <welinder@darter.rentec.com>
-Morten Welinder <welinder@troll.com>
-Nguyen Anh Quynh <aquynh@gmail.com>
-Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
-Peter A Jonsson <pj@ludd.ltu.se>
-Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
-Rudolf Marek <R.Marek@sh.cvut.cz>
-Rui Saraiva <rmps@joel.ist.utl.pt>
-Sachin P Sant <ssant@in.ibm.com>
-Santtu Hyrkk\e,Av\e(B <santtu.hyrkko@gmail.com>
-Simon Kelley <simon@thekelleys.org.uk>
-Tejun Heo <htejun@gmail.com>
-Tony Luck <tony.luck@intel.com>
index 599edc3dd5b3d4305ab2c39a0b1ee566669f4074..07748bc3e3f4091613c8e78647a471ac21ee46d2 100755 (executable)
@@ -21,45 +21,46 @@ $ENV{TZ} = 'UTC';
 $ENV{LC_ALL} = 'C';
 $| = 1; # unbuffer STDOUT
 
+# properties that we do not log:
+my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1,
+             'svn:special' => 1,
+             'svn:executable' => 1,
+             'svn:entry:committed-rev' => 1,
+             'svn:entry:last-author' => 1,
+             'svn:entry:uuid' => 1,
+             'svn:entry:committed-date' => 1,
+);
+
 sub fatal (@) { print STDERR @_; exit 1 }
-# 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:
+require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
+require SVN::Ra;
+require SVN::Delta;
+if ($SVN::Core::VERSION lt '1.1.0') {
+       fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n";
+}
+push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
+push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
+*SVN::Git::Fetcher::process_rm = *process_rm;
 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 File::Copy qw/copy/;
 use POSIX qw/strftime/;
 use IPC::Open3;
 use Memoize;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
 memoize('revisions_eq');
 memoize('cmt_metadata');
 memoize('get_commit_time');
 
-my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
-
-sub nag_lib {
-       print STDERR <<EOF;
-! Please consider installing the SVN Perl libraries (version 1.1.0 or
-! newer).  You will generally get better performance and fewer bugs,
-! especially if you:
-! 1) have a case-insensitive filesystem
-! 2) replace symlinks with files (and vice-versa) in commits
-
-EOF
-}
-
-$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
-libsvn_load();
-nag_lib() unless $_use_lib;
+my ($SVN);
 
 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 $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
 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,
@@ -67,9 +68,11 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
        $_template, $_shared, $_no_default_regex, $_no_graft_copy,
        $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
        $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
-       $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive);
+       $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
+       $_username, $_config_dir, $_no_auth_cache, $_xfer_delta,
+       $_pager, $_color);
 my (@_branch_from, %tree_map, %users, %rusers, %equiv);
-my ($_svn_co_url_revs, $_svn_pg_peg_revs);
+my ($_svn_can_do_switch);
 my @repo_path_split_cache;
 
 my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
@@ -80,6 +83,9 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
                'repack:i' => \$_repack,
                'no-metadata' => \$_no_metadata,
                'quiet|q' => \$_q,
+               'username=s' => \$_username,
+               'config-dir=s' => \$_config_dir,
+               'no-auth-cache' => \$_no_auth_cache,
                'ignore-nodate' => \$_ignore_nodate,
                'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
 
@@ -101,7 +107,12 @@ my %cmd = (
        init => [ \&init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
                          \%init_opts ],
-       commit => [ \&commit, "Commit git revisions to SVN",
+       dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
+                       { 'merge|m|M' => \$_merge,
+                         'strategy|s=s' => \$_strategy,
+                         'dry-run|n' => \$_dry_run,
+                       %cmt_opts } ],
+       'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish",
                        {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
                        { 'revision|r=i' => \$_revision } ],
@@ -118,7 +129,12 @@ my %cmd = (
                          'no-graft-copy' => \$_no_graft_copy } ],
        'multi-init' => [ \&multi_init,
                        'Initialize multiple trees (like git-svnimport)',
-                       { %multi_opts, %fc_opts } ],
+                       { %multi_opts, %init_opts,
+                        'revision|r=i' => \$_revision,
+                        'username=s' => \$_username,
+                        'config-dir=s' => \$_config_dir,
+                        'no-auth-cache' => \$_no_auth_cache,
+                       } ],
        'multi-fetch' => [ \&multi_fetch,
                        'Fetch multiple trees (like git-svnimport)',
                        \%fc_opts ],
@@ -131,17 +147,14 @@ my %cmd = (
                          'show-commit' => \$_show_commit,
                          'non-recursive' => \$_non_recursive,
                          'authors-file|A=s' => \$_authors,
+                         'color' => \$_color,
+                         'pager=s' => \$_pager,
                        } ],
        'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
                        { 'message|m=s' => \$_message,
                          'file|F=s' => \$_file,
                          'revision|r=s' => \$_revision,
                        %cmt_opts } ],
-       dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
-                       { 'merge|m|M' => \$_merge,
-                         'strategy|s=s' => \$_strategy,
-                         'dry-run|n' => \$_dry_run,
-                       %cmt_opts } ],
 );
 
 my $cmd;
@@ -168,7 +181,6 @@ 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;
@@ -209,28 +221,27 @@ sub version {
 }
 
 sub rebuild {
-       if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) {
+       if (!verify_ref("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");
+               command_noisy('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 ($rev_list, $ctx) = command_output_pipe("rev-list",
+                                                  "refs/remotes/$GIT_SVN");
        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`);
+               my @commit = grep(/^git-svn-id: /,
+                                 command(qw/cat-file commit/, $c));
                next if (!@commit); # skip merges
                my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
                if (!defined $rev || !$uuid) {
@@ -256,33 +267,7 @@ sub rebuild {
                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
-
-       }
+       command_close_pipe($rev_list, $ctx);
 }
 
 sub init {
@@ -300,10 +285,10 @@ sub init {
 
        $SVN_URL = $url;
        unless (-d $GIT_DIR) {
-               my @init_db = ('git-init-db');
+               my @init_db = ('init-db');
                push @init_db, "--template=$_template" if defined $_template;
                push @init_db, "--shared" if defined $_shared;
-               sys(@init_db);
+               command_noisy(@init_db);
        }
        setup_git_svn();
 }
@@ -311,77 +296,17 @@ sub init {
 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});
+       my $ret = fetch_lib(@_);
+       if ($ret->{commit} && !verify_ref('refs/heads/master^0')) {
+               command_noisy(qw(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);
+       $SVN ||= libsvn_connect($SVN_URL);
        my ($last_rev, $last_commit) = svn_grab_base_rev();
        my ($base, $head) = libsvn_parse_revision($last_rev);
        if ($base > $head) {
@@ -396,16 +321,16 @@ sub fetch_lib {
        read_uuid();
        if (defined $last_commit) {
                unless (-e $GIT_SVN_INDEX) {
-                       sys(qw/git-read-tree/, $last_commit);
+                       command_noisy('read-tree', $last_commit);
                }
-               chomp (my $x = `git-write-tree`);
-               my ($y) = (`git-cat-file commit $last_commit`
+               my $x = command_oneline('write-tree');
+               my ($y) = (command(qw/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);
+                       command_noisy('read-tree', $last_commit);
                }
-               chomp ($x = `git-write-tree`);
+               $x = command_oneline('write-tree');
                if ($y ne $x) {
                        print STDERR "trees ($last_commit) $y != $x\n",
                                 "Something is seriously wrong...\n";
@@ -423,7 +348,7 @@ sub fetch_lib {
                        # 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,
+                       libsvn_get_log(libsvn_dup_ra($SVN), [''],
                                        $min, $max, 0, 1, 1,
                                sub {
                                        my $log_msg;
@@ -449,6 +374,7 @@ sub fetch_lib {
                $min = $max + 1;
                $max += $inc;
                $max = $head if ($max > $head);
+               $SVN = libsvn_connect($SVN_URL);
        }
        restore_index($index);
        return { revision => $last_rev, commit => $last_commit };
@@ -468,45 +394,19 @@ sub commit {
        }
        my @revs;
        foreach my $c (@commits) {
-               chomp(my @tmp = safe_qx('git-rev-parse',$c));
+               my @tmp = command('rev-parse',$c);
                if (scalar @tmp == 1) {
                        push @revs, $tmp[0];
                } elsif (scalar @tmp > 1) {
-                       push @revs, reverse (safe_qx('git-rev-list',@tmp));
+                       push @revs, reverse(command('rev-list',@tmp));
                } else {
                        die "Failed to rev-parse $c\n";
                }
        }
-       chomp @revs;
-       $_use_lib ? commit_lib(@revs) : commit_cmd(@revs);
+       commit_lib(@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();
@@ -525,7 +425,6 @@ sub commit_lib {
        my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
 
        my $repo;
-       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
        set_svn_commit_env();
        foreach my $c (@revs) {
                my $log_msg = get_commit_message($c, $commit_msg);
@@ -534,13 +433,11 @@ sub commit_lib {
                # can't track down... (it's probably in the SVN code)
                defined(my $pid = open my $fh, '-|') or croak $!;
                if (!$pid) {
-                       $SVN_LOG = libsvn_connect($repo);
-                       $SVN = libsvn_connect($repo);
                        my $ed = SVN::Git::Editor->new(
                                        {       r => $r_last,
-                                               ra => $SVN_LOG,
+                                               ra => libsvn_dup_ra($SVN),
                                                c => $c,
-                                               svn_path => $SVN_PATH
+                                               svn_path => $SVN->{svn_path},
                                        },
                                        $SVN->get_commit_editor(
                                                $log_msg->{msg},
@@ -586,11 +483,12 @@ sub commit_lib {
 }
 
 sub dcommit {
+       my $head = shift || 'HEAD';
        my $gs = "refs/remotes/$GIT_SVN";
-       chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD"));
+       my @refs = command(qw/rev-list --no-merges/, "$gs..$head");
        my $last_rev;
        foreach my $d (reverse @refs) {
-               if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) {
+               if (!verify_ref("$d~1")) {
                        die "Commit $d\n",
                            "has no parent commit, and therefore ",
                            "nothing to diff against.\n",
@@ -614,54 +512,27 @@ sub dcommit {
        }
        return if $_dry_run;
        fetch();
-       my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs);
+       my @diff = command('diff-tree', $head, $gs, '--');
        my @finish;
        if (@diff) {
                @finish = qw/rebase/;
                push @finish, qw/--merge/ if $_merge;
                push @finish, "--strategy=$_strategy" if $_strategy;
-               print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff;
+               print STDERR "W: $head and $gs differ, using @finish:\n", @diff;
        } else {
-               print "No changes between current HEAD and $gs\n",
-                     "Hard resetting to the latest $gs\n";
+               print "No changes between current $head and $gs\n",
+                     "Resetting to the latest $gs\n";
                @finish = qw/reset --mixed/;
        }
-       sys('git', @finish, $gs);
+       command_noisy(@finish, $gs);
 }
 
 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);
+       $SVN ||= libsvn_connect($SVN_URL);
        my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
-       libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r);
+       libsvn_traverse_ignore(\*STDOUT, $SVN->{svn_path}, $r);
 }
 
 sub graft_branches {
@@ -671,7 +542,7 @@ sub graft_branches {
 
        if (%$grafts) {
                # temporarily disable our grafts file to make this idempotent
-               chomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file));
+               chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file));
                rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
        }
 
@@ -689,11 +560,7 @@ sub graft_branches {
                        }
                }
                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_file_copy_lib($grafts,$l_map,$u);
                }
        }
        graft_tree_joins($grafts);
@@ -725,7 +592,7 @@ sub multi_init {
        unless (-d $GIT_SVN_DIR) {
                print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id;
                init($_trunk);
-               sys('git-repo-config', 'svn.trunk', $_trunk);
+               command_noisy('repo-config', 'svn.trunk', $_trunk);
        }
        complete_url_ls_init($url, $_branches, '--branches/-b', '');
        complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
@@ -762,16 +629,14 @@ sub show_log {
                }
        }
 
-       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();
+       config_pager();
+       @args = (git_svn_log_cmd($r_min, $r_max), @args);
+       my $log = command_output_pipe(@args);
+       run_pager();
        my (@k, $c, $d);
 
        while (<$log>) {
-               if (/^commit ($sha1_short)/o) {
+               if (/^${_esc_color}commit ($sha1_short)/o) {
                        my $cmt = $1;
                        if ($c && cmt_showable($c) && $c->{r} != $r_last) {
                                $r_last = $c->{r};
@@ -780,25 +645,25 @@ sub show_log {
                        }
                        $d = undef;
                        $c = { c => $cmt };
-               } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) {
+               } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) {
                        get_author_info($c, $1, $2, $3);
-               } elsif (/^(?:tree|parent|committer) /) {
+               } elsif (/^${_esc_color}(?:tree|parent|committer) /) {
                        # ignore
-               } elsif (/^:\d{6} \d{6} $sha1_short/o) {
+               } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) {
                        push @{$c->{raw}}, $_;
-               } elsif (/^[ACRMDT]\t/) {
-                       # we could add $SVN_PATH here, but that requires
+               } elsif (/^${_esc_color}[ACRMDT]\t/) {
+                       # we could add $SVN->{svn_path} here, but that requires
                        # remote access at the moment (repo_path_split)...
-                       s#^([ACRMDT])\t#   $1 #;
+                       s#^(${_esc_color})([ACRMDT])\t#$1   $2 #;
                        push @{$c->{changed}}, $_;
-               } elsif (/^diff /) {
+               } elsif (/^${_esc_color}diff /) {
                        $d = 1;
                        push @{$c->{diff}}, $_;
                } elsif ($d) {
                        push @{$c->{diff}}, $_;
-               } elsif (/^    (git-svn-id:.+)$/) {
+               } elsif (/^${_esc_color}    (git-svn-id:.+)$/) {
                        ($c->{url}, $c->{r}, undef) = extract_metadata($1);
-               } elsif (s/^    //) {
+               } elsif (s/^${_esc_color}    //) {
                        push @{$c->{l}}, $_;
                }
        }
@@ -813,7 +678,7 @@ sub show_log {
                process_commit($_, $r_min, $r_max) foreach reverse @k;
        }
 out:
-       close $log;
+       eval { command_close_pipe($log) };
        print '-' x72,"\n" unless $_incremental || $_oneline;
 }
 
@@ -823,10 +688,6 @@ sub commit_diff_usage {
 }
 
 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") }) {
@@ -853,10 +714,7 @@ sub commit_diff {
                $_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);
+       $SVN ||= libsvn_connect($SVN_URL);
        if ($r eq 'HEAD') {
                $r = $SVN->get_latest_revnum;
        } elsif ($r !~ /^\d+$/) {
@@ -865,8 +723,9 @@ sub commit_diff {
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
        my $rev_committed;
        my $ed = SVN::Git::Editor->new({        r => $r,
-                                               ra => $SVN_LOG, c => $tb,
-                                               svn_path => $SVN_PATH
+                                               ra => libsvn_dup_ra($SVN),
+                                               c => $tb,
+                                               svn_path => $SVN->{svn_path}
                                        },
                                $SVN->get_commit_editor($_message,
                                        sub {
@@ -895,7 +754,7 @@ sub cmt_showable {
        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});
+               my @msg = command(qw/cat-file commit/, $c->{c});
                shift @msg while ($msg[0] ne "\n");
                shift @msg;
                @{$c->{l}} = grep !/^git-svn-id: /, @msg;
@@ -906,12 +765,49 @@ sub cmt_showable {
        return defined $c->{r};
 }
 
+sub log_use_color {
+       return 1 if $_color;
+       my ($dc, $dcvar);
+       $dcvar = 'color.diff';
+       $dc = `git-repo-config --get $dcvar`;
+       if ($dc eq '') {
+               # nothing at all; fallback to "diff.color"
+               $dcvar = 'diff.color';
+               $dc = `git-repo-config --get $dcvar`;
+       }
+       chomp($dc);
+       if ($dc eq 'auto') {
+               my $pc;
+               $pc = `git-repo-config --get color.pager`;
+               if ($pc eq '') {
+                       # does not have it -- fallback to pager.color
+                       $pc = `git-repo-config --bool --get pager.color`;
+               }
+               else {
+                       $pc = `git-repo-config --bool --get color.pager`;
+                       if ($?) {
+                               $pc = 'false';
+                       }
+               }
+               chomp($pc);
+               if (-t *STDOUT || (defined $_pager && $pc eq 'true')) {
+                       return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
+               }
+               return 0;
+       }
+       return 0 if $dc eq 'never';
+       return 1 if $dc eq 'always';
+       chomp($dc = `git-repo-config --bool --get $dcvar`);
+       return ($dc eq 'true');
+}
+
 sub git_svn_log_cmd {
        my ($r_min, $r_max) = @_;
-       my @cmd = (qw/git-log --abbrev-commit --pretty=raw
+       my @cmd = (qw/log --abbrev-commit --pretty=raw
                        --default/, "refs/remotes/$GIT_SVN");
        push @cmd, '-r' unless $_non_recursive;
        push @cmd, qw/--raw --name-status/ if $_verbose;
+       push @cmd, '--color' if log_use_color();
        return @cmd unless defined $r_max;
        if ($r_max == $r_min) {
                push @cmd, '--max-count=1';
@@ -992,8 +888,7 @@ sub complete_url_ls_init {
                }
                $var = $url . $var;
        }
-       chomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var)
-                               : safe_qx(qw/svn ls --non-interactive/, $var));
+       my @ls = libsvn_ls_fullurl($var);
        my $old = $GIT_SVN;
        defined(my $pid = fork) or croak $!;
        if (!$pid) {
@@ -1017,7 +912,7 @@ sub complete_url_ls_init {
        waitpid $pid, 0;
        croak $? if $?;
        my ($n) = ($switch =~ /^--(\w+)/);
-       sys('git-repo-config', "svn.$n", $var);
+       command_noisy('repo-config', "svn.$n", $var);
 }
 
 sub common_prefix {
@@ -1049,11 +944,8 @@ sub graft_tree_joins {
 
        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 $!;
-               }
+               my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i");
+               my ($fh, $ctx) = command_output_pipe(@args);
                while (<$fh>) {
                        next unless /^commit ($sha1)$/o;
                        my $c = $1;
@@ -1076,9 +968,7 @@ sub graft_tree_joins {
 
                        foreach my $p (@{$tree_map{$t}}) {
                                next if $p eq $c;
-                               my $mb = eval {
-                                       safe_qx('git-merge-base', $c, $p)
-                               };
+                               my $mb = eval { command('merge-base', $c, $p) };
                                next unless ($@ || $?);
                                if (defined $r_a) {
                                        # see if SVN says it's a relative
@@ -1107,48 +997,16 @@ sub graft_tree_joins {
                                # what should we do when $ct == $s ?
                        }
                }
-               close $fh or croak $?;
+               command_close_pipe($fh, $ctx);
        });
 }
 
-# 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);
+       $SVN = libsvn_connect($repo);
 
        my ($base, $head) = libsvn_parse_revision();
        my $inc = 1000;
@@ -1157,7 +1015,8 @@ sub graft_file_copy_lib {
        $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,
+               libsvn_get_log(libsvn_dup_ra($SVN), [$path],
+                              $min, $max, 0, 2, 1,
                        sub {
                                libsvn_graft_file_copies($grafts, $tree_paths,
                                                        $path, @_);
@@ -1232,28 +1091,14 @@ sub graft_merge_msg {
 
 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";
-       }
+       my $pool = SVN::Pool->new;
+       $SVN_UUID = $SVN->get_uuid($pool);
+       $pool->clear;
 }
 
-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 verify_ref {
+       my ($ref) = @_;
+       eval { command_oneline([ 'rev-parse', $ref ], { STDERR => 0 }) };
 }
 
 sub repo_path_split {
@@ -1267,25 +1112,8 @@ sub repo_path_split {
                        return ($u, $full_url);
                }
        }
-
-       if ($_use_lib) {
-               my $tmp = libsvn_connect($full_url);
-               my $url = $tmp->get_repos_root;
-               $full_url =~ s#^\Q$url\E/*##;
-               push @repo_path_split_cache, qr/^(\Q$url\E)/;
-               return ($url, $full_url);
-       } else {
-               my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
-               $path =~ s#^/+##;
-               my @paths = split(m#/+#, $path);
-               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);
-       }
+       my $tmp = libsvn_connect($full_url);
+       return ($tmp->{repos_root}, $tmp->{svn_path});
 }
 
 sub setup_git_svn {
@@ -1301,41 +1129,17 @@ sub setup_git_svn {
 
 }
 
-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);
-       @status = grep(!/^X/,@status) if $_no_ignore_ext;
-       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 $type = command_oneline(qw/cat-file -t/, $treeish);
        my $expected;
        while ($type eq 'tag') {
-               chomp(($treeish, $type) = `git-cat-file tag $treeish`);
+               ($treeish, $type) = command(qw/cat-file tag/, $treeish);
        }
        if ($type eq 'commit') {
-               $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
+               $expected = (grep /^tree /, command(qw/cat-file commit/,
+                                                   $treeish))[0];
                ($expected) = ($expected =~ /^tree ($sha1)$/);
                die "Unable to get tree from $treeish\n" unless $expected;
        } elsif ($type eq 'tree') {
@@ -1346,27 +1150,19 @@ sub get_tree_from_treeish {
        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";
+sub get_diff {
+       my ($from, $treeish) = @_;
+       print "diff-tree $from $treeish\n";
+       my @diff_tree = qw(diff-tree -z -r);
+       if ($_cp_similarity) {
+               push @diff_tree, "-C$_cp_similarity";
+       } else {
+               push @diff_tree, '-C';
        }
-       unlink $tmpindex;
-}
-
-sub parse_diff_tree {
-       my $diff_fh = shift;
+       push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+       push @diff_tree, "-l$_l" if defined $_l;
+       push @diff_tree, $from, $treeish;
+       my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
        local $/ = "\0";
        my $state = 'meta';
        my @mods;
@@ -1402,170 +1198,10 @@ sub parse_diff_tree {
                        croak "Error parsing $_\n";
                }
        }
-       close $diff_fh or croak $?;
-
+       command_close_pipe($diff_fh, $ctx);
        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') {
-                       svn_check_prop_executable($m);
-                       apply_mod_line_blob($m);
-                       if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
-                               sys(qw(svn propdel svn:special), $m->{file_b});
-                       } else {
-                               sys(qw(svn propset svn:special *),$m->{file_b});
-                       }
-               } 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);
@@ -1583,57 +1219,15 @@ sub libsvn_checkout_tree {
        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`);
+       my $type = command_oneline(qw/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 ($msg_fh, $ctx) = command_output_pipe('cat-file',
+                                                        $type, $commit);
                my $in_msg = 0;
                while (<$msg_fh>) {
                        if (!$in_msg) {
@@ -1645,7 +1239,7 @@ sub get_commit_message {
                                print $msg $_ or croak $!;
                        }
                }
-               close $msg_fh or croak $?;
+               command_close_pipe($msg_fh, $ctx);
        }
        close $msg or croak $!;
 
@@ -1670,64 +1264,9 @@ sub set_svn_commit_env {
        }
 }
 
-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 => { } };
+       my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_);
+       return { fh => $fh, ctx => $c, t => { } };
 }
 
 sub next_rev_list_entry {
@@ -1749,184 +1288,10 @@ sub next_rev_list_entry {
                        $x->{m} .= $_;
                }
        }
+       command_close_pipe($fh, $rl->{ctx});
        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+)/);
-                       $date = '1970-01-01 00:00:00 +0000'
-                               if ($_ignore_nodate && $date eq '(no date)');
-                       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 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#^/##;
-                       }
-                       my @s = stat($x);
-                       unlink $x or croak $!;
-                       copy($tb, $x);
-                       chmod(($mode &~ umask), $x) or croak $!;
-                       utime $s[8], $s[9], $x;
-               }
-               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 $!;
@@ -1952,18 +1317,6 @@ sub assert_revision_unknown {
        }
 }
 
-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});
@@ -1988,15 +1341,14 @@ sub git_commit {
        my $tree = $log_msg->{tree};
        if (!defined $tree) {
                my $index = set_index($GIT_SVN_INDEX);
-               index_changes();
-               chomp($tree = `git-write-tree`);
+               $tree = command_oneline('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") }) {
+       if (my $cur = verify_ref("refs/remotes/$GIT_SVN^0")) {
+               chomp $cur;
                push @tmp_parents, $cur;
        }
 
@@ -2005,9 +1357,7 @@ sub git_commit {
                        my $skip;
                        foreach (@tmp_parents) {
                                # see if a common parent is found
-                               my $mb = eval {
-                                       safe_qx('git-merge-base', $_, $p)
-                               };
+                               my $mb = eval { command('merge-base', $_, $p) };
                                next if ($@ || $?);
                                $skip = 1;
                                last;
@@ -2049,7 +1399,7 @@ sub git_commit {
        if ($commit !~ /^$sha1$/o) {
                die "Failed to commit, invalid sha1: $commit\n";
        }
-       sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
+       command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit);
        revdb_set($REVDB, $log_msg->{revision}, $commit);
 
        # this output is read via pipe, do not change:
@@ -2061,7 +1411,8 @@ sub git_commit {
 sub check_repack {
        if ($_repack && (--$_repack_nr == 0)) {
                $_repack_nr = $_repack;
-               sys("git repack $_repack_flags");
+               # repack doesn't use any arguments with spaces in them, does it?
+               command_noisy('repack', split(/\s+/, $_repack_flags));
        }
 }
 
@@ -2078,122 +1429,17 @@ sub set_commit_env {
        $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 eval {
+               command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"],
+                       {STDERR => 0});
        };
-       return unless $old;
-       my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
+       my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") };
        if ($@ || !$head) {
                print STDERR "Please run: $0 rebuild --upgrade\n";
                exit 1;
@@ -2205,12 +1451,8 @@ sub check_upgrade_needed {
 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 $!;
-               }
+               my $pipe = command_output_pipe(qw/rev-list
+                                           --topo-order --pretty=raw/, $br);
                while (<$pipe>) {
                        if (/^commit ($sha1)$/o) {
                                my $commit = $1;
@@ -2226,7 +1468,7 @@ sub map_tree_joins {
                                $seen{$commit} = 1;
                        }
                }
-               close $pipe; # we could be breaking the pipe early
+               eval { command_close_pipe($pipe) };
        }
 }
 
@@ -2238,7 +1480,7 @@ sub load_all_refs {
 
        # 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`);
+       @_branch_from = command(qw/rev-parse --symbolic --all/);
 }
 
 # '<svn username> = real-name <email address>' mapping based on git-svnimport:
@@ -2264,15 +1506,9 @@ sub rload_authors {
        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`) {
+       foreach (command(qw/rev-parse --symbolic --all/)) {
                next unless s#^refs/remotes/##;
                chomp $_;
                next unless -f "$GIT_DIR/svn/$_/info/url";
@@ -2313,7 +1549,7 @@ sub migration_check {
                                "$GIT_SVN_DIR\n\t(required for this version ",
                                "($VERSION) of git-svn) does not.\n";
 
-       foreach my $x (`git-rev-parse --symbolic --all`) {
+       foreach my $x (command(qw/rev-parse --symbolic --all/)) {
                next unless $x =~ s#^refs/remotes/##;
                chomp $x;
                next unless -f "$GIT_DIR/$x/info/url";
@@ -2418,11 +1654,7 @@ sub write_grafts {
                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 $!;
-               }
+               my $ch = command_output_pipe(qw/cat-file commit/, $c);
                while (<$ch>) {
                        if (/^parent ($sha1)/) {
                                $x{$1} = $p->{$1} = 1;
@@ -2430,7 +1662,7 @@ sub write_grafts {
                                last unless /^\S/;
                        }
                }
-               close $ch; # breaking the pipe
+               eval { command_close_pipe($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);
@@ -2442,7 +1674,7 @@ sub write_grafts {
                        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) };
+                               $mb = eval { command('merge-base', $i, $j) };
                                next unless $mb;
                                chomp $mb;
                                next if $x{$mb};
@@ -2513,15 +1745,12 @@ sub extract_metadata {
 
 sub cmt_metadata {
        return extract_metadata((grep(/^git-svn-id: /,
-               safe_qx(qw/git-cat-file commit/, shift)))[-1]);
+               command(qw/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 $!;
-       }
+       my $fh = command_output_pipe(qw/rev-list --pretty=raw -n1/, $cmt);
        while (<$fh>) {
                /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
                my ($s, $tz) = ($1, $2);
@@ -2530,7 +1759,7 @@ sub get_commit_time {
                } elsif ($tz =~ s/^\-//) {
                        $s -= tz_to_s_offset($tz);
                }
-               close $fh;
+               eval { command_close_pipe($fh) };
                return $s;
        }
        die "Can't get commit time for commit: $cmt\n";
@@ -2542,14 +1771,18 @@ sub tz_to_s_offset {
        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;
+# adapted from pager.c
+sub config_pager {
+       $_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
+       if (!defined $_pager) {
+               $_pager = 'less';
+       } elsif (length $_pager == 0 || $_pager eq 'cat') {
+               $_pager = undef;
        }
+}
+
+sub run_pager {
+       return unless -t *STDOUT;
        pipe my $rfd, my $wfd or return;
        defined(my $pid = fork) or croak $!;
        if (!$pid) {
@@ -2557,8 +1790,8 @@ sub setup_pager { # translated to Perl from pager.c
                return;
        }
        open STDIN, '<&', $rfd or croak $!;
-       $ENV{LESS} ||= '-S';
-       exec $pager or croak "Can't run pager: $!\n";;
+       $ENV{LESS} ||= 'FRSX';
+       exec $_pager or croak "Can't run pager: $! ($_pager)\n";
 }
 
 sub get_author_info {
@@ -2672,41 +1905,186 @@ sub show_commit_normal {
        }
 }
 
-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";
+sub _simple_prompt {
+       my ($cred, $realm, $default_username, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       $default_username = $_username if defined $_username;
+       if (defined $default_username && length $default_username) {
+               if (defined $realm && length $realm) {
+                       print "Authentication realm: $realm\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;
-       };
+               $cred->username($default_username);
+       } else {
+               _username_prompt($cred, $realm, $may_save, $pool);
+       }
+       $cred->password(_read_password("Password for '" .
+                                      $cred->username . "': ", $realm));
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_server_trust_prompt {
+       my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       print "Error validating server certificate for '$realm':\n";
+       if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
+               print " - The certificate is not issued by a trusted ",
+                     "authority. Use the\n",
+                     "   fingerprint to validate the certificate manually!\n";
+       }
+       if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
+               print " - The certificate hostname does not match.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
+               print " - The certificate is not yet valid.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::EXPIRED) {
+               print " - The certificate has expired.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::OTHER) {
+               print " - The certificate has an unknown error.\n";
+       }
+       printf( "Certificate information:\n".
+               " - Hostname: %s\n".
+               " - Valid: from %s until %s\n".
+               " - Issuer: %s\n".
+               " - Fingerprint: %s\n",
+               map $cert_info->$_, qw(hostname valid_from valid_until
+                                      issuer_dname fingerprint) );
+       my $choice;
+prompt:
+       print $may_save ?
+             "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
+             "(R)eject or accept (t)emporarily? ";
+       $choice = lc(substr(<STDIN> || 'R', 0, 1));
+       if ($choice =~ /^t$/i) {
+               $cred->may_save(undef);
+       } elsif ($choice =~ /^r$/i) {
+               return -1;
+       } elsif ($may_save && $choice =~ /^p$/i) {
+               $cred->may_save($may_save);
+       } else {
+               goto prompt;
+       }
+       $cred->accepted_failures($failures);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_client_cert_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       print "Client certificate filename: ";
+       chomp(my $filename = <STDIN>);
+       $cred->cert_file($filename);
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_client_cert_pw_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       $cred->password(_read_password("Password: ", $realm));
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _username_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       if (defined $realm && length $realm) {
+               print "Authentication realm: $realm\n";
+       }
+       my $username;
+       if (defined $_username) {
+               $username = $_username;
+       } else {
+               print "Username: ";
+               chomp($username = <STDIN>);
+       }
+       $cred->username($username);
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _read_password {
+       my ($prompt, $realm) = @_;
+       print $prompt;
+       require Term::ReadKey;
+       Term::ReadKey::ReadMode('noecho');
+       my $password = '';
+       while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+               last if $key =~ /[\012\015]/; # \n\r
+               $password .= $key;
+       }
+       Term::ReadKey::ReadMode('restore');
+       print "\n";
+       $password;
 }
 
 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;
+       SVN::_Core::svn_config_ensure($_config_dir, undef);
+       my ($baton, $callbacks) = SVN::Core::auth_open_helper([
+           SVN::Client::get_simple_provider(),
+           SVN::Client::get_ssl_server_trust_file_provider(),
+           SVN::Client::get_simple_prompt_provider(
+             \&_simple_prompt, 2),
+           SVN::Client::get_ssl_client_cert_prompt_provider(
+             \&_ssl_client_cert_prompt, 2),
+           SVN::Client::get_ssl_client_cert_pw_prompt_provider(
+             \&_ssl_client_cert_pw_prompt, 2),
+           SVN::Client::get_username_provider(),
+           SVN::Client::get_ssl_server_trust_prompt_provider(
+             \&_ssl_server_trust_prompt),
+           SVN::Client::get_username_prompt_provider(
+             \&_username_prompt, 2),
+         ]);
+       my $config = SVN::Core::config_get_config($_config_dir);
+       my $ra = SVN::Ra->new(url => $url, auth => $baton,
+                             config => $config,
+                             pool => SVN::Pool->new,
+                             auth_provider_callbacks => $callbacks);
+
+       my $df = $ENV{GIT_SVN_DELTA_FETCH};
+       if (defined $df) {
+               $_xfer_delta = $df;
+       } else {
+               $_xfer_delta = ($url =~ m#^file://#) ? undef : 1;
+       }
+       $ra->{svn_path} = $url;
+       $ra->{repos_root} = $ra->get_repos_root;
+       $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##;
+       push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/;
+       return $ra;
 }
 
-sub libsvn_get_file {
-       my ($gui, $f, $rev, $chg) = @_;
-       my $p = $f;
-       if (length $SVN_PATH > 0) {
-               return unless ($p =~ s#^\Q$SVN_PATH\E/##);
+sub libsvn_can_do_switch {
+       unless (defined $_svn_can_do_switch) {
+               my $pool = SVN::Pool->new;
+               my $rep = eval {
+                       $SVN->do_switch(1, '', 0, $SVN->{url},
+                                       SVN::Delta::Editor->new, $pool);
+               };
+               if ($@) {
+                       $_svn_can_do_switch = 0;
+               } else {
+                       $rep->abort_report($pool);
+                       $_svn_can_do_switch = 1;
+               }
+               $pool->clear;
        }
+       $_svn_can_do_switch;
+}
+
+sub libsvn_dup_ra {
+       my ($ra) = @_;
+       SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url
+                    auth auth_provider_callbacks repos_root svn_path/);
+}
+
+sub libsvn_get_file {
+       my ($gui, $f, $rev, $chg, $untracked) = @_;
+       $f =~ s#^/##;
        print "\t$chg\t$f\n" unless $_q;
 
        my ($hash, $pid, $in, $out);
@@ -2730,7 +2108,7 @@ sub libsvn_get_file {
        my $mode = exists $props->{'svn:executable'} ? '100755' : '100644';
        if (exists $props->{'svn:special'}) {
                $mode = '120000';
-               my $link = `git-cat-file blob $hash`;
+               my $link = `git-cat-file blob $hash`; # no chomping symlinks
                $link =~ s/^link // or die "svn:special file with contents: <",
                                                $link, "> is not understood\n";
                defined($pid = open3($in, $out, '>&STDERR',
@@ -2743,53 +2121,156 @@ sub libsvn_get_file {
                waitpid $pid, 0;
                $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
        }
-       print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!;
+       %{$untracked->{file_prop}->{$f}} = %$props;
+       print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!;
+}
+
+sub uri_encode {
+       my ($f) = @_;
+       $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
+       $f
+}
+
+sub uri_decode {
+       my ($f) = @_;
+       $f =~ tr/+/ /;
+       $f =~ s/%([A-F0-9]{2})/chr hex($1)/ge;
+       $f
 }
 
 sub libsvn_log_entry {
-       my ($rev, $author, $date, $msg, $parents) = @_;
+       my ($rev, $author, $date, $msg, $parents, $untracked) = @_;
        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}) {
+       if (defined $author && length $author > 0 &&
+           defined $_authors && ! defined $users{$author}) {
                die "Author: $author not defined in $_authors file\n";
        }
        $msg = '' if ($rev == 0 && !defined $msg);
-       return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
-               author => $author, msg => $msg."\n", parents => $parents || [] }
+
+       open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!;
+       my $h;
+       print $un "r$rev\n" or croak $!;
+       $h = $untracked->{empty};
+       foreach (sort keys %$h) {
+               my $act = $h->{$_} ? '+empty_dir' : '-empty_dir';
+               print $un "  $act: ", uri_encode($_), "\n" or croak $!;
+               warn "W: $act: $_\n";
+       }
+       foreach my $t (qw/dir_prop file_prop/) {
+               $h = $untracked->{$t} or next;
+               foreach my $path (sort keys %$h) {
+                       my $ppath = $path eq '' ? '.' : $path;
+                       foreach my $prop (sort keys %{$h->{$path}}) {
+                               next if $SKIP{$prop};
+                               my $v = $h->{$path}->{$prop};
+                               if (defined $v) {
+                                       print $un "  +$t: ",
+                                                 uri_encode($ppath), ' ',
+                                                 uri_encode($prop), ' ',
+                                                 uri_encode($v), "\n"
+                                                 or croak $!;
+                               } else {
+                                       print $un "  -$t: ",
+                                                 uri_encode($ppath), ' ',
+                                                 uri_encode($prop), "\n"
+                                                 or croak $!;
+                               }
+                       }
+               }
+       }
+       foreach my $t (qw/absent_file absent_directory/) {
+               $h = $untracked->{$t} or next;
+               foreach my $parent (sort keys %$h) {
+                       foreach my $path (sort @{$h->{$parent}}) {
+                               print $un "  $t: ",
+                                     uri_encode("$parent/$path"), "\n"
+                                     or croak $!;
+                               warn "W: $t: $parent/$path ",
+                                    "Insufficient permissions?\n";
+                       }
+               }
+       }
+
+       # revprops (make this optional? it's an extra network trip...)
+       my $pool = SVN::Pool->new;
+       my $rp = $SVN->rev_proplist($rev, $pool);
+       foreach (sort keys %$rp) {
+               next if /^svn:(?:author|date|log)$/;
+               print $un "  rev_prop: ", uri_encode($_), ' ',
+                         uri_encode($rp->{$_}), "\n";
+       }
+       $pool->clear;
+       close $un or croak $!;
+
+       { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
+         author => $author, msg => $msg."\n", parents => $parents || [],
+         revprops => $rp }
 }
 
 sub process_rm {
-       my ($gui, $last_commit, $f) = @_;
-       $f =~ s#^\Q$SVN_PATH\E/?## or return;
+       my ($gui, $last_commit, $f, $q) = @_;
        # 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 $!;
-               }
+       if (command('ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
+               my ($ls, $ctx) = command_output_pipe(qw/ls-tree
+                                                    -r --name-only -z/,
+                                                    $last_commit,'--',$f);
                local $/ = "\0";
                while (<$ls>) {
                        print $gui '0 ',0 x 40,"\t",$_ or croak $!;
+                       print "\tD\t$_\n" unless $q;
                }
-               close $ls or croak $?;
+               print "\tD\t$f/\n" unless $q;
+               command_close_pipe($ls, $ctx);
+               return $SVN::Node::dir;
        } else {
                print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
+               print "\tD\t$f\n" unless $q;
+               return $SVN::Node::file;
        }
 }
 
 sub libsvn_fetch {
+       $_xfer_delta ? libsvn_fetch_delta(@_) : libsvn_fetch_full(@_);
+}
+
+sub libsvn_fetch_delta {
+       my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
+       my $pool = SVN::Pool->new;
+       my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q });
+       my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
+       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+       my (undef, $last_rev, undef) = cmt_metadata($last_commit);
+       $reporter->set_path('', $last_rev, 0, @lock, $pool);
+       $reporter->finish_report($pool);
+       $pool->clear;
+       unless ($ed->{git_commit_ok}) {
+               die "SVN connection failed somewhere...\n";
+       }
+       libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed);
+}
+
+sub libsvn_fetch_full {
        my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
-       open my $gui, '| git-update-index -z --index-info' or croak $!;
+       my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/);
        my %amr;
+       my $ut = { empty => {}, dir_prop => {}, file_prop => {} };
+       my $p = $SVN->{svn_path};
        foreach my $f (keys %$paths) {
                my $m = $paths->{$f}->action();
-               $f =~ s#^/+##;
+               if (length $p) {
+                       $f =~ s#^/\Q$p\E/##;
+                       next if $f =~ m#^/#;
+               } else {
+                       $f =~ s#^/##;
+               }
                if ($m =~ /^[DR]$/) {
-                       print "\t$m\t$f\n" unless $_q;
-                       process_rm($gui, $last_commit, $f);
-                       next if $m eq 'D';
+                       my $t = process_rm($gui, $last_commit, $f, $_q);
+                       if ($m eq 'D') {
+                               $ut->{empty}->{$f} = 0 if $t == $SVN::Node::dir;
+                               next;
+                       }
                        # 'R' can be file replacements, too, right?
                }
                my $pool = SVN::Pool->new;
@@ -2802,30 +2283,38 @@ sub libsvn_fetch {
                        }
                } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) {
                        my @traversed = ();
-                       libsvn_traverse($gui, '', $f, $rev, \@traversed);
-                       foreach (@traversed) {
-                               $amr{$_} = $m;
+                       libsvn_traverse($gui, '', $f, $rev, \@traversed, $ut);
+                       if (@traversed) {
+                               foreach (@traversed) {
+                                       $amr{$_} = $m;
+                               }
+                       } else {
+                               my ($dir, $file) = ($f =~ m#^(.*?)/?([^/]+)$#);
+                               delete $ut->{empty}->{$dir};
+                               $ut->{empty}->{$f} = 1;
                        }
                }
                $pool->clear;
        }
        foreach (keys %amr) {
-               libsvn_get_file($gui, $_, $rev, $amr{$_});
+               libsvn_get_file($gui, $_, $rev, $amr{$_}, $ut);
+               my ($d) = ($_ =~ m#^(.*?)/?(?:[^/]+)$#);
+               delete $ut->{empty}->{$d};
        }
-       close $gui or croak $?;
-       return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
+       unless (exists $ut->{dir_prop}->{''}) {
+               my $pool = SVN::Pool->new;
+               my (undef, undef, $props) = $SVN->get_dir('', $rev, $pool);
+               %{$ut->{dir_prop}->{''}} = %$props;
+               $pool->clear;
+       }
+       command_close_pipe($gui, $ctx);
+       libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ut);
 }
 
 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;
+       my $c = eval { command_oneline([qw/rev-parse --verify/,
+                                       "refs/remotes/$GIT_SVN^0"],
+                                       { STDERR => 0 }) };
        if (defined $c && length $c) {
                my ($url, $rev, $uuid) = cmt_metadata($c);
                return ($rev, $c) if defined $rev;
@@ -2874,25 +2363,38 @@ sub libsvn_parse_revision {
 }
 
 sub libsvn_traverse {
-       my ($gui, $pfx, $path, $rev, $files) = @_;
-       my $cwd = "$pfx/$path";
+       my ($gui, $pfx, $path, $rev, $files, $untracked) = @_;
+       my $cwd = length $pfx ? "$pfx/$path" : $path;
        my $pool = SVN::Pool->new;
-       $cwd =~ s#^/+##g;
+       $cwd =~ s#^\Q$SVN->{svn_path}\E##;
+       my $nr = 0;
        my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);
+       %{$untracked->{dir_prop}->{$cwd}} = %$props;
        foreach my $d (keys %$dirent) {
                my $t = $dirent->{$d}->kind;
                if ($t == $SVN::Node::dir) {
-                       libsvn_traverse($gui, $cwd, $d, $rev, $files);
+                       my $i = libsvn_traverse($gui, $cwd, $d, $rev,
+                                               $files, $untracked);
+                       if ($i) {
+                               $nr += $i;
+                       } else {
+                               $untracked->{empty}->{"$cwd/$d"} = 1;
+                       }
                } elsif ($t == $SVN::Node::file) {
+                       $nr++;
                        my $file = "$cwd/$d";
                        if (defined $files) {
                                push @$files, $file;
                        } else {
-                               libsvn_get_file($gui, $file, $rev, 'A');
+                               libsvn_get_file($gui, $file, $rev, 'A',
+                                               $untracked);
+                               my ($dir) = ($file =~ m#^(.*?)/?(?:[^/]+)$#);
+                               delete $untracked->{empty}->{$dir};
                        }
                }
        }
        $pool->clear;
+       $nr;
 }
 
 sub libsvn_traverse_ignore {
@@ -2901,7 +2403,7 @@ sub libsvn_traverse_ignore {
        my $pool = SVN::Pool->new;
        my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
        my $p = $path;
-       $p =~ s#^\Q$SVN_PATH\E/?##;
+       $p =~ s#^\Q$SVN->{svn_path}\E/##;
        print $fh length $p ? "\n# $p\n" : "\n# /\n";
        if (my $s = $props->{'svn:ignore'}) {
                $s =~ s/[\r\n]+/\n/g;
@@ -2925,25 +2427,18 @@ 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};
-       }
+       # 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, 0, 1, sub {$nr++}, $pool);
+       $pool->clear;
        return 0 if ($nr > 1);
        return 1;
 }
 
 sub libsvn_find_parent_branch {
        my ($paths, $rev, $author, $date, $msg) = @_;
-       my $svn_path = '/'.$SVN_PATH;
+       my $svn_path = '/'.$SVN->{svn_path};
 
        # look for a parent from another branch:
        my $i = $paths->{$svn_path} or return;
@@ -2954,7 +2449,7 @@ sub libsvn_find_parent_branch {
        $branch_from =~ s#^/##;
        my $l_map = {};
        read_url_paths_all($l_map, '', "$GIT_DIR/svn");
-       my $url = $SVN->{url};
+       my $url = $SVN->{repos_root};
        defined $l_map->{$url} or return;
        my $id = $l_map->{$url}->{$branch_from};
        if (!defined $id && $_follow_parent) {
@@ -2976,7 +2471,7 @@ sub libsvn_find_parent_branch {
                        $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
                        init_vars();
                        $SVN_URL = "$url/$branch_from";
-                       $SVN_LOG = $SVN = undef;
+                       $SVN = undef;
                        setup_git_svn();
                        # we can't assume SVN_URL exists at r+1:
                        $_revision = "0:$r";
@@ -2991,9 +2486,27 @@ sub libsvn_find_parent_branch {
        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);
+               command_noisy('read-tree', $parent);
+               unless (libsvn_can_do_switch()) {
+                       return libsvn_fetch_full($parent, $paths, $rev,
+                                               $author, $date, $msg);
+               }
+               # do_switch works with svn/trunk >= r22312, but that is not
+               # included with SVN 1.4.2 (the latest version at the moment),
+               # so we can't rely on it.
+               my $ra = libsvn_connect("$url/$branch_from");
+               my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q });
+               my $pool = SVN::Pool->new;
+               my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url},
+                                             $ed, $pool);
+               my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+               $reporter->set_path('', $r0, 0, @lock, $pool);
+               $reporter->finish_report($pool);
+               $pool->clear;
+               unless ($ed->{git_commit_ok}) {
+                       die "SVN connection failed somewhere...\n";
+               }
+               return libsvn_log_entry($rev, $author, $date, $msg, [$parent]);
        }
        print STDERR "Nope, branch point not imported or unknown\n";
        return undef;
@@ -3001,6 +2514,7 @@ sub libsvn_find_parent_branch {
 
 sub libsvn_get_log {
        my ($ra, @args) = @_;
+       $args[4]-- if $args[4] && $_xfer_delta && ! $_follow_parent;
        if ($SVN::Core::VERSION le '1.2.0') {
                splice(@args, 3, 1);
        }
@@ -3012,10 +2526,27 @@ sub libsvn_new_tree {
                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);
+       my $ut;
+       if ($_xfer_delta) {
+               my $pool = SVN::Pool->new;
+               my $ed = SVN::Git::Fetcher->new({q => $_q});
+               my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
+               my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+               $reporter->set_path('', $rev, 1, @lock, $pool);
+               $reporter->finish_report($pool);
+               $pool->clear;
+               unless ($ed->{git_commit_ok}) {
+                       die "SVN connection failed somewhere...\n";
+               }
+               $ut = $ed;
+       } else {
+               $ut = { empty => {}, dir_prop => {}, file_prop => {} };
+               my ($gui, $ctx) = command_input_pipe(qw/update-index
+                                                    -z --index-info/);
+               libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut);
+               command_close_pipe($gui, $ctx);
+       }
+       libsvn_log_entry($rev, $author, $date, $msg, [], $ut);
 }
 
 sub find_graft_path_commit {
@@ -3085,7 +2616,7 @@ sub libsvn_commit_cb {
                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);
+               my @diff = command('diff-tree', $cmt, $c);
                if (@diff) {
                        print STDERR "Trees differ: $cmt $c\n",
                                        join('',@diff),"\n";
@@ -3098,12 +2629,11 @@ sub libsvn_commit_cb {
 
 sub libsvn_ls_fullurl {
        my $fullurl = shift;
-       my ($repo, $path) = repo_path_split($fullurl);
-       $SVN ||= libsvn_connect($repo);
+       my $ra = libsvn_connect($fullurl);
        my @ret;
        my $pool = SVN::Pool->new;
-       my ($dirent, undef, undef) = $SVN->get_dir($path,
-                                               $SVN->get_latest_revnum, $pool);
+       my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
+       my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool);
        foreach my $d (keys %$dirent) {
                if ($dirent->{$d}->kind == $SVN::Node::dir) {
                        push @ret, "$d/"; # add '/' for compat with cli svn
@@ -3124,8 +2654,9 @@ sub libsvn_skip_unknown_revs {
        # Wonderfully consistent library, eh?
        # 160013 - svn:// and file://
        # 175002 - http(s)://
+       # 175007 - http(s):// (this repo required authorization, too...)
        #   More codes may be discovered later...
-       if ($errno == 175002 || $errno == 160013) {
+       if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
                return;
        }
        croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
@@ -3177,20 +2708,225 @@ sub revdb_get {
 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");
+       if (command('ls-remote', $origin, $ref)) {
+               command_noisy('fetch', $origin, "$ref:$ref");
        } elsif ($_cp_remote && !$_upgrade) {
                die "Unable to find remote reference: ",
                                "refs/remotes/$GIT_SVN on $origin\n";
        }
 }
 
+{
+       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.
+                               $SVN::Auth::SSL::CNMISMATCH.
+                               $SVN::Auth::SSL::NOTYETVALID.
+                               $SVN::Auth::SSL::EXPIRED.
+                               $SVN::Auth::SSL::UNKNOWNCA.
+                               $SVN::Auth::SSL::OTHER;
+}
+
+package SVN::Git::Fetcher;
+use vars qw/@ISA/;
+use strict;
+use warnings;
+use Carp qw/croak/;
+use IO::File qw//;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
+
+# file baton members: path, mode_a, mode_b, pool, fh, blob, base
+sub new {
+       my ($class, $git_svn) = @_;
+       my $self = SVN::Delta::Editor->new;
+       bless $self, $class;
+       $self->{c} = $git_svn->{c} if exists $git_svn->{c};
+       $self->{q} = $git_svn->{q};
+       $self->{empty} = {};
+       $self->{dir_prop} = {};
+       $self->{file_prop} = {};
+       $self->{absent_dir} = {};
+       $self->{absent_file} = {};
+       ($self->{gui}, $self->{ctx}) = command_input_pipe(
+                                            qw/update-index -z --index-info/);
+       require Digest::MD5;
+       $self;
+}
+
+sub open_root {
+       { path => '' };
+}
+
+sub open_directory {
+       my ($self, $path, $pb, $rev) = @_;
+       { path => $path };
+}
+
+sub delete_entry {
+       my ($self, $path, $rev, $pb) = @_;
+       my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q});
+       $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir;
+       undef;
+}
+
+sub open_file {
+       my ($self, $path, $pb, $rev) = @_;
+       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path)
+                            =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
+       unless (defined $mode && defined $blob) {
+               die "$path was not found in commit $self->{c} (r$rev)\n";
+       }
+       { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
+         pool => SVN::Pool->new, action => 'M' };
+}
+
+sub add_file {
+       my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
+       my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
+       delete $self->{empty}->{$dir};
+       { path => $path, mode_a => 100644, mode_b => 100644,
+         pool => SVN::Pool->new, action => 'A' };
+}
+
+sub add_directory {
+       my ($self, $path, $cp_path, $cp_rev) = @_;
+       my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
+       delete $self->{empty}->{$dir};
+       $self->{empty}->{$path} = 1;
+       { path => $path };
+}
+
+sub change_dir_prop {
+       my ($self, $db, $prop, $value) = @_;
+       $self->{dir_prop}->{$db->{path}} ||= {};
+       $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
+       undef;
+}
+
+sub absent_directory {
+       my ($self, $path, $pb) = @_;
+       $self->{absent_dir}->{$pb->{path}} ||= [];
+       push @{$self->{absent_dir}->{$pb->{path}}}, $path;
+       undef;
+}
+
+sub absent_file {
+       my ($self, $path, $pb) = @_;
+       $self->{absent_file}->{$pb->{path}} ||= [];
+       push @{$self->{absent_file}->{$pb->{path}}}, $path;
+       undef;
+}
+
+sub change_file_prop {
+       my ($self, $fb, $prop, $value) = @_;
+       if ($prop eq 'svn:executable') {
+               if ($fb->{mode_b} != 120000) {
+                       $fb->{mode_b} = defined $value ? 100755 : 100644;
+               }
+       } elsif ($prop eq 'svn:special') {
+               $fb->{mode_b} = defined $value ? 120000 : 100644;
+       } else {
+               $self->{file_prop}->{$fb->{path}} ||= {};
+               $self->{file_prop}->{$fb->{path}}->{$prop} = $value;
+       }
+       undef;
+}
+
+sub apply_textdelta {
+       my ($self, $fb, $exp) = @_;
+       my $fh = IO::File->new_tmpfile;
+       $fh->autoflush(1);
+       # $fh gets auto-closed() by SVN::TxDelta::apply(),
+       # (but $base does not,) so dup() it for reading in close_file
+       open my $dup, '<&', $fh or croak $!;
+       my $base = IO::File->new_tmpfile;
+       $base->autoflush(1);
+       if ($fb->{blob}) {
+               defined (my $pid = fork) or croak $!;
+               if (!$pid) {
+                       open STDOUT, '>&', $base or croak $!;
+                       print STDOUT 'link ' if ($fb->{mode_a} == 120000);
+                       exec qw/git-cat-file blob/, $fb->{blob} or croak $!;
+               }
+               waitpid $pid, 0;
+               croak $? if $?;
+
+               if (defined $exp) {
+                       seek $base, 0, 0 or croak $!;
+                       my $md5 = Digest::MD5->new;
+                       $md5->addfile($base);
+                       my $got = $md5->hexdigest;
+                       die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
+                           "expected: $exp\n",
+                           "     got: $got\n" if ($got ne $exp);
+               }
+       }
+       seek $base, 0, 0 or croak $!;
+       $fb->{fh} = $dup;
+       $fb->{base} = $base;
+       [ SVN::TxDelta::apply($base, $fh, undef, $fb->{path}, $fb->{pool}) ];
+}
+
+sub close_file {
+       my ($self, $fb, $exp) = @_;
+       my $hash;
+       my $path = $fb->{path};
+       if (my $fh = $fb->{fh}) {
+               seek($fh, 0, 0) or croak $!;
+               my $md5 = Digest::MD5->new;
+               $md5->addfile($fh);
+               my $got = $md5->hexdigest;
+               die "Checksum mismatch: $path\n",
+                   "expected: $exp\n    got: $got\n" if ($got ne $exp);
+               seek($fh, 0, 0) or croak $!;
+               if ($fb->{mode_b} == 120000) {
+                       read($fh, my $buf, 5) == 5 or croak $!;
+                       $buf eq 'link ' or die "$path has mode 120000",
+                                              "but is not a link\n";
+               }
+               defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n";
+               if (!$pid) {
+                       open STDIN, '<&', $fh or croak $!;
+                       exec qw/git-hash-object -w --stdin/ or croak $!;
+               }
+               chomp($hash = do { local $/; <$out> });
+               close $out or croak $!;
+               close $fh or croak $!;
+               $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
+               close $fb->{base} or croak $!;
+       } else {
+               $hash = $fb->{blob} or die "no blob information\n";
+       }
+       $fb->{pool}->clear;
+       my $gui = $self->{gui};
+       print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!;
+       print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q};
+       undef;
+}
+
+sub abort_edit {
+       my $self = shift;
+       eval { command_close_pipe($self->{gui}, $self->{ctx}) };
+       $self->SUPER::abort_edit(@_);
+}
+
+sub close_edit {
+       my $self = shift;
+       command_close_pipe($self->{gui}, $self->{ctx});
+       $self->{git_commit_ok} = 1;
+       $self->SUPER::close_edit(@_);
+}
+
 package SVN::Git::Editor;
 use vars qw/@ISA/;
 use strict;
 use warnings;
 use Carp qw/croak/;
 use IO::File;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
 
 sub new {
        my $class = shift;
@@ -3213,8 +2949,7 @@ sub split_path {
 }
 
 sub repo_path {
-       (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]"
-                                       : $_[0]->{svn_path}
+       (defined $_[1] && length $_[1]) ? $_[1] : ''
 }
 
 sub url_path {
@@ -3241,24 +2976,21 @@ sub rmdirs {
        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 $!;
-       }
+       my ($fh, $ctx) = command_output_pipe(
+                                  qw/ls-tree --name-only -r -z/, $self->{c});
        local $/ = "\0";
-       my @svn_path = split m#/#, $self->{svn_path};
        while (<$fh>) {
                chomp;
-               my @dn = (@svn_path, (split m#/#, $_));
+               my @dn = split m#/#, $_;
                while (pop @dn) {
                        delete $rm->{join '/', @dn};
                }
                unless (%$rm) {
-                       close $fh;
+                       eval { command_close_pipe($fh) };
                        return;
                }
        }
-       close $fh;
+       command_close_pipe($fh, $ctx);
 
        my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
        foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
@@ -3433,13 +3165,7 @@ __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)
+$log_msg hashref as returned by libsvn_log_entry()
 {
        msg => 'whitespace-formatted log entry
 ',                                             # trailing newline is preserved
@@ -3448,7 +3174,6 @@ $log_msg hashref as returned by next_log_entry($svn_log)
        author => 'committer name'
 };
 
-
 @mods = array of diff-index line hashes, each element represents one line
        of diff-index output
 
index ac269e3277d9b8c844e8c43b95695375dd829746..36cd6aa256db765aa741099b8d3c63b50f58048f 100755 (executable)
@@ -5,6 +5,7 @@ USAGE='-l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [
 SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
+message_given=
 annotate=
 signed=
 force=
@@ -37,6 +38,23 @@ do
        annotate=1
        shift
        message="$1"
+       if test "$#" = "0"; then
+           die "error: option -m needs an argument"
+           exit 2
+       else
+           message_given=1
+       fi
+       ;;
+    -F)
+       annotate=1
+       shift
+       if test "$#" = "0"; then
+           die "error: option -F needs an argument"
+           exit 2
+       else
+           message="$(cat "$1")"
+           message_given=1
+       fi
        ;;
     -u)
        annotate=1
@@ -83,7 +101,7 @@ tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
 trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
 
 if [ "$annotate" ]; then
-    if [ -z "$message" ]; then
+    if [ -z "$message_given" ]; then
         ( echo "#"
           echo "# Write a tag message"
           echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
@@ -95,7 +113,7 @@ if [ "$annotate" ]; then
     grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
     git-stripspace >"$GIT_DIR"/TAG_FINALMSG
 
-    [ -s "$GIT_DIR"/TAG_FINALMSG ] || {
+    [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
        echo >&2 "No tag message?"
        exit 1
     }
diff --git a/git.c b/git.c
index 1aa07a5164012dddf2fb0dca23c08d9c487e7d54..73cf4d4e019c2c0169e6181b2c557cdeb3f962e7 100644 (file)
--- a/git.c
+++ b/git.c
@@ -1,20 +1,8 @@
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <dirent.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdarg.h>
-#include "git-compat-util.h"
+#include "builtin.h"
 #include "exec_cmd.h"
 #include "cache.h"
 #include "quote.h"
 
-#include "builtin.h"
-
 const char git_usage_string[] =
        "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
 
@@ -247,6 +235,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "ls-tree", cmd_ls_tree, RUN_SETUP },
                { "mailinfo", cmd_mailinfo },
                { "mailsplit", cmd_mailsplit },
+               { "merge-file", cmd_merge_file },
                { "mv", cmd_mv, RUN_SETUP },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
@@ -260,6 +249,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
                { "rm", cmd_rm, RUN_SETUP },
                { "runstatus", cmd_runstatus, RUN_SETUP },
+               { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
                { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
                { "stripspace", cmd_stripspace },
index 83268fc9d9e3ab63920b5ec4a5ffd71e8dd11b0d..fb95e375948ed84abd9c433294692b12e2ff1b8d 100644 (file)
@@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages.
 %package core
 Summary:       Core git tools
 Group:         Development/Tools
-Requires:      zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat
+Requires:      zlib >= 1.2, rsync, curl, less, openssh-clients, expat
 %description core
 This is a stupid (but extremely fast) directory content manager.  It
 doesn't do a whole lot, but what it _does_ do is track directory
diff --git a/gitMergeCommon.py b/gitMergeCommon.py
deleted file mode 100644 (file)
index fdbf9e4..0000000
+++ /dev/null
@@ -1,275 +0,0 @@
-#
-# Copyright (C) 2005 Fredrik Kuivinen
-#
-
-import sys, re, os, traceback
-from sets import Set
-
-def die(*args):
-    printList(args, sys.stderr)
-    sys.exit(2)
-
-def printList(list, file=sys.stdout):
-    for x in list:
-        file.write(str(x))
-        file.write(' ')
-    file.write('\n')
-
-import subprocess
-
-# Debugging machinery
-# -------------------
-
-DEBUG = 0
-functionsToDebug = Set()
-
-def addDebug(func):
-    if type(func) == str:
-        functionsToDebug.add(func)
-    else:
-        functionsToDebug.add(func.func_name)
-
-def debug(*args):
-    if DEBUG:
-        funcName = traceback.extract_stack()[-2][2]
-        if funcName in functionsToDebug:
-            printList(args)
-
-# Program execution
-# -----------------
-
-class ProgramError(Exception):
-    def __init__(self, progStr, error):
-        self.progStr = progStr
-        self.error = error
-
-    def __str__(self):
-        return self.progStr + ': ' + self.error
-
-addDebug('runProgram')
-def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True):
-    debug('runProgram prog:', str(prog), 'input:', str(input))
-    if type(prog) is str:
-        progStr = prog
-    else:
-        progStr = ' '.join(prog)
-    
-    try:
-        if pipeOutput:
-            stderr = subprocess.STDOUT
-            stdout = subprocess.PIPE
-        else:
-            stderr = None
-            stdout = None
-        pop = subprocess.Popen(prog,
-                               shell = type(prog) is str,
-                               stderr=stderr,
-                               stdout=stdout,
-                               stdin=subprocess.PIPE,
-                               env=env)
-    except OSError, e:
-        debug('strerror:', e.strerror)
-        raise ProgramError(progStr, e.strerror)
-
-    if input != None:
-        pop.stdin.write(input)
-    pop.stdin.close()
-
-    if pipeOutput:
-        out = pop.stdout.read()
-    else:
-        out = ''
-
-    code = pop.wait()
-    if returnCode:
-        ret = [out, code]
-    else:
-        ret = out
-    if code != 0 and not returnCode:
-        debug('error output:', out)
-        debug('prog:', prog)
-        raise ProgramError(progStr, out)
-#    debug('output:', out.replace('\0', '\n'))
-    return ret
-
-# Code for computing common ancestors
-# -----------------------------------
-
-currentId = 0
-def getUniqueId():
-    global currentId
-    currentId += 1
-    return currentId
-
-# The 'virtual' commit objects have SHAs which are integers
-shaRE = re.compile('^[0-9a-f]{40}$')
-def isSha(obj):
-    return (type(obj) is str and bool(shaRE.match(obj))) or \
-           (type(obj) is int and obj >= 1)
-
-class Commit(object):
-    __slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha',
-                 'virtual']
-
-    def __init__(self, sha, parents, tree=None):
-        self.parents = parents
-        self.firstLineMsg = None
-        self.children = []
-
-        if tree:
-            tree = tree.rstrip()
-            assert(isSha(tree))
-        self._tree = tree
-
-        if not sha:
-            self.sha = getUniqueId()
-            self.virtual = True
-            self.firstLineMsg = 'virtual commit'
-            assert(isSha(tree))
-        else:
-            self.virtual = False
-            self.sha = sha.rstrip()
-        assert(isSha(self.sha))
-
-    def tree(self):
-        self.getInfo()
-        assert(self._tree != None)
-        return self._tree
-
-    def shortInfo(self):
-        self.getInfo()
-        return str(self.sha) + ' ' + self.firstLineMsg
-
-    def __str__(self):
-        return self.shortInfo()
-
-    def getInfo(self):
-        if self.virtual or self.firstLineMsg != None:
-            return
-        else:
-            info = runProgram(['git-cat-file', 'commit', self.sha])
-            info = info.split('\n')
-            msg = False
-            for l in info:
-                if msg:
-                    self.firstLineMsg = l
-                    break
-                else:
-                    if l.startswith('tree'):
-                        self._tree = l[5:].rstrip()
-                    elif l == '':
-                        msg = True
-
-class Graph:
-    def __init__(self):
-        self.commits = []
-        self.shaMap = {}
-
-    def addNode(self, node):
-        assert(isinstance(node, Commit))
-        self.shaMap[node.sha] = node
-        self.commits.append(node)
-        for p in node.parents:
-            p.children.append(node)
-        return node
-
-    def reachableNodes(self, n1, n2):
-        res = {}
-        def traverse(n):
-            res[n] = True
-            for p in n.parents:
-                traverse(p)
-
-        traverse(n1)
-        traverse(n2)
-        return res
-
-    def fixParents(self, node):
-        for x in range(0, len(node.parents)):
-            node.parents[x] = self.shaMap[node.parents[x]]
-
-# addDebug('buildGraph')
-def buildGraph(heads):
-    debug('buildGraph heads:', heads)
-    for h in heads:
-        assert(isSha(h))
-
-    g = Graph()
-
-    out = runProgram(['git-rev-list', '--parents'] + heads)
-    for l in out.split('\n'):
-        if l == '':
-            continue
-        shas = l.split(' ')
-
-        # This is a hack, we temporarily use the 'parents' attribute
-        # to contain a list of SHA1:s. They are later replaced by proper
-        # Commit objects.
-        c = Commit(shas[0], shas[1:])
-
-        g.commits.append(c)
-        g.shaMap[c.sha] = c
-
-    for c in g.commits:
-        g.fixParents(c)
-
-    for c in g.commits:
-        for p in c.parents:
-            p.children.append(c)
-    return g
-
-# Write the empty tree to the object database and return its SHA1
-def writeEmptyTree():
-    tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index'
-    def delTmpIndex():
-        try:
-            os.unlink(tmpIndex)
-        except OSError:
-            pass
-    delTmpIndex()
-    newEnv = os.environ.copy()
-    newEnv['GIT_INDEX_FILE'] = tmpIndex
-    res = runProgram(['git-write-tree'], env=newEnv).rstrip()
-    delTmpIndex()
-    return res
-
-def addCommonRoot(graph):
-    roots = []
-    for c in graph.commits:
-        if len(c.parents) == 0:
-            roots.append(c)
-
-    superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree())
-    graph.addNode(superRoot)
-    for r in roots:
-        r.parents = [superRoot]
-    superRoot.children = roots
-    return superRoot
-
-def getCommonAncestors(graph, commit1, commit2):
-    '''Find the common ancestors for commit1 and commit2'''
-    assert(isinstance(commit1, Commit) and isinstance(commit2, Commit))
-
-    def traverse(start, set):
-        stack = [start]
-        while len(stack) > 0:
-            el = stack.pop()
-            set.add(el)
-            for p in el.parents:
-                if p not in set:
-                    stack.append(p)
-    h1Set = Set()
-    h2Set = Set()
-    traverse(commit1, h1Set)
-    traverse(commit2, h2Set)
-    shared = h1Set.intersection(h2Set)
-
-    if len(shared) == 0:
-        shared = [addCommonRoot(graph)]
-        
-    res = Set()
-
-    for s in shared:
-        if len([c for c in s.children if c in shared]) == 0:
-            res.add(s)
-    return list(res)
diff --git a/gitk b/gitk
index ab383b3ad29cea5c7c45a8f757941acbc3abaf33..3dabc695167dc33c6655efbabbd9a0c3e870ccda 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -554,7 +554,7 @@ proc makewindow {} {
     pack .ctop.top.lbar.vlabel -side left -fill y
     global viewhlmenu selectedhlview
     set viewhlmenu [tk_optionMenu .ctop.top.lbar.vhl selectedhlview None]
-    $viewhlmenu entryconf 0 -command delvhighlight
+    $viewhlmenu entryconf None -command delvhighlight
     $viewhlmenu conf -font $uifont
     .ctop.top.lbar.vhl conf -font $uifont
     pack .ctop.top.lbar.vhl -side left -fill y
@@ -1474,7 +1474,7 @@ proc doviewmenu {m first cmd op argv} {
 proc allviewmenus {n op args} {
     global viewhlmenu
 
-    doviewmenu .bar.view 7 [list showview $n] $op $args
+    doviewmenu .bar.view 5 [list showview $n] $op $args
     doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
 }
 
@@ -1516,7 +1516,7 @@ proc newviewok {top n} {
        set viewperm($n) $newviewperm($n)
        if {$newviewname($n) ne $viewname($n)} {
            set viewname($n) $newviewname($n)
-           doviewmenu .bar.view 7 [list showview $n] \
+           doviewmenu .bar.view 5 [list showview $n] \
                entryconf [list -label $viewname($n)]
            doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
                entryconf [list -label $viewname($n) -value $viewname($n)]
@@ -1632,8 +1632,8 @@ proc showview {n} {
 
     set curview $n
     set selectedview $n
-    .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
-    .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
 
     if {![info exists viewdata($n)]} {
        set pending_select $selid
@@ -4899,9 +4899,9 @@ proc rowmenu {x y id} {
     } else {
        set state normal
     }
-    $rowctxmenu entryconfigure 0 -state $state
-    $rowctxmenu entryconfigure 1 -state $state
-    $rowctxmenu entryconfigure 2 -state $state
+    $rowctxmenu entryconfigure "Diff this*" -state $state
+    $rowctxmenu entryconfigure "Diff selected*" -state $state
+    $rowctxmenu entryconfigure "Make patch" -state $state
     set rowmenuid $id
     tk_popup $rowctxmenu $x $y
 }
@@ -6305,8 +6305,8 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} {
     set viewargs(1) $revtreeargs
     set viewperm(1) 0
     addviewmenu 1
-    .bar.view entryconf 2 -state normal
-    .bar.view entryconf 3 -state normal
+    .bar.view entryconf Edit* -state normal
+    .bar.view entryconf Delete* -state normal
 }
 
 if {[info exists permviews]} {
index 974b47f19ca61bd5d12ec88229957c3434d23265..7177c6e86b8e8c3dc1e5d0db4bce4122cbe2430a 100644 (file)
@@ -334,11 +334,13 @@ div.diff.extended_header {
        padding: 2px 0px 2px 0px;
 }
 
+div.diff a.list,
 div.diff a.path,
 div.diff a.hash {
        text-decoration: none;
 }
 
+div.diff a.list:hover,
 div.diff a.path:hover,
 div.diff a.hash:hover {
        text-decoration: underline;
@@ -362,14 +364,25 @@ div.diff.rem {
        color: #cc0000;
 }
 
+div.diff.chunk_header a,
 div.diff.chunk_header {
        color: #990099;
+}
 
+div.diff.chunk_header {
        border: dotted #ffe0ff;
        border-width: 1px 0px 0px 0px;
        margin-top: 2px;
 }
 
+div.diff.chunk_header span.chunk_info {
+       background-color: #ffeeff;
+}
+
+div.diff.chunk_header span.section {
+       color: #aa22aa;
+}
+
 div.diff.incomplete {
        color: #cccccc;
 }
index 758759576cbbac596b15dd4940aca24ff6dbdd84..ebbc397ee8dce807bd9700aa72d33c80b13f02bf 100755 (executable)
@@ -120,7 +120,7 @@ our %feature = (
        # To disable system wide have in $GITWEB_CONFIG
        # $feature{'snapshot'}{'default'} = [undef];
        # To have project specific config enable override in $GITWEB_CONFIG
-       # $feature{'blame'}{'override'} = 1;
+       # $feature{'snapshot'}{'override'} = 1;
        # and in project config gitweb.snapshot = none|gzip|bzip2;
        'snapshot' => {
                'sub' => \&feature_snapshot,
@@ -425,6 +425,7 @@ my %actions = (
        "history" => \&git_history,
        "log" => \&git_log,
        "rss" => \&git_rss,
+       "atom" => \&git_atom,
        "search" => \&git_search,
        "search_help" => \&git_search_help,
        "shortlog" => \&git_shortlog,
@@ -433,6 +434,7 @@ my %actions = (
        "tags" => \&git_tags,
        "tree" => \&git_tree,
        "snapshot" => \&git_snapshot,
+       "object" => \&git_object,
        # those below don't need $project
        "opml" => \&git_opml,
        "project_list" => \&git_project_list,
@@ -459,7 +461,8 @@ exit;
 
 sub href(%) {
        my %params = @_;
-       my $href = $my_uri;
+       # default is to use -absolute url() i.e. $my_uri
+       my $href = $params{-full} ? $my_url : $my_uri;
 
        # XXX: Warning: If you touch this, check the search form for updating,
        # too.
@@ -583,7 +586,21 @@ sub esc_html ($;%) {
        return $str;
 }
 
-# Make control characterss "printable".
+# quote control characters and escape filename to HTML
+sub esc_path {
+       my $str = shift;
+       my %opts = @_;
+
+       $str = to_utf8($str);
+       $str = escapeHTML($str);
+       if ($opts{'-nbsp'}) {
+               $str =~ s/ /&nbsp;/g;
+       }
+       $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
+       return $str;
+}
+
+# Make control characters "printable", using character escape codes (CEC)
 sub quot_cec {
        my $cntrl = shift;
        my %es = ( # character escape codes, aka escape sequences
@@ -603,22 +620,14 @@ sub quot_cec {
        return "<span class=\"cntrl\">$chr</span>";
 }
 
-# Alternatively use unicode control pictures codepoints.
+# Alternatively use unicode control pictures codepoints,
+# Unicode "printable representation" (PR)
 sub quot_upr {
        my $cntrl = shift;
        my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
        return "<span class=\"cntrl\">$chr</span>";
 }
 
-# quote control characters and escape filename to HTML
-sub esc_path {
-       my $str = shift;
-
-       $str = esc_html($str);
-       $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
-       return $str;
-}
-
 # git may return quoted and escaped filenames
 sub unquote {
        my $str = shift;
@@ -819,14 +828,12 @@ sub format_log_line_html {
        my $line = shift;
 
        $line = esc_html($line, -nbsp=>1);
-       if ($line =~ m/([0-9a-fA-F]{40})/) {
+       if ($line =~ m/([0-9a-fA-F]{8,40})/) {
                my $hash_text = $1;
-               if (git_get_type($hash_text) eq "commit") {
-                       my $link =
-                               $cgi->a({-href => href(action=>"commit", hash=>$hash_text),
-                                       -class => "text"}, $hash_text);
-                       $line =~ s/$hash_text/$link/;
-               }
+               my $link =
+                       $cgi->a({-href => href(action=>"object", hash=>$hash_text),
+                               -class => "text"}, $hash_text);
+               $line =~ s/$hash_text/$link/;
        }
        return $line;
 }
@@ -848,7 +855,8 @@ sub format_ref_marker {
                                $name = $ref;
                        }
 
-                       $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>";
+                       $markers .= " <span class=\"$type\" title=\"$ref\">" .
+                                   esc_html($name) . "</span>";
                }
        }
 
@@ -874,8 +882,10 @@ sub format_subject_html {
        }
 }
 
+# format patch (diff) line (rather not to be used for diff headers)
 sub format_diff_line {
        my $line = shift;
+       my ($from, $to) = @_;
        my $char = substr($line, 0, 1);
        my $diff_class = "";
 
@@ -891,6 +901,25 @@ sub format_diff_line {
                $diff_class = " incomplete";
        }
        $line = untabify($line);
+       if ($from && $to && $line =~ m/^\@{2} /) {
+               my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
+                       $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
+
+               $from_lines = 0 unless defined $from_lines;
+               $to_lines   = 0 unless defined $to_lines;
+
+               if ($from->{'href'}) {
+                       $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
+                                            -class=>"list"}, $from_text);
+               }
+               if ($to->{'href'}) {
+                       $to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
+                                            -class=>"list"}, $to_text);
+               }
+               $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
+                       "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+               return "<div class=\"diff$diff_class\">$line</div>\n";
+       }
        return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
 }
 
@@ -1125,14 +1154,15 @@ sub git_get_last_activity {
 sub git_get_references {
        my $type = shift || "";
        my %refs;
-       # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c      refs/tags/v2.6.11
-       # c39ae07f393806ccf406ef966e9a15afc43cc36a      refs/tags/v2.6.11^{}
-       open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+       # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
+       # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
+       open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
+               ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
                or return;
 
        while (my $line = <$fd>) {
                chomp $line;
-               if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) {
+               if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
                        if (defined $refs{$1}) {
                                push @{$refs{$1}}, $2;
                        } else {
@@ -1176,10 +1206,12 @@ sub parse_date {
        $date{'mday'} = $mday;
        $date{'day'} = $days[$wday];
        $date{'month'} = $months[$mon];
-       $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
-                          $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
+       $date{'rfc2822'}   = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
+                            $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
        $date{'mday-time'} = sprintf "%d %s %02d:%02d",
                             $mday, $months[$mon], $hour ,$min;
+       $date{'iso-8601'}  = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
+                            1900+$year, $mon, $mday, $hour ,$min, $sec;
 
        $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
        my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@@ -1187,9 +1219,9 @@ sub parse_date {
        $date{'hour_local'} = $hour;
        $date{'minute_local'} = $min;
        $date{'tz_local'} = $tz;
-       $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
-                                  1900+$year, $mon+1, $mday,
-                                  $hour, $min, $sec, $tz);
+       $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
+                                 1900+$year, $mon+1, $mday,
+                                 $hour, $min, $sec, $tz);
        return %date;
 }
 
@@ -1262,8 +1294,9 @@ sub parse_commit {
                        $co{'author'} = $1;
                        $co{'author_epoch'} = $2;
                        $co{'author_tz'} = $3;
-                       if ($co{'author'} =~ m/^([^<]+) </) {
-                               $co{'author_name'} = $1;
+                       if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
+                               $co{'author_name'}  = $1;
+                               $co{'author_email'} = $2;
                        } else {
                                $co{'author_name'} = $co{'author'};
                        }
@@ -1272,7 +1305,12 @@ sub parse_commit {
                        $co{'committer_epoch'} = $2;
                        $co{'committer_tz'} = $3;
                        $co{'committer_name'} = $co{'committer'};
-                       $co{'committer_name'} =~ s/ <.*//;
+                       if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
+                               $co{'committer_name'}  = $1;
+                               $co{'committer_email'} = $2;
+                       } else {
+                               $co{'committer_name'} = $co{'committer'};
+                       }
                }
        }
        if (!defined $co{'tree'}) {
@@ -1650,14 +1688,17 @@ EOF
                }
        }
        if (defined $project) {
-               printf('<link rel="alternate" title="%s log" '.
-                      'href="%s" type="application/rss+xml"/>'."\n",
+               printf('<link rel="alternate" title="%s log RSS feed" '.
+                      'href="%s" type="application/rss+xml" />'."\n",
                       esc_param($project), href(action=>"rss"));
+               printf('<link rel="alternate" title="%s log Atom feed" '.
+                      'href="%s" type="application/atom+xml" />'."\n",
+                      esc_param($project), href(action=>"atom"));
        } else {
                printf('<link rel="alternate" title="%s projects list" '.
                       'href="%s" type="text/plain; charset=utf-8"/>'."\n",
                       $site_name, href(project=>undef, action=>"project_index"));
-               printf('<link rel="alternate" title="%s projects logs" '.
+               printf('<link rel="alternate" title="%s projects feeds" '.
                       'href="%s" type="text/x-opml"/>'."\n",
                       $site_name, href(project=>undef, action=>"opml"));
        }
@@ -1723,7 +1764,9 @@ sub git_footer_html {
                        print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
                }
                print $cgi->a({-href => href(action=>"rss"),
-                             -class => "rss_logo"}, "RSS") . "\n";
+                             -class => "rss_logo"}, "RSS") . " ";
+               print $cgi->a({-href => href(action=>"atom"),
+                             -class => "rss_logo"}, "Atom") . "\n";
        } else {
                print $cgi->a({-href => href(project=>undef, action=>"opml"),
                              -class => "rss_logo"}, "OPML") . " ";
@@ -1946,12 +1989,73 @@ sub git_print_log ($;%) {
        }
 }
 
+# return link target (what link points to)
+sub git_get_link_target {
+       my $hash = shift;
+       my $link_target;
+
+       # read link
+       open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
+               or return;
+       {
+               local $/;
+               $link_target = <$fd>;
+       }
+       close $fd
+               or return;
+
+       return $link_target;
+}
+
+# given link target, and the directory (basedir) the link is in,
+# return target of link relative to top directory (top tree);
+# return undef if it is not possible (including absolute links).
+sub normalize_link_target {
+       my ($link_target, $basedir, $hash_base) = @_;
+
+       # we can normalize symlink target only if $hash_base is provided
+       return unless $hash_base;
+
+       # absolute symlinks (beginning with '/') cannot be normalized
+       return if (substr($link_target, 0, 1) eq '/');
+
+       # normalize link target to path from top (root) tree (dir)
+       my $path;
+       if ($basedir) {
+               $path = $basedir . '/' . $link_target;
+       } else {
+               # we are in top (root) tree (dir)
+               $path = $link_target;
+       }
+
+       # remove //, /./, and /../
+       my @path_parts;
+       foreach my $part (split('/', $path)) {
+               # discard '.' and ''
+               next if (!$part || $part eq '.');
+               # handle '..'
+               if ($part eq '..') {
+                       if (@path_parts) {
+                               pop @path_parts;
+                       } else {
+                               # link leads outside repository (outside top dir)
+                               return;
+                       }
+               } else {
+                       push @path_parts, $part;
+               }
+       }
+       $path = join('/', @path_parts);
+
+       return $path;
+}
+
 # print tree entry (row of git_tree), but without encompassing <tr> element
 sub git_print_tree_entry {
        my ($t, $basedir, $hash_base, $have_blame) = @_;
 
        my %base_key = ();
-       $base_key{hash_base} = $hash_base if defined $hash_base;
+       $base_key{'hash_base'} = $hash_base if defined $hash_base;
 
        # The format of a table row is: mode list link.  Where mode is
        # the mode of the entry, list is the name of the entry, an href,
@@ -1962,16 +2066,31 @@ sub git_print_tree_entry {
                print "<td class=\"list\">" .
                        $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
                                               file_name=>"$basedir$t->{'name'}", %base_key),
-                               -class => "list"}, esc_path($t->{'name'})) . "</td>\n";
+                               -class => "list"}, esc_path($t->{'name'}));
+               if (S_ISLNK(oct $t->{'mode'})) {
+                       my $link_target = git_get_link_target($t->{'hash'});
+                       if ($link_target) {
+                               my $norm_target = normalize_link_target($link_target, $basedir, $hash_base);
+                               if (defined $norm_target) {
+                                       print " -> " .
+                                             $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
+                                                                    file_name=>$norm_target),
+                                                      -title => $norm_target}, esc_path($link_target));
+                               } else {
+                                       print " -> " . esc_path($link_target);
+                               }
+                       }
+               }
+               print "</td>\n";
                print "<td class=\"link\">";
                print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
-                             "blob");
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "blob");
                if ($have_blame) {
                        print " | " .
                              $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
-                                                          file_name=>"$basedir$t->{'name'}", %base_key)},
-                                           "blame");
+                                                    file_name=>"$basedir$t->{'name'}", %base_key)},
+                                     "blame");
                }
                if (defined $hash_base) {
                        print " | " .
@@ -1993,8 +2112,8 @@ sub git_print_tree_entry {
                print "</td>\n";
                print "<td class=\"link\">";
                print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
-                             "tree");
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "tree");
                if (defined $hash_base) {
                        print " | " .
                              $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
@@ -2062,7 +2181,11 @@ sub git_difftree_body {
                                # link to patch
                                $patchno++;
                                print $cgi->a({-href => "#patch$patchno"}, "patch");
+                               print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                                     "blob") . " | ";
                        print "</td>\n";
 
                } elsif ($diff{'status'} eq "D") { # deleted
@@ -2082,13 +2205,11 @@ sub git_difftree_body {
                        }
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
                                                     hash_base=>$parent, file_name=>$diff{'file'})},
-                                     "blob") . " | ";
+                                     "blob") . " | ";
                        if ($have_blame) {
-                               print $cgi->a({-href =>
-                                                  href(action=>"blame",
-                                                       hash_base=>$parent,
-                                                       file_name=>$diff{'file'})},
-                                             "blame") . " | ";
+                               print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+                                                            file_name=>$diff{'file'})},
+                                             "blame") . " | ";
                        }
                        print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
                                                     file_name=>$diff{'file'})},
@@ -2133,13 +2254,12 @@ sub git_difftree_body {
                                      " | ";
                        }
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'})},
-                                     "blob") . " | ";
+                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                                      "blob") . " | ";
                        if ($have_blame) {
-                               print $cgi->a({-href => href(action=>"blame",
-                                                            hash_base=>$hash,
-                                                            file_name=>$diff{'file'})},
-                                             "blame") . " | ";
+                               print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+                                                            file_name=>$diff{'file'})},
+                                             "blame") . " | ";
                        }
                        print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
                                                     file_name=>$diff{'file'})},
@@ -2178,17 +2298,16 @@ sub git_difftree_body {
                                              "diff") .
                                      " | ";
                        }
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
-                                                    hash_base=>$parent, file_name=>$diff{'from_file'})},
-                                     "blob") . " | ";
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                                                    hash_base=>$parent, file_name=>$diff{'to_file'})},
+                                     "blob") . " | ";
                        if ($have_blame) {
-                               print $cgi->a({-href => href(action=>"blame",
-                                                            hash_base=>$hash,
-                                                            file_name=>$diff{'to_file'})},
-                                             "blame") . " | ";
+                               print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+                                                            file_name=>$diff{'to_file'})},
+                                             "blame") . " | ";
                        }
-                       print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
-                                                   file_name=>$diff{'from_file'})},
+                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
+                                                   file_name=>$diff{'to_file'})},
                                      "history");
                        print "</td>\n";
 
@@ -2202,31 +2321,56 @@ sub git_patchset_body {
        my ($fd, $difftree, $hash, $hash_parent) = @_;
 
        my $patch_idx = 0;
-       my $in_header = 0;
-       my $patch_found = 0;
+       my $patch_line;
        my $diffinfo;
        my (%from, %to);
+       my ($from_id, $to_id);
 
        print "<div class=\"patchset\">\n";
 
-       LINE:
-       while (my $patch_line = <$fd>) {
+       # skip to first patch
+       while ($patch_line = <$fd>) {
                chomp $patch_line;
 
-               if ($patch_line =~ m/^diff /) { # "git diff" header
-                       # beginning of patch (in patchset)
-                       if ($patch_found) {
-                               # close extended header for previous empty patch
-                               if ($in_header) {
-                                       print "</div>\n" # class="diff extended_header"
-                               }
-                               # close previous patch
-                               print "</div>\n"; # class="patch"
-                       } else {
-                               # first patch in patchset
-                               $patch_found = 1;
+               last if ($patch_line =~ m/^diff /);
+       }
+
+ PATCH:
+       while ($patch_line) {
+               my @diff_header;
+
+               # git diff header
+               #assert($patch_line =~ m/^diff /) if DEBUG;
+               #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
+               push @diff_header, $patch_line;
+
+               # extended diff header
+       EXTENDED_HEADER:
+               while ($patch_line = <$fd>) {
+                       chomp $patch_line;
+
+                       last EXTENDED_HEADER if ($patch_line =~ m/^--- /);
+
+                       if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
+                               $from_id = $1;
+                               $to_id   = $2;
                        }
-                       print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
+
+                       push @diff_header, $patch_line;
+               }
+               #last PATCH unless $patch_line;
+               my $last_patch_line = $patch_line;
+
+               # check if current patch belong to current raw line
+               # and parse raw git-diff line if needed
+               if (defined $diffinfo &&
+                   $diffinfo->{'from_id'} eq $from_id &&
+                   $diffinfo->{'to_id'}   eq $to_id) {
+                       # this is split patch
+                       print "<div class=\"patch cont\">\n";
+               } else {
+                       # advance raw git-diff output if needed
+                       $patch_idx++ if defined $diffinfo;
 
                        # read and prepare patch information
                        if (ref($difftree->[$patch_idx]) eq "HASH") {
@@ -2247,100 +2391,112 @@ sub git_patchset_body {
                                                   hash=>$diffinfo->{'to_id'},
                                                   file_name=>$to{'file'});
                        }
-                       $patch_idx++;
-
-                       # print "git diff" header
-                       $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
-                       if ($from{'href'}) {
-                               $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
-                                                      'a/' . esc_path($from{'file'}));
-                       } else { # file was added
-                               $patch_line .= 'a/' . esc_path($from{'file'});
-                       }
-                       $patch_line .= ' ';
-                       if ($to{'href'}) {
-                               $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
-                                                      'b/' . esc_path($to{'file'}));
-                       } else { # file was deleted
-                               $patch_line .= 'b/' . esc_path($to{'file'});
-                       }
-
-                       print "<div class=\"diff header\">$patch_line</div>\n";
-                       print "<div class=\"diff extended_header\">\n";
-                       $in_header = 1;
-                       next LINE;
+                       # this is first patch for raw difftree line with $patch_idx index
+                       # we index @$difftree array from 0, but number patches from 1
+                       print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
                }
 
-               if ($in_header) {
-                       if ($patch_line !~ m/^---/) {
-                               # match <path>
-                               if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
-                                       $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
-                                                               esc_path($from{'file'}));
-                               }
-                               if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
-                                       $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"},
-                                                             esc_path($to{'file'}));
-                               }
-                               # match <mode>
-                               if ($patch_line =~ m/\s(\d{6})$/) {
-                                       $patch_line .= '<span class="info"> (' .
-                                                      file_type_long($1) .
-                                                      ')</span>';
+               # print "git diff" header
+               $patch_line = shift @diff_header;
+               $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
+               if ($from{'href'}) {
+                       $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
+                                              'a/' . esc_path($from{'file'}));
+               } else { # file was added
+                       $patch_line .= 'a/' . esc_path($from{'file'});
+               }
+               $patch_line .= ' ';
+               if ($to{'href'}) {
+                       $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
+                                              'b/' . esc_path($to{'file'}));
+               } else { # file was deleted
+                       $patch_line .= 'b/' . esc_path($to{'file'});
+               }
+               print "<div class=\"diff header\">$patch_line</div>\n";
+
+               # print extended diff header
+               print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
+       EXTENDED_HEADER:
+               foreach $patch_line (@diff_header) {
+                       # match <path>
+                       if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
+                               $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
+                                                       esc_path($from{'file'}));
+                       }
+                       if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
+                               $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"},
+                                                     esc_path($to{'file'}));
+                       }
+                       # match <mode>
+                       if ($patch_line =~ m/\s(\d{6})$/) {
+                               $patch_line .= '<span class="info"> (' .
+                                              file_type_long($1) .
+                                              ')</span>';
+                       }
+                       # match <hash>
+                       if ($patch_line =~ m/^index/) {
+                               my ($from_link, $to_link);
+                               if ($from{'href'}) {
+                                       $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
+                                                            substr($diffinfo->{'from_id'},0,7));
+                               } else {
+                                       $from_link = '0' x 7;
                                }
-                               # match <hash>
-                               if ($patch_line =~ m/^index/) {
-                                       my ($from_link, $to_link);
-                                       if ($from{'href'}) {
-                                               $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
-                                                                    substr($diffinfo->{'from_id'},0,7));
-                                       } else {
-                                               $from_link = '0' x 7;
-                                       }
-                                       if ($to{'href'}) {
-                                               $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
-                                                                  substr($diffinfo->{'to_id'},0,7));
-                                       } else {
-                                               $to_link = '0' x 7;
-                                       }
-                                       my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
-                                       $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+                               if ($to{'href'}) {
+                                       $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
+                                                          substr($diffinfo->{'to_id'},0,7));
+                               } else {
+                                       $to_link = '0' x 7;
                                }
-                               print $patch_line . "<br/>\n";
-
-                       } else {
-                               #$in_header && $patch_line =~ m/^---/;
-                               print "</div>\n"; # class="diff extended_header"
-                               $in_header = 0;
+                               #affirm {
+                               #       my ($from_hash, $to_hash) =
+                               #               ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/);
+                               #       my ($from_id, $to_id) =
+                               #               ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+                               #       ($from_hash eq $from_id) && ($to_hash eq $to_id);
+                               #} if DEBUG;
+                               my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+                               $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+                       }
+                       print $patch_line . "<br/>\n";
+               }
+               print "</div>\n"  if (@diff_header > 0); # class="diff extended_header"
+
+               # from-file/to-file diff header
+               $patch_line = $last_patch_line;
+               #assert($patch_line =~ m/^---/) if DEBUG;
+               if ($from{'href'}) {
+                       $patch_line = '--- a/' .
+                                     $cgi->a({-href=>$from{'href'}, -class=>"path"},
+                                             esc_path($from{'file'}));
+               }
+               print "<div class=\"diff from_file\">$patch_line</div>\n";
 
-                               if ($from{'href'}) {
-                                       $patch_line = '--- a/' .
-                                                     $cgi->a({-href=>$from{'href'}, -class=>"path"},
-                                                             esc_path($from{'file'}));
-                               }
-                               print "<div class=\"diff from_file\">$patch_line</div>\n";
+               $patch_line = <$fd>;
+               #last PATCH unless $patch_line;
+               chomp $patch_line;
 
-                               $patch_line = <$fd>;
-                               chomp $patch_line;
+               #assert($patch_line =~ m/^+++/) if DEBUG;
+               if ($to{'href'}) {
+                       $patch_line = '+++ b/' .
+                                     $cgi->a({-href=>$to{'href'}, -class=>"path"},
+                                             esc_path($to{'file'}));
+               }
+               print "<div class=\"diff to_file\">$patch_line</div>\n";
 
-                               #$patch_line =~ m/^+++/;
-                               if ($to{'href'}) {
-                                       $patch_line = '+++ b/' .
-                                                     $cgi->a({-href=>$to{'href'}, -class=>"path"},
-                                                             esc_path($to{'file'}));
-                               }
-                               print "<div class=\"diff to_file\">$patch_line</div>\n";
+               # the patch itself
+       LINE:
+               while ($patch_line = <$fd>) {
+                       chomp $patch_line;
 
-                       }
+                       next PATCH if ($patch_line =~ m/^diff /);
 
-                       next LINE;
+                       print format_diff_line($patch_line, \%from, \%to);
                }
 
-               print format_diff_line($patch_line);
+       } continue {
+               print "</div>\n"; # class="patch"
        }
-       print "</div>\n" if $in_header; # extended header
-
-       print "</div>\n" if $patch_found; # class="patch"
 
        print "</div>\n"; # class="patchset"
 }
@@ -2361,6 +2517,7 @@ sub git_project_list_body {
                ($pr->{'age'}, $pr->{'age_string'}) = @aa;
                if (!defined $pr->{'descr'}) {
                        my $descr = git_get_project_description($pr->{'path'}) || "";
+                       $pr->{'descr_long'} = to_utf8($descr);
                        $pr->{'descr'} = chop_str($descr, 25, 5);
                }
                if (!defined $pr->{'owner'}) {
@@ -2396,7 +2553,7 @@ sub git_project_list_body {
                } else {
                        print "<th>" .
                              $cgi->a({-href => href(project=>undef, order=>'project'),
-                                      -class => "header"}, "Project") .
+                                      -class => "header"}, "Project") .
                              "</th>\n";
                }
                if ($order eq "descr") {
@@ -2405,7 +2562,7 @@ sub git_project_list_body {
                } else {
                        print "<th>" .
                              $cgi->a({-href => href(project=>undef, order=>'descr'),
-                                      -class => "header"}, "Description") .
+                                      -class => "header"}, "Description") .
                              "</th>\n";
                }
                if ($order eq "owner") {
@@ -2414,7 +2571,7 @@ sub git_project_list_body {
                } else {
                        print "<th>" .
                              $cgi->a({-href => href(project=>undef, order=>'owner'),
-                                      -class => "header"}, "Owner") .
+                                      -class => "header"}, "Owner") .
                              "</th>\n";
                }
                if ($order eq "age") {
@@ -2423,7 +2580,7 @@ sub git_project_list_body {
                } else {
                        print "<th>" .
                              $cgi->a({-href => href(project=>undef, order=>'age'),
-                                      -class => "header"}, "Last Change") .
+                                      -class => "header"}, "Last Change") .
                              "</th>\n";
                }
                print "<th></th>\n" .
@@ -2448,7 +2605,9 @@ sub git_project_list_body {
                }
                print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
                                        -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
-                     "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+                     "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+                                       -class => "list", -title => $pr->{'descr_long'}},
+                                       esc_html($pr->{'descr'})) . "</td>\n" .
                      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
                print "<td class=\"". age_class($pr->{'age'}) . "\">" .
                      $pr->{'age_string'} . "</td>\n" .
@@ -2756,8 +2915,10 @@ sub git_summary {
        my $owner = git_get_project_owner($project);
 
        my $refs = git_get_references();
-       my @taglist  = git_get_tags_list(15);
-       my @headlist = git_get_heads_list(15);
+       # These get_*_list functions return one more to allow us to see if
+       # there are more ...
+       my @taglist  = git_get_tags_list(16);
+       my @headlist = git_get_heads_list(16);
        my @forklist;
        my ($check_forks) = gitweb_check_feature('forks');
 
@@ -2793,6 +2954,8 @@ sub git_summary {
                }
        }
 
+       # we need to request one more than 16 (0..15) to check if
+       # those 16 are all
        open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
                git_get_head_hash($project), "--"
                or die_error(undef, "Open git-rev-list failed");
@@ -2800,17 +2963,20 @@ sub git_summary {
        close $fd;
        git_print_header_div('shortlog');
        git_shortlog_body(\@revlist, 0, 15, $refs,
+                         $#revlist <=  15 ? undef :
                          $cgi->a({-href => href(action=>"shortlog")}, "..."));
 
        if (@taglist) {
                git_print_header_div('tags');
                git_tags_body(\@taglist, 0, 15,
+                             $#taglist <=  15 ? undef :
                              $cgi->a({-href => href(action=>"tags")}, "..."));
        }
 
        if (@headlist) {
                git_print_header_div('heads');
                git_heads_body(\@headlist, $head, 0, 15,
+                              $#headlist <= 15 ? undef :
                               $cgi->a({-href => href(action=>"heads")}, "..."));
        }
 
@@ -2851,8 +3017,8 @@ sub git_tag {
        print "<div class=\"page_body\">";
        my $comment = $tag{'comment'};
        foreach my $line (@$comment) {
-               chomp($line);
-               print esc_html($line) . "<br/>\n";
+               chomp $line;
+               print esc_html($line, -nbsp=>1) . "<br/>\n";
        }
        print "</div>\n";
        git_footer_html();
@@ -2921,7 +3087,7 @@ HTML
                        }
                }
                my $data = $_;
-               chomp($data);
+               chomp $data;
                my $rev = substr($full_rev, 0, 8);
                my $author = $meta->{'author'};
                my %date = parse_date($meta->{'author-time'},
@@ -3146,10 +3312,13 @@ sub git_blob {
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(undef, "Couldn't cat $file_name, $hash");
        my $mimetype = blob_mimetype($fd, $file_name);
-       if ($mimetype !~ m/^text\//) {
+       if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)!) {
                close $fd;
                return git_blob_plain($mimetype);
        }
+       # we can have blame only for text/* mimetype
+       $have_blame &&= ($mimetype =~ m!^text/!);
+
        git_header_html(undef, $expires);
        my $formats_nav = '';
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -3186,13 +3355,24 @@ sub git_blob {
        }
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
-       my $nr;
-       while (my $line = <$fd>) {
-               chomp $line;
-               $nr++;
-               $line = untabify($line);
-               printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                      $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+       if ($mimetype =~ m!^text/!) {
+               my $nr;
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       $nr++;
+                       $line = untabify($line);
+                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
+                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+               }
+       } elsif ($mimetype =~ m!^image/!) {
+               print qq!<img type="$mimetype"!;
+               if ($file_name) {
+                       print qq! alt="$file_name" title="$file_name"!;
+               }
+               print qq! src="! .
+                     href(action=>"blob_plain", hash=>$hash,
+                          hash_base=>$hash_base, file_name=>$file_name) .
+                     qq!" />\n!;
        }
        close $fd
                or print "Reading blob failed.\n";
@@ -3317,8 +3497,7 @@ sub git_snapshot {
        my $filename = basename($project) . "-$hash.tar.$suffix";
 
        print $cgi->header(
-               -type => 'application/x-tar',
-               -content_encoding => $ctype,
+               -type => "application/$ctype",
                -content_disposition => 'inline; filename="' . "$filename" . '"',
                -status => '200 OK');
 
@@ -3392,6 +3571,7 @@ sub git_log {
 }
 
 sub git_commit {
+       $hash ||= $hash_base || "HEAD";
        my %co = parse_commit($hash);
        if (!%co) {
                die_error(undef, "Unknown commit object");
@@ -3399,15 +3579,46 @@ sub git_commit {
        my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
        my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
 
-       my $parent = $co{'parent'};
+       my $parent  = $co{'parent'};
+       my $parents = $co{'parents'}; # listref
+
+       # we need to prepare $formats_nav before any parameter munging
+       my $formats_nav;
+       if (!defined $parent) {
+               # --root commitdiff
+               $formats_nav .= '(initial)';
+       } elsif (@$parents == 1) {
+               # single parent commit
+               $formats_nav .=
+                       '(parent: ' .
+                       $cgi->a({-href => href(action=>"commit",
+                                              hash=>$parent)},
+                               esc_html(substr($parent, 0, 7))) .
+                       ')';
+       } else {
+               # merge commit
+               $formats_nav .=
+                       '(merge: ' .
+                       join(' ', map {
+                               $cgi->a({-href => href(action=>"commitdiff",
+                                                      hash=>$_)},
+                                       esc_html(substr($_, 0, 7)));
+                       } @$parents ) .
+                       ')';
+       }
+
        if (!defined $parent) {
                $parent = "--root";
        }
-       open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
-               @diff_opts, $parent, $hash, "--"
-               or die_error(undef, "Open git-diff-tree failed");
-       my @difftree = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading git-diff-tree failed");
+       my @difftree;
+       if (@$parents <= 1) {
+               # difftree output is not printed for merges
+               open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
+                       @diff_opts, $parent, $hash, "--"
+                               or die_error(undef, "Open git-diff-tree failed");
+               @difftree = map { chomp; $_ } <$fd>;
+               close $fd or die_error(undef, "Reading git-diff-tree failed");
+       }
 
        # non-textual hash id's can be cached
        my $expires;
@@ -3419,16 +3630,10 @@ sub git_commit {
 
        my $have_snapshot = gitweb_have_snapshot();
 
-       my @views_nav = ();
-       if (defined $file_name && defined $co{'parent'}) {
-               push @views_nav,
-                       $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)},
-                               "blame");
-       }
        git_header_html(undef, $expires);
        git_print_page_nav('commit', '',
                           $hash, $co{'tree'}, $hash,
-                          join (' | ', @views_nav));
+                          $formats_nav);
 
        if (defined $co{'parent'}) {
                git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
@@ -3469,7 +3674,7 @@ sub git_commit {
        }
        print "</td>" .
              "</tr>\n";
-       my $parents = $co{'parents'};
+
        foreach my $par (@$parents) {
                print "<tr>" .
                      "<td>parent</td>" .
@@ -3491,11 +3696,61 @@ sub git_commit {
        git_print_log($co{'comment'});
        print "</div>\n";
 
-       git_difftree_body(\@difftree, $hash, $parent);
+       if (@$parents <= 1) {
+               # do not output difftree/whatchanged for merges
+               git_difftree_body(\@difftree, $hash, $parent);
+       }
 
        git_footer_html();
 }
 
+sub git_object {
+       # object is defined by:
+       # - hash or hash_base alone
+       # - hash_base and file_name
+       my $type;
+
+       # - hash or hash_base alone
+       if ($hash || ($hash_base && !defined $file_name)) {
+               my $object_id = $hash || $hash_base;
+
+               my $git_command = git_cmd_str();
+               open my $fd, "-|", "$git_command cat-file -t $object_id 2>/dev/null"
+                       or die_error('404 Not Found', "Object does not exist");
+               $type = <$fd>;
+               chomp $type;
+               close $fd
+                       or die_error('404 Not Found', "Object does not exist");
+
+       # - hash_base and file_name
+       } elsif ($hash_base && defined $file_name) {
+               $file_name =~ s,/+$,,;
+
+               system(git_cmd(), "cat-file", '-e', $hash_base) == 0
+                       or die_error('404 Not Found', "Base object does not exist");
+
+               # here errors should not hapen
+               open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
+                       or die_error(undef, "Open git-ls-tree failed");
+               my $line = <$fd>;
+               close $fd;
+
+               #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
+               unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
+                       die_error('404 Not Found', "File or directory for given base does not exist");
+               }
+               $type = $2;
+               $hash = $3;
+       } else {
+               die_error('404 Not Found', "Not enough information to find object");
+       }
+
+       print $cgi->redirect(-uri => href(action=>$type, -full=>1,
+                                         hash=>$hash, hash_base=>$hash_base,
+                                         file_name=>$file_name),
+                            -status => '302 Found');
+}
+
 sub git_blobdiff {
        my $format = shift || 'html';
 
@@ -3669,6 +3924,7 @@ sub git_blobdiff_plain {
 
 sub git_commitdiff {
        my $format = shift || 'html';
+       $hash ||= $hash_base || "HEAD";
        my %co = parse_commit($hash);
        if (!%co) {
                die_error(undef, "Unknown commit object");
@@ -3731,7 +3987,8 @@ sub git_commitdiff {
                        $hash_parent, $hash, "--"
                        or die_error(undef, "Open git-diff-tree failed");
 
-               while (chomp(my $line = <$fd>)) {
+               while (my $line = <$fd>) {
+                       chomp $line;
                        # empty line ends raw part of diff-tree output
                        last unless $line;
                        push @difftree, $line;
@@ -4088,70 +4345,237 @@ sub git_shortlog {
 }
 
 ## ......................................................................
-## feeds (RSS, OPML)
+## feeds (RSS, Atom; OPML)
 
-sub git_rss {
-       # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+sub git_feed {
+       my $format = shift || 'atom';
+       my ($have_blame) = gitweb_check_feature('blame');
+
+       # Atom: http://www.atomenabled.org/developers/syndication/
+       # RSS:  http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+       if ($format ne 'rss' && $format ne 'atom') {
+               die_error(undef, "Unknown web feed format");
+       }
+
+       # log/feed of current (HEAD) branch, log of given branch, history of file/directory
+       my $head = $hash || 'HEAD';
        open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
-               git_get_head_hash($project), "--"
+               $head, "--", (defined $file_name ? $file_name : ())
                or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd or die_error(undef, "Reading git-rev-list failed");
-       print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
-       print <<XML;
-<?xml version="1.0" encoding="utf-8"?>
+
+       my %latest_commit;
+       my %latest_date;
+       my $content_type = "application/$format+xml";
+       if (defined $cgi->http('HTTP_ACCEPT') &&
+                $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
+               # browser (feed reader) prefers text/xml
+               $content_type = 'text/xml';
+       }
+       if (defined($revlist[0])) {
+               %latest_commit = parse_commit($revlist[0]);
+               %latest_date   = parse_date($latest_commit{'author_epoch'});
+               print $cgi->header(
+                       -type => $content_type,
+                       -charset => 'utf-8',
+                       -last_modified => $latest_date{'rfc2822'});
+       } else {
+               print $cgi->header(
+                       -type => $content_type,
+                       -charset => 'utf-8');
+       }
+
+       # Optimization: skip generating the body if client asks only
+       # for Last-Modified date.
+       return if ($cgi->request_method() eq 'HEAD');
+
+       # header variables
+       my $title = "$site_name - $project/$action";
+       my $feed_type = 'log';
+       if (defined $hash) {
+               $title .= " - '$hash'";
+               $feed_type = 'branch log';
+               if (defined $file_name) {
+                       $title .= " :: $file_name";
+                       $feed_type = 'history';
+               }
+       } elsif (defined $file_name) {
+               $title .= " - $file_name";
+               $feed_type = 'history';
+       }
+       $title .= " $feed_type";
+       my $descr = git_get_project_description($project);
+       if (defined $descr) {
+               $descr = esc_html($descr);
+       } else {
+               $descr = "$project " .
+                        ($format eq 'rss' ? 'RSS' : 'Atom') .
+                        " feed";
+       }
+       my $owner = git_get_project_owner($project);
+       $owner = esc_html($owner);
+
+       #header
+       my $alt_url;
+       if (defined $file_name) {
+               $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
+       } elsif (defined $hash) {
+               $alt_url = href(-full=>1, action=>"log", hash=>$hash);
+       } else {
+               $alt_url = href(-full=>1, action=>"summary");
+       }
+       print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
+       if ($format eq 'rss') {
+               print <<XML;
 <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
 <channel>
-<title>$project $my_uri $my_url</title>
-<link>${\esc_html("$my_url?p=$project;a=summary")}</link>
-<description>$project log</description>
-<language>en</language>
 XML
+               print "<title>$title</title>\n" .
+                     "<link>$alt_url</link>\n" .
+                     "<description>$descr</description>\n" .
+                     "<language>en</language>\n";
+       } elsif ($format eq 'atom') {
+               print <<XML;
+<feed xmlns="http://www.w3.org/2005/Atom">
+XML
+               print "<title>$title</title>\n" .
+                     "<subtitle>$descr</subtitle>\n" .
+                     '<link rel="alternate" type="text/html" href="' .
+                     $alt_url . '" />' . "\n" .
+                     '<link rel="self" type="' . $content_type . '" href="' .
+                     $cgi->self_url() . '" />' . "\n" .
+                     "<id>" . href(-full=>1) . "</id>\n" .
+                     # use project owner for feed author
+                     "<author><name>$owner</name></author>\n";
+               if (defined $favicon) {
+                       print "<icon>" . esc_url($favicon) . "</icon>\n";
+               }
+               if (defined $logo_url) {
+                       # not twice as wide as tall: 72 x 27 pixels
+                       print "<logo>" . esc_url($logo) . "</logo>\n";
+               }
+               if (! %latest_date) {
+                       # dummy date to keep the feed valid until commits trickle in:
+                       print "<updated>1970-01-01T00:00:00Z</updated>\n";
+               } else {
+                       print "<updated>$latest_date{'iso-8601'}</updated>\n";
+               }
+       }
 
+       # contents
        for (my $i = 0; $i <= $#revlist; $i++) {
                my $commit = $revlist[$i];
                my %co = parse_commit($commit);
                # we read 150, we always show 30 and the ones more recent than 48 hours
-               if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) {
+               if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
                        last;
                }
-               my %cd = parse_date($co{'committer_epoch'});
+               my %cd = parse_date($co{'author_epoch'});
+
+               # get list of changed files
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       $co{'parent'}, $co{'id'}, "--"
+                       $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
                        or next;
                my @difftree = map { chomp; $_ } <$fd>;
                close $fd
                        or next;
-               print "<item>\n" .
-                     "<title>" .
-                     sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
-                     "</title>\n" .
-                     "<author>" . esc_html($co{'author'}) . "</author>\n" .
-                     "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
-                     "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" .
-                     "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" .
-                     "<description>" . esc_html($co{'title'}) . "</description>\n" .
-                     "<content:encoded>" .
-                     "<![CDATA[\n";
+
+               # print element (entry, item)
+               my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
+               if ($format eq 'rss') {
+                       print "<item>\n" .
+                             "<title>" . esc_html($co{'title'}) . "</title>\n" .
+                             "<author>" . esc_html($co{'author'}) . "</author>\n" .
+                             "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
+                             "<guid isPermaLink=\"true\">$co_url</guid>\n" .
+                             "<link>$co_url</link>\n" .
+                             "<description>" . esc_html($co{'title'}) . "</description>\n" .
+                             "<content:encoded>" .
+                             "<![CDATA[\n";
+               } elsif ($format eq 'atom') {
+                       print "<entry>\n" .
+                             "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
+                             "<updated>$cd{'iso-8601'}</updated>\n" .
+                             "<author>\n" .
+                             "  <name>" . esc_html($co{'author_name'}) . "</name>\n";
+                       if ($co{'author_email'}) {
+                               print "  <email>" . esc_html($co{'author_email'}) . "</email>\n";
+                       }
+                       print "</author>\n" .
+                             # use committer for contributor
+                             "<contributor>\n" .
+                             "  <name>" . esc_html($co{'committer_name'}) . "</name>\n";
+                       if ($co{'committer_email'}) {
+                               print "  <email>" . esc_html($co{'committer_email'}) . "</email>\n";
+                       }
+                       print "</contributor>\n" .
+                             "<published>$cd{'iso-8601'}</published>\n" .
+                             "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
+                             "<id>$co_url</id>\n" .
+                             "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
+                             "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
+               }
                my $comment = $co{'comment'};
+               print "<pre>\n";
                foreach my $line (@$comment) {
-                       $line = to_utf8($line);
-                       print "$line<br/>\n";
+                       $line = esc_html($line);
+                       print "$line\n";
                }
-               print "<br/>\n";
-               foreach my $line (@difftree) {
-                       if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) {
-                               next;
+               print "</pre><ul>\n";
+               foreach my $difftree_line (@difftree) {
+                       my %difftree = parse_difftree_raw_line($difftree_line);
+                       next if !$difftree{'from_id'};
+
+                       my $file = $difftree{'file'} || $difftree{'to_file'};
+
+                       print "<li>" .
+                             "[" .
+                             $cgi->a({-href => href(-full=>1, action=>"blobdiff",
+                                                    hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
+                                                    hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
+                                                    file_name=>$file, file_parent=>$difftree{'from_file'}),
+                                     -title => "diff"}, 'D');
+                       if ($have_blame) {
+                               print $cgi->a({-href => href(-full=>1, action=>"blame",
+                                                            file_name=>$file, hash_base=>$commit),
+                                             -title => "blame"}, 'B');
                        }
-                       my $file = esc_path(unquote($7));
-                       $file = to_utf8($file);
-                       print "$file<br/>\n";
+                       # if this is not a feed of a file history
+                       if (!defined $file_name || $file_name ne $file) {
+                               print $cgi->a({-href => href(-full=>1, action=>"history",
+                                                            file_name=>$file, hash=>$commit),
+                                             -title => "history"}, 'H');
+                       }
+                       $file = esc_path($file);
+                       print "] ".
+                             "$file</li>\n";
+               }
+               if ($format eq 'rss') {
+                       print "</ul>]]>\n" .
+                             "</content:encoded>\n" .
+                             "</item>\n";
+               } elsif ($format eq 'atom') {
+                       print "</ul>\n</div>\n" .
+                             "</content>\n" .
+                             "</entry>\n";
                }
-               print "]]>\n" .
-                     "</content:encoded>\n" .
-                     "</item>\n";
        }
-       print "</channel></rss>";
+
+       # end of feed
+       if ($format eq 'rss') {
+               print "</channel>\n</rss>\n";
+       }       elsif ($format eq 'atom') {
+               print "</feed>\n";
+       }
+}
+
+sub git_rss {
+       git_feed('rss');
+}
+
+sub git_atom {
+       git_feed('atom');
 }
 
 sub git_opml {
diff --git a/grep.c b/grep.c
index 0fc078ec0ac42e39125c8e5a8f05a3b6ecae4fa3..fcc676230282e4c5bda4f9f97c5ae65c1a6beb30 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -1,5 +1,4 @@
 #include "cache.h"
-#include <regex.h>
 #include "grep.h"
 
 void append_grep_pattern(struct grep_opt *opt, const char *pat,
diff --git a/help.c b/help.c
index 0824c25226ad7b106e56d1b7c23fad40eed92749..341b9e370eec4fa90014249a63afac17b083b275 100644 (file)
--- a/help.c
+++ b/help.c
@@ -3,12 +3,11 @@
  *
  * Builtin help-related commands (help, usage, version)
  */
-#include <sys/ioctl.h>
 #include "cache.h"
 #include "builtin.h"
 #include "exec_cmd.h"
 #include "common-cmds.h"
-
+#include <sys/ioctl.h>
 
 /* most GUI terminals set COLUMNS (although some don't export it) */
 static int term_columns(void)
diff --git a/ident.c b/ident.c
index efec97ffd0a9f3aa6573f3476c749d38d6fc388c..6ad8fedd60ba1461d77dc2b1d66e52cf56158c6f 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -7,9 +7,6 @@
  */
 #include "cache.h"
 
-#include <pwd.h>
-#include <netdb.h>
-
 static char git_default_date[50];
 
 static void copy_gecos(struct passwd *w, char *name, int sz)
@@ -158,12 +155,17 @@ static int copy(char *buf, int size, int offset, const char *src)
 static const char au_env[] = "GIT_AUTHOR_NAME";
 static const char co_env[] = "GIT_COMMITTER_NAME";
 static const char *env_hint =
-"\n*** Environment problem:\n"
+"\n"
 "*** Your name cannot be determined from your system services (gecos).\n"
-"*** You would need to set %s and %s\n"
-"*** environment variables; otherwise you won't be able to perform\n"
-"*** certain operations because of \"empty ident\" errors.\n"
-"*** Alternatively, you can use user.name configuration variable.\n\n";
+"\n"
+"Run\n"
+"\n"
+"  git repo-config user.email \"you@email.com\"\n"
+"  git repo-config user.name \"Your Name\"\n"
+"\n"
+"To set the identity in this repository.\n"
+"Add --global to set your account\'s default\n"
+"\n";
 
 static const char *get_ident(const char *name, const char *email,
                             const char *date_str, int error_on_no_name)
@@ -216,3 +218,18 @@ const char *git_committer_info(int error_on_no_name)
                         getenv("GIT_COMMITTER_DATE"),
                         error_on_no_name);
 }
+
+void ignore_missing_committer_name()
+{
+       /* If we did not get a name from the user's gecos entry then
+        * git_default_name is empty; so instead load the username
+        * into it as a 'good enough for now' approximation of who
+        * this user is.
+        */
+       if (!*git_default_name) {
+               struct passwd *pw = getpwuid(getuid());
+               if (!pw)
+                       die("You don't exist. Go away!");
+               strlcpy(git_default_name, pw->pw_name, sizeof(git_default_name));
+       }
+}
index a6a65680ee6daf062180d2580e83616cfff4cda4..894cbbdf53ccf4728276b46b9491f409f3b9e03b 100644 (file)
 
 #include "cache.h"
 
-#include <assert.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-#include <netdb.h>
-
 typedef struct store_conf {
        char *name;
        const char *path; /* should this be here? its interpretation is driver-specific */
index 6d6c92bf14803923f4c98678ae682aef4d41d3ab..5f6d128a836f8ed9447f81280ae679e19c9939ff 100644 (file)
@@ -6,8 +6,6 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
-#include <sys/time.h>
-#include <signal.h>
 
 static const char index_pack_usage[] =
 "git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
@@ -87,7 +85,7 @@ static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc)
 static unsigned char input_buffer[4096];
 static unsigned long input_offset, input_len, consumed_bytes;
 static SHA_CTX input_ctx;
-static int input_fd, output_fd, mmap_fd;
+static int input_fd, output_fd, pack_fd;
 
 /* Discard current buffer used content. */
 static void flush(void)
@@ -148,14 +146,14 @@ static const char *open_pack_file(const char *pack_name)
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
                if (output_fd < 0)
                        die("unable to create %s: %s\n", pack_name, strerror(errno));
-               mmap_fd = output_fd;
+               pack_fd = output_fd;
        } else {
                input_fd = open(pack_name, O_RDONLY);
                if (input_fd < 0)
                        die("cannot open packfile '%s': %s",
                            pack_name, strerror(errno));
                output_fd = -1;
-               mmap_fd = input_fd;
+               pack_fd = input_fd;
        }
        SHA1_Init(&input_ctx);
        return pack_name;
@@ -268,7 +266,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        case OBJ_TAG:
                break;
        default:
-               bad_object(obj->offset, "bad object type %d", obj->type);
+               bad_object(obj->offset, "unknown object type %d", obj->type);
        }
        obj->hdr_size = consumed_bytes - obj->offset;
 
@@ -279,27 +277,25 @@ static void *get_data_from_pack(struct object_entry *obj)
 {
        unsigned long from = obj[0].offset + obj[0].hdr_size;
        unsigned long len = obj[1].offset - from;
-       unsigned pg_offset = from % getpagesize();
-       unsigned char *map, *data;
+       unsigned char *src, *data;
        z_stream stream;
        int st;
 
-       map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE,
-                  mmap_fd, from - pg_offset);
-       if (map == MAP_FAILED)
-               die("cannot mmap pack file: %s", strerror(errno));
+       src = xmalloc(len);
+       if (pread(pack_fd, src, len, from) != len)
+               die("cannot pread pack file: %s", strerror(errno));
        data = xmalloc(obj->size);
        memset(&stream, 0, sizeof(stream));
        stream.next_out = data;
        stream.avail_out = obj->size;
-       stream.next_in = map + pg_offset;
+       stream.next_in = src;
        stream.avail_in = len;
        inflateInit(&stream);
        while ((st = inflate(&stream, Z_FINISH)) == Z_OK);
        inflateEnd(&stream);
        if (st != Z_STREAM_END || stream.total_out != obj->size)
                die("serious inflate inconsistency");
-       munmap(map, len + pg_offset);
+       free(src);
        return data;
 }
 
index 5d9d1889f088c64131cd3c116e6a16876ce95db8..f992ef77533737fe2ec52dc3b662cb6cacca0ea2 100644 (file)
@@ -2,8 +2,6 @@
  * Copyright 2006 Jon Loeliger
  */
 
-#include <string.h>
-
 #include "git-compat-util.h"
 #include "interpolate.h"
 
index 2a2fea3cb6bd1de059e7c0f8c2008c5fe8376b93..261baff049cd8b2e4d1b1a269992851eb00b2aa8 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (c) 2005, Junio C Hamano
  */
-#include <signal.h>
 #include "cache.h"
 
 static struct lock_file *lock_file_list;
index 8787df5cc647dda24f09b077276cbe0aa054fc47..35be33aaf73e0b53cb9ece09bba5f1e12de4b99d 100644 (file)
@@ -114,6 +114,14 @@ void show_log(struct rev_info *opt, const char *sep)
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
+               if (opt->left_right) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->parents)
                        show_parents(commit, abbrev_commit);
@@ -192,10 +200,20 @@ void show_log(struct rev_info *opt, const char *sep)
                        opt->diffopt.stat_sep = buffer;
                }
        } else {
-               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));
+               fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
+                     stdout);
+               if (opt->commit_format != CMIT_FMT_ONELINE)
+                       fputs("commit ", stdout);
+               if (opt->left_right) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
+                     stdout);
                if (opt->parents)
                        show_parents(commit, abbrev_commit);
                if (parent)
index 009caf804b43fdd644ff08991d55d0e417fd34d8..385f4ba386ada3e43f962d9051cf4b651320fcd3 100644 (file)
@@ -1,4 +1,3 @@
-#include <stdlib.h>
 #include "cache.h"
 #include "commit.h"
 
index fc9b148993027d4064257d4df8e63a12e1afc5d4..69dc1ebbf7685f3dd30dcca64974eabba8ee137b 100644 (file)
@@ -3,52 +3,6 @@
 #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 = xstrdup(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;
@@ -72,22 +26,19 @@ static void free_mmfile(mmfile_t *f)
 
 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;
+       mmbuffer_t res;
+       xpparam_t xpp;
+       int merge_status;
+
+       memset(&xpp, 0, sizeof(xpp));
+       merge_status = xdl_merge(base, our, ".our", their, ".their",
+               &xpp, XDL_MERGE_ZEALOUS, &res);
+
+       if (merge_status < 0)
+               return NULL;
+
+       *size = res.size;
+       return res.ptr;
 }
 
 static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf)
index 646d090c58e774bdf0f412da39f4b67fd7a696c6..a9983dd78ad5cd1e364f0e00c259bdb7e6f151f0 100644 (file)
@@ -1,7 +1,3 @@
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
-
 #include "cache.h"
 
 static const char *pgm;
index 32e186c15e7dcdaff99f09a7edcf50a691c7ee74..ae7ae4cd2a9ce392945ad8ed27e4289c27ee24bd 100644 (file)
@@ -3,13 +3,6 @@
  * Fredrik Kuivinen.
  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
  */
-#include <stdarg.h>
-#include <string.h>
-#include <assert.h>
-#include <sys/wait.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <time.h>
 #include "cache.h"
 #include "cache-tree.h"
 #include "commit.h"
@@ -21,6 +14,7 @@
 #include "tag.h"
 #include "unpack-trees.h"
 #include "path-list.h"
+#include "xdiff-interface.h"
 
 /*
  * A virtual commit has
@@ -604,24 +598,21 @@ struct merge_file_info
                 merge:1;
 };
 
-static char *git_unpack_file(const unsigned char *sha1, char *path)
+static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
 {
-       void *buf;
-       char type[20];
        unsigned long size;
-       int fd;
+       char type[20];
 
-       buf = read_sha1_file(sha1, type, &size);
-       if (!buf || strcmp(type, blob_type))
-               die("unable to read blob object %s", sha1_to_hex(sha1));
+       if (!hashcmp(sha1, null_sha1)) {
+               mm->ptr = xstrdup("");
+               mm->size = 0;
+               return;
+       }
 
-       strcpy(path, ".merge_file_XXXXXX");
-       fd = mkstemp(path);
-       if (fd < 0)
-               die("unable to create temp-file");
-       flush_buffer(fd, buf, size);
-       close(fd);
-       return path;
+       mm->ptr = read_sha1_file(sha1, type, &size);
+       if (!mm->ptr || strcmp(type, blob_type))
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+       mm->size = size;
 }
 
 static struct merge_file_info merge_file(struct diff_filespec *o,
@@ -652,49 +643,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
                else if (sha_eq(b->sha1, o->sha1))
                        hashcpy(result.sha, a->sha1);
                else if (S_ISREG(a->mode)) {
-                       int code = 1, fd;
-                       struct stat st;
-                       char orig[PATH_MAX];
-                       char src1[PATH_MAX];
-                       char src2[PATH_MAX];
-                       const char *argv[] = {
-                               "merge", "-L", NULL, "-L", NULL, "-L", NULL,
-                               NULL, NULL, NULL,
-                               NULL
-                       };
-                       char *la, *lb, *lo;
-
-                       git_unpack_file(o->sha1, orig);
-                       git_unpack_file(a->sha1, src1);
-                       git_unpack_file(b->sha1, src2);
-
-                       argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path));
-                       argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path));
-                       argv[4] = lo = xstrdup(mkpath("orig/%s", o->path));
-                       argv[7] = src1;
-                       argv[8] = orig;
-                       argv[9] = src2,
-
-                       code = run_command_v(10, argv);
-
-                       free(la);
-                       free(lb);
-                       free(lo);
-                       if (code && code < -256) {
-                               die("Failed to execute 'merge'. merge(1) is used as the "
-                                   "file-level merge tool. Is 'merge' in your path?");
-                       }
-                       fd = open(src1, O_RDONLY);
-                       if (fd < 0 || fstat(fd, &st) < 0 ||
-                                       index_fd(result.sha, fd, &st, 1,
-                                               "blob"))
-                               die("Unable to add %s to database", src1);
-
-                       unlink(orig);
-                       unlink(src1);
-                       unlink(src2);
-
-                       result.clean = WEXITSTATUS(code) == 0;
+                       mmfile_t orig, src1, src2;
+                       mmbuffer_t result_buf;
+                       xpparam_t xpp;
+                       char *name1, *name2;
+                       int merge_status;
+
+                       name1 = xstrdup(mkpath("%s/%s", branch1, a->path));
+                       name2 = xstrdup(mkpath("%s/%s", branch2, b->path));
+
+                       fill_mm(o->sha1, &orig);
+                       fill_mm(a->sha1, &src1);
+                       fill_mm(b->sha1, &src2);
+
+                       memset(&xpp, 0, sizeof(xpp));
+                       merge_status = xdl_merge(&orig,
+                                                &src1, name1,
+                                                &src2, name2,
+                                                &xpp, XDL_MERGE_ZEALOUS,
+                                                &result_buf);
+                       free(name1);
+                       free(name2);
+                       free(orig.ptr);
+                       free(src1.ptr);
+                       free(src2.ptr);
+
+                       if ((merge_status < 0) || !result_buf.ptr)
+                               die("Failed to execute internal merge");
+
+                       if (write_sha1_file(result_buf.ptr, result_buf.size,
+                                           blob_type, result.sha))
+                               die("Unable to add %s to database",
+                                   a->path);
+
+                       free(result_buf.ptr);
+                       result.clean = (merge_status == 0);
                } else {
                        if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode)))
                                die("cannot merge modes?");
@@ -889,7 +872,7 @@ static int process_renames(struct path_list *a_renames,
                        struct diff_filespec src_other, dst_other;
                        int try_merge, stage = a_renames == renames1 ? 3: 2;
 
-                       remove_file(1, ren1_src, 1);
+                       remove_file(1, ren1_src, index_only);
 
                        hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
                        src_other.mode = ren1->src_entry->stages[stage].mode;
@@ -1061,38 +1044,17 @@ static int process_entry(const char *path, struct stage_data *entry,
                        output("Adding %s", path);
                        update_file(1, sha, mode, path);
                }
-       } else if (!o_sha && a_sha && b_sha) {
-               /* Case C: Added in both (check for same permissions). */
-               if (sha_eq(a_sha, b_sha)) {
-                       if (a_mode != b_mode) {
-                               clean_merge = 0;
-                               output("CONFLICT: File %s added identically in both branches, "
-                                      "but permissions conflict %06o->%06o",
-                                      path, a_mode, b_mode);
-                               output("CONFLICT: adding with permission: %06o", a_mode);
-                               update_file(0, a_sha, a_mode, path);
-                       } else {
-                               /* This case is handled by git-read-tree */
-                               assert(0 && "This case must be handled by git-read-tree");
-                       }
-               } else {
-                       const char *new_path1, *new_path2;
-                       clean_merge = 0;
-                       new_path1 = unique_path(path, branch1);
-                       new_path2 = unique_path(path, branch2);
-                       output("CONFLICT (add/add): File %s added non-identically "
-                              "in both branches. Adding as %s and %s instead.",
-                              path, new_path1, new_path2);
-                       remove_file(0, path, 0);
-                       update_file(0, a_sha, a_mode, new_path1);
-                       update_file(0, b_sha, b_mode, new_path2);
-               }
-
-       } else if (o_sha && a_sha && b_sha) {
+       } else if (a_sha && b_sha) {
+               /* Case C: Added in both (check for same permissions) and */
                /* case D: Modified in both, but differently. */
+               const char *reason = "content";
                struct merge_file_info mfi;
                struct diff_filespec o, a, b;
 
+               if (!o_sha) {
+                       reason = "add/add";
+                       o_sha = (unsigned char *)null_sha1;
+               }
                output("Auto-merging %s", path);
                o.path = a.path = b.path = (char *)path;
                hashcpy(o.sha1, o_sha);
@@ -1109,7 +1071,8 @@ static int process_entry(const char *path, struct stage_data *entry,
                        update_file(1, mfi.sha, mfi.mode, path);
                else {
                        clean_merge = 0;
-                       output("CONFLICT (content): Merge conflict in %s", path);
+                       output("CONFLICT (%s): Merge conflict in %s",
+                                       reason, path);
 
                        if (index_only)
                                update_file(0, mfi.sha, mfi.mode, path);
@@ -1309,7 +1272,7 @@ int main(int argc, char *argv[])
        struct commit *result, *h1, *h2;
 
        git_config(git_default_config); /* core.filemode */
-       original_index_file = getenv("GIT_INDEX_FILE");
+       original_index_file = getenv(INDEX_ENVIRONMENT);
 
        if (!original_index_file)
                original_index_file = xstrdup(git_path("index"));
index e3a1d425ee2e333bce0f87b1e50fc112a8ec51a3..ed9db81fa82c812c9ffa07f5a40540dbb15da0d3 100644 (file)
@@ -9,8 +9,7 @@
  * published by the Free Software Foundation.
  */
 
-#include <stdlib.h>
-#include <string.h>
+#include "git-compat-util.h"
 #include "delta.h"
 
 void *patch_delta(const void *src_buf, unsigned long src_size,
@@ -34,9 +33,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
 
        /* now the result size */
        size = get_delta_hdr_size(&data, top);
-       dst_buf = malloc(size + 1);
-       if (!dst_buf)
-               return NULL;
+       dst_buf = xmalloc(size + 1);
        dst_buf[size] = 0;
 
        out = dst_buf;
@@ -55,13 +52,13 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
                        if (cp_off + cp_size < cp_size ||
                            cp_off + cp_size > src_size ||
                            cp_size > size)
-                               goto bad;
+                               break;
                        memcpy(out, (char *) src_buf + cp_off, cp_size);
                        out += cp_size;
                        size -= cp_size;
                } else if (cmd) {
                        if (cmd > size)
-                               goto bad;
+                               break;
                        memcpy(out, data, cmd);
                        out += cmd;
                        data += cmd;
@@ -72,12 +69,14 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
                         * extensions. In the mean time we must fail when
                         * encountering them (might be data corruption).
                         */
+                       error("unexpected delta opcode 0");
                        goto bad;
                }
        }
 
        /* sanity check */
        if (data != top || size != 0) {
+               error("delta replay has gone wild");
                bad:
                free(dst_buf);
                return NULL;
index f8800f8e66e39f035820c2aeb0843fb3ebf65fb3..caaa5cc57b05e1a27bb4fd8f49bb74e06b9936e3 100644 (file)
@@ -1,4 +1,3 @@
-#include <stdio.h>
 #include "cache.h"
 #include "path-list.h"
 
diff --git a/path.c b/path.c
index d2c076d7cbad3a16a002897d926cc13633be4f77..066f62195508033a5f72504e4805ea436424296e 100644 (file)
--- a/path.c
+++ b/path.c
@@ -11,7 +11,6 @@
  * which is what it's designed for.
  */
 #include "cache.h"
-#include <pwd.h>
 
 static char bad_path[] = "/bad-path/";
 
index e990caeea731d4a6713c32581cf8de3d6af4e1b3..98b24772c7ebe838d513d8e24f3c8acff7839cb9 100644 (file)
@@ -1,4 +1,5 @@
-Makefile
+perl.mak
+perl.mak.old
 blib
 blibdirs
 pm_to_blib
diff --git a/perl/Makefile b/perl/Makefile
new file mode 100644 (file)
index 0000000..099beda
--- /dev/null
@@ -0,0 +1,39 @@
+#
+# Makefile for perl support modules and routine
+#
+makfile:=perl.mak
+
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+prefix_SQ = $(subst ','\'',$(prefix))
+
+all install instlibdir: $(makfile)
+       $(MAKE) -f $(makfile) $@
+
+clean:
+       test -f $(makfile) && $(MAKE) -f $(makfile) $@ || exit 0
+       $(RM) ppport.h
+       $(RM) $(makfile)
+       $(RM) $(makfile).old
+
+ifdef NO_PERL_MAKEMAKER
+instdir_SQ = $(subst ','\'',$(prefix)/lib)
+$(makfile): ../GIT-CFLAGS Makefile
+       echo all: > $@
+       echo '  :' >> $@
+       echo install: >> $@
+       echo '  mkdir -p $(instdir_SQ)' >> $@
+       echo '  $(RM) $(instdir_SQ)/Git.pm; cp Git.pm $(instdir_SQ)' >> $@
+       echo '  $(RM) $(instdir_SQ)/Error.pm; \
+       cp private-Error.pm $(instdir_SQ)/Error.pm' >> $@
+       echo instlibdir: >> $@
+       echo '  echo $(instdir_SQ)' >> $@
+else
+$(makfile): Makefile.PL ../GIT-CFLAGS
+       '$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)'
+endif
+
+# this is just added comfort for calling make directly in perl dir
+# (even though GIT-CFLAGS aren't used yet. If ever)
+../GIT-CFLAGS:
+       $(MAKE) -C .. GIT-CFLAGS
+
index de73235e4c3142381c4ea01597e18b14cd65bc02..41687757a7fdd9c09bd320b92ed7f089af00a403 100644 (file)
@@ -24,5 +24,6 @@ WriteMakefile(
        NAME            => 'Git',
        VERSION_FROM    => 'Git.pm',
        PM              => \%pm,
+       MAKEFILE        => 'perl.mak',
        %extra
 );
index c1e81f976f26726db2432f72f7356087a6e3a7d8..b4cb7e2756dcc52c17aebf4c0fc6adc7b415ef3c 100644 (file)
@@ -72,7 +72,7 @@ static void safe_read(int fd, void *buffer, unsigned size)
                if (ret < 0)
                        die("read error (%s)", strerror(errno));
                if (!ret)
-                       die("unexpected EOF");
+                       die("The remote end hung up unexpectedly");
                n += ret;
        }
 }
index eae4745d284e00e279b5b8f4b032bdb9ca433984..b8d83ccd9f7985d60f69b7cd44db698d4e932612 100644 (file)
@@ -358,7 +358,7 @@ int add_file_to_index(const char *path, int verbose)
 
        if (index_path(ce->sha1, path, &st, 1))
                die("unable to index file %s", path);
-       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
+       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
                die("unable to add %s to index",path);
        if (verbose)
                printf("add '%s'\n", path);
@@ -517,7 +517,7 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace
                pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage)));
                if (pos >= 0) {
                        retval = -1;
-                       if (ok_to_replace)
+                       if (!ok_to_replace)
                                break;
                        remove_cache_entry_at(pos);
                        continue;
@@ -609,7 +609,7 @@ int add_cache_entry(struct cache_entry *ce, int option)
        if (!skip_df_check &&
            check_file_directory_conflict(ce, pos, ok_to_replace)) {
                if (!ok_to_replace)
-                       return -1;
+                       return error("'%s' appears as both a file and as a directory", ce->name);
                pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
                pos = -pos-1;
        }
index f18915117e994a9b228f07edc0d45442acd6902c..59b682c03af53fbfe600d063fa832f874440493c 100644 (file)
@@ -6,15 +6,14 @@
 #include "exec_cmd.h"
 #include "commit.h"
 #include "object.h"
-#include <sys/wait.h>
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
 static int deny_non_fast_forwards = 0;
-static int unpack_limit = 5000;
+static int unpack_limit = 100;
 static int report_status;
 
-static char capabilities[] = "report-status";
+static char capabilities[] = " report-status delete-refs ";
 static int capabilities_sent;
 
 static int receive_pack_config(const char *var, const char *value)
@@ -113,12 +112,14 @@ static int update(struct command *cmd)
 
        strcpy(new_hex, sha1_to_hex(new_sha1));
        strcpy(old_hex, sha1_to_hex(old_sha1));
-       if (!has_sha1_file(new_sha1)) {
+
+       if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
                cmd->error_string = "bad pack";
                return error("unpack should have generated %s, "
                             "but I can't find it!", new_hex);
        }
-       if (deny_non_fast_forwards && !is_null_sha1(old_sha1) &&
+       if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
+           !is_null_sha1(old_sha1) &&
            !strncmp(name, "refs/heads/", 11)) {
                struct commit *old_commit, *new_commit;
                struct commit_list *bases, *ent;
@@ -139,14 +140,22 @@ static int update(struct command *cmd)
                return error("hook declined to update %s", name);
        }
 
-       lock = lock_any_ref_for_update(name, old_sha1);
-       if (!lock) {
-               cmd->error_string = "failed to lock";
-               return error("failed to lock %s", name);
+       if (is_null_sha1(new_sha1)) {
+               if (delete_ref(name, old_sha1)) {
+                       cmd->error_string = "failed to delete";
+                       return error("failed to delete %s", name);
+               }
+               fprintf(stderr, "%s: %s -> deleted\n", name, old_hex);
+       }
+       else {
+               lock = lock_any_ref_for_update(name, old_sha1);
+               if (!lock) {
+                       cmd->error_string = "failed to lock";
+                       return error("failed to lock %s", name);
+               }
+               write_ref_sha1(lock, new_sha1, "push");
+               fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
        }
-       write_ref_sha1(lock, new_sha1, "push");
-
-       fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
        return 0;
 }
 
@@ -376,6 +385,16 @@ static void report(const char *unpack_status)
        packet_flush(1);
 }
 
+static int delete_only(struct command *cmd)
+{
+       while (cmd) {
+               if (!is_null_sha1(cmd->new_sha1))
+                       return 0;
+               cmd = cmd->next;
+       }
+       return 1;
+}
+
 int main(int argc, char **argv)
 {
        int i;
@@ -400,6 +419,8 @@ int main(int argc, char **argv)
                die("'%s': unable to chdir or not a git archive", dir);
 
        setup_ident();
+       /* don't die if gecos is empty */
+       ignore_missing_committer_name();
        git_config(receive_pack_config);
 
        write_head_info();
@@ -409,7 +430,10 @@ int main(int argc, char **argv)
 
        read_head_info();
        if (commands) {
-               const char *unpack_status = unpack();
+               const char *unpack_status = NULL;
+
+               if (!delete_only(commands))
+                       unpack_status = unpack();
                if (!unpack_status)
                        execute_commands();
                if (pack_lockfile)
diff --git a/refs.c b/refs.c
index 0e156c5dee1edff895c576a4491ec71f931ff492..a101ff3bf88dafa56ac85fcd977c0ae6be1b495e 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1,12 +1,16 @@
-#include "refs.h"
 #include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
 
-#include <errno.h>
+/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
+#define REF_KNOWS_PEELED 04
 
 struct ref_list {
        struct ref_list *next;
        unsigned char flag; /* ISSYMREF? ISPACKED? */
        unsigned char sha1[20];
+       unsigned char peeled[20];
        char name[FLEX_ARRAY];
 };
 
@@ -34,11 +38,13 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
        if (line[len] != '\n')
                return NULL;
        line[len] = 0;
+
        return line;
 }
 
 static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
-                               int flag, struct ref_list *list)
+                               int flag, struct ref_list *list,
+                               struct ref_list **new_entry)
 {
        int len;
        struct ref_list **p = &list, *entry;
@@ -50,8 +56,11 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
                        break;
 
                /* Same as existing entry? */
-               if (!cmp)
+               if (!cmp) {
+                       if (new_entry)
+                               *new_entry = entry;
                        return list;
+               }
                p = &entry->next;
        }
 
@@ -59,10 +68,13 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
        len = strlen(name) + 1;
        entry = xmalloc(sizeof(struct ref_list) + len);
        hashcpy(entry->sha1, sha1);
+       hashclr(entry->peeled);
        memcpy(entry->name, name, len);
        entry->flag = flag;
        entry->next = *p;
        *p = entry;
+       if (new_entry)
+               *new_entry = entry;
        return list;
 }
 
@@ -98,25 +110,50 @@ static void invalidate_cached_refs(void)
        ca->did_loose = ca->did_packed = 0;
 }
 
+static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
+{
+       struct ref_list *list = NULL;
+       struct ref_list *last = NULL;
+       char refline[PATH_MAX];
+       int flag = REF_ISPACKED;
+
+       while (fgets(refline, sizeof(refline), f)) {
+               unsigned char sha1[20];
+               const char *name;
+               static const char header[] = "# pack-refs with:";
+
+               if (!strncmp(refline, header, sizeof(header)-1)) {
+                       const char *traits = refline + sizeof(header) - 1;
+                       if (strstr(traits, " peeled "))
+                               flag |= REF_KNOWS_PEELED;
+                       /* perhaps other traits later as well */
+                       continue;
+               }
+
+               name = parse_ref_line(refline, sha1);
+               if (name) {
+                       list = add_ref(name, sha1, flag, list, &last);
+                       continue;
+               }
+               if (last &&
+                   refline[0] == '^' &&
+                   strlen(refline) == 42 &&
+                   refline[41] == '\n' &&
+                   !get_sha1_hex(refline + 1, sha1))
+                       hashcpy(last->peeled, sha1);
+       }
+       cached_refs->packed = list;
+}
+
 static struct ref_list *get_packed_refs(void)
 {
        if (!cached_refs.did_packed) {
-               struct ref_list *refs = NULL;
                FILE *f = fopen(git_path("packed-refs"), "r");
+               cached_refs.packed = NULL;
                if (f) {
-                       struct ref_list *list = NULL;
-                       char refline[PATH_MAX];
-                       while (fgets(refline, sizeof(refline), f)) {
-                               unsigned char sha1[20];
-                               const char *name = parse_ref_line(refline, sha1);
-                               if (!name)
-                                       continue;
-                               list = add_ref(name, sha1, REF_ISPACKED, list);
-                       }
+                       read_packed_refs(f, &cached_refs);
                        fclose(f);
-                       refs = list;
                }
-               cached_refs.packed = refs;
                cached_refs.did_packed = 1;
        }
        return cached_refs.packed;
@@ -159,7 +196,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
                                error("%s points nowhere!", ref);
                                continue;
                        }
-                       list = add_ref(ref, sha1, flag, list);
+                       list = add_ref(ref, sha1, flag, list, NULL);
                }
                free(ref);
                closedir(dir);
@@ -336,6 +373,43 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
        return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
 }
 
+int peel_ref(const char *ref, unsigned char *sha1)
+{
+       int flag;
+       unsigned char base[20];
+       struct object *o;
+
+       if (!resolve_ref(ref, base, 1, &flag))
+               return -1;
+
+       if ((flag & REF_ISPACKED)) {
+               struct ref_list *list = get_packed_refs();
+
+               while (list) {
+                       if (!strcmp(list->name, ref)) {
+                               if (list->flag & REF_KNOWS_PEELED) {
+                                       hashcpy(sha1, list->peeled);
+                                       return 0;
+                               }
+                               /* older pack-refs did not leave peeled ones */
+                               break;
+                       }
+                       list = list->next;
+               }
+       }
+
+       /* fallback - callers should not call this for unpacked refs */
+       o = parse_object(base);
+       if (o->type == OBJ_TAG) {
+               o = deref_tag(o, ref, 0);
+               if (o) {
+                       hashcpy(sha1, o->sha1);
+                       return 0;
+               }
+       }
+       return -1;
+}
+
 static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
                           void *cb_data)
 {
@@ -458,7 +532,7 @@ int check_ref_format(const char *ref)
                level++;
                if (!ch) {
                        if (level < 2)
-                               return -1; /* at least of form "heads/blah" */
+                               return -2; /* at least of form "heads/blah" */
                        return 0;
                }
        }
@@ -534,6 +608,29 @@ static int remove_empty_directories(char *file)
        return remove_empty_dir_recursive(path, len);
 }
 
+static int is_refname_available(const char *ref, const char *oldref,
+                               struct ref_list *list, int quiet)
+{
+       int namlen = strlen(ref); /* e.g. 'foo/bar' */
+       while (list) {
+               /* list->name could be 'foo' or 'foo/bar/baz' */
+               if (!oldref || strcmp(oldref, list->name)) {
+                       int len = strlen(list->name);
+                       int cmplen = (namlen < len) ? namlen : len;
+                       const char *lead = (namlen < len) ? list->name : ref;
+                       if (!strncmp(ref, list->name, cmplen) &&
+                           lead[cmplen] == '/') {
+                               if (!quiet)
+                                       error("'%s' exists; cannot create '%s'",
+                                             list->name, ref);
+                               return 0;
+                       }
+               }
+               list = list->next;
+       }
+       return 1;
+}
+
 static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
 {
        char *ref_file;
@@ -567,29 +664,14 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
                        orig_ref, strerror(errno));
                goto error_return;
        }
-       if (is_null_sha1(lock->old_sha1)) {
-               /* The ref did not exist and we are creating it.
-                * Make sure there is no existing ref that is packed
-                * whose name begins with our refname, nor a ref whose
-                * name is a proper prefix of our refname.
-                */
-               int namlen = strlen(ref); /* e.g. 'foo/bar' */
-               struct ref_list *list = get_packed_refs();
-               while (list) {
-                       /* list->name could be 'foo' or 'foo/bar/baz' */
-                       int len = strlen(list->name);
-                       int cmplen = (namlen < len) ? namlen : len;
-                       const char *lead = (namlen < len) ? list->name : ref;
-
-                       if (!strncmp(ref, list->name, cmplen) &&
-                           lead[cmplen] == '/') {
-                               error("'%s' exists; cannot create '%s'",
-                                     list->name, ref);
-                               goto error_return;
-                       }
-                       list = list->next;
-               }
-       }
+       /* When the ref did not exist and we are creating it,
+        * make sure there is no existing ref that is packed
+        * whose name begins with our refname, nor a ref whose
+        * name is a proper prefix of our refname.
+        */
+       if (is_null_sha1(lock->old_sha1) &&
+            !is_refname_available(ref, NULL, get_packed_refs(), 0))
+               goto error_return;
 
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
@@ -700,6 +782,127 @@ int delete_ref(const char *refname, unsigned char *sha1)
        return ret;
 }
 
+int rename_ref(const char *oldref, const char *newref, const char *logmsg)
+{
+       static const char renamed_ref[] = "RENAMED-REF";
+       unsigned char sha1[20], orig_sha1[20];
+       int flag = 0, logmoved = 0;
+       struct ref_lock *lock;
+       struct stat loginfo;
+       int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+
+       if (S_ISLNK(loginfo.st_mode))
+               return error("reflog for %s is a symlink", oldref);
+
+       if (!resolve_ref(oldref, orig_sha1, 1, &flag))
+               return error("refname %s not found", oldref);
+
+       if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
+               return 1;
+
+       if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
+               return 1;
+
+       lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL);
+       if (!lock)
+               return error("unable to lock %s", renamed_ref);
+       lock->force_write = 1;
+       if (write_ref_sha1(lock, orig_sha1, logmsg))
+               return error("unable to save current sha1 in %s", renamed_ref);
+
+       if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log")))
+               return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
+                       oldref, strerror(errno));
+
+       if (delete_ref(oldref, orig_sha1)) {
+               error("unable to delete old %s", oldref);
+               goto rollback;
+       }
+
+       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) {
+               if (errno==EISDIR) {
+                       if (remove_empty_directories(git_path("%s", newref))) {
+                               error("Directory not empty: %s", newref);
+                               goto rollback;
+                       }
+               } else {
+                       error("unable to delete existing %s", newref);
+                       goto rollback;
+               }
+       }
+
+       if (log && safe_create_leading_directories(git_path("logs/%s", newref))) {
+               error("unable to create directory for %s", newref);
+               goto rollback;
+       }
+
+ retry:
+       if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) {
+               if (errno==EISDIR) {
+                       if (remove_empty_directories(git_path("logs/%s", newref))) {
+                               error("Directory not empty: logs/%s", newref);
+                               goto rollback;
+                       }
+                       goto retry;
+               } else {
+                       error("unable to move logfile tmp-renamed-log to logs/%s: %s",
+                               newref, strerror(errno));
+                       goto rollback;
+               }
+       }
+       logmoved = log;
+
+       lock = lock_ref_sha1_basic(newref, NULL, NULL);
+       if (!lock) {
+               error("unable to lock %s for update", newref);
+               goto rollback;
+       }
+
+       lock->force_write = 1;
+       hashcpy(lock->old_sha1, orig_sha1);
+       if (write_ref_sha1(lock, orig_sha1, logmsg)) {
+               error("unable to write current sha1 into %s", newref);
+               goto rollback;
+       }
+
+       if (!strncmp(oldref, "refs/heads/", 11) &&
+                       !strncmp(newref, "refs/heads/", 11)) {
+               char oldsection[1024], newsection[1024];
+
+               snprintf(oldsection, 1024, "branch.%s", oldref + 11);
+               snprintf(newsection, 1024, "branch.%s", newref + 11);
+               if (git_config_rename_section(oldsection, newsection) < 0)
+                       return 1;
+       }
+
+       return 0;
+
+ rollback:
+       lock = lock_ref_sha1_basic(oldref, NULL, NULL);
+       if (!lock) {
+               error("unable to lock %s for rollback", oldref);
+               goto rollbacklog;
+       }
+
+       lock->force_write = 1;
+       flag = log_all_ref_updates;
+       log_all_ref_updates = 0;
+       if (write_ref_sha1(lock, orig_sha1, NULL))
+               error("unable to write current sha1 into %s", oldref);
+       log_all_ref_updates = flag;
+
+ rollbacklog:
+       if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from %s: %s",
+                       oldref, newref, strerror(errno));
+       if (!logmoved && log &&
+           rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from tmp-renamed-log: %s",
+                       oldref, strerror(errno));
+
+       return 1;
+}
+
 void unlock_ref(struct ref_lock *lock)
 {
        if (lock->lock_fd >= 0) {
diff --git a/refs.h b/refs.h
index a57d43726a0fee5686e2c0d505f250c6b4adc150..51aab1e6bf592ed6412289695d6fe33e5140fab2 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -10,12 +10,13 @@ struct ref_lock {
        int force_write;
 };
 
+#define REF_ISSYMREF 01
+#define REF_ISPACKED 02
+
 /*
  * Calls the specified function for each ref file until it returns nonzero,
  * and returns the value
  */
-#define REF_ISSYMREF 01
-#define REF_ISPACKED 02
 typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
 extern int head_ref(each_ref_fn, void *);
 extern int for_each_ref(each_ref_fn, void *);
@@ -23,6 +24,8 @@ extern int for_each_tag_ref(each_ref_fn, void *);
 extern int for_each_branch_ref(each_ref_fn, void *);
 extern int for_each_remote_ref(each_ref_fn, void *);
 
+extern int peel_ref(const char *, unsigned char *);
+
 /** Reads the refs file specified into sha1 **/
 extern int get_ref_sha1(const char *ref, unsigned char *sha1);
 
@@ -44,4 +47,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned
 /** Returns 0 if target has the right format for a ref. **/
 extern int check_ref_format(const char *target);
 
+/** rename ref, return 0 on success **/
+extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
+
 #endif /* REFS_H */
index 993bb668a205a9fb3e2f5ddd2f378f10ac7905b4..4dae5342eb6e14758c93fa4b2942d7c053111d04 100644 (file)
@@ -6,7 +6,6 @@
 #include "diff.h"
 #include "refs.h"
 #include "revision.h"
-#include <regex.h>
 #include "grep.h"
 
 static char *path_name(struct name_path *path, const char *name)
@@ -344,6 +343,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
 {
        struct commit_list *parent = commit->parents;
+       unsigned left_flag;
 
        if (commit->object.flags & ADDED)
                return;
@@ -388,6 +388,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
        if (revs->no_walk)
                return;
 
+       left_flag = (commit->object.flags & SYMMETRIC_LEFT);
        parent = commit->parents;
        while (parent) {
                struct commit *p = parent->item;
@@ -395,6 +396,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
                parent = parent->next;
 
                parse_commit(p);
+               p->object.flags |= left_flag;
                if (p->object.flags & SEEN)
                        continue;
                p->object.flags |= SEEN;
@@ -640,7 +642,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
-                               a->object.flags |= flags;
+                               a->object.flags |= flags | SYMMETRIC_LEFT;
                        } else
                                a->object.flags |= flags_exclude;
                        b->object.flags |= flags;
@@ -850,6 +852,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->boundary = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--left-right")) {
+                               revs->left_right = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--objects")) {
                                revs->tag_objects = 1;
                                revs->tree_objects = 1;
index 3adab9590a14e25c2659a1933db4af456c263a5b..4585463a44c9faecdcdf4454b735b1e4ba1335aa 100644 (file)
@@ -9,6 +9,7 @@
 #define BOUNDARY       (1u<<5)
 #define BOUNDARY_SHOW  (1u<<6)
 #define ADDED          (1u<<7) /* Parents already parsed and added? */
+#define SYMMETRIC_LEFT (1u<<8)
 
 struct rev_info;
 struct log_info;
@@ -40,6 +41,7 @@ struct rev_info {
                        limited:1,
                        unpacked:1, /* see also ignore_packed below */
                        boundary:1,
+                       left_right:1,
                        parents:1;
 
        /* Diff flags */
diff --git a/rsh.c b/rsh.c
index f34409e1bc5fcf3b6295021043339793b25a8913..5754a230e2c23ce3fea255fccd8726af76c13317 100644 (file)
--- a/rsh.c
+++ b/rsh.c
@@ -1,10 +1,6 @@
-#include <string.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
+#include "cache.h"
 #include "rsh.h"
 #include "quote.h"
-#include "cache.h"
 
 #define COMMAND_SIZE 4096
 
index 61908682b9b251ac49ae655522a3143476a888ea..492ad3e64ce5d2435c029ab032f1221347f37c19 100644 (file)
@@ -1,6 +1,5 @@
 #include "cache.h"
 #include "run-command.h"
-#include <sys/wait.h>
 #include "exec_cmd.h"
 
 int run_command_v_opt(int argc, const char **argv, int flags)
index 447666665bc43090849a66681edec663fe75000c..cc884f3b2debbaadfc3c42cda20074257fa48a65 100644 (file)
@@ -271,6 +271,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        int new_refs;
        int ret = 0;
        int ask_for_status_report = 0;
+       int allow_deleting_refs = 0;
        int expect_status_report = 0;
 
        /* No funny business with the matcher */
@@ -280,6 +281,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        /* Does the other end support the reporting? */
        if (server_supports("report-status"))
                ask_for_status_report = 1;
+       if (server_supports("delete-refs"))
+               allow_deleting_refs = 1;
 
        /* match them up */
        if (!remote_tail)
@@ -299,9 +302,19 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
                char old_hex[60], *new_hex;
+               int delete_ref;
+
                if (!ref->peer_ref)
                        continue;
-               if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
+
+               delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
+               if (delete_ref && !allow_deleting_refs) {
+                       error("remote does not support deleting refs");
+                       ret = -2;
+                       continue;
+               }
+               if (!delete_ref &&
+                   !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
                        if (verbose)
                                fprintf(stderr, "'%s': up-to-date\n", ref->name);
                        continue;
@@ -321,9 +334,13 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                 *
                 * (3) if both new and old are commit-ish, and new is a
                 *     descendant of old, it is OK.
+                *
+                * (4) regardless of all of the above, removing :B is
+                *     always allowed.
                 */
 
                if (!force_update &&
+                   !delete_ref &&
                    !is_zero_sha1(ref->old_sha1) &&
                    !ref->force) {
                        if (!has_sha1_file(ref->old_sha1) ||
@@ -347,12 +364,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                        }
                }
                hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               if (is_zero_sha1(ref->new_sha1)) {
-                       error("cannot happen anymore");
-                       ret = -3;
-                       continue;
-               }
-               new_refs++;
+               if (!delete_ref)
+                       new_refs++;
                strcpy(old_hex, sha1_to_hex(ref->old_sha1));
                new_hex = sha1_to_hex(ref->new_sha1);
 
@@ -366,10 +379,16 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                else
                        packet_write(out, "%s %s %s",
                                     old_hex, new_hex, ref->name);
-               fprintf(stderr, "updating '%s'", ref->name);
-               if (strcmp(ref->name, ref->peer_ref->name))
-                       fprintf(stderr, " using '%s'", ref->peer_ref->name);
-               fprintf(stderr, "\n  from %s\n  to   %s\n", old_hex, new_hex);
+               if (delete_ref)
+                       fprintf(stderr, "deleting '%s'\n", ref->name);
+               else {
+                       fprintf(stderr, "updating '%s'", ref->name);
+                       if (strcmp(ref->name, ref->peer_ref->name))
+                               fprintf(stderr, " using '%s'",
+                                       ref->peer_ref->name);
+                       fprintf(stderr, "\n  from %s\n  to   %s\n",
+                               old_hex, new_hex);
+               }
        }
 
        packet_flush(out);
@@ -387,6 +406,25 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        return ret;
 }
 
+static void verify_remote_names(int nr_heads, char **heads)
+{
+       int i;
+
+       for (i = 0; i < nr_heads; i++) {
+               const char *remote = strchr(heads[i], ':');
+
+               remote = remote ? (remote + 1) : heads[i];
+               switch (check_ref_format(remote)) {
+               case 0: /* ok */
+               case -2: /* ok but a single level -- that is fine for
+                         * a match pattern.
+                         */
+                       continue;
+               }
+               die("remote part of refspec is not a valid name in %s",
+                   heads[i]);
+       }
+}
 
 int main(int argc, char **argv)
 {
@@ -438,6 +476,8 @@ int main(int argc, char **argv)
                usage(send_pack_usage);
        if (heads && send_all)
                usage(send_pack_usage);
+       verify_remote_names(nr_heads, heads);
+
        pid = git_connect(fd, dest, exec);
        if (pid < 0)
                return 1;
index 09456d23f8447711f783178ff4ea5af8f92828e2..1c4df5b73e9dff900c69c0994eb21105614511b6 100644 (file)
@@ -1013,7 +1013,7 @@ void packed_object_info_detail(struct packed_git *p,
        for (;;) {
                switch (kind) {
                default:
-                       die("corrupted pack file %s containing object of kind %d",
+                       die("pack %s contains unknown object type %d",
                            p->pack_name, kind);
                case OBJ_COMMIT:
                case OBJ_TREE:
@@ -1063,7 +1063,7 @@ static int packed_object_info(struct packed_git *p, unsigned long offset,
                strcpy(type, type_names[kind]);
                break;
        default:
-               die("corrupted pack file %s containing object of kind %d",
+               die("pack %s contains unknown object type %d",
                    p->pack_name, kind);
        }
        if (sizep)
@@ -1261,7 +1261,7 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1,
        
 }
 
-int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+static int sha1_loose_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
 {
        int status;
        unsigned long mapsize, size;
@@ -1270,20 +1270,8 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
        char hdr[128];
 
        map = map_sha1_file(sha1, &mapsize);
-       if (!map) {
-               struct pack_entry e;
-
-               if (!find_pack_entry(sha1, &e, NULL)) {
-                       reprepare_packed_git();
-                       if (!find_pack_entry(sha1, &e, NULL))
-                               return error("unable to find %s", sha1_to_hex(sha1));
-               }
-               if (use_packed_git(e.p))
-                       die("cannot map packed file");
-               status = packed_object_info(e.p, e.offset, type, sizep);
-               unuse_packed_git(e.p);
-               return status;
-       }
+       if (!map)
+               return error("unable to find %s", sha1_to_hex(sha1));
        if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
                status = error("unable to unpack %s header",
                               sha1_to_hex(sha1));
@@ -1299,6 +1287,23 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
        return status;
 }
 
+int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+{
+       int status;
+       struct pack_entry e;
+
+       if (!find_pack_entry(sha1, &e, NULL)) {
+               reprepare_packed_git();
+               if (!find_pack_entry(sha1, &e, NULL))
+                       return sha1_loose_object_info(sha1, type, sizep);
+       }
+       if (use_packed_git(e.p))
+               die("cannot map packed file");
+       status = packed_object_info(e.p, e.offset, type, sizep);
+       unuse_packed_git(e.p);
+       return status;
+}
+
 static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size)
 {
        struct pack_entry e;
index 20b15eab57e1c4f7c4b306010d7d12bff2308141..0b52ae15cbd216bff5002c89e0c8af84ea68ed1a 100644 (file)
@@ -12,8 +12,6 @@
 #include "rsh.h"
 #include "refs.h"
 
-#include <string.h>
-
 static unsigned char local_version = 1;
 static unsigned char remote_version;
 
index 9d9d8bed915483abbc2ebb340e0881ae4e296bd4..7f14b0fb59bd7e14979b002f441ae84ff5b0e9a2 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -1,7 +1,5 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include "strbuf.h"
 #include "cache.h"
+#include "strbuf.h"
 
 void strbuf_init(struct strbuf *sb) {
        sb->buf = NULL;
index 89835093fbadf3c55cdf42f7490c6bac98ef2553..250a19019c1f494897c5e43437b93c3f6a80cace 100644 (file)
@@ -13,10 +13,6 @@ 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
-endif
-
 all: $(T) clean
 
 $(T):
@@ -27,12 +23,9 @@ clean:
 
 # 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
+       $(MAKE) $(TSVN) GIT_SVN_DELTA_FETCH=1 \
+                               GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
+       $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
 
 .PHONY: $(T) clean
 .NOTPARALLEL:
index 29a1e72c61eb6e67a70c12361dada49fd77fad49..99ada71349c7eaa0536bba1d622f7f7ffa56110e 100644 (file)
@@ -7,17 +7,18 @@ then
        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
+perl -e 'use SVN::Core; $SVN::Core::VERSION gt "1.1.0" or die' >/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
+       test_expect_success 'Perl SVN libraries not found, skipping test' :
+       test_done
+       exit
 fi
 
+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 $? -ne 1
 then
@@ -45,6 +46,6 @@ else
        svnadmin create "$svnrepo"
 fi
 
-svnrepo="file://$svnrepo/test-git-svn"
+svnrepo="file://$svnrepo"
 
 
index 6aff0b808cbf6ccf973070898976854b1f874b5b..0cd1c41866c6ca344ed99fae3d71014dd9321e2d 100755 (executable)
@@ -19,11 +19,7 @@ modification *should* take notice and update the test vectors here.
 '
 
 ################################################################
-# It appears that people are getting bitten by not installing
-# 'merge' (usually part of RCS package in binary distributions)
-# or have too old python without subprocess.  Check them and error
-# out before running any tests.  Also catch the bogosity of trying
-# to run tests without building while we are at it.
+# It appears that people try to run tests without building...
 
 ../git >/dev/null
 if test $? != 1
@@ -32,22 +28,8 @@ then
        exit 1
 fi
 
-merge >/dev/null 2>/dev/null
-if test $? = 127
-then
-       echo >&2 'You do not seem to have "merge" installed.
-Please check INSTALL document.'
-       exit 1
-fi
-
 . ./test-lib.sh
 
-test "$no_python" || "$PYTHON" -c 'import subprocess' || {
-       echo >&2 'Your python seem to lack "subprocess" module.
-Please check INSTALL document.'
-       exit 1
-}
-
 ################################################################
 # init-db has been done in an empty repository.
 # make sure it is empty.
@@ -290,4 +272,13 @@ test_expect_success \
         wc -l) &&
      test $numparent = 1'
 
+test_expect_success 'update-index D/F conflict' '
+       mv path0 tmp &&
+       mv path2 path0 &&
+       mv tmp path2 &&
+       git update-index --add --replace path2 path0/file2 &&
+       numpath0=$(git ls-files path0 | wc -l) &&
+       test $numpath0 = 1
+'
+
 test_done
index 018fbea450b3481bea2586769de7418dea929e29..4f664f6adfb6153058848d3a3016c34e08b9425c 100755 (executable)
@@ -8,23 +8,27 @@ test_description='read-tree -m -u checks working tree files'
 
 test_expect_success 'two-way setup' '
 
+       mkdir subdir &&
        echo >file1 file one &&
        echo >file2 file two &&
-       git update-index --add file1 file2 &&
+       echo >subdir/file1 file one in subdirectory &&
+       echo >subdir/file2 file two in subdirectory &&
+       git update-index --add file1 file2 subdir/file1 subdir/file2 &&
        git commit -m initial &&
 
        git branch side &&
        git tag -f branch-point &&
 
        echo file2 is not tracked on the master anymore &&
-       rm -f file2 &&
-       git update-index --remove file2 &&
-       git commit -a -m "master removes file2"
+       rm -f file2 subdir/file2 &&
+       git update-index --remove file2 subdir/file2 &&
+       git commit -a -m "master removes file2 and subdir/file2"
 '
 
 test_expect_success 'two-way not clobbering' '
 
        echo >file2 master creates untracked file2 &&
+       echo >subdir/file2 master creates untracked subdir/file2 &&
        if err=`git read-tree -m -u master side 2>&1`
        then
                echo should have complained
@@ -34,20 +38,82 @@ test_expect_success 'two-way not clobbering' '
        fi
 '
 
+echo file2 >.gitignore
+
+test_expect_success 'two-way with incorrect --exclude-per-directory (1)' '
+
+       if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+test_expect_success 'two-way with incorrect --exclude-per-directory (2)' '
+
+       if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+test_expect_success 'two-way clobbering a ignored file' '
+
+       git read-tree -m -u --exclude-per-directory=.gitignore master side
+'
+
+rm -f .gitignore
+
 # three-tree test
 
-test_expect_success 'three-way not complaining' '
+test_expect_success 'three-way not complaining on an untracked path in both' '
 
-       rm -f file2 &&
+       rm -f file2 subdir/file2 &&
        git checkout side &&
        echo >file3 file three &&
-       git update-index --add file3 &&
-       git commit -a -m "side adds file3" &&
+       echo >subdir/file3 file three &&
+       git update-index --add file3 subdir/file3 &&
+       git commit -a -m "side adds file3 and removes file2" &&
 
        git checkout master &&
        echo >file2 file two is untracked on the master side &&
+       echo >subdir/file2 file two is untracked on the master side &&
 
        git-read-tree -m -u branch-point master side
 '
 
+test_expect_success 'three-way not cloberring a working tree file' '
+
+       git reset --hard &&
+       rm -f file2 subdir/file2 file3 subdir/file3 &&
+       git checkout master &&
+       echo >file3 file three created in master, untracked &&
+       echo >subdir/file3 file three created in master, untracked &&
+       if err=`git read-tree -m -u branch-point master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+echo >.gitignore file3
+
+test_expect_success 'three-way not complaining on an untracked file' '
+
+       git reset --hard &&
+       rm -f file2 subdir/file2 file3 subdir/file3 &&
+       git checkout master &&
+       echo >file3 file three created in master, untracked &&
+       echo >subdir/file3 file three created in master, untracked &&
+
+       git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side
+'
+
 test_done
index 0272dd429302534846e3a7f221a520bac2907ca8..eebe643bda9dd5180e8435a59c7510ba97e59144 100755 (executable)
@@ -37,8 +37,6 @@ test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tre
 
 output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
 
-test_expect_success 'commit' "test 'Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb' = \"$output\""
-
 git-diff-index -p HEAD > diff.output
 test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output'
 
index 0de2497746e31f3dce418dbd248ff5d3e7cb939a..e48a4ecdcf7129da1431928bdb942eae8c3e6515 100755 (executable)
@@ -265,6 +265,16 @@ EOF
 test_expect_success '--get-regexp' \
        'git-repo-config --get-regexp in > output && cmp output expect'
 
+git-repo-config --add nextsection.nonewline "wow4 for you"
+
+cat > expect << EOF
+wow2 for me
+wow4 for you
+EOF
+
+test_expect_success '--add' \
+       'git-repo-config --get-all nextsection.nonewline > output && cmp output expect'
+
 cat > .git/config << EOF
 [novalue]
        variable
@@ -333,5 +343,53 @@ EOF
 
 test_expect_success '--set in alternative GIT_CONFIG' 'cmp other-config expect'
 
+cat > .git/config << EOF
+# Hallo
+       #Bello
+[branch "eins"]
+       x = 1
+[branch.eins]
+       y = 1
+       [branch "1 234 blabl/a"]
+weird
+EOF
+
+test_expect_success "rename section" \
+       "git-repo-config --rename-section branch.eins branch.zwei"
+
+cat > expect << EOF
+# Hallo
+       #Bello
+[branch "zwei"]
+       x = 1
+[branch "zwei"]
+       y = 1
+       [branch "1 234 blabl/a"]
+weird
+EOF
+
+test_expect_success "rename succeeded" "diff -u expect .git/config"
+
+test_expect_failure "rename non-existing section" \
+       'git-repo-config --rename-section branch."world domination" branch.drei'
+
+test_expect_success "rename succeeded" "diff -u expect .git/config"
+
+test_expect_success "rename another section" \
+       'git-repo-config --rename-section branch."1 234 blabl/a" branch.drei'
+
+cat > expect << EOF
+# Hallo
+       #Bello
+[branch "zwei"]
+       x = 1
+[branch "zwei"]
+       y = 1
+[branch "drei"]
+weird
+EOF
+
+test_expect_success "rename succeeded" "diff -u expect .git/config"
+
 test_done
 
index 6a917f2ff48661a5d70fdcbcc700cbf659004818..5637cb5eac2fa77ec3882263418c363a4da8a69a 100755 (executable)
@@ -63,8 +63,8 @@ test_expect_failure \
        "test $B"' = $(cat .git/'"$m"')'
 rm -f .git/$m
 
-mkdir -p .git/logs/refs/heads
-touch .git/logs/refs/heads/master
+: a repository with working tree always has reflog these days...
+: >.git/logs/refs/heads/master
 test_expect_success \
        "create $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
index acb54b6a079b2e790c3bf6eb58007188af7ef1a1..a6ea0f6a196c5285c229021c96bb83f33a3aa748 100755 (executable)
@@ -70,4 +70,51 @@ test_expect_success \
         git-branch -d l/m &&
         git-branch l'
 
+test_expect_success \
+    'git branch -m m m/m should work' \
+       'git-branch -l m &&
+        git-branch -m m m/m &&
+        test -f .git/logs/refs/heads/m/m'
+
+test_expect_success \
+    'git branch -m n/n n should work' \
+       'git-branch -l n/n &&
+        git-branch -m n/n n
+        test -f .git/logs/refs/heads/n'
+
+test_expect_failure \
+    'git branch -m o/o o should fail when o/p exists' \
+       'git-branch o/o &&
+        git-branch o/p &&
+        git-branch -m o/o o'
+
+test_expect_failure \
+    'git branch -m q r/q should fail when r exists' \
+       'git-branch q &&
+         git-branch r &&
+         git-branch -m q r/q'
+
+git-repo-config branch.s/s.dummy Hello
+
+test_expect_success \
+    'git branch -m s/s s should work when s/t is deleted' \
+       'git-branch -l s/s &&
+        test -f .git/logs/refs/heads/s/s &&
+        git-branch -l s/t &&
+        test -f .git/logs/refs/heads/s/t &&
+        git-branch -d s/t &&
+        git-branch -m s/s s &&
+        test -f .git/logs/refs/heads/s'
+
+test_expect_success 'config information was renamed, too' \
+       "test $(git-repo-config branch.s.dummy) = Hello &&
+        ! git-repo-config branch.s/s/dummy"
+
+test_expect_failure \
+    'git-branch -m u v should fail when the reflog for u is a symlink' \
+    'git-branch -l u &&
+     mv .git/logs/refs/heads/u real-u &&
+     ln -s real-u .git/logs/refs/heads/u &&
+     git-branch -m u v'
+
 test_done
index 71c454356fbbcd5f67bdfc0b6ef7785fa8b712c1..ed37141b6e5afd8294c5961a1ab96c634a5f4a82 100755 (executable)
@@ -73,6 +73,7 @@ test_expect_success setup '
        for i in 1 2; do echo $i; done >>dir/sub &&
        git update-index file0 dir/sub &&
 
+       git repo-config log.showroot false &&
        git commit --amend &&
        git show-branch
 '
index 1bc5b7a41295426dfdd0b75aaf7b2ec4226f257a..adf4993bacf05b4ccb0b447910dda6a2c453bc83 100755 (executable)
@@ -109,12 +109,10 @@ index d99af23..8b32fb5 100644
 +      whitespace at beginning
  whitespace change
 -whitespace in the middle
--whitespace at end
 +white space in the middle
-+whitespace at end  
+ whitespace at end
  unchanged line
--CR at endQ
-+CR at end
+ CR at endQ
 EOF
 git-diff -b > out
 test_expect_success 'another test, with -b' 'diff -u expect out'
index 8afb89971752fe4a91b9b7c62b4b0a6b69a8272b..28744b35e10602cf5076fbf5c62b3e767a847c0c 100755 (executable)
@@ -64,6 +64,16 @@ test_expect_success \
        cmp victim/.git/refs/heads/master .git/refs/heads/master
 '
 
+test_expect_success \
+        'push can be used to delete a ref' '
+       cd victim &&
+       git branch extra master &&
+       cd .. &&
+       test -f victim/.git/refs/heads/extra &&
+       git-send-pack ./victim/.git/ :extra master &&
+       ! test -f victim/.git/refs/heads/extra
+'
+
 unset GIT_CONFIG GIT_CONFIG_LOCAL
 HOME=`pwd`/no-such-directory
 export HOME ;# this way we force the victim/.git/config to be used.
index a11ab0ad41a006f9f20d8ec6172d34e6c7f92d41..90eeeba2a31949a84daeb34b1996eaa4818321e6 100755 (executable)
@@ -23,15 +23,14 @@ test_expect_success "clone and setup child repos" '
        git clone . two &&
        cd two &&
        git repo-config branch.master.remote one &&
-       {
-               echo "URL: ../one/.git/"
-               echo "Pull: refs/heads/master:refs/heads/one"
-       } >.git/remotes/one
+       git repo-config remote.one.url ../one/.git/ &&
+       git repo-config remote.one.fetch refs/heads/master:refs/heads/one &&
        cd .. &&
        git clone . three &&
        cd three &&
        git repo-config branch.master.remote two &&
        git repo-config branch.master.merge refs/heads/one &&
+       mkdir -p .git/remotes &&
        {
                echo "URL: ../two/.git/"
                echo "Pull: refs/heads/master:refs/heads/two"
diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh
new file mode 100644 (file)
index 0000000..5d9b6f3
--- /dev/null
@@ -0,0 +1,116 @@
+#!/bin/sh
+
+test_description='RCS merge replacement: merge-file'
+. ./test-lib.sh
+
+cat > orig.txt << EOF
+Dominus regit me,
+et nihil mihi deerit.
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+EOF
+
+cat > new1.txt << EOF
+Dominus regit me,
+et nihil mihi deerit.
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+Nam et si ambulavero in medio umbrae mortis,
+non timebo mala, quoniam tu mecum es:
+virga tua et baculus tuus ipsa me consolata sunt.
+EOF
+
+cat > new2.txt << EOF
+Dominus regit me, et nihil mihi deerit.
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+EOF
+
+cat > new3.txt << EOF
+DOMINUS regit me,
+et nihil mihi deerit.
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+EOF
+
+cat > new4.txt << EOF
+Dominus regit me, et nihil mihi deerit.
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+EOF
+echo -n "propter nomen suum." >> new4.txt
+
+cp new1.txt test.txt
+test_expect_success "merge without conflict" \
+       "git-merge-file test.txt orig.txt new2.txt"
+
+cp new1.txt test2.txt
+test_expect_success "merge without conflict (missing LF at EOF)" \
+       "git-merge-file test2.txt orig.txt new2.txt"
+
+test_expect_success "merge result added missing LF" \
+       "diff -u test.txt test2.txt"
+
+cp test.txt backup.txt
+test_expect_failure "merge with conflicts" \
+       "git-merge-file test.txt orig.txt new3.txt"
+
+cat > expect.txt << EOF
+<<<<<<< test.txt
+Dominus regit me, et nihil mihi deerit.
+=======
+DOMINUS regit me,
+et nihil mihi deerit.
+>>>>>>> new3.txt
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+Nam et si ambulavero in medio umbrae mortis,
+non timebo mala, quoniam tu mecum es:
+virga tua et baculus tuus ipsa me consolata sunt.
+EOF
+
+test_expect_success "expected conflict markers" "diff -u test.txt expect.txt"
+
+cp backup.txt test.txt
+test_expect_failure "merge with conflicts, using -L" \
+       "git-merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
+
+cat > expect.txt << EOF
+<<<<<<< 1
+Dominus regit me, et nihil mihi deerit.
+=======
+DOMINUS regit me,
+et nihil mihi deerit.
+>>>>>>> new3.txt
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+Nam et si ambulavero in medio umbrae mortis,
+non timebo mala, quoniam tu mecum es:
+virga tua et baculus tuus ipsa me consolata sunt.
+EOF
+
+test_expect_success "expected conflict markers, with -L" \
+       "diff -u test.txt expect.txt"
+
+test_done
+
diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6023-merge-rename-nocruft.sh
new file mode 100755 (executable)
index 0000000..69c66cf
--- /dev/null
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+test_description='Merge-recursive merging renames'
+. ./test-lib.sh
+
+test_expect_success setup \
+'
+cat >A <<\EOF &&
+a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+c cccccccccccccccccccccccccccccccccccccccccccccccc
+d dddddddddddddddddddddddddddddddddddddddddddddddd
+e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+f ffffffffffffffffffffffffffffffffffffffffffffffff
+g gggggggggggggggggggggggggggggggggggggggggggggggg
+h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
+k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
+l llllllllllllllllllllllllllllllllllllllllllllllll
+m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+o oooooooooooooooooooooooooooooooooooooooooooooooo
+EOF
+
+cat >M <<\EOF &&
+A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
+C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
+E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
+F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
+H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
+I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
+J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
+K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
+L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
+M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
+O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
+EOF
+
+git add A M &&
+git commit -m "initial has A and M" &&
+git branch white &&
+git branch red &&
+
+git checkout white &&
+sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
+sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
+rm -f A M &&
+git update-index --add --remove A B M N &&
+git commit -m "white renames A->B, M->N" &&
+
+git checkout red &&
+echo created by red >R &&
+git update-index --add R &&
+git commit -m "red creates R" &&
+
+git checkout master'
+
+# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae
+test_expect_success 'merge white into red (A->B,M->N)' \
+'
+       git checkout -b red-white red &&
+       git merge white &&
+       git write-tree >/dev/null || {
+               echo "BAD: merge did not complete"
+               return 1
+       }
+
+       test -f B || {
+               echo "BAD: B does not exist in working directory"
+               return 1
+       }
+       test -f N || {
+               echo "BAD: N does not exist in working directory"
+               return 1
+       }
+       test -f R || {
+               echo "BAD: R does not exist in working directory"
+               return 1
+       }
+
+       test -f A && {
+               echo "BAD: A still exists in working directory"
+               return 1
+       }
+       test -f M && {
+               echo "BAD: M still exists in working directory"
+               return 1
+       }
+       return 0
+'
+
+test_done
diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh
new file mode 100644 (file)
index 0000000..964010e
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='Test merge without common ancestors'
+. ./test-lib.sh
+
+# This scenario is based on a real-world repository of Shawn Pearce.
+
+# 1 - A - D - F
+#   \   X   /
+#     B   X
+#       X   \
+# 2 - C - E - G
+
+export GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100"
+echo 1 > a1
+git add a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1
+
+git checkout -b A master
+echo A > a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1
+
+git checkout -b B master
+echo B > a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1
+
+git checkout -b D A
+git-rev-parse B > .git/MERGE_HEAD
+echo D > a1
+git update-index a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D
+
+git symbolic-ref HEAD refs/heads/other
+echo 2 > a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1
+
+git checkout -b C
+echo C > a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1
+
+git checkout -b E C
+git-rev-parse B > .git/MERGE_HEAD
+echo E > a1
+git update-index a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E
+
+git checkout -b G E
+git-rev-parse A > .git/MERGE_HEAD
+echo G > a1
+git update-index a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G
+
+git checkout -b F D
+git-rev-parse C > .git/MERGE_HEAD
+echo F > a1
+git update-index a1
+GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
+
+test_expect_failure "combined merge conflicts" "git merge -m final G"
+
+cat > expect << EOF
+<<<<<<< HEAD/a1
+F
+=======
+G
+>>>>>>> 26f86b677eb03d4d956dbe108b29cb77061c1e73/a1
+EOF
+
+test_expect_success "result contains a conflict" "diff -u expect a1"
+
+git ls-files --stage > out
+cat > expect << EOF
+100644 f16f906ab60483c100d1241dfc39868de9ec9fcb 1      a1
+100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2      a1
+100644 fd7923529855d0b274795ae3349c5e0438333979 3      a1
+EOF
+
+test_expect_success "virtual trees were processed" "diff -u expect out"
+
+test_done
index 34a3ccd31cee8e82fd8a9867637cf610efae3e4c..0edf19e48d20bf03dc567a3be73a03892f143632 100755 (executable)
@@ -56,7 +56,7 @@ 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 &&
+    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch &&
      svn up $SVN_TREE &&
      test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
 
@@ -70,7 +70,7 @@ 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' \
+    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch' \
     || true
 
 
@@ -85,7 +85,7 @@ git update-index --add -- bar
 git commit -m "$name"
 
 test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
+    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
     || true
 
 
@@ -100,7 +100,7 @@ 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' \
+    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
     || true
 
 
@@ -115,7 +115,7 @@ git update-index --add -- dir
 git commit -m "$name"
 
 test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
+    'git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
     || true
 
 
@@ -127,7 +127,7 @@ git update-index exec.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      svn up $SVN_TREE &&
      test ! -x $SVN_TREE/exec.sh"
 
@@ -138,7 +138,7 @@ git update-index exec.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+    "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      svn up $SVN_TREE &&
      test -x $SVN_TREE/exec.sh"
 
@@ -153,7 +153,7 @@ then
        git commit -m "$name"
 
        test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+           "git-svn set-tree --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
             svn up $SVN_TREE &&
             test -L $SVN_TREE/exec.sh"
 
@@ -164,7 +164,7 @@ then
        git commit -m "$name"
 
        test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+           "git-svn set-tree --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"
@@ -177,7 +177,7 @@ then
        git commit -m "$name"
 
        test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
+           "git-svn set-tree --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 &&
@@ -192,7 +192,7 @@ then
        git update-index exec-2.sh
        git commit -m 'éï∏'
        export LC_ALL="$GIT_SVN_LC_ALL"
-       test_expect_success "$name" "git-svn commit HEAD"
+       test_expect_success "$name" "git-svn set-tree HEAD"
        unset LC_ALL
 else
        echo "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)"
@@ -228,6 +228,9 @@ tree 56a30b966619b863674f5978696f4a3594f2fca9
 tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
 tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
 EOF
+
+echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected
+
 test_expect_success "$name" "diff -u a expected"
 
 test_done
index a5a235f100709f503887e9a69c5b510e0b04534f..5543b07f16d54bb5aa6fab6ab9cde79837a3e68b 100755 (executable)
@@ -74,7 +74,7 @@ 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-svn set-tree remotes/git-svn..mybranch &&
        git pull . remotes/git-svn'
 
 expect='/* $Id$ */'
index d693d183c8e41a4c1e948e690f42a55c985543b3..572aaedc06986505ff7b16b5553632051dfa2c58 100755 (executable)
@@ -21,7 +21,7 @@ test_expect_success 'mirror via 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 &&
+       git-svn set-tree --rmdir HEAD &&
        svn ls -R $svnrepo | grep ^deeply/nested/directory/number/1
        "
 
index cc62d4ece888a3be5c237a00c35699ab152cbdf5..293b98f92850375aa3223b6af85461a22cd151ea 100755 (executable)
@@ -1,6 +1,8 @@
 test_description='git-svn graft-branches'
 . ./lib-git-svn.sh
 
+svnrepo="$svnrepo/test-git-svn"
+
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
index 01488ff78acd8324a04a3f0c5e4788e60148a704..8d2e2fec395a328f4bf6a65af3c7eba5a98cc133 100755 (executable)
@@ -6,13 +6,6 @@
 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 &&
index 746c8277d06a7d9d147df16d6c16efce65bf34a2..6323c7e3ace0dda555ee67b384a197a26bf89dd6 100755 (executable)
@@ -4,13 +4,6 @@
 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 &&
index 58698b3f29966593d1f7063b04e8d07322636572..59b6425ce4e5a265443311b7ed033fe6e8e73add 100755 (executable)
@@ -4,13 +4,6 @@
 test_description='git-svn commit-diff clobber'
 . ./lib-git-svn.sh
 
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: commit-diff clobber needs SVN libraries'
-       test_done
-       exit 0
-fi
-
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
index 6e566d44093c5b6fc4a6a891b0ae9473e672b56d..ca0513b1621961f5fd9e589fc62820d42299eccc 100755 (executable)
@@ -89,18 +89,17 @@ test_expect_success \
      ! git cvsexportcommit -c $id
      )'
 
-# Should fail, but only on the git-cvsexportcommit stage
-test_expect_success \
-    'Fail to remove binary file more than one generation old' \
-    'git reset --hard HEAD^ &&
-     cat F/newfile6.png >>D/newfile4.png &&
-     git commit -a -m "generation 2 (again)" &&
-     rm -f D/newfile4.png &&
-     git commit -a -m "generation 3" &&
-     id=$(git rev-list --max-count=1 HEAD) &&
-     (cd "$CVSWORK" &&
-     ! git cvsexportcommit -c $id
-     )'
+#test_expect_success \
+#    'Fail to remove binary file more than one generation old' \
+#    'git reset --hard HEAD^ &&
+#     cat F/newfile6.png >>D/newfile4.png &&
+#     git commit -a -m "generation 2 (again)" &&
+#     rm -f D/newfile4.png &&
+#     git commit -a -m "generation 3" &&
+#     id=$(git rev-list --max-count=1 HEAD) &&
+#     (cd "$CVSWORK" &&
+#     ! git cvsexportcommit -c $id
+#     )'
 
 # We reuse the state from two tests back here
 
@@ -108,7 +107,7 @@ test_expect_success \
 # fail with gnu patch, so cvsexportcommit must handle that.
 test_expect_success \
     'Remove only binary files' \
-    'git reset --hard HEAD^^^ &&
+    'git reset --hard HEAD^^ &&
      rm -f D/newfile4.png &&
      git commit -a -m "test: remove only a binary file" &&
      id=$(git rev-list --max-count=1 HEAD) &&
@@ -142,4 +141,73 @@ test_expect_success \
      diff F/newfile6.png ../F/newfile6.png
      )'
 
+test_expect_success \
+     'New file with spaces in file name' \
+     'mkdir "G g" &&
+      echo ok then >"G g/with spaces.txt" &&
+      git add "G g/with spaces.txt" && \
+      cp ../test9200a.png "G g/with spaces.png" && \
+      git add "G g/with spaces.png" &&
+      git commit -a -m "With spaces" &&
+      id=$(git rev-list --max-count=1 HEAD) &&
+      (cd "$CVSWORK" &&
+      git-cvsexportcommit -c $id &&
+      test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.1/-kb with spaces.txt/1.1/"
+      )'
+
+test_expect_success \
+     'Update file with spaces in file name' \
+     'echo Ok then >>"G g/with spaces.txt" &&
+      cat ../test9200a.png >>"G g/with spaces.png" && \
+      git add "G g/with spaces.png" &&
+      git commit -a -m "Update with spaces" &&
+      id=$(git rev-list --max-count=1 HEAD) &&
+      (cd "$CVSWORK" &&
+      git-cvsexportcommit -c $id
+      test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/"
+      )'
+
+# This test contains ISO-8859-1 characters
+test_expect_success \
+     'File with non-ascii file name' \
+     'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö &&
+      echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
+      git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
+      cp ../test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+      git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+      git commit -a -m "Går det så går det" && \
+      id=$(git rev-list --max-count=1 HEAD) &&
+      (cd "$CVSWORK" &&
+      git-cvsexportcommit -v -c $id &&
+      test "$(echo $(sort Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/CVS/Entries|cut -d/ -f2,3,5))" = "gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/"
+      )'
+
+test_expect_success \
+     'Mismatching patch should fail' \
+     'date >>"E/newfile5.txt" &&
+      git add "E/newfile5.txt" &&
+      git commit -a -m "Update one" &&
+      date >>"E/newfile5.txt" &&
+      git add "E/newfile5.txt" &&
+      git commit -a -m "Update two" &&
+      id=$(git rev-list --max-count=1 HEAD) &&
+      (cd "$CVSWORK" &&
+      ! git-cvsexportcommit -c $id
+      )'
+
+test_expect_success \
+     'Retain execute bit' \
+     'mkdir G &&
+      echo executeon >G/on &&
+      chmod +x G/on &&
+      echo executeoff >G/off &&
+      git add G/on &&
+      git add G/off &&
+      git commit -a -m "Execute test" &&
+      (cd "$CVSWORK" &&
+      git-cvsexportcommit -c HEAD
+      test -x G/on &&
+      ! test -x G/off
+      )'
+
 test_done
index 3895f167093c6a237ad8337ebf8b3c56ee3700ad..f0f9cd6be01681c90d1914eaa8a40144e2280adf 100755 (executable)
@@ -76,7 +76,8 @@ do
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t; shift ;;
        --no-python)
-               no_python=t; shift ;;
+               # noop now...
+               shift ;;
        *)
                break ;;
        esac
@@ -207,21 +208,10 @@ test_done () {
 # t/ subdirectory and are run in trash subdirectory.
 PATH=$(pwd)/..:$PATH
 GIT_EXEC_PATH=$(pwd)/..
+GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
 HOME=$(pwd)/trash
-export PATH GIT_EXEC_PATH HOME
-
-# Similarly use ../compat/subprocess.py if our python does not
-# have subprocess.py on its own.
-PYTHON=`sed -e '1{
-       s/^#!//
-       q
-}' ../git-merge-recursive-old` || {
-       error "You haven't built things yet, have you?"
-}
-"$PYTHON" -c 'import subprocess' 2>/dev/null || {
-       PYTHONPATH=$(pwd)/../compat
-       export PYTHONPATH
-}
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR HOME
+
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
 test -d ../templates/blt || {
diff --git a/templates/remotes-- b/templates/remotes--
deleted file mode 100644 (file)
index fae8870..0000000
+++ /dev/null
@@ -1 +0,0 @@
-: this is just to ensure the directory exists.
index 93e802759f1906665857c82c71f589b7d636b540..62e8f2387a1cab97ec1c71d1993d082274e17bf5 100644 (file)
@@ -1,6 +1,3 @@
-#include <stdio.h>
-#include <time.h>
-
 #include "cache.h"
 
 int main(int argc, char **argv)
index 1be8ee0c721ec35372fee4e686d9664b143b4345..795aa08377aaa9a229affaa054e243251436f755 100644 (file)
@@ -8,13 +8,7 @@
  * published by the Free Software Foundation.
  */
 
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
+#include "git-compat-util.h"
 #include "delta.h"
 
 static const char usage[] =
diff --git a/tree.c b/tree.c
index ea386e506659c65224cfe55bdc4f8d086171637a..b6f02fecc46ec4633dc1ee75f38bc90761a4fbe3 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -4,7 +4,6 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree-walk.h"
-#include <stdlib.h>
 
 const char *tree_type = "tree";
 
index 47aa804a86d735d0fa84718708877ace962094bf..2e2232cbb07e61de3be74302fba67142a58a857b 100644 (file)
@@ -1,6 +1,5 @@
-#include <signal.h>
-#include <sys/time.h>
 #include "cache.h"
+#include "dir.h"
 #include "tree.h"
 #include "tree-walk.h"
 #include "cache-tree.h"
@@ -77,6 +76,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 {
        int baselen = strlen(base);
        int src_size = len + 1;
+       int i_stk = i_stk;
+       int retval = 0;
+
+       if (o->dir)
+               i_stk = push_exclude_per_directory(o->dir, base, strlen(base));
+
        do {
                int i;
                const char *first;
@@ -143,7 +148,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                }
                /* No name means we're done */
                if (!first)
-                       return 0;
+                       goto leave_directory;
 
                pathlen = strlen(first);
                ce_size = cache_entry_size(baselen + pathlen);
@@ -240,13 +245,20 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                        newbase[baselen + pathlen] = '/';
                        newbase[baselen + pathlen + 1] = '\0';
                        if (unpack_trees_rec(subposns, len, newbase, o,
-                                            indpos, df_conflict_list))
-                               return -1;
+                                            indpos, df_conflict_list)) {
+                               retval = -1;
+                               goto leave_directory;
+                       }
                        free(newbase);
                }
                free(subposns);
                free(src);
        } while (1);
+
+ leave_directory:
+       if (o->dir)
+               pop_exclude_per_directory(o->dir, i_stk);
+       return retval;
 }
 
 /* Unlink the last component and attempt to remove leading
@@ -458,7 +470,7 @@ static void invalidate_ce_path(struct cache_entry *ce)
 
 /*
  * We do not want to remove or overwrite a working tree file that
- * is not tracked.
+ * is not tracked, unless it is ignored.
  */
 static void verify_absent(const char *path, const char *action,
                struct unpack_trees_options *o)
@@ -467,7 +479,7 @@ static void verify_absent(const char *path, const char *action,
 
        if (o->index_only || o->reset || !o->update)
                return;
-       if (!lstat(path, &st))
+       if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
                die("Untracked working tree file '%s' "
                    "would be %s by merge.", path, action);
 }
index c4601621cd71ac6e750c90bd3ac4b426d8e12369..191f7442f10683c8043288eece36f39166fedc95 100644 (file)
@@ -16,6 +16,7 @@ struct unpack_trees_options {
        int verbose_update;
        int aggressive;
        const char *prefix;
+       struct dir_struct *dir;
        merge_fn_t fn;
 
        int head_idx;
index ddaa72f0a98e9b7f424279b74798bc3cfaadbd48..32b06b2e66099787d621c2557a8a9ee00aec4be6 100644 (file)
@@ -1,6 +1,3 @@
-#include <signal.h>
-#include <sys/wait.h>
-#include <sys/poll.h>
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
@@ -12,9 +9,15 @@
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
-#define THEY_HAVE (1U << 0)
-#define OUR_REF (1U << 1)
-#define WANTED (1U << 2)
+/* bits #0..7 in revision.h, #8..10 in commit.c */
+#define THEY_HAVE      (1u << 11)
+#define OUR_REF                (1u << 12)
+#define WANTED         (1u << 13)
+#define COMMON_KNOWN   (1u << 14)
+#define REACHABLE      (1u << 15)
+
+static unsigned long oldest_have;
+
 static int multi_ack, nr_our_refs;
 static int use_thin_pack, use_ofs_delta;
 static struct object_array have_obj;
@@ -303,11 +306,12 @@ static void create_pack_file(void)
 static int got_sha1(char *hex, unsigned char *sha1)
 {
        struct object *o;
+       int we_knew_they_have = 0;
 
        if (get_sha1_hex(hex, sha1))
                die("git-upload-pack: expected SHA1 object, got '%s'", hex);
        if (!has_sha1_file(sha1))
-               return 0;
+               return -1;
 
        o = lookup_object(sha1);
        if (!(o && o->parsed))
@@ -316,15 +320,84 @@ static int got_sha1(char *hex, unsigned char *sha1)
                die("oops (%s)", sha1_to_hex(sha1));
        if (o->type == OBJ_COMMIT) {
                struct commit_list *parents;
+               struct commit *commit = (struct commit *)o;
                if (o->flags & THEY_HAVE)
-                       return 0;
-               o->flags |= THEY_HAVE;
-               for (parents = ((struct commit*)o)->parents;
+                       we_knew_they_have = 1;
+               else
+                       o->flags |= THEY_HAVE;
+               if (!oldest_have || (commit->date < oldest_have))
+                       oldest_have = commit->date;
+               for (parents = commit->parents;
                     parents;
                     parents = parents->next)
                        parents->item->object.flags |= THEY_HAVE;
        }
-       add_object_array(o, NULL, &have_obj);
+       if (!we_knew_they_have) {
+               add_object_array(o, NULL, &have_obj);
+               return 1;
+       }
+       return 0;
+}
+
+static int reachable(struct commit *want)
+{
+       struct commit_list *work = NULL;
+
+       insert_by_date(want, &work);
+       while (work) {
+               struct commit_list *list = work->next;
+               struct commit *commit = work->item;
+               free(work);
+               work = list;
+
+               if (commit->object.flags & THEY_HAVE) {
+                       want->object.flags |= COMMON_KNOWN;
+                       break;
+               }
+               if (!commit->object.parsed)
+                       parse_object(commit->object.sha1);
+               if (commit->object.flags & REACHABLE)
+                       continue;
+               commit->object.flags |= REACHABLE;
+               if (commit->date < oldest_have)
+                       continue;
+               for (list = commit->parents; list; list = list->next) {
+                       struct commit *parent = list->item;
+                       if (!(parent->object.flags & REACHABLE))
+                               insert_by_date(parent, &work);
+               }
+       }
+       want->object.flags |= REACHABLE;
+       clear_commit_marks(want, REACHABLE);
+       free_commit_list(work);
+       return (want->object.flags & COMMON_KNOWN);
+}
+
+static int ok_to_give_up(void)
+{
+       int i;
+
+       if (!have_obj.nr)
+               return 0;
+
+       for (i = 0; i < want_obj.nr; i++) {
+               struct object *want = want_obj.objects[i].item;
+
+               if (want->flags & COMMON_KNOWN)
+                       continue;
+               want = deref_tag(want, "a want line", 0);
+               if (!want || want->type != OBJ_COMMIT) {
+                       /* no way to tell if this is reachable by
+                        * looking at the ancestry chain alone, so
+                        * leave a note to ourselves not to worry about
+                        * this object anymore.
+                        */
+                       want_obj.objects[i].item->flags |= COMMON_KNOWN;
+                       continue;
+               }
+               if (!reachable((struct commit *)want))
+                       return 0;
+       }
        return 1;
 }
 
@@ -349,7 +422,13 @@ static int get_common_commits(void)
                }
                len = strip(line, len);
                if (!strncmp(line, "have ", 5)) {
-                       if (got_sha1(line+5, sha1)) {
+                       switch (got_sha1(line+5, sha1)) {
+                       case -1: /* they have what we do not */
+                               if (multi_ack && ok_to_give_up())
+                                       packet_write(1, "ACK %s continue\n",
+                                                    sha1_to_hex(sha1));
+                               break;
+                       default:
                                memcpy(hex, sha1_to_hex(sha1), 41);
                                if (multi_ack) {
                                        const char *msg = "ACK %s continue\n";
@@ -358,6 +437,7 @@ static int get_common_commits(void)
                                }
                                else if (have_obj.nr == 1)
                                        packet_write(1, "ACK %s\n", hex);
+                               break;
                        }
                        continue;
                }
diff --git a/var.c b/var.c
index a57a33b81ac6c9cb5ec0c833edc21bd66428d976..39977b949a1be61f1cf35512bd729862de842108 100644 (file)
--- a/var.c
+++ b/var.c
@@ -4,9 +4,6 @@
  * Copyright (C) Eric Biederman, 2005
  */
 #include "cache.h"
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
 
 static const char var_usage[] = "git-var [-l | <variable>]";
 
index de1be5bc66de634e41c7ef3dee08209631249e13..db427384ff454aa07e38c588537cbaa6b835dee6 100644 (file)
@@ -1,6 +1,6 @@
+#include "cache.h"
 #include "wt-status.h"
 #include "color.h"
-#include "cache.h"
 #include "object.h"
 #include "dir.h"
 #include "commit.h"
@@ -15,12 +15,14 @@ static char wt_status_colors[][COLOR_MAXLEN] = {
        "\033[31m", /* WT_STATUS_CHANGED: red */
        "\033[31m", /* WT_STATUS_UNTRACKED: red */
 };
+static const char* use_add_msg = "use \"git add file1 file2\" to include for commit";
 
 static int parse_status_slot(const char *var, int offset)
 {
        if (!strcasecmp(var+offset, "header"))
                return WT_STATUS_HEADER;
-       if (!strcasecmp(var+offset, "updated"))
+       if (!strcasecmp(var+offset, "updated")
+               || !strcasecmp(var+offset, "added"))
                return WT_STATUS_UPDATED;
        if (!strcasecmp(var+offset, "changed"))
                return WT_STATUS_CHANGED;
@@ -145,7 +147,7 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q,
                if (q->queue[i]->status == 'U')
                        continue;
                if (!shown_header) {
-                       wt_status_print_header("Updated but not checked in",
+                       wt_status_print_header("Added but not yet committed",
                                        "will commit");
                        s->commitable = 1;
                        shown_header = 1;
@@ -162,8 +164,7 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
 {
        int i;
        if (q->nr)
-               wt_status_print_header("Changed but not updated",
-                               "use git-update-index to mark for commit");
+               wt_status_print_header("Changed but not added", use_add_msg);
        for (i = 0; i < q->nr; i++)
                wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
        if (q->nr)
@@ -178,7 +179,7 @@ void wt_status_print_initial(struct wt_status *s)
        read_cache();
        if (active_nr) {
                s->commitable = 1;
-               wt_status_print_header("Updated but not checked in",
+               wt_status_print_header("Added but not yet committed",
                                "will commit");
        }
        for (i = 0; i < active_nr; i++) {
@@ -249,8 +250,7 @@ static void wt_status_print_untracked(const struct wt_status *s)
                                continue;
                }
                if (!shown_header) {
-                       wt_status_print_header("Untracked files",
-                               "use \"git add\" to add to commit");
+                       wt_status_print_header("Untracked files", use_add_msg);
                        shown_header = 1;
                }
                color_printf(color(WT_STATUS_HEADER), "#\t");
@@ -271,7 +271,7 @@ static void wt_status_print_verbose(struct wt_status *s)
 
 void wt_status_print(struct wt_status *s)
 {
-       if (s->branch && strcmp(s->branch, "refs/heads/master"))
+       if (s->branch)
                color_printf_ln(color(WT_STATUS_HEADER),
                        "# On branch %s", s->branch);
 
@@ -292,16 +292,18 @@ void wt_status_print(struct wt_status *s)
        if (s->verbose && !s->is_initial)
                wt_status_print_verbose(s);
        if (!s->commitable)
-               printf("%s\n", s->amend ? "# No changes" : "nothing to commit");
+               printf("%s (%s)\n",
+                       s->amend ? "# No changes" : "nothing to commit",
+                       use_add_msg);
 }
 
 int git_status_config(const char *k, const char *v)
 {
-       if (!strcmp(k, "status.color")) {
+       if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
                wt_status_use_color = git_config_colorbool(k, v);
                return 0;
        }
-       if (!strncmp(k, "status.color.", 13)) {
+       if (!strncmp(k, "status.color.", 13) || !strncmp(k, "color.status", 13)) {
                int slot = parse_status_slot(k, 13);
                color_parse(v, k, wt_status_colors[slot]);
        }
index c9f817818a781164b63c78be2af71a5d21e35d6f..fa409d5234009bf6ddac8803e8d20ed1b23a3258 100644 (file)
@@ -49,6 +49,9 @@ extern "C" {
 #define XDL_BDOP_CPY 2
 #define XDL_BDOP_INSB 3
 
+#define XDL_MERGE_MINIMAL 0
+#define XDL_MERGE_EAGER 1
+#define XDL_MERGE_ZEALOUS 2
 
 typedef struct s_mmfile {
        char *ptr;
@@ -90,6 +93,10 @@ long xdl_mmfile_size(mmfile_t *mmf);
 int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
             xdemitconf_t const *xecfg, xdemitcb_t *ecb);
 
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
+               mmfile_t *mf2, const char *name2,
+               xpparam_t const *xpp, int level, mmbuffer_t *result);
+
 #ifdef __cplusplus
 }
 #endif /* #ifdef __cplusplus */
index d76e76a0e6d987cdec985e304ad682020fef5e1f..9aeebc473b11a3dbfa5b52930b4a8e5a35f22215 100644 (file)
@@ -45,7 +45,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
                      long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
                      xdalgoenv_t *xenv);
 static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2);
-static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags);
 
 
 
@@ -397,7 +396,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
 }
 
 
-static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
        long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec;
        char *rchg = xdf->rchg, *rchgo = xdfo->rchg;
        xrecord_t **recs = xdf->recs;
index d3b72716b58273169904a4351f02fab7ab2fc6e9..472aeaecfab85e7276baaceb446d2095eeab01fd 100644 (file)
@@ -50,6 +50,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
                 long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv);
 int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
                xdfenv_t *xe);
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags);
 int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr);
 void xdl_free_script(xdchange_t *xscr);
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
new file mode 100644 (file)
index 0000000..352207e
--- /dev/null
@@ -0,0 +1,419 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+typedef struct s_xdmerge {
+       struct s_xdmerge *next;
+       /*
+        * 0 = conflict,
+        * 1 = no conflict, take first,
+        * 2 = no conflict, take second.
+        */
+       int mode;
+       long i1, i2;
+       long chg1, chg2;
+} xdmerge_t;
+
+static int xdl_append_merge(xdmerge_t **merge, int mode,
+               long i1, long chg1, long i2, long chg2)
+{
+       xdmerge_t *m = *merge;
+       if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) {
+               if (mode != m->mode)
+                       m->mode = 0;
+               m->chg1 = i1 + chg1 - m->i1;
+               m->chg2 = i2 + chg2 - m->i2;
+       } else {
+               m = xdl_malloc(sizeof(xdmerge_t));
+               if (!m)
+                       return -1;
+               m->next = NULL;
+               m->mode = mode;
+               m->i1 = i1;
+               m->chg1 = chg1;
+               m->i2 = i2;
+               m->chg2 = chg2;
+               if (*merge)
+                       (*merge)->next = m;
+               *merge = m;
+       }
+       return 0;
+}
+
+static int xdl_cleanup_merge(xdmerge_t *c)
+{
+       int count = 0;
+       xdmerge_t *next_c;
+
+       /* were there conflicts? */
+       for (; c; c = next_c) {
+               if (c->mode == 0)
+                       count++;
+               next_c = c->next;
+               free(c);
+       }
+       return count;
+}
+
+static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
+               int line_count, long flags)
+{
+       int i;
+       xrecord_t **rec1 = xe1->xdf2.recs + i1;
+       xrecord_t **rec2 = xe2->xdf2.recs + i2;
+
+       for (i = 0; i < line_count; i++) {
+               int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
+                       rec2[i]->ptr, rec2[i]->size, flags);
+               if (!result)
+                       return -1;
+       }
+       return 0;
+}
+
+static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
+{
+       xrecord_t **recs = xe->xdf2.recs + i;
+       int size = 0;
+
+       if (count < 1)
+               return 0;
+
+       for (i = 0; i < count; size += recs[i++]->size)
+               if (dest)
+                       memcpy(dest + size, recs[i]->ptr, recs[i]->size);
+       if (add_nl) {
+               i = recs[count - 1]->size;
+               if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') {
+                       if (dest)
+                               dest[size] = '\n';
+                       size++;
+               }
+       }
+       return size;
+}
+
+static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
+               xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest)
+{
+       const int marker_size = 7;
+       int marker1_size = (name1 ? strlen(name1) + 1 : 0);
+       int marker2_size = (name2 ? strlen(name2) + 1 : 0);
+       int conflict_marker_size = 3 * (marker_size + 1)
+               + marker1_size + marker2_size;
+       int size, i1, j;
+
+       for (size = i1 = 0; m; m = m->next) {
+               if (m->mode == 0) {
+                       size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0,
+                                       dest ? dest + size : NULL);
+                       if (dest) {
+                               for (j = 0; j < marker_size; j++)
+                                       dest[size++] = '<';
+                               if (marker1_size) {
+                                       dest[size] = ' ';
+                                       memcpy(dest + size + 1, name1,
+                                                       marker1_size - 1);
+                                       size += marker1_size;
+                               }
+                               dest[size++] = '\n';
+                       } else
+                               size += conflict_marker_size;
+                       size += xdl_recs_copy(xe1, m->i1, m->chg1, 1,
+                                       dest ? dest + size : NULL);
+                       if (dest) {
+                               for (j = 0; j < marker_size; j++)
+                                       dest[size++] = '=';
+                               dest[size++] = '\n';
+                       }
+                       size += xdl_recs_copy(xe2, m->i2, m->chg2, 1,
+                                       dest ? dest + size : NULL);
+                       if (dest) {
+                               for (j = 0; j < marker_size; j++)
+                                       dest[size++] = '>';
+                               if (marker2_size) {
+                                       dest[size] = ' ';
+                                       memcpy(dest + size + 1, name2,
+                                                       marker2_size - 1);
+                                       size += marker2_size;
+                               }
+                               dest[size++] = '\n';
+                       }
+               } else if (m->mode == 1)
+                       size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0,
+                                       dest ? dest + size : NULL);
+               else if (m->mode == 2)
+                       size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1,
+                                       m->i1 + m->chg2 - i1, 0,
+                                       dest ? dest + size : NULL);
+               i1 = m->i1 + m->chg1;
+       }
+       size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0,
+                       dest ? dest + size : NULL);
+       return size;
+}
+
+/*
+ * Sometimes, changes are not quite identical, but differ in only a few
+ * lines. Try hard to show only these few lines as conflicting.
+ */
+static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
+               xpparam_t const *xpp)
+{
+       for (; m; m = m->next) {
+               mmfile_t t1, t2;
+               xdfenv_t xe;
+               xdchange_t *xscr, *x;
+               int i1 = m->i1, i2 = m->i2;
+
+               /* let's handle just the conflicts */
+               if (m->mode)
+                       continue;
+
+               /*
+                * This probably does not work outside git, since
+                * we have a very simple mmfile structure.
+                */
+               t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
+               t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
+                       + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+               t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
+               t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
+                       + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+               if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
+                       return -1;
+               if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
+                   xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 ||
+                   xdl_build_script(&xe, &xscr) < 0) {
+                       xdl_free_env(&xe);
+                       return -1;
+               }
+               if (!xscr) {
+                       /* If this happens, it's a bug. */
+                       xdl_free_env(&xe);
+                       return -2;
+               }
+               x = xscr;
+               m->i1 = xscr->i1 + i1;
+               m->chg1 = xscr->chg1;
+               m->i2 = xscr->i2 + i2;
+               m->chg2 = xscr->chg2;
+               while (xscr->next) {
+                       xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t));
+                       if (!m2) {
+                               xdl_free_env(&xe);
+                               xdl_free_script(x);
+                               return -1;
+                       }
+                       xscr = xscr->next;
+                       m2->next = m->next;
+                       m->next = m2;
+                       m = m2;
+                       m->mode = 0;
+                       m->i1 = xscr->i1 + i1;
+                       m->chg1 = xscr->chg1;
+                       m->i2 = xscr->i2 + i2;
+                       m->chg2 = xscr->chg2;
+               }
+               xdl_free_env(&xe);
+               xdl_free_script(x);
+       }
+       return 0;
+}
+
+/*
+ * level == 0: mark all overlapping changes as conflict
+ * level == 1: mark overlapping changes as conflict only if not identical
+ * level == 2: analyze non-identical changes for minimal conflict set
+ *
+ * returns < 0 on error, == 0 for no conflicts, else number of conflicts
+ */
+static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
+               xdfenv_t *xe2, xdchange_t *xscr2, const char *name2,
+               int level, xpparam_t const *xpp, mmbuffer_t *result) {
+       xdmerge_t *changes, *c;
+       int i1, i2, chg1, chg2;
+
+       c = changes = NULL;
+
+       while (xscr1 && xscr2) {
+               if (!changes)
+                       changes = c;
+               if (xscr1->i1 + xscr1->chg1 < xscr2->i1) {
+                       i1 = xscr1->i2;
+                       i2 = xscr2->i2 - xscr2->i1 + xscr1->i1;
+                       chg1 = xscr1->chg2;
+                       chg2 = xscr1->chg1;
+                       if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) {
+                               xdl_cleanup_merge(changes);
+                               return -1;
+                       }
+                       xscr1 = xscr1->next;
+                       continue;
+               }
+               if (xscr2->i1 + xscr2->chg1 < xscr1->i1) {
+                       i1 = xscr1->i2 - xscr1->i1 + xscr2->i1;
+                       i2 = xscr2->i2;
+                       chg1 = xscr2->chg1;
+                       chg2 = xscr2->chg2;
+                       if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) {
+                               xdl_cleanup_merge(changes);
+                               return -1;
+                       }
+                       xscr2 = xscr2->next;
+                       continue;
+               }
+               if (level < 1 || xscr1->i1 != xscr2->i1 ||
+                               xscr1->chg1 != xscr2->chg1 ||
+                               xscr1->chg2 != xscr2->chg2 ||
+                               xdl_merge_cmp_lines(xe1, xscr1->i2,
+                                       xe2, xscr2->i2,
+                                       xscr1->chg2, xpp->flags)) {
+                       /* conflict */
+                       int off = xscr1->i1 - xscr2->i1;
+                       int ffo = off + xscr1->chg1 - xscr2->chg1;
+
+                       i1 = xscr1->i2;
+                       i2 = xscr2->i2;
+                       if (off > 0)
+                               i1 -= off;
+                       else
+                               i2 += off;
+                       chg1 = xscr1->i2 + xscr1->chg2 - i1;
+                       chg2 = xscr2->i2 + xscr2->chg2 - i2;
+                       if (ffo > 0)
+                               chg2 += ffo;
+                       else
+                               chg1 -= ffo;
+                       if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) {
+                               xdl_cleanup_merge(changes);
+                               return -1;
+                       }
+               }
+
+               i1 = xscr1->i1 + xscr1->chg1;
+               i2 = xscr2->i1 + xscr2->chg1;
+
+               if (i1 >= i2)
+                       xscr2 = xscr2->next;
+               if (i2 >= i1)
+                       xscr1 = xscr1->next;
+       }
+       while (xscr1) {
+               if (!changes)
+                       changes = c;
+               i1 = xscr1->i2;
+               i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;
+               chg1 = xscr1->chg2;
+               chg2 = xscr1->chg1;
+               if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) {
+                       xdl_cleanup_merge(changes);
+                       return -1;
+               }
+               xscr1 = xscr1->next;
+       }
+       while (xscr2) {
+               if (!changes)
+                       changes = c;
+               i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;
+               i2 = xscr2->i2;
+               chg1 = xscr2->chg1;
+               chg2 = xscr2->chg2;
+               if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) {
+                       xdl_cleanup_merge(changes);
+                       return -1;
+               }
+               xscr2 = xscr2->next;
+       }
+       if (!changes)
+               changes = c;
+       /* refine conflicts */
+       if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) {
+               xdl_cleanup_merge(changes);
+               return -1;
+       }
+       /* output */
+       if (result) {
+               int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2,
+                       changes, NULL);
+               result->ptr = xdl_malloc(size);
+               if (!result->ptr) {
+                       xdl_cleanup_merge(changes);
+                       return -1;
+               }
+               result->size = size;
+               xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes,
+                               result->ptr);
+       }
+       return xdl_cleanup_merge(changes);
+}
+
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
+               mmfile_t *mf2, const char *name2,
+               xpparam_t const *xpp, int level, mmbuffer_t *result) {
+       xdchange_t *xscr1, *xscr2;
+       xdfenv_t xe1, xe2;
+       int status;
+
+       result->ptr = NULL;
+       result->size = 0;
+
+       if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 ||
+                       xdl_do_diff(orig, mf2, xpp, &xe2) < 0) {
+               return -1;
+       }
+       if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 ||
+           xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 ||
+           xdl_build_script(&xe1, &xscr1) < 0) {
+               xdl_free_env(&xe1);
+               return -1;
+       }
+       if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 ||
+           xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 ||
+           xdl_build_script(&xe2, &xscr2) < 0) {
+               xdl_free_env(&xe2);
+               return -1;
+       }
+       status = 0;
+       if (xscr1 || xscr2) {
+               if (!xscr1) {
+                       result->ptr = xdl_malloc(mf2->size);
+                       memcpy(result->ptr, mf2->ptr, mf2->size);
+                       result->size = mf2->size;
+               } else if (!xscr2) {
+                       result->ptr = xdl_malloc(mf1->size);
+                       memcpy(result->ptr, mf1->ptr, mf1->size);
+                       result->size = mf1->size;
+               } else {
+                       status = xdl_do_merge(&xe1, xscr1, name1,
+                                             &xe2, xscr2, name2,
+                                             level, xpp, result);
+               }
+               xdl_free_script(xscr1);
+               xdl_free_script(xscr2);
+       }
+       xdl_free_env(&xe1);
+       xdl_free_env(&xe2);
+
+       return status;
+}
index 9e4bb47ee97a0fca5120c41365a80531ddb849e4..1b899f32c4b7c3f00c5310a161d0d9afd459f002 100644 (file)
@@ -230,7 +230,8 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
                        while (ptr + 1 < top && isspace(ptr[1])
                                        && ptr[1] != '\n')
                                ptr++;
-                       if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
+                       if (flags & XDF_IGNORE_WHITESPACE_CHANGE
+                                       && ptr[1] != '\n') {
                                ha += (ha << 5);
                                ha ^= (unsigned long) ' ';
                        }