Merge branch 'ph/parseopt'
authorJunio C Hamano <gitster@pobox.com>
Fri, 2 Nov 2007 23:42:23 +0000 (16:42 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 2 Nov 2007 23:42:23 +0000 (16:42 -0700)
* ph/parseopt: (24 commits)
gc: use parse_options
Fixed a command line option type for builtin-fsck.c
Make builtin-pack-refs.c use parse_options.
Make builtin-name-rev.c use parse_options.
Make builtin-count-objects.c use parse_options.
Make builtin-fsck.c use parse_options.
Update manpages to reflect new short and long option aliases
Make builtin-for-each-ref.c use parse-opts.
Make builtin-symbolic-ref.c use parse_options.
Make builtin-update-ref.c use parse_options
Make builtin-revert.c use parse_options.
Make builtin-describe.c use parse_options
Make builtin-branch.c use parse_options.
Make builtin-mv.c use parse-options
Make builtin-rm.c use parse_options.
Port builtin-add.c to use the new option parser.
parse-options: allow callbacks to take no arguments at all.
parse-options: Allow abbreviated options when unambiguous
Add shortcuts for very often used options.
parse-options: make some arguments optional, add callbacks.
...

Conflicts:

Makefile
builtin-add.c

79 files changed:
.gitignore
Documentation/RelNotes-1.5.3.5.txt
Documentation/cmd-list.perl
Documentation/core-tutorial.txt
Documentation/diff-format.txt
Documentation/git-bisect.txt
Documentation/git-clone.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-diff.txt
Documentation/git-filter-branch.txt
Documentation/git-format-patch.txt
Documentation/git-merge.txt
Documentation/git-mergetool.txt
Documentation/git-rev-list.txt
Documentation/git-svnimport.txt [deleted file]
Documentation/git.txt
Makefile
builtin-add.c
builtin-archive.c
builtin-fetch-pack.c
builtin-pack-objects.c
builtin-prune-packed.c
builtin-rerere.c
builtin-rev-list.c
builtin-revert.c
builtin-unpack-objects.c
builtin.h
cache.h
color.c
color.h
commit.h
connect.c
contrib/completion/git-completion.bash
contrib/examples/git-svnimport.perl [new file with mode: 0755]
contrib/examples/git-svnimport.txt [new file with mode: 0644]
convert.c
csum-file.c
csum-file.h
diff.c
diffcore-rename.c
diffcore.h
exec_cmd.c
exec_cmd.h
git-bisect.sh
git-cvsexportcommit.perl
git-mergetool.sh
git-rebase--interactive.sh
git-rebase.sh
git-repack.sh
git-sh-setup.sh
git-svnimport.perl [deleted file]
git.c
gitweb/INSTALL
gitweb/gitweb.perl
hash.c [new file with mode: 0644]
hash.h [new file with mode: 0644]
help.c
index-pack.c
log-tree.c
log-tree.h
pack-write.c
pack.h
peek-remote.c
progress.c
progress.h
run-command.c
run-command.h
send-pack.c
shell.c
sideband.c
t/t0021-conversion.sh
t/t5516-fetch-push.sh
t/t6030-bisect-porcelain.sh
t/test-lib.sh
transport.c
unpack-trees.c
upload-pack.c
wt-status.c
wt-status.h
index 249b451b87cfddc98e9c32f94c7d14ba91c7e1ad..c8c13f550317e12ccb5d3515fbc942463c8be4e0 100644 (file)
@@ -128,7 +128,6 @@ git-status
 git-stripspace
 git-submodule
 git-svn
-git-svnimport
 git-symbolic-ref
 git-tag
 git-tar-tree
index e28d92f61811f5ba3d31d71e299c209a3a6ea74b..4e46d2c2a27cecee0d87461ad08b31cea5dfec2e 100644 (file)
@@ -87,8 +87,8 @@ Fixes since v1.5.3.4
  * A few workarounds to squelch false warnings from recent gcc have
    been added.
 
---
-exec >/var/tmp/1
-O=v1.5.3.4-55-gf120ae2
-echo O=`git describe refs/heads/maint`
-git shortlog --no-merges $O..refs/heads/maint
+ * "git-send-pack $remote frotz" segfaulted when there is nothing
+   named 'frotz' on the local end.
+
+ * "git-rebase -interactive" did not handle its "--strategy" option
+   properly.
index 1061fd8bcdf44964af2b6c55d4c9cb3c0c21f968..8d21d423e5ef777d19369d6d1988f10cb8922fb8 100755 (executable)
@@ -185,7 +185,6 @@ sub format_one {
 git-stripspace                          purehelpers
 git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
-git-svnimport                           foreignscminterface
 git-symbolic-ref                        plumbingmanipulators
 git-tag                                 mainporcelain
 git-tar-tree                            plumbinginterrogators
index d8e78ac8f15013a5db9c8918723d3ee2850fe43e..5df97a1f9d5633657e4aad05a39acbf0d09e4a58 100644 (file)
@@ -878,7 +878,7 @@ script called `git merge`, which wants to know which branches you want
 to resolve and what the merge is all about:
 
 ------------
-$ git merge "Merge work in mybranch" HEAD mybranch
+$ git merge -m "Merge work in mybranch" mybranch
 ------------
 
 where the first argument is going to be used as the commit message if
@@ -965,7 +965,7 @@ to the `master` branch. Let's go back to `mybranch`, and run
 
 ------------
 $ git checkout mybranch
-$ git merge "Merge upstream changes." HEAD master
+$ git merge -m "Merge upstream changes." master
 ------------
 
 This outputs something like this (the actual commit object names
@@ -1607,8 +1607,8 @@ in both of them.  You could merge in 'diff-fix' first and then
 'commit-fix' next, like this:
 
 ------------
-$ git merge 'Merge fix in diff-fix' master diff-fix
-$ git merge 'Merge fix in commit-fix' master commit-fix
+$ git merge -m 'Merge fix in diff-fix' diff-fix
+$ git merge -m 'Merge fix in commit-fix' commit-fix
 ------------
 
 Which would result in:
index 001503205b24d5c20ec10792c4ab6c4c7221bcb7..9709c35c98bc678d1f2e339c8e2d4bbcd7e6231f 100644 (file)
@@ -1,5 +1,5 @@
-The output format from "git-diff-index", "git-diff-tree" and
-"git-diff-files" are very similar.
+The output format from "git-diff-index", "git-diff-tree",
+"git-diff-files" and "git diff --raw" are very similar.
 
 These commands all compare two sets of things; what is
 compared differs:
@@ -62,7 +62,8 @@ respectively.
 diff format for merges
 ----------------------
 
-"git-diff-tree" and "git-diff-files" can take '-c' or '--cc' option
+"git-diff-tree", "git-diff-files" and "git-diff --raw"
+can take '-c' or '--cc' option
 to generate diff output also for merge commits.  The output differs
 from the format described above in the following way:
 
@@ -86,10 +87,10 @@ 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.  You can customize the creation
-of such patches via the GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS
-environment variables.
+with a '-p' option, or "git diff" without the '--raw' option, they
+do not produce the output described above; 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.
 
 What the -p option produces is slightly different from the traditional
 diff format.
@@ -137,8 +138,8 @@ file made it into the new one.
 combined diff format
 --------------------
 
-git-diff-tree and git-diff-files can take '-c' or '--cc' option
-to produce 'combined diff', which looks like this:
+"git-diff-tree", "git-diff-files" and "git-diff" can take '-c' or
+'--cc' option to produce 'combined diff', which looks like this:
 
 ------------
 diff --combined describe.c
index 1072fb87d1fe38a74dc38e2d6886acfb44d9262b..4795349c10fd91c330ddde2d8397401be09cc86b 100644 (file)
@@ -16,8 +16,9 @@ The command takes various subcommands, and different options depending
 on the subcommand:
 
  git bisect start [<bad> [<good>...]] [--] [<paths>...]
- git bisect bad <rev>
- git bisect good <rev>
+ git bisect bad [<rev>]
+ git bisect good [<rev>...]
+ git bisect skip [<rev>...]
  git bisect reset [<branch>]
  git bisect visualize
  git bisect replay <logfile>
@@ -134,6 +135,20 @@ $ git reset --hard HEAD~3          # try 3 revs before what
 Then compile and test the one you chose to try. After that, tell
 bisect what the result was as usual.
 
+Bisect skip
+~~~~~~~~~~~~
+
+Instead of choosing by yourself a nearby commit, you may just want git
+to do it for you using:
+
+------------
+$ git bisect skip                 # Current version cannot be tested
+------------
+
+But computing the commit to test may be slower afterwards and git may
+eventually not be able to tell the first bad among a bad and one or
+more "skip"ped commits.
+
 Cutting down bisection by giving more parameters to bisect start
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -167,14 +182,18 @@ $ git bisect run my_script
 ------------
 
 Note that the "run" script (`my_script` in the above example) should
-exit with code 0 in case the current source code is good and with a
-code between 1 and 127 (included) in case the current source code is
-bad.
+exit with code 0 in case the current source code is good.  Exit with a
+code between 1 and 127 (inclusive), except 125, if the current
+source code is bad.
 
 Any other exit code will abort the automatic bisect process. (A
 program that does "exit(-1)" leaves $? = 255, see exit(3) manual page,
 the value is chopped with "& 0377".)
 
+The special exit code 125 should be used when the current source code
+cannot be tested. If the "run" script exits with this code, the current
+revision will be skipped, see `git bisect skip` above.
+
 You may often find that during bisect you want to have near-constant
 tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or
 "revision that does not have this commit needs this patch applied to
index 253f4f03c532eb34912f1b5f570f3f6359692fb9..cca14d6b5df042fcf48889e5ff00285ffc4c3009 100644 (file)
@@ -111,11 +111,11 @@ OPTIONS
 
 --depth <depth>::
        Create a 'shallow' clone with a history truncated to the
-       specified number of revs.  A shallow repository has
+       specified number of revisions.  A shallow repository has a
        number of limitations (you cannot clone or fetch from
        it, nor push from nor into it), but is adequate if you
-       want to only look at near the tip of a large project
-       with a long history, and would want to send in fixes
+       are only interested in the recent history of a large project
+       with a long history, and would want to send in fixes
        as patches.
 
 <repository>::
index 4c8d1e638632bc67dcd74536dd6dd287b2604111..c3922f9238cf9f33d404665714fb0458122b6e43 100644 (file)
@@ -73,7 +73,7 @@ Merge one patch into CVS::
 $ export GIT_DIR=~/project/.git
 $ cd ~/project_cvs_checkout
 $ git-cvsexportcommit -v <commit-sha1>
-$ cvs commit -F .mgs <files>
+$ cvs commit -F .msg <files>
 ------------
 
 Merge pending patches into CVS automatically -- only if you really know what you are doing::
index ce0f5024687056b696fe5e77362a6f01b18dd0bd..11c4216c4a5c705c6af55b1367cae96b13c20251 100644 (file)
@@ -82,6 +82,9 @@ include::diff-options.txt[]
        the diff to the named paths (you can give directory
        names and get diff for all files under them).
 
+Output format
+-------------
+include::diff-format.txt[]
 
 EXAMPLES
 --------
index ba9b4fbca79321003f8cda6341d066f9ccc00349..385ecc900f2d7f4c982a96f1a97607334682e074 100644 (file)
@@ -219,7 +219,7 @@ git filter-branch --commit-filter '
        fi' HEAD
 ------------------------------------------------------------------------------
 
-The function 'skip_commits' is defined as follows:
+The function 'skip_commit' is defined as follows:
 
 --------------------------
 skip_commit()
index c9857a2d62e55edaf7e71c1a89ae09c8ae2423d2..f0617efa0ab5d6c046e2b880a710ab852f1afd6a 100644 (file)
@@ -168,7 +168,7 @@ git-format-patch origin::
        is created in the current directory.
 
 git-format-patch \--root origin::
-       Extract all commits which that leads to 'origin' since the
+       Extract all commits that lead to 'origin' since the
        inception of the project.
 
 git-format-patch -M -B origin::
index bca4212e565c95f79a76a14cc4444e72e472a22c..eabd7ef33f6c01322fc3794341c8982573b516a4 100644 (file)
@@ -11,26 +11,27 @@ SYNOPSIS
 [verse]
 'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]...
        [-m <msg>] <remote> <remote>...
+'git-merge' <msg> HEAD <remote>...
 
 DESCRIPTION
 -----------
 This is the top-level interface to the merge machinery
 which drives multiple merge strategy scripts.
 
+The second syntax (<msg> `HEAD` <remote>) is supported for
+historical reasons.  Do not use it from the command line or in
+new scripts.  It is the same as `git merge -m <msg> <remote>`.
+
 
 OPTIONS
 -------
 include::merge-options.txt[]
 
-<msg>::
+-m <msg>::
        The commit message to be used for the merge commit (in case
        it is created). The `git-fmt-merge-msg` script can be used
        to give a good default for automated `git-merge` invocations.
 
-<head>::
-       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
        least one <remote>.  Specifying more than one <remote>
index 6c32c6d18ead2047ce590e2853bbc1a5a2dd1e7c..a26c260162cf6cbab922dfd6320b1d440d7090ca 100644 (file)
@@ -25,12 +25,18 @@ OPTIONS
 -t or --tool=<tool>::
        Use the merge resolution program specified by <tool>.
        Valid merge tools are:
-       kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, and opendiff
+       kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff
 +
 If a merge resolution program is not specified, 'git mergetool'
 will use the configuration variable merge.tool.  If the
 configuration variable merge.tool is not set, 'git mergetool'
 will pick a suitable default.
++
+You can explicitly provide a full path to the tool by setting the
+configuration variable mergetool.<tool>.path. For example, you
+can configure the absolute path to kdiff3 by setting
+mergetool.kdiff3.path. Otherwise, 'git mergetool' assumes the tool
+is available in PATH.
 
 Author
 ------
index 7cd0e8913e8ec08b7e125676384e973b585c306b..485280423e90bac43b4c372bbc8d350c34cf4fa8 100644 (file)
@@ -34,6 +34,7 @@ SYNOPSIS
             [ \--pretty | \--header ]
             [ \--bisect ]
             [ \--bisect-vars ]
+            [ \--bisect-all ]
             [ \--merge ]
             [ \--reverse ]
             [ \--walk-reflogs ]
@@ -354,6 +355,21 @@ the expected number of commits to be tested if `bisect_rev`
 turns out to be bad to `bisect_bad`, and the number of commits
 we are bisecting right now to `bisect_all`.
 
+--bisect-all::
+
+This outputs all the commit objects between the included and excluded
+commits, ordered by their distance to the included and excluded
+commits. The farthest from them is displayed first. (This is the only
+one displayed by `--bisect`.)
+
+This is useful because it makes it easy to choose a good commit to
+test when you want to avoid to test some of them for some reason (they
+may not compile for example).
+
+This option can be used along with `--bisect-vars`, in this case,
+after all the sorted commit objects, there will be the same text as if
+`--bisect-vars` had been used alone.
+
 --
 
 Commit Ordering
diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt
deleted file mode 100644 (file)
index 71aad8b..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-git-svnimport(1)
-================
-v0.1, July 2005
-
-NAME
-----
-git-svnimport - Import a SVN repository into git
-
-
-SYNOPSIS
---------
-[verse]
-'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
-               [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
-               [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
-               [ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
-               [ -I <ignorefile_name> ] [ -A <author_file> ]
-               [ -R <repack_each_revs>] [ -P <path_from_trunk> ]
-               <SVN_repository_URL> [ <path> ]
-
-
-DESCRIPTION
------------
-Imports a SVN repository into git. It will either create a new
-repository, or incrementally import into an existing one.
-
-SVN access is done by the SVN::Perl module.
-
-git-svnimport assumes that SVN repositories are organized into one
-"trunk" directory where the main development happens, "branches/FOO"
-directories for branches, and "/tags/FOO" directories for tags.
-Other subdirectories are ignored.
-
-git-svnimport creates a file ".git/svn2git", which is required for
-incremental SVN imports.
-
-OPTIONS
--------
--C <target-dir>::
-        The GIT repository to import to.  If the directory doesn't
-        exist, it will be created.  Default is the current directory.
-
--s <start_rev>::
-        Start importing at this SVN change number. The  default is 1.
-+
-When importing incrementally, you might need to edit the .git/svn2git file.
-
--i::
-       Import-only: don't perform a checkout after importing.  This option
-       ensures the working directory and index remain untouched and will
-       not create them if they do not exist.
-
--T <trunk_subdir>::
-       Name the SVN trunk. Default "trunk".
-
--t <tag_subdir>::
-       Name the SVN subdirectory for tags. Default "tags".
-
--b <branch_subdir>::
-       Name the SVN subdirectory for branches. Default "branches".
-
--o <branch-for-HEAD>::
-       The 'trunk' branch from SVN is imported to the 'origin' branch within
-       the git repository. Use this option if you want to import into a
-       different branch.
-
--r::
-       Prepend 'rX: ' to commit messages, where X is the imported
-       subversion revision.
-
--u::
-       Replace underscores in tag names with periods.
-
--I <ignorefile_name>::
-       Import the svn:ignore directory property to files with this
-       name in each directory. (The Subversion and GIT ignore
-       syntaxes are similar enough that using the Subversion patterns
-       directly with "-I .gitignore" will almost always just work.)
-
--A <author_file>::
-       Read a file with lines on the form
-+
-------
-       username = User's Full Name <email@addr.es>
-
-------
-+
-and use "User's Full Name <email@addr.es>" as the GIT
-author and committer for Subversion commits made by
-"username". If encountering a commit made by a user not in the
-list, abort.
-+
-For convenience, this data is saved to $GIT_DIR/svn-authors
-each time the -A option is provided, and read from that same
-file each time git-svnimport is run with an existing GIT
-repository without -A.
-
--m::
-       Attempt to detect merges based on the commit message. This option
-       will enable default regexes that try to capture the name source
-       branch name from the commit message.
-
--M <regex>::
-       Attempt to detect merges based on the commit message with a custom
-       regex. It can be used with -m to also see the default regexes.
-       You must escape forward slashes.
-
--l <max_rev>::
-       Specify a maximum revision number to pull.
-+
-Formerly, this option controlled how many revisions to pull,
-due to SVN memory leaks. (These have been worked around.)
-
--R <repack_each_revs>::
-       Specify how often git repository should be repacked.
-+
-The default value is 1000. git-svnimport will do import in chunks of 1000
-revisions, after each chunk git repository will be repacked. To disable
-this behavior specify some big value here which is mote than number of
-revisions to import.
-
--P <path_from_trunk>::
-       Partial import of the SVN tree.
-+
-By default, the whole tree on the SVN trunk (/trunk) is imported.
-'-P my/proj' will import starting only from '/trunk/my/proj'.
-This option is useful when you want to import one project from a
-svn repo which hosts multiple projects under the same trunk.
-
--v::
-       Verbosity: let 'svnimport' report what it is doing.
-
--d::
-       Use direct HTTP requests if possible. The "<path>" argument is used
-       only for retrieving the SVN logs; the path to the contents is
-       included in the SVN log.
-
--D::
-       Use direct HTTP requests if possible. The "<path>" argument is used
-       for retrieving the logs, as well as for the contents.
-+
-There's no safe way to automatically find out which of these options to
-use, so you need to try both. Usually, the one that's wrong will die
-with a 40x error pretty quickly.
-
-<SVN_repository_URL>::
-       The URL of the SVN module you want to import. For local
-       repositories, use "file:///absolute/path".
-+
-If you're using the "-d" or "-D" option, this is the URL of the SVN
-repository itself; it usually ends in "/svn".
-
-<path>::
-       The path to the module you want to check out.
-
--h::
-       Print a short usage message and exit.
-
-OUTPUT
-------
-If '-v' is specified, the script reports what it is doing.
-
-Otherwise, success is indicated the Unix way, i.e. by simply exiting with
-a zero exit status.
-
-Author
-------
-Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
-various participants of the git-list <git@vger.kernel.org>.
-
-Based on a cvs2git script by the same author.
-
-Documentation
---------------
-Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
-
-GIT
----
-Part of the gitlink:git[7] suite
index c4d87ac201e2d7a9f43a0d288af15578f51e3785..6db7ae1ea7073ecef07430f13d6bf88758c922ff 100644 (file)
@@ -46,6 +46,7 @@ Documentation for older releases are available here:
 * link:v1.5.3/git.html[documentation for release 1.5.3]
 
 * release notes for
+  link:RelNotes-1.5.3.5.txt[1.5.3.5],
   link:RelNotes-1.5.3.4.txt[1.5.3.4],
   link:RelNotes-1.5.3.3.txt[1.5.3.3],
   link:RelNotes-1.5.3.2.txt[1.5.3.2],
index 3c9af55f441474e0621e31f7dc4566e49fa4c9a7..042f79ef8f9e5863605e7ef19136b39b1487a07a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -225,8 +225,7 @@ SCRIPT_SH = \
 SCRIPT_PERL = \
        git-add--interactive.perl \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-cvsserver.perl git-remote.perl \
-       git-svnimport.perl git-cvsexportcommit.perl \
+       git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
@@ -290,7 +289,7 @@ LIB_H = \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
        tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
        utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
-       mailmap.h remote.h parse-options.h transport.h
+       mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -300,7 +299,7 @@ DIFF_OBJS = \
 LIB_OBJS = \
        blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o \
-       interpolate.o \
+       interpolate.o hash.o \
        lockfile.o \
        patch-ids.o \
        object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \
@@ -917,7 +916,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
 
 $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
 $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
-$(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
index f61681c3ae186e1a6f96c5f7d4d6b978d0de5ddb..45b14e8a61764e18d8b89ebfef550a820ffbf831 100644 (file)
@@ -111,7 +111,7 @@ static void update_callback(struct diff_queue_struct *q,
        }
 }
 
-static void update(int verbose, const char *prefix, const char **files)
+void add_files_to_cache(int verbose, const char *prefix, const char **files)
 {
        struct rev_info rev;
        init_revisions(&rev, prefix);
@@ -120,8 +120,6 @@ static void update(int verbose, const char *prefix, const char **files)
        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = update_callback;
        rev.diffopt.format_callback_data = &verbose;
-       if (read_cache() < 0)
-               die("index file corrupt");
        run_diff_files(&rev, 0);
 }
 
@@ -155,6 +153,13 @@ static int git_add_config(const char *var, const char *value)
        return git_default_config(var, value);
 }
 
+int interactive_add(void)
+{
+       const char *argv[2] = { "add--interactive", NULL };
+
+       return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
 static struct lock_file lock_file;
 
 static const char ignore_error[] =
@@ -183,12 +188,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, builtin_add_options,
                          builtin_add_usage, 0);
        if (add_interactive) {
-               const char *args[] = { "add--interactive", NULL };
-
                if (add_interactive != 1 || orig_argc != 2)
                        die("add --interactive does not take any parameters");
-               execv_git_cmd(args);
-               exit(1);
+               exit(interactive_add());
        }
 
        git_config(git_add_config);
@@ -196,7 +198,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        newfd = hold_locked_index(&lock_file, 1);
 
        if (take_worktree_changes) {
-               update(verbose, prefix, argv);
+               if (read_cache() < 0)
+                       die("index file corrupt");
+               add_files_to_cache(verbose, prefix, argv);
                goto finish;
        }
 
index 6f29c2f40a01b3c60eaa9ecfa1ca4d63fe90c8eb..14a1b3077cd7a5c4d69672bccfadc1354568dc4a 100644 (file)
@@ -30,7 +30,7 @@ static int run_remote_archiver(const char *remote, int argc,
 {
        char *url, buf[LARGE_PACKET_MAX];
        int fd[2], i, len, rv;
-       pid_t pid;
+       struct child_process *conn;
        const char *exec = "git-upload-archive";
        int exec_at = 0;
 
@@ -46,9 +46,7 @@ static int run_remote_archiver(const char *remote, int argc,
        }
 
        url = xstrdup(remote);
-       pid = git_connect(fd, url, exec, 0);
-       if (pid < 0)
-               return pid;
+       conn = git_connect(fd, url, exec, 0);
 
        for (i = 1; i < argc; i++) {
                if (i == exec_at)
@@ -76,7 +74,7 @@ static int run_remote_archiver(const char *remote, int argc,
        rv = recv_sideband("archive", fd[0], 1, 2);
        close(fd[0]);
        close(fd[1]);
-       rv |= finish_connect(pid);
+       rv |= finish_connect(conn);
 
        return !!rv;
 }
index 8753840a825d35249fa74ee80867f2dfd542708b..862652be92f65c50aaa900fd9983f8c54840d672 100644 (file)
@@ -7,6 +7,7 @@
 #include "pack.h"
 #include "sideband.h"
 #include "fetch-pack.h"
+#include "run-command.h"
 
 static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
@@ -457,53 +458,49 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
        return retval;
 }
 
-static pid_t setup_sideband(int fd[2], int xd[2])
+static int sideband_demux(int fd, void *data)
 {
-       pid_t side_pid;
+       int *xd = data;
 
+       close(xd[1]);
+       return recv_sideband("fetch-pack", xd[0], fd, 2);
+}
+
+static void setup_sideband(int fd[2], int xd[2], struct async *demux)
+{
        if (!use_sideband) {
                fd[0] = xd[0];
                fd[1] = xd[1];
-               return 0;
+               return;
        }
        /* xd[] is talking with upload-pack; subprocess reads from
         * xd[0], spits out band#2 to stderr, and feeds us band#1
-        * through our fd[0].
+        * through demux->out.
         */
-       if (pipe(fd) < 0)
-               die("fetch-pack: unable to set up pipe");
-       side_pid = fork();
-       if (side_pid < 0)
+       demux->proc = sideband_demux;
+       demux->data = xd;
+       if (start_async(demux))
                die("fetch-pack: unable to fork off sideband demultiplexer");
-       if (!side_pid) {
-               /* subprocess */
-               close(fd[0]);
-               if (xd[0] != xd[1])
-                       close(xd[1]);
-               if (recv_sideband("fetch-pack", xd[0], fd[1], 2))
-                       exit(1);
-               exit(0);
-       }
        close(xd[0]);
-       close(fd[1]);
+       fd[0] = demux->out;
        fd[1] = xd[1];
-       return side_pid;
 }
 
 static int get_pack(int xd[2], char **pack_lockfile)
 {
-       int status;
-       pid_t pid, side_pid;
+       struct async demux;
        int fd[2];
        const char *argv[20];
        char keep_arg[256];
        char hdr_arg[256];
        const char **av;
        int do_keep = args.keep_pack;
-       int keep_pipe[2];
+       struct child_process cmd;
 
-       side_pid = setup_sideband(fd, xd);
+       setup_sideband(fd, xd, &demux);
 
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.argv = argv;
        av = argv;
        *hdr_arg = 0;
        if (!args.keep_pack && unpack_limit) {
@@ -520,8 +517,8 @@ static int get_pack(int xd[2], char **pack_lockfile)
        }
 
        if (do_keep) {
-               if (pack_lockfile && pipe(keep_pipe))
-                       die("fetch-pack: pipe setup failure: %s", strerror(errno));
+               if (pack_lockfile)
+                       cmd.out = -1;
                *av++ = "index-pack";
                *av++ = "--stdin";
                if (!args.quiet && !args.no_progress)
@@ -545,43 +542,19 @@ static int get_pack(int xd[2], char **pack_lockfile)
                *av++ = hdr_arg;
        *av++ = NULL;
 
-       pid = fork();
-       if (pid < 0)
+       cmd.in = fd[0];
+       cmd.git_cmd = 1;
+       if (start_command(&cmd))
                die("fetch-pack: unable to fork off %s", argv[0]);
-       if (!pid) {
-               dup2(fd[0], 0);
-               if (do_keep && pack_lockfile) {
-                       dup2(keep_pipe[1], 1);
-                       close(keep_pipe[0]);
-                       close(keep_pipe[1]);
-               }
-               close(fd[0]);
-               close(fd[1]);
-               execv_git_cmd(argv);
-               die("%s exec failed", argv[0]);
-       }
-       close(fd[0]);
        close(fd[1]);
-       if (do_keep && pack_lockfile) {
-               close(keep_pipe[1]);
-               *pack_lockfile = index_pack_lockfile(keep_pipe[0]);
-               close(keep_pipe[0]);
-       }
-       while (waitpid(pid, &status, 0) < 0) {
-               if (errno != EINTR)
-                       die("waiting for %s: %s", argv[0], strerror(errno));
-       }
-       if (WIFEXITED(status)) {
-               int code = WEXITSTATUS(status);
-               if (code)
-                       die("%s died with error code %d", argv[0], code);
-               return 0;
-       }
-       if (WIFSIGNALED(status)) {
-               int sig = WTERMSIG(status);
-               die("%s died of signal %d", argv[0], sig);
-       }
-       die("%s died of unnatural causes %d", argv[0], status);
+       if (do_keep && pack_lockfile)
+               *pack_lockfile = index_pack_lockfile(cmd.out);
+
+       if (finish_command(&cmd))
+               die("%s failed", argv[0]);
+       if (use_sideband && finish_async(&demux))
+               die("error in sideband demultiplexer");
+       return 0;
 }
 
 static struct ref *do_fetch_pack(int fd[2],
@@ -763,7 +736,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
 {
        int i, ret;
        int fd[2];
-       pid_t pid;
+       struct child_process *conn;
        struct ref *ref;
        struct stat st;
 
@@ -774,16 +747,14 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
                        st.st_mtime = 0;
        }
 
-       pid = git_connect(fd, (char *)dest, args.uploadpack,
+       conn = git_connect(fd, (char *)dest, args.uploadpack,
                           args.verbose ? CONNECT_VERBOSE : 0);
-       if (pid < 0)
-               return NULL;
        if (heads && nr_heads)
                nr_heads = remove_duplicates(nr_heads, heads);
        ref = do_fetch_pack(fd, nr_heads, heads, pack_lockfile);
        close(fd[0]);
        close(fd[1]);
-       ret = finish_connect(pid);
+       ret = finish_connect(conn);
 
        if (!ret && nr_heads) {
                /* If the heads to pull were given, we should have
index 0be539ed7fd9bf95bb40515b560c7615ed318f37..25ec65d0f0e89f795329feae2a3eac04ca96a976 100644 (file)
@@ -65,8 +65,6 @@ static int no_reuse_delta, no_reuse_object, keep_unreachable;
 static int local;
 static int incremental;
 static int allow_ofs_delta;
-static const char *pack_tmp_name, *idx_tmp_name;
-static char tmpname[PATH_MAX];
 static const char *base_name;
 static int progress = 1;
 static int window = 10;
@@ -75,7 +73,7 @@ static int depth = 50;
 static int delta_search_threads = 1;
 static int pack_to_stdout;
 static int num_preferred_base;
-static struct progress progress_state;
+static struct progress *progress_state;
 static int pack_compression_level = Z_DEFAULT_COMPRESSION;
 static int pack_compression_seen;
 
@@ -587,12 +585,6 @@ static off_t write_one(struct sha1file *f,
        return offset + size;
 }
 
-static int open_object_dir_tmp(const char *path)
-{
-    snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path);
-    return xmkstemp(tmpname);
-}
-
 /* forward declaration for write_pack_file */
 static int adjust_perm(const char *path, mode_t mode);
 
@@ -606,16 +598,21 @@ static void write_pack_file(void)
        uint32_t nr_remaining = nr_result;
 
        if (do_progress)
-               start_progress(&progress_state, "Writing %u objects...", "", nr_result);
+               progress_state = start_progress("Writing objects", nr_result);
        written_list = xmalloc(nr_objects * sizeof(struct object_entry *));
 
        do {
                unsigned char sha1[20];
+               char *pack_tmp_name = NULL;
 
                if (pack_to_stdout) {
-                       f = sha1fd(1, "<stdout>");
+                       f = sha1fd_throughput(1, "<stdout>", progress_state);
                } else {
-                       int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
+                       char tmpname[PATH_MAX];
+                       int fd;
+                       snprintf(tmpname, sizeof(tmpname),
+                                "%s/tmp_pack_XXXXXX", get_object_directory());
+                       fd = xmkstemp(tmpname);
                        pack_tmp_name = xstrdup(tmpname);
                        f = sha1fd(fd, pack_tmp_name);
                }
@@ -632,8 +629,7 @@ static void write_pack_file(void)
                        if (!offset_one)
                                break;
                        offset = offset_one;
-                       if (do_progress)
-                               display_progress(&progress_state, written);
+                       display_progress(progress_state, written);
                }
 
                /*
@@ -643,19 +639,21 @@ static void write_pack_file(void)
                if (pack_to_stdout || nr_written == nr_remaining) {
                        sha1close(f, sha1, 1);
                } else {
-                       sha1close(f, sha1, 0);
-                       fixup_pack_header_footer(f->fd, sha1, pack_tmp_name, nr_written);
-                       close(f->fd);
+                       int fd = sha1close(f, NULL, 0);
+                       fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written);
+                       close(fd);
                }
 
                if (!pack_to_stdout) {
                        mode_t mode = umask(0);
+                       char *idx_tmp_name, tmpname[PATH_MAX];
 
                        umask(mode);
                        mode = 0444 & ~mode;
 
                        idx_tmp_name = write_idx_file(NULL,
-                               (struct pack_idx_entry **) written_list, nr_written, sha1);
+                                       (struct pack_idx_entry **) written_list,
+                                       nr_written, sha1);
                        snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
                                 base_name, sha1_to_hex(sha1));
                        if (adjust_perm(pack_tmp_name, mode))
@@ -672,6 +670,8 @@ static void write_pack_file(void)
                        if (rename(idx_tmp_name, tmpname))
                                die("unable to rename temporary index file: %s",
                                    strerror(errno));
+                       free(idx_tmp_name);
+                       free(pack_tmp_name);
                        puts(sha1_to_hex(sha1));
                }
 
@@ -683,8 +683,7 @@ static void write_pack_file(void)
        } while (nr_remaining && i < nr_objects);
 
        free(written_list);
-       if (do_progress)
-               stop_progress(&progress_state);
+       stop_progress(&progress_state);
        if (written != nr_result)
                die("wrote %u objects while expecting %u", written, nr_result);
        /*
@@ -852,8 +851,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
        else
                object_ix[-1 - ix] = nr_objects;
 
-       if (progress)
-               display_progress(&progress_state, nr_objects);
+       display_progress(progress_state, nr_objects);
 
        if (name && no_try_delta(name))
                entry->no_try_delta = 1;
@@ -1516,8 +1514,7 @@ static void find_deltas(struct object_entry **list, unsigned list_size,
 
                progress_lock();
                (*processed)++;
-               if (progress)
-                       display_progress(&progress_state, *processed);
+               display_progress(progress_state, *processed);
                progress_unlock();
 
                /*
@@ -1714,16 +1711,14 @@ static void prepare_pack(int window, int depth)
                delta_list[n++] = entry;
        }
 
-       if (nr_deltas) {
+       if (nr_deltas && n > 1) {
                unsigned nr_done = 0;
                if (progress)
-                       start_progress(&progress_state,
-                                      "Deltifying %u objects...", "",
-                                      nr_deltas);
+                       progress_state = start_progress("Compressing objects",
+                                                       nr_deltas);
                qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
                ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
-               if (progress)
-                       stop_progress(&progress_state);
+               stop_progress(&progress_state);
                if (nr_done != nr_deltas)
                        die("inconsistency with delta count");
        }
@@ -2135,23 +2130,17 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        prepare_packed_git();
 
        if (progress)
-               start_progress(&progress_state, "Generating pack...",
-                              "Counting objects: ", 0);
+               progress_state = start_progress("Counting objects", 0);
        if (!use_internal_rev_list)
                read_object_list_from_stdin();
        else {
                rp_av[rp_ac] = NULL;
                get_object_list(rp_ac, rp_av);
        }
-       if (progress) {
-               stop_progress(&progress_state);
-               fprintf(stderr, "Done counting %u objects.\n", nr_objects);
-       }
+       stop_progress(&progress_state);
 
        if (non_empty && !nr_result)
                return 0;
-       if (progress && (nr_objects != nr_result))
-               fprintf(stderr, "Result has %u objects.\n", nr_result);
        if (nr_result)
                prepare_pack(window, depth);
        write_pack_file();
index 977730064b949c983163e972ef85a63d1fe4c914..23faf3129fb65ec5592c5021d661498143d4f608 100644 (file)
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "progress.h"
 
 static const char prune_packed_usage[] =
 "git-prune-packed [-n] [-q]";
@@ -7,6 +8,8 @@ static const char prune_packed_usage[] =
 #define DRY_RUN 01
 #define VERBOSE 02
 
+static struct progress *progress;
+
 static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
 {
        struct dirent *de;
@@ -27,6 +30,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
                        printf("rm -f %s\n", pathname);
                else if (unlink(pathname) < 0)
                        error("unable to unlink %s", pathname);
+               display_progress(progress, i + 1);
        }
        pathname[len] = 0;
        rmdir(pathname);
@@ -39,6 +43,10 @@ void prune_packed_objects(int opts)
        const char *dir = get_object_directory();
        int len = strlen(dir);
 
+       if (opts == VERBOSE)
+               progress = start_progress_delay("Removing duplicate objects",
+                       256, 95, 2);
+
        if (len > PATH_MAX - 42)
                die("impossible object directory");
        memcpy(pathname, dir, len);
@@ -49,16 +57,12 @@ void prune_packed_objects(int opts)
 
                sprintf(pathname + len, "%02x/", i);
                d = opendir(pathname);
-               if (opts == VERBOSE && (d || i == 255))
-                       fprintf(stderr, "Removing unused objects %d%%...\015",
-                               ((i+1) * 100) / 256);
                if (!d)
                        continue;
                prune_dir(i, d, pathname, len + 3, opts);
                closedir(d);
        }
-       if (opts == VERBOSE)
-               fprintf(stderr, "\nDone.\n");
+       stop_progress(&progress);
 }
 
 int cmd_prune_packed(int argc, const char **argv, const char *prefix)
index b8206744c12307b3efbdebc99e5bcdd4a8632212..74493237c9ca129cd7398963ace747a04a6bf6fd 100644 (file)
@@ -389,18 +389,39 @@ static int is_rerere_enabled(void)
        return 1;
 }
 
-int cmd_rerere(int argc, const char **argv, const char *prefix)
+static int setup_rerere(struct path_list *merge_rr)
 {
-       struct path_list merge_rr = { NULL, 0, 0, 1 };
-       int i, fd = -1;
+       int fd;
 
        git_config(git_rerere_config);
        if (!is_rerere_enabled())
-               return 0;
+               return -1;
 
        merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
        fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
-       read_rr(&merge_rr);
+       read_rr(merge_rr);
+       return fd;
+}
+
+int rerere(void)
+{
+       struct path_list merge_rr = { NULL, 0, 0, 1 };
+       int fd;
+
+       fd = setup_rerere(&merge_rr);
+       if (fd < 0)
+               return 0;
+       return do_plain_rerere(&merge_rr, fd);
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+       struct path_list merge_rr = { NULL, 0, 0, 1 };
+       int i, fd;
+
+       fd = setup_rerere(&merge_rr);
+       if (fd < 0)
+               return 0;
 
        if (argc < 2)
                return do_plain_rerere(&merge_rr, fd);
index 33726b8d8426b4878ed63184dc280957ba4434e0..44393320e8ec27a35ccc1da550852ce3333a25a8 100644 (file)
@@ -9,6 +9,7 @@
 #include "revision.h"
 #include "list-objects.h"
 #include "builtin.h"
+#include "log-tree.h"
 
 /* bits #0-15 in revision.h */
 
@@ -38,7 +39,8 @@ static const char rev_list_usage[] =
 "    --left-right\n"
 "  special purpose:\n"
 "    --bisect\n"
-"    --bisect-vars"
+"    --bisect-vars\n"
+"    --bisect-all"
 ;
 
 static struct rev_info revs;
@@ -74,6 +76,7 @@ static void show_commit(struct commit *commit)
                        parents = parents->next;
                }
        }
+       show_decorations(commit);
        if (revs.commit_format == CMIT_FMT_ONELINE)
                putchar(' ');
        else
@@ -278,6 +281,57 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
        return best;
 }
 
+struct commit_dist {
+       struct commit *commit;
+       int distance;
+};
+
+static int compare_commit_dist(const void *a_, const void *b_)
+{
+       struct commit_dist *a, *b;
+
+       a = (struct commit_dist *)a_;
+       b = (struct commit_dist *)b_;
+       if (a->distance != b->distance)
+               return b->distance - a->distance; /* desc sort */
+       return hashcmp(a->commit->object.sha1, b->commit->object.sha1);
+}
+
+static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
+{
+       struct commit_list *p;
+       struct commit_dist *array = xcalloc(nr, sizeof(*array));
+       int cnt, i;
+
+       for (p = list, cnt = 0; p; p = p->next) {
+               int distance;
+               unsigned flags = p->item->object.flags;
+
+               if (revs.prune_fn && !(flags & TREECHANGE))
+                       continue;
+               distance = weight(p);
+               if (nr - distance < distance)
+                       distance = nr - distance;
+               array[cnt].commit = p->item;
+               array[cnt].distance = distance;
+               cnt++;
+       }
+       qsort(array, cnt, sizeof(*array), compare_commit_dist);
+       for (p = list, i = 0; i < cnt; i++) {
+               struct name_decoration *r = xmalloc(sizeof(*r) + 100);
+               struct object *obj = &(array[i].commit->object);
+
+               sprintf(r->name, "dist=%d", array[i].distance);
+               r->next = add_decoration(&name_decoration, obj, r);
+               p->item = array[i].commit;
+               p = p->next;
+       }
+       if (p)
+               p->next = NULL;
+       free(array);
+       return list;
+}
+
 /*
  * zero or positive weight is the number of interesting commits it can
  * reach, including itself.  Especially, weight = 0 means it does not
@@ -292,7 +346,8 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
  * or positive distance.
  */
 static struct commit_list *do_find_bisection(struct commit_list *list,
-                                            int nr, int *weights)
+                                            int nr, int *weights,
+                                            int find_all)
 {
        int n, counted;
        struct commit_list *p;
@@ -351,7 +406,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
                clear_distance(list);
 
                /* Does it happen to be at exactly half-way? */
-               if (halfway(p, nr))
+               if (!find_all && halfway(p, nr))
                        return p;
                counted++;
        }
@@ -389,19 +444,22 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
                                weight_set(p, weight(q));
 
                        /* Does it happen to be at exactly half-way? */
-                       if (halfway(p, nr))
+                       if (!find_all && halfway(p, nr))
                                return p;
                }
        }
 
        show_list("bisection 2 counted all", counted, nr, list);
 
-       /* Then find the best one */
-       return best_bisection(list, nr);
+       if (!find_all)
+               return best_bisection(list, nr);
+       else
+               return best_bisection_sorted(list, nr);
 }
 
 static struct commit_list *find_bisection(struct commit_list *list,
-                                         int *reaches, int *all)
+                                         int *reaches, int *all,
+                                         int find_all)
 {
        int nr, on_list;
        struct commit_list *p, *best, *next, *last;
@@ -434,14 +492,13 @@ static struct commit_list *find_bisection(struct commit_list *list,
        weights = xcalloc(on_list, sizeof(*weights));
 
        /* Do the real work of finding bisection commit. */
-       best = do_find_bisection(list, nr, weights);
-
+       best = do_find_bisection(list, nr, weights, find_all);
        if (best) {
-               best->next = NULL;
+               if (!find_all)
+                       best->next = NULL;
                *reaches = weight(best);
        }
        free(weights);
-
        return best;
 }
 
@@ -468,6 +525,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        int i;
        int read_from_stdin = 0;
        int bisect_show_vars = 0;
+       int bisect_find_all = 0;
 
        git_config(git_default_config);
        init_revisions(&revs, prefix);
@@ -490,6 +548,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        bisect_list = 1;
                        continue;
                }
+               if (!strcmp(arg, "--bisect-all")) {
+                       bisect_list = 1;
+                       bisect_find_all = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--bisect-vars")) {
                        bisect_list = 1;
                        bisect_show_vars = 1;
@@ -536,9 +599,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        if (bisect_list) {
                int reaches = reaches, all = all;
 
-               revs.commits = find_bisection(revs.commits, &reaches, &all);
+               revs.commits = find_bisection(revs.commits, &reaches, &all,
+                                             bisect_find_all);
                if (bisect_show_vars) {
                        int cnt;
+                       char hex[41];
                        if (!revs.commits)
                                return 1;
                        /*
@@ -550,15 +615,22 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                         * A bisect set of size N has (N-1) commits further
                         * to test, as we already know one bad one.
                         */
-                       cnt = all-reaches;
+                       cnt = all - reaches;
                        if (cnt < reaches)
                                cnt = reaches;
+                       strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1));
+
+                       if (bisect_find_all) {
+                               traverse_commit_list(&revs, show_commit, show_object);
+                               printf("------\n");
+                       }
+
                        printf("bisect_rev=%s\n"
                               "bisect_nr=%d\n"
                               "bisect_good=%d\n"
                               "bisect_bad=%d\n"
                               "bisect_all=%d\n",
-                              sha1_to_hex(revs.commits->item->object.sha1),
+                              hex,
                               cnt - 1,
                               all - reaches - 1,
                               reaches - 1,
index 2ea766b5bfc1375a2ff03664ffb55fa558b11da0..a9347cf9c57b2e4d15d37da609a2851e5b1cfc8e 100644 (file)
@@ -341,7 +341,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                        die ("Error wrapping up %s", defmsg);
                fprintf(stderr, "Automatic %s failed.  "
                        "After resolving the conflicts,\n"
-                       "mark the corrected paths with 'git-add <paths>'\n"
+                       "mark the corrected paths with 'git add <paths>' "
                        "and commit the result.\n", me);
                if (action == CHERRY_PICK) {
                        fprintf(stderr, "When commiting, use the option "
index a6ff62fd8c66f075550e01718acf56d90b44d4bb..1e51865c52231e80cfdbbb19c8b6fa86ee8855d2 100644 (file)
@@ -311,7 +311,7 @@ static void unpack_one(unsigned nr)
 static void unpack_all(void)
 {
        int i;
-       struct progress progress;
+       struct progress *progress = NULL;
        struct pack_header *hdr = fill(sizeof(struct pack_header));
        unsigned nr_objects = ntohl(hdr->hdr_entries);
 
@@ -322,15 +322,13 @@ static void unpack_all(void)
        use(sizeof(struct pack_header));
 
        if (!quiet)
-               start_progress(&progress, "Unpacking %u objects...", "", nr_objects);
+               progress = start_progress("Unpacking objects", nr_objects);
        obj_list = xmalloc(nr_objects * sizeof(*obj_list));
        for (i = 0; i < nr_objects; i++) {
                unpack_one(i);
-               if (!quiet)
-                       display_progress(&progress, i + 1);
+               display_progress(progress, i + 1);
        }
-       if (!quiet)
-               stop_progress(&progress);
+       stop_progress(&progress);
 
        if (delta_list)
                die("unresolved deltas left after unpacking");
index 65cc0fb34a952356e0c5fce3e2e4bea2f1804b02..9a6213af126212dc131133cc2e9bf8b8d7a34557 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -6,6 +6,7 @@
 extern const char git_version_string[];
 extern const char git_usage_string[];
 
+extern void list_common_cmds_help(void);
 extern void help_unknown_cmd(const char *cmd);
 extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
 extern void prune_packed_objects(int);
diff --git a/cache.h b/cache.h
index 27485d36c2f56b6832f96e68ca8cbc7ffad0b957..bfffa05dff149a948e46b04f6b15589e6f4be07e 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -503,8 +503,8 @@ struct ref {
 #define REF_TAGS       (1u << 2)
 
 #define CONNECT_VERBOSE       (1u << 0)
-extern pid_t git_connect(int fd[2], char *url, const char *prog, int flags);
-extern int finish_connect(pid_t pid);
+extern struct child_process *git_connect(int fd[2], char *url, const char *prog, int flags);
+extern int finish_connect(struct child_process *conn);
 extern int path_match(const char *path, int nr, char **match);
 extern int get_ack(int fd, unsigned char *result_sha1);
 extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
diff --git a/color.c b/color.c
index 09d82eec3d0adf5b7bd8828b0c8df0554695f75c..124ba331c7f798b9b922fc8482dbc215202b99a6 100644 (file)
--- a/color.c
+++ b/color.c
@@ -135,39 +135,39 @@ int git_config_colorbool(const char *var, const char *value)
        return git_config_bool(var, value);
 }
 
-static int color_vprintf(const char *color, const char *fmt,
+static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
                va_list args, const char *trail)
 {
        int r = 0;
 
        if (*color)
-               r += printf("%s", color);
-       r += vprintf(fmt, args);
+               r += fprintf(fp, "%s", color);
+       r += vfprintf(fp, fmt, args);
        if (*color)
-               r += printf("%s", COLOR_RESET);
+               r += fprintf(fp, "%s", COLOR_RESET);
        if (trail)
-               r += printf("%s", trail);
+               r += fprintf(fp, "%s", trail);
        return r;
 }
 
 
 
-int color_printf(const char *color, const char *fmt, ...)
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)
 {
        va_list args;
        int r;
        va_start(args, fmt);
-       r = color_vprintf(color, fmt, args, NULL);
+       r = color_vfprintf(fp, color, fmt, args, NULL);
        va_end(args);
        return r;
 }
 
-int color_printf_ln(const char *color, const char *fmt, ...)
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
 {
        va_list args;
        int r;
        va_start(args, fmt);
-       r = color_vprintf(color, fmt, args, "\n");
+       r = color_vfprintf(fp, color, fmt, args, "\n");
        va_end(args);
        return r;
 }
diff --git a/color.h b/color.h
index 88bb8ff1bd337d1281ee7f814d0529a7756f39e6..68098006ed057552370ec27359eb776e139bec5e 100644 (file)
--- a/color.h
+++ b/color.h
@@ -6,7 +6,7 @@
 
 int git_config_colorbool(const char *var, const char *value);
 void color_parse(const char *var, const char *value, char *dst);
-int color_printf(const char *color, const char *fmt, ...);
-int color_printf_ln(const char *color, const char *fmt, ...);
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
 
 #endif /* COLOR_H */
index b779de8cbca74220aeebbc1df194b5f7301f51d0..b661503972e54a6c0eb20295cf982f8f8b48f461 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -128,4 +128,9 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
                int depth, int shallow_flag, int not_shallow_flag);
 
 int in_merge_bases(struct commit *, struct commit **, int);
+
+extern int interactive_add(void);
+extern void add_files_to_cache(int verbose, const char *prefix, const char **files);
+extern int rerere(void);
+
 #endif /* COMMIT_H */
index 3d5c4ab7550d3665a4b24265c0c052e3c7e00231..44e423dafd5c5e6566124fd1bb05f3d419b19a40 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -468,24 +468,26 @@ char *get_port(char *host)
 }
 
 /*
- * This returns 0 if the transport protocol does not need fork(2),
- * or a process id if it does.  Once done, finish the connection
+ * This returns NULL if the transport protocol does not need fork(2), or a
+ * struct child_process object if it does.  Once done, finish the connection
  * with finish_connect() with the value returned from this function
- * (it is safe to call finish_connect() with 0 to support the former
+ * (it is safe to call finish_connect() with NULL to support the former
  * case).
  *
- * Does not return a negative value on error; it just dies.
+ * If it returns, the connect is successful; it just dies on errors.
  */
-pid_t git_connect(int fd[2], char *url, const char *prog, int flags)
+struct child_process *git_connect(int fd[2], char *url,
+                                 const char *prog, int flags)
 {
        char *host, *path = url;
        char *end;
        int c;
-       int pipefd[2][2];
-       pid_t pid;
+       struct child_process *conn;
        enum protocol protocol = PROTO_LOCAL;
        int free_path = 0;
        char *port = NULL;
+       const char **arg;
+       struct strbuf cmd;
 
        /* Without this we cannot rely on waitpid() to tell
         * what happened to our children.
@@ -568,74 +570,68 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags)
                free(target_host);
                if (free_path)
                        free(path);
-               return 0;
+               return NULL;
        }
 
-       if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
-               die("unable to create pipe pair for communication");
-       pid = fork();
-       if (pid < 0)
-               die("unable to fork");
-       if (!pid) {
-               struct strbuf cmd;
-
-               strbuf_init(&cmd, MAX_CMD_LEN);
-               strbuf_addstr(&cmd, prog);
-               strbuf_addch(&cmd, ' ');
-               sq_quote_buf(&cmd, path);
-               if (cmd.len >= MAX_CMD_LEN)
-                       die("command line too long");
-
-               dup2(pipefd[1][0], 0);
-               dup2(pipefd[0][1], 1);
-               close(pipefd[0][0]);
-               close(pipefd[0][1]);
-               close(pipefd[1][0]);
-               close(pipefd[1][1]);
-               if (protocol == PROTO_SSH) {
-                       const char *ssh, *ssh_basename;
-                       ssh = getenv("GIT_SSH");
-                       if (!ssh) ssh = "ssh";
-                       ssh_basename = strrchr(ssh, '/');
-                       if (!ssh_basename)
-                               ssh_basename = ssh;
-                       else
-                               ssh_basename++;
-
-                       if (!port)
-                               execlp(ssh, ssh_basename, host, cmd.buf, NULL);
-                       else
-                               execlp(ssh, ssh_basename, "-p", port, host,
-                                      cmd.buf, NULL);
-               }
-               else {
-                       unsetenv(ALTERNATE_DB_ENVIRONMENT);
-                       unsetenv(DB_ENVIRONMENT);
-                       unsetenv(GIT_DIR_ENVIRONMENT);
-                       unsetenv(GIT_WORK_TREE_ENVIRONMENT);
-                       unsetenv(GRAFT_ENVIRONMENT);
-                       unsetenv(INDEX_ENVIRONMENT);
-                       execlp("sh", "sh", "-c", cmd.buf, NULL);
+       conn = xcalloc(1, sizeof(*conn));
+
+       strbuf_init(&cmd, MAX_CMD_LEN);
+       strbuf_addstr(&cmd, prog);
+       strbuf_addch(&cmd, ' ');
+       sq_quote_buf(&cmd, path);
+       if (cmd.len >= MAX_CMD_LEN)
+               die("command line too long");
+
+       conn->in = conn->out = -1;
+       conn->argv = arg = xcalloc(6, sizeof(*arg));
+       if (protocol == PROTO_SSH) {
+               const char *ssh = getenv("GIT_SSH");
+               if (!ssh) ssh = "ssh";
+
+               *arg++ = ssh;
+               if (port) {
+                       *arg++ = "-p";
+                       *arg++ = port;
                }
-               die("exec failed");
+               *arg++ = host;
        }
-       fd[0] = pipefd[0][0];
-       fd[1] = pipefd[1][1];
-       close(pipefd[0][1]);
-       close(pipefd[1][0]);
+       else {
+               /* remove these from the environment */
+               const char *env[] = {
+                       ALTERNATE_DB_ENVIRONMENT,
+                       DB_ENVIRONMENT,
+                       GIT_DIR_ENVIRONMENT,
+                       GIT_WORK_TREE_ENVIRONMENT,
+                       GRAFT_ENVIRONMENT,
+                       INDEX_ENVIRONMENT,
+                       NULL
+               };
+               conn->env = env;
+               *arg++ = "sh";
+               *arg++ = "-c";
+       }
+       *arg++ = cmd.buf;
+       *arg = NULL;
+
+       if (start_command(conn))
+               die("unable to fork");
+
+       fd[0] = conn->out; /* read from child's stdout */
+       fd[1] = conn->in;  /* write to child's stdin */
+       strbuf_release(&cmd);
        if (free_path)
                free(path);
-       return pid;
+       return conn;
 }
 
-int finish_connect(pid_t pid)
+int finish_connect(struct child_process *conn)
 {
-       if (pid == 0)
+       int code;
+       if (!conn)
                return 0;
 
-       while (waitpid(pid, NULL, 0) < 0) {
-               if (errno != EINTR)
-                       return -1;
-       }
-       return 0;
+       code = finish_command(conn);
+       free(conn->argv);
+       free(conn);
+       return code;
 }
index e76093074035d4b9aa4d41e993b5c29e359dad53..599b2fc5711bf6022fdb635b08baac16068de7dd 100755 (executable)
@@ -346,7 +346,6 @@ __git_commands ()
                ssh-*)            : transport;;
                stripspace)       : plumbing;;
                svn)              : import export;;
-               svnimport)        : import;;
                symbolic-ref)     : plumbing;;
                tar-tree)         : deprecated;;
                unpack-file)      : plumbing;;
diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl
new file mode 100755 (executable)
index 0000000..ea8c1b2
--- /dev/null
@@ -0,0 +1,976 @@
+#!/usr/bin/perl -w
+
+# This tool is copyright (c) 2005, Matthias Urlichs.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to pull and analyze SVN changes.
+#
+# Checking out the files is done by a single long-running SVN connection.
+#
+# The head revision is on branch "origin" by default.
+# You can change that with the '-o' option.
+
+use strict;
+use warnings;
+use Getopt::Std;
+use File::Copy;
+use File::Spec;
+use File::Temp qw(tempfile);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use Time::Local;
+use IO::Pipe;
+use POSIX qw(strftime dup2);
+use IPC::Open2;
+use SVN::Core;
+use SVN::Ra;
+
+die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
+
+$SIG{'PIPE'}="IGNORE";
+$ENV{'TZ'}="UTC";
+
+our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
+    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,
+    $opt_P,$opt_R);
+
+sub usage() {
+       print STDERR <<END;
+Usage: ${\basename $0}     # fetch/update GIT from SVN
+       [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-R repack_each_revs]
+       [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
+       [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
+       [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL]
+END
+       exit(1);
+}
+
+getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:R:uv") or usage();
+usage if $opt_h;
+
+my $tag_name = $opt_t || "tags";
+my $trunk_name = defined $opt_T ? $opt_T : "trunk";
+my $branch_name = $opt_b || "branches";
+my $project_name = $opt_P || "";
+$project_name = "/" . $project_name if ($project_name);
+my $repack_after = $opt_R || 1000;
+my $root_pool = SVN::Pool->new_default;
+
+@ARGV == 1 or @ARGV == 2 or usage();
+
+$opt_o ||= "origin";
+$opt_s ||= 1;
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $svn_url = $ARGV[0];
+my $svn_dir = $ARGV[1];
+
+our @mergerx = ();
+if ($opt_m) {
+       my $branch_esc = quotemeta ($branch_name);
+       my $trunk_esc  = quotemeta ($trunk_name);
+       @mergerx =
+       (
+               qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+               qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+               qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i
+       );
+}
+if ($opt_M) {
+       unshift (@mergerx, qr/$opt_M/);
+}
+
+# Absolutize filename now, since we will have chdir'ed by the time we
+# get around to opening it.
+$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
+
+our %users = ();
+our $users_file = undef;
+sub read_users($) {
+       $users_file = File::Spec->rel2abs(@_);
+       die "Cannot open $users_file\n" unless -f $users_file;
+       open(my $authors,$users_file);
+       while(<$authors>) {
+               chomp;
+               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               (my $user,my $name,my $email) = ($1,$2,$3);
+               $users{$user} = [$name,$email];
+       }
+       close($authors);
+}
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package SVNconn;
+# Basic SVN connection.
+# We're only interested in connecting and downloading, so ...
+
+use File::Spec;
+use File::Temp qw(tempfile);
+use POSIX qw(strftime dup2);
+use Fcntl qw(SEEK_SET);
+
+sub new {
+       my($what,$repo) = @_;
+       $what=ref($what) if ref($what);
+
+       my $self = {};
+       $self->{'buffer'} = "";
+       bless($self,$what);
+
+       $repo =~ s#/+$##;
+       $self->{'fullrep'} = $repo;
+       $self->conn();
+
+       return $self;
+}
+
+sub conn {
+       my $self = shift;
+       my $repo = $self->{'fullrep'};
+       my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider,
+                         SVN::Client::get_ssl_server_trust_file_provider,
+                         SVN::Client::get_username_provider]);
+       my $s = SVN::Ra->new(url => $repo, auth => $auth, pool => $root_pool);
+       die "SVN connection to $repo: $!\n" unless defined $s;
+       $self->{'svn'} = $s;
+       $self->{'repo'} = $repo;
+       $self->{'maxrev'} = $s->get_latest_revnum();
+}
+
+sub file {
+       my($self,$path,$rev) = @_;
+
+       my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                   DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+       print "... $rev $path ...\n" if $opt_v;
+       my (undef, $properties);
+       $path =~ s#^/*##;
+       my $subpool = SVN::Pool::new_default_sub;
+       eval { (undef, $properties)
+                  = $self->{'svn'}->get_file($path,$rev,$fh); };
+       if($@) {
+               return undef if $@ =~ /Attempted to get checksum/;
+               die $@;
+       }
+       my $mode;
+       if (exists $properties->{'svn:executable'}) {
+               $mode = '100755';
+       } elsif (exists $properties->{'svn:special'}) {
+               my ($special_content, $filesize);
+               $filesize = tell $fh;
+               seek $fh, 0, SEEK_SET;
+               read $fh, $special_content, $filesize;
+               if ($special_content =~ s/^link //) {
+                       $mode = '120000';
+                       seek $fh, 0, SEEK_SET;
+                       truncate $fh, 0;
+                       print $fh $special_content;
+               } else {
+                       die "unexpected svn:special file encountered";
+               }
+       } else {
+               $mode = '100644';
+       }
+       close ($fh);
+
+       return ($name, $mode);
+}
+
+sub ignore {
+       my($self,$path,$rev) = @_;
+
+       print "... $rev $path ...\n" if $opt_v;
+       $path =~ s#^/*##;
+       my $subpool = SVN::Pool::new_default_sub;
+       my (undef,undef,$properties)
+           = $self->{'svn'}->get_dir($path,$rev,undef);
+       if (exists $properties->{'svn:ignore'}) {
+               my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                                          DIR => File::Spec->tmpdir(),
+                                          UNLINK => 1);
+               print $fh $properties->{'svn:ignore'};
+               close($fh);
+               return $name;
+       } else {
+               return undef;
+       }
+}
+
+sub dir_list {
+       my($self,$path,$rev) = @_;
+       $path =~ s#^/*##;
+       my $subpool = SVN::Pool::new_default_sub;
+       my ($dirents,undef,$properties)
+           = $self->{'svn'}->get_dir($path,$rev,undef);
+       return $dirents;
+}
+
+package main;
+use URI;
+
+our $svn = $svn_url;
+$svn .= "/$svn_dir" if defined $svn_dir;
+my $svn2 = SVNconn->new($svn);
+$svn = SVNconn->new($svn);
+
+my $lwp_ua;
+if($opt_d or $opt_D) {
+       $svn_url = URI->new($svn_url)->canonical;
+       if($opt_D) {
+               $svn_dir =~ s#/*$#/#;
+       } else {
+               $svn_dir = "";
+       }
+       if ($svn_url->scheme eq "http") {
+               use LWP::UserAgent;
+               $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []);
+       } else {
+               print STDERR "Warning: not HTTP; turning off direct file access\n";
+               $opt_d=0;
+       }
+}
+
+sub pdate($) {
+       my($d) = @_;
+       $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)#
+               or die "Unparseable date: $d\n";
+       my $y=$1; $y-=1900 if $y>1900;
+       return timegm($6||0,$5,$4,$3,$2-1,$y);
+}
+
+sub getwd() {
+       my $pwd = `pwd`;
+       chomp $pwd;
+       return $pwd;
+}
+
+
+sub get_headref($$) {
+    my $name    = shift;
+    my $git_dir = shift;
+    my $sha;
+
+    if (open(C,"$git_dir/refs/heads/$name")) {
+       chomp($sha = <C>);
+       close(C);
+       length($sha) == 40
+           or die "Cannot get head id for $name ($sha): $!\n";
+    }
+    return $sha;
+}
+
+
+-d $git_tree
+       or mkdir($git_tree,0777)
+       or die "Could not create $git_tree: $!";
+chdir($git_tree);
+
+my $orig_branch = "";
+my $forward_master = 0;
+my %branches;
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
+$ENV{"GIT_DIR"} = $git_dir;
+my $orig_git_index;
+$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
+my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
+                                   DIR => File::Spec->tmpdir());
+close ($git_ih);
+$ENV{GIT_INDEX_FILE} = $git_index;
+my $maxnum = 0;
+my $last_rev = "";
+my $last_branch;
+my $current_rev = $opt_s || 1;
+unless(-d $git_dir) {
+       system("git-init");
+       die "Cannot init the GIT db at $git_tree: $?\n" if $?;
+       system("git-read-tree");
+       die "Cannot init an empty tree: $?\n" if $?;
+
+       $last_branch = $opt_o;
+       $orig_branch = "";
+} else {
+       -f "$git_dir/refs/heads/$opt_o"
+               or die "Branch '$opt_o' does not exist.\n".
+                      "Either use the correct '-o branch' option,\n".
+                      "or import to a new repository.\n";
+
+       -f "$git_dir/svn2git"
+               or die "'$git_dir/svn2git' does not exist.\n".
+                      "You need that file for incremental imports.\n";
+       open(F, "git-symbolic-ref HEAD |") or
+               die "Cannot run git-symbolic-ref: $!\n";
+       chomp ($last_branch = <F>);
+       $last_branch = basename($last_branch);
+       close(F);
+       unless($last_branch) {
+               warn "Cannot read the last branch name: $! -- assuming 'master'\n";
+               $last_branch = "master";
+       }
+       $orig_branch = $last_branch;
+       $last_rev = get_headref($orig_branch, $git_dir);
+       if (-f "$git_dir/SVN2GIT_HEAD") {
+               die <<EOM;
+SVN2GIT_HEAD exists.
+Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD.
+You may need to run
+
+    git-read-tree -m -u SVN2GIT_HEAD HEAD
+EOM
+       }
+       system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD");
+
+       $forward_master =
+           $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
+           system('cmp', '-s', "$git_dir/refs/heads/master",
+                               "$git_dir/refs/heads/$opt_o") == 0;
+
+       # populate index
+       system('git-read-tree', $last_rev);
+       die "read-tree failed: $?\n" if $?;
+
+       # Get the last import timestamps
+       open my $B,"<", "$git_dir/svn2git";
+       while(<$B>) {
+               chomp;
+               my($num,$branch,$ref) = split;
+               $branches{$branch}{$num} = $ref;
+               $branches{$branch}{"LAST"} = $ref;
+               $current_rev = $num+1 if $current_rev <= $num;
+       }
+       close($B);
+}
+-d $git_dir
+       or die "Could not create git subdir ($git_dir).\n";
+
+my $default_authors = "$git_dir/svn-authors";
+if ($opt_A) {
+       read_users($opt_A);
+       copy($opt_A,$default_authors) or die "Copy failed: $!";
+} else {
+       read_users($default_authors) if -f $default_authors;
+}
+
+open BRANCHES,">>", "$git_dir/svn2git";
+
+sub node_kind($$) {
+       my ($svnpath, $revision) = @_;
+       $svnpath =~ s#^/*##;
+       my $subpool = SVN::Pool::new_default_sub;
+       my $kind = $svn->{'svn'}->check_path($svnpath,$revision);
+       return $kind;
+}
+
+sub get_file($$$) {
+       my($svnpath,$rev,$path) = @_;
+
+       # now get it
+       my ($name,$mode);
+       if($opt_d) {
+               my($req,$res);
+
+               # /svn/!svn/bc/2/django/trunk/django-docs/build.py
+               my $url=$svn_url->clone();
+               $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath");
+               print "... $path...\n" if $opt_v;
+               $req = HTTP::Request->new(GET => $url);
+               $res = $lwp_ua->request($req);
+               if ($res->is_success) {
+                       my $fh;
+                       ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                       DIR => File::Spec->tmpdir(), UNLINK => 1);
+                       print $fh $res->content;
+                       close($fh) or die "Could not write $name: $!\n";
+               } else {
+                       return undef if $res->code == 301; # directory?
+                       die $res->status_line." at $url\n";
+               }
+               $mode = '0644'; # can't obtain mode via direct http request?
+       } else {
+               ($name,$mode) = $svn->file("$svnpath",$rev);
+               return undef unless defined $name;
+       }
+
+       my $pid = open(my $F, '-|');
+       die $! unless defined $pid;
+       if (!$pid) {
+           exec("git-hash-object", "-w", $name)
+               or die "Cannot create object: $!\n";
+       }
+       my $sha = <$F>;
+       chomp $sha;
+       close $F;
+       unlink $name;
+       return [$mode, $sha, $path];
+}
+
+sub get_ignore($$$$$) {
+       my($new,$old,$rev,$path,$svnpath) = @_;
+
+       return unless $opt_I;
+       my $name = $svn->ignore("$svnpath",$rev);
+       if ($path eq '/') {
+               $path = $opt_I;
+       } else {
+               $path = File::Spec->catfile($path,$opt_I);
+       }
+       if (defined $name) {
+               my $pid = open(my $F, '-|');
+               die $! unless defined $pid;
+               if (!$pid) {
+                       exec("git-hash-object", "-w", $name)
+                           or die "Cannot create object: $!\n";
+               }
+               my $sha = <$F>;
+               chomp $sha;
+               close $F;
+               unlink $name;
+               push(@$new,['0644',$sha,$path]);
+       } elsif (defined $old) {
+               push(@$old,$path);
+       }
+}
+
+sub project_path($$)
+{
+       my ($path, $project) = @_;
+
+       $path = "/".$path unless ($path =~ m#^\/#) ;
+       return $1 if ($path =~ m#^$project\/(.*)$#);
+
+       $path =~ s#\.#\\\.#g;
+       $path =~ s#\+#\\\+#g;
+       return "/" if ($project =~ m#^$path.*$#);
+
+       return undef;
+}
+
+sub split_path($$) {
+       my($rev,$path) = @_;
+       my $branch;
+
+       if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) {
+               $branch = "/$1";
+       } elsif($path =~ s#^/\Q$trunk_name\E/?##) {
+               $branch = "/";
+       } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) {
+               $branch = $1;
+       } else {
+               my %no_error = (
+                       "/" => 1,
+                       "/$tag_name" => 1,
+                       "/$branch_name" => 1
+               );
+               print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
+               return ()
+       }
+       if ($path eq "") {
+               $path = "/";
+       } elsif ($project_name) {
+               $path = project_path($path, $project_name);
+       }
+       return ($branch,$path);
+}
+
+sub branch_rev($$) {
+
+       my ($srcbranch,$uptorev) = @_;
+
+       my $bbranches = $branches{$srcbranch};
+       my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches;
+       my $therev;
+       foreach my $arev(@revs) {
+               next if  ($arev eq 'LAST');
+               if ($arev <= $uptorev) {
+                       $therev = $arev;
+                       last;
+               }
+       }
+       return $therev;
+}
+
+sub expand_svndir($$$);
+
+sub expand_svndir($$$)
+{
+       my ($svnpath, $rev, $path) = @_;
+       my @list;
+       get_ignore(\@list, undef, $rev, $path, $svnpath);
+       my $dirents = $svn->dir_list($svnpath, $rev);
+       foreach my $p(keys %$dirents) {
+               my $kind = node_kind($svnpath.'/'.$p, $rev);
+               if ($kind eq $SVN::Node::file) {
+                       my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p);
+                       push(@list, $f) if $f;
+               } elsif ($kind eq $SVN::Node::dir) {
+                       push(@list,
+                            expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p));
+               }
+       }
+       return @list;
+}
+
+sub copy_path($$$$$$$$) {
+       # Somebody copied a whole subdirectory.
+       # We need to find the index entries from the old version which the
+       # SVN log entry points to, and add them to the new place.
+
+       my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
+
+       my($srcbranch,$srcpath) = split_path($rev,$oldpath);
+       unless(defined $srcbranch && defined $srcpath) {
+               print "Path not found when copying from $oldpath @ $rev.\n".
+                       "Will try to copy from original SVN location...\n"
+                       if $opt_v;
+               push (@$new, expand_svndir($oldpath, $rev, $path));
+               return;
+       }
+       my $therev = branch_rev($srcbranch, $rev);
+       my $gitrev = $branches{$srcbranch}{$therev};
+       unless($gitrev) {
+               print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n";
+               return;
+       }
+       if ($srcbranch ne $newbranch) {
+               push(@$parents, $branches{$srcbranch}{'LAST'});
+       }
+       print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
+       if ($node_kind eq $SVN::Node::dir) {
+               $srcpath =~ s#/*$#/#;
+       }
+
+       my $pid = open my $f,'-|';
+       die $! unless defined $pid;
+       if (!$pid) {
+               exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
+                       or die $!;
+       }
+       local $/ = "\0";
+       while(<$f>) {
+               chomp;
+               my($m,$p) = split(/\t/,$_,2);
+               my($mode,$type,$sha1) = split(/ /,$m);
+               next if $type ne "blob";
+               if ($node_kind eq $SVN::Node::dir) {
+                       $p = $path . substr($p,length($srcpath)-1);
+               } else {
+                       $p = $path;
+               }
+               push(@$new,[$mode,$sha1,$p]);
+       }
+       close($f) or
+               print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
+}
+
+sub commit {
+       my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
+       my($committer_name,$committer_email,$dest);
+       my($author_name,$author_email);
+       my(@old,@new,@parents);
+
+       if (not defined $author or $author eq "") {
+               $committer_name = $committer_email = "unknown";
+       } elsif (defined $users_file) {
+               die "User $author is not listed in $users_file\n"
+                   unless exists $users{$author};
+               ($committer_name,$committer_email) = @{$users{$author}};
+       } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
+               ($committer_name, $committer_email) = ($1, $2);
+       } else {
+               $author =~ s/^<(.*)>$/$1/;
+               $committer_name = $committer_email = $author;
+       }
+
+       if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) {
+               ($author_name, $author_email) = ($1, $2);
+               print "Author from From: $1 <$2>\n" if ($opt_v);;
+       } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
+               ($author_name, $author_email) = ($1, $2);
+               print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);;
+       } else {
+               $author_name = $committer_name;
+               $author_email = $committer_email;
+       }
+
+       $date = pdate($date);
+
+       my $tag;
+       my $parent;
+       if($branch eq "/") { # trunk
+               $parent = $opt_o;
+       } elsif($branch =~ m#^/(.+)#) { # tag
+               $tag = 1;
+               $parent = $1;
+       } else { # "normal" branch
+               # nothing to do
+               $parent = $branch;
+       }
+       $dest = $parent;
+
+       my $prev = $changed_paths->{"/"};
+       if($prev and $prev->[0] eq "A") {
+               delete $changed_paths->{"/"};
+               my $oldpath = $prev->[1];
+               my $rev;
+               if(defined $oldpath) {
+                       my $p;
+                       ($parent,$p) = split_path($revision,$oldpath);
+                       if(defined $parent) {
+                               if($parent eq "/") {
+                                       $parent = $opt_o;
+                               } else {
+                                       $parent =~ s#^/##; # if it's a tag
+                               }
+                       }
+               } else {
+                       $parent = undef;
+               }
+       }
+
+       my $rev;
+       if($revision > $opt_s and defined $parent) {
+               open(H,'-|',"git-rev-parse","--verify",$parent);
+               $rev = <H>;
+               close(H) or do {
+                       print STDERR "$revision: cannot find commit '$parent'!\n";
+                       return;
+               };
+               chop $rev;
+               if(length($rev) != 40) {
+                       print STDERR "$revision: cannot find commit '$parent'!\n";
+                       return;
+               }
+               $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"};
+               if($revision != $opt_s and not $rev) {
+                       print STDERR "$revision: do not know ancestor for '$parent'!\n";
+                       return;
+               }
+       } else {
+               $rev = undef;
+       }
+
+#      if($prev and $prev->[0] eq "A") {
+#              if(not $tag) {
+#                      unless(open(H,"> $git_dir/refs/heads/$branch")) {
+#                              print STDERR "$revision: Could not create branch $branch: $!\n";
+#                              $state=11;
+#                              next;
+#                      }
+#                      print H "$rev\n"
+#                              or die "Could not write branch $branch: $!";
+#                      close(H)
+#                              or die "Could not write branch $branch: $!";
+#              }
+#      }
+       if(not defined $rev) {
+               unlink($git_index);
+       } elsif ($rev ne $last_rev) {
+               print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
+               system("git-read-tree", $rev);
+               die "read-tree failed for $rev: $?\n" if $?;
+               $last_rev = $rev;
+       }
+
+       push (@parents, $rev) if defined $rev;
+
+       my $cid;
+       if($tag and not %$changed_paths) {
+               $cid = $rev;
+       } else {
+               my @paths = sort keys %$changed_paths;
+               foreach my $path(@paths) {
+                       my $action = $changed_paths->{$path};
+
+                       if ($action->[0] eq "R") {
+                               # refer to a file/tree in an earlier commit
+                               push(@old,$path); # remove any old stuff
+                       }
+                       if(($action->[0] eq "A") || ($action->[0] eq "R")) {
+                               my $node_kind = node_kind($action->[3], $revision);
+                               if ($node_kind eq $SVN::Node::file) {
+                                       my $f = get_file($action->[3],
+                                                        $revision, $path);
+                                       if ($f) {
+                                               push(@new,$f) if $f;
+                                       } else {
+                                               my $opath = $action->[3];
+                                               print STDERR "$revision: $branch: could not fetch '$opath'\n";
+                                       }
+                               } elsif ($node_kind eq $SVN::Node::dir) {
+                                       if($action->[1]) {
+                                               copy_path($revision, $branch,
+                                                         $path, $action->[1],
+                                                         $action->[2], $node_kind,
+                                                         \@new, \@parents);
+                                       } else {
+                                               get_ignore(\@new, \@old, $revision,
+                                                          $path, $action->[3]);
+                                       }
+                               }
+                       } elsif ($action->[0] eq "D") {
+                               push(@old,$path);
+                       } elsif ($action->[0] eq "M") {
+                               my $node_kind = node_kind($action->[3], $revision);
+                               if ($node_kind eq $SVN::Node::file) {
+                                       my $f = get_file($action->[3],
+                                                        $revision, $path);
+                                       push(@new,$f) if $f;
+                               } elsif ($node_kind eq $SVN::Node::dir) {
+                                       get_ignore(\@new, \@old, $revision,
+                                                  $path, $action->[3]);
+                               }
+                       } else {
+                               die "$revision: unknown action '".$action->[0]."' for $path\n";
+                       }
+               }
+
+               while(@old) {
+                       my @o1;
+                       if(@old > 55) {
+                               @o1 = splice(@old,0,50);
+                       } else {
+                               @o1 = @old;
+                               @old = ();
+                       }
+                       my $pid = open my $F, "-|";
+                       die "$!" unless defined $pid;
+                       if (!$pid) {
+                               exec("git-ls-files", "-z", @o1) or die $!;
+                       }
+                       @o1 = ();
+                       local $/ = "\0";
+                       while(<$F>) {
+                               chomp;
+                               push(@o1,$_);
+                       }
+                       close($F);
+
+                       while(@o1) {
+                               my @o2;
+                               if(@o1 > 55) {
+                                       @o2 = splice(@o1,0,50);
+                               } else {
+                                       @o2 = @o1;
+                                       @o1 = ();
+                               }
+                               system("git-update-index","--force-remove","--",@o2);
+                               die "Cannot remove files: $?\n" if $?;
+                       }
+               }
+               while(@new) {
+                       my @n2;
+                       if(@new > 12) {
+                               @n2 = splice(@new,0,10);
+                       } else {
+                               @n2 = @new;
+                               @new = ();
+                       }
+                       system("git-update-index","--add",
+                               (map { ('--cacheinfo', @$_) } @n2));
+                       die "Cannot add files: $?\n" if $?;
+               }
+
+               my $pid = open(C,"-|");
+               die "Cannot fork: $!" unless defined $pid;
+               unless($pid) {
+                       exec("git-write-tree");
+                       die "Cannot exec git-write-tree: $!\n";
+               }
+               chomp(my $tree = <C>);
+               length($tree) == 40
+                       or die "Cannot get tree id ($tree): $!\n";
+               close(C)
+                       or die "Error running git-write-tree: $?\n";
+               print "Tree ID $tree\n" if $opt_v;
+
+               my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+               my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+               $pid = fork();
+               die "Fork: $!\n" unless defined $pid;
+               unless($pid) {
+                       $pr->writer();
+                       $pw->reader();
+                       open(OUT,">&STDOUT");
+                       dup2($pw->fileno(),0);
+                       dup2($pr->fileno(),1);
+                       $pr->close();
+                       $pw->close();
+
+                       my @par = ();
+
+                       # loose detection of merges
+                       # based on the commit msg
+                       foreach my $rx (@mergerx) {
+                               if ($message =~ $rx) {
+                                       my $mparent = $1;
+                                       if ($mparent eq 'HEAD') { $mparent = $opt_o };
+                                       if ( -e "$git_dir/refs/heads/$mparent") {
+                                               $mparent = get_headref($mparent, $git_dir);
+                                               push (@parents, $mparent);
+                                               print OUT "Merge parent branch: $mparent\n" if $opt_v;
+                                       }
+                               }
+                       }
+                       my %seen_parents = ();
+                       my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents;
+                       foreach my $bparent (@unique_parents) {
+                               push @par, '-p', $bparent;
+                               print OUT "Merge parent branch: $bparent\n" if $opt_v;
+                       }
+
+                       exec("env",
+                               "GIT_AUTHOR_NAME=$author_name",
+                               "GIT_AUTHOR_EMAIL=$author_email",
+                               "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                               "GIT_COMMITTER_NAME=$committer_name",
+                               "GIT_COMMITTER_EMAIL=$committer_email",
+                               "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                               "git-commit-tree", $tree,@par);
+                       die "Cannot exec git-commit-tree: $!\n";
+               }
+               $pw->writer();
+               $pr->reader();
+
+               $message =~ s/[\s\n]+\z//;
+               $message = "r$revision: $message" if $opt_r;
+
+               print $pw "$message\n"
+                       or die "Error writing to git-commit-tree: $!\n";
+               $pw->close();
+
+               print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
+               chomp($cid = <$pr>);
+               length($cid) == 40
+                       or die "Cannot get commit id ($cid): $!\n";
+               print "Commit ID $cid\n" if $opt_v;
+               $pr->close();
+
+               waitpid($pid,0);
+               die "Error running git-commit-tree: $?\n" if $?;
+       }
+
+       if (not defined $cid) {
+               $cid = $branches{"/"}{"LAST"};
+       }
+
+       if(not defined $dest) {
+               print "... no known parent\n" if $opt_v;
+       } elsif(not $tag) {
+               print "Writing to refs/heads/$dest\n" if $opt_v;
+               open(C,">$git_dir/refs/heads/$dest") and
+               print C ("$cid\n") and
+               close(C)
+                       or die "Cannot write branch $dest for update: $!\n";
+       }
+
+       if ($tag) {
+               $last_rev = "-" if %$changed_paths;
+               # the tag was 'complex', i.e. did not refer to a "real" revision
+
+               $dest =~ tr/_/\./ if $opt_u;
+
+               system('git-tag', '-f', $dest, $cid) == 0
+                       or die "Cannot create tag $dest: $!\n";
+
+               print "Created tag '$dest' on '$branch'\n" if $opt_v;
+       }
+       $branches{$branch}{"LAST"} = $cid;
+       $branches{$branch}{$revision} = $cid;
+       $last_rev = $cid;
+       print BRANCHES "$revision $branch $cid\n";
+       print "DONE: $revision $dest $cid\n" if $opt_v;
+}
+
+sub commit_all {
+       # Recursive use of the SVN connection does not work
+       local $svn = $svn2;
+
+       my ($changed_paths, $revision, $author, $date, $message) = @_;
+       my %p;
+       while(my($path,$action) = each %$changed_paths) {
+               $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
+       }
+       $changed_paths = \%p;
+
+       my %done;
+       my @col;
+       my $pref;
+       my $branch;
+
+       while(my($path,$action) = each %$changed_paths) {
+               ($branch,$path) = split_path($revision,$path);
+               next if not defined $branch;
+               next if not defined $path;
+               $done{$branch}{$path} = $action;
+       }
+       while(($branch,$changed_paths) = each %done) {
+               commit($branch, $changed_paths, $revision, $author, $date, $message);
+       }
+}
+
+$opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'};
+
+if ($opt_l < $current_rev) {
+    print "Up to date: no new revisions to fetch!\n" if $opt_v;
+    unlink("$git_dir/SVN2GIT_HEAD");
+    exit;
+}
+
+print "Processing from $current_rev to $opt_l ...\n" if $opt_v;
+
+my $from_rev;
+my $to_rev = $current_rev - 1;
+
+my $subpool = SVN::Pool::new_default_sub;
+while ($to_rev < $opt_l) {
+       $subpool->clear;
+       $from_rev = $to_rev + 1;
+       $to_rev = $from_rev + $repack_after;
+       $to_rev = $opt_l if $opt_l < $to_rev;
+       print "Fetching from $from_rev to $to_rev ...\n" if $opt_v;
+       $svn->{'svn'}->get_log("/",$from_rev,$to_rev,0,1,1,\&commit_all);
+       my $pid = fork();
+       die "Fork: $!\n" unless defined $pid;
+       unless($pid) {
+               exec("git-repack", "-d")
+                       or die "Cannot repack: $!\n";
+       }
+       waitpid($pid, 0);
+}
+
+
+unlink($git_index);
+
+if (defined $orig_git_index) {
+       $ENV{GIT_INDEX_FILE} = $orig_git_index;
+} else {
+       delete $ENV{GIT_INDEX_FILE};
+}
+
+# Now switch back to the branch we were in before all of this happened
+if($orig_branch) {
+       print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+               if $forward_master;
+       unless ($opt_i) {
+               system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
+               die "read-tree failed: $?\n" if $?;
+       }
+} else {
+       $orig_branch = "master";
+       print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+               unless -f "$git_dir/refs/heads/master";
+       system('git-update-ref', 'HEAD', "$orig_branch");
+       unless ($opt_i) {
+               system('git checkout');
+               die "checkout failed: $?\n" if $?;
+       }
+}
+unlink("$git_dir/SVN2GIT_HEAD");
+close(BRANCHES);
diff --git a/contrib/examples/git-svnimport.txt b/contrib/examples/git-svnimport.txt
new file mode 100644 (file)
index 0000000..71aad8b
--- /dev/null
@@ -0,0 +1,179 @@
+git-svnimport(1)
+================
+v0.1, July 2005
+
+NAME
+----
+git-svnimport - Import a SVN repository into git
+
+
+SYNOPSIS
+--------
+[verse]
+'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
+               [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
+               [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
+               [ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
+               [ -I <ignorefile_name> ] [ -A <author_file> ]
+               [ -R <repack_each_revs>] [ -P <path_from_trunk> ]
+               <SVN_repository_URL> [ <path> ]
+
+
+DESCRIPTION
+-----------
+Imports a SVN repository into git. It will either create a new
+repository, or incrementally import into an existing one.
+
+SVN access is done by the SVN::Perl module.
+
+git-svnimport assumes that SVN repositories are organized into one
+"trunk" directory where the main development happens, "branches/FOO"
+directories for branches, and "/tags/FOO" directories for tags.
+Other subdirectories are ignored.
+
+git-svnimport creates a file ".git/svn2git", which is required for
+incremental SVN imports.
+
+OPTIONS
+-------
+-C <target-dir>::
+        The GIT repository to import to.  If the directory doesn't
+        exist, it will be created.  Default is the current directory.
+
+-s <start_rev>::
+        Start importing at this SVN change number. The  default is 1.
++
+When importing incrementally, you might need to edit the .git/svn2git file.
+
+-i::
+       Import-only: don't perform a checkout after importing.  This option
+       ensures the working directory and index remain untouched and will
+       not create them if they do not exist.
+
+-T <trunk_subdir>::
+       Name the SVN trunk. Default "trunk".
+
+-t <tag_subdir>::
+       Name the SVN subdirectory for tags. Default "tags".
+
+-b <branch_subdir>::
+       Name the SVN subdirectory for branches. Default "branches".
+
+-o <branch-for-HEAD>::
+       The 'trunk' branch from SVN is imported to the 'origin' branch within
+       the git repository. Use this option if you want to import into a
+       different branch.
+
+-r::
+       Prepend 'rX: ' to commit messages, where X is the imported
+       subversion revision.
+
+-u::
+       Replace underscores in tag names with periods.
+
+-I <ignorefile_name>::
+       Import the svn:ignore directory property to files with this
+       name in each directory. (The Subversion and GIT ignore
+       syntaxes are similar enough that using the Subversion patterns
+       directly with "-I .gitignore" will almost always just work.)
+
+-A <author_file>::
+       Read a file with lines on the form
++
+------
+       username = User's Full Name <email@addr.es>
+
+------
++
+and use "User's Full Name <email@addr.es>" as the GIT
+author and committer for Subversion commits made by
+"username". If encountering a commit made by a user not in the
+list, abort.
++
+For convenience, this data is saved to $GIT_DIR/svn-authors
+each time the -A option is provided, and read from that same
+file each time git-svnimport is run with an existing GIT
+repository without -A.
+
+-m::
+       Attempt to detect merges based on the commit message. This option
+       will enable default regexes that try to capture the name source
+       branch name from the commit message.
+
+-M <regex>::
+       Attempt to detect merges based on the commit message with a custom
+       regex. It can be used with -m to also see the default regexes.
+       You must escape forward slashes.
+
+-l <max_rev>::
+       Specify a maximum revision number to pull.
++
+Formerly, this option controlled how many revisions to pull,
+due to SVN memory leaks. (These have been worked around.)
+
+-R <repack_each_revs>::
+       Specify how often git repository should be repacked.
++
+The default value is 1000. git-svnimport will do import in chunks of 1000
+revisions, after each chunk git repository will be repacked. To disable
+this behavior specify some big value here which is mote than number of
+revisions to import.
+
+-P <path_from_trunk>::
+       Partial import of the SVN tree.
++
+By default, the whole tree on the SVN trunk (/trunk) is imported.
+'-P my/proj' will import starting only from '/trunk/my/proj'.
+This option is useful when you want to import one project from a
+svn repo which hosts multiple projects under the same trunk.
+
+-v::
+       Verbosity: let 'svnimport' report what it is doing.
+
+-d::
+       Use direct HTTP requests if possible. The "<path>" argument is used
+       only for retrieving the SVN logs; the path to the contents is
+       included in the SVN log.
+
+-D::
+       Use direct HTTP requests if possible. The "<path>" argument is used
+       for retrieving the logs, as well as for the contents.
++
+There's no safe way to automatically find out which of these options to
+use, so you need to try both. Usually, the one that's wrong will die
+with a 40x error pretty quickly.
+
+<SVN_repository_URL>::
+       The URL of the SVN module you want to import. For local
+       repositories, use "file:///absolute/path".
++
+If you're using the "-d" or "-D" option, this is the URL of the SVN
+repository itself; it usually ends in "/svn".
+
+<path>::
+       The path to the module you want to check out.
+
+-h::
+       Print a short usage message and exit.
+
+OUTPUT
+------
+If '-v' is specified, the script reports what it is doing.
+
+Otherwise, success is indicated the Unix way, i.e. by simply exiting with
+a zero exit status.
+
+Author
+------
+Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
+various participants of the git-list <git@vger.kernel.org>.
+
+Based on a cvs2git script by the same author.
+
+Documentation
+--------------
+Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index aa95834eb3e3f55a2bcb8e6c6f8258f4a57b594e..4df75595b1f3e689a9685039c372ffb4c8688291 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -192,48 +192,39 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len,
        return 1;
 }
 
-static int filter_buffer(const char *path, const char *src,
-                        unsigned long size, const char *cmd)
+struct filter_params {
+       const char *src;
+       unsigned long size;
+       const char *cmd;
+};
+
+static int filter_buffer(int fd, void *data)
 {
        /*
         * Spawn cmd and feed the buffer contents through its stdin.
         */
        struct child_process child_process;
-       int pipe_feed[2];
+       struct filter_params *params = (struct filter_params *)data;
        int write_err, status;
+       const char *argv[] = { "sh", "-c", params->cmd, NULL };
 
        memset(&child_process, 0, sizeof(child_process));
+       child_process.argv = argv;
+       child_process.in = -1;
+       child_process.out = fd;
 
-       if (pipe(pipe_feed) < 0) {
-               error("cannot create pipe to run external filter %s", cmd);
-               return 1;
-       }
-
-       child_process.pid = fork();
-       if (child_process.pid < 0) {
-               error("cannot fork to run external filter %s", cmd);
-               close(pipe_feed[0]);
-               close(pipe_feed[1]);
-               return 1;
-       }
-       if (!child_process.pid) {
-               dup2(pipe_feed[0], 0);
-               close(pipe_feed[0]);
-               close(pipe_feed[1]);
-               execlp("sh", "sh", "-c", cmd, NULL);
-               return 1;
-       }
-       close(pipe_feed[0]);
+       if (start_command(&child_process))
+               return error("cannot fork to run external filter %s", params->cmd);
 
-       write_err = (write_in_full(pipe_feed[1], src, size) < 0);
-       if (close(pipe_feed[1]))
+       write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
+       if (close(child_process.in))
                write_err = 1;
        if (write_err)
-               error("cannot feed the input to external filter %s", cmd);
+               error("cannot feed the input to external filter %s", params->cmd);
 
        status = finish_command(&child_process);
        if (status)
-               error("external filter %s failed %d", cmd, -status);
+               error("external filter %s failed %d", params->cmd, -status);
        return (write_err || status);
 }
 
@@ -246,49 +237,36 @@ static int apply_filter(const char *path, const char *src, size_t len,
         *
         * (child --> cmd) --> us
         */
-       int pipe_feed[2];
-       int status, ret = 1;
-       struct child_process child_process;
+       int ret = 1;
        struct strbuf nbuf;
+       struct async async;
+       struct filter_params params;
 
        if (!cmd)
                return 0;
 
-       memset(&child_process, 0, sizeof(child_process));
-
-       if (pipe(pipe_feed) < 0) {
-               error("cannot create pipe to run external filter %s", cmd);
-               return 0;
-       }
+       memset(&async, 0, sizeof(async));
+       async.proc = filter_buffer;
+       async.data = &params;
+       params.src = src;
+       params.size = len;
+       params.cmd = cmd;
 
        fflush(NULL);
-       child_process.pid = fork();
-       if (child_process.pid < 0) {
-               error("cannot fork to run external filter %s", cmd);
-               close(pipe_feed[0]);
-               close(pipe_feed[1]);
-               return 0;
-       }
-       if (!child_process.pid) {
-               dup2(pipe_feed[1], 1);
-               close(pipe_feed[0]);
-               close(pipe_feed[1]);
-               exit(filter_buffer(path, src, len, cmd));
-       }
-       close(pipe_feed[1]);
+       if (start_async(&async))
+               return 0;       /* error was already reported */
 
        strbuf_init(&nbuf, 0);
-       if (strbuf_read(&nbuf, pipe_feed[0], len) < 0) {
+       if (strbuf_read(&nbuf, async.out, len) < 0) {
                error("read from external filter %s failed", cmd);
                ret = 0;
        }
-       if (close(pipe_feed[0])) {
+       if (close(async.out)) {
                error("read from external filter %s failed", cmd);
                ret = 0;
        }
-       status = finish_command(&child_process);
-       if (status) {
-               error("external filter %s failed %d", cmd, -status);
+       if (finish_async(&async)) {
+               error("external filter %s failed", cmd);
                ret = 0;
        }
 
index 9ab997120d04c5be0aa9d3ff3ba090ba12b7bec8..3729e73e19e18a78c15fb90dfc28133f541176ef 100644 (file)
@@ -8,6 +8,7 @@
  * able to verify hasn't been messed with afterwards.
  */
 #include "cache.h"
+#include "progress.h"
 #include "csum-file.h"
 
 static void sha1flush(struct sha1file *f, unsigned int count)
@@ -17,6 +18,7 @@ static void sha1flush(struct sha1file *f, unsigned int count)
        for (;;) {
                int ret = xwrite(f->fd, buf, count);
                if (ret > 0) {
+                       display_throughput(f->tp, ret);
                        buf = (char *) buf + ret;
                        count -= ret;
                        if (count)
@@ -31,22 +33,27 @@ static void sha1flush(struct sha1file *f, unsigned int count)
 
 int sha1close(struct sha1file *f, unsigned char *result, int final)
 {
+       int fd;
        unsigned offset = f->offset;
        if (offset) {
                SHA1_Update(&f->ctx, f->buffer, offset);
                sha1flush(f, offset);
                f->offset = 0;
        }
-       if (!final)
-               return 0;       /* only want to flush (no checksum write, no close) */
-       SHA1_Final(f->buffer, &f->ctx);
-       if (result)
-               hashcpy(result, f->buffer);
-       sha1flush(f, 20);
-       if (close(f->fd))
-               die("%s: sha1 file error on close (%s)", f->name, strerror(errno));
+       if (final) {
+               /* write checksum and close fd */
+               SHA1_Final(f->buffer, &f->ctx);
+               if (result)
+                       hashcpy(result, f->buffer);
+               sha1flush(f, 20);
+               if (close(f->fd))
+                       die("%s: sha1 file error on close (%s)",
+                           f->name, strerror(errno));
+               fd = 0;
+       } else
+               fd = f->fd;
        free(f);
-       return 0;
+       return fd;
 }
 
 int sha1write(struct sha1file *f, void *buf, unsigned int count)
@@ -74,6 +81,11 @@ int sha1write(struct sha1file *f, void *buf, unsigned int count)
 }
 
 struct sha1file *sha1fd(int fd, const char *name)
+{
+       return sha1fd_throughput(fd, name, NULL);
+}
+
+struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp)
 {
        struct sha1file *f;
        unsigned len;
@@ -89,6 +101,7 @@ struct sha1file *sha1fd(int fd, const char *name)
        f->fd = fd;
        f->error = 0;
        f->offset = 0;
+       f->tp = tp;
        f->do_crc = 0;
        SHA1_Init(&f->ctx);
        return f;
index c3c792f1b56026b6a4d9d10b6ba63947c7f383cf..4d1b231292ad4cfc7996b73ea31fa74a5eff2687 100644 (file)
@@ -1,11 +1,14 @@
 #ifndef CSUM_FILE_H
 #define CSUM_FILE_H
 
+struct progress;
+
 /* A SHA1-protected file */
 struct sha1file {
        int fd, error;
        unsigned int offset, namelen;
        SHA_CTX ctx;
+       struct progress *tp;
        char name[PATH_MAX];
        int do_crc;
        uint32_t crc32;
@@ -13,6 +16,7 @@ struct sha1file {
 };
 
 extern struct sha1file *sha1fd(int fd, const char *name);
+extern struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp);
 extern int sha1close(struct sha1file *, unsigned char *, int);
 extern int sha1write(struct sha1file *, void *, unsigned int);
 extern void crc32_begin(struct sha1file *);
diff --git a/diff.c b/diff.c
index dfb8595b7086c71b3be0ece408d65a7285f42e9f..a6aaaf7e5a91f3e5ad5f85341429c80a3452504e 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -9,6 +9,7 @@
 #include "xdiff-interface.h"
 #include "color.h"
 #include "attr.h"
+#include "run-command.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -1440,9 +1441,18 @@ struct diff_filespec *alloc_filespec(const char *path)
        memset(spec, 0, sizeof(*spec));
        spec->path = (char *)(spec + 1);
        memcpy(spec->path, path, namelen+1);
+       spec->count = 1;
        return spec;
 }
 
+void free_filespec(struct diff_filespec *spec)
+{
+       if (!--spec->count) {
+               diff_free_filespec_data(spec);
+               free(spec);
+       }
+}
+
 void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
                   unsigned short mode)
 {
@@ -1752,40 +1762,6 @@ static void remove_tempfile_on_signal(int signo)
        raise(signo);
 }
 
-static int spawn_prog(const char *pgm, const char **arg)
-{
-       pid_t pid;
-       int status;
-
-       fflush(NULL);
-       pid = fork();
-       if (pid < 0)
-               die("unable to fork");
-       if (!pid) {
-               execvp(pgm, (char *const*) arg);
-               exit(255);
-       }
-
-       while (waitpid(pid, &status, 0) < 0) {
-               if (errno == EINTR)
-                       continue;
-               return -1;
-       }
-
-       /* Earlier we did not check the exit status because
-        * diff exits non-zero if files are different, and
-        * we are not interested in knowing that.  It was a
-        * mistake which made it harder to quit a diff-*
-        * session that uses the git-apply-patch-script as
-        * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
-        * should also exit non-zero only when it wants to
-        * abort the entire diff-* session.
-        */
-       if (WIFEXITED(status) && !WEXITSTATUS(status))
-               return 0;
-       return -1;
-}
-
 /* An external diff command takes:
  *
  * diff-cmd name infile1 infile1-sha1 infile1-mode \
@@ -1838,7 +1814,8 @@ static void run_external_diff(const char *pgm,
                *arg++ = name;
        }
        *arg = NULL;
-       retval = spawn_prog(pgm, spawn_arg);
+       fflush(NULL);
+       retval = run_command_v_opt(spawn_arg, 0);
        remove_tempfile();
        if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@ -2435,10 +2412,8 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
 
 void diff_free_filepair(struct diff_filepair *p)
 {
-       diff_free_filespec_data(p->one);
-       diff_free_filespec_data(p->two);
-       free(p->one);
-       free(p->two);
+       free_filespec(p->one);
+       free_filespec(p->two);
        free(p);
 }
 
@@ -2590,9 +2565,9 @@ void diff_debug_filepair(const struct diff_filepair *p, int i)
 {
        diff_debug_filespec(p->one, i, "one");
        diff_debug_filespec(p->two, i, "two");
-       fprintf(stderr, "score %d, status %c stays %d broken %d\n",
+       fprintf(stderr, "score %d, status %c rename_used %d broken %d\n",
                p->score, p->status ? p->status : '?',
-               p->source_stays, p->broken_pair);
+               p->one->rename_used, p->broken_pair);
 }
 
 void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
@@ -2610,8 +2585,8 @@ void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
 
 static void diff_resolve_rename_copy(void)
 {
-       int i, j;
-       struct diff_filepair *p, *pp;
+       int i;
+       struct diff_filepair *p;
        struct diff_queue_struct *q = &diff_queued_diff;
 
        diff_debug_queue("resolve-rename-copy", q);
@@ -2633,27 +2608,21 @@ static void diff_resolve_rename_copy(void)
                 * either in-place edit or rename/copy edit.
                 */
                else if (DIFF_PAIR_RENAME(p)) {
-                       if (p->source_stays) {
-                               p->status = DIFF_STATUS_COPIED;
-                               continue;
-                       }
-                       /* See if there is some other filepair that
-                        * copies from the same source as us.  If so
-                        * we are a copy.  Otherwise we are either a
-                        * copy if the path stays, or a rename if it
-                        * does not, but we already handled "stays" case.
+                       /*
+                        * A rename might have re-connected a broken
+                        * pair up, causing the pathnames to be the
+                        * same again. If so, that's not a rename at
+                        * all, just a modification..
+                        *
+                        * Otherwise, see if this source was used for
+                        * multiple renames, in which case we decrement
+                        * the count, and call it a copy.
                         */
-                       for (j = i + 1; j < q->nr; j++) {
-                               pp = q->queue[j];
-                               if (strcmp(pp->one->path, p->one->path))
-                                       continue; /* not us */
-                               if (!DIFF_PAIR_RENAME(pp))
-                                       continue; /* not a rename/copy */
-                               /* pp is a rename/copy from the same source */
+                       if (!strcmp(p->one->path, p->two->path))
+                               p->status = DIFF_STATUS_MODIFIED;
+                       else if (--p->one->rename_used > 0)
                                p->status = DIFF_STATUS_COPIED;
-                               break;
-                       }
-                       if (!p->status)
+                       else
                                p->status = DIFF_STATUS_RENAMED;
                }
                else if (hashcmp(p->one->sha1, p->two->sha1) ||
index 142e5376dd741377c311075816f139a0949ee82f..f9ebea56406090af207f79951618742dcd7d397f 100644 (file)
@@ -4,6 +4,7 @@
 #include "cache.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "hash.h"
 
 /* Table of rename/copy destinations */
 
@@ -55,12 +56,10 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
 static struct diff_rename_src {
        struct diff_filespec *one;
        unsigned short score; /* to remember the break score */
-       unsigned src_path_left : 1;
 } *rename_src;
 static int rename_src_nr, rename_src_alloc;
 
 static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
-                                                  int src_path_left,
                                                   unsigned short score)
 {
        int first, last;
@@ -92,33 +91,9 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
                        (rename_src_nr - first - 1) * sizeof(*rename_src));
        rename_src[first].one = one;
        rename_src[first].score = score;
-       rename_src[first].src_path_left = src_path_left;
        return &(rename_src[first]);
 }
 
-static int is_exact_match(struct diff_filespec *src,
-                         struct diff_filespec *dst,
-                         int contents_too)
-{
-       if (src->sha1_valid && dst->sha1_valid &&
-           !hashcmp(src->sha1, dst->sha1))
-               return 1;
-       if (!contents_too)
-               return 0;
-       if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1))
-               return 0;
-       if (src->size != dst->size)
-               return 0;
-       if (src->sha1_valid && dst->sha1_valid)
-           return !hashcmp(src->sha1, dst->sha1);
-       if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
-               return 0;
-       if (src->size == dst->size &&
-           !memcmp(src->data, dst->data, src->size))
-               return 1;
-       return 0;
-}
-
 static int basename_same(struct diff_filespec *src, struct diff_filespec *dst)
 {
        int src_len = strlen(src->path), dst_len = strlen(dst->path);
@@ -169,6 +144,20 @@ static int estimate_similarity(struct diff_filespec *src,
        if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
                return 0;
 
+       /*
+        * Need to check that source and destination sizes are
+        * filled in before comparing them.
+        *
+        * If we already have "cnt_data" filled in, we know it's
+        * all good (avoid checking the size for zero, as that
+        * is a possible size - we really should have a flag to
+        * say whether the size is valid or not!)
+        */
+       if (!src->cnt_data && diff_populate_filespec(src, 0))
+               return 0;
+       if (!dst->cnt_data && diff_populate_filespec(dst, 0))
+               return 0;
+
        max_size = ((src->size > dst->size) ? src->size : dst->size);
        base_size = ((src->size < dst->size) ? src->size : dst->size);
        delta_size = max_size - base_size;
@@ -184,11 +173,6 @@ static int estimate_similarity(struct diff_filespec *src,
        if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
                return 0;
 
-       if ((!src->cnt_data && diff_populate_filespec(src, 0))
-               || (!dst->cnt_data && diff_populate_filespec(dst, 0)))
-               return 0; /* error but caught downstream */
-
-
        delta_limit = (unsigned long)
                (base_size * (MAX_SCORE-minimum_score) / MAX_SCORE);
        if (diffcore_count_changes(src, dst,
@@ -209,27 +193,25 @@ static int estimate_similarity(struct diff_filespec *src,
 
 static void record_rename_pair(int dst_index, int src_index, int score)
 {
-       struct diff_filespec *one, *two, *src, *dst;
+       struct diff_filespec *src, *dst;
        struct diff_filepair *dp;
 
        if (rename_dst[dst_index].pair)
                die("internal error: dst already matched.");
 
        src = rename_src[src_index].one;
-       one = alloc_filespec(src->path);
-       fill_filespec(one, src->sha1, src->mode);
+       src->rename_used++;
+       src->count++;
 
        dst = rename_dst[dst_index].two;
-       two = alloc_filespec(dst->path);
-       fill_filespec(two, dst->sha1, dst->mode);
+       dst->count++;
 
-       dp = diff_queue(NULL, one, two);
+       dp = diff_queue(NULL, src, dst);
        dp->renamed_pair = 1;
        if (!strcmp(src->path, dst->path))
                dp->score = rename_src[src_index].score;
        else
                dp->score = score;
-       dp->source_stays = rename_src[src_index].src_path_left;
        rename_dst[dst_index].pair = dp;
 }
 
@@ -247,19 +229,155 @@ static int score_compare(const void *a_, const void *b_)
        return b->score - a->score;
 }
 
-static int compute_stays(struct diff_queue_struct *q,
-                        struct diff_filespec *one)
+struct file_similarity {
+       int src_dst, index;
+       struct diff_filespec *filespec;
+       struct file_similarity *next;
+};
+
+static int find_identical_files(struct file_similarity *src,
+                               struct file_similarity *dst)
 {
-       int i;
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               if (strcmp(one->path, p->two->path))
-                       continue;
-               if (DIFF_PAIR_RENAME(p)) {
-                       return 0; /* something else is renamed into this */
+       int renames = 0;
+
+       /*
+        * Walk over all the destinations ...
+        */
+       do {
+               struct diff_filespec *one = dst->filespec;
+               struct file_similarity *p, *best;
+               int i = 100;
+
+               /*
+                * .. to find the best source match
+                */
+               best = NULL;
+               for (p = src; p; p = p->next) {
+                       struct diff_filespec *two = p->filespec;
+
+                       /* False hash collission? */
+                       if (hashcmp(one->sha1, two->sha1))
+                               continue;
+                       /* Non-regular files? If so, the modes must match! */
+                       if (!S_ISREG(one->mode) || !S_ISREG(two->mode)) {
+                               if (one->mode != two->mode)
+                                       continue;
+                       }
+                       best = p;
+                       if (basename_same(one, two))
+                               break;
+
+                       /* Too many identical alternatives? Pick one */
+                       if (!--i)
+                               break;
+               }
+               if (best) {
+                       record_rename_pair(dst->index, best->index, MAX_SCORE);
+                       renames++;
                }
+       } while ((dst = dst->next) != NULL);
+       return renames;
+}
+
+static void free_similarity_list(struct file_similarity *p)
+{
+       while (p) {
+               struct file_similarity *entry = p;
+               p = p->next;
+               free(entry);
        }
-       return 1;
+}
+
+static int find_same_files(void *ptr)
+{
+       int ret;
+       struct file_similarity *p = ptr;
+       struct file_similarity *src = NULL, *dst = NULL;
+
+       /* Split the hash list up into sources and destinations */
+       do {
+               struct file_similarity *entry = p;
+               p = p->next;
+               if (entry->src_dst < 0) {
+                       entry->next = src;
+                       src = entry;
+               } else {
+                       entry->next = dst;
+                       dst = entry;
+               }
+       } while (p);
+
+       /*
+        * If we have both sources *and* destinations, see if
+        * we can match them up
+        */
+       ret = (src && dst) ? find_identical_files(src, dst) : 0;
+
+       /* Free the hashes and return the number of renames found */
+       free_similarity_list(src);
+       free_similarity_list(dst);
+       return ret;
+}
+
+static unsigned int hash_filespec(struct diff_filespec *filespec)
+{
+       unsigned int hash;
+       if (!filespec->sha1_valid) {
+               if (diff_populate_filespec(filespec, 0))
+                       return 0;
+               hash_sha1_file(filespec->data, filespec->size, "blob", filespec->sha1);
+       }
+       memcpy(&hash, filespec->sha1, sizeof(hash));
+       return hash;
+}
+
+static void insert_file_table(struct hash_table *table, int src_dst, int index, struct diff_filespec *filespec)
+{
+       void **pos;
+       unsigned int hash;
+       struct file_similarity *entry = xmalloc(sizeof(*entry));
+
+       entry->src_dst = src_dst;
+       entry->index = index;
+       entry->filespec = filespec;
+       entry->next = NULL;
+
+       hash = hash_filespec(filespec);
+       pos = insert_hash(hash, entry, table);
+
+       /* We already had an entry there? */
+       if (pos) {
+               entry->next = *pos;
+               *pos = entry;
+       }
+}
+
+/*
+ * Find exact renames first.
+ *
+ * The first round matches up the up-to-date entries,
+ * and then during the second round we try to match
+ * cache-dirty entries as well.
+ */
+static int find_exact_renames(void)
+{
+       int i;
+       struct hash_table file_table;
+
+       init_hash(&file_table);
+       for (i = 0; i < rename_src_nr; i++)
+               insert_file_table(&file_table, -1, i, rename_src[i].one);
+
+       for (i = 0; i < rename_dst_nr; i++)
+               insert_file_table(&file_table, 1, i, rename_dst[i].two);
+
+       /* Find the renames */
+       i = for_each_hash(&file_table, find_same_files);
+
+       /* .. and free the hash data structure */
+       free_hash(&file_table);
+
+       return i;
 }
 
 void diffcore_rename(struct diff_options *options)
@@ -270,12 +388,11 @@ void diffcore_rename(struct diff_options *options)
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
        struct diff_score *mx;
-       int i, j, rename_count, contents_too;
+       int i, j, rename_count;
        int num_create, num_src, dst_cnt;
 
        if (!minimum_score)
                minimum_score = DEFAULT_RENAME_SCORE;
-       rename_count = 0;
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
@@ -289,81 +406,66 @@ void diffcore_rename(struct diff_options *options)
                                locate_rename_dst(p->two, 1);
                }
                else if (!DIFF_FILE_VALID(p->two)) {
-                       /* If the source is a broken "delete", and
+                       /*
+                        * If the source is a broken "delete", and
                         * they did not really want to get broken,
                         * that means the source actually stays.
+                        * So we increment the "rename_used" score
+                        * by one, to indicate ourselves as a user
+                        */
+                       if (p->broken_pair && !p->score)
+                               p->one->rename_used++;
+                       register_rename_src(p->one, p->score);
+               }
+               else if (detect_rename == DIFF_DETECT_COPY) {
+                       /*
+                        * Increment the "rename_used" score by
+                        * one, to indicate ourselves as a user.
                         */
-                       int stays = (p->broken_pair && !p->score);
-                       register_rename_src(p->one, stays, p->score);
+                       p->one->rename_used++;
+                       register_rename_src(p->one, p->score);
                }
-               else if (detect_rename == DIFF_DETECT_COPY)
-                       register_rename_src(p->one, 1, p->score);
        }
        if (rename_dst_nr == 0 || rename_src_nr == 0)
                goto cleanup; /* nothing to do */
 
+       /*
+        * We really want to cull the candidates list early
+        * with cheap tests in order to avoid doing deltas.
+        */
+       rename_count = find_exact_renames();
+
+       /* Did we only want exact renames? */
+       if (minimum_score == MAX_SCORE)
+               goto cleanup;
+
+       /*
+        * Calculate how many renames are left (but all the source
+        * files still remain as options for rename/copies!)
+        */
+       num_create = (rename_dst_nr - rename_count);
+       num_src = rename_src_nr;
+
+       /* All done? */
+       if (!num_create)
+               goto cleanup;
+
        /*
         * This basically does a test for the rename matrix not
         * growing larger than a "rename_limit" square matrix, ie:
         *
-        *    rename_dst_nr * rename_src_nr > rename_limit * rename_limit
+        *    num_create * num_src > rename_limit * rename_limit
         *
         * but handles the potential overflow case specially (and we
         * assume at least 32-bit integers)
         */
        if (rename_limit <= 0 || rename_limit > 32767)
                rename_limit = 32767;
-       if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit)
+       if (num_create > rename_limit && num_src > rename_limit)
                goto cleanup;
-       if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit)
+       if (num_create * num_src > rename_limit * rename_limit)
                goto cleanup;
 
-       /* We really want to cull the candidates list early
-        * with cheap tests in order to avoid doing deltas.
-        * The first round matches up the up-to-date entries,
-        * and then during the second round we try to match
-        * cache-dirty entries as well.
-        */
-       for (contents_too = 0; contents_too < 2; contents_too++) {
-               for (i = 0; i < rename_dst_nr; i++) {
-                       struct diff_filespec *two = rename_dst[i].two;
-                       if (rename_dst[i].pair)
-                               continue; /* dealt with an earlier round */
-                       for (j = 0; j < rename_src_nr; j++) {
-                               int k;
-                               struct diff_filespec *one = rename_src[j].one;
-                               if (!is_exact_match(one, two, contents_too))
-                                       continue;
-
-                               /* see if there is a basename match, too */
-                               for (k = j; k < rename_src_nr; k++) {
-                                       one = rename_src[k].one;
-                                       if (basename_same(one, two) &&
-                                               is_exact_match(one, two,
-                                                       contents_too)) {
-                                               j = k;
-                                               break;
-                                       }
-                               }
-
-                               record_rename_pair(i, j, (int)MAX_SCORE);
-                               rename_count++;
-                               break; /* we are done with this entry */
-                       }
-               }
-       }
-
-       /* Have we run out the created file pool?  If so we can avoid
-        * doing the delta matrix altogether.
-        */
-       if (rename_count == rename_dst_nr)
-               goto cleanup;
-
-       if (minimum_score == MAX_SCORE)
-               goto cleanup;
-
-       num_create = (rename_dst_nr - rename_count);
-       num_src = rename_src_nr;
        mx = xmalloc(sizeof(*mx) * num_create * num_src);
        for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
                int base = dst_cnt * num_src;
@@ -452,16 +554,7 @@ void diffcore_rename(struct diff_options *options)
                                        pair_to_free = p;
                        }
                        else {
-                               for (j = 0; j < rename_dst_nr; j++) {
-                                       if (!rename_dst[j].pair)
-                                               continue;
-                                       if (strcmp(rename_dst[j].pair->
-                                                  one->path,
-                                                  p->one->path))
-                                               continue;
-                                       break;
-                               }
-                               if (j < rename_dst_nr)
+                               if (p->one->rename_used)
                                        /* this path remains */
                                        pair_to_free = p;
                        }
@@ -487,27 +580,8 @@ void diffcore_rename(struct diff_options *options)
        *q = outq;
        diff_debug_queue("done collapsing", q);
 
-       /* We need to see which rename source really stays here;
-        * earlier we only checked if the path is left in the result,
-        * but even if a path remains in the result, if that is coming
-        * from copying something else on top of it, then the original
-        * source is lost and does not stay.
-        */
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               if (DIFF_PAIR_RENAME(p) && p->source_stays) {
-                       /* If one appears as the target of a rename-copy,
-                        * then mark p->source_stays = 0; otherwise
-                        * leave it as is.
-                        */
-                       p->source_stays = compute_stays(q, p->one);
-               }
-       }
-
-       for (i = 0; i < rename_dst_nr; i++) {
-               diff_free_filespec_data(rename_dst[i].two);
-               free(rename_dst[i].two);
-       }
+       for (i = 0; i < rename_dst_nr; i++)
+               free_filespec(rename_dst[i].two);
 
        free(rename_dst);
        rename_dst = NULL;
index eb618b1ec00113dabcd5231f141f82e1cdfdca46..cc96c20734bf4184970f5381416637cf6e45ea13 100644 (file)
@@ -29,7 +29,9 @@ struct diff_filespec {
        void *cnt_data;
        const char *funcname_pattern_ident;
        unsigned long size;
+       int count;               /* Reference count */
        int xfrm_flags;          /* for use by the xfrm */
+       int rename_used;         /* Count of rename users */
        unsigned short mode;     /* file mode */
        unsigned sha1_valid : 1; /* if true, use sha1 and trust mode;
                                  * if false, use the name and read from
@@ -43,6 +45,7 @@ struct diff_filespec {
 };
 
 extern struct diff_filespec *alloc_filespec(const char *);
+extern void free_filespec(struct diff_filespec *);
 extern void fill_filespec(struct diff_filespec *, const unsigned char *,
                          unsigned short);
 
@@ -56,7 +59,6 @@ struct diff_filepair {
        struct diff_filespec *two;
        unsigned short int score;
        char status; /* M C R N D U (see Documentation/diff-format.txt) */
-       unsigned source_stays : 1; /* all of R/C are copies */
        unsigned broken_pair : 1;
        unsigned renamed_pair : 1;
        unsigned is_unmerged : 1;
index 9b74ed2f42ad9a452f5a1d9ab55d5ca1e1de2594..2d0a75851284392aa8ae44bc486df6a034d0af13 100644 (file)
@@ -5,11 +5,11 @@
 
 extern char **environ;
 static const char *builtin_exec_path = GIT_EXEC_PATH;
-static const char *current_exec_path;
+static const char *argv_exec_path;
 
-void git_set_exec_path(const char *exec_path)
+void git_set_argv_exec_path(const char *exec_path)
 {
-       current_exec_path = exec_path;
+       argv_exec_path = exec_path;
 }
 
 
@@ -18,8 +18,8 @@ const char *git_exec_path(void)
 {
        const char *env;
 
-       if (current_exec_path)
-               return current_exec_path;
+       if (argv_exec_path)
+               return argv_exec_path;
 
        env = getenv(EXEC_PATH_ENVIRONMENT);
        if (env && *env) {
@@ -29,85 +29,69 @@ const char *git_exec_path(void)
        return builtin_exec_path;
 }
 
+static void add_path(struct strbuf *out, const char *path)
+{
+       if (path && *path) {
+               if (is_absolute_path(path))
+                       strbuf_addstr(out, path);
+               else
+                       strbuf_addstr(out, make_absolute_path(path));
+
+               strbuf_addch(out, ':');
+       }
+}
+
+void setup_path(const char *cmd_path)
+{
+       const char *old_path = getenv("PATH");
+       struct strbuf new_path;
+
+       strbuf_init(&new_path, 0);
+
+       add_path(&new_path, argv_exec_path);
+       add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT));
+       add_path(&new_path, builtin_exec_path);
+       add_path(&new_path, cmd_path);
+
+       if (old_path)
+               strbuf_addstr(&new_path, old_path);
+       else
+               strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin");
+
+       setenv("PATH", new_path.buf, 1);
+
+       strbuf_release(&new_path);
+}
 
 int execv_git_cmd(const char **argv)
 {
-       char git_command[PATH_MAX + 1];
-       int i;
-       const char *paths[] = { current_exec_path,
-                               getenv(EXEC_PATH_ENVIRONMENT),
-                               builtin_exec_path };
-
-       for (i = 0; i < ARRAY_SIZE(paths); ++i) {
-               size_t len;
-               int rc;
-               const char *exec_dir = paths[i];
-               const char *tmp;
-
-               if (!exec_dir || !*exec_dir) continue;
-
-               if (*exec_dir != '/') {
-                       if (!getcwd(git_command, sizeof(git_command))) {
-                               fprintf(stderr, "git: cannot determine "
-                                       "current directory: %s\n",
-                                       strerror(errno));
-                               break;
-                       }
-                       len = strlen(git_command);
-
-                       /* Trivial cleanup */
-                       while (!prefixcmp(exec_dir, "./")) {
-                               exec_dir += 2;
-                               while (*exec_dir == '/')
-                                       exec_dir++;
-                       }
-
-                       rc = snprintf(git_command + len,
-                                     sizeof(git_command) - len, "/%s",
-                                     exec_dir);
-                       if (rc < 0 || rc >= sizeof(git_command) - len) {
-                               fprintf(stderr, "git: command name given "
-                                       "is too long.\n");
-                               break;
-                       }
-               } else {
-                       if (strlen(exec_dir) + 1 > sizeof(git_command)) {
-                               fprintf(stderr, "git: command name given "
-                                       "is too long.\n");
-                               break;
-                       }
-                       strcpy(git_command, exec_dir);
-               }
-
-               len = strlen(git_command);
-               rc = snprintf(git_command + len, sizeof(git_command) - len,
-                             "/git-%s", argv[0]);
-               if (rc < 0 || rc >= sizeof(git_command) - len) {
-                       fprintf(stderr,
-                               "git: command name given is too long.\n");
-                       break;
-               }
+       struct strbuf cmd;
+       const char *tmp;
 
-               /* argv[0] must be the git command, but the argv array
-                * belongs to the caller, and my be reused in
-                * subsequent loop iterations. Save argv[0] and
-                * restore it on error.
-                */
+       strbuf_init(&cmd, 0);
+       strbuf_addf(&cmd, "git-%s", argv[0]);
 
-               tmp = argv[0];
-               argv[0] = git_command;
+       /*
+        * argv[0] must be the git command, but the argv array
+        * belongs to the caller, and may be reused in
+        * subsequent loop iterations. Save argv[0] and
+        * restore it on error.
+        */
+       tmp = argv[0];
+       argv[0] = cmd.buf;
 
-               trace_argv_printf(argv, -1, "trace: exec:");
+       trace_argv_printf(argv, -1, "trace: exec:");
 
-               /* execve() can only ever return if it fails */
-               execve(git_command, (char **)argv, environ);
+       /* execvp() can only ever return if it fails */
+       execvp(cmd.buf, (char **)argv);
 
-               trace_printf("trace: exec failed: %s\n", strerror(errno));
+       trace_printf("trace: exec failed: %s\n", strerror(errno));
 
-               argv[0] = tmp;
-       }
-       return -1;
+       argv[0] = tmp;
 
+       strbuf_release(&cmd);
+
+       return -1;
 }
 
 
index 849a8395a0c03ba2976fe4802eee8150fdec5816..a892355c8212298130fb3925c6cba352ed6999b6 100644 (file)
@@ -1,8 +1,9 @@
 #ifndef GIT_EXEC_CMD_H
 #define GIT_EXEC_CMD_H
 
-extern void git_set_exec_path(const char *exec_path);
+extern void git_set_argv_exec_path(const char *exec_path);
 extern const char* git_exec_path(void);
+extern void setup_path(const char *);
 extern int execv_git_cmd(const char **argv); /* NULL terminated */
 extern int execl_git_cmd(const char *cmd, ...);
 
index 388887a556e47fa803c1965777d52532ac233b05..b74f44df603fa38dd2954aebe7a56a8480450236 100755 (executable)
@@ -1,12 +1,14 @@
 #!/bin/sh
 
-USAGE='[start|bad|good|next|reset|visualize|replay|log|run]'
+USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]'
 LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
         reset bisect state and start bisection.
 git bisect bad [<rev>]
         mark <rev> a known-bad revision.
 git bisect good [<rev>...]
         mark <rev>... known-good revisions.
+git bisect skip [<rev>...]
+        mark <rev>... untestable revisions.
 git bisect next
         find next bisection to test and check it out.
 git bisect reset [<branch>]
@@ -64,7 +66,7 @@ bisect_start() {
                    branch=`cat "$GIT_DIR/head-name"`
                else
                    branch=master
-               fi
+               fi
                git checkout $branch || exit
                ;;
        refs/heads/*)
@@ -95,75 +97,74 @@ bisect_start() {
            arg="$1"
            case "$arg" in
            --)
-               shift
+               shift
                break
                ;;
            *)
-               rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
+               rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
                    test $has_double_dash -eq 1 &&
                        die "'$arg' does not appear to be a valid revision"
                    break
                }
-               if [ $bad_seen -eq 0 ]; then
-                   bad_seen=1
-                   bisect_write_bad "$rev"
-               else
-                   bisect_write_good "$rev"
-               fi
-               shift
+               case $bad_seen in
+               0) state='bad' ; bad_seen=1 ;;
+               *) state='good' ;;
+               esac
+               bisect_write "$state" "$rev" 'nolog'
+               shift
                ;;
            esac
-        done
+       done
 
        sq "$@" >"$GIT_DIR/BISECT_NAMES"
        echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG"
        bisect_auto_next
 }
 
-bisect_bad() {
-       bisect_autostart
-       case "$#" in
-       0)
-               rev=$(git rev-parse --verify HEAD) ;;
-       1)
-               rev=$(git rev-parse --verify "$1^{commit}") ;;
-       *)
-               usage ;;
-       esac || exit
-       bisect_write_bad "$rev"
-       echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
-       bisect_auto_next
-}
-
-bisect_write_bad() {
-       rev="$1"
-       echo "$rev" >"$GIT_DIR/refs/bisect/bad"
-       echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+bisect_write() {
+       state="$1"
+       rev="$2"
+       nolog="$3"
+       case "$state" in
+               bad)            tag="$state" ;;
+               good|skip)      tag="$state"-"$rev" ;;
+               *)              die "Bad bisect_write argument: $state" ;;
+       esac
+       echo "$rev" >"$GIT_DIR/refs/bisect/$tag"
+       echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+       test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
 }
 
-bisect_good() {
+bisect_state() {
        bisect_autostart
-        case "$#" in
-       0)    revs=$(git rev-parse --verify HEAD) || exit ;;
-       *)    revs=$(git rev-parse --revs-only --no-flags "$@") &&
-               test '' != "$revs" || die "Bad rev input: $@" ;;
+       state=$1
+       case "$#,$state" in
+       0,*)
+               die "Please call 'bisect_state' with at least one argument." ;;
+       1,bad|1,good|1,skip)
+               rev=$(git rev-parse --verify HEAD) ||
+                       die "Bad rev input: HEAD"
+               bisect_write "$state" "$rev" ;;
+       2,bad)
+               rev=$(git rev-parse --verify "$2^{commit}") ||
+                       die "Bad rev input: $2"
+               bisect_write "$state" "$rev" ;;
+       *,good|*,skip)
+               shift
+               revs=$(git rev-parse --revs-only --no-flags "$@") &&
+                       test '' != "$revs" || die "Bad rev input: $@"
+               for rev in $revs
+               do
+                       rev=$(git rev-parse --verify "$rev^{commit}") ||
+                               die "Bad rev commit: $rev^{commit}"
+                       bisect_write "$state" "$rev"
+               done ;;
+       *)
+               usage ;;
        esac
-       for rev in $revs
-       do
-               rev=$(git rev-parse --verify "$rev^{commit}") || exit
-               bisect_write_good "$rev"
-               echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
-
-       done
        bisect_auto_next
 }
 
-bisect_write_good() {
-       rev="$1"
-       echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
-       echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
-}
-
 bisect_next_check() {
        missing_good= missing_bad=
        git show-ref -q --verify refs/bisect/bad || missing_bad=t
@@ -206,17 +207,97 @@ bisect_auto_next() {
        bisect_next_check && bisect_next || :
 }
 
+filter_skipped() {
+       _eval="$1"
+       _skip="$2"
+
+       if [ -z "$_skip" ]; then
+               eval $_eval
+               return
+       fi
+
+       # Let's parse the output of:
+       # "git rev-list --bisect-vars --bisect-all ..."
+       eval $_eval | while read hash line
+       do
+               case "$VARS,$FOUND,$TRIED,$hash" in
+                       # We display some vars.
+                       1,*,*,*) echo "$hash $line" ;;
+
+                       # Split line.
+                       ,*,*,---*) ;;
+
+                       # We had nothing to search.
+                       ,,,bisect_rev*)
+                               echo "bisect_rev="
+                               VARS=1
+                               ;;
+
+                       # We did not find a good bisect rev.
+                       # This should happen only if the "bad"
+                       # commit is also a "skip" commit.
+                       ,,*,bisect_rev*)
+                               echo "bisect_rev=$TRIED"
+                               VARS=1
+                               ;;
+
+                       # We are searching.
+                       ,,*,*)
+                               TRIED="${TRIED:+$TRIED|}$hash"
+                               case "$_skip" in
+                               *$hash*) ;;
+                               *)
+                                       echo "bisect_rev=$hash"
+                                       echo "bisect_tried=\"$TRIED\""
+                                       FOUND=1
+                                       ;;
+                               esac
+                               ;;
+
+                       # We have already found a rev to be tested.
+                       ,1,*,bisect_rev*) VARS=1 ;;
+                       ,1,*,*) ;;
+
+                       # ???
+                       *) die "filter_skipped error " \
+                           "VARS: '$VARS' " \
+                           "FOUND: '$FOUND' " \
+                           "TRIED: '$TRIED' " \
+                           "hash: '$hash' " \
+                           "line: '$line'"
+                       ;;
+               esac
+       done
+}
+
+exit_if_skipped_commits () {
+       _tried=$1
+       if expr "$_tried" : ".*[|].*" > /dev/null ; then
+               echo "There are only 'skip'ped commit left to test."
+               echo "The first bad commit could be any of:"
+               echo "$_tried" | sed -e 's/[|]/\n/g'
+               echo "We cannot bisect more!"
+               exit 2
+       fi
+}
+
 bisect_next() {
-        case "$#" in 0) ;; *) usage ;; esac
+       case "$#" in 0) ;; *) usage ;; esac
        bisect_autostart
        bisect_next_check good
 
+       skip=$(git for-each-ref --format='%(objectname)' \
+               "refs/bisect/skip-*" | tr '[\012]' ' ') || exit
+
+       BISECT_OPT=''
+       test -n "$skip" && BISECT_OPT='--bisect-all'
+
        bad=$(git rev-parse --verify refs/bisect/bad) &&
        good=$(git for-each-ref --format='^%(objectname)' \
                "refs/bisect/good-*" | tr '[\012]' ' ') &&
-       eval="git rev-list --bisect-vars $good $bad --" &&
+       eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
        eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
-       eval=$(eval "$eval") &&
+       eval=$(filter_skipped "$eval" "$skip") &&
        eval "$eval" || exit
 
        if [ -z "$bisect_rev" ]; then
@@ -224,11 +305,16 @@ bisect_next() {
                exit 1
        fi
        if [ "$bisect_rev" = "$bad" ]; then
+               exit_if_skipped_commits "$bisect_tried"
                echo "$bisect_rev is first bad commit"
                git diff-tree --pretty $bisect_rev
                exit 0
        fi
 
+       # We should exit here only if the "bad"
+       # commit is also a "skip" commit (see above).
+       exit_if_skipped_commits "$bisect_rev"
+
        echo "Bisecting: $bisect_nr revisions left to test after this"
        echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect"
        git checkout -q new-bisect || exit
@@ -250,12 +336,10 @@ bisect_reset() {
           else
               branch=master
           fi ;;
-       1) git show-ref --verify --quiet -- "refs/heads/$1" || {
-              echo >&2 "$1 does not seem to be a valid branch"
-              exit 1
-          }
+       1) git show-ref --verify --quiet -- "refs/heads/$1" ||
+              die "$1 does not seem to be a valid branch"
           branch="$1" ;;
-        *)
+       *)
            usage ;;
        esac
        if git checkout "$branch"; then
@@ -273,10 +357,7 @@ bisect_clean_state() {
 }
 
 bisect_replay () {
-       test -r "$1" || {
-               echo >&2 "cannot read $1 for replaying"
-               exit 1
-       }
+       test -r "$1" || die "cannot read $1 for replaying"
        bisect_reset
        while read bisect command rev
        do
@@ -284,21 +365,11 @@ bisect_replay () {
                case "$command" in
                start)
                        cmd="bisect_start $rev"
-                       eval "$cmd"
-                       ;;
-               good)
-                       echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
-                       echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
-                       echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
-                       ;;
-               bad)
-                       echo "$rev" >"$GIT_DIR/refs/bisect/bad"
-                       echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
-                       echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
-                       ;;
+                       eval "$cmd" ;;
+               good|bad|skip)
+                       bisect_write "$command" "$rev" ;;
                *)
-                       echo >&2 "?? what are you talking about?"
-                       exit 1 ;;
+                       die "?? what are you talking about?" ;;
                esac
        done <"$1"
        bisect_auto_next
@@ -320,24 +391,31 @@ bisect_run () {
          exit $res
       fi
 
-      # Use "bisect_good" or "bisect_bad"
-      # depending on run success or failure.
-      if [ $res -gt 0 ]; then
-         next_bisect='bisect_bad'
+      # Find current state depending on run success or failure.
+      # A special exit code of 125 means cannot test.
+      if [ $res -eq 125 ]; then
+         state='skip'
+      elif [ $res -gt 0 ]; then
+         state='bad'
       else
-         next_bisect='bisect_good'
+         state='good'
       fi
 
-      # We have to use a subshell because bisect_good or
-      # bisect_bad functions can exit.
-      ( $next_bisect > "$GIT_DIR/BISECT_RUN" )
+      # We have to use a subshell because "bisect_state" can exit.
+      ( bisect_state $state > "$GIT_DIR/BISECT_RUN" )
       res=$?
 
       cat "$GIT_DIR/BISECT_RUN"
 
+      if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \
+               > /dev/null; then
+         echo >&2 "bisect run cannot continue any more"
+         exit $res
+      fi
+
       if [ $res -ne 0 ]; then
          echo >&2 "bisect run failed:"
-         echo >&2 "$next_bisect exited with error code $res"
+         echo >&2 "'bisect_state $state' exited with error code $res"
          exit $res
       fi
 
@@ -359,10 +437,8 @@ case "$#" in
     case "$cmd" in
     start)
         bisect_start "$@" ;;
-    bad)
-        bisect_bad "$@" ;;
-    good)
-        bisect_good "$@" ;;
+    bad|good|skip)
+        bisect_state "$cmd" "$@" ;;
     next)
         # Not sure we want "next" at the UI level anymore.
         bisect_next "$@" ;;
index f284c88a46b5cc7d6e75b78346829ce03e60b060..26844af4390b8b0902ad853f40e4d9f60b75fd23 100755 (executable)
@@ -82,6 +82,7 @@
     }
 }
 
+my $noparent = "0000000000000000000000000000000000000000";
 if ($parent) {
     my $found;
     # double check that it's a valid parent
 } else { # we don't have a parent from the cmdline...
     if (@parents == 1) { # it's safe to get it from the commit
        $parent = $parents[0];
-    } else { # or perhaps not!
-       die "This commit has more than one parent -- please name the parent you want to use explicitly";
+    } elsif (@parents == 0) { # there is no parent
+        $parent = $noparent;
+    } else { # cannot choose automatically from multiple parents
+        die "This commit has more than one parent -- please name the parent you want to use explicitly";
     }
 }
 
 }
 close MSG;
 
-`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+if ($parent eq $noparent) {
+    `git-diff-tree --binary -p --root $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+} else {
+    `git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+}
 
 ## apply non-binary changes
 
index 9f4f3134b60174495c23968cf1ca01ffdbc49bdf..a68b40386bde1c47c33a19514ec3a22db7fb0e4b 100755 (executable)
@@ -192,10 +192,10 @@ merge_file () {
     case "$merge_tool" in
        kdiff3)
            if base_present ; then
-               (kdiff3 --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \
+               ("$merge_tool_path" --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \
                    -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
            else
-               (kdiff3 --auto --L1 "$path (Local)" --L2 "$path (Remote)" \
+               ("$merge_tool_path" --auto --L1 "$path (Local)" --L2 "$path (Remote)" \
                    -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
            fi
            status=$?
@@ -203,35 +203,35 @@ merge_file () {
            ;;
        tkdiff)
            if base_present ; then
-               tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
+               "$merge_tool_path" -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
            else
-               tkdiff -o "$path" -- "$LOCAL" "$REMOTE"
+               "$merge_tool_path" -o "$path" -- "$LOCAL" "$REMOTE"
            fi
            status=$?
            save_backup
            ;;
        meld|vimdiff)
            touch "$BACKUP"
-           $merge_tool -- "$LOCAL" "$path" "$REMOTE"
+           "$merge_tool_path" -- "$LOCAL" "$path" "$REMOTE"
            check_unchanged
            save_backup
            ;;
        gvimdiff)
                touch "$BACKUP"
-               gvimdiff -f -- "$LOCAL" "$path" "$REMOTE"
+               "$merge_tool_path" -f -- "$LOCAL" "$path" "$REMOTE"
                check_unchanged
                save_backup
                ;;
        xxdiff)
            touch "$BACKUP"
            if base_present ; then
-               xxdiff -X --show-merged-pane \
+               "$merge_tool_path" -X --show-merged-pane \
                    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
                    -R 'Accel.Search: "Ctrl+F"' \
                    -R 'Accel.SearchForward: "Ctrl-G"' \
                    --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE"
            else
-               xxdiff -X --show-merged-pane \
+               "$merge_tool_path" -X --show-merged-pane \
                    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
                    -R 'Accel.Search: "Ctrl+F"' \
                    -R 'Accel.SearchForward: "Ctrl-G"' \
@@ -243,18 +243,28 @@ merge_file () {
        opendiff)
            touch "$BACKUP"
            if base_present; then
-               opendiff "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat
+               "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat
            else
-               opendiff "$LOCAL" "$REMOTE" -merge "$path" | cat
+               "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$path" | cat
+           fi
+           check_unchanged
+           save_backup
+           ;;
+       ecmerge)
+           touch "$BACKUP"
+           if base_present; then
+               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$path"
+           else
+               "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$path"
            fi
            check_unchanged
            save_backup
            ;;
        emerge)
            if base_present ; then
-               emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")"
+               "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")"
            else
-               emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")"
+               "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")"
            fi
            status=$?
            save_backup
@@ -297,17 +307,38 @@ do
     shift
 done
 
+valid_tool() {
+       case "$1" in
+               kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
+                       ;; # happy
+               *)
+                       return 1
+                       ;;
+       esac
+}
+
+init_merge_tool_path() {
+       merge_tool_path=`git config mergetool.$1.path`
+       if test -z "$merge_tool_path" ; then
+               case "$1" in
+                       emerge)
+                               merge_tool_path=emacs
+                               ;;
+                       *)
+                               merge_tool_path=$1
+                               ;;
+               esac
+       fi
+}
+
+
 if test -z "$merge_tool"; then
     merge_tool=`git config merge.tool`
-    case "$merge_tool" in
-       kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | "")
-           ;; # happy
-       *)
+    if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
            echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
            echo >&2 "Resetting to default..."
            unset merge_tool
-           ;;
-    esac
+    fi
 fi
 
 if test -z "$merge_tool" ; then
@@ -329,40 +360,30 @@ if test -z "$merge_tool" ; then
     merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
     echo "merge tool candidates: $merge_tool_candidates"
     for i in $merge_tool_candidates; do
-        if test $i = emerge ; then
-            cmd=emacs
-        else
-            cmd=$i
-        fi
-        if type $cmd > /dev/null 2>&1; then
+        init_merge_tool_path $i
+        if type "$merge_tool_path" > /dev/null 2>&1; then
             merge_tool=$i
             break
         fi
     done
     if test -z "$merge_tool" ; then
-       echo "No available merge resolution programs available."
+       echo "No known merge resolution program available."
        exit 1
     fi
+else
+    if ! valid_tool "$merge_tool"; then
+        echo >&2 "Unknown merge_tool $merge_tool"
+        exit 1
+    fi
+
+    init_merge_tool_path "$merge_tool"
+
+    if ! type "$merge_tool_path" > /dev/null 2>&1; then
+        echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
+        exit 1
+    fi
 fi
 
-case "$merge_tool" in
-    kdiff3|tkdiff|meld|xxdiff|vimdiff|gvimdiff|opendiff)
-       if ! type "$merge_tool" > /dev/null 2>&1; then
-           echo "The merge tool $merge_tool is not available"
-           exit 1
-       fi
-       ;;
-    emerge)
-       if ! type "emacs" > /dev/null 2>&1; then
-           echo "Emacs is not available"
-           exit 1
-       fi
-       ;;
-    *)
-       echo "Unknown merge tool: $merge_tool"
-       exit 1
-       ;;
-esac
 
 if test $# -eq 0 ; then
        files=`git ls-files -u | sed -e 's/^[^  ]*      //' | sort -u`
index 0dd77b4005b9d80bfe6c606954d7666df77abc57..76dc679e62cf32a5ba04902558812e3244baf936 100755 (executable)
@@ -116,7 +116,7 @@ pick_one () {
                sha1=$(git rev-parse --short $sha1)
                output warn Fast forward to $sha1
        else
-               output git cherry-pick $STRATEGY "$@"
+               output git cherry-pick "$@"
        fi
 }
 
@@ -172,6 +172,8 @@ pick_one_preserving_merges () {
                        author_script=$(get_author_ident_from_commit $sha1)
                        eval "$author_script"
                        msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
+                       # No point in merging the first parent, that's HEAD
+                       new_parents=${new_parents# $first_parent}
                        # NEEDSWORK: give rerere a chance
                        if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
                                GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
@@ -184,7 +186,7 @@ pick_one_preserving_merges () {
                        fi
                        ;;
                *)
-                       output git cherry-pick $STRATEGY "$@" ||
+                       output git cherry-pick "$@" ||
                                die_with_patch $sha1 "Could not pick $sha1"
                        ;;
                esac
@@ -387,7 +389,6 @@ do
                output git reset --hard && do_rest
                ;;
        -s|--strategy)
-               shift
                case "$#,$1" in
                *,*=*)
                        STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
index 1583402a060793c25e49c3446c2a35fe27101883..224cca98eea324cabf30885f7c92c254b184410b 100755 (executable)
@@ -59,7 +59,7 @@ continue_merge () {
                die "$RESOLVEMSG"
        fi
 
-       cmt=`cat $dotest/current`
+       cmt=`cat "$dotest/current"`
        if ! git diff-index --quiet HEAD
        then
                if ! git-commit -C "$cmt"
@@ -84,14 +84,14 @@ continue_merge () {
 }
 
 call_merge () {
-       cmt="$(cat $dotest/cmt.$1)"
+       cmt="$(cat "$dotest/cmt.$1")"
        echo "$cmt" > "$dotest/current"
        hd=$(git rev-parse --verify HEAD)
        cmt_name=$(git symbolic-ref HEAD)
-       msgnum=$(cat $dotest/msgnum)
-       end=$(cat $dotest/end)
+       msgnum=$(cat "$dotest/msgnum")
+       end=$(cat "$dotest/end")
        eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
-       eval GITHEAD_$hd='"$(cat $dotest/onto_name)"'
+       eval GITHEAD_$hd='$(cat "$dotest/onto_name")'
        export GITHEAD_$cmt GITHEAD_$hd
        git-merge-$strategy "$cmt^" -- "$hd" "$cmt"
        rv=$?
@@ -140,10 +140,10 @@ do
                }
                if test -d "$dotest"
                then
-                       prev_head="`cat $dotest/prev_head`"
-                       end="`cat $dotest/end`"
-                       msgnum="`cat $dotest/msgnum`"
-                       onto="`cat $dotest/onto`"
+                       prev_head=$(cat "$dotest/prev_head")
+                       end=$(cat "$dotest/end")
+                       msgnum=$(cat "$dotest/msgnum")
+                       onto=$(cat "$dotest/onto")
                        continue_merge
                        while test "$msgnum" -le "$end"
                        do
@@ -160,11 +160,11 @@ do
                if test -d "$dotest"
                then
                        git rerere clear
-                       prev_head="`cat $dotest/prev_head`"
-                       end="`cat $dotest/end`"
-                       msgnum="`cat $dotest/msgnum`"
+                       prev_head=$(cat "$dotest/prev_head")
+                       end=$(cat "$dotest/end")
+                       msgnum=$(cat "$dotest/msgnum")
                        msgnum=$(($msgnum + 1))
-                       onto="`cat $dotest/onto`"
+                       onto=$(cat "$dotest/onto")
                        while test "$msgnum" -le "$end"
                        do
                                call_merge "$msgnum"
index e72adc4d91efb8eb6dc96c1f431c8863c408439b..7220635c96a75a17634297b33bac8322647b8270 100755 (executable)
@@ -83,9 +83,6 @@ for name in $names ; do
        fullbases="$fullbases pack-$name"
        chmod a-w "$PACKTMP-$name.pack"
        chmod a-w "$PACKTMP-$name.idx"
-       if test "$quiet" != '-q'; then
-           echo "Pack pack-$name created."
-       fi
        mkdir -p "$PACKDIR" || exit
 
        for sfx in pack idx
index 3c325fd1339608d681d3485aa37647d9ac734037..86d7d4c4e742c80e936d5f1c9f2037327eba7287 100755 (executable)
@@ -110,7 +110,7 @@ esac
 if [ -z "$SUBDIRECTORY_OK" ]
 then
        : ${GIT_DIR=.git}
-       GIT_DIR=$(GIT_DIR="$GIT_DIR" git rev-parse --git-dir) || {
+       test -z "$(git rev-parse --show-cdup)" || {
                exit=$?
                echo >&2 "You need to run this command from the toplevel of the working tree."
                exit $exit
diff --git a/git-svnimport.perl b/git-svnimport.perl
deleted file mode 100755 (executable)
index ea8c1b2..0000000
+++ /dev/null
@@ -1,976 +0,0 @@
-#!/usr/bin/perl -w
-
-# This tool is copyright (c) 2005, Matthias Urlichs.
-# It is released under the Gnu Public License, version 2.
-#
-# The basic idea is to pull and analyze SVN changes.
-#
-# Checking out the files is done by a single long-running SVN connection.
-#
-# The head revision is on branch "origin" by default.
-# You can change that with the '-o' option.
-
-use strict;
-use warnings;
-use Getopt::Std;
-use File::Copy;
-use File::Spec;
-use File::Temp qw(tempfile);
-use File::Path qw(mkpath);
-use File::Basename qw(basename dirname);
-use Time::Local;
-use IO::Pipe;
-use POSIX qw(strftime dup2);
-use IPC::Open2;
-use SVN::Core;
-use SVN::Ra;
-
-die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
-
-$SIG{'PIPE'}="IGNORE";
-$ENV{'TZ'}="UTC";
-
-our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
-    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,
-    $opt_P,$opt_R);
-
-sub usage() {
-       print STDERR <<END;
-Usage: ${\basename $0}     # fetch/update GIT from SVN
-       [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-R repack_each_revs]
-       [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
-       [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
-       [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL]
-END
-       exit(1);
-}
-
-getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:R:uv") or usage();
-usage if $opt_h;
-
-my $tag_name = $opt_t || "tags";
-my $trunk_name = defined $opt_T ? $opt_T : "trunk";
-my $branch_name = $opt_b || "branches";
-my $project_name = $opt_P || "";
-$project_name = "/" . $project_name if ($project_name);
-my $repack_after = $opt_R || 1000;
-my $root_pool = SVN::Pool->new_default;
-
-@ARGV == 1 or @ARGV == 2 or usage();
-
-$opt_o ||= "origin";
-$opt_s ||= 1;
-my $git_tree = $opt_C;
-$git_tree ||= ".";
-
-my $svn_url = $ARGV[0];
-my $svn_dir = $ARGV[1];
-
-our @mergerx = ();
-if ($opt_m) {
-       my $branch_esc = quotemeta ($branch_name);
-       my $trunk_esc  = quotemeta ($trunk_name);
-       @mergerx =
-       (
-               qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
-               qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
-               qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i
-       );
-}
-if ($opt_M) {
-       unshift (@mergerx, qr/$opt_M/);
-}
-
-# Absolutize filename now, since we will have chdir'ed by the time we
-# get around to opening it.
-$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
-
-our %users = ();
-our $users_file = undef;
-sub read_users($) {
-       $users_file = File::Spec->rel2abs(@_);
-       die "Cannot open $users_file\n" unless -f $users_file;
-       open(my $authors,$users_file);
-       while(<$authors>) {
-               chomp;
-               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
-               (my $user,my $name,my $email) = ($1,$2,$3);
-               $users{$user} = [$name,$email];
-       }
-       close($authors);
-}
-
-select(STDERR); $|=1; select(STDOUT);
-
-
-package SVNconn;
-# Basic SVN connection.
-# We're only interested in connecting and downloading, so ...
-
-use File::Spec;
-use File::Temp qw(tempfile);
-use POSIX qw(strftime dup2);
-use Fcntl qw(SEEK_SET);
-
-sub new {
-       my($what,$repo) = @_;
-       $what=ref($what) if ref($what);
-
-       my $self = {};
-       $self->{'buffer'} = "";
-       bless($self,$what);
-
-       $repo =~ s#/+$##;
-       $self->{'fullrep'} = $repo;
-       $self->conn();
-
-       return $self;
-}
-
-sub conn {
-       my $self = shift;
-       my $repo = $self->{'fullrep'};
-       my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider,
-                         SVN::Client::get_ssl_server_trust_file_provider,
-                         SVN::Client::get_username_provider]);
-       my $s = SVN::Ra->new(url => $repo, auth => $auth, pool => $root_pool);
-       die "SVN connection to $repo: $!\n" unless defined $s;
-       $self->{'svn'} = $s;
-       $self->{'repo'} = $repo;
-       $self->{'maxrev'} = $s->get_latest_revnum();
-}
-
-sub file {
-       my($self,$path,$rev) = @_;
-
-       my ($fh, $name) = tempfile('gitsvn.XXXXXX',
-                   DIR => File::Spec->tmpdir(), UNLINK => 1);
-
-       print "... $rev $path ...\n" if $opt_v;
-       my (undef, $properties);
-       $path =~ s#^/*##;
-       my $subpool = SVN::Pool::new_default_sub;
-       eval { (undef, $properties)
-                  = $self->{'svn'}->get_file($path,$rev,$fh); };
-       if($@) {
-               return undef if $@ =~ /Attempted to get checksum/;
-               die $@;
-       }
-       my $mode;
-       if (exists $properties->{'svn:executable'}) {
-               $mode = '100755';
-       } elsif (exists $properties->{'svn:special'}) {
-               my ($special_content, $filesize);
-               $filesize = tell $fh;
-               seek $fh, 0, SEEK_SET;
-               read $fh, $special_content, $filesize;
-               if ($special_content =~ s/^link //) {
-                       $mode = '120000';
-                       seek $fh, 0, SEEK_SET;
-                       truncate $fh, 0;
-                       print $fh $special_content;
-               } else {
-                       die "unexpected svn:special file encountered";
-               }
-       } else {
-               $mode = '100644';
-       }
-       close ($fh);
-
-       return ($name, $mode);
-}
-
-sub ignore {
-       my($self,$path,$rev) = @_;
-
-       print "... $rev $path ...\n" if $opt_v;
-       $path =~ s#^/*##;
-       my $subpool = SVN::Pool::new_default_sub;
-       my (undef,undef,$properties)
-           = $self->{'svn'}->get_dir($path,$rev,undef);
-       if (exists $properties->{'svn:ignore'}) {
-               my ($fh, $name) = tempfile('gitsvn.XXXXXX',
-                                          DIR => File::Spec->tmpdir(),
-                                          UNLINK => 1);
-               print $fh $properties->{'svn:ignore'};
-               close($fh);
-               return $name;
-       } else {
-               return undef;
-       }
-}
-
-sub dir_list {
-       my($self,$path,$rev) = @_;
-       $path =~ s#^/*##;
-       my $subpool = SVN::Pool::new_default_sub;
-       my ($dirents,undef,$properties)
-           = $self->{'svn'}->get_dir($path,$rev,undef);
-       return $dirents;
-}
-
-package main;
-use URI;
-
-our $svn = $svn_url;
-$svn .= "/$svn_dir" if defined $svn_dir;
-my $svn2 = SVNconn->new($svn);
-$svn = SVNconn->new($svn);
-
-my $lwp_ua;
-if($opt_d or $opt_D) {
-       $svn_url = URI->new($svn_url)->canonical;
-       if($opt_D) {
-               $svn_dir =~ s#/*$#/#;
-       } else {
-               $svn_dir = "";
-       }
-       if ($svn_url->scheme eq "http") {
-               use LWP::UserAgent;
-               $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []);
-       } else {
-               print STDERR "Warning: not HTTP; turning off direct file access\n";
-               $opt_d=0;
-       }
-}
-
-sub pdate($) {
-       my($d) = @_;
-       $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)#
-               or die "Unparseable date: $d\n";
-       my $y=$1; $y-=1900 if $y>1900;
-       return timegm($6||0,$5,$4,$3,$2-1,$y);
-}
-
-sub getwd() {
-       my $pwd = `pwd`;
-       chomp $pwd;
-       return $pwd;
-}
-
-
-sub get_headref($$) {
-    my $name    = shift;
-    my $git_dir = shift;
-    my $sha;
-
-    if (open(C,"$git_dir/refs/heads/$name")) {
-       chomp($sha = <C>);
-       close(C);
-       length($sha) == 40
-           or die "Cannot get head id for $name ($sha): $!\n";
-    }
-    return $sha;
-}
-
-
--d $git_tree
-       or mkdir($git_tree,0777)
-       or die "Could not create $git_tree: $!";
-chdir($git_tree);
-
-my $orig_branch = "";
-my $forward_master = 0;
-my %branches;
-
-my $git_dir = $ENV{"GIT_DIR"} || ".git";
-$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
-$ENV{"GIT_DIR"} = $git_dir;
-my $orig_git_index;
-$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
-my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
-                                   DIR => File::Spec->tmpdir());
-close ($git_ih);
-$ENV{GIT_INDEX_FILE} = $git_index;
-my $maxnum = 0;
-my $last_rev = "";
-my $last_branch;
-my $current_rev = $opt_s || 1;
-unless(-d $git_dir) {
-       system("git-init");
-       die "Cannot init the GIT db at $git_tree: $?\n" if $?;
-       system("git-read-tree");
-       die "Cannot init an empty tree: $?\n" if $?;
-
-       $last_branch = $opt_o;
-       $orig_branch = "";
-} else {
-       -f "$git_dir/refs/heads/$opt_o"
-               or die "Branch '$opt_o' does not exist.\n".
-                      "Either use the correct '-o branch' option,\n".
-                      "or import to a new repository.\n";
-
-       -f "$git_dir/svn2git"
-               or die "'$git_dir/svn2git' does not exist.\n".
-                      "You need that file for incremental imports.\n";
-       open(F, "git-symbolic-ref HEAD |") or
-               die "Cannot run git-symbolic-ref: $!\n";
-       chomp ($last_branch = <F>);
-       $last_branch = basename($last_branch);
-       close(F);
-       unless($last_branch) {
-               warn "Cannot read the last branch name: $! -- assuming 'master'\n";
-               $last_branch = "master";
-       }
-       $orig_branch = $last_branch;
-       $last_rev = get_headref($orig_branch, $git_dir);
-       if (-f "$git_dir/SVN2GIT_HEAD") {
-               die <<EOM;
-SVN2GIT_HEAD exists.
-Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD.
-You may need to run
-
-    git-read-tree -m -u SVN2GIT_HEAD HEAD
-EOM
-       }
-       system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD");
-
-       $forward_master =
-           $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
-           system('cmp', '-s', "$git_dir/refs/heads/master",
-                               "$git_dir/refs/heads/$opt_o") == 0;
-
-       # populate index
-       system('git-read-tree', $last_rev);
-       die "read-tree failed: $?\n" if $?;
-
-       # Get the last import timestamps
-       open my $B,"<", "$git_dir/svn2git";
-       while(<$B>) {
-               chomp;
-               my($num,$branch,$ref) = split;
-               $branches{$branch}{$num} = $ref;
-               $branches{$branch}{"LAST"} = $ref;
-               $current_rev = $num+1 if $current_rev <= $num;
-       }
-       close($B);
-}
--d $git_dir
-       or die "Could not create git subdir ($git_dir).\n";
-
-my $default_authors = "$git_dir/svn-authors";
-if ($opt_A) {
-       read_users($opt_A);
-       copy($opt_A,$default_authors) or die "Copy failed: $!";
-} else {
-       read_users($default_authors) if -f $default_authors;
-}
-
-open BRANCHES,">>", "$git_dir/svn2git";
-
-sub node_kind($$) {
-       my ($svnpath, $revision) = @_;
-       $svnpath =~ s#^/*##;
-       my $subpool = SVN::Pool::new_default_sub;
-       my $kind = $svn->{'svn'}->check_path($svnpath,$revision);
-       return $kind;
-}
-
-sub get_file($$$) {
-       my($svnpath,$rev,$path) = @_;
-
-       # now get it
-       my ($name,$mode);
-       if($opt_d) {
-               my($req,$res);
-
-               # /svn/!svn/bc/2/django/trunk/django-docs/build.py
-               my $url=$svn_url->clone();
-               $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath");
-               print "... $path...\n" if $opt_v;
-               $req = HTTP::Request->new(GET => $url);
-               $res = $lwp_ua->request($req);
-               if ($res->is_success) {
-                       my $fh;
-                       ($fh, $name) = tempfile('gitsvn.XXXXXX',
-                       DIR => File::Spec->tmpdir(), UNLINK => 1);
-                       print $fh $res->content;
-                       close($fh) or die "Could not write $name: $!\n";
-               } else {
-                       return undef if $res->code == 301; # directory?
-                       die $res->status_line." at $url\n";
-               }
-               $mode = '0644'; # can't obtain mode via direct http request?
-       } else {
-               ($name,$mode) = $svn->file("$svnpath",$rev);
-               return undef unless defined $name;
-       }
-
-       my $pid = open(my $F, '-|');
-       die $! unless defined $pid;
-       if (!$pid) {
-           exec("git-hash-object", "-w", $name)
-               or die "Cannot create object: $!\n";
-       }
-       my $sha = <$F>;
-       chomp $sha;
-       close $F;
-       unlink $name;
-       return [$mode, $sha, $path];
-}
-
-sub get_ignore($$$$$) {
-       my($new,$old,$rev,$path,$svnpath) = @_;
-
-       return unless $opt_I;
-       my $name = $svn->ignore("$svnpath",$rev);
-       if ($path eq '/') {
-               $path = $opt_I;
-       } else {
-               $path = File::Spec->catfile($path,$opt_I);
-       }
-       if (defined $name) {
-               my $pid = open(my $F, '-|');
-               die $! unless defined $pid;
-               if (!$pid) {
-                       exec("git-hash-object", "-w", $name)
-                           or die "Cannot create object: $!\n";
-               }
-               my $sha = <$F>;
-               chomp $sha;
-               close $F;
-               unlink $name;
-               push(@$new,['0644',$sha,$path]);
-       } elsif (defined $old) {
-               push(@$old,$path);
-       }
-}
-
-sub project_path($$)
-{
-       my ($path, $project) = @_;
-
-       $path = "/".$path unless ($path =~ m#^\/#) ;
-       return $1 if ($path =~ m#^$project\/(.*)$#);
-
-       $path =~ s#\.#\\\.#g;
-       $path =~ s#\+#\\\+#g;
-       return "/" if ($project =~ m#^$path.*$#);
-
-       return undef;
-}
-
-sub split_path($$) {
-       my($rev,$path) = @_;
-       my $branch;
-
-       if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) {
-               $branch = "/$1";
-       } elsif($path =~ s#^/\Q$trunk_name\E/?##) {
-               $branch = "/";
-       } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) {
-               $branch = $1;
-       } else {
-               my %no_error = (
-                       "/" => 1,
-                       "/$tag_name" => 1,
-                       "/$branch_name" => 1
-               );
-               print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
-               return ()
-       }
-       if ($path eq "") {
-               $path = "/";
-       } elsif ($project_name) {
-               $path = project_path($path, $project_name);
-       }
-       return ($branch,$path);
-}
-
-sub branch_rev($$) {
-
-       my ($srcbranch,$uptorev) = @_;
-
-       my $bbranches = $branches{$srcbranch};
-       my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches;
-       my $therev;
-       foreach my $arev(@revs) {
-               next if  ($arev eq 'LAST');
-               if ($arev <= $uptorev) {
-                       $therev = $arev;
-                       last;
-               }
-       }
-       return $therev;
-}
-
-sub expand_svndir($$$);
-
-sub expand_svndir($$$)
-{
-       my ($svnpath, $rev, $path) = @_;
-       my @list;
-       get_ignore(\@list, undef, $rev, $path, $svnpath);
-       my $dirents = $svn->dir_list($svnpath, $rev);
-       foreach my $p(keys %$dirents) {
-               my $kind = node_kind($svnpath.'/'.$p, $rev);
-               if ($kind eq $SVN::Node::file) {
-                       my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p);
-                       push(@list, $f) if $f;
-               } elsif ($kind eq $SVN::Node::dir) {
-                       push(@list,
-                            expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p));
-               }
-       }
-       return @list;
-}
-
-sub copy_path($$$$$$$$) {
-       # Somebody copied a whole subdirectory.
-       # We need to find the index entries from the old version which the
-       # SVN log entry points to, and add them to the new place.
-
-       my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
-
-       my($srcbranch,$srcpath) = split_path($rev,$oldpath);
-       unless(defined $srcbranch && defined $srcpath) {
-               print "Path not found when copying from $oldpath @ $rev.\n".
-                       "Will try to copy from original SVN location...\n"
-                       if $opt_v;
-               push (@$new, expand_svndir($oldpath, $rev, $path));
-               return;
-       }
-       my $therev = branch_rev($srcbranch, $rev);
-       my $gitrev = $branches{$srcbranch}{$therev};
-       unless($gitrev) {
-               print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n";
-               return;
-       }
-       if ($srcbranch ne $newbranch) {
-               push(@$parents, $branches{$srcbranch}{'LAST'});
-       }
-       print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
-       if ($node_kind eq $SVN::Node::dir) {
-               $srcpath =~ s#/*$#/#;
-       }
-
-       my $pid = open my $f,'-|';
-       die $! unless defined $pid;
-       if (!$pid) {
-               exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
-                       or die $!;
-       }
-       local $/ = "\0";
-       while(<$f>) {
-               chomp;
-               my($m,$p) = split(/\t/,$_,2);
-               my($mode,$type,$sha1) = split(/ /,$m);
-               next if $type ne "blob";
-               if ($node_kind eq $SVN::Node::dir) {
-                       $p = $path . substr($p,length($srcpath)-1);
-               } else {
-                       $p = $path;
-               }
-               push(@$new,[$mode,$sha1,$p]);
-       }
-       close($f) or
-               print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
-}
-
-sub commit {
-       my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
-       my($committer_name,$committer_email,$dest);
-       my($author_name,$author_email);
-       my(@old,@new,@parents);
-
-       if (not defined $author or $author eq "") {
-               $committer_name = $committer_email = "unknown";
-       } elsif (defined $users_file) {
-               die "User $author is not listed in $users_file\n"
-                   unless exists $users{$author};
-               ($committer_name,$committer_email) = @{$users{$author}};
-       } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
-               ($committer_name, $committer_email) = ($1, $2);
-       } else {
-               $author =~ s/^<(.*)>$/$1/;
-               $committer_name = $committer_email = $author;
-       }
-
-       if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) {
-               ($author_name, $author_email) = ($1, $2);
-               print "Author from From: $1 <$2>\n" if ($opt_v);;
-       } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
-               ($author_name, $author_email) = ($1, $2);
-               print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);;
-       } else {
-               $author_name = $committer_name;
-               $author_email = $committer_email;
-       }
-
-       $date = pdate($date);
-
-       my $tag;
-       my $parent;
-       if($branch eq "/") { # trunk
-               $parent = $opt_o;
-       } elsif($branch =~ m#^/(.+)#) { # tag
-               $tag = 1;
-               $parent = $1;
-       } else { # "normal" branch
-               # nothing to do
-               $parent = $branch;
-       }
-       $dest = $parent;
-
-       my $prev = $changed_paths->{"/"};
-       if($prev and $prev->[0] eq "A") {
-               delete $changed_paths->{"/"};
-               my $oldpath = $prev->[1];
-               my $rev;
-               if(defined $oldpath) {
-                       my $p;
-                       ($parent,$p) = split_path($revision,$oldpath);
-                       if(defined $parent) {
-                               if($parent eq "/") {
-                                       $parent = $opt_o;
-                               } else {
-                                       $parent =~ s#^/##; # if it's a tag
-                               }
-                       }
-               } else {
-                       $parent = undef;
-               }
-       }
-
-       my $rev;
-       if($revision > $opt_s and defined $parent) {
-               open(H,'-|',"git-rev-parse","--verify",$parent);
-               $rev = <H>;
-               close(H) or do {
-                       print STDERR "$revision: cannot find commit '$parent'!\n";
-                       return;
-               };
-               chop $rev;
-               if(length($rev) != 40) {
-                       print STDERR "$revision: cannot find commit '$parent'!\n";
-                       return;
-               }
-               $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"};
-               if($revision != $opt_s and not $rev) {
-                       print STDERR "$revision: do not know ancestor for '$parent'!\n";
-                       return;
-               }
-       } else {
-               $rev = undef;
-       }
-
-#      if($prev and $prev->[0] eq "A") {
-#              if(not $tag) {
-#                      unless(open(H,"> $git_dir/refs/heads/$branch")) {
-#                              print STDERR "$revision: Could not create branch $branch: $!\n";
-#                              $state=11;
-#                              next;
-#                      }
-#                      print H "$rev\n"
-#                              or die "Could not write branch $branch: $!";
-#                      close(H)
-#                              or die "Could not write branch $branch: $!";
-#              }
-#      }
-       if(not defined $rev) {
-               unlink($git_index);
-       } elsif ($rev ne $last_rev) {
-               print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
-               system("git-read-tree", $rev);
-               die "read-tree failed for $rev: $?\n" if $?;
-               $last_rev = $rev;
-       }
-
-       push (@parents, $rev) if defined $rev;
-
-       my $cid;
-       if($tag and not %$changed_paths) {
-               $cid = $rev;
-       } else {
-               my @paths = sort keys %$changed_paths;
-               foreach my $path(@paths) {
-                       my $action = $changed_paths->{$path};
-
-                       if ($action->[0] eq "R") {
-                               # refer to a file/tree in an earlier commit
-                               push(@old,$path); # remove any old stuff
-                       }
-                       if(($action->[0] eq "A") || ($action->[0] eq "R")) {
-                               my $node_kind = node_kind($action->[3], $revision);
-                               if ($node_kind eq $SVN::Node::file) {
-                                       my $f = get_file($action->[3],
-                                                        $revision, $path);
-                                       if ($f) {
-                                               push(@new,$f) if $f;
-                                       } else {
-                                               my $opath = $action->[3];
-                                               print STDERR "$revision: $branch: could not fetch '$opath'\n";
-                                       }
-                               } elsif ($node_kind eq $SVN::Node::dir) {
-                                       if($action->[1]) {
-                                               copy_path($revision, $branch,
-                                                         $path, $action->[1],
-                                                         $action->[2], $node_kind,
-                                                         \@new, \@parents);
-                                       } else {
-                                               get_ignore(\@new, \@old, $revision,
-                                                          $path, $action->[3]);
-                                       }
-                               }
-                       } elsif ($action->[0] eq "D") {
-                               push(@old,$path);
-                       } elsif ($action->[0] eq "M") {
-                               my $node_kind = node_kind($action->[3], $revision);
-                               if ($node_kind eq $SVN::Node::file) {
-                                       my $f = get_file($action->[3],
-                                                        $revision, $path);
-                                       push(@new,$f) if $f;
-                               } elsif ($node_kind eq $SVN::Node::dir) {
-                                       get_ignore(\@new, \@old, $revision,
-                                                  $path, $action->[3]);
-                               }
-                       } else {
-                               die "$revision: unknown action '".$action->[0]."' for $path\n";
-                       }
-               }
-
-               while(@old) {
-                       my @o1;
-                       if(@old > 55) {
-                               @o1 = splice(@old,0,50);
-                       } else {
-                               @o1 = @old;
-                               @old = ();
-                       }
-                       my $pid = open my $F, "-|";
-                       die "$!" unless defined $pid;
-                       if (!$pid) {
-                               exec("git-ls-files", "-z", @o1) or die $!;
-                       }
-                       @o1 = ();
-                       local $/ = "\0";
-                       while(<$F>) {
-                               chomp;
-                               push(@o1,$_);
-                       }
-                       close($F);
-
-                       while(@o1) {
-                               my @o2;
-                               if(@o1 > 55) {
-                                       @o2 = splice(@o1,0,50);
-                               } else {
-                                       @o2 = @o1;
-                                       @o1 = ();
-                               }
-                               system("git-update-index","--force-remove","--",@o2);
-                               die "Cannot remove files: $?\n" if $?;
-                       }
-               }
-               while(@new) {
-                       my @n2;
-                       if(@new > 12) {
-                               @n2 = splice(@new,0,10);
-                       } else {
-                               @n2 = @new;
-                               @new = ();
-                       }
-                       system("git-update-index","--add",
-                               (map { ('--cacheinfo', @$_) } @n2));
-                       die "Cannot add files: $?\n" if $?;
-               }
-
-               my $pid = open(C,"-|");
-               die "Cannot fork: $!" unless defined $pid;
-               unless($pid) {
-                       exec("git-write-tree");
-                       die "Cannot exec git-write-tree: $!\n";
-               }
-               chomp(my $tree = <C>);
-               length($tree) == 40
-                       or die "Cannot get tree id ($tree): $!\n";
-               close(C)
-                       or die "Error running git-write-tree: $?\n";
-               print "Tree ID $tree\n" if $opt_v;
-
-               my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
-               my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
-               $pid = fork();
-               die "Fork: $!\n" unless defined $pid;
-               unless($pid) {
-                       $pr->writer();
-                       $pw->reader();
-                       open(OUT,">&STDOUT");
-                       dup2($pw->fileno(),0);
-                       dup2($pr->fileno(),1);
-                       $pr->close();
-                       $pw->close();
-
-                       my @par = ();
-
-                       # loose detection of merges
-                       # based on the commit msg
-                       foreach my $rx (@mergerx) {
-                               if ($message =~ $rx) {
-                                       my $mparent = $1;
-                                       if ($mparent eq 'HEAD') { $mparent = $opt_o };
-                                       if ( -e "$git_dir/refs/heads/$mparent") {
-                                               $mparent = get_headref($mparent, $git_dir);
-                                               push (@parents, $mparent);
-                                               print OUT "Merge parent branch: $mparent\n" if $opt_v;
-                                       }
-                               }
-                       }
-                       my %seen_parents = ();
-                       my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents;
-                       foreach my $bparent (@unique_parents) {
-                               push @par, '-p', $bparent;
-                               print OUT "Merge parent branch: $bparent\n" if $opt_v;
-                       }
-
-                       exec("env",
-                               "GIT_AUTHOR_NAME=$author_name",
-                               "GIT_AUTHOR_EMAIL=$author_email",
-                               "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-                               "GIT_COMMITTER_NAME=$committer_name",
-                               "GIT_COMMITTER_EMAIL=$committer_email",
-                               "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-                               "git-commit-tree", $tree,@par);
-                       die "Cannot exec git-commit-tree: $!\n";
-               }
-               $pw->writer();
-               $pr->reader();
-
-               $message =~ s/[\s\n]+\z//;
-               $message = "r$revision: $message" if $opt_r;
-
-               print $pw "$message\n"
-                       or die "Error writing to git-commit-tree: $!\n";
-               $pw->close();
-
-               print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
-               chomp($cid = <$pr>);
-               length($cid) == 40
-                       or die "Cannot get commit id ($cid): $!\n";
-               print "Commit ID $cid\n" if $opt_v;
-               $pr->close();
-
-               waitpid($pid,0);
-               die "Error running git-commit-tree: $?\n" if $?;
-       }
-
-       if (not defined $cid) {
-               $cid = $branches{"/"}{"LAST"};
-       }
-
-       if(not defined $dest) {
-               print "... no known parent\n" if $opt_v;
-       } elsif(not $tag) {
-               print "Writing to refs/heads/$dest\n" if $opt_v;
-               open(C,">$git_dir/refs/heads/$dest") and
-               print C ("$cid\n") and
-               close(C)
-                       or die "Cannot write branch $dest for update: $!\n";
-       }
-
-       if ($tag) {
-               $last_rev = "-" if %$changed_paths;
-               # the tag was 'complex', i.e. did not refer to a "real" revision
-
-               $dest =~ tr/_/\./ if $opt_u;
-
-               system('git-tag', '-f', $dest, $cid) == 0
-                       or die "Cannot create tag $dest: $!\n";
-
-               print "Created tag '$dest' on '$branch'\n" if $opt_v;
-       }
-       $branches{$branch}{"LAST"} = $cid;
-       $branches{$branch}{$revision} = $cid;
-       $last_rev = $cid;
-       print BRANCHES "$revision $branch $cid\n";
-       print "DONE: $revision $dest $cid\n" if $opt_v;
-}
-
-sub commit_all {
-       # Recursive use of the SVN connection does not work
-       local $svn = $svn2;
-
-       my ($changed_paths, $revision, $author, $date, $message) = @_;
-       my %p;
-       while(my($path,$action) = each %$changed_paths) {
-               $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
-       }
-       $changed_paths = \%p;
-
-       my %done;
-       my @col;
-       my $pref;
-       my $branch;
-
-       while(my($path,$action) = each %$changed_paths) {
-               ($branch,$path) = split_path($revision,$path);
-               next if not defined $branch;
-               next if not defined $path;
-               $done{$branch}{$path} = $action;
-       }
-       while(($branch,$changed_paths) = each %done) {
-               commit($branch, $changed_paths, $revision, $author, $date, $message);
-       }
-}
-
-$opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'};
-
-if ($opt_l < $current_rev) {
-    print "Up to date: no new revisions to fetch!\n" if $opt_v;
-    unlink("$git_dir/SVN2GIT_HEAD");
-    exit;
-}
-
-print "Processing from $current_rev to $opt_l ...\n" if $opt_v;
-
-my $from_rev;
-my $to_rev = $current_rev - 1;
-
-my $subpool = SVN::Pool::new_default_sub;
-while ($to_rev < $opt_l) {
-       $subpool->clear;
-       $from_rev = $to_rev + 1;
-       $to_rev = $from_rev + $repack_after;
-       $to_rev = $opt_l if $opt_l < $to_rev;
-       print "Fetching from $from_rev to $to_rev ...\n" if $opt_v;
-       $svn->{'svn'}->get_log("/",$from_rev,$to_rev,0,1,1,\&commit_all);
-       my $pid = fork();
-       die "Fork: $!\n" unless defined $pid;
-       unless($pid) {
-               exec("git-repack", "-d")
-                       or die "Cannot repack: $!\n";
-       }
-       waitpid($pid, 0);
-}
-
-
-unlink($git_index);
-
-if (defined $orig_git_index) {
-       $ENV{GIT_INDEX_FILE} = $orig_git_index;
-} else {
-       delete $ENV{GIT_INDEX_FILE};
-}
-
-# Now switch back to the branch we were in before all of this happened
-if($orig_branch) {
-       print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
-       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
-               if $forward_master;
-       unless ($opt_i) {
-               system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
-               die "read-tree failed: $?\n" if $?;
-       }
-} else {
-       $orig_branch = "master";
-       print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
-       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
-               unless -f "$git_dir/refs/heads/master";
-       system('git-update-ref', 'HEAD', "$orig_branch");
-       unless ($opt_i) {
-               system('git checkout');
-               die "checkout failed: $?\n" if $?;
-       }
-}
-unlink("$git_dir/SVN2GIT_HEAD");
-close(BRANCHES);
diff --git a/git.c b/git.c
index 23a430c3690ed1f921ec22196edf1f0062bc6dcd..4e10581101c26444da5c7c44a80219b11607705b 100644 (file)
--- a/git.c
+++ b/git.c
@@ -6,28 +6,6 @@
 const char git_usage_string[] =
        "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
 
-static void prepend_to_path(const char *dir, int len)
-{
-       const char *old_path = getenv("PATH");
-       char *path;
-       int path_len = len;
-
-       if (!old_path)
-               old_path = "/usr/local/bin:/usr/bin:/bin";
-
-       path_len = len + strlen(old_path) + 1;
-
-       path = xmalloc(path_len + 1);
-
-       memcpy(path, dir, len);
-       path[len] = ':';
-       memcpy(path + len + 1, old_path, path_len - len);
-
-       setenv("PATH", path, 1);
-
-       free(path);
-}
-
 static int handle_options(const char*** argv, int* argc, int* envchanged)
 {
        int handled = 0;
@@ -51,7 +29,7 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
                if (!prefixcmp(cmd, "--exec-path")) {
                        cmd += 11;
                        if (*cmd == '=')
-                               git_set_exec_path(cmd + 1);
+                               git_set_argv_exec_path(cmd + 1);
                        else {
                                puts(git_exec_path());
                                exit(0);
@@ -408,7 +386,7 @@ int main(int argc, const char **argv)
 {
        const char *cmd = argv[0] ? argv[0] : "git-help";
        char *slash = strrchr(cmd, '/');
-       const char *exec_path = NULL;
+       const char *cmd_path = NULL;
        int done_alias = 0;
 
        /*
@@ -418,10 +396,7 @@ int main(int argc, const char **argv)
         */
        if (slash) {
                *slash++ = 0;
-               if (*cmd == '/')
-                       exec_path = cmd;
-               else
-                       exec_path = xstrdup(make_absolute_path(cmd));
+               cmd_path = cmd;
                cmd = slash;
        }
 
@@ -450,23 +425,20 @@ int main(int argc, const char **argv)
                if (!prefixcmp(argv[0], "--"))
                        argv[0] += 2;
        } else {
-               /* Default command: "help" */
-               argv[0] = "help";
-               argc = 1;
+               /* The user didn't specify a command; give them help */
+               printf("usage: %s\n\n", git_usage_string);
+               list_common_cmds_help();
+               exit(1);
        }
        cmd = argv[0];
 
        /*
-        * We execute external git command via execv_git_cmd(),
-        * which looks at "--exec-path" option, GIT_EXEC_PATH
-        * environment, and $(gitexecdir) in Makefile while built,
-        * in this order.  For scripted commands, we prepend
-        * the value of the exec_path variable to the PATH.
+        * We use PATH to find git commands, but we prepend some higher
+        * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH
+        * environment, and the $(gitexecdir) from the Makefile at build
+        * time.
         */
-       if (exec_path)
-               prepend_to_path(exec_path, strlen(exec_path));
-       exec_path = git_exec_path();
-       prepend_to_path(exec_path, strlen(exec_path));
+       setup_path(cmd_path);
 
        while (1) {
                /* See if it's an internal command */
index 6328e26f564887dad0b92edce1d64d54d7d324d6..9cd5b0a2b111de7e252c407d1ad77e05722d8385 100644 (file)
@@ -116,7 +116,7 @@ GITWEB_CONFIG file:
        $feature{'pickaxe'}{'default'} = [1];
        $feature{'pickaxe'}{'override'} = 1;
 
-       $feature{'snapshot'}{'default'} = ['x-gzip', 'gz', 'gzip'];
+       $feature{'snapshot'}{'default'} = ['zip', 'tgz'];
        $feature{'snapshot'}{'override'} = 1;
 
 
index 48e21dad6cec580cd874e32e981a86dc909893f7..2e00756276142491cee650e204cb2b091f31526d 100755 (executable)
@@ -846,6 +846,23 @@ sub chop_str {
        return "$body$tail";
 }
 
+# takes the same arguments as chop_str, but also wraps a <span> around the
+# result with a title attribute if it does get chopped. Additionally, the
+# string is HTML-escaped.
+sub chop_and_escape_str {
+       my $str = shift;
+       my $len = shift;
+       my $add_len = shift || 10;
+
+       my $chopped = chop_str($str, $len, $add_len);
+       if ($chopped eq $str) {
+               return esc_html($chopped);
+       } else {
+               return qq{<span title="} . esc_html($str) . qq{">} .
+                       esc_html($chopped) . qq{</span>};
+       }
+}
+
 ## ----------------------------------------------------------------------
 ## functions returning short strings
 
@@ -2000,6 +2017,19 @@ sub parse_difftree_raw_line {
        return wantarray ? %res : \%res;
 }
 
+# wrapper: return parsed line of git-diff-tree "raw" output
+# (the argument might be raw line, or parsed info)
+sub parsed_difftree_line {
+       my $line_or_ref = shift;
+
+       if (ref($line_or_ref) eq "HASH") {
+               # pre-parsed (or generated by hand)
+               return $line_or_ref;
+       } else {
+               return parse_difftree_raw_line($line_or_ref);
+       }
+}
+
 # parse line of git-ls-tree output
 sub parse_ls_tree_line ($;%) {
        my $line = shift;
@@ -2043,6 +2073,7 @@ sub parse_from_to_diffinfo {
                        }
                }
        } else {
+               # ordinary (not combined) diff
                $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
                if ($diffinfo->{'status'} ne "A") { # not new (added) file
                        $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
@@ -2766,6 +2797,7 @@ sub git_print_tree_entry {
 ## ......................................................................
 ## functions printing large fragments of HTML
 
+# get pre-image filenames for merge (combined) diff
 sub fill_from_file_info {
        my ($diff, @parents) = @_;
 
@@ -2782,28 +2814,25 @@ sub fill_from_file_info {
        return $diff;
 }
 
-# parameters can be strings, or references to arrays of strings
-sub from_ids_eq {
-       my ($a, $b) = @_;
-
-       if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) {
-               for (my $i = 0; $i < @$a; ++$i) {
-                       return 0 unless ($a->[$i] eq $b->[$i]);
-               }
-               return 1;
-       } elsif (!ref($a) && !ref($b)) {
-               return $a eq $b;
-       } else {
-               return 0;
-       }
-}
-
+# is current raw difftree line of file deletion
 sub is_deleted {
        my $diffinfo = shift;
 
        return $diffinfo->{'to_id'} eq ('0' x 40);
 }
 
+# does patch correspond to [previous] difftree raw line
+# $diffinfo  - hashref of parsed raw diff format
+# $patchinfo - hashref of parsed patch diff format
+#              (the same keys as in $diffinfo)
+sub is_patch_split {
+       my ($diffinfo, $patchinfo) = @_;
+
+       return defined $diffinfo && defined $patchinfo
+               && ($diffinfo->{'to_file'} || $diffinfo->{'file'}) eq $patchinfo->{'to_file'};
+}
+
+
 sub git_difftree_body {
        my ($difftree, $hash, @parents) = @_;
        my ($parent) = $parents[0];
@@ -2840,13 +2869,7 @@ sub git_difftree_body {
        my $alternate = 1;
        my $patchno = 0;
        foreach my $line (@{$difftree}) {
-               my $diff;
-               if (ref($line) eq "HASH") {
-                       # pre-parsed (or generated by hand)
-                       $diff = $line;
-               } else {
-                       $diff = parse_difftree_raw_line($line);
-               }
+               my $diff = parsed_difftree_line($line);
 
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
@@ -3117,10 +3140,12 @@ sub git_patchset_body {
        my ($fd, $difftree, $hash, @hash_parents) = @_;
        my ($hash_parent) = $hash_parents[0];
 
+       my $is_combined = (@hash_parents > 1);
        my $patch_idx = 0;
        my $patch_number = 0;
        my $patch_line;
        my $diffinfo;
+       my $to_name;
        my (%from, %to);
 
        print "<div class=\"patchset\">\n";
@@ -3134,73 +3159,46 @@ sub git_patchset_body {
 
  PATCH:
        while ($patch_line) {
-               my @diff_header;
-               my ($from_id, $to_id);
-
-               # git diff header
-               #assert($patch_line =~ m/^diff /) if DEBUG;
-               #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
-               $patch_number++;
-               push @diff_header, $patch_line;
-
-               # extended diff header
-       EXTENDED_HEADER:
-               while ($patch_line = <$fd>) {
-                       chomp $patch_line;
-
-                       last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
-
-                       if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
-                               $from_id = $1;
-                               $to_id   = $2;
-                       } elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
-                               $from_id = [ split(',', $1) ];
-                               $to_id   = $2;
-                       }
 
-                       push @diff_header, $patch_line;
+               # parse "git diff" header line
+               if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) {
+                       # $1 is from_name, which we do not use
+                       $to_name = unquote($2);
+                       $to_name =~ s!^b/!!;
+               } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) {
+                       # $1 is 'cc' or 'combined', which we do not use
+                       $to_name = unquote($2);
+               } else {
+                       $to_name = undef;
                }
-               my $last_patch_line = $patch_line;
 
                # check if current patch belong to current raw line
                # and parse raw git-diff line if needed
-               if (defined $diffinfo &&
-                   defined $from_id && defined $to_id &&
-                   from_ids_eq($diffinfo->{'from_id'}, $from_id) &&
-                   $diffinfo->{'to_id'} eq $to_id) {
+               if (is_patch_split($diffinfo, { 'to_file' => $to_name })) {
                        # this is continuation of a split patch
                        print "<div class=\"patch cont\">\n";
                } else {
                        # advance raw git-diff output if needed
                        $patch_idx++ if defined $diffinfo;
 
-                       # compact combined diff output can have some patches skipped
-                       # find which patch (using pathname of result) we are at now
-                       my $to_name;
-                       if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) {
-                               $to_name = $1;
-                       }
-
-                       do {
-                               # read and prepare patch information
-                               if (ref($difftree->[$patch_idx]) eq "HASH") {
-                                       # pre-parsed (or generated by hand)
-                                       $diffinfo = $difftree->[$patch_idx];
-                               } else {
-                                       $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
-                               }
+                       # read and prepare patch information
+                       $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
 
-                               # check if current raw line has no patch (it got simplified)
-                               if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) {
+                       # compact combined diff output can have some patches skipped
+                       # find which patch (using pathname of result) we are at now;
+                       if ($is_combined) {
+                               while ($to_name ne $diffinfo->{'to_file'}) {
                                        print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
                                              format_diff_cc_simplified($diffinfo, @hash_parents) .
                                              "</div>\n";  # class="patch"
 
                                        $patch_idx++;
                                        $patch_number++;
+
+                                       last if $patch_idx > $#$difftree;
+                                       $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
                                }
-                       } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} ||
-                                $patch_idx > $#$difftree);
+                       }
 
                        # modifies %from, %to hashes
                        parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
@@ -3210,30 +3208,36 @@ sub git_patchset_body {
                        print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
                }
 
+               # git diff header
+               #assert($patch_line =~ m/^diff /) if DEBUG;
+               #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
+               $patch_number++;
                # print "git diff" header
-               $patch_line = shift @diff_header;
                print format_git_diff_header_line($patch_line, $diffinfo,
                                                  \%from, \%to);
 
                # print extended diff header
-               print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
+               print "<div class=\"diff extended_header\">\n";
        EXTENDED_HEADER:
-               foreach $patch_line (@diff_header) {
+               while ($patch_line = <$fd>) {
+                       chomp $patch_line;
+
+                       last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
+
                        print format_extended_diff_header_line($patch_line, $diffinfo,
                                                               \%from, \%to);
                }
-               print "</div>\n"  if (@diff_header > 0); # class="diff extended_header"
+               print "</div>\n"; # class="diff extended_header"
 
                # from-file/to-file diff header
-               $patch_line = $last_patch_line;
                if (! $patch_line) {
                        print "</div>\n"; # class="patch"
                        last PATCH;
                }
                next PATCH if ($patch_line =~ m/^diff /);
                #assert($patch_line =~ m/^---/) if DEBUG;
-               #assert($patch_line eq $last_patch_line) if DEBUG;
 
+               my $last_patch_line = $patch_line;
                $patch_line = <$fd>;
                chomp $patch_line;
                #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
@@ -3258,16 +3262,11 @@ sub git_patchset_body {
 
        # for compact combined (--cc) format, with chunk and patch simpliciaction
        # patchset might be empty, but there might be unprocessed raw lines
-       for ($patch_idx++ if $patch_number > 0;
+       for (++$patch_idx if $patch_number > 0;
             $patch_idx < @$difftree;
-            $patch_idx++) {
+            ++$patch_idx) {
                # read and prepare patch information
-               if (ref($difftree->[$patch_idx]) eq "HASH") {
-                       # pre-parsed (or generated by hand)
-                       $diffinfo = $difftree->[$patch_idx];
-               } else {
-                       $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
-               }
+               $diffinfo = parsed_difftree_line($difftree->[$patch_idx]);
 
                # generate anchor for "patch" links in difftree / whatchanged part
                print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
@@ -3395,7 +3394,7 @@ sub git_project_list_body {
                      "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
                                        -class => "list", -title => $pr->{'descr_long'}},
                                        esc_html($pr->{'descr'})) . "</td>\n" .
-                     "<td><i>" . esc_html(chop_str($pr->{'owner'}, 15)) . "</i></td>\n";
+                     "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n";
                print "<td class=\"". age_class($pr->{'age'}) . "\">" .
                      (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
                      "<td class=\"link\">" .
@@ -3437,9 +3436,10 @@ sub git_shortlog_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
+               my $author = chop_and_escape_str($co{'author_name'}, 10);
                # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                     "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
+                     "<td><i>" . $author . "</i></td>\n" .
                      "<td>";
                print format_subject_html($co{'title'}, $co{'title_short'},
                                          href(action=>"commit", hash=>$commit), $ref);
@@ -3487,9 +3487,10 @@ sub git_history_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
+       # shortlog uses      chop_str($co{'author_name'}, 10)
+               my $author = chop_and_escape_str($co{'author_name'}, 15, 3);
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                     # shortlog uses      chop_str($co{'author_name'}, 10)
-                     "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
+                     "<td><i>" . $author . "</i></td>\n" .
                      "<td>";
                # originally git_history used chop_str($co{'title'}, 50)
                print format_subject_html($co{'title'}, $co{'title_short'},
@@ -3643,11 +3644,12 @@ sub git_search_grep_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
+               my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
                print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                     "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+                     "<td><i>" . $author . "</i></td>\n" .
                      "<td>" .
                      $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
-                              esc_html(chop_str($co{'title'}, 50)) . "<br/>");
+                              chop_and_escape_str($co{'title'}, 50) . "<br/>");
                my $comment = $co{'comment'};
                foreach my $line (@$comment) {
                        if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
@@ -5157,12 +5159,13 @@ sub git_search {
                                                print "<tr class=\"light\">\n";
                                        }
                                        $alternate ^= 1;
+                                       my $author = chop_and_escape_str($co{'author_name'}, 15, 5);
                                        print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                                             "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+                                             "<td><i>" . $author . "</i></td>\n" .
                                              "<td>" .
                                              $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
                                                      -class => "list subject"},
-                                                     esc_html(chop_str($co{'title'}, 50)) . "<br/>");
+                                                     chop_and_escape_str($co{'title'}, 50) . "<br/>");
                                        while (my $setref = shift @files) {
                                                my %set = %$setref;
                                                print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
diff --git a/hash.c b/hash.c
new file mode 100644 (file)
index 0000000..7b492d4
--- /dev/null
+++ b/hash.c
@@ -0,0 +1,110 @@
+/*
+ * Some generic hashing helpers.
+ */
+#include "cache.h"
+#include "hash.h"
+
+/*
+ * Look up a hash entry in the hash table. Return the pointer to
+ * the existing entry, or the empty slot if none existed. The caller
+ * can then look at the (*ptr) to see whether it existed or not.
+ */
+static struct hash_table_entry *lookup_hash_entry(unsigned int hash, struct hash_table *table)
+{
+       unsigned int size = table->size, nr = hash % size;
+       struct hash_table_entry *array = table->array;
+
+       while (array[nr].ptr) {
+               if (array[nr].hash == hash)
+                       break;
+               nr++;
+               if (nr >= size)
+                       nr = 0;
+       }
+       return array + nr;
+}
+
+
+/*
+ * Insert a new hash entry pointer into the table.
+ *
+ * If that hash entry already existed, return the pointer to
+ * the existing entry (and the caller can create a list of the
+ * pointers or do anything else). If it didn't exist, return
+ * NULL (and the caller knows the pointer has been inserted).
+ */
+static void **insert_hash_entry(unsigned int hash, void *ptr, struct hash_table *table)
+{
+       struct hash_table_entry *entry = lookup_hash_entry(hash, table);
+
+       if (!entry->ptr) {
+               entry->ptr = ptr;
+               entry->hash = hash;
+               table->nr++;
+               return NULL;
+       }
+       return &entry->ptr;
+}
+
+static void grow_hash_table(struct hash_table *table)
+{
+       unsigned int i;
+       unsigned int old_size = table->size, new_size;
+       struct hash_table_entry *old_array = table->array, *new_array;
+
+       new_size = alloc_nr(old_size);
+       new_array = xcalloc(sizeof(struct hash_table_entry), new_size);
+       table->size = new_size;
+       table->array = new_array;
+       table->nr = 0;
+       for (i = 0; i < old_size; i++) {
+               unsigned int hash = old_array[i].hash;
+               void *ptr = old_array[i].ptr;
+               if (ptr)
+                       insert_hash_entry(hash, ptr, table);
+       }
+       free(old_array);
+}
+
+void *lookup_hash(unsigned int hash, struct hash_table *table)
+{
+       if (!table->array)
+               return NULL;
+       return &lookup_hash_entry(hash, table)->ptr;
+}
+
+void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table)
+{
+       unsigned int nr = table->nr;
+       if (nr >= table->size/2)
+               grow_hash_table(table);
+       return insert_hash_entry(hash, ptr, table);
+}
+
+int for_each_hash(struct hash_table *table, int (*fn)(void *))
+{
+       int sum = 0;
+       unsigned int i;
+       unsigned int size = table->size;
+       struct hash_table_entry *array = table->array;
+
+       for (i = 0; i < size; i++) {
+               void *ptr = array->ptr;
+               array++;
+               if (ptr) {
+                       int val = fn(ptr);
+                       if (val < 0)
+                               return val;
+                       sum += val;
+               }
+       }
+       return sum;
+}
+
+void free_hash(struct hash_table *table)
+{
+       free(table->array);
+       table->array = NULL;
+       table->size = 0;
+       table->nr = 0;
+}
diff --git a/hash.h b/hash.h
new file mode 100644 (file)
index 0000000..a8b0fbb
--- /dev/null
+++ b/hash.h
@@ -0,0 +1,43 @@
+#ifndef HASH_H
+#define HASH_H
+
+/*
+ * These are some simple generic hash table helper functions.
+ * Not necessarily suitable for all users, but good for things
+ * where you want to just keep track of a list of things, and
+ * have a good hash to use on them.
+ *
+ * It keeps the hash table at roughly 50-75% free, so the memory
+ * cost of the hash table itself is roughly
+ *
+ *     3 * 2*sizeof(void *) * nr_of_objects
+ *
+ * bytes.
+ *
+ * FIXME: on 64-bit architectures, we waste memory. It would be
+ * good to have just 32-bit pointers, requiring a special allocator
+ * for hashed entries or something.
+ */
+struct hash_table_entry {
+       unsigned int hash;
+       void *ptr;
+};
+
+struct hash_table {
+       unsigned int size, nr;
+       struct hash_table_entry *array;
+};
+
+extern void *lookup_hash(unsigned int hash, struct hash_table *table);
+extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table);
+extern int for_each_hash(struct hash_table *table, int (*fn)(void *));
+extern void free_hash(struct hash_table *table);
+
+static inline void init_hash(struct hash_table *table)
+{
+       table->size = 0;
+       table->nr = 0;
+       table->array = NULL;
+}
+
+#endif
diff --git a/help.c b/help.c
index e5662d9014e758fee00b890e06ffc66cd4aa0291..855aeef92f00689fd7b283c42e42b90df42f897e 100644 (file)
--- a/help.c
+++ b/help.c
@@ -37,24 +37,25 @@ static inline void mput_char(char c, unsigned int num)
                putchar(c);
 }
 
-static struct cmdname {
-       size_t len;
-       char name[1];
-} **cmdname;
-static int cmdname_alloc, cmdname_cnt;
-
-static void add_cmdname(const char *name, int len)
+static struct cmdnames {
+       int alloc;
+       int cnt;
+       struct cmdname {
+               size_t len;
+               char name[1];
+       } **names;
+} main_cmds, other_cmds;
+
+static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
-       struct cmdname *ent;
-       if (cmdname_alloc <= cmdname_cnt) {
-               cmdname_alloc = cmdname_alloc + 200;
-               cmdname = xrealloc(cmdname, cmdname_alloc * sizeof(*cmdname));
-       }
-       ent = xmalloc(sizeof(*ent) + len);
+       struct cmdname *ent = xmalloc(sizeof(*ent) + len);
+
        ent->len = len;
        memcpy(ent->name, name, len);
        ent->name[len] = 0;
-       cmdname[cmdname_cnt++] = ent;
+
+       ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
+       cmds->names[cmds->cnt++] = ent;
 }
 
 static int cmdname_compare(const void *a_, const void *b_)
@@ -64,7 +65,42 @@ static int cmdname_compare(const void *a_, const void *b_)
        return strcmp(a->name, b->name);
 }
 
-static void pretty_print_string_list(struct cmdname **cmdname, int longest)
+static void uniq(struct cmdnames *cmds)
+{
+       int i, j;
+
+       if (!cmds->cnt)
+               return;
+
+       for (i = j = 1; i < cmds->cnt; i++)
+               if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name))
+                       cmds->names[j++] = cmds->names[i];
+
+       cmds->cnt = j;
+}
+
+static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) {
+       int ci, cj, ei;
+       int cmp;
+
+       ci = cj = ei = 0;
+       while (ci < cmds->cnt && ei < excludes->cnt) {
+               cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
+               if (cmp < 0)
+                       cmds->names[cj++] = cmds->names[ci++];
+               else if (cmp == 0)
+                       ci++, ei++;
+               else if (cmp > 0)
+                       ei++;
+       }
+
+       while (ci < cmds->cnt)
+               cmds->names[cj++] = cmds->names[ci++];
+
+       cmds->cnt = cj;
+}
+
+static void pretty_print_string_list(struct cmdnames *cmds, int longest)
 {
        int cols = 1, rows;
        int space = longest + 1; /* min 1 SP between words */
@@ -73,9 +109,7 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest)
 
        if (space < max_cols)
                cols = max_cols / space;
-       rows = (cmdname_cnt + cols - 1) / cols;
-
-       qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
+       rows = (cmds->cnt + cols - 1) / cols;
 
        for (i = 0; i < rows; i++) {
                printf("  ");
@@ -83,71 +117,112 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest)
                for (j = 0; j < cols; j++) {
                        int n = j * rows + i;
                        int size = space;
-                       if (n >= cmdname_cnt)
+                       if (n >= cmds->cnt)
                                break;
-                       if (j == cols-1 || n + rows >= cmdname_cnt)
+                       if (j == cols-1 || n + rows >= cmds->cnt)
                                size = 1;
-                       printf("%-*s", size, cmdname[n]->name);
+                       printf("%-*s", size, cmds->names[n]->name);
                }
                putchar('\n');
        }
 }
 
-static void list_commands(const char *exec_path, const char *pattern)
+static unsigned int list_commands_in_dir(struct cmdnames *cmds,
+                                        const char *path)
 {
        unsigned int longest = 0;
-       char path[PATH_MAX];
-       int dirlen;
-       DIR *dir = opendir(exec_path);
+       const char *prefix = "git-";
+       int prefix_len = strlen(prefix);
+       DIR *dir = opendir(path);
        struct dirent *de;
 
-       if (!dir) {
-               fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
-               exit(1);
-       }
-
-       dirlen = strlen(exec_path);
-       if (PATH_MAX - 20 < dirlen) {
-               fprintf(stderr, "git: insanely long exec-path '%s'\n",
-                       exec_path);
-               exit(1);
-       }
-
-       memcpy(path, exec_path, dirlen);
-       path[dirlen++] = '/';
+       if (!dir || chdir(path))
+               return 0;
 
        while ((de = readdir(dir)) != NULL) {
                struct stat st;
                int entlen;
 
-               if (prefixcmp(de->d_name, "git-"))
+               if (prefixcmp(de->d_name, prefix))
                        continue;
-               strcpy(path+dirlen, de->d_name);
-               if (stat(path, &st) || /* stat, not lstat */
+
+               if (stat(de->d_name, &st) || /* stat, not lstat */
                    !S_ISREG(st.st_mode) ||
                    !(st.st_mode & S_IXUSR))
                        continue;
 
-               entlen = strlen(de->d_name);
+               entlen = strlen(de->d_name) - prefix_len;
                if (has_extension(de->d_name, ".exe"))
                        entlen -= 4;
 
                if (longest < entlen)
                        longest = entlen;
 
-               add_cmdname(de->d_name + 4, entlen-4);
+               add_cmdname(cmds, de->d_name + prefix_len, entlen);
        }
        closedir(dir);
 
-       printf("git commands available in '%s'\n", exec_path);
-       printf("----------------------------");
-       mput_char('-', strlen(exec_path));
-       putchar('\n');
-       pretty_print_string_list(cmdname, longest - 4);
-       putchar('\n');
+       return longest;
 }
 
-static void list_common_cmds_help(void)
+static void list_commands(void)
+{
+       unsigned int longest = 0;
+       unsigned int len;
+       const char *env_path = getenv("PATH");
+       char *paths, *path, *colon;
+       const char *exec_path = git_exec_path();
+
+       if (exec_path)
+               longest = list_commands_in_dir(&main_cmds, exec_path);
+
+       if (!env_path) {
+               fprintf(stderr, "PATH not set\n");
+               exit(1);
+       }
+
+       path = paths = xstrdup(env_path);
+       while (1) {
+               if ((colon = strchr(path, ':')))
+                       *colon = 0;
+
+               len = list_commands_in_dir(&other_cmds, path);
+               if (len > longest)
+                       longest = len;
+
+               if (!colon)
+                       break;
+               path = colon + 1;
+       }
+       free(paths);
+
+       qsort(main_cmds.names, main_cmds.cnt,
+             sizeof(*main_cmds.names), cmdname_compare);
+       uniq(&main_cmds);
+
+       qsort(other_cmds.names, other_cmds.cnt,
+             sizeof(*other_cmds.names), cmdname_compare);
+       uniq(&other_cmds);
+       exclude_cmds(&other_cmds, &main_cmds);
+
+       if (main_cmds.cnt) {
+               printf("available git commands in '%s'\n", exec_path);
+               printf("----------------------------");
+               mput_char('-', strlen(exec_path));
+               putchar('\n');
+               pretty_print_string_list(&main_cmds, longest);
+               putchar('\n');
+       }
+
+       if (other_cmds.cnt) {
+               printf("git commands available from elsewhere on your $PATH\n");
+               printf("---------------------------------------------------\n");
+               pretty_print_string_list(&other_cmds, longest);
+               putchar('\n');
+       }
+}
+
+void list_common_cmds_help(void)
 {
        int i, longest = 0;
 
@@ -198,19 +273,17 @@ int cmd_version(int argc, const char **argv, const char *prefix)
 int cmd_help(int argc, const char **argv, const char *prefix)
 {
        const char *help_cmd = argc > 1 ? argv[1] : NULL;
-       const char *exec_path = git_exec_path();
 
        if (!help_cmd) {
                printf("usage: %s\n\n", git_usage_string);
                list_common_cmds_help();
-               exit(1);
+               exit(0);
        }
 
        else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
                printf("usage: %s\n\n", git_usage_string);
-               if(exec_path)
-                       list_commands(exec_path, "git-*");
-               exit(1);
+               list_commands();
+               exit(0);
        }
 
        else
index db58e050410197f6eda4b91705e20a8bfc87b088..61ea7621bef3d653b3b88ba89801305b95a8d1a7 100644 (file)
@@ -46,7 +46,7 @@ static int nr_resolved_deltas;
 static int from_stdin;
 static int verbose;
 
-static struct progress progress;
+static struct progress *progress;
 
 /* We always read in 4kB chunks. */
 static unsigned char input_buffer[4096];
@@ -87,6 +87,8 @@ static void *fill(int min)
                                die("early EOF");
                        die("read error on input: %s", strerror(errno));
                }
+               if (from_stdin)
+                       display_throughput(progress, ret);
                input_len += ret;
        } while (input_len < min);
        return input_buffer;
@@ -106,7 +108,7 @@ static void use(int bytes)
        consumed_bytes += bytes;
 }
 
-static const char *open_pack_file(const char *pack_name)
+static char *open_pack_file(char *pack_name)
 {
        if (from_stdin) {
                input_fd = 0;
@@ -406,7 +408,9 @@ static void parse_pack_objects(unsigned char *sha1)
         * - remember base (SHA1 or offset) for all deltas.
         */
        if (verbose)
-               start_progress(&progress, "Indexing %u objects...", "", nr_objects);
+               progress = start_progress(
+                               from_stdin ? "Receiving objects" : "Indexing objects",
+                               nr_objects);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
                data = unpack_raw_entry(obj, &delta->base);
@@ -418,12 +422,10 @@ static void parse_pack_objects(unsigned char *sha1)
                } else
                        sha1_object(data, obj->size, obj->type, obj->idx.sha1);
                free(data);
-               if (verbose)
-                       display_progress(&progress, i+1);
+               display_progress(progress, i+1);
        }
        objects[i].idx.offset = consumed_bytes;
-       if (verbose)
-               stop_progress(&progress);
+       stop_progress(&progress);
 
        /* Check pack integrity */
        flush();
@@ -455,7 +457,7 @@ static void parse_pack_objects(unsigned char *sha1)
         *   for some more deltas.
         */
        if (verbose)
-               start_progress(&progress, "Resolving %u deltas...", "", nr_deltas);
+               progress = start_progress("Resolving deltas", nr_deltas);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
                union delta_base base;
@@ -486,8 +488,7 @@ static void parse_pack_objects(unsigned char *sha1)
                                                      obj->size, obj->type);
                        }
                free(data);
-               if (verbose)
-                       display_progress(&progress, nr_resolved_deltas);
+               display_progress(progress, nr_resolved_deltas);
        }
 }
 
@@ -594,8 +595,7 @@ static void fix_unresolved_deltas(int nr_unresolved)
                        die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
                append_obj_to_pack(d->base.sha1, data, size, type);
                free(data);
-               if (verbose)
-                       display_progress(&progress, nr_resolved_deltas);
+               display_progress(progress, nr_resolved_deltas);
        }
        free(sorted_by_pos);
 }
@@ -686,15 +686,15 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 int main(int argc, char **argv)
 {
        int i, fix_thin_pack = 0;
-       const char *curr_pack, *pack_name = NULL;
-       const char *curr_index, *index_name = NULL;
+       char *curr_pack, *pack_name = NULL;
+       char *curr_index, *index_name = NULL;
        const char *keep_name = NULL, *keep_msg = NULL;
        char *index_name_buf = NULL, *keep_name_buf = NULL;
        struct pack_idx_entry **idx_objects;
        unsigned char sha1[20];
 
        for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
+               char *arg = argv[i];
 
                if (*arg == '-') {
                        if (!strcmp(arg, "--stdin")) {
@@ -774,8 +774,7 @@ int main(int argc, char **argv)
        deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
        parse_pack_objects(sha1);
        if (nr_deltas == nr_resolved_deltas) {
-               if (verbose)
-                       stop_progress(&progress);
+               stop_progress(&progress);
                /* Flush remaining pack final 20-byte SHA1. */
                flush();
        } else {
@@ -788,11 +787,10 @@ int main(int argc, char **argv)
                                           (nr_objects + nr_unresolved + 1)
                                           * sizeof(*objects));
                        fix_unresolved_deltas(nr_unresolved);
-                       if (verbose) {
-                               stop_progress(&progress);
+                       stop_progress(&progress);
+                       if (verbose)
                                fprintf(stderr, "%d objects were added to complete this thin pack.\n",
                                        nr_objects - nr_objects_initial);
-                       }
                        fixup_pack_header_footer(output_fd, sha1,
                                curr_pack, nr_objects);
                }
@@ -815,6 +813,10 @@ int main(int argc, char **argv)
        free(objects);
        free(index_name_buf);
        free(keep_name_buf);
+       if (pack_name == NULL)
+               free(curr_pack);
+       if (index_name == NULL)
+               free(curr_index);
 
        return 0;
 }
index 62edd344558049f05c33888de9d2fc2a854b51e9..3763ce94fcb5c96f366f84e755c8abd725b00b00 100644 (file)
@@ -15,7 +15,7 @@ static void show_parents(struct commit *commit, int abbrev)
        }
 }
 
-static void show_decorations(struct commit *commit)
+void show_decorations(struct commit *commit)
 {
        const char *prefix;
        struct name_decoration *decoration;
index e82b56a20d3cfad318a4af6ea78fbe098653211d..b33f7cd7ac2ef6a2587109c4ee618d63ccedae96 100644 (file)
@@ -12,5 +12,6 @@ int log_tree_diff_flush(struct rev_info *);
 int log_tree_commit(struct rev_info *, struct commit *);
 int log_tree_opt_parse(struct rev_info *, const char **, int);
 void show_log(struct rev_info *opt, const char *sep);
+void show_decorations(struct commit *commit);
 
 #endif
index 979bdfff7c516ada7fb36281a22b41d303d1b99c..665e2b29b8817aa6a8aa83ccd859ab51e7bb2234 100644 (file)
@@ -17,7 +17,8 @@ static int sha1_compare(const void *_a, const void *_b)
  * the SHA1 hash of sorted object names. The objects array passed in
  * will be sorted by SHA1 on exit.
  */
-const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1)
+char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
+                    int nr_objects, unsigned char *sha1)
 {
        struct sha1file *f;
        struct pack_idx_entry **sorted_by_sha, **list, **last;
diff --git a/pack.h b/pack.h
index b57ba2d9ed6120612c2576b07d8c185d4b54bb76..b31b37608d7f1901c74a20552770c306e633670c 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -55,7 +55,7 @@ struct pack_idx_entry {
        off_t offset;
 };
 
-extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
+extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
 
 extern int verify_pack(struct packed_git *, int);
 extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t);
index ceb787170e17df130e26658e5a189bb63684ab3e..8d20f7c9c626787d157be0f07c18c126e272db52 100644 (file)
@@ -25,7 +25,7 @@ int main(int argc, char **argv)
        int i, ret;
        char *dest = NULL;
        int fd[2];
-       pid_t pid;
+       struct child_process *conn;
        int nongit = 0;
        unsigned flags = 0;
 
@@ -64,12 +64,10 @@ int main(int argc, char **argv)
        if (!dest || i != argc - 1)
                usage(peek_remote_usage);
 
-       pid = git_connect(fd, dest, uploadpack, 0);
-       if (pid < 0)
-               return 1;
+       conn = git_connect(fd, dest, uploadpack, 0);
        ret = peek_remote(fd, flags);
        close(fd[0]);
        close(fd[1]);
-       ret |= finish_connect(pid);
+       ret |= finish_connect(conn);
        return !!ret;
 }
index 4344f4eed5e46c4e013018af8ef9ab062f740d8f..3f6a602a534a7bd5dfea2fca4239af6f9ed5ceca 100644 (file)
@@ -1,6 +1,40 @@
+/*
+ * Simple text-based progress display module for GIT
+ *
+ * Copyright (c) 2007 by Nicolas Pitre <nico@cam.org>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
 #include "git-compat-util.h"
 #include "progress.h"
 
+#define TP_IDX_MAX      8
+
+struct throughput {
+       struct timeval prev_tv;
+       off_t total;
+       unsigned long count;
+       unsigned long avg_bytes;
+       unsigned long last_bytes[TP_IDX_MAX];
+       unsigned int avg_misecs;
+       unsigned int last_misecs[TP_IDX_MAX];
+       unsigned int idx;
+       char display[32];
+};
+
+struct progress {
+       const char *title;
+       int last_value;
+       unsigned total;
+       unsigned last_percent;
+       unsigned delay;
+       unsigned delayed_percent_treshold;
+       struct throughput *throughput;
+};
+
 static volatile sig_atomic_t progress_update;
 
 static void progress_interval(int signum)
@@ -35,10 +69,11 @@ static void clear_progress_signal(void)
        progress_update = 0;
 }
 
-int display_progress(struct progress *progress, unsigned n)
+static int display(struct progress *progress, unsigned n, int done)
 {
+       char *eol, *tp;
+
        if (progress->delay) {
-               char buf[80];
                if (!progress_update || --progress->delay)
                        return 0;
                if (progress->total) {
@@ -51,60 +86,151 @@ int display_progress(struct progress *progress, unsigned n)
                                return 0;
                        }
                }
-               if (snprintf(buf, sizeof(buf),
-                            progress->delayed_title, progress->total))
-                       fprintf(stderr, "%s\n", buf);
        }
+
+       progress->last_value = n;
+       tp = (progress->throughput) ? progress->throughput->display : "";
+       eol = done ? ", done.   \n" : "   \r";
        if (progress->total) {
                unsigned percent = n * 100 / progress->total;
                if (percent != progress->last_percent || progress_update) {
                        progress->last_percent = percent;
-                       fprintf(stderr, "%s%4u%% (%u/%u) done\r",
-                               progress->prefix, percent, n, progress->total);
+                       fprintf(stderr, "%s: %3u%% (%u/%u)%s%s",
+                               progress->title, percent, n,
+                               progress->total, tp, eol);
                        progress_update = 0;
-                       progress->need_lf = 1;
                        return 1;
                }
        } else if (progress_update) {
-               fprintf(stderr, "%s%u\r", progress->prefix, n);
+               fprintf(stderr, "%s: %u%s%s", progress->title, n, tp, eol);
                progress_update = 0;
-               progress->need_lf = 1;
                return 1;
        }
+
        return 0;
 }
 
-void start_progress(struct progress *progress, const char *title,
-                   const char *prefix, unsigned total)
+void display_throughput(struct progress *progress, unsigned long n)
 {
-       char buf[80];
-       progress->prefix = prefix;
-       progress->total = total;
-       progress->last_percent = -1;
-       progress->delay = 0;
-       progress->need_lf = 0;
-       if (snprintf(buf, sizeof(buf), title, total))
-               fprintf(stderr, "%s\n", buf);
-       set_progress_signal();
+       struct throughput *tp;
+       struct timeval tv;
+       unsigned int misecs;
+
+       if (!progress)
+               return;
+       tp = progress->throughput;
+
+       gettimeofday(&tv, NULL);
+
+       if (!tp) {
+               progress->throughput = tp = calloc(1, sizeof(*tp));
+               if (tp)
+                       tp->prev_tv = tv;
+               return;
+       }
+
+       tp->total += n;
+       tp->count += n;
+
+       /*
+        * We have x = bytes and y = microsecs.  We want z = KiB/s:
+        *
+        *      z = (x / 1024) / (y / 1000000)
+        *      z = x / y * 1000000 / 1024
+        *      z = x / (y * 1024 / 1000000)
+        *      z = x / y'
+        *
+        * To simplify things we'll keep track of misecs, or 1024th of a sec
+        * obtained with:
+        *
+        *      y' = y * 1024 / 1000000
+        *      y' = y / (1000000 / 1024)
+        *      y' = y / 977
+        */
+       misecs = (tv.tv_sec - tp->prev_tv.tv_sec) * 1024;
+       misecs += (int)(tv.tv_usec - tp->prev_tv.tv_usec) / 977;
+
+       if (misecs > 512) {
+               int l = sizeof(tp->display);
+               tp->prev_tv = tv;
+               tp->avg_bytes += tp->count;
+               tp->avg_misecs += misecs;
+
+               if (tp->total > 1 << 30) {
+                       l -= snprintf(tp->display, l, ", %u.%2.2u GiB",
+                                     (int)(tp->total >> 30),
+                                     (int)(tp->total & ((1 << 30) - 1)) / 10737419);
+               } else if (tp->total > 1 << 20) {
+                       l -= snprintf(tp->display, l, ", %u.%2.2u MiB",
+                                     (int)(tp->total >> 20),
+                                     ((int)(tp->total & ((1 << 20) - 1))
+                                      * 100) >> 20);
+               } else if (tp->total > 1 << 10) {
+                       l -= snprintf(tp->display, l, ", %u.%2.2u KiB",
+                                     (int)(tp->total >> 10),
+                                     ((int)(tp->total & ((1 << 10) - 1))
+                                      * 100) >> 10);
+               } else {
+                       l -= snprintf(tp->display, l, ", %u bytes",
+                                     (int)tp->total);
+               }
+               snprintf(tp->display + sizeof(tp->display) - l, l,
+                        " | %lu KiB/s", tp->avg_bytes / tp->avg_misecs);
+
+               tp->avg_bytes -= tp->last_bytes[tp->idx];
+               tp->avg_misecs -= tp->last_misecs[tp->idx];
+               tp->last_bytes[tp->idx] = tp->count;
+               tp->last_misecs[tp->idx] = misecs;
+               tp->idx = (tp->idx + 1) % TP_IDX_MAX;
+               tp->count = 0;
+
+               if (progress->last_value != -1 && progress_update)
+                       display(progress, progress->last_value, 0);
+       }
+}
+
+int display_progress(struct progress *progress, unsigned n)
+{
+       return progress ? display(progress, n, 0) : 0;
 }
 
-void start_progress_delay(struct progress *progress, const char *title,
-                         const char *prefix, unsigned total,
-                         unsigned percent_treshold, unsigned delay)
+struct progress *start_progress_delay(const char *title, unsigned total,
+                                      unsigned percent_treshold, unsigned delay)
 {
-       progress->prefix = prefix;
+       struct progress *progress = malloc(sizeof(*progress));
+       if (!progress) {
+               /* unlikely, but here's a good fallback */
+               fprintf(stderr, "%s...\n", title);
+               return NULL;
+       }
+       progress->title = title;
        progress->total = total;
+       progress->last_value = -1;
        progress->last_percent = -1;
        progress->delayed_percent_treshold = percent_treshold;
-       progress->delayed_title = title;
        progress->delay = delay;
-       progress->need_lf = 0;
+       progress->throughput = NULL;
        set_progress_signal();
+       return progress;
 }
 
-void stop_progress(struct progress *progress)
+struct progress *start_progress(const char *title, unsigned total)
 {
+       return start_progress_delay(title, total, 0, 0);
+}
+
+void stop_progress(struct progress **p_progress)
+{
+       struct progress *progress = *p_progress;
+       if (!progress)
+               return;
+       *p_progress = NULL;
+       if (progress->last_value != -1) {
+               /* Force the last update */
+               progress_update = 1;
+               display(progress, progress->last_value, 1);
+       }
        clear_progress_signal();
-       if (progress->need_lf)
-               fputc('\n', stderr);
+       free(progress->throughput);
+       free(progress);
 }
index a7c17ca7c4bdad953508d03c20e73022b03bd25a..61cb68dfa512bb3668d7e6060262041937aa82d3 100644 (file)
@@ -1,22 +1,13 @@
 #ifndef PROGRESS_H
 #define PROGRESS_H
 
-struct progress {
-       const char *prefix;
-       unsigned total;
-       unsigned last_percent;
-       unsigned delay;
-       unsigned delayed_percent_treshold;
-       const char *delayed_title;
-       int need_lf;
-};
+struct progress;
 
+void display_throughput(struct progress *progress, unsigned long n);
 int display_progress(struct progress *progress, unsigned n);
-void start_progress(struct progress *progress, const char *title,
-                   const char *prefix, unsigned total);
-void start_progress_delay(struct progress *progress, const char *title,
-                         const char *prefix, unsigned total,
-                         unsigned percent_treshold, unsigned delay);
-void stop_progress(struct progress *progress);
+struct progress *start_progress(const char *title, unsigned total);
+struct progress *start_progress_delay(const char *title, unsigned total,
+                                      unsigned percent_treshold, unsigned delay);
+void stop_progress(struct progress **progress);
 
 #endif
index 7e779d33ee9ea5f7d2e6aedc8c3a0a0476e87135..d99a6c4ea7c776bec717c9952f77db7fa9a69c21 100644 (file)
@@ -17,8 +17,8 @@ static inline void dup_devnull(int to)
 
 int start_command(struct child_process *cmd)
 {
-       int need_in, need_out;
-       int fdin[2], fdout[2];
+       int need_in, need_out, need_err;
+       int fdin[2], fdout[2], fderr[2];
 
        need_in = !cmd->no_stdin && cmd->in < 0;
        if (need_in) {
@@ -41,12 +41,26 @@ int start_command(struct child_process *cmd)
                cmd->close_out = 1;
        }
 
+       need_err = cmd->err < 0;
+       if (need_err) {
+               if (pipe(fderr) < 0) {
+                       if (need_in)
+                               close_pair(fdin);
+                       if (need_out)
+                               close_pair(fdout);
+                       return -ERR_RUN_COMMAND_PIPE;
+               }
+               cmd->err = fderr[0];
+       }
+
        cmd->pid = fork();
        if (cmd->pid < 0) {
                if (need_in)
                        close_pair(fdin);
                if (need_out)
                        close_pair(fdout);
+               if (need_err)
+                       close_pair(fderr);
                return -ERR_RUN_COMMAND_FORK;
        }
 
@@ -73,6 +87,11 @@ int start_command(struct child_process *cmd)
                        close(cmd->out);
                }
 
+               if (need_err) {
+                       dup2(fderr[1], 2);
+                       close_pair(fderr);
+               }
+
                if (cmd->dir && chdir(cmd->dir))
                        die("exec %s: cd to %s failed (%s)", cmd->argv[0],
                            cmd->dir, strerror(errno));
@@ -102,19 +121,17 @@ int start_command(struct child_process *cmd)
        else if (cmd->out > 1)
                close(cmd->out);
 
+       if (need_err)
+               close(fderr[1]);
+
        return 0;
 }
 
-int finish_command(struct child_process *cmd)
+static int wait_or_whine(pid_t pid)
 {
-       if (cmd->close_in)
-               close(cmd->in);
-       if (cmd->close_out)
-               close(cmd->out);
-
        for (;;) {
                int status, code;
-               pid_t waiting = waitpid(cmd->pid, &status, 0);
+               pid_t waiting = waitpid(pid, &status, 0);
 
                if (waiting < 0) {
                        if (errno == EINTR)
@@ -122,7 +139,7 @@ int finish_command(struct child_process *cmd)
                        error("waitpid failed (%s)", strerror(errno));
                        return -ERR_RUN_COMMAND_WAITPID;
                }
-               if (waiting != cmd->pid)
+               if (waiting != pid)
                        return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
                if (WIFSIGNALED(status))
                        return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
@@ -136,6 +153,15 @@ int finish_command(struct child_process *cmd)
        }
 }
 
+int finish_command(struct child_process *cmd)
+{
+       if (cmd->close_in)
+               close(cmd->in);
+       if (cmd->close_out)
+               close(cmd->out);
+       return wait_or_whine(cmd->pid);
+}
+
 int run_command(struct child_process *cmd)
 {
        int code = start_command(cmd);
@@ -178,3 +204,34 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
        cmd.env = env;
        return run_command(&cmd);
 }
+
+int start_async(struct async *async)
+{
+       int pipe_out[2];
+
+       if (pipe(pipe_out) < 0)
+               return error("cannot create pipe: %s", strerror(errno));
+
+       async->pid = fork();
+       if (async->pid < 0) {
+               error("fork (async) failed: %s", strerror(errno));
+               close_pair(pipe_out);
+               return -1;
+       }
+       if (!async->pid) {
+               close(pipe_out[0]);
+               exit(!!async->proc(pipe_out[1], async->data));
+       }
+       async->out = pipe_out[0];
+       close(pipe_out[1]);
+       return 0;
+}
+
+int finish_async(struct async *async)
+{
+       int ret = 0;
+
+       if (wait_or_whine(async->pid))
+               ret = error("waitpid (async) failed");
+       return ret;
+}
index 7958eb1e0b7a927019460e06d7a01622eddf81df..94e1e9d516887d818f99f8f6d6b3ded3f3be6d6f 100644 (file)
@@ -16,6 +16,7 @@ struct child_process {
        pid_t pid;
        int in;
        int out;
+       int err;
        const char *dir;
        const char *const *env;
        unsigned close_in:1;
@@ -42,4 +43,26 @@ int run_command_v_opt_cd(const char **argv, int opt, const char *dir);
  */
 int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
 
+/*
+ * The purpose of the following functions is to feed a pipe by running
+ * a function asynchronously and providing output that the caller reads.
+ *
+ * It is expected that no synchronization and mutual exclusion between
+ * the caller and the feed function is necessary so that the function
+ * can run in a thread without interfering with the caller.
+ */
+struct async {
+       /*
+        * proc writes to fd and closes it;
+        * returns 0 on success, non-zero on failure
+        */
+       int (*proc)(int fd, void *data);
+       void *data;
+       int out;        /* caller reads from here and closes it */
+       pid_t pid;
+};
+
+int start_async(struct async *async);
+int finish_async(struct async *async);
+
 #endif
index e9b9a39f411b6cfff1c0a4bc3f7e31274c8d2782..5e127a1b7b33bdc9aaafc80873c5c987573566bd 100644 (file)
@@ -178,6 +178,35 @@ static int receive_status(int in)
        return ret;
 }
 
+static void update_tracking_ref(struct remote *remote, struct ref *ref)
+{
+       struct refspec rs;
+       int will_delete_ref;
+
+       rs.src = ref->name;
+       rs.dst = NULL;
+
+       if (!ref->peer_ref)
+               return;
+
+       will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
+
+       if (!will_delete_ref &&
+                       !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1))
+               return;
+
+       if (!remote_find_tracking(remote, &rs)) {
+               fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
+               if (is_null_sha1(ref->peer_ref->new_sha1)) {
+                       if (delete_ref(rs.dst, NULL))
+                               error("Failed to delete");
+               } else
+                       update_ref("update by push", rs.dst,
+                                       ref->new_sha1, NULL, 0, 0);
+               free(rs.dst);
+       }
+}
+
 static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec)
 {
        struct ref *ref;
@@ -306,22 +335,6 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha
                        fprintf(stderr, "\n  from %s\n  to   %s\n",
                                old_hex, new_hex);
                }
-               if (remote && !dry_run) {
-                       struct refspec rs;
-                       rs.src = ref->name;
-                       rs.dst = NULL;
-                       if (!remote_find_tracking(remote, &rs)) {
-                               fprintf(stderr, " Also local %s\n", rs.dst);
-                               if (will_delete_ref) {
-                                       if (delete_ref(rs.dst, NULL)) {
-                                               error("Failed to delete");
-                                       }
-                               } else
-                                       update_ref("update by push", rs.dst,
-                                               ref->new_sha1, NULL, 0, 0);
-                               free(rs.dst);
-                       }
-               }
        }
 
        packet_flush(out);
@@ -334,6 +347,11 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha
                        ret = -4;
        }
 
+       if (!dry_run && remote && ret == 0) {
+               for (ref = remote_refs; ref; ref = ref->next)
+                       update_tracking_ref(remote, ref);
+       }
+
        if (!new_refs && ret == 0)
                fprintf(stderr, "Everything up-to-date\n");
        return ret;
@@ -366,7 +384,7 @@ int main(int argc, char **argv)
        char *dest = NULL;
        char **heads = NULL;
        int fd[2], ret;
-       pid_t pid;
+       struct child_process *conn;
        char *remote_name = NULL;
        struct remote *remote = NULL;
 
@@ -434,12 +452,10 @@ int main(int argc, char **argv)
                }
        }
 
-       pid = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0);
-       if (pid < 0)
-               return 1;
+       conn = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0);
        ret = send_pack(fd[0], fd[1], remote, nr_heads, heads);
        close(fd[0]);
        close(fd[1]);
-       ret |= finish_connect(pid);
+       ret |= finish_connect(conn);
        return !!ret;
 }
diff --git a/shell.c b/shell.c
index cfe372b21379486ea0c7e4d5686c08064c20c279..9826109d5b1b3746aea33dc6b7ebe7b6da19bd22 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -24,17 +24,11 @@ static int do_cvs_cmd(const char *me, char *arg)
        const char *cvsserver_argv[3] = {
                "cvsserver", "server", NULL
        };
-       const char *oldpath = getenv("PATH");
-       struct strbuf newpath = STRBUF_INIT;
 
        if (!arg || strcmp(arg, "server"))
                die("git-cvsserver only handles server: %s", arg);
 
-       strbuf_addstr(&newpath, git_exec_path());
-       strbuf_addch(&newpath, ':');
-       strbuf_addstr(&newpath, oldpath);
-
-       setenv("PATH", strbuf_detach(&newpath, NULL), 1);
+       setup_path(NULL);
 
        return execv_git_cmd(cvsserver_argv);
 }
index 277fa3c10d19ee7997ee5b38c5f77a6cd04576f1..ab8a1e990db39155efc624ca99e85c1cf8eaefa9 100644 (file)
@@ -17,7 +17,7 @@ int recv_sideband(const char *me, int in_stream, int out, int err)
        strcpy(buf, "remote:");
        while (1) {
                int band, len;
-               len     = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX);
+               len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX);
                if (len == 0)
                        break;
                if (len < 1) {
@@ -35,7 +35,22 @@ int recv_sideband(const char *me, int in_stream, int out, int err)
                        return SIDEBAND_REMOTE_ERROR;
                case 2:
                        buf[7] = ' ';
-                       safe_write(err, buf, 8+len);
+                       len += 8;
+                       while (1) {
+                               int brk = 8;
+                               while (brk < len) {
+                                       brk++;
+                                       if (buf[brk-1] == '\n' ||
+                                           buf[brk-1] == '\r')
+                                               break;
+                               }
+                               safe_write(err, buf, brk);
+                               if (brk < len) {
+                                       memmove(buf + 8, buf + brk, len - brk);
+                                       len = len - brk + 8;
+                               } else
+                                       break;
+                       }
                        continue;
                case 1:
                        safe_write(out, buf+8, len);
index a839f4e0744cd9344a3d71a48fe2224a99750729..cb860296edfab2d117fc8b62505df00a51baed02 100755 (executable)
@@ -42,7 +42,12 @@ test_expect_success check '
        git diff --raw --exit-code :test :test.i &&
        id=$(git rev-parse --verify :test) &&
        embedded=$(sed -ne "$script" test.i) &&
-       test "z$id" = "z$embedded"
+       test "z$id" = "z$embedded" &&
+
+       git cat-file blob :test.t > test.r &&
+
+       ./rot13.sh < test.o > test.t &&
+       cmp test.r test.t
 '
 
 # If an expanded ident ever gets into the repository, we want to make sure that
index 4fbd5b1f473578ac5a1ac61a87883015c04fdc63..86f9b5346a0d7d8807d8c37c063690540873813a 100755 (executable)
@@ -254,4 +254,32 @@ test_expect_success 'push with dry-run' '
        check_push_result $old_commit heads/master
 '
 
+test_expect_success 'push updates local refs' '
+
+       rm -rf parent child &&
+       mkdir parent && cd parent && git init &&
+               echo one >foo && git add foo && git commit -m one &&
+       cd .. &&
+       git clone parent child && cd child &&
+               echo two >foo && git commit -a -m two &&
+               git push &&
+       test $(git rev-parse master) = $(git rev-parse remotes/origin/master)
+
+'
+
+test_expect_success 'push does not update local refs on failure' '
+
+       rm -rf parent child &&
+       mkdir parent && cd parent && git init &&
+               echo one >foo && git add foo && git commit -m one &&
+               echo exit 1 >.git/hooks/pre-receive &&
+               chmod +x .git/hooks/pre-receive &&
+       cd .. &&
+       git clone parent child && cd child &&
+               echo two >foo && git commit -a -m two || exit 1
+               git push && exit 1
+       test $(git rev-parse master) != $(git rev-parse remotes/origin/master)
+
+'
+
 test_done
index 03cdba5808aef6fbec2d95f771e6551396ff94cf..53956c08e2754eb68a2a8311b26e16f5ee891308 100755 (executable)
@@ -71,6 +71,63 @@ test_expect_success 'bisect start with one bad and good' '
        git bisect next
 '
 
+# $HASH1 is good, $HASH4 is bad, we skip $HASH3
+# but $HASH2 is bad,
+# so we should find $HASH2 as the first bad commit
+test_expect_success 'bisect skip: successfull result' '
+       git bisect reset &&
+       git bisect start $HASH4 $HASH1 &&
+       git bisect skip &&
+       git bisect bad > my_bisect_log.txt &&
+       grep "$HASH2 is first bad commit" my_bisect_log.txt &&
+       git bisect reset
+'
+
+# $HASH1 is good, $HASH4 is bad, we skip $HASH3 and $HASH2
+# so we should not be able to tell the first bad commit
+# among $HASH2, $HASH3 and $HASH4
+test_expect_success 'bisect skip: cannot tell between 3 commits' '
+       git bisect start $HASH4 $HASH1 &&
+       git bisect skip || return 1
+
+       if git bisect skip > my_bisect_log.txt
+       then
+               echo Oops, should have failed.
+               false
+       else
+               test $? -eq 2 &&
+               grep "first bad commit could be any of" my_bisect_log.txt &&
+               ! grep $HASH1 my_bisect_log.txt &&
+               grep $HASH2 my_bisect_log.txt &&
+               grep $HASH3 my_bisect_log.txt &&
+               grep $HASH4 my_bisect_log.txt &&
+               git bisect reset
+       fi
+'
+
+# $HASH1 is good, $HASH4 is bad, we skip $HASH3
+# but $HASH2 is good,
+# so we should not be able to tell the first bad commit
+# among $HASH3 and $HASH4
+test_expect_success 'bisect skip: cannot tell between 2 commits' '
+       git bisect start $HASH4 $HASH1 &&
+       git bisect skip || return 1
+
+       if git bisect good > my_bisect_log.txt
+       then
+               echo Oops, should have failed.
+               false
+       else
+               test $? -eq 2 &&
+               grep "first bad commit could be any of" my_bisect_log.txt &&
+               ! grep $HASH1 my_bisect_log.txt &&
+               ! grep $HASH2 my_bisect_log.txt &&
+               grep $HASH3 my_bisect_log.txt &&
+               grep $HASH4 my_bisect_log.txt &&
+               git bisect reset
+       fi
+'
+
 # We want to automatically find the commit that
 # introduced "Another" into hello.
 test_expect_success \
@@ -99,6 +156,67 @@ test_expect_success \
      grep "$HASH4 is first bad commit" my_bisect_log.txt &&
      git bisect reset'
 
+# $HASH1 is good, $HASH5 is bad, we skip $HASH3
+# but $HASH4 is good,
+# so we should find $HASH5 as the first bad commit
+HASH5=
+test_expect_success 'bisect skip: add line and then a new test' '
+       add_line_into_file "5: Another new line." hello &&
+       HASH5=$(git rev-parse --verify HEAD) &&
+       git bisect start $HASH5 $HASH1 &&
+       git bisect skip &&
+       git bisect good > my_bisect_log.txt &&
+       grep "$HASH5 is first bad commit" my_bisect_log.txt &&
+       git bisect log > log_to_replay.txt
+       git bisect reset
+'
+
+test_expect_success 'bisect skip and bisect replay' '
+       git bisect replay log_to_replay.txt > my_bisect_log.txt &&
+       grep "$HASH5 is first bad commit" my_bisect_log.txt &&
+       git bisect reset
+'
+
+HASH6=
+test_expect_success 'bisect run & skip: cannot tell between 2' '
+       add_line_into_file "6: Yet a line." hello &&
+       HASH6=$(git rev-parse --verify HEAD) &&
+       echo "#"\!"/bin/sh" > test_script.sh &&
+       echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh &&
+       echo "grep line hello > /dev/null" >> test_script.sh &&
+       echo "test \$? -ne 0" >> test_script.sh &&
+       chmod +x test_script.sh &&
+       git bisect start $HASH6 $HASH1 &&
+       if git bisect run ./test_script.sh > my_bisect_log.txt
+       then
+               echo Oops, should have failed.
+               false
+       else
+               test $? -eq 2 &&
+               grep "first bad commit could be any of" my_bisect_log.txt &&
+               ! grep $HASH3 my_bisect_log.txt &&
+               ! grep $HASH6 my_bisect_log.txt &&
+               grep $HASH4 my_bisect_log.txt &&
+               grep $HASH5 my_bisect_log.txt
+       fi
+'
+
+HASH7=
+test_expect_success 'bisect run & skip: find first bad' '
+       git bisect reset &&
+       add_line_into_file "7: Should be the last line." hello &&
+       HASH7=$(git rev-parse --verify HEAD) &&
+       echo "#"\!"/bin/sh" > test_script.sh &&
+       echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh &&
+       echo "tail -1 hello | grep day > /dev/null && exit 125" >> test_script.sh &&
+       echo "grep Yet hello > /dev/null" >> test_script.sh &&
+       echo "test \$? -ne 0" >> test_script.sh &&
+       chmod +x test_script.sh &&
+       git bisect start $HASH7 $HASH1 &&
+       git bisect run ./test_script.sh > my_bisect_log.txt &&
+       grep "$HASH6 is first bad commit" my_bisect_log.txt
+'
+
 #
 #
 test_done
index 714de6e5753e200d8685ef244a428d099a3ae95d..603a8cd5e7c53569fea3dba66754b52ea1fad9ba 100644 (file)
@@ -60,6 +60,7 @@ esac
 # . ./test-lib.sh
 
 [ "x$TERM" != "xdumb" ] &&
+       [ -t 1 ] &&
        tput bold >/dev/null 2>&1 &&
        tput setaf 1 >/dev/null 2>&1 &&
        tput sgr0 >/dev/null 2>&1 &&
index 400af71c76d8cf699778f98b1b07ea7f719701a3..d44fe7cee7bd8f0e524eff6b516cd2c8b8c919e2 100644 (file)
@@ -107,7 +107,7 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list)
                return;
 
        for (;;) {
-               int cmp, len;
+               int cmp = cmp, len;
 
                if (!fgets(buffer, sizeof(buffer), f)) {
                        fclose(f);
@@ -601,18 +601,13 @@ static struct ref *get_refs_via_connect(const struct transport *transport)
        struct git_transport_data *data = transport->data;
        struct ref *refs;
        int fd[2];
-       pid_t pid;
        char *dest = xstrdup(transport->url);
-
-       pid = git_connect(fd, dest, data->uploadpack, 0);
-
-       if (pid < 0)
-               die("Failed to connect to \"%s\"", transport->url);
+       struct child_process *conn = git_connect(fd, dest, data->uploadpack, 0);
 
        get_remote_heads(fd[0], &refs, 0, NULL, 0);
        packet_flush(fd[1]);
 
-       finish_connect(pid);
+       finish_connect(conn);
 
        free(dest);
 
index ccfeb6e245f32d88170ae51f0367ee15aa950a37..c527d7d049155d1808e0bf69aba42d49cea7b68f 100644 (file)
@@ -297,7 +297,7 @@ static void check_updates(struct cache_entry **src, int nr,
 {
        unsigned short mask = htons(CE_UPDATE);
        unsigned cnt = 0, total = 0;
-       struct progress progress;
+       struct progress *progress = NULL;
        char last_symlink[PATH_MAX];
 
        if (o->update && o->verbose_update) {
@@ -307,8 +307,8 @@ static void check_updates(struct cache_entry **src, int nr,
                                total++;
                }
 
-               start_progress_delay(&progress, "Checking %u files out...",
-                                    "", total, 50, 2);
+               progress = start_progress_delay("Checking out files",
+                                               total, 50, 2);
                cnt = 0;
        }
 
@@ -316,9 +316,8 @@ static void check_updates(struct cache_entry **src, int nr,
        while (nr--) {
                struct cache_entry *ce = *src++;
 
-               if (total)
-                       if (!ce->ce_mode || ce->ce_flags & mask)
-                               display_progress(&progress, ++cnt);
+               if (!ce->ce_mode || ce->ce_flags & mask)
+                       display_progress(progress, ++cnt);
                if (!ce->ce_mode) {
                        if (o->update)
                                unlink_entry(ce->name, last_symlink);
@@ -332,8 +331,7 @@ static void check_updates(struct cache_entry **src, int nr,
                        }
                }
        }
-       if (total)
-               stop_progress(&progress);;
+       stop_progress(&progress);
 }
 
 int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
index fe96ef15c43fa6e3f8f947f84ddce3c498e82859..67994680f2f48e573f78c592c853422e6d8995cf 100644 (file)
@@ -9,6 +9,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
+#include "run-command.h"
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
@@ -96,110 +97,86 @@ static void show_edge(struct commit *commit)
        fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1));
 }
 
+static int do_rev_list(int fd, void *create_full_pack)
+{
+       int i;
+       struct rev_info revs;
+
+       pack_pipe = fdopen(fd, "w");
+       if (create_full_pack)
+               use_thin_pack = 0; /* no point doing it */
+       init_revisions(&revs, NULL);
+       revs.tag_objects = 1;
+       revs.tree_objects = 1;
+       revs.blob_objects = 1;
+       if (use_thin_pack)
+               revs.edge_hint = 1;
+
+       if (create_full_pack) {
+               const char *args[] = {"rev-list", "--all", NULL};
+               setup_revisions(2, args, &revs, NULL);
+       } else {
+               for (i = 0; i < want_obj.nr; i++) {
+                       struct object *o = want_obj.objects[i].item;
+                       /* why??? */
+                       o->flags &= ~UNINTERESTING;
+                       add_pending_object(&revs, o, NULL);
+               }
+               for (i = 0; i < have_obj.nr; i++) {
+                       struct object *o = have_obj.objects[i].item;
+                       o->flags |= UNINTERESTING;
+                       add_pending_object(&revs, o, NULL);
+               }
+               setup_revisions(0, NULL, &revs, NULL);
+       }
+       prepare_revision_walk(&revs);
+       mark_edges_uninteresting(revs.commits, &revs, show_edge);
+       traverse_commit_list(&revs, show_commit, show_object);
+       return 0;
+}
+
 static void create_pack_file(void)
 {
-       /* Pipes between rev-list to pack-objects, pack-objects to us
-        * and pack-objects error stream for progress bar.
-        */
-       int lp_pipe[2], pu_pipe[2], pe_pipe[2];
-       pid_t pid_rev_list, pid_pack_objects;
+       struct async rev_list;
+       struct child_process pack_objects;
        int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr);
        char data[8193], progress[128];
        char abort_msg[] = "aborting due to possible repository "
                "corruption on the remote side.";
        int buffered = -1;
+       const char *argv[10];
+       int arg = 0;
 
-       if (pipe(lp_pipe) < 0)
-               die("git-upload-pack: unable to create pipe");
-       pid_rev_list = fork();
-       if (pid_rev_list < 0)
+       rev_list.proc = do_rev_list;
+       /* .data is just a boolean: any non-NULL value will do */
+       rev_list.data = create_full_pack ? &rev_list : NULL;
+       if (start_async(&rev_list))
                die("git-upload-pack: unable to fork git-rev-list");
 
-       if (!pid_rev_list) {
-               int i;
-               struct rev_info revs;
-
-               close(lp_pipe[0]);
-               pack_pipe = fdopen(lp_pipe[1], "w");
-
-               if (create_full_pack)
-                       use_thin_pack = 0; /* no point doing it */
-               init_revisions(&revs, NULL);
-               revs.tag_objects = 1;
-               revs.tree_objects = 1;
-               revs.blob_objects = 1;
-               if (use_thin_pack)
-                       revs.edge_hint = 1;
-
-               if (create_full_pack) {
-                       const char *args[] = {"rev-list", "--all", NULL};
-                       setup_revisions(2, args, &revs, NULL);
-               } else {
-                       for (i = 0; i < want_obj.nr; i++) {
-                               struct object *o = want_obj.objects[i].item;
-                               /* why??? */
-                               o->flags &= ~UNINTERESTING;
-                               add_pending_object(&revs, o, NULL);
-                       }
-                       for (i = 0; i < have_obj.nr; i++) {
-                               struct object *o = have_obj.objects[i].item;
-                               o->flags |= UNINTERESTING;
-                               add_pending_object(&revs, o, NULL);
-                       }
-                       setup_revisions(0, NULL, &revs, NULL);
-               }
-               prepare_revision_walk(&revs);
-               mark_edges_uninteresting(revs.commits, &revs, show_edge);
-               traverse_commit_list(&revs, show_commit, show_object);
-               exit(0);
-       }
-
-       if (pipe(pu_pipe) < 0)
-               die("git-upload-pack: unable to create pipe");
-       if (pipe(pe_pipe) < 0)
-               die("git-upload-pack: unable to create pipe");
-       pid_pack_objects = fork();
-       if (pid_pack_objects < 0) {
+       argv[arg++] = "pack-objects";
+       argv[arg++] = "--stdout";
+       if (!no_progress)
+               argv[arg++] = "--progress";
+       if (use_ofs_delta)
+               argv[arg++] = "--delta-base-offset";
+       argv[arg++] = NULL;
+
+       memset(&pack_objects, 0, sizeof(pack_objects));
+       pack_objects.in = rev_list.out; /* start_command closes it */
+       pack_objects.out = -1;
+       pack_objects.err = -1;
+       pack_objects.git_cmd = 1;
+       pack_objects.argv = argv;
+
+       if (start_command(&pack_objects)) {
                /* daemon sets things up to ignore TERM */
-               kill(pid_rev_list, SIGKILL);
+               kill(rev_list.pid, SIGKILL);
                die("git-upload-pack: unable to fork git-pack-objects");
        }
-       if (!pid_pack_objects) {
-               const char *argv[10];
-               int i = 0;
-
-               dup2(lp_pipe[0], 0);
-               dup2(pu_pipe[1], 1);
-               dup2(pe_pipe[1], 2);
-
-               close(lp_pipe[0]);
-               close(lp_pipe[1]);
-               close(pu_pipe[0]);
-               close(pu_pipe[1]);
-               close(pe_pipe[0]);
-               close(pe_pipe[1]);
-
-               argv[i++] = "pack-objects";
-               argv[i++] = "--stdout";
-               if (!no_progress)
-                       argv[i++] = "--progress";
-               if (use_ofs_delta)
-                       argv[i++] = "--delta-base-offset";
-               argv[i++] = NULL;
-
-               execv_git_cmd(argv);
-               kill(pid_rev_list, SIGKILL);
-               die("git-upload-pack: unable to exec git-pack-objects");
-       }
-
-       close(lp_pipe[0]);
-       close(lp_pipe[1]);
 
-       /* We read from pe_pipe[0] to capture stderr output for
-        * progress bar, and pu_pipe[0] to capture the pack data.
+       /* We read from pack_objects.err to capture stderr output for
+        * progress bar, and pack_objects.out to capture the pack data.
         */
-       close(pe_pipe[1]);
-       close(pu_pipe[1]);
 
        while (1) {
                const char *who;
@@ -214,14 +191,14 @@ static void create_pack_file(void)
                pollsize = 0;
                pe = pu = -1;
 
-               if (0 <= pu_pipe[0]) {
-                       pfd[pollsize].fd = pu_pipe[0];
+               if (0 <= pack_objects.out) {
+                       pfd[pollsize].fd = pack_objects.out;
                        pfd[pollsize].events = POLLIN;
                        pu = pollsize;
                        pollsize++;
                }
-               if (0 <= pe_pipe[0]) {
-                       pfd[pollsize].fd = pe_pipe[0];
+               if (0 <= pack_objects.err) {
+                       pfd[pollsize].fd = pack_objects.err;
                        pfd[pollsize].events = POLLIN;
                        pe = pollsize;
                        pollsize++;
@@ -254,13 +231,13 @@ static void create_pack_file(void)
                                        *cp++ = buffered;
                                        outsz++;
                                }
-                               sz = xread(pu_pipe[0], cp,
+                               sz = xread(pack_objects.out, cp,
                                          sizeof(data) - outsz);
                                if (0 < sz)
                                                ;
                                else if (sz == 0) {
-                                       close(pu_pipe[0]);
-                                       pu_pipe[0] = -1;
+                                       close(pack_objects.out);
+                                       pack_objects.out = -1;
                                }
                                else
                                        goto fail;
@@ -279,13 +256,13 @@ static void create_pack_file(void)
                                /* Status ready; we ship that in the side-band
                                 * or dump to the standard error.
                                 */
-                               sz = xread(pe_pipe[0], progress,
+                               sz = xread(pack_objects.err, progress,
                                          sizeof(progress));
                                if (0 < sz)
                                        send_client_data(2, progress, sz);
                                else if (sz == 0) {
-                                       close(pe_pipe[0]);
-                                       pe_pipe[0] = -1;
+                                       close(pack_objects.err);
+                                       pack_objects.err = -1;
                                }
                                else
                                        goto fail;
@@ -293,12 +270,12 @@ static void create_pack_file(void)
                }
 
                /* See if the children are still there */
-               if (pid_rev_list || pid_pack_objects) {
+               if (rev_list.pid || pack_objects.pid) {
                        pid = waitpid(-1, &status, WNOHANG);
                        if (!pid)
                                continue;
-                       who = ((pid == pid_rev_list) ? "git-rev-list" :
-                              (pid == pid_pack_objects) ? "git-pack-objects" :
+                       who = ((pid == rev_list.pid) ? "git-rev-list" :
+                              (pid == pack_objects.pid) ? "git-pack-objects" :
                               NULL);
                        if (!who) {
                                if (pid < 0) {
@@ -315,11 +292,11 @@ static void create_pack_file(void)
                                      who);
                                goto fail;
                        }
-                       if (pid == pid_rev_list)
-                               pid_rev_list = 0;
-                       if (pid == pid_pack_objects)
-                               pid_pack_objects = 0;
-                       if (pid_rev_list || pid_pack_objects)
+                       if (pid == rev_list.pid)
+                               rev_list.pid = 0;
+                       if (pid == pack_objects.pid)
+                               pack_objects.pid = 0;
+                       if (rev_list.pid || pack_objects.pid)
                                continue;
                }
 
@@ -340,10 +317,10 @@ static void create_pack_file(void)
                return;
        }
  fail:
-       if (pid_pack_objects)
-               kill(pid_pack_objects, SIGKILL);
-       if (pid_rev_list)
-               kill(pid_rev_list, SIGKILL);
+       if (pack_objects.pid)
+               kill(pack_objects.pid, SIGKILL);
+       if (rev_list.pid)
+               kill(rev_list.pid, SIGKILL);
        send_client_data(3, abort_msg, sizeof(abort_msg));
        die("git-upload-pack: %s", abort_msg);
 }
index 10ce6eedc7e8adbcc3b12e1987d2e16b532a4e07..03b5ec4488cb25bca2f9d96493e874d7c12d3636 100644 (file)
@@ -52,31 +52,34 @@ void wt_status_prepare(struct wt_status *s)
        head = resolve_ref("HEAD", sha1, 0, NULL);
        s->branch = head ? xstrdup(head) : NULL;
        s->reference = "HEAD";
+       s->fp = stdout;
+       s->index_file = get_index_file();
 }
 
-static void wt_status_print_cached_header(const char *reference)
+static void wt_status_print_cached_header(struct wt_status *s)
 {
        const char *c = color(WT_STATUS_HEADER);
-       color_printf_ln(c, "# Changes to be committed:");
-       if (reference) {
-               color_printf_ln(c, "#   (use \"git reset %s <file>...\" to unstage)", reference);
+       color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+       if (s->reference) {
+               color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
        } else {
-               color_printf_ln(c, "#   (use \"git rm --cached <file>...\" to unstage)");
+               color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
        }
-       color_printf_ln(c, "#");
+       color_fprintf_ln(s->fp, c, "#");
 }
 
-static void wt_status_print_header(const char *main, const char *sub)
+static void wt_status_print_header(struct wt_status *s,
+                                  const char *main, const char *sub)
 {
        const char *c = color(WT_STATUS_HEADER);
-       color_printf_ln(c, "# %s:", main);
-       color_printf_ln(c, "#   (%s)", sub);
-       color_printf_ln(c, "#");
+       color_fprintf_ln(s->fp, c, "# %s:", main);
+       color_fprintf_ln(s->fp, c, "#   (%s)", sub);
+       color_fprintf_ln(s->fp, c, "#");
 }
 
-static void wt_status_print_trailer(void)
+static void wt_status_print_trailer(struct wt_status *s)
 {
-       color_printf_ln(color(WT_STATUS_HEADER), "#");
+       color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
 }
 
 static const char *quote_crlf(const char *in, char *buf, size_t sz)
@@ -108,7 +111,8 @@ static const char *quote_crlf(const char *in, char *buf, size_t sz)
        return ret;
 }
 
-static void wt_status_print_filepair(int t, struct diff_filepair *p)
+static void wt_status_print_filepair(struct wt_status *s,
+                                    int t, struct diff_filepair *p)
 {
        const char *c = color(t);
        const char *one, *two;
@@ -117,36 +121,36 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p)
        one = quote_crlf(p->one->path, onebuf, sizeof(onebuf));
        two = quote_crlf(p->two->path, twobuf, sizeof(twobuf));
 
-       color_printf(color(WT_STATUS_HEADER), "#\t");
+       color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
        switch (p->status) {
        case DIFF_STATUS_ADDED:
-               color_printf(c, "new file:   %s", one);
+               color_fprintf(s->fp, c, "new file:   %s", one);
                break;
        case DIFF_STATUS_COPIED:
-               color_printf(c, "copied:     %s -> %s", one, two);
+               color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
                break;
        case DIFF_STATUS_DELETED:
-               color_printf(c, "deleted:    %s", one);
+               color_fprintf(s->fp, c, "deleted:    %s", one);
                break;
        case DIFF_STATUS_MODIFIED:
-               color_printf(c, "modified:   %s", one);
+               color_fprintf(s->fp, c, "modified:   %s", one);
                break;
        case DIFF_STATUS_RENAMED:
-               color_printf(c, "renamed:    %s -> %s", one, two);
+               color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
                break;
        case DIFF_STATUS_TYPE_CHANGED:
-               color_printf(c, "typechange: %s", one);
+               color_fprintf(s->fp, c, "typechange: %s", one);
                break;
        case DIFF_STATUS_UNKNOWN:
-               color_printf(c, "unknown:    %s", one);
+               color_fprintf(s->fp, c, "unknown:    %s", one);
                break;
        case DIFF_STATUS_UNMERGED:
-               color_printf(c, "unmerged:   %s", one);
+               color_fprintf(s->fp, c, "unmerged:   %s", one);
                break;
        default:
                die("bug: unhandled diff status %c", p->status);
        }
-       printf("\n");
+       fprintf(s->fp, "\n");
 }
 
 static void wt_status_print_updated_cb(struct diff_queue_struct *q,
@@ -160,14 +164,14 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q,
                if (q->queue[i]->status == 'U')
                        continue;
                if (!shown_header) {
-                       wt_status_print_cached_header(s->reference);
+                       wt_status_print_cached_header(s);
                        s->commitable = 1;
                        shown_header = 1;
                }
-               wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]);
+               wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]);
        }
        if (shown_header)
-               wt_status_print_trailer();
+               wt_status_print_trailer(s);
 }
 
 static void wt_status_print_changed_cb(struct diff_queue_struct *q,
@@ -184,18 +188,18 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
                                msg = use_add_rm_msg;
                                break;
                        }
-               wt_status_print_header("Changed but not updated", msg);
+               wt_status_print_header(s, "Changed but not updated", msg);
        }
        for (i = 0; i < q->nr; i++)
-               wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
+               wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
        if (q->nr)
-               wt_status_print_trailer();
+               wt_status_print_trailer(s);
 }
 
 static void wt_read_cache(struct wt_status *s)
 {
        discard_cache();
-       read_cache();
+       read_cache_from(s->index_file);
 }
 
 static void wt_status_print_initial(struct wt_status *s)
@@ -206,16 +210,16 @@ static void wt_status_print_initial(struct wt_status *s)
        wt_read_cache(s);
        if (active_nr) {
                s->commitable = 1;
-               wt_status_print_cached_header(NULL);
+               wt_status_print_cached_header(s);
        }
        for (i = 0; i < active_nr; i++) {
-               color_printf(color(WT_STATUS_HEADER), "#\t");
-               color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s",
+               color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+               color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s",
                                quote_crlf(active_cache[i]->name,
                                           buf, sizeof(buf)));
        }
        if (active_nr)
-               wt_status_print_trailer();
+               wt_status_print_trailer(s);
 }
 
 static void wt_status_print_updated(struct wt_status *s)
@@ -282,12 +286,12 @@ static void wt_status_print_untracked(struct wt_status *s)
                }
                if (!shown_header) {
                        s->workdir_untracked = 1;
-                       wt_status_print_header("Untracked files",
+                       wt_status_print_header(s, "Untracked files",
                                               use_add_to_include_msg);
                        shown_header = 1;
                }
-               color_printf(color(WT_STATUS_HEADER), "#\t");
-               color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s",
+               color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+               color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%.*s",
                                ent->len, ent->name);
        }
 }
@@ -317,14 +321,14 @@ void wt_status_print(struct wt_status *s)
                        branch_name = "";
                        on_what = "Not currently on any branch.";
                }
-               color_printf_ln(color(WT_STATUS_HEADER),
+               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),
                        "# %s%s", on_what, branch_name);
        }
 
        if (s->is_initial) {
-               color_printf_ln(color(WT_STATUS_HEADER), "#");
-               color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit");
-               color_printf_ln(color(WT_STATUS_HEADER), "#");
+               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
+               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit");
+               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
                wt_status_print_initial(s);
        }
        else {
@@ -338,7 +342,7 @@ void wt_status_print(struct wt_status *s)
                wt_status_print_verbose(s);
        if (!s->commitable) {
                if (s->amend)
-                       printf("# No changes\n");
+                       fprintf(s->fp, "# No changes\n");
                else if (s->workdir_dirty)
                        printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
                else if (s->workdir_untracked)
index cfea4ae68805d74b825cae6935a4a0dd5de5134d..77449326dbec5c3f83559c300aec2922445829ef 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef STATUS_H
 #define STATUS_H
 
+#include <stdio.h>
+
 enum color_wt_status {
        WT_STATUS_HEADER,
        WT_STATUS_UPDATED,
@@ -19,6 +21,8 @@ struct wt_status {
        int commitable;
        int workdir_dirty;
        int workdir_untracked;
+       const char *index_file;
+       FILE *fp;
 };
 
 int git_status_config(const char *var, const char *value);