Merge branch 'jc/read-tree-ignore'
authorJunio C Hamano <junkio@cox.net>
Wed, 13 Dec 2006 19:10:24 +0000 (11:10 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 13 Dec 2006 19:10:24 +0000 (11:10 -0800)
* jc/read-tree-ignore:
read-tree: document --exclude-per-directory
Loosen "working file will be lost" check in Porcelain-ish
read-tree: further loosen "working file will be lost" check.

63 files changed:
.gitignore
Documentation/Makefile
Documentation/callouts.xsl
Documentation/config.txt
Documentation/cvs-migration.txt
Documentation/git-add.txt
Documentation/git-branch.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-merge-index.txt
Documentation/git-push.txt
Documentation/git-rerere.txt
Documentation/git-svn.txt
Documentation/tutorial-2.txt
Documentation/tutorial.txt
INSTALL
Makefile
builtin-add.c
builtin-branch.c
builtin-log.c
builtin-merge-file.c [new file with mode: 0644]
builtin-push.c
builtin-shortlog.c
builtin.h
config.c
contrib/completion/git-completion.bash
diff.c
fetch.c
git-am.sh
git-clone.sh
git-commit.sh
git-cvsexportcommit.perl
git-cvsserver.perl
git-merge.sh
git-parse-remote.sh
git-rebase.sh
git-rerere.perl
git-reset.sh
git-svn.perl
git.c
git.spec.in
gitweb/gitweb.perl
index-pack.c
merge-file.c
merge-recursive.c
perl/Makefile
perl/Makefile.PL
receive-pack.c
refs.c
refs.h
send-pack.c
t/t0000-basic.sh
t/t3200-branch.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/t9200-git-cvsexportcommit.sh
unpack-trees.c
wt-status.c
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xmerge.c [new file with mode: 0644]
index 7f2cd55088de2bdd109dbcd6b50b7dfd0c593115..d706dd92c6a63474616c09388fc9744ff055cedc 100644 (file)
@@ -60,6 +60,7 @@ git-mailsplit
 git-merge
 git-merge-base
 git-merge-index
+git-merge-file
 git-merge-tree
 git-merge-octopus
 git-merge-one-file
index c00f5f62b741150e92f38e6394bfa6f111b82b6e..d68bc4a788743412719b3590487ec6dc7efee3e1 100644 (file)
@@ -56,8 +56,8 @@ man7: $(DOC_MAN7)
 
 install: man
        $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir)
-       $(INSTALL) $(DOC_MAN1) $(DESTDIR)$(man1dir)
-       $(INSTALL) $(DOC_MAN7) $(DESTDIR)$(man7dir)
+       $(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir)
+       $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
 
 
 #
index ad03755d8fadd016ceb9b3118f457b864902e655..6a361a21367bfed4ae325049556f2e82e7e1dbe4 100644 (file)
        <xsl:apply-templates/>
        <xsl:text>.br&#10;</xsl:text>
 </xsl:template>
+
+<!-- sorry, this is not about callouts, but attempts to work around
+ spurious .sp at the tail of the line docbook stylesheets seem to add -->
+<xsl:template match="simpara">
+  <xsl:variable name="content">
+    <xsl:apply-templates/>
+  </xsl:variable>
+  <xsl:value-of select="normalize-space($content)"/>
+  <xsl:if test="not(ancestor::authorblurb) and
+                not(ancestor::personblurb)">
+    <xsl:text>&#10;&#10;</xsl:text>
+  </xsl:if>
+</xsl:template>
+
 </xsl:stylesheet>
index 9090762819fac988c63eaa17089c0d4c57a8d338..f5a552ee8722dc0e18265164fd6a6b344d691d57 100644 (file)
@@ -125,21 +125,28 @@ 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::
+       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.pager::
        A boolean to enable/disable colored output when the pager is in
        use (default is true).
 
-diff.color::
+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`
@@ -264,19 +271,19 @@ showbranch.default::
        The default set of branches for gitlink:git-show-branch[1].
        See gitlink:git-show-branch[1].
 
-status.color::
+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.
 
-status.color.<slot>::
+color.status.<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>.
+       these variables may be specified as in color.diff.<slot>.
 
 tar.umask::
        By default, gitlink:git-tar-tree[1] sets file and directories modes
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 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 4f5b5d5028e15f693440baa57358ab4c898994e8..71417feba8f4987bb9d7565ec58f82044d343283 100644 (file)
@@ -8,8 +8,9 @@ git-branch - List, create, or delete branches.
 SYNOPSIS
 --------
 [verse]
-'git-branch' [-r] [-a] [-v] [--abbrev=<length>]
+'git-branch' [-r | -a] [-v [--abbrev=<length>]]
 'git-branch' [-l] [-f] <branchname> [<start-point>]
+'git-branch' (-m | -M) [<oldbranch>] <newbranch>
 'git-branch' (-d | -D) <branchname>...
 
 DESCRIPTION
@@ -24,6 +25,12 @@ 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.
@@ -46,6 +53,12 @@ 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 the remote-tracking branches.
 
@@ -53,7 +66,7 @@ OPTIONS
        List both remote-tracking branches and local branches.
 
 -v::
-       Show sha1 and subject message for each head.
+       Show sha1 and commit subjectline for each head.
 
 --abbrev=<length>::
        Alter minimum display length for sha1 in output listing,
@@ -70,6 +83,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
index d5efa00dea7de8429b32dac006d0c5496076303d..985043facab96a29e7cf19df213539312e7b5642 100644 (file)
@@ -16,22 +16,21 @@ SYNOPSIS
 
 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
 -------
index 517a86b238a91a1c853b5fbe331a5cc984e95878..97d66ef4d2c982706be98eaaea056107694e8d8c 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,137 @@ 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.
-
--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.
+       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.
 
 \--::
        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 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 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 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 a45067e16483c2eab509b0f01ed08a82522e384b..c589a9863064ef3e5f12876d40174fb991cf3b59 100644 (file)
@@ -57,11 +57,13 @@ 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.
+       An optional command-line argument may be specified as an
+       alternative to HEAD.
        This is advantageous over 'commit' (below) because it produces
        cleaner, more linear history.
 
index 42b6e7d7d2bbb24d96f29635967945f516b25e15..6389de5ef7e221d84bb9c5443af09fb62960de4c 100644 (file)
@@ -23,6 +23,7 @@ $ echo 'hello world' > file.txt
 $ git add .
 $ git commit -a -m "initial commit"
 Committing initial tree 92b8b694ffb1675e5975148e1121810081dbdffe
+ create mode 100644 file.txt
 $ echo 'hello world!' >file.txt
 $ git commit -a -m "add emphasis"
 ------------------------------------------------
index fe4491de41d1bcaeb56d5152e580eb483b0a12b0..02dede320ca9ddff4a9de0c5bb273bdf5eff9e74 100644 (file)
@@ -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.
+
+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'.
+
 
-then commit as usual.  No special command is required when removing a
-file; just remove it, then tell `commit` about the file as usual.
+Viewing the changelog
+---------------------
 
 At any point you can view the history of your changes using
 
diff --git a/INSTALL b/INSTALL
index 8f69039fb90100f8fba5c908455660b9bcaf552d..b5dd9f0abb2483ad986950785fa95b8d373b60b6 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -82,15 +82,6 @@ Issues of note:
          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
 
index a1861de2cc835cff1d72322bc6510abb1e32c529..2d17fa702795ca08a8ffc3aeaf6393b300401e33 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -279,6 +279,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 \
@@ -326,18 +327,6 @@ ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
        NO_STRLCPY = YesPlease
-       ifndef NO_FINK
-               ifeq ($(shell test -d /sw/lib && echo y),y)
-                       BASIC_CFLAGS += -I/sw/include
-                       BASIC_LDFLAGS += -L/sw/lib
-               endif
-       endif
-       ifndef NO_DARWIN_PORTS
-               ifeq ($(shell test -d /opt/local/lib && echo y),y)
-                       BASIC_CFLAGS += -I/opt/local/include
-                       BASIC_LDFLAGS += -L/opt/local/lib
-               endif
-       endif
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -415,6 +404,21 @@ endif
 -include config.mak.autogen
 -include config.mak
 
+ifeq ($(uname_S),Darwin)
+       ifndef NO_FINK
+               ifeq ($(shell test -d /sw/lib && echo y),y)
+                       BASIC_CFLAGS += -I/sw/include
+                       BASIC_LDFLAGS += -L/sw/lib
+               endif
+       endif
+       ifndef NO_DARWIN_PORTS
+               ifeq ($(shell test -d /opt/local/lib && echo y),y)
+                       BASIC_CFLAGS += -I/opt/local/include
+                       BASIC_LDFLAGS += -L/opt/local/lib
+               endif
+       endif
+endif
+
 ifndef NO_CURL
        ifdef CURLDIR
                # This is still problematic -- gcc does not always want -R.
@@ -734,7 +738,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
 
index febb75ed994b5edc51afc60e2d46483fa9e273f2..b3f920676a958e581c55dcca48e9a8f01b43859b 100644 (file)
@@ -94,9 +94,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];
 
@@ -131,6 +128,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 3d5cb0e4b2de8abf3a9f1af8fadb9c1bc5393967..560309cb154504a8d280f12e3d9d2d3d6d715d7c 100644 (file)
@@ -5,18 +5,71 @@
  * Based on git-branch.sh by Junio C Hamano.
  */
 
+#include "color.h"
 #include "cache.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 | -a] [-v] [--abbrev=<length>] ";
+  "git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [-r | -a] [-v [--abbrev=<length>]]";
 
 
 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)
@@ -183,6 +236,7 @@ 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;
@@ -191,18 +245,38 @@ static void print_ref_list(int kinds, int verbose, int abbrev)
        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;
+               }
+
                c = ' ';
                if (ref_list.list[i].kind == REF_LOCAL_BRANCH &&
-                               !strcmp(ref_list.list[i].name, head))
+                               !strcmp(ref_list.list[i].name, head)) {
                        c = '*';
+                       color = COLOR_BRANCH_CURRENT;
+               }
 
                if (verbose) {
-                       printf("%c %-*s", c, ref_list.maxwidth,
-                              ref_list.list[i].name);
+                       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\n", c, ref_list.list[i].name);
+                       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);
@@ -245,15 +319,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;
+       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];
@@ -277,6 +383,15 @@ 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")) {
                        kinds = REF_REMOTE_BRANCH;
                        continue;
@@ -297,9 +412,21 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        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.");
@@ -311,6 +438,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                delete_branches(argc - i, argv + i, force_delete);
        else if (i == argc)
                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 7acf5d3b0c3393ee700e084470824bb7fd7cf88a..6821a08442603ba28ee2190a9f602f3d8d0f43f3 100644 (file)
@@ -118,7 +118,7 @@ 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_log_config(var, value);
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 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 f1124e261b04ca93118c662f391dec41d9ee9713..3fc43dd7dd89fea2fb7f02e694ed632215002e13 100644 (file)
@@ -188,18 +188,25 @@ static void read_from_stdin(struct path_list *list)
                                bob = buffer + strlen(buffer);
                        else {
                                offset = 8;
-                               while (isspace(bob[-1]))
+                               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))
+                       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, strlen(buffer2));
+                                               buffer2 + i, l2 - i);
+                       }
                }
        }
 }
@@ -236,7 +243,7 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list)
                                        author = scratch;
                                        authorlen = strlen(scratch);
                                } else {
-                                       while (bracket[-1] == ' ')
+                                       if (bracket[-1] == ' ')
                                                bracket--;
 
                                        author = buffer + 7;
index b5116f30e47cb41699b178271b09f0b12854869e..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);
index 3cae3901aa9b50e8d04a8ce2633a1a1ef8ed0f6c..1bdef44a3ad315ecc4cdfa879e9c0881bd82722b 100644 (file)
--- a/config.c
+++ b/config.c
@@ -314,7 +314,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;
        }
index 447ec20467f667ec4ad4260e68bc35904b2b65ce..9c4d23a23c899798613978c1766d08a80ced6f2e 100755 (executable)
@@ -712,10 +712,13 @@ _git_repo_config ()
                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
diff --git a/diff.c b/diff.c
index 33153787b8117396cf906e69e656849ac04f3257..2df14b2469362bfd8a30a45457953d993baae66d 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -60,7 +60,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 +74,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;
@@ -802,7 +802,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);
diff --git a/fetch.c b/fetch.c
index c426c049974fa1bf1aceea9d43d2e2c5ce194e06..663b4b2f42744a2d12ba1d8661b8b0d3645ef82b 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -22,14 +22,15 @@ void pull_say(const char *fmt, const char *hex)
                fprintf(stderr, fmt, hex);
 }
 
-static void report_missing(const char *what, const unsigned char *missing)
+static void report_missing(const struct object *obj)
 {
        char missing_hex[41];
-
-       strcpy(missing_hex, sha1_to_hex(missing));;
-       fprintf(stderr,
-               "Cannot obtain needed %s %s\nwhile processing commit %s.\n",
-               what, missing_hex, sha1_to_hex(current_commit_sha1));
+       strcpy(missing_hex, sha1_to_hex(obj->sha1));;
+       fprintf(stderr, "Cannot obtain needed %s %s\n",
+               obj->type ? typename(obj->type): "object", missing_hex);
+       if (!is_null_sha1(current_commit_sha1))
+               fprintf(stderr, "while processing commit %s.\n",
+                       sha1_to_hex(current_commit_sha1));
 }
 
 static int process(struct object *obj);
@@ -177,7 +178,7 @@ static int loop(void)
                 */
                if (! (obj->flags & TO_SCAN)) {
                        if (fetch(obj->sha1)) {
-                               report_missing(typename(obj->type), obj->sha1);
+                               report_missing(obj);
                                return -1;
                        }
                }
index afe322b20fb0b40dfeb1cb17dc3cad09096e943a..5df6787a3f285f18d3a41c1ab2f0579f407e02de 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
@@ -408,6 +412,10 @@ do
                        stop_here_user_resolve $this
                fi
                apply_status=0
+               if test -d "$GIT_DIR/rr-cache"
+               then
+                       git rerere
+               fi
                ;;
        esac
 
index 89640396e9716dcc545710fc3f7697a1594d8e97..1f5d07a057ed04dd9ffc7eb705d0c752d250bf72 100755 (executable)
@@ -377,9 +377,9 @@ then
                *)      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" &&
+               git-repo-config remote."$origin".url "$repo" &&
+               git-repo-config remote."$origin".fetch \
+                       "refs/heads/$head_points_at:$origin_track" &&
                (cd "$GIT_DIR/$remote_top" && find . -type f -print) |
                while read dotslref
                do
@@ -393,14 +393,16 @@ Pull: refs/heads/$head_points_at:$origin_track" &&
                        then
                                continue
                        fi
-                       echo "Pull: refs/heads/${name}:$remote_top/${name}"
-               done >>"$GIT_DIR/remotes/$origin" &&
+                       git-repo-config remote."$origin".fetch "refs/heads/${name}:$remote_top/${name}" '^$'
+               done &&
                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
+               esac &&
+               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..05828bb113d715a1067838ed2ac58ad8db06bf34 100755 (executable)
@@ -350,19 +350,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
@@ -629,4 +619,7 @@ if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0
 then
        "$GIT_DIR"/hooks/post-commit
 fi
+
+test "$ret" = 0 && git-diff-tree --summary --root --no-commit-id HEAD
+
 exit "$ret"
index c9d1d88f2eafd9c3fa1ffec9ad9e7d997928c709..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;
 }
 close MSG;
 
-my (@afiles, @dfiles, @mfiles, @dirs);
-my %amodes;
-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];
-       $amodes{$path} = $fields[1];
-       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;
     }
 }
 
-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?'};
     }
 }
 
-###
-### 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) {
-    set_new_file_permissions($f, $amodes{$f});
     if (grep { $_ eq $f } @bfiles) {
       system('cvs', 'add','-kb',$f);
     } else {
       system('cvs', 'add', $f);
     }
     if ($?) {
-       $dirty = 1;
+       $dirtypatch = 1;
        warn "Failed to cvs add $f -- you may need to do it manually";
     }
 }
 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
@@ -321,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
@@ -346,12 +270,15 @@ sub safe_pipe_capture {
     return wantarray ? @output : join('',@output);
 }
 
-# For any file we want to add to cvs, we must first set its permissions
-# properly, *before* the "cvs add ..." command.  Otherwise, it is impossible
-# to change the permission of the file in the CVS repository using only cvs
-# commands.  This should be fixed in cvs-1.12.14.
-sub set_new_file_permissions {
-    my ($file, $perm) = @_;
-    chmod oct($perm), $file
-      or die "failed to set permissions of \"$file\": $!\n";
+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 ca519b7e49a0b2cc5104f71459fe621041382f19..2a8447e2534e93b4cec4e86c2dcbadfede99208a 100755 (executable)
@@ -17,6 +17,7 @@
 
 use strict;
 use warnings;
+use bytes;
 
 use Fcntl;
 use File::Temp qw/tempdir tempfile/;
@@ -945,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 397b33f8d33b67018669cb4610a2051407fc3eb7..c895a04f6be0e09670af71532558abe994369a64 100755 (executable)
@@ -188,8 +188,9 @@ else
        # in this loop.
        merge_name=$(for remote
                do
-                       rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) &&
-                       bh=$(git show-ref -s --verify "refs/heads/$remote") &&
+                       rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
+                       continue ;# not something we can merge
+                       bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
                        if test "$rh" = "$bh"
                        then
                                echo "$rh               branch '$remote' of ."
@@ -399,7 +400,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 da064a53f666db52ce2341132a117355ad74d2a1..6ae534bf89a7a1fd0bfeee4248c37b4ea71290b9 100755 (executable)
@@ -134,6 +134,8 @@ 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=
+       found_mergeref=
+       curr_branch=
        if test "$1" = "-d"
        then
                shift ; remote="$1" ; shift
@@ -171,6 +173,10 @@ canon_refs_list_for_fetch () {
                            dot_prefix= && break
                        done
                fi
+               if test -z $dot_prefix
+               then
+                       found_mergeref=true
+               fi
                case "$remote" in
                '') remote=HEAD ;;
                refs/heads/* | refs/tags/* | refs/remotes/*) ;;
@@ -191,6 +197,11 @@ canon_refs_list_for_fetch () {
                fi
                echo "${dot_prefix}${force}${remote}:${local}"
        done
+       if test -z "$found_mergeref" -a "$curr_branch"
+       then
+               echo >&2 "Warning: No merge candidate found because value of config option
+         \"branch.${curr_branch}.merge\" does not match any remote branch fetched."
+       fi
 }
 
 # Returns list of src: (no store), or src:dst (store)
index 25530dfdc5ba3b88ba9f45393d3aace8faa89472..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"
index d3664ff491fe73d8a6acf6e2c4da5806c5753ce1..fdd68548961247b38e637f61e280ab4084650475 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 3133b5bd25ca7ad3c2a5451ee9c2e0f4514cd980..c0feb4435d15da9cc9928800cbe270018f044152 100755 (executable)
@@ -63,6 +63,7 @@ case "$reset_type" in
        ;;
 esac
 
-rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" "$GIT_DIR/SQUASH_MSG"
+rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" \
+       "$GIT_DIR/SQUASH_MSG" "$GIT_DIR/MERGE_MSG"
 
 exit $update_ref_status
index d0bd0bdeb8462f7ad4550b5fe406f4e0a1a0ee1c..15254e479595de047258188e0979bea322869696 100755 (executable)
 $ENV{LC_ALL} = 'C';
 $| = 1; # unbuffer STDOUT
 
-sub fatal (@) { print STDERR $@; exit 1 }
+# 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
@@ -72,7 +82,7 @@ sub nag_lib {
        $_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_co_url_revs, $_svn_pg_peg_revs, $_svn_can_do_switch);
 my @repo_path_split_cache;
 
 my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
@@ -459,6 +469,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 };
@@ -593,8 +604,9 @@ 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"));
+       chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..$head"));
        my $last_rev;
        foreach my $d (reverse @refs) {
                if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) {
@@ -621,16 +633,16 @@ sub dcommit {
        }
        return if $_dry_run;
        fetch();
-       my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs);
+       my @diff = safe_qx('git-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);
@@ -2015,9 +2027,17 @@ sub git_commit {
 
        # 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") }) {
+       open my $null, '>', '/dev/null' or croak $!;
+       open my $stderr, '>&', \*STDERR or croak $!;
+       open STDERR, '>&', $null or croak $!;
+       if (my $cur = eval { safe_qx('git-rev-parse',
+                                    "refs/remotes/$GIT_SVN^0") }) {
+               chomp $cur;
                push @tmp_parents, $cur;
        }
+       open STDERR, '>&', $stderr or croak $!;
+       close $stderr or croak $!;
+       close $null or croak $!;
 
        if (exists $tree_map{$tree}) {
                foreach my $p (@{$tree_map{$tree}}) {
@@ -2876,6 +2896,24 @@ sub libsvn_connect {
        return $ra;
 }
 
+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
@@ -2883,7 +2921,7 @@ sub libsvn_dup_ra {
 }
 
 sub libsvn_get_file {
-       my ($gui, $f, $rev, $chg) = @_;
+       my ($gui, $f, $rev, $chg, $untracked) = @_;
        $f =~ s#^/##;
        print "\t$chg\t$f\n" unless $_q;
 
@@ -2921,11 +2959,25 @@ sub libsvn_get_file {
                waitpid $pid, 0;
                $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
        }
+       %{$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";
@@ -2933,8 +2985,65 @@ sub libsvn_log_entry {
                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 {
@@ -2953,9 +3062,11 @@ sub process_rm {
                }
                print "\tD\t$f/\n" unless $q;
                close $ls or croak $?;
+               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;
        }
 }
 
@@ -2976,13 +3087,14 @@ sub libsvn_fetch_delta {
        unless ($ed->{git_commit_ok}) {
                die "SVN connection failed somewhere...\n";
        }
-       libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
+       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 %amr;
+       my $ut = { empty => {}, dir_prop => {}, file_prop => {} };
        my $p = $SVN->{svn_path};
        foreach my $f (keys %$paths) {
                my $m = $paths->{$f}->action();
@@ -2993,8 +3105,11 @@ sub libsvn_fetch_full {
                        $f =~ s#^/##;
                }
                if ($m =~ /^[DR]$/) {
-                       process_rm($gui, $last_commit, $f, $_q);
-                       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;
@@ -3007,18 +3122,32 @@ sub libsvn_fetch_full {
                        }
                } 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};
+       }
+       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;
        }
        close $gui or croak $?;
-       return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
+       libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ut);
 }
 
 sub svn_grab_base_rev {
@@ -3079,25 +3208,38 @@ sub libsvn_parse_revision {
 }
 
 sub libsvn_traverse {
-       my ($gui, $pfx, $path, $rev, $files) = @_;
+       my ($gui, $pfx, $path, $rev, $files, $untracked) = @_;
        my $cwd = length $pfx ? "$pfx/$path" : $path;
        my $pool = SVN::Pool->new;
        $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 {
@@ -3197,12 +3339,26 @@ sub libsvn_find_parent_branch {
                unlink $GIT_SVN_INDEX;
                print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
                sys(qw/git-read-tree/, $parent);
-               # I can't seem to get do_switch() to work correctly with
-               # the SWIG interface (TypeError when passing switch_url...),
-               # so we'll unconditionally bypass the delta interface here
-               # for now
-               return libsvn_fetch_full($parent, $paths, $rev,
-                                       $author, $date, $msg);
+               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;
@@ -3222,6 +3378,7 @@ sub libsvn_new_tree {
                return $log_entry;
        }
        my ($paths, $rev, $author, $date, $msg) = @_;
+       my $ut;
        if ($_xfer_delta) {
                my $pool = SVN::Pool->new;
                my $ed = SVN::Git::Fetcher->new({q => $_q});
@@ -3233,12 +3390,14 @@ sub libsvn_new_tree {
                unless ($ed->{git_commit_ok}) {
                        die "SVN connection failed somewhere...\n";
                }
+               $ut = $ed;
        } else {
+               $ut = { empty => {}, dir_prop => {}, file_prop => {} };
                open my $gui, '| git-update-index -z --index-info' or croak $!;
-               libsvn_traverse($gui, '', $SVN->{svn_path}, $rev);
+               libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut);
                close $gui or croak $?;
        }
-       return libsvn_log_entry($rev, $author, $date, $msg);
+       libsvn_log_entry($rev, $author, $date, $msg, [], $ut);
 }
 
 sub find_graft_path_commit {
@@ -3423,13 +3582,28 @@ sub new {
        $self->{gui} = $gui;
        $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} = {};
        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) = @_;
-       process_rm($self->{gui}, $self->{c}, $path, $self->{q});
+       my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q});
+       $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir;
        undef;
 }
 
@@ -3437,16 +3611,50 @@ sub open_file {
        my ($self, $path, $pb, $rev) = @_;
        my ($mode, $blob) = (safe_qx('git-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') {
@@ -3455,6 +3663,9 @@ sub change_file_prop {
                }
        } 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;
 }
diff --git a/git.c b/git.c
index 357330e02cf4c5645a8e37b0ae8fe8c5596990f3..016ee8adb730ea7c390dcb84dcbbafda7936f2d5 100644 (file)
--- a/git.c
+++ b/git.c
@@ -247,6 +247,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 },
index f2374b7331cfe49792e7a76145fdb1cea8f6795c..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, 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
index ffe8ce13ff3e5a66f3ffc548238bee29f4593f97..5ea3fda540f00e4b7ee2c11c380eac86168eca17 100755 (executable)
        # 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,
@@ -3229,10 +3229,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))) {
@@ -3269,13 +3272,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";
index 8331d99a62a457cb341a834792aedf5de9c5625f..6d6c92bf14803923f4c98678ae682aef4d41d3ab 100644 (file)
@@ -96,7 +96,7 @@ static void flush(void)
                if (output_fd >= 0)
                        write_or_die(output_fd, input_buffer, input_offset);
                SHA1_Update(&input_ctx, input_buffer, input_offset);
-               memcpy(input_buffer, input_buffer + input_offset, input_len);
+               memmove(input_buffer, input_buffer + input_offset, input_len);
                input_offset = 0;
        }
 }
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 cd2cc77bf47025a9dfc88f594961521c3ec8f22c..6dd6e2e5af779f2b3ad5da050776c37646b45a84 100644 (file)
@@ -21,6 +21,7 @@
 #include "tag.h"
 #include "unpack-trees.h"
 #include "path-list.h"
+#include "xdiff-interface.h"
 
 /*
  * A virtual commit has
@@ -604,24 +605,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 +650,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 +879,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 +1051,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 +1078,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);
@@ -1238,7 +1208,7 @@ static int merge(struct commit *h1,
 
                tree->object.parsed = 1;
                tree->object.type = OBJ_TREE;
-               hash_sha1_file(NULL, 0, tree_type, tree->object.sha1);
+               write_sha1_file(NULL, 0, tree_type, tree->object.sha1);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
 
index bd483b099760719bcbd3ae67c2321b675ad26881..099beda873e91ebacf1068531fe194fa041ed9d6 100644 (file)
@@ -29,7 +29,7 @@ $(makfile): ../GIT-CFLAGS Makefile
        echo '  echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-       '$(PERL_PATH_SQ)' $< FIRST_MAKEFILE='$@' PREFIX='$(prefix_SQ)'
+       '$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)'
 endif
 
 # this is just added comfort for calling make directly in perl dir
index de73235e4c3142381c4ea01597e18b14cd65bc02..41687757a7fdd9c09bd320b92ed7f089af00a403 100644 (file)
@@ -24,5 +24,6 @@ WriteMakefile(
        NAME            => 'Git',
        VERSION_FROM    => 'Git.pm',
        PM              => \%pm,
+       MAKEFILE        => 'perl.mak',
        %extra
 );
index a20bc924d6b261138002f2e95b127e0ffd226411..e76d9aea31886ec9b0d287f02a12af9793fb44db 100644 (file)
@@ -11,7 +11,7 @@
 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 delete-refs ";
diff --git a/refs.c b/refs.c
index 96ea8b6907b1e4be9c7b8575ff7e4a36f5a7a5f8..a02957c399ded94bb9a49c9dc3d8ab5d9411bbec 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -534,7 +534,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;
                }
        }
@@ -610,6 +610,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;
@@ -643,29 +666,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));
 
@@ -776,6 +784,117 @@ 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;
+       }
+
+       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 cd1e1d620e6513cd1542d15763ccd5c07f108e48..51aab1e6bf592ed6412289695d6fe33e5140fab2 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -47,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 328dbbc16a7c529659fabd2704a2a6b1cc625210..cc884f3b2debbaadfc3c42cda20074257fa48a65 100644 (file)
@@ -406,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)
 {
@@ -457,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 81f3bedc90f4ead3f12f344641d7c3bff44f6ab8..3260d1d7a746f6968aab0d3f374bd7e76bf33bd0 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).
-# Check this 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,14 +28,6 @@ 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
 
 ################################################################
index acb54b6a079b2e790c3bf6eb58007188af7ef1a1..5782c30b03a7e7bb7d244d305e13e856eed40a89 100755 (executable)
@@ -70,4 +70,45 @@ 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'
+
+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_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
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 c1024790e41693097d588b3c544a2e4b15ed71bf..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,20 +141,73 @@ test_expect_success \
      diff F/newfile6.png ../F/newfile6.png
      )'
 
-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_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 79d21e2e8dd89f6e0560a2aded03f5bf3eb77f78..b8689ebc86bb69c21fd650572c55a4c3d246a183 100644 (file)
@@ -384,7 +384,7 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
        int i;
        struct object_list *posn = trees;
        struct tree_entry_list df_conflict_list;
-       struct cache_entry df_conflict_entry;
+       static struct cache_entry *dfc;
 
        memset(&df_conflict_list, 0, sizeof(df_conflict_list));
        df_conflict_list.next = &df_conflict_list;
@@ -395,8 +395,10 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
        state.refresh_cache = 1;
 
        o->merge_size = len;
-       memset(&df_conflict_entry, 0, sizeof(df_conflict_entry));
-       o->df_conflict_entry = &df_conflict_entry;
+
+       if (!dfc)
+               dfc = xcalloc(1, sizeof(struct cache_entry) + 1);
+       o->df_conflict_entry = dfc;
 
        if (len) {
                posns = xmalloc(len * sizeof(struct tree_entry_list *));
index de1be5bc66de634e41c7ef3dee08209631249e13..6e9414dbb07f1457eb436907a77d14476a6e6b1f 100644 (file)
@@ -163,7 +163,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");
+                               "use git-add on files to include for commit");
        for (i = 0; i < q->nr; i++)
                wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
        if (q->nr)
@@ -297,11 +297,11 @@ void wt_status_print(struct wt_status *s)
 
 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;
+}