summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Documentation/Makefile4
-rw-r--r--Documentation/callouts.xsl14
-rw-r--r--Documentation/config.txt25
-rw-r--r--Documentation/cvs-migration.txt352
-rw-r--r--Documentation/diff-format.txt57
-rw-r--r--Documentation/diff-options.txt16
-rw-r--r--Documentation/git-clone.txt29
-rw-r--r--Documentation/git-diff.txt6
-rw-r--r--Documentation/git-merge-index.txt4
-rw-r--r--Documentation/git-merge.txt18
-rw-r--r--Documentation/git-push.txt15
-rw-r--r--Documentation/git-repo-config.txt6
-rw-r--r--Documentation/git-shortlog.txt1
-rw-r--r--Documentation/git-svn.txt46
-rw-r--r--Documentation/git-symbolic-ref.txt29
-rw-r--r--Documentation/git.txt32
-rw-r--r--Documentation/tutorial.txt70
-rw-r--r--INSTALL12
-rw-r--r--Makefile93
-rw-r--r--builtin-blame.c11
-rw-r--r--builtin-branch.c92
-rw-r--r--builtin-diff.c2
-rw-r--r--builtin-grep.c25
-rw-r--r--builtin-log.c2
-rw-r--r--builtin-ls-files.c6
-rw-r--r--builtin-merge-file.c79
-rw-r--r--builtin-mv.c11
-rw-r--r--builtin-pack-objects.c6
-rw-r--r--builtin-pack-refs.c28
-rw-r--r--builtin-push.c35
-rw-r--r--builtin-shortlog.c342
-rw-r--r--builtin-show-ref.c40
-rw-r--r--builtin.h2
-rw-r--r--compat/subprocess.py1149
-rw-r--r--config.c2
-rw-r--r--config.mak.in2
-rw-r--r--configure.ac39
-rw-r--r--connect.c11
-rwxr-xr-xcontrib/completion/git-completion.bash542
-rw-r--r--contrib/mailmap.linux42
-rw-r--r--diff.c9
-rw-r--r--fetch-pack.c25
-rw-r--r--fetch.c15
-rwxr-xr-xgit-clone.sh20
-rwxr-xr-xgit-cvsexportcommit.perl194
-rwxr-xr-xgit-cvsimport.perl168
-rwxr-xr-xgit-cvsserver.perl3
-rwxr-xr-xgit-fetch.sh52
-rwxr-xr-xgit-merge-recursive-old.py944
-rwxr-xr-xgit-merge.sh96
-rwxr-xr-xgit-parse-remote.sh50
-rwxr-xr-xgit-rebase.sh9
-rwxr-xr-xgit-request-pull.sh2
-rwxr-xr-xgit-rerere.perl2
-rwxr-xr-xgit-reset.sh3
-rwxr-xr-xgit-shortlog.perl234
-rwxr-xr-xgit-svn.perl561
-rwxr-xr-xgit-tag.sh11
-rw-r--r--git.c2
-rw-r--r--git.spec.in2
-rw-r--r--gitMergeCommon.py275
-rwxr-xr-xgitk20
-rwxr-xr-xgitweb/gitweb.perl116
-rw-r--r--ident.c15
-rw-r--r--index-pack.c2
-rw-r--r--merge-file.c75
-rw-r--r--merge-recursive.c146
-rw-r--r--perl/.gitignore3
-rw-r--r--perl/Makefile39
-rw-r--r--perl/Makefile.PL1
-rw-r--r--receive-pack.c48
-rw-r--r--refs.c108
-rw-r--r--refs.h7
-rw-r--r--send-pack.c62
-rw-r--r--sha1_file.c35
-rw-r--r--t/Makefile7
-rw-r--r--t/lib-git-svn.sh2
-rwxr-xr-xt/t0000-basic.sh20
-rwxr-xr-xt/t4015-diff-whitespace.sh6
-rwxr-xr-xt/t5400-send-pack.sh10
-rw-r--r--t/t6023-merge-file.sh116
-rwxr-xr-xt/t6023-merge-rename-nocruft.sh97
-rw-r--r--t/t6024-recursive-merge.sh80
-rwxr-xr-xt/t7001-mv.sh13
-rwxr-xr-xt/t9100-git-svn-basic.sh5
-rwxr-xr-xt/t9103-git-svn-graft-branches.sh2
-rwxr-xr-xt/t9200-git-cvsexportcommit.sh94
-rwxr-xr-xt/test-lib.sh15
-rw-r--r--unpack-trees.c8
-rw-r--r--wt-status.c4
-rw-r--r--xdiff/xdiff.h7
-rw-r--r--xdiff/xdiffi.c3
-rw-r--r--xdiff/xdiffi.h1
-rw-r--r--xdiff/xmerge.c419
-rw-r--r--xdiff/xutils.c3
96 files changed, 3698 insertions, 3867 deletions
diff --git a/.gitignore b/.gitignore
index 4c8c8e4115..d706dd92c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,13 +60,13 @@ git-mailsplit
git-merge
git-merge-base
git-merge-index
+git-merge-file
git-merge-tree
git-merge-octopus
git-merge-one-file
git-merge-ours
git-merge-recur
git-merge-recursive
-git-merge-recursive-old
git-merge-resolve
git-merge-stupid
git-mktag
diff --git a/Documentation/Makefile b/Documentation/Makefile
index c00f5f62b7..d68bc4a788 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -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)
#
diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl
index ad03755d8f..6a361a2136 100644
--- a/Documentation/callouts.xsl
+++ b/Documentation/callouts.xsl
@@ -13,4 +13,18 @@
<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>
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 9090762819..f5a552ee87 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -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
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
index 6812683a16..b657f4589f 100644
--- a/Documentation/cvs-migration.txt
+++ b/Documentation/cvs-migration.txt
@@ -1,113 +1,21 @@
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.
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
index e4520e28e5..883c1bb0a6 100644
--- a/Documentation/diff-format.txt
+++ b/Documentation/diff-format.txt
@@ -65,62 +65,17 @@ Generating patches with -p
When "git-diff-index", "git-diff-tree", or "git-diff-files" are run
with a '-p' option, they do not produce the output described above;
-instead they produce a patch file.
+instead they produce a patch file. You can customize the creation
+of such patches via the GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS
+environment variables.
-The patch generation can be customized at two levels.
-
-1. When the environment variable 'GIT_EXTERNAL_DIFF' is not set,
- these commands internally invoke "diff" like this:
-
- diff -L a/<path> -L b/<path> -pu <old> <new>
-+
-For added files, `/dev/null` is used for <old>. For removed
-files, `/dev/null` is used for <new>
-+
-The "diff" formatting options can be customized via the
-environment variable 'GIT_DIFF_OPTS'. For example, if you
-prefer context diff:
-
- GIT_DIFF_OPTS=-c git-diff-index -p HEAD
-
-
-2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
- program named by it is called, instead of the diff invocation
- described above.
-+
-For a path that is added, removed, or modified,
-'GIT_EXTERNAL_DIFF' is called with 7 parameters:
-
- path old-file old-hex old-mode new-file new-hex new-mode
-+
-where:
-
- <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
- contents of <old|new>,
- <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
- <old|new>-mode:: are the octal representation of the file modes.
-
-+
-The file parameters can point at the user's working file
-(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
-when a new file is added), or a temporary file (e.g. `old-file` in the
-index). 'GIT_EXTERNAL_DIFF' should not worry about unlinking the
-temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits.
-
-For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
-parameter, <path>.
-
-
-git specific extension to diff format
--------------------------------------
-
-What -p option produces is slightly different from the
-traditional diff format.
+What the -p option produces is slightly different from the traditional
+diff format.
1. It is preceded with a "git diff" header, that looks like
this:
- diff --git a/file1 b/file2
+ diff --git a/file1 b/file2
+
The `a/` and `b/` filenames are the same unless rename/copy is
involved. Especially, even for a creation or a deletion,
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index e112172ca5..9cdd171af7 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -129,5 +129,21 @@
-a::
Shorthand for "--text".
+--ignore-space-change::
+ Ignore changes in amount of white space. This ignores white
+ space at line end, and consider all other sequences of one or
+ more white space characters to be equivalent.
+
+-b::
+ Shorthand for "--ignore-space-change".
+
+--ignore-all-space::
+ Ignore white space when comparing lines. This ignores
+ difference even if one line has white space where the other
+ line has none.
+
+-w::
+ Shorthand for "--ignore-all-space".
+
For more detailed explanation on these common options, see also
link:diffcore.html[diffcore documentation].
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 4cb42237b5..985043faca 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -11,27 +11,26 @@ SYNOPSIS
[verse]
'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
[-o <name>] [-u <upload-pack>] [--reference <repository>]
- [--use-separate-remote | --use-immingled-remote] <repository>
+ [--use-separate-remote | --no-separate-remote] <repository>
[<directory>]
DESCRIPTION
-----------
-Clones a repository into a newly created directory. All remote
-branch heads are copied under `$GIT_DIR/refs/heads/`, except
-that the remote `master` is also copied to `origin` branch.
-In addition, `$GIT_DIR/remotes/origin` file is set up to have
-this line:
+Clones a repository into a newly created directory, creates
+remote-tracking branches for each branch in the cloned repository
+(visible using `git branch -r`), and creates and checks out a master
+branch equal to the cloned repository's master branch.
- Pull: master:origin
-
-This is to help the typical workflow of working off of the
-remote `master` branch. Every time `git pull` without argument
-is run, the progress on the remote `master` branch is tracked by
-copying it into the local `origin` branch, and merged into the
-branch you are currently working on. Remote branches other than
-`master` are also added there to be tracked.
+After the clone, a plain `git fetch` without arguments will update
+all the remote-tracking branches, and a `git pull` without
+arguments will in addition merge the remote master branch into the
+current branch.
+This default configuration is achieved by creating references to
+the remote branch heads under `$GIT_DIR/refs/remotes/origin` and
+by initializing `remote.origin.url` and `remote.origin.fetch`
+configuration variables.
OPTIONS
-------
@@ -105,7 +104,7 @@ OPTIONS
of `$GIT_DIR/refs/heads/`. Only the local master branch is
saved in the latter. This is the default.
---use-immingled-remote::
+--no-separate-remote::
Save remotes heads in the same namespace as the local
heads, `$GIT_DIR/refs/heads/'. In regular repositories,
this is a legacy setup git-clone created by default in
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index 228c4d95bd..3144864d85 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -22,8 +22,10 @@ the number of trees given to the command.
* When one <tree-ish> is given, the working tree and the named
tree are compared, using `git-diff-index`. The option
- `--cached` can be given to compare the index file and
+ `--index` can be given to compare the index file and
the named tree.
+ `--cached` is a deprecated alias for `--index`. It's use is
+ discouraged.
* When two <tree-ish>s are given, these two trees are compared
using `git-diff-tree`.
@@ -47,7 +49,7 @@ Various ways to check your working tree::
+
------------
$ git diff <1>
-$ git diff --cached <2>
+$ git diff --index <2>
$ git diff HEAD <3>
------------
+
diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt
index 6cd0601082..0cf505ea84 100644
--- a/Documentation/git-merge-index.txt
+++ b/Documentation/git-merge-index.txt
@@ -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.
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
index bebf30ad3d..e2954aa76e 100644
--- a/Documentation/git-merge.txt
+++ b/Documentation/git-merge.txt
@@ -8,12 +8,14 @@ git-merge - Grand Unified Merge Driver
SYNOPSIS
--------
-'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>...
-
+[verse]
+'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]...
+ [--reflog-action=<action>]
+ -m=<msg> <remote> <remote>...
DESCRIPTION
-----------
-This is the top-level user interface to the merge machinery
+This is the top-level interface to the merge machinery
which drives multiple merge strategy scripts.
@@ -27,13 +29,19 @@ include::merge-options.txt[]
to give a good default for automated `git-merge` invocations.
<head>::
- our branch head commit.
+ Our branch head commit. This has to be `HEAD`, so new
+ syntax does not require it
<remote>::
- other branch head merged into our branch. You need at
+ Other branch head merged into our branch. You need at
least one <remote>. Specifying more than one <remote>
obviously means you are trying an Octopus.
+--reflog-action=<action>::
+ This is used internally when `git-pull` calls this command
+ to record that the merge was created by `pull` command
+ in the `ref-log` entry that results from the merge.
+
include::merge-strategies.txt[]
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index d4ae99fa53..197f4b512f 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -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
--------------
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index 8199615dde..5bede9ac22 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -77,6 +77,12 @@ OPTIONS
-l, --list::
List all variables set in config file.
+--bool::
+ git-repo-config will ensure that the output is "true" or "false"
+
+--int::
+ git-repo-config will ensure that the output is a simple decimal number
+
ENVIRONMENT
-----------
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index d54fc3e5c6..95fa9010c1 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -8,6 +8,7 @@ git-shortlog - Summarize 'git log' output
SYNOPSIS
--------
git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s]
+git-shortlog [-n|--number] [-s|--summary] [<committish>...]
DESCRIPTION
-----------
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index a764d1f8ee..c589a98630 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -49,7 +49,7 @@ latest revision.
Note: You should never attempt to modify the remotes/git-svn
branch outside of git-svn. Instead, create a branch from
-remotes/git-svn and work on that branch. Use the 'commit'
+remotes/git-svn and work on that branch. Use the 'dcommit'
command (see below) to write git commits back to
remotes/git-svn.
@@ -57,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.
@@ -274,7 +276,7 @@ ADVANCED OPTIONS
-b<refname>::
--branch <refname>::
-Used with 'fetch' or 'commit'.
+Used with 'fetch', 'dcommit' or 'commit'.
This can be used to join arbitrary git branches to remotes/git-svn
on new commits where the tree object is equivalent.
@@ -368,7 +370,7 @@ SVN was very wrong.
Basic Examples
~~~~~~~~~~~~~~
-Tracking and contributing to an Subversion managed-project:
+Tracking and contributing to a Subversion-managed project:
------------------------------------------------------------------------
# Initialize a repo (like git init-db):
@@ -377,10 +379,9 @@ Tracking and contributing to an Subversion managed-project:
git-svn fetch
# Create your own branch to hack on:
git checkout -b my-branch remotes/git-svn
-# Commit only the git commits you want to SVN:
- git-svn commit <tree-ish> [<tree-ish_2> ...]
-# Commit all the git commits from my-branch that don't exist in SVN:
- git-svn commit remotes/git-svn..my-branch
+# Do some work, and then commit your new changes to SVN, as well as
+# automatically updating your working HEAD:
+ git-svn dcommit
# Something is committed to SVN, rebase the latest into your branch:
git-svn fetch && git rebase remotes/git-svn
# Append svn:ignore settings to the default git exclude file:
@@ -404,26 +405,24 @@ which can lead to merge commits reversing previous commits in SVN.
DESIGN PHILOSOPHY
-----------------
Merge tracking in Subversion is lacking and doing branched development
-with Subversion is cumbersome as a result. git-svn completely forgoes
-any automated merge/branch tracking on the Subversion side and leaves it
-entirely up to the user on the git side. It's simply not worth it to do
-a useful translation when the original signal is weak.
+with Subversion is cumbersome as a result. git-svn does not do
+automated merge/branch tracking by default and leaves it entirely up to
+the user on the git side.
[[tracking-multiple-repos]]
TRACKING MULTIPLE REPOSITORIES OR BRANCHES
------------------------------------------
-This is for advanced users, most users should ignore this section.
-
Because git-svn does not care about relationships between different
branches or directories in a Subversion repository, git-svn has a simple
hack to allow it to track an arbitrary number of related _or_ unrelated
-SVN repositories via one git repository. Simply set the GIT_SVN_ID
-environment variable to a name other other than "git-svn" (the default)
-and git-svn will ignore the contents of the $GIT_DIR/svn/git-svn directory
-and instead do all of its work in $GIT_DIR/svn/$GIT_SVN_ID for that
-invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of
-remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified
-by the user outside of git-svn commands.
+SVN repositories via one git repository. Simply use the --id/-i flag or
+set the GIT_SVN_ID environment variable to a name other other than
+"git-svn" (the default) and git-svn will ignore the contents of the
+$GIT_DIR/svn/git-svn directory and instead do all of its work in
+$GIT_DIR/svn/$GIT_SVN_ID for that invocation. The interface branch will
+be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any
+remotes/$GIT_SVN_ID branch should never be modified by the user outside
+of git-svn commands.
[[fetch-args]]
ADDITIONAL FETCH ARGUMENTS
@@ -486,7 +485,8 @@ If you are not using the SVN::* Perl libraries and somebody commits a
conflicting changeset to SVN at a bad moment (right before you commit)
causing a conflict and your commit to fail, your svn working tree
($GIT_DIR/git-svn/tree) may be dirtied. The easiest thing to do is
-probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'.
+probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'. You
+can avoid this problem entirely by using 'dcommit'.
We ignore all SVN properties except svn:executable. Too difficult to
map them since we rely heavily on git write-tree being _exactly_ the
diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
index 68ac6a65df..4bc35a1d4b 100644
--- a/Documentation/git-symbolic-ref.txt
+++ b/Documentation/git-symbolic-ref.txt
@@ -19,29 +19,22 @@ argument to see on which branch your working tree is on.
Give two arguments, create or update a symbolic ref <name> to
point at the given branch <ref>.
-Traditionally, `.git/HEAD` is a symlink pointing at
-`refs/heads/master`. When we want to switch to another branch,
-we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we want
+A symbolic ref is a regular file that stores a string that
+begins with `ref: refs/`. For example, your `.git/HEAD` is
+a regular file whose contents is `ref: refs/heads/master`.
+
+NOTES
+-----
+In the past, `.git/HEAD` was a symbolic link pointing at
+`refs/heads/master`. When we wanted to switch to another branch,
+we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we wanted
to find out which branch we are on, we did `readlink .git/HEAD`.
This was fine, and internally that is what still happens by
default, but on platforms that do not have working symlinks,
or that do not have the `readlink(1)` command, this was a bit
cumbersome. On some platforms, `ln -sf` does not even work as
-advertised (horrors).
-
-A symbolic ref can be a regular file that stores a string that
-begins with `ref: refs/`. For example, your `.git/HEAD` *can*
-be a regular file whose contents is `ref: refs/heads/master`.
-This can be used on a filesystem that does not support symbolic
-links. Instead of doing `readlink .git/HEAD`, `git-symbolic-ref
-HEAD` can be used to find out which branch we are on. To point
-the HEAD to `newbranch`, instead of `ln -sf refs/heads/newbranch
-.git/HEAD`, `git-symbolic-ref HEAD refs/heads/newbranch` can be
-used.
-
-Currently, .git/HEAD uses a regular file symbolic ref on Cygwin,
-and everywhere else it is implemented as a symlink. This can be
-changed at compilation time.
+advertised (horrors). Therefore symbolic links are now deprecated
+and symbolic refs are used by default.
Author
------
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 619d65685e..6382ef0a02 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -639,11 +639,35 @@ git Commits
git Diffs
~~~~~~~~~
'GIT_DIFF_OPTS'::
+ Only valid setting is "--unified=??" or "-u??" to set the
+ number of context lines shown when a unified diff is created.
+ This takes precedence over any "-U" or "--unified" option
+ value passed on the git diff command line.
+
'GIT_EXTERNAL_DIFF'::
- see the "generating patches" section in :
- gitlink:git-diff-index[1];
- gitlink:git-diff-files[1];
- gitlink:git-diff-tree[1]
+ When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
+ program named by it is called, instead of the diff invocation
+ described above. For a path that is added, removed, or modified,
+ 'GIT_EXTERNAL_DIFF' is called with 7 parameters:
+
+ path old-file old-hex old-mode new-file new-hex new-mode
++
+where:
+
+ <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
+ contents of <old|new>,
+ <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
+ <old|new>-mode:: are the octal representation of the file modes.
+
++
+The file parameters can point at the user's working file
+(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
+when a new file is added), or a temporary file (e.g. `old-file` in the
+index). 'GIT_EXTERNAL_DIFF' should not worry about unlinking the
+temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits.
++
+For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
+parameter, <path>.
other
~~~~~
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
index 1e4ddfbd11..fe4491de41 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -11,6 +11,18 @@ diff" with:
$ man git-diff
------------------------------------------------
+It is a good idea to introduce yourself to git before doing any
+operation. The easiest way to do so is:
+
+------------------------------------------------
+$ cat >~/.gitconfig <<\EOF
+[user]
+ name = Your Name Comes Here
+ email = you@yourdomain.example.com
+EOF
+------------------------------------------------
+
+
Importing a new project
-----------------------
@@ -31,7 +43,8 @@ defaulting to local storage area
You've now initialized the working directory--you may notice a new
directory created, named ".git". Tell git that you want it to track
-every file under the current directory with
+every file under the current directory with (notice the dot '.'
+that means the current directory):
------------------------------------------------
$ git add .
@@ -40,7 +53,7 @@ $ git add .
Finally,
------------------------------------------------
-$ git commit -a
+$ git commit
------------------------------------------------
will prompt you for a commit message, then record the current state
@@ -55,11 +68,17 @@ $ git diff
to review your changes. When you're done,
------------------------------------------------
-$ git commit -a
+$ git commit file1 file2...
------------------------------------------------
will again prompt your for a message describing the change, and then
-record the new versions of the modified files.
+record the new versions of the files you listed. It is cumbersome
+to list all files and you can say `-a` (which stands for 'all')
+instead.
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
A note on commit messages: Though not required, it's a good idea to
begin the commit message with a single short (less than 50 character)
@@ -75,7 +94,7 @@ $ git add path/to/new/file
------------------------------------------------
then commit as usual. No special command is required when removing a
-file; just remove it, then commit.
+file; just remove it, then tell `commit` about the file as usual.
At any point you can view the history of your changes using
@@ -209,29 +228,28 @@ at /home/bob/myrepo. She does this with:
------------------------------------------------
$ cd /home/alice/project
-$ git pull /home/bob/myrepo
+$ git pull /home/bob/myrepo master
------------------------------------------------
-This actually pulls changes from the branch in Bob's repository named
-"master". Alice could request a different branch by adding the name
-of the branch to the end of the git pull command line.
+This merges the changes from Bob's "master" branch into Alice's
+current branch. If Alice has made her own changes in the meantime,
+then she may need to manually fix any conflicts. (Note that the
+"master" argument in the above command is actually unnecessary, as it
+is the default.)
-This merges Bob's changes into her repository; "git log" will
-now show the new commits. If Alice has made her own changes in the
-meantime, then Bob's changes will be merged in, and she will need to
-manually fix any conflicts.
+The "pull" command thus performs two operations: it fetches changes
+from a remote branch, then merges them into the current branch.
-A more cautious Alice might wish to examine Bob's changes before
-pulling them. She can do this by creating a temporary branch just
-for the purpose of studying Bob's changes:
+You can perform the first operation alone using the "git fetch"
+command. For example, Alice could create a temporary branch just to
+track Bob's changes, without merging them with her own, using:
-------------------------------------
$ git fetch /home/bob/myrepo master:bob-incoming
-------------------------------------
which fetches the changes from Bob's master branch into a new branch
-named bob-incoming. (Unlike git pull, git fetch just fetches a copy
-of Bob's line of development without doing any merging). Then
+named bob-incoming. Then
-------------------------------------
$ git log -p master..bob-incoming
@@ -240,8 +258,8 @@ $ git log -p master..bob-incoming
shows a list of all the changes that Bob made since he branched from
Alice's master branch.
-After examining those changes, and possibly fixing things, Alice can
-pull the changes into her master branch:
+After examining those changes, and possibly fixing things, Alice
+could pull the changes into her master branch:
-------------------------------------
$ git checkout master
@@ -251,6 +269,18 @@ $ git pull . bob-incoming
The last command is a pull from the "bob-incoming" branch in Alice's
own repository.
+Alice could also perform both steps at once with:
+
+-------------------------------------
+$ git pull /home/bob/myrepo master:bob-incoming
+-------------------------------------
+
+This is just like the "git pull /home/bob/myrepo master" that we saw
+before, except that it also stores the unmerged changes from bob's
+master branch in bob-incoming before merging them into Alice's
+current branch. Note that git pull always merges into the current
+branch, regardless of what else is given on the commandline.
+
Later, Bob can update his repo with Alice's latest changes using
-------------------------------------
diff --git a/INSTALL b/INSTALL
index fce6bc39d5..b5dd9f0abb 100644
--- 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
@@ -99,9 +90,6 @@ Issues of note:
- "perl" and POSIX-compliant shells are needed to use most of
the barebone Porcelainish scripts.
- - "python" 2.3 or more recent; if you have 2.3, you may need
- to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease".
-
- Some platform specific issues are dealt with Makefile rules,
but depending on your specific installation, you may not
have all the libraries/tools needed, or you may have
diff --git a/Makefile b/Makefile
index 36ce8cd606..2d17fa7027 100644
--- a/Makefile
+++ b/Makefile
@@ -69,8 +69,6 @@ all:
#
# Define NO_MMAP if you want to avoid mmap.
#
-# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
-#
# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
#
# Define NO_SOCKADDR_STORAGE if your platform does not have struct
@@ -93,6 +91,10 @@ all:
#
# Define USE_STDEV below if you want git to care about the underlying device
# change being considered an inode change from the update-cache perspective.
+#
+# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
+# MakeMaker (e.g. using ActiveState under Cygwin).
+#
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -116,7 +118,6 @@ prefix = $(HOME)
bindir = $(prefix)/bin
gitexecdir = $(bindir)
template_dir = $(prefix)/share/git-core/templates/
-GIT_PYTHON_DIR = $(prefix)/share/git-core/python
# DESTDIR=
# default configuration for gitweb
@@ -135,7 +136,7 @@ GITWEB_FAVICON = git-favicon.png
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
-export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
+export prefix bindir gitexecdir template_dir
CC = gcc
AR = ar
@@ -174,17 +175,13 @@ SCRIPT_SH = \
SCRIPT_PERL = \
git-archimport.perl git-cvsimport.perl git-relink.perl \
- git-shortlog.perl git-rerere.perl \
+ git-rerere.perl \
git-cvsserver.perl \
git-svnimport.perl git-cvsexportcommit.perl \
git-send-email.perl git-svn.perl
-SCRIPT_PYTHON = \
- git-merge-recursive-old.py
-
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
- $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
git-cherry-pick git-status git-instaweb
# ... and all the rest that could be moved out of bindir to gitexecdir
@@ -227,12 +224,6 @@ endif
ifndef PERL_PATH
PERL_PATH = /usr/bin/perl
endif
-ifndef PYTHON_PATH
- PYTHON_PATH = /usr/bin/python
-endif
-
-PYMODULES = \
- gitMergeCommon.py
LIB_FILE=libgit.a
XDIFF_LIB=xdiff/lib.a
@@ -288,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 \
@@ -300,6 +292,7 @@ BUILTIN_OBJS = \
builtin-rev-parse.o \
builtin-rm.o \
builtin-runstatus.o \
+ builtin-shortlog.o \
builtin-show-branch.o \
builtin-stripspace.o \
builtin-symbolic-ref.o \
@@ -334,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
@@ -423,12 +404,17 @@ endif
-include config.mak.autogen
-include config.mak
-ifdef WITH_OWN_SUBPROCESS_PY
- PYMODULES += compat/subprocess.py
-else
- ifeq ($(NO_PYTHON),)
- ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
- PYMODULES += compat/subprocess.py
+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
@@ -561,6 +547,9 @@ endif
ifdef NO_ACCURATE_DIFF
BASIC_CFLAGS += -DNO_ACCURATE_DIFF
endif
+ifdef NO_PERL_MAKEMAKER
+ export NO_PERL_MAKEMAKER
+endif
# Shell quote (do not use $(call) to accommodate ancient setups);
@@ -574,8 +563,6 @@ prefix_SQ = $(subst ','\'',$(prefix))
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
-PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
-GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
LIBS = $(GITLIBS) $(EXTLIBS)
@@ -592,8 +579,8 @@ export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
-all: perl/Makefile
- $(MAKE) -C perl
+all:
+ $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
$(MAKE) -C templates
strip: $(PROGRAMS) git$X
@@ -622,12 +609,15 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
-e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
- -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
$@.sh >$@+
chmod +x $@+
mv $@+ $@
-$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile
+$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
+
+perl/perl.mak: GIT-CFLAGS
+ $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
+
$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
rm -f $@ $@+
INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
@@ -644,15 +634,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
chmod +x $@+
mv $@+ $@
-$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py GIT-CFLAGS
- rm -f $@ $@+
- sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
- -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \
- -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
- $@.py >$@+
- chmod +x $@+
- mv $@+ $@
-
git-cherry-pick: git-revert
cp $< $@+
mv $@+ $@
@@ -689,7 +670,6 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
- -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
-e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \
-e '/@@GITWEB_CGI@@/d' \
-e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
@@ -709,7 +689,6 @@ configure: configure.ac
git$X git.spec \
$(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
- $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
: GIT-VERSION-FILE
%.o: %.c GIT-CFLAGS
@@ -759,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
@@ -783,7 +763,7 @@ tags:
find . -name '*.[hcS]' -print | xargs ctags -a
### Detect prefix changes
-TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):$(GIT_PYTHON_DIR_SQ):\
+TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
GIT-CFLAGS: .FORCE-GIT-CFLAGS
@@ -799,7 +779,6 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
# However, the environment gets quite big, and some programs have problems
# with that.
-export NO_PYTHON
export NO_SVN_TESTS
test: all
@@ -833,9 +812,7 @@ install: all
$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
- $(MAKE) -C perl install
- $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
- $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
+ $(MAKE) -C perl prefix='$(prefix_SQ)' install
if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
then \
ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
@@ -905,8 +882,7 @@ clean:
rm -f $(htmldocs).tar.gz $(manpages).tar.gz
rm -f gitweb/gitweb.cgi
$(MAKE) -C Documentation/ clean
- [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean
- rm -f perl/ppport.h perl/Makefile.old
+ $(MAKE) -C perl clean
$(MAKE) -C templates/ clean
$(MAKE) -C t/ clean
rm -f GIT-VERSION-FILE GIT-CFLAGS
@@ -922,7 +898,6 @@ check-docs::
case "$$v" in \
git-merge-octopus | git-merge-ours | git-merge-recursive | \
git-merge-resolve | git-merge-stupid | git-merge-recur | \
- git-merge-recursive-old | \
git-ssh-pull | git-ssh-push ) continue ;; \
esac ; \
test -f "Documentation/$$v.txt" || \
diff --git a/builtin-blame.c b/builtin-blame.c
index 066dee743e..dc3ffeaff8 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -1435,14 +1435,14 @@ static void find_alignment(struct scoreboard *sb, int *option)
struct commit_info ci;
int num;
+ if (strcmp(suspect->path, sb->path))
+ *option |= OUTPUT_SHOW_NAME;
+ num = strlen(suspect->path);
+ if (longest_file < num)
+ longest_file = num;
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
- if (strcmp(suspect->path, sb->path))
- *option |= OUTPUT_SHOW_NAME;
- num = strlen(suspect->path);
- if (longest_file < num)
- longest_file = num;
num = strlen(ci.author);
if (longest_author < num)
longest_author = num;
@@ -1787,6 +1787,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
/* Now we got rev and path. We do not want the path pruning
* but we may want "bottom" processing.
*/
+ argv[unk++] = "--"; /* terminate the rev name */
argv[unk] = NULL;
init_revisions(&revs, NULL);
diff --git a/builtin-branch.c b/builtin-branch.c
index 3d5cb0e4b2..d1c243d372 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -5,6 +5,7 @@
* Based on git-branch.sh by Junio C Hamano.
*/
+#include "color.h"
#include "cache.h"
#include "refs.h"
#include "commit.h"
@@ -17,6 +18,58 @@ static const char builtin_branch_usage[] =
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);
@@ -253,7 +327,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
int kinds = REF_LOCAL_BRANCH;
int i;
- git_config(git_default_config);
+ git_config(git_branch_config);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -297,6 +371,14 @@ 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);
}
diff --git a/builtin-diff.c b/builtin-diff.c
index a6590205e8..1c535b1dd6 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -137,7 +137,7 @@ static int builtin_diff_index(struct rev_info *revs,
int cached = 0;
while (1 < argc) {
const char *arg = argv[1];
- if (!strcmp(arg, "--cached"))
+ if (!strcmp(arg, "--index") || !strcmp(arg, "--cached"))
cached = 1;
else
usage(builtin_diff_usage);
diff --git a/builtin-grep.c b/builtin-grep.c
index ad7dc00cde..9873e3d1db 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -268,7 +268,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
char *name;
- if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+ if (!S_ISREG(ntohl(ce->ce_mode)))
continue;
if (!pathspec_matches(paths, ce->name))
continue;
@@ -280,12 +280,19 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
memcpy(name + 2, ce->name, len + 1);
}
argv[argc++] = name;
- if (argc < MAXARGS)
+ if (argc < MAXARGS && !ce_stage(ce))
continue;
status = exec_grep(argc, argv);
if (0 < status)
hit = 1;
argc = nr;
+ if (ce_stage(ce)) {
+ do {
+ i++;
+ } while (i < active_nr &&
+ !strcmp(ce->name, active_cache[i]->name));
+ i--; /* compensate for loop control */
+ }
}
if (argc > nr) {
status = exec_grep(argc, argv);
@@ -316,14 +323,24 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
for (nr = 0; nr < active_nr; nr++) {
struct cache_entry *ce = active_cache[nr];
- if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+ if (!S_ISREG(ntohl(ce->ce_mode)))
continue;
if (!pathspec_matches(paths, ce->name))
continue;
- if (cached)
+ if (cached) {
+ if (ce_stage(ce))
+ continue;
hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
+ }
else
hit |= grep_file(opt, ce->name);
+ if (ce_stage(ce)) {
+ do {
+ nr++;
+ } while (nr < active_nr &&
+ !strcmp(ce->name, active_cache[nr]->name));
+ nr--; /* compensate for loop control */
+ }
}
free_grep_patterns(opt);
return hit;
diff --git a/builtin-log.c b/builtin-log.c
index 7acf5d3b0c..6821a08442 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -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-ls-files.c b/builtin-ls-files.c
index ad8c41e731..bc79ce40fc 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -487,10 +487,14 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
for (num = 0; pathspec[num]; num++) {
if (ps_matched[num])
continue;
- error("pathspec '%s' did not match any.",
+ error("pathspec '%s' did not match any file(s) known to git.",
pathspec[num] + prefix_offset);
errors++;
}
+
+ if (errors)
+ fprintf(stderr, "Did you forget to 'git add'?\n");
+
return errors ? 1 : 0;
}
diff --git a/builtin-merge-file.c b/builtin-merge-file.c
new file mode 100644
index 0000000000..6c4c3a3513
--- /dev/null
+++ b/builtin-merge-file.c
@@ -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;
+}
diff --git a/builtin-mv.c b/builtin-mv.c
index 54dd3bfe8a..d14a4a7f5c 100644
--- a/builtin-mv.c
+++ b/builtin-mv.c
@@ -146,21 +146,24 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
&& lstat(dst, &st) == 0)
bad = "cannot move directory over file";
else if (src_is_dir) {
+ const char *src_w_slash = add_slash(src);
+ int len_w_slash = length + 1;
int first, last;
modes[i] = WORKING_DIRECTORY;
- first = cache_name_pos(src, length);
+ first = cache_name_pos(src_w_slash, len_w_slash);
if (first >= 0)
- die ("Huh? %s/ is in index?", src);
+ die ("Huh? %.*s is in index?",
+ len_w_slash, src_w_slash);
first = -1 - first;
for (last = first; last < active_nr; last++) {
const char *path = active_cache[last]->name;
- if (strncmp(path, src, length)
- || path[length] != '/')
+ if (strncmp(path, src_w_slash, len_w_slash))
break;
}
+ free((char *)src_w_slash);
if (last - first < 1)
bad = "source directory is empty";
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 753bcd57b0..a2dc7d1d9d 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -514,6 +514,8 @@ static void write_pack_file(void)
if (do_progress)
fputc('\n', stderr);
done:
+ if (written != nr_result)
+ die("wrote %d objects while expecting %d", written, nr_result);
sha1close(f, pack_file_sha1, 1);
}
@@ -1662,7 +1664,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
}
}
if (progress)
- fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n",
- nr_result, written, written_delta, reused, reused_delta);
+ fprintf(stderr, "Total %d (delta %d), reused %d (delta %d)\n",
+ written, written_delta, reused, reused_delta);
return 0;
}
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
index 042d2718f9..8dc5b9efff 100644
--- a/builtin-pack-refs.c
+++ b/builtin-pack-refs.c
@@ -1,5 +1,7 @@
#include "cache.h"
#include "refs.h"
+#include "object.h"
+#include "tag.h"
static const char builtin_pack_refs_usage[] =
"git-pack-refs [--all] [--prune]";
@@ -29,12 +31,26 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
int flags, void *cb_data)
{
struct pack_refs_cb_data *cb = cb_data;
+ int is_tag_ref;
- if (!cb->all && strncmp(path, "refs/tags/", 10))
- return 0;
/* Do not pack the symbolic refs */
- if (!(flags & REF_ISSYMREF))
- fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+ if ((flags & REF_ISSYMREF))
+ return 0;
+ is_tag_ref = !strncmp(path, "refs/tags/", 10);
+ if (!cb->all && !is_tag_ref)
+ return 0;
+
+ fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+ if (is_tag_ref) {
+ struct object *o = parse_object(sha1);
+ if (o->type == OBJ_TAG) {
+ o = deref_tag(o, path, 0);
+ if (o)
+ fprintf(cb->refs_file, "^%s\n",
+ sha1_to_hex(o->sha1));
+ }
+ }
+
if (cb->prune && !do_not_prune(flags)) {
int namelen = strlen(path) + 1;
struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
@@ -95,6 +111,10 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
if (!cbdata.refs_file)
die("unable to create ref-pack file structure (%s)",
strerror(errno));
+
+ /* perhaps other traits later as well */
+ fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
+
for_each_ref(handle_one_ref, &cbdata);
fflush(cbdata.refs_file);
fsync(fd);
diff --git a/builtin-push.c b/builtin-push.c
index d23974e708..b7412e8293 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -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();
}
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
new file mode 100644
index 0000000000..3fc43dd7dd
--- /dev/null
+++ b/builtin-shortlog.c
@@ -0,0 +1,342 @@
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "path-list.h"
+#include "revision.h"
+#include <string.h>
+
+static const char shortlog_usage[] =
+"git-shortlog [-n] [-s] [<commit-id>... ]";
+
+static char *common_repo_prefix;
+
+static int compare_by_number(const void *a1, const void *a2)
+{
+ const struct path_list_item *i1 = a1, *i2 = a2;
+ const struct path_list *l1 = i1->util, *l2 = i2->util;
+
+ if (l1->nr < l2->nr)
+ return 1;
+ else if (l1->nr == l2->nr)
+ return 0;
+ else
+ return -1;
+}
+
+static struct path_list mailmap = {NULL, 0, 0, 0};
+
+static int read_mailmap(const char *filename)
+{
+ char buffer[1024];
+ FILE *f = fopen(filename, "r");
+
+ if (f == NULL)
+ return 1;
+ while (fgets(buffer, sizeof(buffer), f) != NULL) {
+ char *end_of_name, *left_bracket, *right_bracket;
+ char *name, *email;
+ int i;
+ if (buffer[0] == '#') {
+ static const char abbrev[] = "# repo-abbrev:";
+ int abblen = sizeof(abbrev) - 1;
+ int len = strlen(buffer);
+
+ if (len && buffer[len - 1] == '\n')
+ buffer[--len] = 0;
+ if (!strncmp(buffer, abbrev, abblen)) {
+ char *cp;
+
+ if (common_repo_prefix)
+ free(common_repo_prefix);
+ common_repo_prefix = xmalloc(len);
+
+ for (cp = buffer + abblen; isspace(*cp); cp++)
+ ; /* nothing */
+ strcpy(common_repo_prefix, cp);
+ }
+ continue;
+ }
+ if ((left_bracket = strchr(buffer, '<')) == NULL)
+ continue;
+ if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
+ continue;
+ if (right_bracket == left_bracket + 1)
+ continue;
+ for (end_of_name = left_bracket; end_of_name != buffer
+ && isspace(end_of_name[-1]); end_of_name--)
+ /* keep on looking */
+ if (end_of_name == buffer)
+ continue;
+ name = xmalloc(end_of_name - buffer + 1);
+ strlcpy(name, buffer, end_of_name - buffer + 1);
+ email = xmalloc(right_bracket - left_bracket);
+ for (i = 0; i < right_bracket - left_bracket - 1; i++)
+ email[i] = tolower(left_bracket[i + 1]);
+ email[right_bracket - left_bracket - 1] = '\0';
+ path_list_insert(email, &mailmap)->util = name;
+ }
+ fclose(f);
+ return 0;
+}
+
+static int map_email(char *email, char *name, int maxlen)
+{
+ char *p;
+ struct path_list_item *item;
+
+ /* autocomplete common developers */
+ p = strchr(email, '>');
+ if (!p)
+ return 0;
+
+ *p = '\0';
+ /* downcase the email address */
+ for (p = email; *p; p++)
+ *p = tolower(*p);
+ item = path_list_lookup(email, &mailmap);
+ if (item != NULL) {
+ const char *realname = (const char *)item->util;
+ strncpy(name, realname, maxlen);
+ return 1;
+ }
+ return 0;
+}
+
+static void insert_author_oneline(struct path_list *list,
+ const char *author, int authorlen,
+ const char *oneline, int onelinelen)
+{
+ const char *dot3 = common_repo_prefix;
+ char *buffer, *p;
+ struct path_list_item *item;
+ struct path_list *onelines;
+
+ while (authorlen > 0 && isspace(author[authorlen - 1]))
+ authorlen--;
+
+ buffer = xmalloc(authorlen + 1);
+ memcpy(buffer, author, authorlen);
+ buffer[authorlen] = '\0';
+
+ item = path_list_insert(buffer, list);
+ if (item->util == NULL)
+ item->util = xcalloc(1, sizeof(struct path_list));
+ else
+ free(buffer);
+
+ if (!strncmp(oneline, "[PATCH", 6)) {
+ char *eob = strchr(oneline, ']');
+
+ if (eob) {
+ while (isspace(eob[1]) && eob[1] != '\n')
+ eob++;
+ if (eob - oneline < onelinelen) {
+ onelinelen -= eob - oneline;
+ oneline = eob;
+ }
+ }
+ }
+
+ while (onelinelen > 0 && isspace(oneline[0])) {
+ oneline++;
+ onelinelen--;
+ }
+
+ while (onelinelen > 0 && isspace(oneline[onelinelen - 1]))
+ onelinelen--;
+
+ buffer = xmalloc(onelinelen + 1);
+ memcpy(buffer, oneline, onelinelen);
+ buffer[onelinelen] = '\0';
+
+ if (dot3) {
+ int dot3len = strlen(dot3);
+ if (dot3len > 5) {
+ while ((p = strstr(buffer, dot3)) != NULL) {
+ int taillen = strlen(p) - dot3len;
+ memcpy(p, "/.../", 5);
+ memmove(p + 5, p + dot3len, taillen + 1);
+ }
+ }
+ }
+
+ onelines = item->util;
+ if (onelines->nr >= onelines->alloc) {
+ onelines->alloc = alloc_nr(onelines->nr);
+ onelines->items = xrealloc(onelines->items,
+ onelines->alloc
+ * sizeof(struct path_list_item));
+ }
+
+ onelines->items[onelines->nr].util = NULL;
+ onelines->items[onelines->nr++].path = buffer;
+}
+
+static void read_from_stdin(struct path_list *list)
+{
+ char buffer[1024];
+
+ while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
+ char *bob;
+ if ((buffer[0] == 'A' || buffer[0] == 'a') &&
+ !strncmp(buffer + 1, "uthor: ", 7) &&
+ (bob = strchr(buffer + 7, '<')) != NULL) {
+ char buffer2[1024], offset = 0;
+
+ if (map_email(bob + 1, buffer, sizeof(buffer)))
+ bob = buffer + strlen(buffer);
+ else {
+ offset = 8;
+ while (buffer + offset < bob &&
+ isspace(bob[-1]))
+ bob--;
+ }
+
+ while (fgets(buffer2, sizeof(buffer2), stdin) &&
+ buffer2[0] != '\n')
+ ; /* chomp input */
+ if (fgets(buffer2, sizeof(buffer2), stdin)) {
+ int l2 = strlen(buffer2);
+ int i;
+ for (i = 0; i < l2; i++)
+ if (!isspace(buffer2[i]))
+ break;
+ insert_author_oneline(list,
+ buffer + offset,
+ bob - buffer - offset,
+ buffer2 + i, l2 - i);
+ }
+ }
+ }
+}
+
+static void get_from_rev(struct rev_info *rev, struct path_list *list)
+{
+ char scratch[1024];
+ struct commit *commit;
+
+ prepare_revision_walk(rev);
+ while ((commit = get_revision(rev)) != NULL) {
+ char *author = NULL, *oneline, *buffer;
+ int authorlen = authorlen, onelinelen;
+
+ /* get author and oneline */
+ for (buffer = commit->buffer; buffer && *buffer != '\0' &&
+ *buffer != '\n'; ) {
+ char *eol = strchr(buffer, '\n');
+
+ if (eol == NULL)
+ eol = buffer + strlen(buffer);
+ else
+ eol++;
+
+ if (!strncmp(buffer, "author ", 7)) {
+ char *bracket = strchr(buffer, '<');
+
+ if (bracket == NULL || bracket > eol)
+ die("Invalid commit buffer: %s",
+ sha1_to_hex(commit->object.sha1));
+
+ if (map_email(bracket + 1, scratch,
+ sizeof(scratch))) {
+ author = scratch;
+ authorlen = strlen(scratch);
+ } else {
+ if (bracket[-1] == ' ')
+ bracket--;
+
+ author = buffer + 7;
+ authorlen = bracket - buffer - 7;
+ }
+ }
+ buffer = eol;
+ }
+
+ if (author == NULL)
+ die ("Missing author: %s",
+ sha1_to_hex(commit->object.sha1));
+
+ if (buffer == NULL || *buffer == '\0') {
+ oneline = "<none>";
+ onelinelen = sizeof(oneline) + 1;
+ } else {
+ char *eol;
+
+ oneline = buffer + 1;
+ eol = strchr(oneline, '\n');
+ if (eol == NULL)
+ onelinelen = strlen(oneline);
+ else
+ onelinelen = eol - oneline;
+ }
+
+ insert_author_oneline(list,
+ author, authorlen, oneline, onelinelen);
+ }
+
+}
+
+int cmd_shortlog(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+ struct path_list list = { NULL, 0, 0, 1 };
+ int i, j, sort_by_number = 0, summary = 0;
+
+ /* since -n is a shadowed rev argument, parse our args first */
+ while (argc > 1) {
+ if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
+ sort_by_number = 1;
+ else if (!strcmp(argv[1], "-s") ||
+ !strcmp(argv[1], "--summary"))
+ summary = 1;
+ else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
+ usage(shortlog_usage);
+ else
+ break;
+ argv++;
+ argc--;
+ }
+ init_revisions(&rev, prefix);
+ argc = setup_revisions(argc, argv, &rev, NULL);
+ if (argc > 1)
+ die ("unrecognized argument: %s", argv[1]);
+
+ if (!access(".mailmap", R_OK))
+ read_mailmap(".mailmap");
+
+ if (rev.pending.nr == 0)
+ read_from_stdin(&list);
+ else
+ get_from_rev(&rev, &list);
+
+ if (sort_by_number)
+ qsort(list.items, list.nr, sizeof(struct path_list_item),
+ compare_by_number);
+
+ for (i = 0; i < list.nr; i++) {
+ struct path_list *onelines = list.items[i].util;
+
+ if (summary) {
+ printf("%s: %d\n", list.items[i].path, onelines->nr);
+ } else {
+ printf("%s (%d):\n", list.items[i].path, onelines->nr);
+ for (j = onelines->nr - 1; j >= 0; j--)
+ printf(" %s\n", onelines->items[j].path);
+ printf("\n");
+ }
+
+ onelines->strdup_paths = 1;
+ path_list_clear(onelines, 1);
+ free(onelines);
+ list.items[i].util = NULL;
+ }
+
+ list.strdup_paths = 1;
+ path_list_clear(&list, 1);
+ mailmap.strdup_paths = 1;
+ path_list_clear(&mailmap, 1);
+
+ return 0;
+}
+
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
index 06ec400d7f..073979855b 100644
--- a/builtin-show-ref.c
+++ b/builtin-show-ref.c
@@ -13,6 +13,7 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
{
struct object *obj;
const char *hex;
+ unsigned char peeled[20];
if (tags_only || heads_only) {
int match;
@@ -44,12 +45,15 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
match:
found_match++;
- obj = parse_object(sha1);
- if (!obj) {
- if (quiet)
- return 0;
- die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1));
- }
+
+ /* This changes the semantics slightly that even under quiet we
+ * detect and return error if the repository is corrupt and
+ * ref points at a nonexistent object.
+ */
+ if (!has_sha1_file(sha1))
+ die("git-show-ref: bad ref %s (%s)", refname,
+ sha1_to_hex(sha1));
+
if (quiet)
return 0;
@@ -58,10 +62,26 @@ match:
printf("%s\n", hex);
else
printf("%s %s\n", hex, refname);
- if (deref_tags && obj->type == OBJ_TAG) {
- obj = deref_tag(obj, refname, 0);
- hex = find_unique_abbrev(obj->sha1, abbrev);
- printf("%s %s^{}\n", hex, refname);
+
+ if (!deref_tags)
+ return 0;
+
+ if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) {
+ if (!is_null_sha1(peeled)) {
+ hex = find_unique_abbrev(peeled, abbrev);
+ printf("%s %s^{}\n", hex, refname);
+ }
+ }
+ else {
+ obj = parse_object(sha1);
+ if (!obj)
+ die("git-show-ref: bad ref %s (%s)", refname,
+ sha1_to_hex(sha1));
+ if (obj->type == OBJ_TAG) {
+ obj = deref_tag(obj, refname, 0);
+ hex = find_unique_abbrev(obj->sha1, abbrev);
+ printf("%s %s^{}\n", hex, refname);
+ }
}
return 0;
}
diff --git a/builtin.h b/builtin.h
index 43fed329ba..08519e7c82 100644
--- a/builtin.h
+++ b/builtin.h
@@ -42,6 +42,7 @@ extern int cmd_ls_files(int argc, const char **argv, const char *prefix);
extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
extern int cmd_mv(int argc, const char **argv, const char *prefix);
extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
@@ -55,6 +56,7 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_rm(int argc, const char **argv, const char *prefix);
extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
+extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
extern int cmd_show(int argc, const char **argv, const char *prefix);
extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
diff --git a/compat/subprocess.py b/compat/subprocess.py
deleted file mode 100644
index 6474eab119..0000000000
--- a/compat/subprocess.py
+++ /dev/null
@@ -1,1149 +0,0 @@
-# subprocess - Subprocesses with accessible I/O streams
-#
-# For more information about this module, see PEP 324.
-#
-# This module should remain compatible with Python 2.2, see PEP 291.
-#
-# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se>
-#
-# Licensed to PSF under a Contributor Agreement.
-# See http://www.python.org/2.4/license for licensing details.
-
-r"""subprocess - Subprocesses with accessible I/O streams
-
-This module allows you to spawn processes, connect to their
-input/output/error pipes, and obtain their return codes. This module
-intends to replace several other, older modules and functions, like:
-
-os.system
-os.spawn*
-os.popen*
-popen2.*
-commands.*
-
-Information about how the subprocess module can be used to replace these
-modules and functions can be found below.
-
-
-
-Using the subprocess module
-===========================
-This module defines one class called Popen:
-
-class Popen(args, bufsize=0, executable=None,
- stdin=None, stdout=None, stderr=None,
- preexec_fn=None, close_fds=False, shell=False,
- cwd=None, env=None, universal_newlines=False,
- startupinfo=None, creationflags=0):
-
-
-Arguments are:
-
-args should be a string, or a sequence of program arguments. The
-program to execute is normally the first item in the args sequence or
-string, but can be explicitly set by using the executable argument.
-
-On UNIX, with shell=False (default): In this case, the Popen class
-uses os.execvp() to execute the child program. args should normally
-be a sequence. A string will be treated as a sequence with the string
-as the only item (the program to execute).
-
-On UNIX, with shell=True: If args is a string, it specifies the
-command string to execute through the shell. If args is a sequence,
-the first item specifies the command string, and any additional items
-will be treated as additional shell arguments.
-
-On Windows: the Popen class uses CreateProcess() to execute the child
-program, which operates on strings. If args is a sequence, it will be
-converted to a string using the list2cmdline method. Please note that
-not all MS Windows applications interpret the command line the same
-way: The list2cmdline is designed for applications using the same
-rules as the MS C runtime.
-
-bufsize, if given, has the same meaning as the corresponding argument
-to the built-in open() function: 0 means unbuffered, 1 means line
-buffered, any other positive value means use a buffer of
-(approximately) that size. A negative bufsize means to use the system
-default, which usually means fully buffered. The default value for
-bufsize is 0 (unbuffered).
-
-stdin, stdout and stderr specify the executed programs' standard
-input, standard output and standard error file handles, respectively.
-Valid values are PIPE, an existing file descriptor (a positive
-integer), an existing file object, and None. PIPE indicates that a
-new pipe to the child should be created. With None, no redirection
-will occur; the child's file handles will be inherited from the
-parent. Additionally, stderr can be STDOUT, which indicates that the
-stderr data from the applications should be captured into the same
-file handle as for stdout.
-
-If preexec_fn is set to a callable object, this object will be called
-in the child process just before the child is executed.
-
-If close_fds is true, all file descriptors except 0, 1 and 2 will be
-closed before the child process is executed.
-
-if shell is true, the specified command will be executed through the
-shell.
-
-If cwd is not None, the current directory will be changed to cwd
-before the child is executed.
-
-If env is not None, it defines the environment variables for the new
-process.
-
-If universal_newlines is true, the file objects stdout and stderr are
-opened as a text files, but lines may be terminated by any of '\n',
-the Unix end-of-line convention, '\r', the Macintosh convention or
-'\r\n', the Windows convention. All of these external representations
-are seen as '\n' by the Python program. Note: This feature is only
-available if Python is built with universal newline support (the
-default). Also, the newlines attribute of the file objects stdout,
-stdin and stderr are not updated by the communicate() method.
-
-The startupinfo and creationflags, if given, will be passed to the
-underlying CreateProcess() function. They can specify things such as
-appearance of the main window and priority for the new process.
-(Windows only)
-
-
-This module also defines two shortcut functions:
-
-call(*args, **kwargs):
- Run command with arguments. Wait for command to complete, then
- return the returncode attribute.
-
- The arguments are the same as for the Popen constructor. Example:
-
- retcode = call(["ls", "-l"])
-
-
-Exceptions
-----------
-Exceptions raised in the child process, before the new program has
-started to execute, will be re-raised in the parent. Additionally,
-the exception object will have one extra attribute called
-'child_traceback', which is a string containing traceback information
-from the childs point of view.
-
-The most common exception raised is OSError. This occurs, for
-example, when trying to execute a non-existent file. Applications
-should prepare for OSErrors.
-
-A ValueError will be raised if Popen is called with invalid arguments.
-
-
-Security
---------
-Unlike some other popen functions, this implementation will never call
-/bin/sh implicitly. This means that all characters, including shell
-metacharacters, can safely be passed to child processes.
-
-
-Popen objects
-=============
-Instances of the Popen class have the following methods:
-
-poll()
- Check if child process has terminated. Returns returncode
- attribute.
-
-wait()
- Wait for child process to terminate. Returns returncode attribute.
-
-communicate(input=None)
- Interact with process: Send data to stdin. Read data from stdout
- and stderr, until end-of-file is reached. Wait for process to
- terminate. The optional stdin argument should be a string to be
- sent to the child process, or None, if no data should be sent to
- the child.
-
- communicate() returns a tuple (stdout, stderr).
-
- Note: The data read is buffered in memory, so do not use this
- method if the data size is large or unlimited.
-
-The following attributes are also available:
-
-stdin
- If the stdin argument is PIPE, this attribute is a file object
- that provides input to the child process. Otherwise, it is None.
-
-stdout
- If the stdout argument is PIPE, this attribute is a file object
- that provides output from the child process. Otherwise, it is
- None.
-
-stderr
- If the stderr argument is PIPE, this attribute is file object that
- provides error output from the child process. Otherwise, it is
- None.
-
-pid
- The process ID of the child process.
-
-returncode
- The child return code. A None value indicates that the process
- hasn't terminated yet. A negative value -N indicates that the
- child was terminated by signal N (UNIX only).
-
-
-Replacing older functions with the subprocess module
-====================================================
-In this section, "a ==> b" means that b can be used as a replacement
-for a.
-
-Note: All functions in this section fail (more or less) silently if
-the executed program cannot be found; this module raises an OSError
-exception.
-
-In the following examples, we assume that the subprocess module is
-imported with "from subprocess import *".
-
-
-Replacing /bin/sh shell backquote
----------------------------------
-output=`mycmd myarg`
-==>
-output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0]
-
-
-Replacing shell pipe line
--------------------------
-output=`dmesg | grep hda`
-==>
-p1 = Popen(["dmesg"], stdout=PIPE)
-p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
-output = p2.communicate()[0]
-
-
-Replacing os.system()
----------------------
-sts = os.system("mycmd" + " myarg")
-==>
-p = Popen("mycmd" + " myarg", shell=True)
-sts = os.waitpid(p.pid, 0)
-
-Note:
-
-* Calling the program through the shell is usually not required.
-
-* It's easier to look at the returncode attribute than the
- exitstatus.
-
-A more real-world example would look like this:
-
-try:
- retcode = call("mycmd" + " myarg", shell=True)
- if retcode < 0:
- print >>sys.stderr, "Child was terminated by signal", -retcode
- else:
- print >>sys.stderr, "Child returned", retcode
-except OSError, e:
- print >>sys.stderr, "Execution failed:", e
-
-
-Replacing os.spawn*
--------------------
-P_NOWAIT example:
-
-pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg")
-==>
-pid = Popen(["/bin/mycmd", "myarg"]).pid
-
-
-P_WAIT example:
-
-retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg")
-==>
-retcode = call(["/bin/mycmd", "myarg"])
-
-
-Vector example:
-
-os.spawnvp(os.P_NOWAIT, path, args)
-==>
-Popen([path] + args[1:])
-
-
-Environment example:
-
-os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env)
-==>
-Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"})
-
-
-Replacing os.popen*
--------------------
-pipe = os.popen(cmd, mode='r', bufsize)
-==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout
-
-pipe = os.popen(cmd, mode='w', bufsize)
-==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
-
-
-(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
- stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdin, child_stdout) = (p.stdin, p.stdout)
-
-
-(child_stdin,
- child_stdout,
- child_stderr) = os.popen3(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
- stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
-(child_stdin,
- child_stdout,
- child_stderr) = (p.stdin, p.stdout, p.stderr)
-
-
-(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
- stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
-(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
-
-
-Replacing popen2.*
-------------------
-Note: If the cmd argument to popen2 functions is a string, the command
-is executed through /bin/sh. If it is a list, the command is directly
-executed.
-
-(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode)
-==>
-p = Popen(["somestring"], shell=True, bufsize=bufsize
- stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdout, child_stdin) = (p.stdout, p.stdin)
-
-
-(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode)
-==>
-p = Popen(["mycmd", "myarg"], bufsize=bufsize,
- stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdout, child_stdin) = (p.stdout, p.stdin)
-
-The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen,
-except that:
-
-* subprocess.Popen raises an exception if the execution fails
-* the capturestderr argument is replaced with the stderr argument.
-* stdin=PIPE and stdout=PIPE must be specified.
-* popen2 closes all filedescriptors by default, but you have to specify
- close_fds=True with subprocess.Popen.
-
-
-"""
-
-import sys
-mswindows = (sys.platform == "win32")
-
-import os
-import types
-import traceback
-
-if mswindows:
- import threading
- import msvcrt
- if 0: # <-- change this to use pywin32 instead of the _subprocess driver
- import pywintypes
- from win32api import GetStdHandle, STD_INPUT_HANDLE, \
- STD_OUTPUT_HANDLE, STD_ERROR_HANDLE
- from win32api import GetCurrentProcess, DuplicateHandle, \
- GetModuleFileName, GetVersion
- from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE
- from win32pipe import CreatePipe
- from win32process import CreateProcess, STARTUPINFO, \
- GetExitCodeProcess, STARTF_USESTDHANDLES, \
- STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
- from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0
- else:
- from _subprocess import *
- class STARTUPINFO:
- dwFlags = 0
- hStdInput = None
- hStdOutput = None
- hStdError = None
- class pywintypes:
- error = IOError
-else:
- import select
- import errno
- import fcntl
- import pickle
-
-__all__ = ["Popen", "PIPE", "STDOUT", "call"]
-
-try:
- MAXFD = os.sysconf("SC_OPEN_MAX")
-except:
- MAXFD = 256
-
-# True/False does not exist on 2.2.0
-try:
- False
-except NameError:
- False = 0
- True = 1
-
-_active = []
-
-def _cleanup():
- for inst in _active[:]:
- inst.poll()
-
-PIPE = -1
-STDOUT = -2
-
-
-def call(*args, **kwargs):
- """Run command with arguments. Wait for command to complete, then
- return the returncode attribute.
-
- The arguments are the same as for the Popen constructor. Example:
-
- retcode = call(["ls", "-l"])
- """
- return Popen(*args, **kwargs).wait()
-
-
-def list2cmdline(seq):
- """
- Translate a sequence of arguments into a command line
- string, using the same rules as the MS C runtime:
-
- 1) Arguments are delimited by white space, which is either a
- space or a tab.
-
- 2) A string surrounded by double quotation marks is
- interpreted as a single argument, regardless of white space
- contained within. A quoted string can be embedded in an
- argument.
-
- 3) A double quotation mark preceded by a backslash is
- interpreted as a literal double quotation mark.
-
- 4) Backslashes are interpreted literally, unless they
- immediately precede a double quotation mark.
-
- 5) If backslashes immediately precede a double quotation mark,
- every pair of backslashes is interpreted as a literal
- backslash. If the number of backslashes is odd, the last
- backslash escapes the next double quotation mark as
- described in rule 3.
- """
-
- # See
- # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp
- result = []
- needquote = False
- for arg in seq:
- bs_buf = []
-
- # Add a space to separate this argument from the others
- if result:
- result.append(' ')
-
- needquote = (" " in arg) or ("\t" in arg)
- if needquote:
- result.append('"')
-
- for c in arg:
- if c == '\\':
- # Don't know if we need to double yet.
- bs_buf.append(c)
- elif c == '"':
- # Double backspaces.
- result.append('\\' * len(bs_buf)*2)
- bs_buf = []
- result.append('\\"')
- else:
- # Normal char
- if bs_buf:
- result.extend(bs_buf)
- bs_buf = []
- result.append(c)
-
- # Add remaining backspaces, if any.
- if bs_buf:
- result.extend(bs_buf)
-
- if needquote:
- result.extend(bs_buf)
- result.append('"')
-
- return ''.join(result)
-
-
-class Popen(object):
- def __init__(self, args, bufsize=0, executable=None,
- stdin=None, stdout=None, stderr=None,
- preexec_fn=None, close_fds=False, shell=False,
- cwd=None, env=None, universal_newlines=False,
- startupinfo=None, creationflags=0):
- """Create new Popen instance."""
- _cleanup()
-
- if not isinstance(bufsize, (int, long)):
- raise TypeError("bufsize must be an integer")
-
- if mswindows:
- if preexec_fn is not None:
- raise ValueError("preexec_fn is not supported on Windows "
- "platforms")
- if close_fds:
- raise ValueError("close_fds is not supported on Windows "
- "platforms")
- else:
- # POSIX
- if startupinfo is not None:
- raise ValueError("startupinfo is only supported on Windows "
- "platforms")
- if creationflags != 0:
- raise ValueError("creationflags is only supported on Windows "
- "platforms")
-
- self.stdin = None
- self.stdout = None
- self.stderr = None
- self.pid = None
- self.returncode = None
- self.universal_newlines = universal_newlines
-
- # Input and output objects. The general principle is like
- # this:
- #
- # Parent Child
- # ------ -----
- # p2cwrite ---stdin---> p2cread
- # c2pread <--stdout--- c2pwrite
- # errread <--stderr--- errwrite
- #
- # On POSIX, the child objects are file descriptors. On
- # Windows, these are Windows file handles. The parent objects
- # are file descriptors on both platforms. The parent objects
- # are None when not using PIPEs. The child objects are None
- # when not redirecting.
-
- (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite) = self._get_handles(stdin, stdout, stderr)
-
- self._execute_child(args, executable, preexec_fn, close_fds,
- cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
-
- if p2cwrite:
- self.stdin = os.fdopen(p2cwrite, 'wb', bufsize)
- if c2pread:
- if universal_newlines:
- self.stdout = os.fdopen(c2pread, 'rU', bufsize)
- else:
- self.stdout = os.fdopen(c2pread, 'rb', bufsize)
- if errread:
- if universal_newlines:
- self.stderr = os.fdopen(errread, 'rU', bufsize)
- else:
- self.stderr = os.fdopen(errread, 'rb', bufsize)
-
- _active.append(self)
-
-
- def _translate_newlines(self, data):
- data = data.replace("\r\n", "\n")
- data = data.replace("\r", "\n")
- return data
-
-
- if mswindows:
- #
- # Windows methods
- #
- def _get_handles(self, stdin, stdout, stderr):
- """Construct and return tuple with IO objects:
- p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
- """
- if stdin == None and stdout == None and stderr == None:
- return (None, None, None, None, None, None)
-
- p2cread, p2cwrite = None, None
- c2pread, c2pwrite = None, None
- errread, errwrite = None, None
-
- if stdin == None:
- p2cread = GetStdHandle(STD_INPUT_HANDLE)
- elif stdin == PIPE:
- p2cread, p2cwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- p2cwrite = p2cwrite.Detach()
- p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0)
- elif type(stdin) == types.IntType:
- p2cread = msvcrt.get_osfhandle(stdin)
- else:
- # Assuming file-like object
- p2cread = msvcrt.get_osfhandle(stdin.fileno())
- p2cread = self._make_inheritable(p2cread)
-
- if stdout == None:
- c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE)
- elif stdout == PIPE:
- c2pread, c2pwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- c2pread = c2pread.Detach()
- c2pread = msvcrt.open_osfhandle(c2pread, 0)
- elif type(stdout) == types.IntType:
- c2pwrite = msvcrt.get_osfhandle(stdout)
- else:
- # Assuming file-like object
- c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
- c2pwrite = self._make_inheritable(c2pwrite)
-
- if stderr == None:
- errwrite = GetStdHandle(STD_ERROR_HANDLE)
- elif stderr == PIPE:
- errread, errwrite = CreatePipe(None, 0)
- # Detach and turn into fd
- errread = errread.Detach()
- errread = msvcrt.open_osfhandle(errread, 0)
- elif stderr == STDOUT:
- errwrite = c2pwrite
- elif type(stderr) == types.IntType:
- errwrite = msvcrt.get_osfhandle(stderr)
- else:
- # Assuming file-like object
- errwrite = msvcrt.get_osfhandle(stderr.fileno())
- errwrite = self._make_inheritable(errwrite)
-
- return (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
-
-
- def _make_inheritable(self, handle):
- """Return a duplicate of handle, which is inheritable"""
- return DuplicateHandle(GetCurrentProcess(), handle,
- GetCurrentProcess(), 0, 1,
- DUPLICATE_SAME_ACCESS)
-
-
- def _find_w9xpopen(self):
- """Find and return absolute path to w9xpopen.exe"""
- w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)),
- "w9xpopen.exe")
- if not os.path.exists(w9xpopen):
- # Eeek - file-not-found - possibly an embedding
- # situation - see if we can locate it in sys.exec_prefix
- w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix),
- "w9xpopen.exe")
- if not os.path.exists(w9xpopen):
- raise RuntimeError("Cannot locate w9xpopen.exe, which is "
- "needed for Popen to work with your "
- "shell or platform.")
- return w9xpopen
-
-
- def _execute_child(self, args, executable, preexec_fn, close_fds,
- cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite):
- """Execute program (MS Windows version)"""
-
- if not isinstance(args, types.StringTypes):
- args = list2cmdline(args)
-
- # Process startup details
- default_startupinfo = STARTUPINFO()
- if startupinfo == None:
- startupinfo = default_startupinfo
- if not None in (p2cread, c2pwrite, errwrite):
- startupinfo.dwFlags |= STARTF_USESTDHANDLES
- startupinfo.hStdInput = p2cread
- startupinfo.hStdOutput = c2pwrite
- startupinfo.hStdError = errwrite
-
- if shell:
- default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW
- default_startupinfo.wShowWindow = SW_HIDE
- comspec = os.environ.get("COMSPEC", "cmd.exe")
- args = comspec + " /c " + args
- if (GetVersion() >= 0x80000000L or
- os.path.basename(comspec).lower() == "command.com"):
- # Win9x, or using command.com on NT. We need to
- # use the w9xpopen intermediate program. For more
- # information, see KB Q150956
- # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp)
- w9xpopen = self._find_w9xpopen()
- args = '"%s" %s' % (w9xpopen, args)
- # Not passing CREATE_NEW_CONSOLE has been known to
- # cause random failures on win9x. Specifically a
- # dialog: "Your program accessed mem currently in
- # use at xxx" and a hopeful warning about the
- # stability of your system. Cost is Ctrl+C wont
- # kill children.
- creationflags |= CREATE_NEW_CONSOLE
-
- # Start the process
- try:
- hp, ht, pid, tid = CreateProcess(executable, args,
- # no special security
- None, None,
- # must inherit handles to pass std
- # handles
- 1,
- creationflags,
- env,
- cwd,
- startupinfo)
- except pywintypes.error, e:
- # Translate pywintypes.error to WindowsError, which is
- # a subclass of OSError. FIXME: We should really
- # translate errno using _sys_errlist (or simliar), but
- # how can this be done from Python?
- raise WindowsError(*e.args)
-
- # Retain the process handle, but close the thread handle
- self._handle = hp
- self.pid = pid
- ht.Close()
-
- # Child is launched. Close the parent's copy of those pipe
- # handles that only the child should have open. You need
- # to make sure that no handles to the write end of the
- # output pipe are maintained in this process or else the
- # pipe will not close when the child process exits and the
- # ReadFile will hang.
- if p2cread != None:
- p2cread.Close()
- if c2pwrite != None:
- c2pwrite.Close()
- if errwrite != None:
- errwrite.Close()
-
-
- def poll(self):
- """Check if child process has terminated. Returns returncode
- attribute."""
- if self.returncode == None:
- if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0:
- self.returncode = GetExitCodeProcess(self._handle)
- _active.remove(self)
- return self.returncode
-
-
- def wait(self):
- """Wait for child process to terminate. Returns returncode
- attribute."""
- if self.returncode == None:
- obj = WaitForSingleObject(self._handle, INFINITE)
- self.returncode = GetExitCodeProcess(self._handle)
- _active.remove(self)
- return self.returncode
-
-
- def _readerthread(self, fh, buffer):
- buffer.append(fh.read())
-
-
- def communicate(self, input=None):
- """Interact with process: Send data to stdin. Read data from
- stdout and stderr, until end-of-file is reached. Wait for
- process to terminate. The optional input argument should be a
- string to be sent to the child process, or None, if no data
- should be sent to the child.
-
- communicate() returns a tuple (stdout, stderr)."""
- stdout = None # Return
- stderr = None # Return
-
- if self.stdout:
- stdout = []
- stdout_thread = threading.Thread(target=self._readerthread,
- args=(self.stdout, stdout))
- stdout_thread.setDaemon(True)
- stdout_thread.start()
- if self.stderr:
- stderr = []
- stderr_thread = threading.Thread(target=self._readerthread,
- args=(self.stderr, stderr))
- stderr_thread.setDaemon(True)
- stderr_thread.start()
-
- if self.stdin:
- if input != None:
- self.stdin.write(input)
- self.stdin.close()
-
- if self.stdout:
- stdout_thread.join()
- if self.stderr:
- stderr_thread.join()
-
- # All data exchanged. Translate lists into strings.
- if stdout != None:
- stdout = stdout[0]
- if stderr != None:
- stderr = stderr[0]
-
- # Translate newlines, if requested. We cannot let the file
- # object do the translation: It is based on stdio, which is
- # impossible to combine with select (unless forcing no
- # buffering).
- if self.universal_newlines and hasattr(open, 'newlines'):
- if stdout:
- stdout = self._translate_newlines(stdout)
- if stderr:
- stderr = self._translate_newlines(stderr)
-
- self.wait()
- return (stdout, stderr)
-
- else:
- #
- # POSIX methods
- #
- def _get_handles(self, stdin, stdout, stderr):
- """Construct and return tuple with IO objects:
- p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
- """
- p2cread, p2cwrite = None, None
- c2pread, c2pwrite = None, None
- errread, errwrite = None, None
-
- if stdin == None:
- pass
- elif stdin == PIPE:
- p2cread, p2cwrite = os.pipe()
- elif type(stdin) == types.IntType:
- p2cread = stdin
- else:
- # Assuming file-like object
- p2cread = stdin.fileno()
-
- if stdout == None:
- pass
- elif stdout == PIPE:
- c2pread, c2pwrite = os.pipe()
- elif type(stdout) == types.IntType:
- c2pwrite = stdout
- else:
- # Assuming file-like object
- c2pwrite = stdout.fileno()
-
- if stderr == None:
- pass
- elif stderr == PIPE:
- errread, errwrite = os.pipe()
- elif stderr == STDOUT:
- errwrite = c2pwrite
- elif type(stderr) == types.IntType:
- errwrite = stderr
- else:
- # Assuming file-like object
- errwrite = stderr.fileno()
-
- return (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
-
-
- def _set_cloexec_flag(self, fd):
- try:
- cloexec_flag = fcntl.FD_CLOEXEC
- except AttributeError:
- cloexec_flag = 1
-
- old = fcntl.fcntl(fd, fcntl.F_GETFD)
- fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag)
-
-
- def _close_fds(self, but):
- for i in range(3, MAXFD):
- if i == but:
- continue
- try:
- os.close(i)
- except:
- pass
-
-
- def _execute_child(self, args, executable, preexec_fn, close_fds,
- cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite):
- """Execute program (POSIX version)"""
-
- if isinstance(args, types.StringTypes):
- args = [args]
-
- if shell:
- args = ["/bin/sh", "-c"] + args
-
- if executable == None:
- executable = args[0]
-
- # For transferring possible exec failure from child to parent
- # The first char specifies the exception type: 0 means
- # OSError, 1 means some other error.
- errpipe_read, errpipe_write = os.pipe()
- self._set_cloexec_flag(errpipe_write)
-
- self.pid = os.fork()
- if self.pid == 0:
- # Child
- try:
- # Close parent's pipe ends
- if p2cwrite:
- os.close(p2cwrite)
- if c2pread:
- os.close(c2pread)
- if errread:
- os.close(errread)
- os.close(errpipe_read)
-
- # Dup fds for child
- if p2cread:
- os.dup2(p2cread, 0)
- if c2pwrite:
- os.dup2(c2pwrite, 1)
- if errwrite:
- os.dup2(errwrite, 2)
-
- # Close pipe fds. Make sure we doesn't close the same
- # fd more than once.
- if p2cread:
- os.close(p2cread)
- if c2pwrite and c2pwrite not in (p2cread,):
- os.close(c2pwrite)
- if errwrite and errwrite not in (p2cread, c2pwrite):
- os.close(errwrite)
-
- # Close all other fds, if asked for
- if close_fds:
- self._close_fds(but=errpipe_write)
-
- if cwd != None:
- os.chdir(cwd)
-
- if preexec_fn:
- apply(preexec_fn)
-
- if env == None:
- os.execvp(executable, args)
- else:
- os.execvpe(executable, args, env)
-
- except:
- exc_type, exc_value, tb = sys.exc_info()
- # Save the traceback and attach it to the exception object
- exc_lines = traceback.format_exception(exc_type,
- exc_value,
- tb)
- exc_value.child_traceback = ''.join(exc_lines)
- os.write(errpipe_write, pickle.dumps(exc_value))
-
- # This exitcode won't be reported to applications, so it
- # really doesn't matter what we return.
- os._exit(255)
-
- # Parent
- os.close(errpipe_write)
- if p2cread and p2cwrite:
- os.close(p2cread)
- if c2pwrite and c2pread:
- os.close(c2pwrite)
- if errwrite and errread:
- os.close(errwrite)
-
- # Wait for exec to fail or succeed; possibly raising exception
- data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB
- os.close(errpipe_read)
- if data != "":
- os.waitpid(self.pid, 0)
- child_exception = pickle.loads(data)
- raise child_exception
-
-
- def _handle_exitstatus(self, sts):
- if os.WIFSIGNALED(sts):
- self.returncode = -os.WTERMSIG(sts)
- elif os.WIFEXITED(sts):
- self.returncode = os.WEXITSTATUS(sts)
- else:
- # Should never happen
- raise RuntimeError("Unknown child exit status!")
-
- _active.remove(self)
-
-
- def poll(self):
- """Check if child process has terminated. Returns returncode
- attribute."""
- if self.returncode == None:
- try:
- pid, sts = os.waitpid(self.pid, os.WNOHANG)
- if pid == self.pid:
- self._handle_exitstatus(sts)
- except os.error:
- pass
- return self.returncode
-
-
- def wait(self):
- """Wait for child process to terminate. Returns returncode
- attribute."""
- if self.returncode == None:
- pid, sts = os.waitpid(self.pid, 0)
- self._handle_exitstatus(sts)
- return self.returncode
-
-
- def communicate(self, input=None):
- """Interact with process: Send data to stdin. Read data from
- stdout and stderr, until end-of-file is reached. Wait for
- process to terminate. The optional input argument should be a
- string to be sent to the child process, or None, if no data
- should be sent to the child.
-
- communicate() returns a tuple (stdout, stderr)."""
- read_set = []
- write_set = []
- stdout = None # Return
- stderr = None # Return
-
- if self.stdin:
- # Flush stdio buffer. This might block, if the user has
- # been writing to .stdin in an uncontrolled fashion.
- self.stdin.flush()
- if input:
- write_set.append(self.stdin)
- else:
- self.stdin.close()
- if self.stdout:
- read_set.append(self.stdout)
- stdout = []
- if self.stderr:
- read_set.append(self.stderr)
- stderr = []
-
- while read_set or write_set:
- rlist, wlist, xlist = select.select(read_set, write_set, [])
-
- if self.stdin in wlist:
- # When select has indicated that the file is writable,
- # we can write up to PIPE_BUF bytes without risk
- # blocking. POSIX defines PIPE_BUF >= 512
- bytes_written = os.write(self.stdin.fileno(), input[:512])
- input = input[bytes_written:]
- if not input:
- self.stdin.close()
- write_set.remove(self.stdin)
-
- if self.stdout in rlist:
- data = os.read(self.stdout.fileno(), 1024)
- if data == "":
- self.stdout.close()
- read_set.remove(self.stdout)
- stdout.append(data)
-
- if self.stderr in rlist:
- data = os.read(self.stderr.fileno(), 1024)
- if data == "":
- self.stderr.close()
- read_set.remove(self.stderr)
- stderr.append(data)
-
- # All data exchanged. Translate lists into strings.
- if stdout != None:
- stdout = ''.join(stdout)
- if stderr != None:
- stderr = ''.join(stderr)
-
- # Translate newlines, if requested. We cannot let the file
- # object do the translation: It is based on stdio, which is
- # impossible to combine with select (unless forcing no
- # buffering).
- if self.universal_newlines and hasattr(open, 'newlines'):
- if stdout:
- stdout = self._translate_newlines(stdout)
- if stderr:
- stderr = self._translate_newlines(stderr)
-
- self.wait()
- return (stdout, stderr)
-
-
-def _demo_posix():
- #
- # Example 1: Simple redirection: Get process list
- #
- plist = Popen(["ps"], stdout=PIPE).communicate()[0]
- print "Process list:"
- print plist
-
- #
- # Example 2: Change uid before executing child
- #
- if os.getuid() == 0:
- p = Popen(["id"], preexec_fn=lambda: os.setuid(100))
- p.wait()
-
- #
- # Example 3: Connecting several subprocesses
- #
- print "Looking for 'hda'..."
- p1 = Popen(["dmesg"], stdout=PIPE)
- p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
- print repr(p2.communicate()[0])
-
- #
- # Example 4: Catch execution error
- #
- print
- print "Trying a weird file..."
- try:
- print Popen(["/this/path/does/not/exist"]).communicate()
- except OSError, e:
- if e.errno == errno.ENOENT:
- print "The file didn't exist. I thought so..."
- print "Child traceback:"
- print e.child_traceback
- else:
- print "Error", e.errno
- else:
- print >>sys.stderr, "Gosh. No error."
-
-
-def _demo_windows():
- #
- # Example 1: Connecting several subprocesses
- #
- print "Looking for 'PROMPT' in set output..."
- p1 = Popen("set", stdout=PIPE, shell=True)
- p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE)
- print repr(p2.communicate()[0])
-
- #
- # Example 2: Simple execution of program
- #
- print "Executing calc..."
- p = Popen("calc")
- p.wait()
-
-
-if __name__ == "__main__":
- if mswindows:
- _demo_windows()
- else:
- _demo_posix()
diff --git a/config.c b/config.c
index 3cae3901aa..1bdef44a3a 100644
--- 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;
}
diff --git a/config.mak.in b/config.mak.in
index 1cafa19ed4..9a578405d8 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -13,7 +13,6 @@ bindir = @bindir@
#gitexecdir = @libexecdir@/git-core/
datarootdir = @datarootdir@
template_dir = @datadir@/git-core/templates/
-GIT_PYTHON_DIR = @datadir@/git-core/python
mandir=@mandir@
@@ -23,7 +22,6 @@ VPATH = @srcdir@
export exec_prefix mandir
export srcdir VPATH
-NO_PYTHON=@NO_PYTHON@
NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
NO_OPENSSL=@NO_OPENSSL@
NO_CURL=@NO_CURL@
diff --git a/configure.ac b/configure.ac
index cff5722eb9..34e34789bd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -75,20 +75,6 @@ GIT_ARG_SET_PATH(shell)
# Define PERL_PATH to provide path to Perl.
GIT_ARG_SET_PATH(perl)
#
-# Define PYTHON_PATH to provide path to Python.
-AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python])
-AS_HELP_STRING([--without-python], [don't use python scripts])],
- [if test "$withval" = "no"; then \
- NO_PYTHON=YesPlease; \
- elif test "$withval" = "yes"; then \
- NO_PYTHON=; \
- else \
- NO_PYTHON=; \
- PYTHON_PATH=$withval; \
- fi; \
- ])
-AC_SUBST(NO_PYTHON)
-AC_SUBST(PYTHON_PATH)
## Checks for programs.
@@ -98,18 +84,6 @@ AC_PROG_CC([cc gcc])
#AC_PROG_INSTALL # needs install-sh or install.sh in sources
AC_CHECK_TOOL(AR, ar, :)
AC_CHECK_PROGS(TAR, [gtar tar])
-#
-# Define PYTHON_PATH to provide path to Python.
-if test -z "$NO_PYTHON"; then
- if test -z "$PYTHON_PATH"; then
- AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2])
- fi
- if test -n "$PYTHON_PATH"; then
- GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@])
- NO_PYTHON=""
- fi
-fi
-
## Checks for libraries.
AC_MSG_NOTICE([CHECKS for libraries])
@@ -262,22 +236,9 @@ AC_SUBST(NO_SETENV)
# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
# Enable it on Windows. By default, symrefs are still used.
#
-# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
-AC_CACHE_CHECK([for subprocess.py],
- [ac_cv_python_has_subprocess_py],
-[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then
- ac_cv_python_has_subprocess_py=yes
-else
- ac_cv_python_has_subprocess_py=no
-fi])
-if test $ac_cv_python_has_subprocess_py != yes; then
- GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease])
-fi
-#
# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
# a missing newline at the end of the file.
-
## Site configuration (override autodetection)
## --with-PACKAGE[=ARG] and --without-PACKAGE
AC_MSG_NOTICE([CHECKS for site configuration])
diff --git a/connect.c b/connect.c
index b9666cc0d8..f7edba82c4 100644
--- a/connect.c
+++ b/connect.c
@@ -144,6 +144,7 @@ struct refspec {
* +A:B means overwrite remote B with local A.
* +A is a shorthand for +A:A.
* A is a shorthand for A:A.
+ * :B means delete remote B.
*/
static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
{
@@ -240,6 +241,13 @@ static struct ref *try_explicit_object_name(const char *name)
unsigned char sha1[20];
struct ref *ref;
int len;
+
+ if (!*name) {
+ ref = xcalloc(1, sizeof(*ref) + 20);
+ strcpy(ref->name, "(delete)");
+ hashclr(ref->new_sha1);
+ return ref;
+ }
if (get_sha1(name, sha1))
return NULL;
len = strlen(name) + 1;
@@ -262,7 +270,8 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
break;
case 0:
/* The source could be in the get_sha1() format
- * not a reference name.
+ * not a reference name. :refs/other is a
+ * way to delete 'other' ref at the remote end.
*/
matched_src = try_explicit_object_name(rs[i].src);
if (matched_src)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index a43a177160..9c4d23a23c 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -18,26 +18,94 @@
# 2) Added the following line to your .bashrc:
# source ~/.git-completion.sh
#
+# 3) You may want to make sure the git executable is available
+# in your PATH before this script is sourced, as some caching
+# is performed while the script loads. If git isn't found
+# at source time then all lookups will be done on demand,
+# which may be slightly slower.
+#
+# 4) Consider changing your PS1 to also show the current branch:
+# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+#
+# The argument to __git_ps1 will be displayed only if you
+# are currently in a git repository. The %s token will be
+# the name of the current branch.
+#
__gitdir ()
{
- echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}"
+ if [ -z "$1" ]; then
+ if [ -n "$__git_dir" ]; then
+ echo "$__git_dir"
+ elif [ -d .git ]; then
+ echo .git
+ else
+ git rev-parse --git-dir 2>/dev/null
+ fi
+ elif [ -d "$1/.git" ]; then
+ echo "$1/.git"
+ else
+ echo "$1"
+ fi
+}
+
+__git_ps1 ()
+{
+ local b="$(git symbolic-ref HEAD 2>/dev/null)"
+ if [ -n "$b" ]; then
+ if [ -n "$1" ]; then
+ printf "$1" "${b##refs/heads/}"
+ else
+ printf " (%s)" "${b##refs/heads/}"
+ fi
+ fi
+}
+
+__git_heads ()
+{
+ local cmd i is_hash=y dir="$(__gitdir "$1")"
+ if [ -d "$dir" ]; then
+ for i in $(git --git-dir="$dir" \
+ for-each-ref --format='%(refname)' \
+ refs/heads ); do
+ echo "${i#refs/heads/}"
+ done
+ return
+ fi
+ for i in $(git-ls-remote "$1" 2>/dev/null); do
+ case "$is_hash,$i" in
+ y,*) is_hash=n ;;
+ n,*^{}) is_hash=y ;;
+ n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+ n,*) is_hash=y; echo "$i" ;;
+ esac
+ done
}
__git_refs ()
{
- local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+ local cmd i is_hash=y dir="$(__gitdir "$1")"
if [ -d "$dir" ]; then
- cmd=git-peek-remote
- else
- cmd=git-ls-remote
+ if [ -e "$dir/HEAD" ]; then echo HEAD; fi
+ for i in $(git --git-dir="$dir" \
+ for-each-ref --format='%(refname)' \
+ refs/tags refs/heads refs/remotes); do
+ case "$i" in
+ refs/tags/*) echo "${i#refs/tags/}" ;;
+ refs/heads/*) echo "${i#refs/heads/}" ;;
+ refs/remotes/*) echo "${i#refs/remotes/}" ;;
+ *) echo "$i" ;;
+ esac
+ done
+ return
fi
- for i in $($cmd "$dir" 2>/dev/null); do
+ for i in $(git-ls-remote "$dir" 2>/dev/null); do
case "$is_hash,$i" in
y,*) is_hash=n ;;
n,*^{}) is_hash=y ;;
n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+ n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;;
n,*) is_hash=y; echo "$i" ;;
esac
done
@@ -45,19 +113,25 @@ __git_refs ()
__git_refs2 ()
{
- local cmd i is_hash=y dir="${1:-$(__gitdir)}"
- if [ -d "$dir" ]; then
- cmd=git-peek-remote
- else
- cmd=git-ls-remote
- fi
- for i in $($cmd "$dir" 2>/dev/null); do
+ local i
+ for i in $(__git_refs "$1"); do
+ echo "$i:$i"
+ done
+}
+
+__git_refs_remotes ()
+{
+ local cmd i is_hash=y
+ for i in $(git-ls-remote "$1" 2>/dev/null); do
case "$is_hash,$i" in
+ n,refs/heads/*)
+ is_hash=y
+ echo "$i:refs/remotes/$1/${i#refs/heads/}"
+ ;;
y,*) is_hash=n ;;
n,*^{}) is_hash=y ;;
- n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;;
- n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;;
- n,*) is_hash=y; echo "$i:$i" ;;
+ n,refs/tags/*) is_hash=y;;
+ n,*) is_hash=y; ;;
esac
done
}
@@ -81,6 +155,22 @@ __git_remotes ()
done
}
+__git_merge_strategies ()
+{
+ if [ -n "$__git_merge_strategylist" ]; then
+ echo "$__git_merge_strategylist"
+ return
+ fi
+ sed -n "/^all_strategies='/{
+ s/^all_strategies='//
+ s/'//
+ p
+ q
+ }" "$(git --exec-path)/git-merge"
+}
+__git_merge_strategylist=
+__git_merge_strategylist="$(__git_merge_strategies 2>/dev/null)"
+
__git_complete_file ()
{
local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
@@ -115,6 +205,84 @@ __git_complete_file ()
esac
}
+__git_complete_revlist ()
+{
+ local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ *...*)
+ pfx="${cur%...*}..."
+ cur="${cur#*...}"
+ COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+ ;;
+ *..*)
+ pfx="${cur%..*}.."
+ cur="${cur#*..}"
+ COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+ ;;
+ esac
+}
+
+__git_commands ()
+{
+ if [ -n "$__git_commandlist" ]; then
+ echo "$__git_commandlist"
+ return
+ fi
+ local i IFS=" "$'\n'
+ for i in $(git help -a|egrep '^ ')
+ do
+ case $i in
+ check-ref-format) : plumbing;;
+ commit-tree) : plumbing;;
+ convert-objects) : plumbing;;
+ cvsserver) : daemon;;
+ daemon) : daemon;;
+ fetch-pack) : plumbing;;
+ hash-object) : plumbing;;
+ http-*) : transport;;
+ index-pack) : plumbing;;
+ local-fetch) : plumbing;;
+ mailinfo) : plumbing;;
+ mailsplit) : plumbing;;
+ merge-*) : plumbing;;
+ mktree) : plumbing;;
+ mktag) : plumbing;;
+ pack-objects) : plumbing;;
+ pack-redundant) : plumbing;;
+ pack-refs) : plumbing;;
+ parse-remote) : plumbing;;
+ patch-id) : plumbing;;
+ peek-remote) : plumbing;;
+ read-tree) : plumbing;;
+ receive-pack) : plumbing;;
+ rerere) : plumbing;;
+ rev-list) : plumbing;;
+ rev-parse) : plumbing;;
+ runstatus) : plumbing;;
+ sh-setup) : internal;;
+ shell) : daemon;;
+ send-pack) : plumbing;;
+ show-index) : plumbing;;
+ ssh-*) : transport;;
+ stripspace) : plumbing;;
+ symbolic-ref) : plumbing;;
+ unpack-file) : plumbing;;
+ unpack-objects) : plumbing;;
+ update-ref) : plumbing;;
+ update-server-info) : daemon;;
+ upload-archive) : plumbing;;
+ upload-pack) : plumbing;;
+ write-tree) : plumbing;;
+ *) echo $i;;
+ esac
+ done
+}
+__git_commandlist=
+__git_commandlist="$(__git_commands 2>/dev/null)"
+
__git_aliases ()
{
local i IFS=$'\n'
@@ -140,6 +308,54 @@ __git_aliased_command ()
done
}
+__git_whitespacelist="nowarn warn error error-all strip"
+
+_git_am ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ if [ -d .dotest ]; then
+ COMPREPLY=($(compgen -W "
+ --skip --resolved
+ " -- "$cur"))
+ return
+ fi
+ case "$cur" in
+ --whitespace=*)
+ COMPREPLY=($(compgen -W "$__git_whitespacelist" \
+ -- "${cur##--whitespace=}"))
+ return
+ ;;
+ --*)
+ COMPREPLY=($(compgen -W "
+ --signoff --utf8 --binary --3way --interactive
+ --whitespace=
+ " -- "$cur"))
+ return
+ esac
+ COMPREPLY=()
+}
+
+_git_apply ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ --whitespace=*)
+ COMPREPLY=($(compgen -W "$__git_whitespacelist" \
+ -- "${cur##--whitespace=}"))
+ return
+ ;;
+ --*)
+ COMPREPLY=($(compgen -W "
+ --stat --numstat --summary --check --index
+ --cached --index-info --reverse --reject --unidiff-zero
+ --apply --no-add --exclude=
+ --whitespace= --inaccurate-eof --verbose
+ " -- "$cur"))
+ return
+ esac
+ COMPREPLY=()
+}
+
_git_branch ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -168,6 +384,35 @@ _git_checkout ()
COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur"))
}
+_git_cherry_pick ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ --*)
+ COMPREPLY=($(compgen -W "
+ --edit --no-commit
+ " -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+ ;;
+ esac
+}
+
+_git_commit ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ --*)
+ COMPREPLY=($(compgen -W "
+ --all --author= --signoff --verify --no-verify
+ --edit --amend --include --only
+ " -- "$cur"))
+ return
+ esac
+ COMPREPLY=()
+}
+
_git_diff ()
{
__git_complete_file
@@ -209,6 +454,26 @@ _git_fetch ()
esac
}
+_git_format_patch ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ --*)
+ COMPREPLY=($(compgen -W "
+ --stdout --attach --thread
+ --output-directory
+ --numbered --start-number
+ --keep-subject
+ --signoff
+ --in-reply-to=
+ --full-index --binary
+ " -- "$cur"))
+ return
+ ;;
+ esac
+ __git_complete_revlist
+}
+
_git_ls_remote ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -222,22 +487,53 @@ _git_ls_tree ()
_git_log ()
{
- local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
- *...*)
- pfx="${cur%...*}..."
- cur="${cur#*...}"
- COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+ --pretty=*)
+ COMPREPLY=($(compgen -W "
+ oneline short medium full fuller email raw
+ " -- "${cur##--pretty=}"))
+ return
;;
- *..*)
- pfx="${cur%..*}.."
- cur="${cur#*..}"
- COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+ --*)
+ COMPREPLY=($(compgen -W "
+ --max-count= --max-age= --since= --after=
+ --min-age= --before= --until=
+ --root --not --topo-order --date-order
+ --no-merges
+ --abbrev-commit --abbrev=
+ --relative-date
+ --author= --committer= --grep=
+ --all-match
+ --pretty= --name-status --name-only
+ " -- "$cur"))
+ return
;;
- *)
- COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+ esac
+ __git_complete_revlist
+}
+
+_git_merge ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "${COMP_WORDS[COMP_CWORD-1]}" in
+ -s|--strategy)
+ COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur"))
+ return
+ esac
+ case "$cur" in
+ --strategy=*)
+ COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \
+ -- "${cur##--strategy=}"))
+ return
;;
+ --*)
+ COMPREPLY=($(compgen -W "
+ --no-commit --no-summary --squash --strategy
+ " -- "$cur"))
+ return
esac
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
}
_git_merge_base ()
@@ -246,6 +542,12 @@ _git_merge_base ()
COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
}
+_git_name_rev ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "--tags --all --stdin" -- "$cur"))
+}
+
_git_pull ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -298,17 +600,156 @@ _git_push ()
esac
}
-_git_reset ()
+_git_rebase ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
- local opt="--mixed --hard --soft"
- COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur"))
+ if [ -d .dotest ]; then
+ COMPREPLY=($(compgen -W "
+ --continue --skip --abort
+ " -- "$cur"))
+ return
+ fi
+ case "${COMP_WORDS[COMP_CWORD-1]}" in
+ -s|--strategy)
+ COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur"))
+ return
+ esac
+ case "$cur" in
+ --strategy=*)
+ COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \
+ -- "${cur##--strategy=}"))
+ return
+ ;;
+ --*)
+ COMPREPLY=($(compgen -W "
+ --onto --merge --strategy
+ " -- "$cur"))
+ return
+ esac
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
}
-_git_show ()
+_git_repo_config ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
- COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+ local prv="${COMP_WORDS[COMP_CWORD-1]}"
+ case "$prv" in
+ branch.*.remote)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ return
+ ;;
+ branch.*.merge)
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+ return
+ ;;
+ remote.*.fetch)
+ local remote="${prv#remote.}"
+ remote="${remote%.fetch}"
+ COMPREPLY=($(compgen -W "$(__git_refs_remotes "$remote")" \
+ -- "$cur"))
+ return
+ ;;
+ remote.*.push)
+ local remote="${prv#remote.}"
+ remote="${remote%.push}"
+ COMPREPLY=($(compgen -W "$(git --git-dir="$(__gitdir)" \
+ for-each-ref --format='%(refname):%(refname)' \
+ refs/heads)" -- "$cur"))
+ return
+ ;;
+ *.*)
+ COMPREPLY=()
+ return
+ ;;
+ esac
+ case "$cur" in
+ --*)
+ COMPREPLY=($(compgen -W "
+ --global --list --replace-all
+ --get --get-all --get-regexp
+ --unset --unset-all
+ " -- "$cur"))
+ return
+ ;;
+ branch.*.*)
+ local pfx="${cur%.*}."
+ cur="${cur##*.}"
+ COMPREPLY=($(compgen -P "$pfx" -W "remote merge" -- "$cur"))
+ return
+ ;;
+ branch.*)
+ local pfx="${cur%.*}."
+ cur="${cur#*.}"
+ COMPREPLY=($(compgen -P "$pfx" -S . \
+ -W "$(__git_heads)" -- "$cur"))
+ return
+ ;;
+ remote.*.*)
+ local pfx="${cur%.*}."
+ cur="${cur##*.}"
+ COMPREPLY=($(compgen -P "$pfx" -W "url fetch push" -- "$cur"))
+ return
+ ;;
+ remote.*)
+ local pfx="${cur%.*}."
+ cur="${cur#*.}"
+ COMPREPLY=($(compgen -P "$pfx" -S . \
+ -W "$(__git_remotes)" -- "$cur"))
+ return
+ ;;
+ esac
+ COMPREPLY=($(compgen -W "
+ apply.whitespace
+ core.fileMode
+ core.gitProxy
+ core.ignoreStat
+ core.preferSymlinkRefs
+ core.logAllRefUpdates
+ core.repositoryFormatVersion
+ core.sharedRepository
+ core.warnAmbiguousRefs
+ core.compression
+ core.legacyHeaders
+ i18n.commitEncoding
+ diff.color
+ color.diff
+ diff.renameLimit
+ diff.renames
+ pager.color
+ color.pager
+ status.color
+ color.status
+ log.showroot
+ show.difftree
+ showbranch.default
+ whatchanged.difftree
+ http.sslVerify
+ http.sslCert
+ http.sslKey
+ http.sslCAInfo
+ http.sslCAPath
+ http.maxRequests
+ http.lowSpeedLimit http.lowSpeedTime
+ http.noEPSV
+ pack.window
+ repack.useDeltaBaseOffset
+ pull.octopus pull.twohead
+ merge.summary
+ receive.unpackLimit
+ receive.denyNonFastForwards
+ user.name user.email
+ tar.umask
+ gitcvs.enabled
+ gitcvs.logfile
+ branch. remote.
+ " -- "$cur"))
+}
+
+_git_reset ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ local opt="--mixed --hard --soft"
+ COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur"))
}
_git ()
@@ -327,11 +768,11 @@ _git ()
done
if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
- COMPREPLY=($(compgen \
- -W "--git-dir= --version \
- $(git help -a|egrep '^ ') \
- $(__git_aliases)" \
- -- "${COMP_WORDS[COMP_CWORD]}"))
+ COMPREPLY=($(compgen -W "
+ --git-dir= --version --exec-path
+ $(__git_commands)
+ $(__git_aliases)
+ " -- "${COMP_WORDS[COMP_CWORD]}"))
return;
fi
@@ -339,20 +780,29 @@ _git ()
[ "$expansion" ] && command="$expansion"
case "$command" in
+ am) _git_am ;;
+ apply) _git_apply ;;
branch) _git_branch ;;
cat-file) _git_cat_file ;;
checkout) _git_checkout ;;
+ cherry-pick) _git_cherry_pick ;;
+ commit) _git_commit ;;
diff) _git_diff ;;
diff-tree) _git_diff_tree ;;
fetch) _git_fetch ;;
+ format-patch) _git_format_patch ;;
log) _git_log ;;
ls-remote) _git_ls_remote ;;
ls-tree) _git_ls_tree ;;
+ merge) _git_merge;;
merge-base) _git_merge_base ;;
+ name-rev) _git_name_rev ;;
pull) _git_pull ;;
push) _git_push ;;
+ rebase) _git_rebase ;;
+ repo-config) _git_repo_config ;;
reset) _git_reset ;;
- show) _git_show ;;
+ show) _git_log ;;
show-branch) _git_log ;;
whatchanged) _git_log ;;
*) COMPREPLY=() ;;
@@ -367,20 +817,29 @@ _gitk ()
complete -o default -o nospace -F _git git
complete -o default -F _gitk gitk
+complete -o default -F _git_am git-am
+complete -o default -F _git_apply git-apply
complete -o default -F _git_branch git-branch
complete -o default -o nospace -F _git_cat_file git-cat-file
complete -o default -F _git_checkout git-checkout
+complete -o default -F _git_cherry_pick git-cherry-pick
+complete -o default -F _git_commit git-commit
complete -o default -o nospace -F _git_diff git-diff
complete -o default -F _git_diff_tree git-diff-tree
complete -o default -o nospace -F _git_fetch git-fetch
+complete -o default -o nospace -F _git_format_patch git-format-patch
complete -o default -o nospace -F _git_log git-log
complete -o default -F _git_ls_remote git-ls-remote
complete -o default -o nospace -F _git_ls_tree git-ls-tree
+complete -o default -F _git_merge git-merge
complete -o default -F _git_merge_base git-merge-base
+complete -o default -F _git_name_rev git-name-rev
complete -o default -o nospace -F _git_pull git-pull
complete -o default -o nospace -F _git_push git-push
+complete -o default -F _git_rebase git-rebase
+complete -o default -F _git_repo_config git-repo-config
complete -o default -F _git_reset git-reset
-complete -o default -F _git_show git-show
+complete -o default -F _git_log git-show
complete -o default -o nospace -F _git_log git-show-branch
complete -o default -o nospace -F _git_log git-whatchanged
@@ -389,15 +848,20 @@ complete -o default -o nospace -F _git_log git-whatchanged
# included the '.exe' suffix.
#
if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
+complete -o default -F _git_apply git-apply.exe
complete -o default -o nospace -F _git git.exe
complete -o default -F _git_branch git-branch.exe
complete -o default -o nospace -F _git_cat_file git-cat-file.exe
complete -o default -o nospace -F _git_diff git-diff.exe
complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
+complete -o default -o nospace -F _git_format_patch git-format-patch.exe
complete -o default -o nospace -F _git_log git-log.exe
complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
complete -o default -F _git_merge_base git-merge-base.exe
+complete -o default -F _git_name_rev git-name-rev.exe
complete -o default -o nospace -F _git_push git-push.exe
+complete -o default -F _git_repo_config git-repo-config
+complete -o default -o nospace -F _git_log git-show.exe
complete -o default -o nospace -F _git_log git-show-branch.exe
complete -o default -o nospace -F _git_log git-whatchanged.exe
fi
diff --git a/contrib/mailmap.linux b/contrib/mailmap.linux
new file mode 100644
index 0000000000..e4907f80f1
--- /dev/null
+++ b/contrib/mailmap.linux
@@ -0,0 +1,42 @@
+#
+# Even with git, we don't always have name translations.
+# So have an email->real name table to translate the
+# (hopefully few) missing names
+#
+# repo-abbrev: /pub/scm/linux/kernel/git/
+#
+Adrian Bunk <bunk@stusta.de>
+Andreas Herrmann <aherrman@de.ibm.com>
+Andrew Morton <akpm@osdl.org>
+Andrew Vasquez <andrew.vasquez@qlogic.com>
+Christoph Hellwig <hch@lst.de>
+Corey Minyard <minyard@acm.org>
+David Woodhouse <dwmw2@shinybook.infradead.org>
+Domen Puncer <domen@coderock.org>
+Douglas Gilbert <dougg@torque.net>
+Ed L Cashin <ecashin@coraid.com>
+Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+Felix Moeller <felix@derklecks.de>
+Frank Zago <fzago@systemfabricworks.com>
+Greg Kroah-Hartman <gregkh@suse.de>
+James Bottomley <jejb@mulgrave.(none)>
+James Bottomley <jejb@titanic.il.steeleye.com>
+Jeff Garzik <jgarzik@pretzel.yyz.us>
+Jens Axboe <axboe@suse.de>
+Kay Sievers <kay.sievers@vrfy.org>
+Mitesh shah <mshah@teja.com>
+Morten Welinder <terra@gnome.org>
+Morten Welinder <welinder@anemone.rentec.com>
+Morten Welinder <welinder@darter.rentec.com>
+Morten Welinder <welinder@troll.com>
+Nguyen Anh Quynh <aquynh@gmail.com>
+Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
+Peter A Jonsson <pj@ludd.ltu.se>
+Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+Rudolf Marek <R.Marek@sh.cvut.cz>
+Rui Saraiva <rmps@joel.ist.utl.pt>
+Sachin P Sant <ssant@in.ibm.com>
+Santtu Hyrkk,Av(B <santtu.hyrkko@gmail.com>
+Simon Kelley <simon@thekelleys.org.uk>
+Tejun Heo <htejun@gmail.com>
+Tony Luck <tony.luck@intel.com>
diff --git a/diff.c b/diff.c
index 33153787b8..2df14b2469 100644
--- 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-pack.c b/fetch-pack.c
index 0a169dce85..743eab7efa 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -566,6 +566,29 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
return 0;
}
+static int remove_duplicates(int nr_heads, char **heads)
+{
+ int src, dst;
+
+ for (src = dst = 0; src < nr_heads; src++) {
+ /* If heads[src] is different from any of
+ * heads[0..dst], push it in.
+ */
+ int i;
+ for (i = 0; i < dst; i++) {
+ if (!strcmp(heads[i], heads[src]))
+ break;
+ }
+ if (i < dst)
+ continue;
+ if (src != dst)
+ heads[dst] = heads[src];
+ dst++;
+ }
+ heads[dst] = 0;
+ return dst;
+}
+
int main(int argc, char **argv)
{
int i, ret, nr_heads;
@@ -617,6 +640,8 @@ int main(int argc, char **argv)
pid = git_connect(fd, dest, exec);
if (pid < 0)
return 1;
+ if (heads && nr_heads)
+ nr_heads = remove_duplicates(nr_heads, heads);
ret = fetch_pack(fd, nr_heads, heads);
close(fd[0]);
close(fd[1]);
diff --git a/fetch.c b/fetch.c
index c426c04997..663b4b2f42 100644
--- 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;
}
}
diff --git a/git-clone.sh b/git-clone.sh
index d4ee93f75b..1f5d07a057 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -14,7 +14,7 @@ die() {
}
usage() {
- die "Usage: $0 [--template=<template_directory>] [--use-immingled-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+ die "Usage: $0 [--template=<template_directory>] [--no-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
}
get_repo_base() {
@@ -140,7 +140,7 @@ while
*,--use-separate-remote)
# default
use_separate_remote=t ;;
- *,--use-immingled-remote)
+ *,--no-separate-remote)
use_separate_remote= ;;
1,--reference) usage ;;
*,--reference)
@@ -176,7 +176,7 @@ repo="$1"
test -n "$repo" ||
die 'you must specify a repository to clone.'
-# --bare implies --no-checkout and --use-immingled-remote
+# --bare implies --no-checkout and --no-separate-remote
if test yes = "$bare"
then
if test yes = "$origin_override"
@@ -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
diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
index 7bac16e946..4863c91fe3 100755
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
@@ -2,9 +2,8 @@
# Known limitations:
# - does not propagate permissions
-# - tells "ready for commit" even when things could not be completed
-# (not sure this is true anymore, more testing is needed)
-# - does not handle whitespace in pathnames at all.
+# - error handling has not been extensively tested
+#
use strict;
use Getopt::Std;
@@ -115,47 +114,40 @@ if ($opt_a) {
}
close MSG;
-my (@afiles, @dfiles, @mfiles, @dirs);
-my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);
-#print @files;
-$? && die "Error in git-diff-tree";
-foreach my $f (@files) {
- chomp $f;
- my @fields = split(m!\s+!, $f);
- if ($fields[4] eq 'A') {
- my $path = $fields[5];
- push @afiles, $path;
- # add any needed parent directories
- $path = dirname $path;
- while (!-d $path and ! grep { $_ eq $path } @dirs) {
- unshift @dirs, $path;
- $path = dirname $path;
- }
- }
- if ($fields[4] eq 'M') {
- push @mfiles, $fields[5];
- }
- if ($fields[4] eq 'D') {
- push @dfiles, $fields[5];
- }
+`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+
+## apply non-binary changes
+my $fuzz = $opt_p ? 0 : 2;
+
+print "Checking if patch will apply\n";
+
+my @stat;
+open APPLY, "GIT_DIR= git-apply -C$fuzz --binary --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch";
+@stat=<APPLY>;
+close APPLY || die "Cannot patch";
+my (@bfiles,@files,@afiles,@dfiles);
+chomp @stat;
+foreach (@stat) {
+ push (@bfiles,$1) if m/^-\t-\t(.*)$/;
+ push (@files, $1) if m/^-\t-\t(.*)$/;
+ push (@files, $1) if m/^\d+\t\d+\t(.*)$/;
+ push (@afiles,$1) if m/^ create mode [0-7]+ (.*)$/;
+ push (@dfiles,$1) if m/^ delete mode [0-7]+ (.*)$/;
}
-my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles);
-@binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit);
-map { chomp } @binfiles;
-@abfiles = grep s/^Binary files \/dev\/null and b\/(.*) differ$/$1/, @binfiles;
-@dbfiles = grep s/^Binary files a\/(.*) and \/dev\/null differ$/$1/, @binfiles;
-@mbfiles = grep s/^Binary files a\/(.*) and b\/(.*) differ$/$1/, @binfiles;
-push @bfiles, @abfiles;
-push @bfiles, @dbfiles;
-push @bfiles, @mbfiles;
-push @mfiles, @mbfiles;
-
-$opt_v && print "The commit affects:\n ";
-$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n";
-undef @files; # don't need it anymore
+map { s/^"(.*)"$/$1/g } @bfiles,@files;
+map { s/\\([0-7]{3})/sprintf('%c',oct $1)/eg } @bfiles,@files;
# check that the files are clean and up to date according to cvs
my $dirty;
+my @dirs;
+foreach my $p (@afiles) {
+ my $path = dirname $p;
+ while (!-d $path and ! grep { $_ eq $path } @dirs) {
+ unshift @dirs, $path;
+ $path = dirname $path;
+ }
+}
+
foreach my $d (@dirs) {
if (-e $d) {
$dirty = 1;
@@ -178,7 +170,8 @@ foreach my $f (@afiles) {
}
}
-foreach my $f (@mfiles, @dfiles) {
+foreach my $f (@files) {
+ next if grep { $_ eq $f } @afiles;
# TODO:we need to handle removed in cvs
my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f));
if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
@@ -195,78 +188,18 @@ if ($dirty) {
}
}
-###
-### NOTE: if you are planning to die() past this point
-### you MUST call cleanupcvs(@files) before die()
-###
-
+print "Applying\n";
+`GIT_DIR= git-apply -C$fuzz --binary --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch";
-print "Creating new directories\n";
+print "Patch applied successfully. Adding new files and directories to CVS\n";
+my $dirtypatch = 0;
foreach my $d (@dirs) {
- unless (mkdir $d) {
- warn "Could not mkdir $d: $!";
- $dirty = 1;
- }
- `cvs add $d`;
- if ($?) {
- $dirty = 1;
+ if (system('cvs','add',$d)) {
+ $dirtypatch = 1;
warn "Failed to cvs add directory $d -- you may need to do it manually";
}
}
-print "'Patching' binary files\n";
-
-foreach my $f (@bfiles) {
- # check that the file in cvs matches the "old" file
- # extract the file to $tmpdir and compare with cmp
- if (not(grep { $_ eq $f } @afiles)) {
- my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}");
- chomp $tree;
- my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
- chomp $blob;
- `git-cat-file blob $blob > $tmpdir/blob`;
- if (system('cmp', '-s', $f, "$tmpdir/blob")) {
- warn "Binary file $f in CVS does not match parent.\n";
- if (not $opt_f) {
- $dirty = 1;
- next;
- }
- }
- }
- if (not(grep { $_ eq $f } @dfiles)) {
- my $tree = safe_pipe_capture('git-rev-parse', "$commit^{tree}");
- chomp $tree;
- my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
- chomp $blob;
- # replace with the new file
- `git-cat-file blob $blob > $f`;
- }
-
- # TODO: something smart with file modes
-
-}
-if ($dirty) {
- cleanupcvs(@files);
- die "Exiting: Binary files in CVS do not match parent";
-}
-
-## apply non-binary changes
-my $fuzz = $opt_p ? 0 : 2;
-
-print "Patching non-binary files\n";
-
-if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) {
- print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;
-}
-
-my $dirtypatch = 0;
-if (($? >> 8) == 2) {
- cleanupcvs(@files);
- die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually";
-} elsif (($? >> 8) == 1) { # some hunks failed to apply
- $dirtypatch = 1;
-}
-
foreach my $f (@afiles) {
if (grep { $_ eq $f } @bfiles) {
system('cvs', 'add','-kb',$f);
@@ -274,7 +207,7 @@ foreach my $f (@afiles) {
system('cvs', 'add', $f);
}
if ($?) {
- $dirty = 1;
+ $dirtypatch = 1;
warn "Failed to cvs add $f -- you may need to do it manually";
}
}
@@ -282,35 +215,40 @@ foreach my $f (@afiles) {
foreach my $f (@dfiles) {
system('cvs', 'rm', '-f', $f);
if ($?) {
- $dirty = 1;
+ $dirtypatch = 1;
warn "Failed to cvs rm -f $f -- you may need to do it manually";
}
}
print "Commit to CVS\n";
-print "Patch: $title\n";
-my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);
-my $cmd = "cvs commit -F .msg $commitfiles";
+print "Patch title (first comment line): $title\n";
+my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files);
+my $cmd = "cvs commit -F .msg @commitfiles";
if ($dirtypatch) {
print "NOTE: One or more hunks failed to apply cleanly.\n";
- print "Resolve the conflicts and then commit using:\n";
+ print "You'll need to apply the patch in .cvsexportcommit.diff manually\n";
+ print "using a patch program. After applying the patch and resolving the\n";
+ print "problems you may commit using:";
print "\n $cmd\n\n";
exit(1);
}
-
if ($opt_c) {
print "Autocommit\n $cmd\n";
- print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles);
+ print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files);
if ($?) {
- cleanupcvs(@files);
die "Exiting: The commit did not succeed";
}
print "Committed successfully to CVS\n";
} else {
print "Ready for you to commit, just run:\n\n $cmd\n";
}
+
+# clean up
+unlink(".cvsexportcommit.diff");
+unlink(".msg");
+
sub usage {
print STDERR <<END;
Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
@@ -318,17 +256,6 @@ END
exit(1);
}
-# ensure cvs is clean before we die
-sub cleanupcvs {
- my @files = @_;
- foreach my $f (@files) {
- system('cvs', '-q', 'update', '-C', $f);
- if ($?) {
- warn "Warning! Failed to cleanup state of $f\n";
- }
- }
-}
-
# An alternative to `command` that allows input to be passed as an array
# to work around shell problems with weird characters in arguments
# if the exec returns non-zero we die
@@ -342,3 +269,16 @@ sub safe_pipe_capture {
}
return wantarray ? @output : join('',@output);
}
+
+sub safe_pipe_capture_blob {
+ my $output;
+ if (my $pid = open my $child, '-|') {
+ local $/;
+ undef $/;
+ $output = (<$child>);
+ close $child or die join(' ',@_).": $! $?";
+ } else {
+ exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
+ }
+ return $output;
+}
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 4310dea132..c5bf2d19cd 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -29,7 +29,7 @@ use IPC::Open2;
$SIG{'PIPE'}="IGNORE";
$ENV{'TZ'}="UTC";
-our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L);
+our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L);
my (%conv_author_name, %conv_author_email);
sub usage() {
@@ -90,15 +90,15 @@ usage if $opt_h;
@ARGV <= 1 or usage();
-if($opt_d) {
+if ($opt_d) {
$ENV{"CVSROOT"} = $opt_d;
-} elsif(-f 'CVS/Root') {
+} elsif (-f 'CVS/Root') {
open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
$opt_d = <$f>;
chomp $opt_d;
close $f;
$ENV{"CVSROOT"} = $opt_d;
-} elsif($ENV{"CVSROOT"}) {
+} elsif ($ENV{"CVSROOT"}) {
$opt_d = $ENV{"CVSROOT"};
} else {
die "CVSROOT needs to be set";
@@ -141,7 +141,7 @@ use File::Temp qw(tempfile);
use POSIX qw(strftime dup2);
sub new {
- my($what,$repo,$subdir) = @_;
+ my ($what,$repo,$subdir) = @_;
$what=ref($what) if ref($what);
my $self = {};
@@ -161,38 +161,38 @@ sub new {
sub conn {
my $self = shift;
my $repo = $self->{'fullrep'};
- if($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
- my($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
+ if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+ my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
- my($proxyhost,$proxyport);
- if($param && ($param =~ m/proxy=([^;]+)/)) {
+ my ($proxyhost,$proxyport);
+ if ($param && ($param =~ m/proxy=([^;]+)/)) {
$proxyhost = $1;
# Default proxyport, if not specified, is 8080.
$proxyport = 8080;
- if($ENV{"CVS_PROXY_PORT"}) {
+ if ($ENV{"CVS_PROXY_PORT"}) {
$proxyport = $ENV{"CVS_PROXY_PORT"};
}
- if($param =~ m/proxyport=([^;]+)/){
+ if ($param =~ m/proxyport=([^;]+)/) {
$proxyport = $1;
}
}
$user="anonymous" unless defined $user;
my $rr2 = "-";
- unless($port) {
+ unless ($port) {
$rr2 = ":pserver:$user\@$serv:$repo";
$port=2401;
}
my $rr = ":pserver:$user\@$serv:$port$repo";
- unless($pass) {
+ unless ($pass) {
open(H,$ENV{'HOME'}."/.cvspass") and do {
# :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
- while(<H>) {
+ while (<H>) {
chomp;
s/^\/\d+\s+//;
my ($w,$p) = split(/\s/,$_,2);
- if($w eq $rr or $w eq $rr2) {
+ if ($w eq $rr or $w eq $rr2) {
$pass = $p;
last;
}
@@ -202,7 +202,7 @@ sub conn {
$pass="A" unless $pass;
my ($s, $rep);
- if($proxyhost) {
+ if ($proxyhost) {
# Use a HTTP Proxy. Only works for HTTP proxies that
# don't require user authentication
@@ -218,7 +218,7 @@ sub conn {
$rep = <$s>;
# The answer should look like 'HTTP/1.x 2yy ....'
- if(!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
+ if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
die "Proxy connect: $rep\n";
}
# Skip up to the empty line of the proxy server output
@@ -239,7 +239,7 @@ sub conn {
$rep = <$s>;
- if($rep ne "I LOVE YOU\n") {
+ if ($rep ne "I LOVE YOU\n") {
$rep="<unknown>" unless $rep;
die "AuthReply: $rep\n";
}
@@ -271,7 +271,7 @@ sub conn {
}
}
- unless($pid) {
+ unless ($pid) {
$pr->writer();
$pw->reader();
dup2($pw->fileno(),0);
@@ -294,7 +294,7 @@ sub conn {
$self->{'socketo'}->flush();
chomp(my $rep=$self->readline());
- if($rep !~ s/^Valid-requests\s*//) {
+ if ($rep !~ s/^Valid-requests\s*//) {
$rep="<unknown>" unless $rep;
die "Expected Valid-requests from server, but got: $rep\n";
}
@@ -306,14 +306,14 @@ sub conn {
}
sub readline {
- my($self) = @_;
+ my ($self) = @_;
return $self->{'socketi'}->getline();
}
sub _file {
# Request a file with a given revision.
# Trial and error says this is a good way to do it. :-/
- my($self,$fn,$rev) = @_;
+ my ($self,$fn,$rev) = @_;
$self->{'socketo'}->write("Argument -N\n") or return undef;
$self->{'socketo'}->write("Argument -P\n") or return undef;
# -kk: Linus' version doesn't use it - defaults to off
@@ -335,12 +335,12 @@ sub _file {
sub _line {
# Read a line from the server.
# ... except that 'line' may be an entire file. ;-)
- my($self, $fh) = @_;
+ my ($self, $fh) = @_;
die "Not in lines" unless defined $self->{'lines'};
my $line;
my $res=0;
- while(defined($line = $self->readline())) {
+ while (defined($line = $self->readline())) {
# M U gnupg-cvs-rep/AUTHORS
# Updated gnupg-cvs-rep/
# /daten/src/rsync/gnupg-cvs-rep/AUTHORS
@@ -349,7 +349,7 @@ sub _line {
# 0
# ok
- if($line =~ s/^(?:Created|Updated) //) {
+ if ($line =~ s/^(?:Created|Updated) //) {
$line = $self->readline(); # path
$line = $self->readline(); # Entries line
my $mode = $self->readline(); chomp $mode;
@@ -360,12 +360,12 @@ sub _line {
die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
$line="";
$res = $self->_fetchfile($fh, $cnt);
- } elsif($line =~ s/^ //) {
+ } elsif ($line =~ s/^ //) {
print $fh $line;
$res += length($line);
- } elsif($line =~ /^M\b/) {
+ } elsif ($line =~ /^M\b/) {
# output, do nothing
- } elsif($line =~ /^Mbinary\b/) {
+ } elsif ($line =~ /^Mbinary\b/) {
my $cnt;
die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
chomp $cnt;
@@ -374,12 +374,12 @@ sub _line {
$res += $self->_fetchfile($fh, $cnt);
} else {
chomp $line;
- if($line eq "ok") {
+ if ($line eq "ok") {
# print STDERR "S: ok (".length($res).")\n";
return $res;
- } elsif($line =~ s/^E //) {
+ } elsif ($line =~ s/^E //) {
# print STDERR "S: $line\n";
- } elsif($line =~ /^(Remove-entry|Removed) /i) {
+ } elsif ($line =~ /^(Remove-entry|Removed) /i) {
$line = $self->readline(); # filename
$line = $self->readline(); # OK
chomp $line;
@@ -393,7 +393,7 @@ sub _line {
return undef;
}
sub file {
- my($self,$fn,$rev) = @_;
+ my ($self,$fn,$rev) = @_;
my $res;
my ($fh, $name) = tempfile('gitcvs.XXXXXX',
@@ -417,7 +417,7 @@ sub _fetchfile {
my ($self, $fh, $cnt) = @_;
my $res = 0;
my $bufsize = 1024 * 1024;
- while($cnt) {
+ while ($cnt) {
if ($bufsize > $cnt) {
$bufsize = $cnt;
}
@@ -438,7 +438,7 @@ my $cvs = CVSconn->new($opt_d, $cvs_tree);
sub pdate($) {
- my($d) = @_;
+ my ($d) = @_;
m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?#
or die "Unparseable date: $d\n";
my $y=$1; $y-=1900 if $y>1900;
@@ -446,22 +446,22 @@ sub pdate($) {
}
sub pmode($) {
- my($mode) = @_;
+ my ($mode) = @_;
my $m = 0;
my $mm = 0;
my $um = 0;
for my $x(split(//,$mode)) {
- if($x eq ",") {
+ if ($x eq ",") {
$m |= $mm&$um;
$mm = 0;
$um = 0;
- } elsif($x eq "u") { $um |= 0700;
- } elsif($x eq "g") { $um |= 0070;
- } elsif($x eq "o") { $um |= 0007;
- } elsif($x eq "r") { $mm |= 0444;
- } elsif($x eq "w") { $mm |= 0222;
- } elsif($x eq "x") { $mm |= 0111;
- } elsif($x eq "=") { # do nothing
+ } elsif ($x eq "u") { $um |= 0700;
+ } elsif ($x eq "g") { $um |= 0070;
+ } elsif ($x eq "o") { $um |= 0007;
+ } elsif ($x eq "r") { $mm |= 0444;
+ } elsif ($x eq "w") { $mm |= 0222;
+ } elsif ($x eq "x") { $mm |= 0111;
+ } elsif ($x eq "=") { # do nothing
} else { die "Unknown mode: $mode\n";
}
}
@@ -485,7 +485,7 @@ sub get_headref ($$) {
my $git_dir = shift;
my $f = "$git_dir/refs/heads/$name";
- if(open(my $fh, $f)) {
+ if (open(my $fh, $f)) {
chomp(my $r = <$fh>);
is_sha1($r) or die "Cannot get head id for $name ($r): $!";
return $r;
@@ -512,7 +512,7 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
my %index; # holds filenames of one index per branch
-unless(-d $git_dir) {
+unless (-d $git_dir) {
system("git-init-db");
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
system("git-read-tree");
@@ -531,7 +531,7 @@ unless(-d $git_dir) {
chomp ($last_branch = <F>);
$last_branch = basename($last_branch);
close(F);
- unless($last_branch) {
+ unless ($last_branch) {
warn "Cannot read the last branch name: $! -- assuming 'master'\n";
$last_branch = "master";
}
@@ -542,7 +542,7 @@ unless(-d $git_dir) {
my $fmt = '($ref, $author) = (%(refname), %(author));';
open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or
die "Cannot run git-for-each-ref: $!\n";
- while(defined(my $entry = <H>)) {
+ while (defined(my $entry = <H>)) {
my ($ref, $author);
eval($entry) || die "cannot eval refs list: $@";
my ($head) = ($ref =~ m|^refs/heads/(.*)|);
@@ -572,7 +572,7 @@ unless ($opt_P) {
print "Running cvsps...\n" if $opt_v;
my $pid = open(CVSPS,"-|");
die "Cannot fork: $!\n" unless defined $pid;
- unless($pid) {
+ unless ($pid) {
my @opt;
@opt = split(/,/,$opt_p) if defined $opt_p;
unshift @opt, '-z', $opt_z if defined $opt_z;
@@ -642,8 +642,8 @@ sub write_tree () {
return $tree;
}
-my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
-my(@old,@new,@skipped,%ignorebranch);
+my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
+my (@old,@new,@skipped,%ignorebranch);
# commits that cvsps cannot place anywhere...
$ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
@@ -684,7 +684,7 @@ sub commit {
foreach my $rx (@mergerx) {
next unless $logmsg =~ $rx && $1;
my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
- if(my $sha1 = get_headref($mparent, $git_dir)) {
+ if (my $sha1 = get_headref($mparent, $git_dir)) {
push @commit_args, '-p', $mparent;
print "Merge parent branch: $mparent\n" if $opt_v;
}
@@ -725,9 +725,9 @@ sub commit {
system("git-update-ref refs/heads/$branch $cid") == 0
or die "Cannot write branch $branch for update: $!\n";
- if($tag) {
- my($in, $out) = ('','');
- my($xtag) = $tag;
+ if ($tag) {
+ my ($in, $out) = ('','');
+ my ($xtag) = $tag;
$xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
$xtag =~ tr/_/\./ if ( $opt_u );
$xtag =~ s/[\/]/$opt_s/g;
@@ -762,25 +762,25 @@ sub commit {
};
my $commitcount = 1;
-while(<CVS>) {
+while (<CVS>) {
chomp;
- if($state == 0 and /^-+$/) {
+ if ($state == 0 and /^-+$/) {
$state = 1;
- } elsif($state == 0) {
+ } elsif ($state == 0) {
$state = 1;
redo;
- } elsif(($state==0 or $state==1) and s/^PatchSet\s+//) {
+ } elsif (($state==0 or $state==1) and s/^PatchSet\s+//) {
$patchset = 0+$_;
$state=2;
- } elsif($state == 2 and s/^Date:\s+//) {
+ } elsif ($state == 2 and s/^Date:\s+//) {
$date = pdate($_);
- unless($date) {
+ unless ($date) {
print STDERR "Could not parse date: $_\n";
$state=0;
next;
}
$state=3;
- } elsif($state == 3 and s/^Author:\s+//) {
+ } elsif ($state == 3 and s/^Author:\s+//) {
s/\s+$//;
if (/^(.*?)\s+<(.*)>/) {
($author_name, $author_email) = ($1, $2);
@@ -791,34 +791,34 @@ while(<CVS>) {
$author_name = $author_email = $_;
}
$state = 4;
- } elsif($state == 4 and s/^Branch:\s+//) {
+ } elsif ($state == 4 and s/^Branch:\s+//) {
s/\s+$//;
s/[\/]/$opt_s/g;
$branch = $_;
$state = 5;
- } elsif($state == 5 and s/^Ancestor branch:\s+//) {
+ } elsif ($state == 5 and s/^Ancestor branch:\s+//) {
s/\s+$//;
$ancestor = $_;
$ancestor = $opt_o if $ancestor eq "HEAD";
$state = 6;
- } elsif($state == 5) {
+ } elsif ($state == 5) {
$ancestor = undef;
$state = 6;
redo;
- } elsif($state == 6 and s/^Tag:\s+//) {
+ } elsif ($state == 6 and s/^Tag:\s+//) {
s/\s+$//;
- if($_ eq "(none)") {
+ if ($_ eq "(none)") {
$tag = undef;
} else {
$tag = $_;
}
$state = 7;
- } elsif($state == 7 and /^Log:/) {
+ } elsif ($state == 7 and /^Log:/) {
$logmsg = "";
$state = 8;
- } elsif($state == 8 and /^Members:/) {
+ } elsif ($state == 8 and /^Members:/) {
$branch = $opt_o if $branch eq "HEAD";
- if(defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
+ if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
# skip
print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
$state = 11;
@@ -829,17 +829,17 @@ while(<CVS>) {
$state = 11;
next;
}
- if($ancestor) {
- if($ancestor eq $branch) {
+ if ($ancestor) {
+ if ($ancestor eq $branch) {
print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
$ancestor = $opt_o;
}
- if(-f "$git_dir/refs/heads/$branch") {
+ if (-f "$git_dir/refs/heads/$branch") {
print STDERR "Branch $branch already exists!\n";
$state=11;
next;
}
- unless(open(H,"$git_dir/refs/heads/$ancestor")) {
+ unless (open(H,"$git_dir/refs/heads/$ancestor")) {
print STDERR "Branch $ancestor does not exist!\n";
$ignorebranch{$branch} = 1;
$state=11;
@@ -847,7 +847,7 @@ while(<CVS>) {
}
chomp(my $id = <H>);
close(H);
- unless(open(H,"> $git_dir/refs/heads/$branch")) {
+ unless (open(H,"> $git_dir/refs/heads/$branch")) {
print STDERR "Could not create branch $branch: $!\n";
$ignorebranch{$branch} = 1;
$state=11;
@@ -860,9 +860,9 @@ while(<CVS>) {
}
$last_branch = $branch if $branch ne $last_branch;
$state = 9;
- } elsif($state == 8) {
+ } elsif ($state == 8) {
$logmsg .= "$_\n";
- } elsif($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
+ } elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
# VERSION:1.96->1.96.2.1
my $init = ($2 eq "INITIAL");
my $fn = $1;
@@ -875,7 +875,7 @@ while(<CVS>) {
}
print "Fetching $fn v $rev\n" if $opt_v;
my ($tmpname, $size) = $cvs->file($fn,$rev);
- if($size == -1) {
+ if ($size == -1) {
push(@old,$fn);
print "Drop $fn\n" if $opt_v;
} else {
@@ -893,14 +893,14 @@ while(<CVS>) {
push(@new,[$mode, $sha, $fn]); # may be resurrected!
}
unlink($tmpname);
- } elsif($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
+ } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
my $fn = $1;
$fn =~ s#^/+##;
push(@old,$fn);
print "Delete $fn\n" if $opt_v;
- } elsif($state == 9 and /^\s*$/) {
+ } elsif ($state == 9 and /^\s*$/) {
$state = 10;
- } elsif(($state == 9 or $state == 10) and /^-+$/) {
+ } elsif (($state == 9 or $state == 10) and /^-+$/) {
$commitcount++;
if ($opt_L && $commitcount > $opt_L) {
last;
@@ -910,11 +910,11 @@ while(<CVS>) {
system("git repack -a -d");
}
$state = 1;
- } elsif($state == 11 and /^-+$/) {
+ } elsif ($state == 11 and /^-+$/) {
$state = 1;
- } elsif(/^-+$/) { # end of unknown-line processing
+ } elsif (/^-+$/) { # end of unknown-line processing
$state = 1;
- } elsif($state != 11) { # ignore stuff when skipping
+ } elsif ($state != 11) { # ignore stuff when skipping
print "* UNKNOWN LINE * $_\n";
}
}
@@ -943,7 +943,7 @@ if (defined $orig_git_index) {
}
# Now switch back to the branch we were in before all of this happened
-if($orig_branch) {
+if ($orig_branch) {
print "DONE.\n" if $opt_v;
if ($opt_i) {
exit 0;
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index ca519b7e49..2a8447e253 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -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 )
diff --git a/git-fetch.sh b/git-fetch.sh
index eb32476bbd..4eecf148ea 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -88,6 +88,10 @@ then
: >"$GIT_DIR/FETCH_HEAD"
fi
+# Global that is reused later
+ls_remote_result=$(git ls-remote $upload_pack "$remote") ||
+ die "Cannot find the reflist at $remote"
+
append_fetch_head () {
head_="$1"
remote_="$2"
@@ -233,10 +237,7 @@ reflist=$(get_remote_refs_for_fetch "$@")
if test "$tags"
then
taglist=`IFS=" " &&
- (
- git-ls-remote $upload_pack --tags "$remote" ||
- echo fail ouch
- ) |
+ echo "$ls_remote_result" |
while read sha1 name
do
case "$sha1" in
@@ -245,6 +246,8 @@ then
esac
case "$name" in
*^*) continue ;;
+ refs/tags/*) ;;
+ *) continue ;;
esac
if git-check-ref-format "$name"
then
@@ -304,22 +307,20 @@ fetch_main () {
"`git-repo-config --bool http.noEPSV`" = true ]; then
noepsv_opt="--disable-epsv"
fi
- max_depth=5
- depth=0
- head="ref: $remote_name"
- while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
- do
- remote_name_quoted=$(@@PERL@@ -e '
- my $u = $ARGV[0];
- $u =~ s/^ref:\s*//;
- $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
- print "$u";
- ' "$head")
- head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted")
- depth=$( expr \( $depth + 1 \) )
- done
+
+ # Find $remote_name from ls-remote output.
+ head=$(
+ IFS=' '
+ echo "$ls_remote_result" |
+ while read sha1 name
+ do
+ test "z$name" = "z$remote_name" || continue
+ echo "$sha1"
+ break
+ done
+ )
expr "z$head" : "z$_x40\$" >/dev/null ||
- die "Failed to fetch $remote_name from $remote"
+ die "No such ref $remote_name at $remote"
echo >&2 "Fetching $remote_name from $remote using $proto"
git-http-fetch -v -a "$head" "$remote/" || exit
;;
@@ -359,7 +360,7 @@ fetch_main () {
esac
append_fetch_head "$head" "$remote" \
- "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+ "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit
done
@@ -413,15 +414,16 @@ fetch_main () {
done
local_name=$(expr "z$found" : 'z[^:]*:\(.*\)')
append_fetch_head "$sha1" "$remote" \
- "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
- done
+ "$remote_name" "$remote_nick" "$local_name" \
+ "$not_for_merge" || exit
+ done &&
if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi
) || exit ;;
esac
}
-fetch_main "$reflist"
+fetch_main "$reflist" || exit
# automated tag following
case "$no_tags$tags" in
@@ -431,7 +433,7 @@ case "$no_tags$tags" in
# effective only when we are following remote branch
# using local tracking branch.
taglist=$(IFS=" " &&
- git-ls-remote $upload_pack --tags "$remote" |
+ echo "$ls_remote_result" |
sed -n -e 's|^\('"$_x40"'\) \(refs/tags/.*\)^{}$|\1 \2|p' \
-e 's|^\('"$_x40"'\) \(refs/tags/.*\)$|\1 \2|p' |
while read sha1 name
@@ -449,7 +451,7 @@ case "$no_tags$tags" in
case "$taglist" in
'') ;;
?*)
- fetch_main "$taglist" ;;
+ fetch_main "$taglist" || exit ;;
esac
esac
diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py
deleted file mode 100755
index 4039435ce4..0000000000
--- a/git-merge-recursive-old.py
+++ /dev/null
@@ -1,944 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright (C) 2005 Fredrik Kuivinen
-#
-
-import sys
-sys.path.append('''@@GIT_PYTHON_PATH@@''')
-
-import math, random, os, re, signal, tempfile, stat, errno, traceback
-from heapq import heappush, heappop
-from sets import Set
-
-from gitMergeCommon import *
-
-outputIndent = 0
-def output(*args):
- sys.stdout.write(' '*outputIndent)
- printList(args)
-
-originalIndexFile = os.environ.get('GIT_INDEX_FILE',
- os.environ.get('GIT_DIR', '.git') + '/index')
-temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
- '/merge-recursive-tmp-index'
-def setupIndex(temporary):
- try:
- os.unlink(temporaryIndexFile)
- except OSError:
- pass
- if temporary:
- newIndex = temporaryIndexFile
- else:
- newIndex = originalIndexFile
- os.environ['GIT_INDEX_FILE'] = newIndex
-
-# This is a global variable which is used in a number of places but
-# only written to in the 'merge' function.
-
-# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and
-# don't update the working directory.
-# False => Leave unmerged entries in the cache and update
-# the working directory.
-
-cacheOnly = False
-
-# The entry point to the merge code
-# ---------------------------------
-
-def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None):
- '''Merge the commits h1 and h2, return the resulting virtual
- commit object and a flag indicating the cleanness of the merge.'''
- assert(isinstance(h1, Commit) and isinstance(h2, Commit))
-
- global outputIndent
-
- output('Merging:')
- output(h1)
- output(h2)
- sys.stdout.flush()
-
- if ancestor:
- ca = [ancestor]
- else:
- assert(isinstance(graph, Graph))
- ca = getCommonAncestors(graph, h1, h2)
- output('found', len(ca), 'common ancestor(s):')
- for x in ca:
- output(x)
- sys.stdout.flush()
-
- mergedCA = ca[0]
- for h in ca[1:]:
- outputIndent = callDepth+1
- [mergedCA, dummy] = merge(mergedCA, h,
- 'Temporary merge branch 1',
- 'Temporary merge branch 2',
- graph, callDepth+1)
- outputIndent = callDepth
- assert(isinstance(mergedCA, Commit))
-
- global cacheOnly
- if callDepth == 0:
- setupIndex(False)
- cacheOnly = False
- else:
- setupIndex(True)
- runProgram(['git-read-tree', h1.tree()])
- cacheOnly = True
-
- [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
- branch1Name, branch2Name)
-
- if graph and (clean or cacheOnly):
- res = Commit(None, [h1, h2], tree=shaRes)
- graph.addNode(res)
- else:
- res = None
-
- return [res, clean]
-
-getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
-def getFilesAndDirs(tree):
- files = Set()
- dirs = Set()
- out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree])
- for l in out.split('\0'):
- m = getFilesRE.match(l)
- if m:
- if m.group(2) == 'tree':
- dirs.add(m.group(4))
- elif m.group(2) == 'blob':
- files.add(m.group(4))
-
- return [files, dirs]
-
-# Those two global variables are used in a number of places but only
-# written to in 'mergeTrees' and 'uniquePath'. They keep track of
-# every file and directory in the two branches that are about to be
-# merged.
-currentFileSet = None
-currentDirectorySet = None
-
-def mergeTrees(head, merge, common, branch1Name, branch2Name):
- '''Merge the trees 'head' and 'merge' with the common ancestor
- 'common'. The name of the head branch is 'branch1Name' and the name of
- the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
- where tree is the resulting tree and cleanMerge is True iff the
- merge was clean.'''
-
- assert(isSha(head) and isSha(merge) and isSha(common))
-
- if common == merge:
- output('Already uptodate!')
- return [head, True]
-
- if cacheOnly:
- updateArg = '-i'
- else:
- updateArg = '-u'
-
- [out, code] = runProgram(['git-read-tree', updateArg, '-m',
- common, head, merge], returnCode = True)
- if code != 0:
- die('git-read-tree:', out)
-
- [tree, code] = runProgram('git-write-tree', returnCode=True)
- tree = tree.rstrip()
- if code != 0:
- global currentFileSet, currentDirectorySet
- [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
- [filesM, dirsM] = getFilesAndDirs(merge)
- currentFileSet.union_update(filesM)
- currentDirectorySet.union_update(dirsM)
-
- entries = unmergedCacheEntries()
- renamesHead = getRenames(head, common, head, merge, entries)
- renamesMerge = getRenames(merge, common, head, merge, entries)
-
- cleanMerge = processRenames(renamesHead, renamesMerge,
- branch1Name, branch2Name)
- for entry in entries:
- if entry.processed:
- continue
- if not processEntry(entry, branch1Name, branch2Name):
- cleanMerge = False
-
- if cleanMerge or cacheOnly:
- tree = runProgram('git-write-tree').rstrip()
- else:
- tree = None
- else:
- cleanMerge = True
-
- return [tree, cleanMerge]
-
-# Low level file merging, update and removal
-# ------------------------------------------
-
-def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
- branch1Name, branch2Name):
-
- merge = False
- clean = True
-
- if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
- clean = False
- if stat.S_ISREG(aMode):
- mode = aMode
- sha = aSha
- else:
- mode = bMode
- sha = bSha
- else:
- if aSha != oSha and bSha != oSha:
- merge = True
-
- if aMode == oMode:
- mode = bMode
- else:
- mode = aMode
-
- if aSha == oSha:
- sha = bSha
- elif bSha == oSha:
- sha = aSha
- elif stat.S_ISREG(aMode):
- assert(stat.S_ISREG(bMode))
-
- orig = runProgram(['git-unpack-file', oSha]).rstrip()
- src1 = runProgram(['git-unpack-file', aSha]).rstrip()
- src2 = runProgram(['git-unpack-file', bSha]).rstrip()
- try:
- [out, code] = runProgram(['merge',
- '-L', branch1Name + '/' + aPath,
- '-L', 'orig/' + oPath,
- '-L', branch2Name + '/' + bPath,
- src1, orig, src2], returnCode=True)
- except ProgramError, e:
- print >>sys.stderr, e
- die("Failed to execute 'merge'. merge(1) is used as the "
- "file-level merge tool. Is 'merge' in your path?")
-
- sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
- src1]).rstrip()
-
- os.unlink(orig)
- os.unlink(src1)
- os.unlink(src2)
-
- clean = (code == 0)
- else:
- assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
- sha = aSha
-
- if aSha != bSha:
- clean = False
-
- return [sha, mode, clean, merge]
-
-def updateFile(clean, sha, mode, path):
- updateCache = cacheOnly or clean
- updateWd = not cacheOnly
-
- return updateFileExt(sha, mode, path, updateCache, updateWd)
-
-def updateFileExt(sha, mode, path, updateCache, updateWd):
- if cacheOnly:
- updateWd = False
-
- if updateWd:
- pathComponents = path.split('/')
- for x in xrange(1, len(pathComponents)):
- p = '/'.join(pathComponents[0:x])
-
- try:
- createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
- except OSError:
- createDir = True
-
- if createDir:
- try:
- os.mkdir(p)
- except OSError, e:
- die("Couldn't create directory", p, e.strerror)
-
- prog = ['git-cat-file', 'blob', sha]
- if stat.S_ISREG(mode):
- try:
- os.unlink(path)
- except OSError:
- pass
- if mode & 0100:
- mode = 0777
- else:
- mode = 0666
- fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
- proc = subprocess.Popen(prog, stdout=fd)
- proc.wait()
- os.close(fd)
- elif stat.S_ISLNK(mode):
- linkTarget = runProgram(prog)
- os.symlink(linkTarget, path)
- else:
- assert(False)
-
- if updateWd and updateCache:
- runProgram(['git-update-index', '--add', '--', path])
- elif updateCache:
- runProgram(['git-update-index', '--add', '--cacheinfo',
- '0%o' % mode, sha, path])
-
-def setIndexStages(path,
- oSHA1, oMode,
- aSHA1, aMode,
- bSHA1, bMode,
- clear=True):
- istring = []
- if clear:
- istring.append("0 " + ("0" * 40) + "\t" + path + "\0")
- if oMode:
- istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path))
- if aMode:
- istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path))
- if bMode:
- istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path))
-
- runProgram(['git-update-index', '-z', '--index-info'],
- input="".join(istring))
-
-def removeFile(clean, path):
- updateCache = cacheOnly or clean
- updateWd = not cacheOnly
-
- if updateCache:
- runProgram(['git-update-index', '--force-remove', '--', path])
-
- if updateWd:
- try:
- os.unlink(path)
- except OSError, e:
- if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
- raise
- try:
- os.removedirs(os.path.dirname(path))
- except OSError:
- pass
-
-def uniquePath(path, branch):
- def fileExists(path):
- try:
- os.lstat(path)
- return True
- except OSError, e:
- if e.errno == errno.ENOENT:
- return False
- else:
- raise
-
- branch = branch.replace('/', '_')
- newPath = path + '~' + branch
- suffix = 0
- while newPath in currentFileSet or \
- newPath in currentDirectorySet or \
- fileExists(newPath):
- suffix += 1
- newPath = path + '~' + branch + '_' + str(suffix)
- currentFileSet.add(newPath)
- return newPath
-
-# Cache entry management
-# ----------------------
-
-class CacheEntry:
- def __init__(self, path):
- class Stage:
- def __init__(self):
- self.sha1 = None
- self.mode = None
-
- # Used for debugging only
- def __str__(self):
- if self.mode != None:
- m = '0%o' % self.mode
- else:
- m = 'None'
-
- if self.sha1:
- sha1 = self.sha1
- else:
- sha1 = 'None'
- return 'sha1: ' + sha1 + ' mode: ' + m
-
- self.stages = [Stage(), Stage(), Stage(), Stage()]
- self.path = path
- self.processed = False
-
- def __str__(self):
- return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
-
-class CacheEntryContainer:
- def __init__(self):
- self.entries = {}
-
- def add(self, entry):
- self.entries[entry.path] = entry
-
- def get(self, path):
- return self.entries.get(path)
-
- def __iter__(self):
- return self.entries.itervalues()
-
-unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
-def unmergedCacheEntries():
- '''Create a dictionary mapping file names to CacheEntry
- objects. The dictionary contains one entry for every path with a
- non-zero stage entry.'''
-
- lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
- lines.pop()
-
- res = CacheEntryContainer()
- for l in lines:
- m = unmergedRE.match(l)
- if m:
- mode = int(m.group(1), 8)
- sha1 = m.group(2)
- stage = int(m.group(3))
- path = m.group(4)
-
- e = res.get(path)
- if not e:
- e = CacheEntry(path)
- res.add(e)
-
- e.stages[stage].mode = mode
- e.stages[stage].sha1 = sha1
- else:
- die('Error: Merge program failed: Unexpected output from',
- 'git-ls-files:', l)
- return res
-
-lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
-def getCacheEntry(path, origTree, aTree, bTree):
- '''Returns a CacheEntry object which doesn't have to correspond to
- a real cache entry in Git's index.'''
-
- def parse(out):
- if out == '':
- return [None, None]
- else:
- m = lsTreeRE.match(out)
- if not m:
- die('Unexpected output from git-ls-tree:', out)
- elif m.group(2) == 'blob':
- return [m.group(3), int(m.group(1), 8)]
- else:
- return [None, None]
-
- res = CacheEntry(path)
-
- [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
- [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
- [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
-
- res.stages[1].sha1 = oSha
- res.stages[1].mode = oMode
- res.stages[2].sha1 = aSha
- res.stages[2].mode = aMode
- res.stages[3].sha1 = bSha
- res.stages[3].mode = bMode
-
- return res
-
-# Rename detection and handling
-# -----------------------------
-
-class RenameEntry:
- def __init__(self,
- src, srcSha, srcMode, srcCacheEntry,
- dst, dstSha, dstMode, dstCacheEntry,
- score):
- self.srcName = src
- self.srcSha = srcSha
- self.srcMode = srcMode
- self.srcCacheEntry = srcCacheEntry
- self.dstName = dst
- self.dstSha = dstSha
- self.dstMode = dstMode
- self.dstCacheEntry = dstCacheEntry
- self.score = score
-
- self.processed = False
-
-class RenameEntryContainer:
- def __init__(self):
- self.entriesSrc = {}
- self.entriesDst = {}
-
- def add(self, entry):
- self.entriesSrc[entry.srcName] = entry
- self.entriesDst[entry.dstName] = entry
-
- def getSrc(self, path):
- return self.entriesSrc.get(path)
-
- def getDst(self, path):
- return self.entriesDst.get(path)
-
- def __iter__(self):
- return self.entriesSrc.itervalues()
-
-parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
-def getRenames(tree, oTree, aTree, bTree, cacheEntries):
- '''Get information of all renames which occured between 'oTree' and
- 'tree'. We need the three trees in the merge ('oTree', 'aTree' and
- 'bTree') to be able to associate the correct cache entries with
- the rename information. 'tree' is always equal to either aTree or bTree.'''
-
- assert(tree == aTree or tree == bTree)
- inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
- '-z', oTree, tree])
-
- ret = RenameEntryContainer()
- try:
- recs = inp.split("\0")
- recs.pop() # remove last entry (which is '')
- it = recs.__iter__()
- while True:
- rec = it.next()
- m = parseDiffRenamesRE.match(rec)
-
- if not m:
- die('Unexpected output from git-diff-tree:', rec)
-
- srcMode = int(m.group(1), 8)
- dstMode = int(m.group(2), 8)
- srcSha = m.group(3)
- dstSha = m.group(4)
- score = m.group(5)
- src = it.next()
- dst = it.next()
-
- srcCacheEntry = cacheEntries.get(src)
- if not srcCacheEntry:
- srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
- cacheEntries.add(srcCacheEntry)
-
- dstCacheEntry = cacheEntries.get(dst)
- if not dstCacheEntry:
- dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
- cacheEntries.add(dstCacheEntry)
-
- ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
- dst, dstSha, dstMode, dstCacheEntry,
- score))
- except StopIteration:
- pass
- return ret
-
-def fmtRename(src, dst):
- srcPath = src.split('/')
- dstPath = dst.split('/')
- path = []
- endIndex = min(len(srcPath), len(dstPath)) - 1
- for x in range(0, endIndex):
- if srcPath[x] == dstPath[x]:
- path.append(srcPath[x])
- else:
- endIndex = x
- break
-
- if len(path) > 0:
- return '/'.join(path) + \
- '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
- '/'.join(dstPath[endIndex:]) + '}'
- else:
- return src + ' => ' + dst
-
-def processRenames(renamesA, renamesB, branchNameA, branchNameB):
- srcNames = Set()
- for x in renamesA:
- srcNames.add(x.srcName)
- for x in renamesB:
- srcNames.add(x.srcName)
-
- cleanMerge = True
- for path in srcNames:
- if renamesA.getSrc(path):
- renames1 = renamesA
- renames2 = renamesB
- branchName1 = branchNameA
- branchName2 = branchNameB
- else:
- renames1 = renamesB
- renames2 = renamesA
- branchName1 = branchNameB
- branchName2 = branchNameA
-
- ren1 = renames1.getSrc(path)
- ren2 = renames2.getSrc(path)
-
- ren1.dstCacheEntry.processed = True
- ren1.srcCacheEntry.processed = True
-
- if ren1.processed:
- continue
-
- ren1.processed = True
-
- if ren2:
- # Renamed in 1 and renamed in 2
- assert(ren1.srcName == ren2.srcName)
- ren2.dstCacheEntry.processed = True
- ren2.processed = True
-
- if ren1.dstName != ren2.dstName:
- output('CONFLICT (rename/rename): Rename',
- fmtRename(path, ren1.dstName), 'in branch', branchName1,
- 'rename', fmtRename(path, ren2.dstName), 'in',
- branchName2)
- cleanMerge = False
-
- if ren1.dstName in currentDirectorySet:
- dstName1 = uniquePath(ren1.dstName, branchName1)
- output(ren1.dstName, 'is a directory in', branchName2,
- 'adding as', dstName1, 'instead.')
- removeFile(False, ren1.dstName)
- else:
- dstName1 = ren1.dstName
-
- if ren2.dstName in currentDirectorySet:
- dstName2 = uniquePath(ren2.dstName, branchName2)
- output(ren2.dstName, 'is a directory in', branchName1,
- 'adding as', dstName2, 'instead.')
- removeFile(False, ren2.dstName)
- else:
- dstName2 = ren2.dstName
- setIndexStages(dstName1,
- None, None,
- ren1.dstSha, ren1.dstMode,
- None, None)
- setIndexStages(dstName2,
- None, None,
- None, None,
- ren2.dstSha, ren2.dstMode)
-
- else:
- removeFile(True, ren1.srcName)
-
- [resSha, resMode, clean, merge] = \
- mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
- ren1.dstName, ren1.dstSha, ren1.dstMode,
- ren2.dstName, ren2.dstSha, ren2.dstMode,
- branchName1, branchName2)
-
- if merge or not clean:
- output('Renaming', fmtRename(path, ren1.dstName))
-
- if merge:
- output('Auto-merging', ren1.dstName)
-
- if not clean:
- output('CONFLICT (content): merge conflict in',
- ren1.dstName)
- cleanMerge = False
-
- if not cacheOnly:
- setIndexStages(ren1.dstName,
- ren1.srcSha, ren1.srcMode,
- ren1.dstSha, ren1.dstMode,
- ren2.dstSha, ren2.dstMode)
-
- updateFile(clean, resSha, resMode, ren1.dstName)
- else:
- removeFile(True, ren1.srcName)
-
- # Renamed in 1, maybe changed in 2
- if renamesA == renames1:
- stage = 3
- else:
- stage = 2
-
- srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1
- srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
-
- dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1
- dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
-
- tryMerge = False
-
- if ren1.dstName in currentDirectorySet:
- newPath = uniquePath(ren1.dstName, branchName1)
- output('CONFLICT (rename/directory): Rename',
- fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
- 'directory', ren1.dstName, 'added in', branchName2)
- output('Renaming', ren1.srcName, 'to', newPath, 'instead')
- cleanMerge = False
- removeFile(False, ren1.dstName)
- updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
- elif srcShaOtherBranch == None:
- output('CONFLICT (rename/delete): Rename',
- fmtRename(ren1.srcName, ren1.dstName), 'in',
- branchName1, 'and deleted in', branchName2)
- cleanMerge = False
- updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
- elif dstShaOtherBranch:
- newPath = uniquePath(ren1.dstName, branchName2)
- output('CONFLICT (rename/add): Rename',
- fmtRename(ren1.srcName, ren1.dstName), 'in',
- branchName1 + '.', ren1.dstName, 'added in', branchName2)
- output('Adding as', newPath, 'instead')
- updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
- cleanMerge = False
- tryMerge = True
- elif renames2.getDst(ren1.dstName):
- dst2 = renames2.getDst(ren1.dstName)
- newPath1 = uniquePath(ren1.dstName, branchName1)
- newPath2 = uniquePath(dst2.dstName, branchName2)
- output('CONFLICT (rename/rename): Rename',
- fmtRename(ren1.srcName, ren1.dstName), 'in',
- branchName1+'. Rename',
- fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
- output('Renaming', ren1.srcName, 'to', newPath1, 'and',
- dst2.srcName, 'to', newPath2, 'instead')
- removeFile(False, ren1.dstName)
- updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
- updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
- dst2.processed = True
- cleanMerge = False
- else:
- tryMerge = True
-
- if tryMerge:
-
- oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode
- aName, bName = ren1.dstName, ren1.srcName
- aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch
- aMode, bMode = ren1.dstMode, srcModeOtherBranch
- aBranch, bBranch = branchName1, branchName2
-
- if renamesA != renames1:
- aName, bName = bName, aName
- aSHA1, bSHA1 = bSHA1, aSHA1
- aMode, bMode = bMode, aMode
- aBranch, bBranch = bBranch, aBranch
-
- [resSha, resMode, clean, merge] = \
- mergeFile(oName, oSHA1, oMode,
- aName, aSHA1, aMode,
- bName, bSHA1, bMode,
- aBranch, bBranch);
-
- if merge or not clean:
- output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
-
- if merge:
- output('Auto-merging', ren1.dstName)
-
- if not clean:
- output('CONFLICT (rename/modify): Merge conflict in',
- ren1.dstName)
- cleanMerge = False
-
- if not cacheOnly:
- setIndexStages(ren1.dstName,
- oSHA1, oMode,
- aSHA1, aMode,
- bSHA1, bMode)
-
- updateFile(clean, resSha, resMode, ren1.dstName)
-
- return cleanMerge
-
-# Per entry merge function
-# ------------------------
-
-def processEntry(entry, branch1Name, branch2Name):
- '''Merge one cache entry.'''
-
- debug('processing', entry.path, 'clean cache:', cacheOnly)
-
- cleanMerge = True
-
- path = entry.path
- oSha = entry.stages[1].sha1
- oMode = entry.stages[1].mode
- aSha = entry.stages[2].sha1
- aMode = entry.stages[2].mode
- bSha = entry.stages[3].sha1
- bMode = entry.stages[3].mode
-
- assert(oSha == None or isSha(oSha))
- assert(aSha == None or isSha(aSha))
- assert(bSha == None or isSha(bSha))
-
- assert(oMode == None or type(oMode) is int)
- assert(aMode == None or type(aMode) is int)
- assert(bMode == None or type(bMode) is int)
-
- if (oSha and (not aSha or not bSha)):
- #
- # Case A: Deleted in one
- #
- if (not aSha and not bSha) or \
- (aSha == oSha and not bSha) or \
- (not aSha and bSha == oSha):
- # Deleted in both or deleted in one and unchanged in the other
- if aSha:
- output('Removing', path)
- removeFile(True, path)
- else:
- # Deleted in one and changed in the other
- cleanMerge = False
- if not aSha:
- output('CONFLICT (delete/modify):', path, 'deleted in',
- branch1Name, 'and modified in', branch2Name + '.',
- 'Version', branch2Name, 'of', path, 'left in tree.')
- mode = bMode
- sha = bSha
- else:
- output('CONFLICT (modify/delete):', path, 'deleted in',
- branch2Name, 'and modified in', branch1Name + '.',
- 'Version', branch1Name, 'of', path, 'left in tree.')
- mode = aMode
- sha = aSha
-
- updateFile(False, sha, mode, path)
-
- elif (not oSha and aSha and not bSha) or \
- (not oSha and not aSha and bSha):
- #
- # Case B: Added in one.
- #
- if aSha:
- addBranch = branch1Name
- otherBranch = branch2Name
- mode = aMode
- sha = aSha
- conf = 'file/directory'
- else:
- addBranch = branch2Name
- otherBranch = branch1Name
- mode = bMode
- sha = bSha
- conf = 'directory/file'
-
- if path in currentDirectorySet:
- cleanMerge = False
- newPath = uniquePath(path, addBranch)
- output('CONFLICT (' + conf + '):',
- 'There is a directory with name', path, 'in',
- otherBranch + '. Adding', path, 'as', newPath)
-
- removeFile(False, path)
- updateFile(False, sha, mode, newPath)
- else:
- output('Adding', path)
- updateFile(True, sha, mode, path)
-
- elif not oSha and aSha and bSha:
- #
- # Case C: Added in both (check for same permissions).
- #
- if aSha == bSha:
- if aMode != bMode:
- cleanMerge = False
- output('CONFLICT: File', path,
- 'added identically in both branches, but permissions',
- 'conflict', '0%o' % aMode, '->', '0%o' % bMode)
- output('CONFLICT: adding with permission:', '0%o' % aMode)
-
- updateFile(False, aSha, aMode, path)
- else:
- # This case is handled by git-read-tree
- assert(False)
- else:
- cleanMerge = False
- newPath1 = uniquePath(path, branch1Name)
- newPath2 = uniquePath(path, branch2Name)
- output('CONFLICT (add/add): File', path,
- 'added non-identically in both branches. Adding as',
- newPath1, 'and', newPath2, 'instead.')
- removeFile(False, path)
- updateFile(False, aSha, aMode, newPath1)
- updateFile(False, bSha, bMode, newPath2)
-
- elif oSha and aSha and bSha:
- #
- # case D: Modified in both, but differently.
- #
- output('Auto-merging', path)
- [sha, mode, clean, dummy] = \
- mergeFile(path, oSha, oMode,
- path, aSha, aMode,
- path, bSha, bMode,
- branch1Name, branch2Name)
- if clean:
- updateFile(True, sha, mode, path)
- else:
- cleanMerge = False
- output('CONFLICT (content): Merge conflict in', path)
-
- if cacheOnly:
- updateFile(False, sha, mode, path)
- else:
- updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
- else:
- die("ERROR: Fatal merge failure, shouldn't happen.")
-
- return cleanMerge
-
-def usage():
- die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
-
-# main entry point as merge strategy module
-# The first parameters up to -- are merge bases, and the rest are heads.
-
-if len(sys.argv) < 4:
- usage()
-
-bases = []
-for nextArg in xrange(1, len(sys.argv)):
- if sys.argv[nextArg] == '--':
- if len(sys.argv) != nextArg + 3:
- die('Not handling anything other than two heads merge.')
- try:
- h1 = firstBranch = sys.argv[nextArg + 1]
- h2 = secondBranch = sys.argv[nextArg + 2]
- except IndexError:
- usage()
- break
- else:
- bases.append(sys.argv[nextArg])
-
-print 'Merging', h1, 'with', h2
-
-try:
- h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
- h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
-
- if len(bases) == 1:
- base = runProgram(['git-rev-parse', '--verify',
- bases[0] + '^0']).rstrip()
- ancestor = Commit(base, None)
- [dummy, clean] = merge(Commit(h1, None), Commit(h2, None),
- firstBranch, secondBranch, None, 0,
- ancestor)
- else:
- graph = buildGraph([h1, h2])
- [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
- firstBranch, secondBranch, graph)
-
- print ''
-except:
- if isinstance(sys.exc_info()[1], SystemExit):
- raise
- else:
- traceback.print_exc(None, sys.stderr)
- sys.exit(2)
-
-if clean:
- sys.exit(0)
-else:
- sys.exit(1)
diff --git a/git-merge.sh b/git-merge.sh
index cb094388bb..2f3d936b9c 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -3,22 +3,20 @@
# Copyright (c) 2005 Junio C Hamano
#
-USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <remote>+'
+USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [--reflog-action=<action>] [-m=<merge-message>] <commit>+'
+
. git-sh-setup
LF='
'
-all_strategies='recur recursive recursive-old octopus resolve stupid ours'
+all_strategies='recur recursive octopus resolve stupid ours'
default_twohead_strategies='recursive'
default_octopus_strategies='octopus'
no_trivial_merge_strategies='ours'
use_strategies=
index_merge=t
-if test "@@NO_PYTHON@@"; then
- all_strategies='recur recursive resolve octopus stupid ours'
-fi
dropsave() {
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
@@ -95,7 +93,7 @@ finish () {
case "$#" in 0) usage ;; esac
-rloga=
+rloga= have_message=
while case "$#" in 0) break ;; esac
do
case "$1" in
@@ -128,17 +126,82 @@ do
--reflog-action=*)
rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
;;
+ -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+ merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ have_message=t
+ ;;
+ -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+ shift
+ case "$#" in
+ 1) usage ;;
+ esac
+ merge_msg="$1"
+ have_message=t
+ ;;
-*) usage ;;
*) break ;;
esac
shift
done
-merge_msg="$1"
-shift
-head_arg="$1"
-head=$(git-rev-parse --verify "$1"^0) || usage
-shift
+# This could be traditional "merge <msg> HEAD <commit>..." and the
+# way we can tell it is to see if the second token is HEAD, but some
+# people might have misused the interface and used a committish that
+# is the same as HEAD there instead. Traditional format never would
+# have "-m" so it is an additional safety measure to check for it.
+
+if test -z "$have_message" &&
+ second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) &&
+ head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) &&
+ test "$second_token" = "$head_commit"
+then
+ merge_msg="$1"
+ shift
+ head_arg="$1"
+ shift
+elif ! git-rev-parse --verify HEAD >/dev/null 2>&1
+then
+ # If the merged head is a valid one there is no reason to
+ # forbid "git merge" into a branch yet to be born. We do
+ # the same for "git pull".
+ if test 1 -ne $#
+ then
+ echo >&2 "Can merge only exactly one commit into empty head"
+ exit 1
+ fi
+
+ rh=$(git rev-parse --verify "$1^0") ||
+ die "$1 - not something we can merge"
+
+ git-update-ref -m "initial pull" HEAD "$rh" "" &&
+ git-read-tree --reset -u HEAD
+ exit
+
+else
+ # We are invoked directly as the first-class UI.
+ head_arg=HEAD
+
+ # All the rest are the commits being merged; prepare
+ # the standard merge summary message to be appended to
+ # the given message. If remote is invalid we will die
+ # later in the common codepath so we discard the error
+ # in this loop.
+ merge_name=$(for remote
+ do
+ 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 ."
+ else
+ echo "$rh commit '$remote'"
+ fi
+ done | git-fmt-merge-msg
+ )
+ merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
+fi
+head=$(git-rev-parse --verify "$head_arg"^0) || usage
# All the rest are remote heads
test "$#" = 0 && usage ;# we need at least one remote head.
@@ -147,7 +210,7 @@ test "$rloga" = '' && rloga="merge: $@"
remoteheads=
for remote
do
- remotehead=$(git-rev-parse --verify "$remote"^0) ||
+ remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
die "$remote - not something we can merge"
remoteheads="${remoteheads}$remotehead "
done
@@ -337,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")
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index c325ef761e..6ae534bf89 100755
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -90,6 +90,43 @@ get_remote_default_refs_for_push () {
esac
}
+# Called from canon_refs_list_for_fetch -d "$remote", which
+# is called from get_remote_default_refs_for_fetch to grok
+# refspecs that are retrieved from the configuration, but not
+# from get_remote_refs_for_fetch when it deals with refspecs
+# supplied on the command line. $ls_remote_result has the list
+# of refs available at remote.
+expand_refs_wildcard () {
+ for ref
+ do
+ lref=${ref#'+'}
+ # a non glob pattern is given back as-is.
+ expr "z$lref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || {
+ echo "$ref"
+ continue
+ }
+
+ from=`expr "z$lref" : 'z\(refs/.*/\)\*:refs/.*/\*$'`
+ to=`expr "z$lref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'`
+ local_force=
+ test "z$lref" = "z$ref" || local_force='+'
+ echo "$ls_remote_result" |
+ (
+ IFS=' '
+ while read sha1 name
+ do
+ mapped=${name#"$from"}
+ if test "z$name" != "z${name%'^{}'}" ||
+ test "z$name" = "z$mapped"
+ then
+ continue
+ fi
+ echo "${local_force}${name}:${to}${mapped}"
+ done
+ )
+ done
+}
+
# Subroutine to canonicalize remote:local notation.
canon_refs_list_for_fetch () {
# If called from get_remote_default_refs_for_fetch
@@ -97,6 +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
@@ -107,6 +146,8 @@ canon_refs_list_for_fetch () {
merge_branches=$(git-repo-config \
--get-all "branch.${curr_branch}.merge")
fi
+ set x $(expand_refs_wildcard "$@")
+ shift
fi
for ref
do
@@ -132,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/*) ;;
@@ -152,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)
diff --git a/git-rebase.sh b/git-rebase.sh
index 546fa446fc..25530dfdc5 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -302,15 +302,6 @@ then
exit $?
fi
-if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old"
-then
- die 'The recursive-old merge strategy is written in Python,
-which this installation of git was not configured with. Please consider
-a different merge strategy (e.g. recursive, resolve, or stupid)
-or install Python and git with Python support.'
-
-fi
-
# start doing a rebase with git-merge
# this is rename-aware if the recursive (default) strategy is used
diff --git a/git-request-pull.sh b/git-request-pull.sh
index 4319e35c62..4eacc3a059 100755
--- a/git-request-pull.sh
+++ b/git-request-pull.sh
@@ -30,4 +30,4 @@ echo " $url"
echo
git log $baserev..$headrev | git-shortlog ;
-git diff --stat --summary $baserev..$headrev
+git diff -M --stat --summary $baserev..$headrev
diff --git a/git-rerere.perl b/git-rerere.perl
index d3664ff491..2e8dbbd4ea 100755
--- a/git-rerere.perl
+++ b/git-rerere.perl
@@ -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
diff --git a/git-reset.sh b/git-reset.sh
index 3133b5bd25..c0feb4435d 100755
--- a/git-reset.sh
+++ b/git-reset.sh
@@ -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
diff --git a/git-shortlog.perl b/git-shortlog.perl
deleted file mode 100755
index 334fec7477..0000000000
--- a/git-shortlog.perl
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Getopt::Std;
-use File::Basename qw(basename dirname);
-
-our ($opt_h, $opt_n, $opt_s);
-getopts('hns');
-
-$opt_h && usage();
-
-sub usage {
- print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < <log_data>\n";
- exit(1);
-}
-
-my (%mailmap);
-my (%email);
-my (%map);
-my $pstate = 1;
-my $n_records = 0;
-my $n_output = 0;
-
-sub shortlog_entry($$) {
- my ($name, $desc) = @_;
- my $key = $name;
-
- $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g;
- $desc =~ s#\[PATCH\] ##g;
-
- # store description in array, in email->{desc list} map
- if (exists $map{$key}) {
- # grab ref
- my $obj = $map{$key};
-
- # add desc to array
- push(@$obj, $desc);
- } else {
- # create new array, containing 1 item
- my @arr = ($desc);
-
- # store ref to array
- $map{$key} = \@arr;
- }
-}
-
-# sort comparison function
-sub by_name($$) {
- my ($a, $b) = @_;
-
- uc($a) cmp uc($b);
-}
-sub by_nbentries($$) {
- my ($a, $b) = @_;
- my $a_entries = $map{$a};
- my $b_entries = $map{$b};
-
- @$b_entries - @$a_entries || by_name $a, $b;
-}
-
-my $sort_method = $opt_n ? \&by_nbentries : \&by_name;
-
-sub summary_output {
- my ($obj, $num, $key);
-
- foreach $key (sort $sort_method keys %map) {
- $obj = $map{$key};
- $num = @$obj;
- printf "%s: %u\n", $key, $num;
- $n_output += $num;
- }
-}
-
-sub shortlog_output {
- my ($obj, $num, $key, $desc);
-
- foreach $key (sort $sort_method keys %map) {
- $obj = $map{$key};
- $num = @$obj;
-
- # output author
- printf "%s (%u):\n", $key, $num;
-
- # output author's 1-line summaries
- foreach $desc (reverse @$obj) {
- print " $desc\n";
- $n_output++;
- }
-
- # blank line separating author from next author
- print "\n";
- }
-}
-
-sub changelog_input {
- my ($author, $desc);
-
- while (<>) {
- # get author and email
- if ($pstate == 1) {
- my ($email);
-
- next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/;
-
- $n_records++;
-
- $author = $1;
- $email = $2;
- $desc = undef;
-
- # cset author fixups
- if (exists $mailmap{$email}) {
- $author = $mailmap{$email};
- } elsif (exists $mailmap{$author}) {
- $author = $mailmap{$author};
- } elsif (!$author) {
- $author = $email;
- }
- $email{$author}{$email}++;
- $pstate++;
- }
-
- # skip to blank line
- elsif ($pstate == 2) {
- next unless /^\s*$/;
- $pstate++;
- }
-
- # skip to non-blank line
- elsif ($pstate == 3) {
- next unless /^\s*?(.*)/;
-
- # skip lines that are obviously not
- # a 1-line cset description
- next if /^\s*From: /;
-
- chomp;
- $desc = $1;
-
- &shortlog_entry($author, $desc);
-
- $pstate = 1;
- }
-
- else {
- die "invalid parse state $pstate";
- }
- }
-}
-
-sub read_mailmap {
- my ($fh, $mailmap) = @_;
- while (<$fh>) {
- chomp;
- if (/^([^#].*?)\s*<(.*)>/) {
- $mailmap->{$2} = $1;
- }
- }
-}
-
-sub setup_mailmap {
- read_mailmap(\*DATA, \%mailmap);
- if (-f '.mailmap') {
- my $fh = undef;
- open $fh, '<', '.mailmap';
- read_mailmap($fh, \%mailmap);
- close $fh;
- }
-}
-
-sub finalize {
- #print "\n$n_records records parsed.\n";
-
- if ($n_records != $n_output) {
- die "parse error: input records != output records\n";
- }
- if (0) {
- for my $author (sort keys %email) {
- my $e = $email{$author};
- for my $email (sort keys %$e) {
- print STDERR "$author <$email>\n";
- }
- }
- }
-}
-
-&setup_mailmap;
-&changelog_input;
-$opt_s ? &summary_output : &shortlog_output;
-&finalize;
-exit(0);
-
-
-__DATA__
-#
-# Even with git, we don't always have name translations.
-# So have an email->real name table to translate the
-# (hopefully few) missing names
-#
-Adrian Bunk <bunk@stusta.de>
-Andreas Herrmann <aherrman@de.ibm.com>
-Andrew Morton <akpm@osdl.org>
-Andrew Vasquez <andrew.vasquez@qlogic.com>
-Christoph Hellwig <hch@lst.de>
-Corey Minyard <minyard@acm.org>
-David Woodhouse <dwmw2@shinybook.infradead.org>
-Domen Puncer <domen@coderock.org>
-Douglas Gilbert <dougg@torque.net>
-Ed L Cashin <ecashin@coraid.com>
-Evgeniy Polyakov <johnpol@2ka.mipt.ru>
-Felix Moeller <felix@derklecks.de>
-Frank Zago <fzago@systemfabricworks.com>
-Greg Kroah-Hartman <gregkh@suse.de>
-James Bottomley <jejb@mulgrave.(none)>
-James Bottomley <jejb@titanic.il.steeleye.com>
-Jeff Garzik <jgarzik@pretzel.yyz.us>
-Jens Axboe <axboe@suse.de>
-Kay Sievers <kay.sievers@vrfy.org>
-Mitesh shah <mshah@teja.com>
-Morten Welinder <terra@gnome.org>
-Morten Welinder <welinder@anemone.rentec.com>
-Morten Welinder <welinder@darter.rentec.com>
-Morten Welinder <welinder@troll.com>
-Nguyen Anh Quynh <aquynh@gmail.com>
-Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
-Peter A Jonsson <pj@ludd.ltu.se>
-Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
-Rudolf Marek <R.Marek@sh.cvut.cz>
-Rui Saraiva <rmps@joel.ist.utl.pt>
-Sachin P Sant <ssant@in.ibm.com>
-Santtu Hyrkk,Av(B <santtu.hyrkko@gmail.com>
-Simon Kelley <simon@thekelleys.org.uk>
-Tejun Heo <htejun@gmail.com>
-Tony Luck <tony.luck@intel.com>
diff --git a/git-svn.perl b/git-svn.perl
index 0a47b1f9fd..15254e4795 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -21,7 +21,17 @@ $ENV{TZ} = 'UTC';
$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
@@ -60,6 +70,7 @@ nag_lib() unless $_use_lib;
my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
my $sha1 = qr/[a-f\d]{40}/;
my $sha1_short = qr/[a-f\d]{4,40}/;
+my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
$_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
$_repack, $_repack_nr, $_repack_flags, $_q,
@@ -68,9 +79,10 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
$_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
- $_username, $_config_dir, $_no_auth_cache);
+ $_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,
@@ -122,7 +134,12 @@ my %cmd = (
'no-graft-copy' => \$_no_graft_copy } ],
'multi-init' => [ \&multi_init,
'Initialize multiple trees (like git-svnimport)',
- { %multi_opts, %fc_opts } ],
+ { %multi_opts, %init_opts,
+ 'revision|r=i' => \$_revision,
+ 'username=s' => \$_username,
+ 'config-dir=s' => \$_config_dir,
+ 'no-auth-cache' => \$_no_auth_cache,
+ } ],
'multi-fetch' => [ \&multi_fetch,
'Fetch multiple trees (like git-svnimport)',
\%fc_opts ],
@@ -135,6 +152,8 @@ my %cmd = (
'show-commit' => \$_show_commit,
'non-recursive' => \$_non_recursive,
'authors-file|A=s' => \$_authors,
+ 'color' => \$_color,
+ 'pager=s' => \$_pager,
} ],
'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
{ 'message|m=s' => \$_message,
@@ -450,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 };
@@ -584,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) {
@@ -612,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);
@@ -759,16 +780,17 @@ sub show_log {
}
}
+ config_pager();
my $pid = open(my $log,'-|');
defined $pid or croak $!;
if (!$pid) {
exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!;
}
- setup_pager();
+ run_pager();
my (@k, $c, $d);
while (<$log>) {
- if (/^commit ($sha1_short)/o) {
+ if (/^${_esc_color}commit ($sha1_short)/o) {
my $cmt = $1;
if ($c && cmt_showable($c) && $c->{r} != $r_last) {
$r_last = $c->{r};
@@ -777,25 +799,25 @@ sub show_log {
}
$d = undef;
$c = { c => $cmt };
- } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) {
+ } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) {
get_author_info($c, $1, $2, $3);
- } elsif (/^(?:tree|parent|committer) /) {
+ } elsif (/^${_esc_color}(?:tree|parent|committer) /) {
# ignore
- } elsif (/^:\d{6} \d{6} $sha1_short/o) {
+ } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) {
push @{$c->{raw}}, $_;
- } elsif (/^[ACRMDT]\t/) {
+ } elsif (/^${_esc_color}[ACRMDT]\t/) {
# we could add $SVN->{svn_path} here, but that requires
# remote access at the moment (repo_path_split)...
- s#^([ACRMDT])\t# $1 #;
+ s#^(${_esc_color})([ACRMDT])\t#$1 $2 #;
push @{$c->{changed}}, $_;
- } elsif (/^diff /) {
+ } elsif (/^${_esc_color}diff /) {
$d = 1;
push @{$c->{diff}}, $_;
} elsif ($d) {
push @{$c->{diff}}, $_;
- } elsif (/^ (git-svn-id:.+)$/) {
+ } elsif (/^${_esc_color} (git-svn-id:.+)$/) {
($c->{url}, $c->{r}, undef) = extract_metadata($1);
- } elsif (s/^ //) {
+ } elsif (s/^${_esc_color} //) {
push @{$c->{l}}, $_;
}
}
@@ -901,12 +923,30 @@ sub cmt_showable {
return defined $c->{r};
}
+sub log_use_color {
+ return 1 if $_color;
+ my $dc;
+ chomp($dc = `git-repo-config --get diff.color`);
+ if ($dc eq 'auto') {
+ if (-t *STDOUT || (defined $_pager &&
+ `git-repo-config --bool --get pager.color` !~ /^false/)) {
+ return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
+ }
+ return 0;
+ }
+ return 0 if $dc eq 'never';
+ return 1 if $dc eq 'always';
+ chomp($dc = `git-repo-config --bool --get diff.color`);
+ $dc eq 'true';
+}
+
sub git_svn_log_cmd {
my ($r_min, $r_max) = @_;
my @cmd = (qw/git-log --abbrev-commit --pretty=raw
--default/, "refs/remotes/$GIT_SVN");
push @cmd, '-r' unless $_non_recursive;
push @cmd, qw/--raw --name-status/ if $_verbose;
+ push @cmd, '--color' if log_use_color();
return @cmd unless defined $r_max;
if ($r_max == $r_min) {
push @cmd, '--max-count=1';
@@ -1152,7 +1192,7 @@ sub graft_file_copy_lib {
while (1) {
my $pool = SVN::Pool->new;
libsvn_get_log(libsvn_dup_ra($SVN), [$path],
- $min, $max, 0, 1, 1,
+ $min, $max, 0, 2, 1,
sub {
libsvn_graft_file_copies($grafts, $tree_paths,
$path, @_);
@@ -1987,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}}) {
@@ -2533,14 +2581,18 @@ sub tz_to_s_offset {
return ($1 * 60) + ($tz * 3600);
}
-sub setup_pager { # translated to Perl from pager.c
- return unless (-t *STDOUT);
- my $pager = $ENV{PAGER};
- if (!defined $pager) {
- $pager = 'less';
- } elsif (length $pager == 0 || $pager eq 'cat') {
- return;
+# adapted from pager.c
+sub config_pager {
+ $_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
+ if (!defined $_pager) {
+ $_pager = 'less';
+ } elsif (length $_pager == 0 || $_pager eq 'cat') {
+ $_pager = undef;
}
+}
+
+sub run_pager {
+ return unless -t *STDOUT;
pipe my $rfd, my $wfd or return;
defined(my $pid = fork) or croak $!;
if (!$pid) {
@@ -2548,8 +2600,8 @@ sub setup_pager { # translated to Perl from pager.c
return;
}
open STDIN, '<&', $rfd or croak $!;
- $ENV{LESS} ||= '-S';
- exec $pager or croak "Can't run pager: $!\n";;
+ $ENV{LESS} ||= 'FRSX';
+ exec $_pager or croak "Can't run pager: $! ($_pager)\n";
}
sub get_author_info {
@@ -2675,6 +2727,9 @@ sub libsvn_load {
require SVN::Ra;
require SVN::Delta;
push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
+ push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
+ *SVN::Git::Fetcher::process_rm = *process_rm;
+ *SVN::Git::Fetcher::safe_qx = *safe_qx;
my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
$SVN::Node::dir.$SVN::Node::unknown.
$SVN::Node::none.$SVN::Node::file.
@@ -2822,9 +2877,18 @@ sub libsvn_connect {
SVN::Client::get_username_prompt_provider(
\&_username_prompt, 2),
]);
+ my $config = SVN::Core::config_get_config($_config_dir);
my $ra = SVN::Ra->new(url => $url, auth => $baton,
+ config => $config,
pool => SVN::Pool->new,
auth_provider_callbacks => $callbacks);
+
+ my $df = $ENV{GIT_SVN_DELTA_FETCH};
+ if (defined $df) {
+ $_xfer_delta = $df;
+ } else {
+ $_xfer_delta = ($url =~ m#^file://#) ? undef : 1;
+ }
$ra->{svn_path} = $url;
$ra->{repos_root} = $ra->get_repos_root;
$ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##;
@@ -2832,14 +2896,32 @@ 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/url auth auth_provider_callbacks repos_root svn_path/);
+ SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url
+ auth auth_provider_callbacks repos_root svn_path/);
}
sub libsvn_get_file {
- my ($gui, $f, $rev, $chg) = @_;
+ my ($gui, $f, $rev, $chg, $untracked) = @_;
$f =~ s#^/##;
print "\t$chg\t$f\n" unless $_q;
@@ -2877,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";
@@ -2889,12 +2985,69 @@ 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 {
- my ($gui, $last_commit, $f) = @_;
+ my ($gui, $last_commit, $f, $q) = @_;
# remove entire directories.
if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
defined(my $pid = open my $ls, '-|') or croak $!;
@@ -2905,50 +3058,96 @@ sub process_rm {
local $/ = "\0";
while (<$ls>) {
print $gui '0 ',0 x 40,"\t",$_ or croak $!;
+ print "\tD\t$_\n" unless $q;
}
+ 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;
}
}
sub libsvn_fetch {
+ $_xfer_delta ? libsvn_fetch_delta(@_) : libsvn_fetch_full(@_);
+}
+
+sub libsvn_fetch_delta {
+ my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
+ my $pool = SVN::Pool->new;
+ my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q });
+ my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
+ my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+ my (undef, $last_rev, undef) = cmt_metadata($last_commit);
+ $reporter->set_path('', $last_rev, 0, @lock, $pool);
+ $reporter->finish_report($pool);
+ $pool->clear;
+ unless ($ed->{git_commit_ok}) {
+ die "SVN connection failed somewhere...\n";
+ }
+ libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed);
+}
+
+sub libsvn_fetch_full {
my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
open my $gui, '| git-update-index -z --index-info' or croak $!;
- my @amr;
+ my %amr;
+ my $ut = { empty => {}, dir_prop => {}, file_prop => {} };
my $p = $SVN->{svn_path};
foreach my $f (keys %$paths) {
my $m = $paths->{$f}->action();
- $f =~ s#^/\Q$p\E/##;
- next if $f =~ m#^/#;
+ if (length $p) {
+ $f =~ s#^/\Q$p\E/##;
+ next if $f =~ m#^/#;
+ } else {
+ $f =~ s#^/##;
+ }
if ($m =~ /^[DR]$/) {
- print "\t$m\t$f\n" unless $_q;
- process_rm($gui, $last_commit, $f);
- next if $m eq 'D';
+ my $t = process_rm($gui, $last_commit, $f, $_q);
+ if ($m eq 'D') {
+ $ut->{empty}->{$f} = 0 if $t == $SVN::Node::dir;
+ next;
+ }
# 'R' can be file replacements, too, right?
}
my $pool = SVN::Pool->new;
my $t = $SVN->check_path($f, $rev, $pool);
if ($t == $SVN::Node::file) {
if ($m =~ /^[AMR]$/) {
- push @amr, [ $m, $f ];
+ $amr{$f} = $m;
} else {
die "Unrecognized action: $m, ($f r$rev)\n";
}
} elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) {
my @traversed = ();
- libsvn_traverse($gui, '', $f, $rev, \@traversed);
- foreach (@traversed) {
- push @amr, [ $m, $_ ]
+ 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 (@amr) {
- libsvn_get_file($gui, $_->[1], $rev, $_->[0]);
+ foreach (keys %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 {
@@ -3009,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 {
@@ -3064,7 +3276,7 @@ sub revisions_eq {
# should be OK to use Pool here (r1 - r0) should be small
my $pool = SVN::Pool->new;
libsvn_get_log($SVN, [$path], $r0, $r1,
- 0, 1, 1, sub {$nr++}, $pool);
+ 0, 0, 1, sub {$nr++}, $pool);
$pool->clear;
} else {
my ($url, undef) = repo_path_split($SVN_URL);
@@ -3127,8 +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);
- return libsvn_fetch($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;
@@ -3136,6 +3366,7 @@ sub libsvn_find_parent_branch {
sub libsvn_get_log {
my ($ra, @args) = @_;
+ $args[4]-- if $args[4] && $_xfer_delta && ! $_follow_parent;
if ($SVN::Core::VERSION le '1.2.0') {
splice(@args, 3, 1);
}
@@ -3147,10 +3378,26 @@ sub libsvn_new_tree {
return $log_entry;
}
my ($paths, $rev, $author, $date, $msg) = @_;
- open my $gui, '| git-update-index -z --index-info' or croak $!;
- libsvn_traverse($gui, '', $SVN->{svn_path}, $rev);
- close $gui or croak $?;
- return libsvn_log_entry($rev, $author, $date, $msg);
+ my $ut;
+ if ($_xfer_delta) {
+ my $pool = SVN::Pool->new;
+ my $ed = SVN::Git::Fetcher->new({q => $_q});
+ my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
+ my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+ $reporter->set_path('', $rev, 1, @lock, $pool);
+ $reporter->finish_report($pool);
+ $pool->clear;
+ unless ($ed->{git_commit_ok}) {
+ die "SVN connection failed somewhere...\n";
+ }
+ $ut = $ed;
+ } else {
+ $ut = { empty => {}, dir_prop => {}, file_prop => {} };
+ open my $gui, '| git-update-index -z --index-info' or croak $!;
+ libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut);
+ close $gui or croak $?;
+ }
+ libsvn_log_entry($rev, $author, $date, $msg, [], $ut);
}
sub find_graft_path_commit {
@@ -3233,11 +3480,11 @@ sub libsvn_commit_cb {
sub libsvn_ls_fullurl {
my $fullurl = shift;
- $SVN ||= libsvn_connect($fullurl);
+ my $ra = libsvn_connect($fullurl);
my @ret;
my $pool = SVN::Pool->new;
- my ($dirent, undef, undef) = $SVN->get_dir($SVN->{svn_path},
- $SVN->get_latest_revnum, $pool);
+ my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
+ my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool);
foreach my $d (keys %$dirent) {
if ($dirent->{$d}->kind == $SVN::Node::dir) {
push @ret, "$d/"; # add '/' for compat with cli svn
@@ -3319,6 +3566,194 @@ sub copy_remote_ref {
"refs/remotes/$GIT_SVN on $origin\n";
}
}
+package SVN::Git::Fetcher;
+use vars qw/@ISA/;
+use strict;
+use warnings;
+use Carp qw/croak/;
+use IO::File qw//;
+
+# file baton members: path, mode_a, mode_b, pool, fh, blob, base
+sub new {
+ my ($class, $git_svn) = @_;
+ my $self = SVN::Delta::Editor->new;
+ bless $self, $class;
+ open my $gui, '| git-update-index -z --index-info' or croak $!;
+ $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) = @_;
+ my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q});
+ $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir;
+ undef;
+}
+
+sub open_file {
+ my ($self, $path, $pb, $rev) = @_;
+ my ($mode, $blob) = (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') {
+ if ($fb->{mode_b} != 120000) {
+ $fb->{mode_b} = defined $value ? 100755 : 100644;
+ }
+ } elsif ($prop eq 'svn:special') {
+ $fb->{mode_b} = defined $value ? 120000 : 100644;
+ } else {
+ $self->{file_prop}->{$fb->{path}} ||= {};
+ $self->{file_prop}->{$fb->{path}}->{$prop} = $value;
+ }
+ undef;
+}
+
+sub apply_textdelta {
+ my ($self, $fb, $exp) = @_;
+ my $fh = IO::File->new_tmpfile;
+ $fh->autoflush(1);
+ # $fh gets auto-closed() by SVN::TxDelta::apply(),
+ # (but $base does not,) so dup() it for reading in close_file
+ open my $dup, '<&', $fh or croak $!;
+ my $base = IO::File->new_tmpfile;
+ $base->autoflush(1);
+ if ($fb->{blob}) {
+ defined (my $pid = fork) or croak $!;
+ if (!$pid) {
+ open STDOUT, '>&', $base or croak $!;
+ print STDOUT 'link ' if ($fb->{mode_a} == 120000);
+ exec qw/git-cat-file blob/, $fb->{blob} or croak $!;
+ }
+ waitpid $pid, 0;
+ croak $? if $?;
+
+ if (defined $exp) {
+ seek $base, 0, 0 or croak $!;
+ my $md5 = Digest::MD5->new;
+ $md5->addfile($base);
+ my $got = $md5->hexdigest;
+ die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
+ "expected: $exp\n",
+ " got: $got\n" if ($got ne $exp);
+ }
+ }
+ seek $base, 0, 0 or croak $!;
+ $fb->{fh} = $dup;
+ $fb->{base} = $base;
+ [ SVN::TxDelta::apply($base, $fh, undef, $fb->{path}, $fb->{pool}) ];
+}
+
+sub close_file {
+ my ($self, $fb, $exp) = @_;
+ my $hash;
+ my $path = $fb->{path};
+ if (my $fh = $fb->{fh}) {
+ seek($fh, 0, 0) or croak $!;
+ my $md5 = Digest::MD5->new;
+ $md5->addfile($fh);
+ my $got = $md5->hexdigest;
+ die "Checksum mismatch: $path\n",
+ "expected: $exp\n got: $got\n" if ($got ne $exp);
+ seek($fh, 0, 0) or croak $!;
+ if ($fb->{mode_b} == 120000) {
+ read($fh, my $buf, 5) == 5 or croak $!;
+ $buf eq 'link ' or die "$path has mode 120000",
+ "but is not a link\n";
+ }
+ defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n";
+ if (!$pid) {
+ open STDIN, '<&', $fh or croak $!;
+ exec qw/git-hash-object -w --stdin/ or croak $!;
+ }
+ chomp($hash = do { local $/; <$out> });
+ close $out or croak $!;
+ close $fh or croak $!;
+ $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
+ close $fb->{base} or croak $!;
+ } else {
+ $hash = $fb->{blob} or die "no blob information\n";
+ }
+ $fb->{pool}->clear;
+ my $gui = $self->{gui};
+ print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!;
+ print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q};
+ undef;
+}
+
+sub abort_edit {
+ my $self = shift;
+ close $self->{gui};
+ $self->SUPER::abort_edit(@_);
+}
+
+sub close_edit {
+ my $self = shift;
+ close $self->{gui} or croak $!;
+ $self->{git_commit_ok} = 1;
+ $self->SUPER::close_edit(@_);
+}
package SVN::Git::Editor;
use vars qw/@ISA/;
diff --git a/git-tag.sh b/git-tag.sh
index ac269e3277..d53f94cd9c 100755
--- a/git-tag.sh
+++ b/git-tag.sh
@@ -5,6 +5,7 @@ USAGE='-l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [
SUBDIRECTORY_OK='Yes'
. git-sh-setup
+message_given=
annotate=
signed=
force=
@@ -37,6 +38,12 @@ do
annotate=1
shift
message="$1"
+ if test "$#" = "0"; then
+ die "error: option -m needs an argument"
+ exit 2
+ else
+ message_given=1
+ fi
;;
-u)
annotate=1
@@ -83,7 +90,7 @@ tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
if [ "$annotate" ]; then
- if [ -z "$message" ]; then
+ if [ -z "$message_given" ]; then
( echo "#"
echo "# Write a tag message"
echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
@@ -95,7 +102,7 @@ if [ "$annotate" ]; then
grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
git-stripspace >"$GIT_DIR"/TAG_FINALMSG
- [ -s "$GIT_DIR"/TAG_FINALMSG ] || {
+ [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
echo >&2 "No tag message?"
exit 1
}
diff --git a/git.c b/git.c
index 1aa07a5164..016ee8adb7 100644
--- 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 },
@@ -260,6 +261,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
{ "rm", cmd_rm, RUN_SETUP },
{ "runstatus", cmd_runstatus, RUN_SETUP },
+ { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP | USE_PAGER },
{ "stripspace", cmd_stripspace },
diff --git a/git.spec.in b/git.spec.in
index 83268fc9d9..fb95e37594 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages.
%package core
Summary: Core git tools
Group: Development/Tools
-Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat
+Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat
%description core
This is a stupid (but extremely fast) directory content manager. It
doesn't do a whole lot, but what it _does_ do is track directory
diff --git a/gitMergeCommon.py b/gitMergeCommon.py
deleted file mode 100644
index fdbf9e4778..0000000000
--- a/gitMergeCommon.py
+++ /dev/null
@@ -1,275 +0,0 @@
-#
-# Copyright (C) 2005 Fredrik Kuivinen
-#
-
-import sys, re, os, traceback
-from sets import Set
-
-def die(*args):
- printList(args, sys.stderr)
- sys.exit(2)
-
-def printList(list, file=sys.stdout):
- for x in list:
- file.write(str(x))
- file.write(' ')
- file.write('\n')
-
-import subprocess
-
-# Debugging machinery
-# -------------------
-
-DEBUG = 0
-functionsToDebug = Set()
-
-def addDebug(func):
- if type(func) == str:
- functionsToDebug.add(func)
- else:
- functionsToDebug.add(func.func_name)
-
-def debug(*args):
- if DEBUG:
- funcName = traceback.extract_stack()[-2][2]
- if funcName in functionsToDebug:
- printList(args)
-
-# Program execution
-# -----------------
-
-class ProgramError(Exception):
- def __init__(self, progStr, error):
- self.progStr = progStr
- self.error = error
-
- def __str__(self):
- return self.progStr + ': ' + self.error
-
-addDebug('runProgram')
-def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True):
- debug('runProgram prog:', str(prog), 'input:', str(input))
- if type(prog) is str:
- progStr = prog
- else:
- progStr = ' '.join(prog)
-
- try:
- if pipeOutput:
- stderr = subprocess.STDOUT
- stdout = subprocess.PIPE
- else:
- stderr = None
- stdout = None
- pop = subprocess.Popen(prog,
- shell = type(prog) is str,
- stderr=stderr,
- stdout=stdout,
- stdin=subprocess.PIPE,
- env=env)
- except OSError, e:
- debug('strerror:', e.strerror)
- raise ProgramError(progStr, e.strerror)
-
- if input != None:
- pop.stdin.write(input)
- pop.stdin.close()
-
- if pipeOutput:
- out = pop.stdout.read()
- else:
- out = ''
-
- code = pop.wait()
- if returnCode:
- ret = [out, code]
- else:
- ret = out
- if code != 0 and not returnCode:
- debug('error output:', out)
- debug('prog:', prog)
- raise ProgramError(progStr, out)
-# debug('output:', out.replace('\0', '\n'))
- return ret
-
-# Code for computing common ancestors
-# -----------------------------------
-
-currentId = 0
-def getUniqueId():
- global currentId
- currentId += 1
- return currentId
-
-# The 'virtual' commit objects have SHAs which are integers
-shaRE = re.compile('^[0-9a-f]{40}$')
-def isSha(obj):
- return (type(obj) is str and bool(shaRE.match(obj))) or \
- (type(obj) is int and obj >= 1)
-
-class Commit(object):
- __slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha',
- 'virtual']
-
- def __init__(self, sha, parents, tree=None):
- self.parents = parents
- self.firstLineMsg = None
- self.children = []
-
- if tree:
- tree = tree.rstrip()
- assert(isSha(tree))
- self._tree = tree
-
- if not sha:
- self.sha = getUniqueId()
- self.virtual = True
- self.firstLineMsg = 'virtual commit'
- assert(isSha(tree))
- else:
- self.virtual = False
- self.sha = sha.rstrip()
- assert(isSha(self.sha))
-
- def tree(self):
- self.getInfo()
- assert(self._tree != None)
- return self._tree
-
- def shortInfo(self):
- self.getInfo()
- return str(self.sha) + ' ' + self.firstLineMsg
-
- def __str__(self):
- return self.shortInfo()
-
- def getInfo(self):
- if self.virtual or self.firstLineMsg != None:
- return
- else:
- info = runProgram(['git-cat-file', 'commit', self.sha])
- info = info.split('\n')
- msg = False
- for l in info:
- if msg:
- self.firstLineMsg = l
- break
- else:
- if l.startswith('tree'):
- self._tree = l[5:].rstrip()
- elif l == '':
- msg = True
-
-class Graph:
- def __init__(self):
- self.commits = []
- self.shaMap = {}
-
- def addNode(self, node):
- assert(isinstance(node, Commit))
- self.shaMap[node.sha] = node
- self.commits.append(node)
- for p in node.parents:
- p.children.append(node)
- return node
-
- def reachableNodes(self, n1, n2):
- res = {}
- def traverse(n):
- res[n] = True
- for p in n.parents:
- traverse(p)
-
- traverse(n1)
- traverse(n2)
- return res
-
- def fixParents(self, node):
- for x in range(0, len(node.parents)):
- node.parents[x] = self.shaMap[node.parents[x]]
-
-# addDebug('buildGraph')
-def buildGraph(heads):
- debug('buildGraph heads:', heads)
- for h in heads:
- assert(isSha(h))
-
- g = Graph()
-
- out = runProgram(['git-rev-list', '--parents'] + heads)
- for l in out.split('\n'):
- if l == '':
- continue
- shas = l.split(' ')
-
- # This is a hack, we temporarily use the 'parents' attribute
- # to contain a list of SHA1:s. They are later replaced by proper
- # Commit objects.
- c = Commit(shas[0], shas[1:])
-
- g.commits.append(c)
- g.shaMap[c.sha] = c
-
- for c in g.commits:
- g.fixParents(c)
-
- for c in g.commits:
- for p in c.parents:
- p.children.append(c)
- return g
-
-# Write the empty tree to the object database and return its SHA1
-def writeEmptyTree():
- tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index'
- def delTmpIndex():
- try:
- os.unlink(tmpIndex)
- except OSError:
- pass
- delTmpIndex()
- newEnv = os.environ.copy()
- newEnv['GIT_INDEX_FILE'] = tmpIndex
- res = runProgram(['git-write-tree'], env=newEnv).rstrip()
- delTmpIndex()
- return res
-
-def addCommonRoot(graph):
- roots = []
- for c in graph.commits:
- if len(c.parents) == 0:
- roots.append(c)
-
- superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree())
- graph.addNode(superRoot)
- for r in roots:
- r.parents = [superRoot]
- superRoot.children = roots
- return superRoot
-
-def getCommonAncestors(graph, commit1, commit2):
- '''Find the common ancestors for commit1 and commit2'''
- assert(isinstance(commit1, Commit) and isinstance(commit2, Commit))
-
- def traverse(start, set):
- stack = [start]
- while len(stack) > 0:
- el = stack.pop()
- set.add(el)
- for p in el.parents:
- if p not in set:
- stack.append(p)
- h1Set = Set()
- h2Set = Set()
- traverse(commit1, h1Set)
- traverse(commit2, h2Set)
- shared = h1Set.intersection(h2Set)
-
- if len(shared) == 0:
- shared = [addCommonRoot(graph)]
-
- res = Set()
-
- for s in shared:
- if len([c for c in s.children if c in shared]) == 0:
- res.add(s)
- return list(res)
diff --git a/gitk b/gitk
index ab383b3ad2..3dabc69516 100755
--- a/gitk
+++ b/gitk
@@ -554,7 +554,7 @@ proc makewindow {} {
pack .ctop.top.lbar.vlabel -side left -fill y
global viewhlmenu selectedhlview
set viewhlmenu [tk_optionMenu .ctop.top.lbar.vhl selectedhlview None]
- $viewhlmenu entryconf 0 -command delvhighlight
+ $viewhlmenu entryconf None -command delvhighlight
$viewhlmenu conf -font $uifont
.ctop.top.lbar.vhl conf -font $uifont
pack .ctop.top.lbar.vhl -side left -fill y
@@ -1474,7 +1474,7 @@ proc doviewmenu {m first cmd op argv} {
proc allviewmenus {n op args} {
global viewhlmenu
- doviewmenu .bar.view 7 [list showview $n] $op $args
+ doviewmenu .bar.view 5 [list showview $n] $op $args
doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
}
@@ -1516,7 +1516,7 @@ proc newviewok {top n} {
set viewperm($n) $newviewperm($n)
if {$newviewname($n) ne $viewname($n)} {
set viewname($n) $newviewname($n)
- doviewmenu .bar.view 7 [list showview $n] \
+ doviewmenu .bar.view 5 [list showview $n] \
entryconf [list -label $viewname($n)]
doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
entryconf [list -label $viewname($n) -value $viewname($n)]
@@ -1632,8 +1632,8 @@ proc showview {n} {
set curview $n
set selectedview $n
- .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
- .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
+ .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
+ .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
if {![info exists viewdata($n)]} {
set pending_select $selid
@@ -4899,9 +4899,9 @@ proc rowmenu {x y id} {
} else {
set state normal
}
- $rowctxmenu entryconfigure 0 -state $state
- $rowctxmenu entryconfigure 1 -state $state
- $rowctxmenu entryconfigure 2 -state $state
+ $rowctxmenu entryconfigure "Diff this*" -state $state
+ $rowctxmenu entryconfigure "Diff selected*" -state $state
+ $rowctxmenu entryconfigure "Make patch" -state $state
set rowmenuid $id
tk_popup $rowctxmenu $x $y
}
@@ -6305,8 +6305,8 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} {
set viewargs(1) $revtreeargs
set viewperm(1) 0
addviewmenu 1
- .bar.view entryconf 2 -state normal
- .bar.view entryconf 3 -state normal
+ .bar.view entryconf Edit* -state normal
+ .bar.view entryconf Delete* -state normal
}
if {[info exists permviews]} {
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 6ae7e80351..5ea3fda540 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -120,7 +120,7 @@ our %feature = (
# To disable system wide have in $GITWEB_CONFIG
# $feature{'snapshot'}{'default'} = [undef];
# To have project specific config enable override in $GITWEB_CONFIG
- # $feature{'blame'}{'override'} = 1;
+ # $feature{'snapshot'}{'override'} = 1;
# and in project config gitweb.snapshot = none|gzip|bzip2;
'snapshot' => {
'sub' => \&feature_snapshot,
@@ -585,7 +585,21 @@ sub esc_html ($;%) {
return $str;
}
-# Make control characterss "printable".
+# quote control characters and escape filename to HTML
+sub esc_path {
+ my $str = shift;
+ my %opts = @_;
+
+ $str = to_utf8($str);
+ $str = escapeHTML($str);
+ if ($opts{'-nbsp'}) {
+ $str =~ s/ /&nbsp;/g;
+ }
+ $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
+ return $str;
+}
+
+# Make control characters "printable", using character escape codes (CEC)
sub quot_cec {
my $cntrl = shift;
my %es = ( # character escape codes, aka escape sequences
@@ -605,22 +619,14 @@ sub quot_cec {
return "<span class=\"cntrl\">$chr</span>";
}
-# Alternatively use unicode control pictures codepoints.
+# Alternatively use unicode control pictures codepoints,
+# Unicode "printable representation" (PR)
sub quot_upr {
my $cntrl = shift;
my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
return "<span class=\"cntrl\">$chr</span>";
}
-# quote control characters and escape filename to HTML
-sub esc_path {
- my $str = shift;
-
- $str = esc_html($str);
- $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
- return $str;
-}
-
# git may return quoted and escaped filenames
sub unquote {
my $str = shift;
@@ -1148,14 +1154,15 @@ sub git_get_last_activity {
sub git_get_references {
my $type = shift || "";
my %refs;
- # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
- # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
- open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+ # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
+ # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
+ open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
+ ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
or return;
while (my $line = <$fd>) {
chomp $line;
- if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) {
+ if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
if (defined $refs{$1}) {
push @{$refs{$1}}, $2;
} else {
@@ -1287,8 +1294,9 @@ sub parse_commit {
$co{'author'} = $1;
$co{'author_epoch'} = $2;
$co{'author_tz'} = $3;
- if ($co{'author'} =~ m/^([^<]+) </) {
- $co{'author_name'} = $1;
+ if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
+ $co{'author_name'} = $1;
+ $co{'author_email'} = $2;
} else {
$co{'author_name'} = $co{'author'};
}
@@ -1297,7 +1305,12 @@ sub parse_commit {
$co{'committer_epoch'} = $2;
$co{'committer_tz'} = $3;
$co{'committer_name'} = $co{'committer'};
- $co{'committer_name'} =~ s/ <.*//;
+ if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
+ $co{'committer_name'} = $1;
+ $co{'committer_email'} = $2;
+ } else {
+ $co{'committer_name'} = $co{'committer'};
+ }
}
}
if (!defined $co{'tree'}) {
@@ -2428,6 +2441,7 @@ sub git_project_list_body {
($pr->{'age'}, $pr->{'age_string'}) = @aa;
if (!defined $pr->{'descr'}) {
my $descr = git_get_project_description($pr->{'path'}) || "";
+ $pr->{'descr_long'} = to_utf8($descr);
$pr->{'descr'} = chop_str($descr, 25, 5);
}
if (!defined $pr->{'owner'}) {
@@ -2463,7 +2477,7 @@ sub git_project_list_body {
} else {
print "<th>" .
$cgi->a({-href => href(project=>undef, order=>'project'),
- -class => "header"}, "Project") .
+ -class => "header"}, "Project") .
"</th>\n";
}
if ($order eq "descr") {
@@ -2472,7 +2486,7 @@ sub git_project_list_body {
} else {
print "<th>" .
$cgi->a({-href => href(project=>undef, order=>'descr'),
- -class => "header"}, "Description") .
+ -class => "header"}, "Description") .
"</th>\n";
}
if ($order eq "owner") {
@@ -2481,7 +2495,7 @@ sub git_project_list_body {
} else {
print "<th>" .
$cgi->a({-href => href(project=>undef, order=>'owner'),
- -class => "header"}, "Owner") .
+ -class => "header"}, "Owner") .
"</th>\n";
}
if ($order eq "age") {
@@ -2490,7 +2504,7 @@ sub git_project_list_body {
} else {
print "<th>" .
$cgi->a({-href => href(project=>undef, order=>'age'),
- -class => "header"}, "Last Change") .
+ -class => "header"}, "Last Change") .
"</th>\n";
}
print "<th></th>\n" .
@@ -2515,7 +2529,9 @@ sub git_project_list_body {
}
print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
- "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+ "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+ -class => "list", -title => $pr->{'descr_long'}},
+ esc_html($pr->{'descr'})) . "</td>\n" .
"<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
print "<td class=\"". age_class($pr->{'age'}) . "\">" .
$pr->{'age_string'} . "</td>\n" .
@@ -3213,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))) {
@@ -3253,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";
@@ -4188,7 +4218,7 @@ sub git_feed {
}
if (defined($revlist[0])) {
%latest_commit = parse_commit($revlist[0]);
- %latest_date = parse_date($latest_commit{'committer_epoch'});
+ %latest_date = parse_date($latest_commit{'author_epoch'});
print $cgi->header(
-type => $content_type,
-charset => 'utf-8',
@@ -4266,7 +4296,7 @@ XML
}
if (defined $logo_url) {
# not twice as wide as tall: 72 x 27 pixels
- print "<logo>" . esc_url($logo_url) . "</logo>\n";
+ print "<logo>" . esc_url($logo) . "</logo>\n";
}
if (! %latest_date) {
# dummy date to keep the feed valid until commits trickle in:
@@ -4281,10 +4311,10 @@ XML
my $commit = $revlist[$i];
my %co = parse_commit($commit);
# we read 150, we always show 30 and the ones more recent than 48 hours
- if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) {
+ if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
last;
}
- my %cd = parse_date($co{'committer_epoch'});
+ my %cd = parse_date($co{'author_epoch'});
# get list of changed files
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
@@ -4310,9 +4340,19 @@ XML
print "<entry>\n" .
"<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
"<updated>$cd{'iso-8601'}</updated>\n" .
- "<author><name>" . esc_html($co{'author_name'}) . "</name></author>\n" .
+ "<author>\n" .
+ " <name>" . esc_html($co{'author_name'}) . "</name>\n";
+ if ($co{'author_email'}) {
+ print " <email>" . esc_html($co{'author_email'}) . "</email>\n";
+ }
+ print "</author>\n" .
# use committer for contributor
- "<contributor><name>" . esc_html($co{'committer_name'}) . "</name></contributor>\n" .
+ "<contributor>\n" .
+ " <name>" . esc_html($co{'committer_name'}) . "</name>\n";
+ if ($co{'committer_email'}) {
+ print " <email>" . esc_html($co{'committer_email'}) . "</email>\n";
+ }
+ print "</contributor>\n" .
"<published>$cd{'iso-8601'}</published>\n" .
"<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
"<id>$co_url</id>\n" .
diff --git a/ident.c b/ident.c
index efec97ffd0..e415fd3588 100644
--- a/ident.c
+++ b/ident.c
@@ -158,12 +158,17 @@ static int copy(char *buf, int size, int offset, const char *src)
static const char au_env[] = "GIT_AUTHOR_NAME";
static const char co_env[] = "GIT_COMMITTER_NAME";
static const char *env_hint =
-"\n*** Environment problem:\n"
+"\n"
"*** Your name cannot be determined from your system services (gecos).\n"
-"*** You would need to set %s and %s\n"
-"*** environment variables; otherwise you won't be able to perform\n"
-"*** certain operations because of \"empty ident\" errors.\n"
-"*** Alternatively, you can use user.name configuration variable.\n\n";
+"\n"
+"Run\n"
+"\n"
+" git repo-config user.email \"you@email.com\"\n"
+" git repo-config user.name \"Your Name\"\n"
+"\n"
+"To set the identity in this repository.\n"
+"Add --global to set your account\'s default\n"
+"\n";
static const char *get_ident(const char *name, const char *email,
const char *date_str, int error_on_no_name)
diff --git a/index-pack.c b/index-pack.c
index 8331d99a62..6d6c92bf14 100644
--- a/index-pack.c
+++ b/index-pack.c
@@ -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;
}
}
diff --git a/merge-file.c b/merge-file.c
index fc9b148993..69dc1ebbf7 100644
--- a/merge-file.c
+++ b/merge-file.c
@@ -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)
diff --git a/merge-recursive.c b/merge-recursive.c
index cd2cc77bf4..6dd6e2e5af 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -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");
}
diff --git a/perl/.gitignore b/perl/.gitignore
index e990caeea7..98b24772c7 100644
--- a/perl/.gitignore
+++ b/perl/.gitignore
@@ -1,4 +1,5 @@
-Makefile
+perl.mak
+perl.mak.old
blib
blibdirs
pm_to_blib
diff --git a/perl/Makefile b/perl/Makefile
new file mode 100644
index 0000000000..099beda873
--- /dev/null
+++ b/perl/Makefile
@@ -0,0 +1,39 @@
+#
+# Makefile for perl support modules and routine
+#
+makfile:=perl.mak
+
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+prefix_SQ = $(subst ','\'',$(prefix))
+
+all install instlibdir: $(makfile)
+ $(MAKE) -f $(makfile) $@
+
+clean:
+ test -f $(makfile) && $(MAKE) -f $(makfile) $@ || exit 0
+ $(RM) ppport.h
+ $(RM) $(makfile)
+ $(RM) $(makfile).old
+
+ifdef NO_PERL_MAKEMAKER
+instdir_SQ = $(subst ','\'',$(prefix)/lib)
+$(makfile): ../GIT-CFLAGS Makefile
+ echo all: > $@
+ echo ' :' >> $@
+ echo install: >> $@
+ echo ' mkdir -p $(instdir_SQ)' >> $@
+ echo ' $(RM) $(instdir_SQ)/Git.pm; cp Git.pm $(instdir_SQ)' >> $@
+ echo ' $(RM) $(instdir_SQ)/Error.pm; \
+ cp private-Error.pm $(instdir_SQ)/Error.pm' >> $@
+ echo instlibdir: >> $@
+ echo ' echo $(instdir_SQ)' >> $@
+else
+$(makfile): Makefile.PL ../GIT-CFLAGS
+ '$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)'
+endif
+
+# this is just added comfort for calling make directly in perl dir
+# (even though GIT-CFLAGS aren't used yet. If ever)
+../GIT-CFLAGS:
+ $(MAKE) -C .. GIT-CFLAGS
+
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index de73235e4c..41687757a7 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -24,5 +24,6 @@ WriteMakefile(
NAME => 'Git',
VERSION_FROM => 'Git.pm',
PM => \%pm,
+ MAKEFILE => 'perl.mak',
%extra
);
diff --git a/receive-pack.c b/receive-pack.c
index d56898c9b2..e76d9aea31 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -11,10 +11,10 @@
static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
static int deny_non_fast_forwards = 0;
-static int unpack_limit = 5000;
+static int unpack_limit = 100;
static int report_status;
-static char capabilities[] = "report-status";
+static char capabilities[] = " report-status delete-refs ";
static int capabilities_sent;
static int receive_pack_config(const char *var, const char *value)
@@ -113,12 +113,15 @@ static int update(struct command *cmd)
strcpy(new_hex, sha1_to_hex(new_sha1));
strcpy(old_hex, sha1_to_hex(old_sha1));
- if (!has_sha1_file(new_sha1)) {
+
+ if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
cmd->error_string = "bad pack";
return error("unpack should have generated %s, "
"but I can't find it!", new_hex);
}
- if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) {
+ if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
+ !is_null_sha1(old_sha1) &&
+ !strncmp(name, "refs/heads/", 11)) {
struct commit *old_commit, *new_commit;
struct commit_list *bases, *ent;
@@ -138,14 +141,22 @@ static int update(struct command *cmd)
return error("hook declined to update %s", name);
}
- lock = lock_any_ref_for_update(name, old_sha1);
- if (!lock) {
- cmd->error_string = "failed to lock";
- return error("failed to lock %s", name);
+ if (is_null_sha1(new_sha1)) {
+ if (delete_ref(name, old_sha1)) {
+ cmd->error_string = "failed to delete";
+ return error("failed to delete %s", name);
+ }
+ fprintf(stderr, "%s: %s -> deleted\n", name, old_hex);
+ }
+ else {
+ lock = lock_any_ref_for_update(name, old_sha1);
+ if (!lock) {
+ cmd->error_string = "failed to lock";
+ return error("failed to lock %s", name);
+ }
+ write_ref_sha1(lock, new_sha1, "push");
+ fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
}
- write_ref_sha1(lock, new_sha1, "push");
-
- fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
return 0;
}
@@ -375,6 +386,16 @@ static void report(const char *unpack_status)
packet_flush(1);
}
+static int delete_only(struct command *cmd)
+{
+ while (cmd) {
+ if (!is_null_sha1(cmd->new_sha1))
+ return 0;
+ cmd = cmd->next;
+ }
+ return 1;
+}
+
int main(int argc, char **argv)
{
int i;
@@ -408,7 +429,10 @@ int main(int argc, char **argv)
read_head_info();
if (commands) {
- const char *unpack_status = unpack();
+ const char *unpack_status = NULL;
+
+ if (!delete_only(commands))
+ unpack_status = unpack();
if (!unpack_status)
execute_commands();
if (pack_lockfile)
diff --git a/refs.c b/refs.c
index 0e156c5dee..e56abb8585 100644
--- a/refs.c
+++ b/refs.c
@@ -1,12 +1,18 @@
#include "refs.h"
#include "cache.h"
+#include "object.h"
+#include "tag.h"
#include <errno.h>
+/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
+#define REF_KNOWS_PEELED 04
+
struct ref_list {
struct ref_list *next;
unsigned char flag; /* ISSYMREF? ISPACKED? */
unsigned char sha1[20];
+ unsigned char peeled[20];
char name[FLEX_ARRAY];
};
@@ -34,11 +40,13 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
if (line[len] != '\n')
return NULL;
line[len] = 0;
+
return line;
}
static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
- int flag, struct ref_list *list)
+ int flag, struct ref_list *list,
+ struct ref_list **new_entry)
{
int len;
struct ref_list **p = &list, *entry;
@@ -50,8 +58,11 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
break;
/* Same as existing entry? */
- if (!cmp)
+ if (!cmp) {
+ if (new_entry)
+ *new_entry = entry;
return list;
+ }
p = &entry->next;
}
@@ -59,10 +70,13 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
len = strlen(name) + 1;
entry = xmalloc(sizeof(struct ref_list) + len);
hashcpy(entry->sha1, sha1);
+ hashclr(entry->peeled);
memcpy(entry->name, name, len);
entry->flag = flag;
entry->next = *p;
*p = entry;
+ if (new_entry)
+ *new_entry = entry;
return list;
}
@@ -98,25 +112,50 @@ static void invalidate_cached_refs(void)
ca->did_loose = ca->did_packed = 0;
}
+static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
+{
+ struct ref_list *list = NULL;
+ struct ref_list *last = NULL;
+ char refline[PATH_MAX];
+ int flag = REF_ISPACKED;
+
+ while (fgets(refline, sizeof(refline), f)) {
+ unsigned char sha1[20];
+ const char *name;
+ static const char header[] = "# pack-refs with:";
+
+ if (!strncmp(refline, header, sizeof(header)-1)) {
+ const char *traits = refline + sizeof(header) - 1;
+ if (strstr(traits, " peeled "))
+ flag |= REF_KNOWS_PEELED;
+ /* perhaps other traits later as well */
+ continue;
+ }
+
+ name = parse_ref_line(refline, sha1);
+ if (name) {
+ list = add_ref(name, sha1, flag, list, &last);
+ continue;
+ }
+ if (last &&
+ refline[0] == '^' &&
+ strlen(refline) == 42 &&
+ refline[41] == '\n' &&
+ !get_sha1_hex(refline + 1, sha1))
+ hashcpy(last->peeled, sha1);
+ }
+ cached_refs->packed = list;
+}
+
static struct ref_list *get_packed_refs(void)
{
if (!cached_refs.did_packed) {
- struct ref_list *refs = NULL;
FILE *f = fopen(git_path("packed-refs"), "r");
+ cached_refs.packed = NULL;
if (f) {
- struct ref_list *list = NULL;
- char refline[PATH_MAX];
- while (fgets(refline, sizeof(refline), f)) {
- unsigned char sha1[20];
- const char *name = parse_ref_line(refline, sha1);
- if (!name)
- continue;
- list = add_ref(name, sha1, REF_ISPACKED, list);
- }
+ read_packed_refs(f, &cached_refs);
fclose(f);
- refs = list;
}
- cached_refs.packed = refs;
cached_refs.did_packed = 1;
}
return cached_refs.packed;
@@ -159,7 +198,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
error("%s points nowhere!", ref);
continue;
}
- list = add_ref(ref, sha1, flag, list);
+ list = add_ref(ref, sha1, flag, list, NULL);
}
free(ref);
closedir(dir);
@@ -336,6 +375,43 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
}
+int peel_ref(const char *ref, unsigned char *sha1)
+{
+ int flag;
+ unsigned char base[20];
+ struct object *o;
+
+ if (!resolve_ref(ref, base, 1, &flag))
+ return -1;
+
+ if ((flag & REF_ISPACKED)) {
+ struct ref_list *list = get_packed_refs();
+
+ while (list) {
+ if (!strcmp(list->name, ref)) {
+ if (list->flag & REF_KNOWS_PEELED) {
+ hashcpy(sha1, list->peeled);
+ return 0;
+ }
+ /* older pack-refs did not leave peeled ones */
+ break;
+ }
+ list = list->next;
+ }
+ }
+
+ /* fallback - callers should not call this for unpacked refs */
+ o = parse_object(base);
+ if (o->type == OBJ_TAG) {
+ o = deref_tag(o, ref, 0);
+ if (o) {
+ hashcpy(sha1, o->sha1);
+ return 0;
+ }
+ }
+ return -1;
+}
+
static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
void *cb_data)
{
@@ -458,7 +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;
}
}
diff --git a/refs.h b/refs.h
index a57d43726a..cd1e1d620e 100644
--- a/refs.h
+++ b/refs.h
@@ -10,12 +10,13 @@ struct ref_lock {
int force_write;
};
+#define REF_ISSYMREF 01
+#define REF_ISPACKED 02
+
/*
* Calls the specified function for each ref file until it returns nonzero,
* and returns the value
*/
-#define REF_ISSYMREF 01
-#define REF_ISPACKED 02
typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
extern int head_ref(each_ref_fn, void *);
extern int for_each_ref(each_ref_fn, void *);
@@ -23,6 +24,8 @@ extern int for_each_tag_ref(each_ref_fn, void *);
extern int for_each_branch_ref(each_ref_fn, void *);
extern int for_each_remote_ref(each_ref_fn, void *);
+extern int peel_ref(const char *, unsigned char *);
+
/** Reads the refs file specified into sha1 **/
extern int get_ref_sha1(const char *ref, unsigned char *sha1);
diff --git a/send-pack.c b/send-pack.c
index 447666665b..cc884f3b2d 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -271,6 +271,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
int new_refs;
int ret = 0;
int ask_for_status_report = 0;
+ int allow_deleting_refs = 0;
int expect_status_report = 0;
/* No funny business with the matcher */
@@ -280,6 +281,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
/* Does the other end support the reporting? */
if (server_supports("report-status"))
ask_for_status_report = 1;
+ if (server_supports("delete-refs"))
+ allow_deleting_refs = 1;
/* match them up */
if (!remote_tail)
@@ -299,9 +302,19 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
new_refs = 0;
for (ref = remote_refs; ref; ref = ref->next) {
char old_hex[60], *new_hex;
+ int delete_ref;
+
if (!ref->peer_ref)
continue;
- if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
+
+ delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
+ if (delete_ref && !allow_deleting_refs) {
+ error("remote does not support deleting refs");
+ ret = -2;
+ continue;
+ }
+ if (!delete_ref &&
+ !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
if (verbose)
fprintf(stderr, "'%s': up-to-date\n", ref->name);
continue;
@@ -321,9 +334,13 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
*
* (3) if both new and old are commit-ish, and new is a
* descendant of old, it is OK.
+ *
+ * (4) regardless of all of the above, removing :B is
+ * always allowed.
*/
if (!force_update &&
+ !delete_ref &&
!is_zero_sha1(ref->old_sha1) &&
!ref->force) {
if (!has_sha1_file(ref->old_sha1) ||
@@ -347,12 +364,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
}
}
hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
- if (is_zero_sha1(ref->new_sha1)) {
- error("cannot happen anymore");
- ret = -3;
- continue;
- }
- new_refs++;
+ if (!delete_ref)
+ new_refs++;
strcpy(old_hex, sha1_to_hex(ref->old_sha1));
new_hex = sha1_to_hex(ref->new_sha1);
@@ -366,10 +379,16 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
else
packet_write(out, "%s %s %s",
old_hex, new_hex, ref->name);
- fprintf(stderr, "updating '%s'", ref->name);
- if (strcmp(ref->name, ref->peer_ref->name))
- fprintf(stderr, " using '%s'", ref->peer_ref->name);
- fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex);
+ if (delete_ref)
+ fprintf(stderr, "deleting '%s'\n", ref->name);
+ else {
+ fprintf(stderr, "updating '%s'", ref->name);
+ if (strcmp(ref->name, ref->peer_ref->name))
+ fprintf(stderr, " using '%s'",
+ ref->peer_ref->name);
+ fprintf(stderr, "\n from %s\n to %s\n",
+ old_hex, new_hex);
+ }
}
packet_flush(out);
@@ -387,6 +406,25 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
return ret;
}
+static void verify_remote_names(int nr_heads, char **heads)
+{
+ int i;
+
+ for (i = 0; i < nr_heads; i++) {
+ const char *remote = strchr(heads[i], ':');
+
+ remote = remote ? (remote + 1) : heads[i];
+ switch (check_ref_format(remote)) {
+ case 0: /* ok */
+ case -2: /* ok but a single level -- that is fine for
+ * a match pattern.
+ */
+ continue;
+ }
+ die("remote part of refspec is not a valid name in %s",
+ heads[i]);
+ }
+}
int main(int argc, char **argv)
{
@@ -438,6 +476,8 @@ int main(int argc, char **argv)
usage(send_pack_usage);
if (heads && send_all)
usage(send_pack_usage);
+ verify_remote_names(nr_heads, heads);
+
pid = git_connect(fd, dest, exec);
if (pid < 0)
return 1;
diff --git a/sha1_file.c b/sha1_file.c
index 09456d23f8..63f416bb5a 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -1261,7 +1261,7 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1,
}
-int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+static int sha1_loose_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
{
int status;
unsigned long mapsize, size;
@@ -1270,20 +1270,8 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
char hdr[128];
map = map_sha1_file(sha1, &mapsize);
- if (!map) {
- struct pack_entry e;
-
- if (!find_pack_entry(sha1, &e, NULL)) {
- reprepare_packed_git();
- if (!find_pack_entry(sha1, &e, NULL))
- return error("unable to find %s", sha1_to_hex(sha1));
- }
- if (use_packed_git(e.p))
- die("cannot map packed file");
- status = packed_object_info(e.p, e.offset, type, sizep);
- unuse_packed_git(e.p);
- return status;
- }
+ if (!map)
+ return error("unable to find %s", sha1_to_hex(sha1));
if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
status = error("unable to unpack %s header",
sha1_to_hex(sha1));
@@ -1299,6 +1287,23 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
return status;
}
+int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+{
+ int status;
+ struct pack_entry e;
+
+ if (!find_pack_entry(sha1, &e, NULL)) {
+ reprepare_packed_git();
+ if (!find_pack_entry(sha1, &e, NULL))
+ return sha1_loose_object_info(sha1, type, sizep);
+ }
+ if (use_packed_git(e.p))
+ die("cannot map packed file");
+ status = packed_object_info(e.p, e.offset, type, sizep);
+ unuse_packed_git(e.p);
+ return status;
+}
+
static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size)
{
struct pack_entry e;
diff --git a/t/Makefile b/t/Makefile
index 89835093fb..c9bd9a4690 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -13,10 +13,6 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
TSVN = $(wildcard t91[0-9][0-9]-*.sh)
-ifdef NO_PYTHON
- GIT_TEST_OPTS += --no-python
-endif
-
all: $(T) clean
$(T):
@@ -27,8 +23,9 @@ clean:
# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
full-svn-test:
+ $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_DELTA_FETCH=1 \
+ GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
$(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
- $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
$(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
LC_ALL=en_US.UTF-8
$(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index 29a1e72c61..63c670304f 100644
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
@@ -45,6 +45,6 @@ else
svnadmin create "$svnrepo"
fi
-svnrepo="file://$svnrepo/test-git-svn"
+svnrepo="file://$svnrepo"
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 6aff0b808c..3260d1d7a7 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -19,11 +19,7 @@ modification *should* take notice and update the test vectors here.
'
################################################################
-# It appears that people are getting bitten by not installing
-# 'merge' (usually part of RCS package in binary distributions)
-# or have too old python without subprocess. Check them and error
-# out before running any tests. Also catch the bogosity of trying
-# to run tests without building while we are at it.
+# It appears that people try to run tests without building...
../git >/dev/null
if test $? != 1
@@ -32,22 +28,8 @@ then
exit 1
fi
-merge >/dev/null 2>/dev/null
-if test $? = 127
-then
- echo >&2 'You do not seem to have "merge" installed.
-Please check INSTALL document.'
- exit 1
-fi
-
. ./test-lib.sh
-test "$no_python" || "$PYTHON" -c 'import subprocess' || {
- echo >&2 'Your python seem to lack "subprocess" module.
-Please check INSTALL document.'
- exit 1
-}
-
################################################################
# init-db has been done in an empty repository.
# make sure it is empty.
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 1bc5b7a412..adf4993bac 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -109,12 +109,10 @@ index d99af23..8b32fb5 100644
+ whitespace at beginning
whitespace change
-whitespace in the middle
--whitespace at end
+white space in the middle
-+whitespace at end
+ whitespace at end
unchanged line
--CR at endQ
-+CR at end
+ CR at endQ
EOF
git-diff -b > out
test_expect_success 'another test, with -b' 'diff -u expect out'
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index 8afb899717..28744b35e1 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -64,6 +64,16 @@ test_expect_success \
cmp victim/.git/refs/heads/master .git/refs/heads/master
'
+test_expect_success \
+ 'push can be used to delete a ref' '
+ cd victim &&
+ git branch extra master &&
+ cd .. &&
+ test -f victim/.git/refs/heads/extra &&
+ git-send-pack ./victim/.git/ :extra master &&
+ ! test -f victim/.git/refs/heads/extra
+'
+
unset GIT_CONFIG GIT_CONFIG_LOCAL
HOME=`pwd`/no-such-directory
export HOME ;# this way we force the victim/.git/config to be used.
diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh
new file mode 100644
index 0000000000..5d9b6f34b8
--- /dev/null
+++ b/t/t6023-merge-file.sh
@@ -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
index 0000000000..69c66cf6fa
--- /dev/null
+++ b/t/t6023-merge-rename-nocruft.sh
@@ -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
index 0000000000..964010e764
--- /dev/null
+++ b/t/t6024-recursive-merge.sh
@@ -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
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 23a1eff3bb..2f4ff82e14 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -105,4 +105,17 @@ test_expect_success "Michael Cassar's test case" '
}
'
+rm -fr papers partA path?
+
+test_expect_success "Sergey Vlasov's test case" '
+ rm -fr .git &&
+ git init-db &&
+ mkdir ab &&
+ date >ab.c &&
+ date >ab/d &&
+ git add ab.c ab &&
+ git commit -m 'initial' &&
+ git mv ab a
+'
+
test_done
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh
index 34a3ccd31c..f9de232366 100755
--- a/t/t9100-git-svn-basic.sh
+++ b/t/t9100-git-svn-basic.sh
@@ -228,6 +228,11 @@ tree 56a30b966619b863674f5978696f4a3594f2fca9
tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
EOF
+
+if test -z "$GIT_SVN_NO_LIB" || test "$GIT_SVN_NO_LIB" -eq 0; then
+ echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected
+fi
+
test_expect_success "$name" "diff -u a expected"
test_done
diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh
index cc62d4ece8..293b98f928 100755
--- a/t/t9103-git-svn-graft-branches.sh
+++ b/t/t9103-git-svn-graft-branches.sh
@@ -1,6 +1,8 @@
test_description='git-svn graft-branches'
. ./lib-git-svn.sh
+svnrepo="$svnrepo/test-git-svn"
+
test_expect_success 'initialize repo' "
mkdir import &&
cd import &&
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index 6e566d4409..ca0513b162 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -89,18 +89,17 @@ test_expect_success \
! git cvsexportcommit -c $id
)'
-# Should fail, but only on the git-cvsexportcommit stage
-test_expect_success \
- 'Fail to remove binary file more than one generation old' \
- 'git reset --hard HEAD^ &&
- cat F/newfile6.png >>D/newfile4.png &&
- git commit -a -m "generation 2 (again)" &&
- rm -f D/newfile4.png &&
- git commit -a -m "generation 3" &&
- id=$(git rev-list --max-count=1 HEAD) &&
- (cd "$CVSWORK" &&
- ! git cvsexportcommit -c $id
- )'
+#test_expect_success \
+# 'Fail to remove binary file more than one generation old' \
+# 'git reset --hard HEAD^ &&
+# cat F/newfile6.png >>D/newfile4.png &&
+# git commit -a -m "generation 2 (again)" &&
+# rm -f D/newfile4.png &&
+# git commit -a -m "generation 3" &&
+# id=$(git rev-list --max-count=1 HEAD) &&
+# (cd "$CVSWORK" &&
+# ! git cvsexportcommit -c $id
+# )'
# We reuse the state from two tests back here
@@ -108,7 +107,7 @@ test_expect_success \
# fail with gnu patch, so cvsexportcommit must handle that.
test_expect_success \
'Remove only binary files' \
- 'git reset --hard HEAD^^^ &&
+ 'git reset --hard HEAD^^ &&
rm -f D/newfile4.png &&
git commit -a -m "test: remove only a binary file" &&
id=$(git rev-list --max-count=1 HEAD) &&
@@ -142,4 +141,73 @@ test_expect_success \
diff F/newfile6.png ../F/newfile6.png
)'
+test_expect_success \
+ 'New file with spaces in file name' \
+ 'mkdir "G g" &&
+ echo ok then >"G g/with spaces.txt" &&
+ git add "G g/with spaces.txt" && \
+ cp ../test9200a.png "G g/with spaces.png" && \
+ git add "G g/with spaces.png" &&
+ git commit -a -m "With spaces" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ git-cvsexportcommit -c $id &&
+ test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.1/-kb with spaces.txt/1.1/"
+ )'
+
+test_expect_success \
+ 'Update file with spaces in file name' \
+ 'echo Ok then >>"G g/with spaces.txt" &&
+ cat ../test9200a.png >>"G g/with spaces.png" && \
+ git add "G g/with spaces.png" &&
+ git commit -a -m "Update with spaces" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ git-cvsexportcommit -c $id
+ test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/"
+ )'
+
+# This test contains ISO-8859-1 characters
+test_expect_success \
+ 'File with non-ascii file name' \
+ 'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö &&
+ echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
+ git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
+ cp ../test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+ git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+ git commit -a -m "Går det så går det" && \
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ git-cvsexportcommit -v -c $id &&
+ test "$(echo $(sort Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/CVS/Entries|cut -d/ -f2,3,5))" = "gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/"
+ )'
+
+test_expect_success \
+ 'Mismatching patch should fail' \
+ 'date >>"E/newfile5.txt" &&
+ git add "E/newfile5.txt" &&
+ git commit -a -m "Update one" &&
+ date >>"E/newfile5.txt" &&
+ git add "E/newfile5.txt" &&
+ git commit -a -m "Update two" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ ! git-cvsexportcommit -c $id
+ )'
+
+test_expect_success \
+ 'Retain execute bit' \
+ 'mkdir G &&
+ echo executeon >G/on &&
+ chmod +x G/on &&
+ echo executeoff >G/off &&
+ git add G/on &&
+ git add G/off &&
+ git commit -a -m "Execute test" &&
+ (cd "$CVSWORK" &&
+ git-cvsexportcommit -c HEAD
+ test -x G/on &&
+ ! test -x G/off
+ )'
+
test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 3895f16709..ac7be769b4 100755
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -76,7 +76,8 @@ do
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
verbose=t; shift ;;
--no-python)
- no_python=t; shift ;;
+ # noop now...
+ shift ;;
*)
break ;;
esac
@@ -210,18 +211,6 @@ GIT_EXEC_PATH=$(pwd)/..
HOME=$(pwd)/trash
export PATH GIT_EXEC_PATH HOME
-# Similarly use ../compat/subprocess.py if our python does not
-# have subprocess.py on its own.
-PYTHON=`sed -e '1{
- s/^#!//
- q
-}' ../git-merge-recursive-old` || {
- error "You haven't built things yet, have you?"
-}
-"$PYTHON" -c 'import subprocess' 2>/dev/null || {
- PYTHONPATH=$(pwd)/../compat
- export PYTHONPATH
-}
GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
export GITPERLLIB
test -d ../templates/blt || {
diff --git a/unpack-trees.c b/unpack-trees.c
index 7cfd628d8e..47aa804a86 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -370,7 +370,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;
@@ -381,8 +381,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 *));
diff --git a/wt-status.c b/wt-status.c
index de1be5bc66..df582a03ef 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -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]);
}
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index c9f817818a..fa409d5234 100644
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
@@ -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 */
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
index d76e76a0e6..9aeebc473b 100644
--- a/xdiff/xdiffi.c
+++ b/xdiff/xdiffi.c
@@ -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;
diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h
index d3b72716b5..472aeaecfa 100644
--- a/xdiff/xdiffi.h
+++ b/xdiff/xdiffi.h
@@ -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
index 0000000000..352207e516
--- /dev/null
+++ b/xdiff/xmerge.c
@@ -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;
+}
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 9e4bb47ee9..1b899f32c4 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -230,7 +230,8 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
while (ptr + 1 < top && isspace(ptr[1])
&& ptr[1] != '\n')
ptr++;
- if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
+ if (flags & XDF_IGNORE_WHITESPACE_CHANGE
+ && ptr[1] != '\n') {
ha += (ha << 5);
ha ^= (unsigned long) ' ';
}